"""Qualify router: decision qualification engine endpoint.""" from __future__ import annotations from dataclasses import asdict from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.engine.qualifier import QualificationConfig, QualificationInput, qualify from app.models.qualification import QualificationProtocol from app.schemas.qualification import ( QualificationProtocolCreate, QualificationProtocolOut, QualifyRequest, QualifyResponse, ) from app.services.auth_service import get_current_identity router = APIRouter() async def _load_config(db: AsyncSession) -> QualificationConfig: """Load the active QualificationProtocol from DB, or fall back to defaults.""" result = await db.execute( select(QualificationProtocol) .where(QualificationProtocol.is_active == True) # noqa: E712 .order_by(QualificationProtocol.created_at.desc()) .limit(1) ) proto = result.scalar_one_or_none() if proto is None: return QualificationConfig() return QualificationConfig( small_group_max=proto.small_group_max, collective_wot_min=proto.collective_wot_min, default_modalities=proto.default_modalities, ) @router.post("/", response_model=QualifyResponse) async def qualify_decision( payload: QualifyRequest, db: AsyncSession = Depends(get_db), ) -> QualifyResponse: """Qualify a decision: determine type, process, and modalities. No authentication required — this is an advisory endpoint that helps users understand which decision pathway fits their situation. """ config = await _load_config(db) inp = QualificationInput( within_mandate=payload.within_mandate, affected_count=payload.affected_count, is_structural=payload.is_structural, context_description=payload.context_description, ) result = qualify(inp, config) return QualifyResponse(**asdict(result)) @router.get("/protocol", response_model=QualificationProtocolOut | None) async def get_active_protocol( db: AsyncSession = Depends(get_db), ) -> QualificationProtocolOut | None: """Return the currently active qualification protocol (thresholds).""" result = await db.execute( select(QualificationProtocol) .where(QualificationProtocol.is_active == True) # noqa: E712 .order_by(QualificationProtocol.created_at.desc()) .limit(1) ) proto = result.scalar_one_or_none() if proto is None: return None return QualificationProtocolOut.model_validate(proto) @router.post("/protocol", response_model=QualificationProtocolOut, status_code=201) async def create_protocol( payload: QualificationProtocolCreate, db: AsyncSession = Depends(get_db), _identity=Depends(get_current_identity), ) -> QualificationProtocolOut: """Create a new qualification protocol (requires auth). Deactivates the current active protocol before saving the new one. """ # Deactivate current current = await db.execute( select(QualificationProtocol).where(QualificationProtocol.is_active == True) # noqa: E712 ) for proto in current.scalars().all(): proto.is_active = False import json proto = QualificationProtocol( name=payload.name, description=payload.description, small_group_max=payload.small_group_max, collective_wot_min=payload.collective_wot_min, default_modalities_json=json.dumps(payload.default_modalities), is_active=True, ) db.add(proto) await db.commit() await db.refresh(proto) return QualificationProtocolOut.model_validate(proto)