Coverage for node / src / stigmem_node / routes / cards.py: 94%
25 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"""Memory cards route — spec §20 (Phase 9).
3GET /v1/cards/{entity_uri} Fetch (and optionally force-refresh) the memory card
4 for a specific entity.
5"""
7from __future__ import annotations
9from typing import Annotated
11from fastapi import APIRouter, Depends, HTTPException, Query, status
13from ..auth import Identity, resolve_identity
14from ..card_materializer import get_fresh_card, refresh_card
15from ..db import db
16from ..entity_normalizer import NormalizationError, normalize_entity_uri
17from ..models.cards import MemoryCardResponse
18from ..models.constants import VALID_SCOPES
20router = APIRouter(prefix="/v1/cards", tags=["cards"])
23@router.get("/{entity_uri:path}", response_model=MemoryCardResponse)
24def get_card(
25 entity_uri: str,
26 identity: Annotated[Identity, Depends(resolve_identity)],
27 scope: str = Query("local"),
28 refresh: bool = Query(False, description="Force refresh even if card is fresh"),
29) -> MemoryCardResponse:
30 """Fetch the synthesized memory card for an entity (Spec-X11-Recall-Graph).
32 Returns 404 when the entity has no live facts.
33 """
34 if not identity.can_read(): 34 ↛ 35line 34 didn't jump to line 35 because the condition on line 34 was never true
35 raise HTTPException(
36 status_code=status.HTTP_403_FORBIDDEN,
37 detail="read permission required",
38 )
39 if scope not in VALID_SCOPES:
40 raise HTTPException(
41 status_code=status.HTTP_400_BAD_REQUEST,
42 detail=f"scope must be one of {sorted(VALID_SCOPES)}",
43 )
45 try:
46 entity_uri = normalize_entity_uri(entity_uri)
47 except NormalizationError as exc:
48 raise HTTPException(
49 status_code=status.HTTP_400_BAD_REQUEST,
50 detail=f"invalid_entity_uri: {exc}",
51 ) from exc
53 with db() as conn:
54 card = (
55 refresh_card(entity_uri, scope, identity.tenant_id, conn)
56 if refresh
57 else get_fresh_card(entity_uri, scope, identity.tenant_id, conn)
58 )
60 if card is None:
61 raise HTTPException(
62 status_code=status.HTTP_404_NOT_FOUND,
63 detail="no facts found for entity",
64 )
66 return MemoryCardResponse(
67 entity_uri=card.entity_uri,
68 scope=card.scope,
69 summary=card.summary,
70 fact_hashes=card.fact_hashes,
71 avg_confidence=card.avg_confidence,
72 refreshed_at=card.refreshed_at,
73 is_stale=card.is_stale,
74 has_contradictions=card.has_contradictions,
75 )