"""In-memory notification store used when Postgres is not configured. Mirrors MockEventStore's role for BaseEventStore: keeps the feature usable in local dev and in tests without a live database, and matches DOCUMENT_REPOSITORY_BACKEND's existing Mock/Postgres split. """ from __future__ import annotations from datetime import UTC, datetime from app.infrastructure.perception.base_notification_store import BaseNotificationStore class MockNotificationStore(BaseNotificationStore): """Dict-backed notification store. Data does not survive a process restart.""" def __init__(self) -> None: """Start with an empty feed and no read receipts.""" self._notifications: list[dict] = [] self._next_id = 1 # (notification_id, user_id) pairs — presence means read. self._reads: set[tuple[int, str]] = set() def create( self, *, event_id: str, kind: str, title: str, impact_level: str | None, summary: str | None, ) -> None: """Append a notification with an auto-incrementing id.""" self._notifications.append({ "id": self._next_id, "event_id": event_id, "kind": kind, "title": title, "impact_level": impact_level, "summary": summary, "created_at": datetime.now(UTC).isoformat(), }) self._next_id += 1 def list_for_user(self, user_id: str, limit: int = 20) -> list[dict]: """Return the newest `limit` notifications with this user's read state.""" ordered = sorted(self._notifications, key=lambda n: n["id"], reverse=True) return [ {**n, "read": (n["id"], user_id) in self._reads} for n in ordered[:limit] ] def unread_count(self, user_id: str) -> int: """Count notifications this user has not yet read.""" return sum(1 for n in self._notifications if (n["id"], user_id) not in self._reads) def mark_all_read(self, user_id: str) -> int: """Add a read receipt for every currently-unread notification.""" marked = 0 for n in self._notifications: key = (n["id"], user_id) if key not in self._reads: self._reads.add(key) marked += 1 return marked