Rifqi Hafizuddin
[KM-626][AI] Slow-path wiring prep: context + persistence seams, retrieve_documents few-shot
3b27b6b | """AnalysisStore — the seam the slow path persists its AnalysisRecord through. | |
| The Assembler produces an `AnalysisRecord` (the faithful, structured record of a | |
| run — §8.3, INV-4). Persisting it is a separate concern from streaming the answer, | |
| so it sits behind this one-method seam. | |
| `NullAnalysisStore` is the default: it logs that a record was produced but stores | |
| nothing, because the backing table does not exist yet. The plan is to store records | |
| in the **same catalog DB** (Neon `dataeyond`, `settings.postgres_connstring`). | |
| TODO(persistence): add a Postgres-backed `AnalysisStore` writing an | |
| `analysis_records` table in the catalog DB, keyed on | |
| (business_context_id, plan_id, created_at), then inject it into ChatHandler. | |
| """ | |
| from __future__ import annotations | |
| from typing import Protocol, runtime_checkable | |
| from src.middlewares.logging import get_logger | |
| from .schemas import AnalysisRecord | |
| logger = get_logger("analysis_store") | |
| class AnalysisStore(Protocol): | |
| """Persist a completed analysis. Implementations must never raise on the | |
| caller's path — a persistence failure must not break the user's answer.""" | |
| async def save(self, record: AnalysisRecord) -> None: ... | |
| class NullAnalysisStore: | |
| """Default no-op store: logs the record, persists nothing (no table yet).""" | |
| async def save(self, record: AnalysisRecord) -> None: | |
| logger.info( | |
| "analysis_record produced (not persisted — no store configured)", | |
| plan_id=record.plan_id, | |
| business_context_id=record.business_context_id, | |
| n_tasks=len(record.tasks_run), | |
| ) | |