- 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)
47 lines
1.1 KiB
Python
47 lines
1.1 KiB
Python
"""Simple JSON file cache for LLM outputs. Keyed by sha256; avoids paying twice."""
|
|
from __future__ import annotations
|
|
import json
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from . import config
|
|
|
|
cache_dir: Path | None = None
|
|
|
|
|
|
def _dir() -> Path:
|
|
global cache_dir
|
|
if cache_dir is None:
|
|
cache_dir = config.RAW_DIR / "llm_cache"
|
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
return cache_dir
|
|
|
|
|
|
def llm_cache_get(key: str):
|
|
"""Return cached value or None."""
|
|
p = _dir() / f"{key}.json"
|
|
if not p.exists():
|
|
return None
|
|
try:
|
|
return json.loads(p.read_text("utf-8"))
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def llm_cache_put(key: str, value) -> None:
|
|
p = _dir() / f"{key}.json"
|
|
try:
|
|
p.write_text(json.dumps(value, ensure_ascii=False, default=str), "utf-8")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def clear() -> None:
|
|
global cache_dir
|
|
if cache_dir and cache_dir.exists():
|
|
for f in cache_dir.iterdir():
|
|
try:
|
|
f.unlink()
|
|
except Exception:
|
|
pass
|