"""GET /api/operator/failure-patterns — failure patterns + safety signals (TASK-08-03, TASK-10-02, D-053). Auth-gated. Returns failure pattern metrics: failure_mode frequency (cells with metric prefix `failure_mode:`) + branch outcome distribution (cells with metric prefix `branch:`) + the assist guardrail block rate safety signal (TASK-10-02 — `assist_guardrail_block_rate`). Weak-spot rubric criteria (mean < 3.0) are highlighted by the frontend. All k-anonymized. The `assist_guardrail_block_rate` is a safety signal for operators: a sudden spike signals either a prompt regression or learners pushing boundaries. High block rate = flag for operator review. """ from __future__ import annotations import datetime as _dt from fastapi import APIRouter, Depends, Request from server.auth.dependencies import current_operator from server.auth.models import Operator from server.operator._common import ( ViewResponse, all_recent_aggregates, group_by_path, require_pg_store, ) router = APIRouter(prefix="/api/operator", tags=["operator-failure-patterns"]) # The assist guardrail block-rate safety signal (TASK-10-02, D-060 layer 3). ASSIST_GUARDRAIL_BLOCK_RATE = "assist_guardrail_block_rate" def _is_failure_metric(metric: str) -> bool: # Failure patterns (v0.4) + the assist guardrail block-rate safety signal (v0.5). return ( metric.startswith("failure_mode:") or metric.startswith("branch:") or metric == ASSIST_GUARDRAIL_BLOCK_RATE ) @router.get("/failure-patterns", response_model=ViewResponse) async def failure_patterns_view( request: Request, op: Operator = Depends(current_operator), ) -> ViewResponse: pg_store = await require_pg_store(request) since = _dt.date.today() - _dt.timedelta(days=30) rows = await all_recent_aggregates(pg_store, since) failure_rows = [r for r in rows if _is_failure_metric(r.get("metric", ""))] views, last_updated = group_by_path(failure_rows) return ViewResponse(views=views, last_updated=last_updated) __all__ = ["router"]