"""kb-postgres access for kb-query beyond what `kb_retrieval.retrieval` covers -- the envelope join (`source` + `entities` for link/metadata building, app/links.py) is out of scope for the shared retrieval package (module 5 phase 4 plan §4: "to jest nowy kod w warstwie HTTP-handlera kb-query, nie zmiana w kb_retrieval").""" from __future__ import annotations import json import asyncpg async def create_pool(dsn: str) -> asyncpg.Pool: return await asyncpg.create_pool(dsn, min_size=1, max_size=5) def _decode_jsonb(value: object) -> object: """asyncpg may return jsonb as a str or an already-decoded object.""" if value is None: return None if isinstance(value, str): return json.loads(value) return value async def fetch_envelopes(conn: asyncpg.Connection, envelope_ids: list[str]) -> dict[str, dict]: """Batch-fetch `source`/`entities` for a set of chunk hits, keyed by envelope id.""" if not envelope_ids: return {} rows = await conn.fetch( "SELECT id, source, entities FROM envelope WHERE id = ANY($1::text[])", envelope_ids, ) return { r["id"]: {"source": r["source"], "entities": _decode_jsonb(r["entities"]) or []} for r in rows } async def fetch_summaries( conn: asyncpg.Connection, envelope_ids: list[str], summary_model: str ) -> dict[str, dict]: """Batch-fetch `document_summary.summary`/`tags` (the haiku compilation track) for the envelopes a search hit, keyed by envelope id -- frontend krok (plan §7): document summary as the per-envelope result header, chunks as its expandable fragments.""" if not envelope_ids: return {} rows = await conn.fetch( "SELECT envelope_id, summary, tags FROM document_summary " "WHERE envelope_id = ANY($1::text[]) AND model = $2", envelope_ids, summary_model, ) return { r["envelope_id"]: {"summary": r["summary"], "tags": _decode_jsonb(r["tags"]) or []} for r in rows }