import uuid from datetime import date, datetime, timezone from typing import Sequence from sqlalchemy import select, update as sa_update from sqlalchemy.ext.asyncio import AsyncSession from app.models.data_query import DataQuery from app.models.site import Site from app.models.subject import Subject from app.schemas.data_query import DataQueryCreate, DataQueryUpdate async def _validate_site_subject(db: AsyncSession, study_id: uuid.UUID, site_id: uuid.UUID | None, subject_id: uuid.UUID | None): if site_id: result = await db.execute(select(Site).where(Site.id == site_id)) site = result.scalar_one_or_none() if not site or site.study_id != study_id: raise ValueError("Site not found in study") subject_site = None if subject_id: 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") subject_site = subj.site_id if site_id and subject_site and site_id != subject_site: raise ValueError("Site and subject mismatch") async def create_query( db: AsyncSession, study_id: uuid.UUID, query_in: DataQueryCreate, *, created_by: uuid.UUID, ) -> DataQuery: await _validate_site_subject(db, study_id, query_in.site_id, query_in.subject_id) dq = DataQuery( study_id=study_id, site_id=query_in.site_id, subject_id=query_in.subject_id, visit_id=query_in.visit_id, title=query_in.title, description=query_in.description, category=query_in.category, priority=query_in.priority, assigned_to=query_in.assigned_to, due_date=query_in.due_date, status="OPEN", resolution=None, closed_at=None, created_by=created_by, ) db.add(dq) await db.commit() await db.refresh(dq) return dq async def get_query(db: AsyncSession, query_id: uuid.UUID) -> DataQuery | None: result = await db.execute(select(DataQuery).where(DataQuery.id == query_id)) return result.scalar_one_or_none() async def list_queries( db: AsyncSession, study_id: uuid.UUID, *, status: str | None = None, site_id: uuid.UUID | None = None, subject_id: uuid.UUID | None = None, assigned_to: uuid.UUID | None = None, overdue: bool | None = None, category: str | None = None, priority: str | None = None, skip: int = 0, limit: int = 100, ) -> Sequence[DataQuery]: stmt = select(DataQuery).where(DataQuery.study_id == study_id) if status: stmt = stmt.where(DataQuery.status == status) if site_id: stmt = stmt.where(DataQuery.site_id == site_id) if subject_id: stmt = stmt.where(DataQuery.subject_id == subject_id) if assigned_to: stmt = stmt.where(DataQuery.assigned_to == assigned_to) if category: stmt = stmt.where(DataQuery.category == category) if priority: stmt = stmt.where(DataQuery.priority == priority) if overdue is True: stmt = stmt.where(DataQuery.due_date < date.today(), DataQuery.status != "CLOSED") if overdue is False: stmt = stmt.where((DataQuery.due_date >= date.today()) | (DataQuery.due_date.is_(None)) | (DataQuery.status == "CLOSED")) stmt = stmt.offset(skip).limit(limit) result = await db.execute(stmt) return result.scalars().all() async def update_query(db: AsyncSession, dq: DataQuery, dq_in: DataQueryUpdate) -> DataQuery: update_data = dq_in.model_dump(exclude_unset=True) if "status" in update_data: if update_data["status"] == "CLOSED": update_data["closed_at"] = datetime.now(timezone.utc) else: update_data["closed_at"] = None if update_data: await db.execute( sa_update(DataQuery) .where(DataQuery.id == dq.id) .values(**update_data) ) await db.commit() await db.refresh(dq) return dq