- pipeline: fetch/extract/normalize/categorize/validate/llm_extract/raw/orchestrator - analytics: dynamics, spreads/arbitrage, seasonality, anomalies, alerts, forecast - dashboard: FastAPI + Plotly (overview/products/product/countries/alerts/quality/sources) - scheduler (APScheduler 07:30), digest (Telegram/SMTP dry-run), demo_data - SQLite (PORTABLE) with idempotent upserts, quarantine, alerts dedup - truthfulness: LLM quote-verified, source link + date on every price - tests: 13 passed, 1 skipped (robots port)
33 lines
1.2 KiB
Python
33 lines
1.2 KiB
Python
"""Raw snapshot storage (thin wrapper around db.save_raw / db.read_raw)."""
|
|
from __future__ import annotations
|
|
from .. import db
|
|
from ..models import RawSnapshot
|
|
|
|
|
|
class RawStore:
|
|
"""Facade over the DB raw-snapshot API. Keeps the orchestrator decoupled from db."""
|
|
def save(self, source_id: str, url: str, content: bytes, content_type: str) -> RawSnapshot:
|
|
return db.save_raw(source_id, url, content, content_type)
|
|
|
|
def load(self, raw_snapshot_id: str) -> RawSnapshot | None:
|
|
data = db.read_raw(raw_snapshot_id)
|
|
if data is None:
|
|
return None
|
|
meta = db.get_raw(raw_snapshot_id)
|
|
if not meta:
|
|
return None
|
|
return RawSnapshot(
|
|
id=meta["id"], source_id=meta.get("source_id") or "", url=meta.get("url") or "",
|
|
fetched_at=__import__("datetime").datetime.fromisoformat(meta["fetched_at"]),
|
|
content_type=meta.get("content_type") or "application/octet-stream",
|
|
size=meta["size"], stored_path=meta["stored_path"],
|
|
)
|
|
|
|
@staticmethod
|
|
def read_bytes(raw_snapshot_id: str) -> bytes | None:
|
|
return db.read_raw(raw_snapshot_id)
|
|
|
|
|
|
__all__ = ["RawStore", "raw_store"]
|
|
raw_store = RawStore()
|