ec6fcc64bc
Phase 2 complete — Cohort Dashboard + Aggregation: - Cohort aggregation pipeline (k-anon ≥10 write-time suppression, async hook, nightly 03:00 CT reconcile) - 4 auth-gated operator API endpoints (cohort, mastery, failure-patterns, credentials) - React cohort dashboard (BrowserRouter, login, 3 views, inline SVG sparklines, auth gate) - SPA fallback via SpaStaticFiles subclass (G-041 — NOT catch-all route) - G-038 differencing-attack test (unit + API e2e) - 317 pytest pass, 36 skip, 0 fail; 17/17 vitest pass; npm build + typecheck clean ---ci--- project: praxis phase: 2 milestone: v0.4 status: complete requirements: covered: [REQ-DASH-01, REQ-NFR-DASH-01, REQ-NFR-DASH-02, REQ-MT-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"] |