from fastapi import APIRouter, HTTPException from sqlalchemy import func, select from sqlalchemy.dialects.postgresql import insert from sqlalchemy.orm import selectinload from app.db import SessionDep from app.models import Variant, VariantDecision from app.schemas import CandidateOut, DecisionIn, DecisionOut, VariantDetailOut from app.services import candidates as case_view from app.services import triage router = APIRouter() @router.get("/{variant_id}", response_model=VariantDetailOut) async def get_variant(variant_id: int, session: SessionDep) -> VariantDetailOut: """Every piece of evidence for one variant, including the raw VEP record.""" variant = await session.scalar( select(Variant) .where(Variant.id == variant_id) .options( selectinload(Variant.prediction), selectinload(Variant.decision), selectinload(Variant.job), ) ) if variant is None: raise HTTPException(404, "variant not found") case = await case_view.get_case(session, variant.job.case_id) terms = [p.hpo_id for p in case.phenotypes] if case else [] ontology = await case_view.ontology_for( session, {variant.gene} if variant.gene else set(), terms ) evidence = triage.Evidence( frequencies=variant.job.has_frequencies, effect_scores=variant.job.has_effect_scores ) # evaluate, not rank: the panel must work for a variant that did not make the candidate list. scored = triage.evaluate(variant, terms, ontology, evidence) labels = {p.hpo_id: p.label for p in case.phenotypes} if case else {} base = CandidateOut.from_candidate(scored, labels).model_dump() return VariantDetailOut(**base, annotations=variant.annotations or {}) @router.post("/{variant_id}/decision", response_model=DecisionOut) async def decide(variant_id: int, payload: DecisionIn, session: SessionDep) -> DecisionOut: if await session.get(Variant, variant_id) is None: raise HTTPException(404, "variant not found") stmt = insert(VariantDecision).values( variant_id=variant_id, state=payload.state, reason=payload.reason, note=payload.note ) # Changing your mind replaces the call rather than failing on the unique constraint. await session.execute( stmt.on_conflict_do_update( index_elements=[VariantDecision.variant_id], set_={ "state": stmt.excluded.state, "reason": stmt.excluded.reason, "note": stmt.excluded.note, "decided_at": func.now(), }, ) ) await session.commit() decision = await session.scalar( select(VariantDecision).where(VariantDecision.variant_id == variant_id) ) return DecisionOut.model_validate(decision)