Coverage for node / src / stigmem_node / routes / recall / common.py: 96%
41 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-25 01:49 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-25 01:49 +0000
1"""Shared recall route helpers and compatibility utilities."""
3from __future__ import annotations
5import logging
6import sys
7from datetime import UTC, datetime
8from typing import Any
10from fastapi import APIRouter
12from ...auth import Identity
13from ...models.facts import FactRecord, row_to_record
14from ..cid_integrity import enforce_read_path_cid
16logger = logging.getLogger("stigmem.recall")
18router = APIRouter(prefix="/v1/recall", tags=["recall"])
20def _public_module() -> Any:
21 """Return the public recall module so test monkey-patches stay visible."""
22 return sys.modules["stigmem_node.routes.recall"]
24def _now_iso() -> str:
25 return datetime.now(UTC).isoformat()
28def _estimate_tokens(record: FactRecord) -> int:
29 """Rough token estimate: 4 chars ≈ 1 token (GPT tokeniser heuristic)."""
30 text = f"{record.entity} {record.relation} {record.value.v or ''} {record.source}"
31 return max(1, len(text) // 4)
34def _recency_score(timestamp_str: str) -> float:
35 """Normalised recency: 1.0 = now, 0.0 = ≥1 year old."""
36 try:
37 ts = datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
38 if ts.tzinfo is None:
39 ts = ts.replace(tzinfo=UTC)
40 days_old = (datetime.now(UTC) - ts).days
41 return max(0.0, 1.0 - days_old / 365.0)
42 except Exception:
43 return 0.5
46def _fetch_facts_by_ids(
47 conn: Any,
48 fact_ids: list[str],
49) -> dict[str, FactRecord]:
50 """Bulk-fetch facts by ID; returns {id: FactRecord}."""
51 if not fact_ids:
52 return {}
53 placeholders = ",".join("?" * len(fact_ids))
54 rows = conn.execute(
55 f"""
56 SELECT f.*,
57 COALESCE(fvo.valid_until, f.valid_until) AS projected_valid_until,
58 COALESCE(fvo.confidence, f.confidence) AS projected_confidence,
59 COALESCE(fgm.garden_id, f.garden_id) AS projected_garden_id,
60 COALESCE(fqs.quarantine_status, f.quarantine_status)
61 AS projected_quarantine_status,
62 COALESCE(fqs.quarantine_garden_id, f.quarantine_garden_id)
63 AS projected_quarantine_garden_id,
64 (
65 SELECT fca.cid
66 FROM fact_cid_aliases fca
67 WHERE fca.fact_id = f.id
68 ORDER BY fca.cid
69 LIMIT 1
70 ) AS projected_cid
71 FROM facts f
72 LEFT JOIN fact_validity_overrides fvo ON fvo.fact_id = f.id
73 LEFT JOIN fact_garden_membership fgm ON fgm.fact_id = f.id
74 LEFT JOIN fact_quarantine_status fqs ON fqs.fact_id = f.id
75 WHERE f.id IN ({placeholders})
76 """, # noqa: S608 # nosec B608
77 fact_ids,
78 ).fetchall()
79 for row in rows:
80 enforce_read_path_cid(row)
81 return {row["id"]: row_to_record(row) for row in rows}
84def _write_recall_audit(
85 conn: Any,
86 recall_id: str,
87 identity: Identity,
88 query_hash: str,
89 scope: str,
90 token_budget: int,
91 facts_returned: int,
92 tokens_used: int,
93 truncated: bool,
94) -> None:
95 now = _now_iso()
96 try:
97 conn.execute(
98 """
99 INSERT INTO recall_audit_log
100 (id, entity_uri, query_hash, scope, token_budget,
101 facts_returned, tokens_used, truncated, tenant_id, ts)
102 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
103 """,
104 (
105 recall_id,
106 identity.entity_uri,
107 query_hash,
108 scope,
109 token_budget,
110 facts_returned,
111 tokens_used,
112 1 if truncated else 0,
113 identity.tenant_id,
114 now,
115 ),
116 )
117 except Exception as exc:
118 logger.error("Failed to write recall_audit_log: %s", exc)