"""/api/memory — visitor-memory CRUD (P3, package-local, mounted by app_p3.py). The VisitorMemory singleton is resolved lazily from the Project.Sanad.main shim (app_p3 sets it), so a missing store degrades to 503 rather than crashing. Kept Python-3.8 compatible. """ from __future__ import annotations from typing import Any, Dict, List, Optional from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter() def _mem(): try: from Project.Sanad.main import memory except Exception: memory = None if memory is None: raise HTTPException(503, "visitor memory store unavailable") return memory class ProfileCreate(BaseModel): name: str attributes: Optional[Dict[str, Any]] = None notes: Optional[str] = "" tags: Optional[List[str]] = None linked_face_id: Optional[str] = "" class ProfileUpdate(BaseModel): name: Optional[str] = None attributes: Optional[Dict[str, Any]] = None notes: Optional[str] = None tags: Optional[List[str]] = None linked_face_id: Optional[str] = None @router.get("/") async def list_profiles(): return {"ok": True, "profiles": _mem().list()} @router.post("/") async def create_profile(payload: ProfileCreate): if not (payload.name or "").strip(): raise HTTPException(400, "name is required") prof = _mem().add(payload.name.strip(), attributes=payload.attributes, notes=payload.notes or "", tags=payload.tags, linked_face_id=payload.linked_face_id or "") return {"ok": True, "profile": prof} @router.get("/status") async def status(): return _mem().status() @router.get("/primer") async def primer(): """The compact known-visitors summary fed into personalized greetings.""" return {"ok": True, "primer": _mem().load_for_primer()} @router.get("/by-face/{face_id}") async def by_face(face_id: str): prof = _mem().find_by_face(face_id) if prof is None: raise HTTPException(404, "no visitor linked to face %s" % face_id) return {"ok": True, "profile": prof} @router.get("/{pid}") async def get_profile(pid: str): prof = _mem().get(pid) if prof is None: raise HTTPException(404, "no visitor %s" % pid) return {"ok": True, "profile": prof} @router.put("/{pid}") async def update_profile(pid: str, payload: ProfileUpdate): prof = _mem().update(pid, **payload.dict(exclude_unset=True)) if prof is None: raise HTTPException(404, "no visitor %s" % pid) return {"ok": True, "profile": prof} @router.post("/{pid}/touch") async def touch_profile(pid: str): prof = _mem().touch(pid) if prof is None: raise HTTPException(404, "no visitor %s" % pid) return {"ok": True, "profile": prof} @router.delete("/{pid}") async def delete_profile(pid: str): ok = _mem().delete(pid) if not ok: raise HTTPException(404, "no visitor %s" % pid) return {"ok": True, "deleted": pid}