feat(subjects): add visit drawer and editable screening dates

This commit is contained in:
Cheng Zhou
2026-06-08 11:01:32 +08:00
parent e5ed18c3cd
commit a3f6a04f35
15 changed files with 1515 additions and 525 deletions
+32 -10
View File
@@ -111,6 +111,20 @@ def get_last_planned_visit_window_start_date(visits: Sequence[Visit]) -> date |
return max(window_start_dates) if window_start_dates else None
def derive_visit_status_from_actual_date(
*,
actual_date: date | None,
window_start: date | None,
window_end: date | None,
default_status: str = "PLANNED",
) -> str:
if actual_date is None:
return default_status
if (window_start and actual_date < window_start) or (window_end and actual_date > window_end):
return "OVERDUE"
return "DONE"
def validate_early_termination_date(termination_date: date, visits: Sequence[Visit]) -> None:
last_window_start_date = get_last_planned_visit_window_start_date(visits)
if last_window_start_date is not None and termination_date >= last_window_start_date:
@@ -157,16 +171,24 @@ async def create_visit(
actual_date: date | None = None,
status: str = "PLANNED",
) -> Visit:
resolved_window_start = window_start if window_start is not None else (visit_in.window_start if visit_in else None)
resolved_window_end = window_end if window_end is not None else (visit_in.window_end if visit_in else None)
resolved_actual_date = actual_date if actual_date is not None else (visit_in.actual_date if visit_in else None)
visit = Visit(
study_id=study_id,
subject_id=subject.id,
visit_code=visit_code,
planned_date=planned_date,
actual_date=actual_date,
status=status,
window_start=window_start if window_start is not None else (visit_in.window_start if visit_in else None),
window_end=window_end if window_end is not None else (visit_in.window_end if visit_in else None),
notes=None,
actual_date=resolved_actual_date,
status=derive_visit_status_from_actual_date(
actual_date=resolved_actual_date,
window_start=resolved_window_start,
window_end=resolved_window_end,
default_status=status,
),
window_start=resolved_window_start,
window_end=resolved_window_end,
notes=visit_in.notes if visit_in else None,
)
db.add(visit)
await db.commit()
@@ -240,11 +262,11 @@ async def get_visit(db: AsyncSession, visit_id: uuid.UUID) -> Visit | None:
async def update_visit(db: AsyncSession, visit: Visit, visit_in: VisitUpdate) -> Visit:
update_data = visit_in.model_dump(exclude_unset=True)
if "actual_date" in update_data and update_data["actual_date"] is not None and "status" not in update_data:
actual_date = update_data["actual_date"]
if (visit.window_start and actual_date < visit.window_start) or (visit.window_end and actual_date > visit.window_end):
update_data["status"] = "OVERDUE"
else:
update_data["status"] = "DONE"
update_data["status"] = derive_visit_status_from_actual_date(
actual_date=update_data["actual_date"],
window_start=visit.window_start,
window_end=visit.window_end,
)
if update_data:
await db.execute(
sa_update(Visit)