import uuid from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from api.database import get_session from api.models.decision import Decision, DecisionStatus, DecisionType from api.schemas.decision import DecisionCreate, DecisionRead, DecisionUpdate router = APIRouter(prefix="/decisions", tags=["decisions"]) _FINANCIAL_LEGAL_KEYWORDS = ( "financ", "legal", "payment", "purchas", "contract", "commit", "obligation", "external representation", ) def _needs_escalation(body: DecisionCreate) -> str | None: if body.decision_type != DecisionType.pending: return None text = f"{body.title} {body.description or ''}".lower() for kw in _FINANCIAL_LEGAL_KEYWORDS: if kw in text: return ( "Auto-escalated per constitution ยง4: this pending decision touches " "financial or legal territory and requires explicit human approval before action." ) return None @router.get("/", response_model=list[DecisionRead]) async def list_decisions( topic_id: uuid.UUID | None = None, workstream_id: uuid.UUID | None = None, status: DecisionStatus | None = None, decision_type: DecisionType | None = None, session: AsyncSession = Depends(get_session), ) -> list[Decision]: q = select(Decision) if topic_id: q = q.where(Decision.topic_id == topic_id) if workstream_id: q = q.where(Decision.workstream_id == workstream_id) if status: q = q.where(Decision.status == status) if decision_type: q = q.where(Decision.decision_type == decision_type) q = q.order_by(Decision.created_at) result = await session.execute(q) return list(result.scalars().all()) @router.post("/", response_model=DecisionRead, status_code=status.HTTP_201_CREATED) async def create_decision( body: DecisionCreate, session: AsyncSession = Depends(get_session), ) -> Decision: data = body.model_dump() note = _needs_escalation(body) if note: data["escalation_note"] = note data["status"] = DecisionStatus.escalated decision = Decision(**data) session.add(decision) await session.commit() await session.refresh(decision) return decision @router.get("/{decision_id}", response_model=DecisionRead) async def get_decision( decision_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> Decision: decision = await session.get(Decision, decision_id) if decision is None: raise HTTPException(status_code=404, detail="Decision not found") return decision @router.patch("/{decision_id}", response_model=DecisionRead) async def update_decision( decision_id: uuid.UUID, body: DecisionUpdate, session: AsyncSession = Depends(get_session), ) -> Decision: decision = await session.get(Decision, decision_id) if decision is None: raise HTTPException(status_code=404, detail="Decision not found") for field, value in body.model_dump(exclude_unset=True).items(): setattr(decision, field, value) await session.commit() await session.refresh(decision) return decision @router.delete("/{decision_id}", response_model=DecisionRead) async def supersede_decision( decision_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> Decision: decision = await session.get(Decision, decision_id) if decision is None: raise HTTPException(status_code=404, detail="Decision not found") decision.status = DecisionStatus.superseded await session.commit() await session.refresh(decision) return decision