import uuid from datetime import date from typing import Sequence from sqlalchemy import select, update as sa_update from sqlalchemy.ext.asyncio import AsyncSession from app.models.subject import Subject from app.models.verification import VerificationProgress from app.schemas.verification import VerificationCreate, VerificationUpdate async def _validate_site_subject(db: AsyncSession, study_id: uuid.UUID, site_id: uuid.UUID, subject_id: uuid.UUID): result = await db.execute(select(Subject).where(Subject.id == subject_id)) subj = result.scalar_one_or_none() if not subj or subj.study_id != study_id: raise ValueError("Subject not found in study") if subj.site_id != site_id: raise ValueError("Site does not match subject") async def upsert_progress( db: AsyncSession, study_id: uuid.UUID, data: VerificationCreate, ) -> VerificationProgress: await _validate_site_subject(db, study_id, data.site_id, data.subject_id) result = await db.execute( select(VerificationProgress).where( VerificationProgress.study_id == study_id, VerificationProgress.subject_id == data.subject_id, VerificationProgress.level == data.level, ) ) existing = result.scalar_one_or_none() if existing: update_data = data.model_dump() await db.execute( sa_update(VerificationProgress) .where(VerificationProgress.id == existing.id) .values(**update_data) ) await db.commit() await db.refresh(existing) return existing vp = VerificationProgress( study_id=study_id, site_id=data.site_id, subject_id=data.subject_id, level=data.level, percent=data.percent, last_verified_at=data.last_verified_at, verifier_id=data.verifier_id, notes=data.notes, ) db.add(vp) await db.commit() await db.refresh(vp) return vp async def get_progress(db: AsyncSession, study_id: uuid.UUID, subject_id: uuid.UUID, level: str) -> VerificationProgress | None: result = await db.execute( select(VerificationProgress).where( VerificationProgress.study_id == study_id, VerificationProgress.subject_id == subject_id, VerificationProgress.level == level, ) ) return result.scalar_one_or_none() async def get_by_id(db: AsyncSession, verification_id: uuid.UUID) -> VerificationProgress | None: result = await db.execute(select(VerificationProgress).where(VerificationProgress.id == verification_id)) return result.scalar_one_or_none() async def list_progress( db: AsyncSession, study_id: uuid.UUID, site_id: uuid.UUID | None = None, subject_id: uuid.UUID | None = None, level: str | None = None, ) -> Sequence[VerificationProgress]: stmt = select(VerificationProgress).where(VerificationProgress.study_id == study_id) if site_id: stmt = stmt.where(VerificationProgress.site_id == site_id) if subject_id: stmt = stmt.where(VerificationProgress.subject_id == subject_id) if level: stmt = stmt.where(VerificationProgress.level == level) result = await db.execute(stmt) return result.scalars().all() async def update_progress(db: AsyncSession, vp: VerificationProgress, data: VerificationUpdate) -> VerificationProgress: update_data = data.model_dump(exclude_unset=True) if update_data: await db.execute( sa_update(VerificationProgress) .where(VerificationProgress.id == vp.id) .values(**update_data) ) await db.commit() await db.refresh(vp) return vp