import re from fastapi import APIRouter, BackgroundTasks, HTTPException from pydantic import BaseModel from app.schemas.annotation import StudentAnnotation from app.services.annotation_service import get_annotation_service from app.services.project_service import ProjectService router = APIRouter(prefix="/annotations", tags=["annotations"]) _svc = get_annotation_service() def _require_project_document(project_id: str, document_id: str) -> None: if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,127}", project_id): raise HTTPException(404, "Project document not found") if not re.fullmatch(r"[0-9a-f]{64}", document_id): raise HTTPException(404, "Project document not found") project = ProjectService().load(project_id) if project is None or not any(item.file_id == document_id for item in project.files): raise HTTPException(404, "Project document not found") class PatchNoteRequest(BaseModel): note_text: str def _refresh_annotation_evidence(project_id: str, document_id: str) -> None: try: from app.rag.evidence_ingestion import EvidenceIngestionService EvidenceIngestionService().refresh_annotations(project_id, document_id) except KeyError: pass @router.get("/stt-status") def check_stt_status(): from app.services.voice_transcription_service import VoiceTranscriptionService stt = VoiceTranscriptionService.get() return { "available": stt.is_available, "is_loading": stt.is_model_loading() } @router.get("/{document_id}") def list_annotations(document_id: str, project_id: str): _require_project_document(project_id, document_id) return [ annotation.model_dump() for annotation in _svc.get_for_project_document(project_id, document_id) ] @router.post("") def create_annotation(annotation: StudentAnnotation, background_tasks: BackgroundTasks): _require_project_document(annotation.project_id, annotation.document_id) created = _svc.create(annotation) background_tasks.add_task( _refresh_annotation_evidence, created.project_id, created.document_id, ) return created.model_dump() @router.patch("/{annotation_id}") def patch_annotation(annotation_id: str, req: PatchNoteRequest, background_tasks: BackgroundTasks): updated = _svc.patch_note(annotation_id, req.note_text) if not updated: raise HTTPException(404, f"Annotation {annotation_id} not found") background_tasks.add_task( _refresh_annotation_evidence, updated.project_id, updated.document_id, ) return updated.model_dump() @router.delete("/{annotation_id}") def delete_annotation(annotation_id: str, background_tasks: BackgroundTasks): existing = _svc.get(annotation_id) ok = _svc.delete(annotation_id) if not ok: raise HTTPException(404, f"Annotation {annotation_id} not found") if existing: background_tasks.add_task( _refresh_annotation_evidence, existing.project_id, existing.document_id, ) return {"status": "deleted"}