"""Shared in-process cache for parsed uploads. Decisions: - We don't persist uploads to disk — they're held in memory keyed by content sha256. This mirrors the original SPA's localStorage model: the user re-uploads after a server restart. - Each upload kind gets its own TTLCache (size-bounded, time-bounded). Timelog stays small (8 entries, no explicit TTL — bounded LRU is fine). Deliverable and ProjectSummary uploads use a 1h TTL to match the brief. - All entries are stored as the raw dict the parser returned ({"rows", "unrecognised_columns", "content_hash"}). """ from __future__ import annotations from collections import OrderedDict from typing import Any from cachetools import TTLCache # Bounded LRU — keeps the most recent N timelog uploads for the session. _MAX_TIMELOG = 8 _timelog_store: "OrderedDict[str, dict[str, Any]]" = OrderedDict() # 1h TTL caches for deliverable + projectsummary, max 16 each. _TTL_SECONDS = 60 * 60 _deliverable_store: TTLCache = TTLCache(maxsize=16, ttl=_TTL_SECONDS) _project_summary_store: TTLCache = TTLCache(maxsize=16, ttl=_TTL_SECONDS) def _put_lru(store: "OrderedDict[str, dict[str, Any]]", parsed: dict[str, Any], max_size: int) -> None: h = parsed["content_hash"] store[h] = parsed store.move_to_end(h) while len(store) > max_size: store.popitem(last=False) # ---- Timelog ---------------------------------------------------------- def remember_timelog(parsed: dict[str, Any]) -> None: _put_lru(_timelog_store, parsed, _MAX_TIMELOG) def get_timelog(content_hash: str) -> dict[str, Any] | None: return _timelog_store.get(content_hash) # ---- Deliverable ------------------------------------------------------ def remember_deliverable(parsed: dict[str, Any]) -> None: _deliverable_store[parsed["content_hash"]] = parsed def get_deliverable(content_hash: str) -> dict[str, Any] | None: try: return _deliverable_store[content_hash] except KeyError: return None # ---- Project Summary -------------------------------------------------- def remember_project_summary(parsed: dict[str, Any]) -> None: _project_summary_store[parsed["content_hash"]] = parsed def get_project_summary(content_hash: str) -> dict[str, Any] | None: try: return _project_summary_store[content_hash] except KeyError: return None def clear_all() -> None: """Test helper — wipes every store.""" _timelog_store.clear() _deliverable_store.clear() _project_summary_store.clear()