f2a12f9fed
v0.4 (Operator Tier — Cohort Dashboard + Auth + Postgres) milestone complete. Phases: ✓ P0 pre-execution (planning) → v0.1.6 ✓ P1 operator foundation (Postgres+auth+VC migration) → v0.1.7 ✓ P2 cohort dashboard + aggregation → v0.1.8 ✓ P3 final review + ship → v0.1.9 (= v0.4 milestone release) Requirements covered (8/8): REQ-MT-01 (Postgres store), REQ-MT-02 (aggregation pipeline), REQ-AUTH-01 (operator auth), REQ-DASH-01 (cohort dashboard), REQ-NFR-AUTH-01 (auth NFRs), REQ-NFR-MT-01 (Postgres-in-LXC), REQ-NFR-DASH-01 (k-anonymity ≥10), REQ-NFR-DASH-02 (freshness ≤24h) Grill MUSTs honored (6/6): G-008, G-011, G-027, G-031, G-038, G-041 Tests: 317 pytest pass, 36 skip (Postgres-requiring), 0 fail; 17/17 vitest pass Review: APPROVE_WITH_NOTES (6/6 personas, 0 P0, 8 P1+ carry-forward) Audit: HEALTHY (reconstruction PASS, 8/8 REQ, 6/6 grill) ---ci--- project: praxis phase: 3 milestone: v0.4 status: complete phase_role: final milestone_complete: true milestone_merged_to_main: true tag: v0.1.9 requirements: covered: [REQ-MT-01, REQ-MT-02, REQ-AUTH-01, REQ-DASH-01, REQ-NFR-AUTH-01, REQ-NFR-MT-01, REQ-NFR-DASH-01, REQ-NFR-DASH-02] partial: [] ---/ci---
44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
"""On-session-end async aggregation hook (TASK-07-02, D-054).
|
|
|
|
Fire-and-forget: designed to be chained as an `asyncio.create_task` after
|
|
the mastery flow. Failures log + the nightly job reconciles (no exception
|
|
propagation to the caller — the session-end response returns immediately).
|
|
|
|
If `pg_store` is None (no Postgres), no-op + log WARNING.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from db.pg_store import PgStore
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
async def on_session_end(pg_store: PgStore | None, session_outcome: dict[str, Any]) -> None:
|
|
"""Aggregate one session outcome. Non-blocking, fire-and-forget (D-054).
|
|
|
|
Failures are logged but never raised — the caller (session_recorder) has
|
|
already returned its response; aggregation is off the voice path. The
|
|
nightly job (nightly.py) reconciles any missed/hook-failed sessions.
|
|
"""
|
|
if pg_store is None:
|
|
log.warning(
|
|
"cohort aggregation skipped (no Postgres) for session %s",
|
|
session_outcome.get("scenario_id"),
|
|
)
|
|
return
|
|
try:
|
|
from server.cohort.aggregator import aggregate_session
|
|
|
|
await aggregate_session(pg_store, session_outcome)
|
|
except Exception:
|
|
log.exception(
|
|
"cohort aggregation hook failed for session %s — nightly job will reconcile",
|
|
session_outcome.get("scenario_id"),
|
|
)
|
|
|
|
|
|
__all__ = ["on_session_end"] |