feat: derive subject status from visits
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
from datetime import date
|
||||
from types import SimpleNamespace
|
||||
|
||||
from app.crud.subject import should_generate_visits_after_subject_update
|
||||
from app.crud.subject import derive_subject_status, should_generate_visits_after_subject_update
|
||||
from app.crud.visit import (
|
||||
build_early_termination_visit_changes,
|
||||
build_visit_schedule_dates,
|
||||
@@ -227,3 +227,51 @@ def test_validate_early_termination_date_requires_date_before_last_visit_window_
|
||||
assert "提前终止日期必须早于方案最后一个计划访视窗口开始日" in str(exc)
|
||||
else:
|
||||
raise AssertionError("same-day final visit window start should not be accepted as early termination")
|
||||
|
||||
|
||||
def test_derive_subject_status_marks_dropped_when_early_termination_has_actual_date():
|
||||
subject = SimpleNamespace(screening_date=date(2026, 5, 1), enrollment_date=date(2026, 5, 3))
|
||||
visits = [
|
||||
SimpleNamespace(visit_code="V1", planned_date=date(2026, 5, 10), actual_date=date(2026, 5, 10), status="DONE", created_at=None),
|
||||
SimpleNamespace(visit_code="提前终止", planned_date=None, actual_date=date(2026, 5, 12), status="DONE", created_at=None),
|
||||
]
|
||||
|
||||
assert derive_subject_status(subject, visits, [{"visit_code": "V1"}]) == "DROPPED"
|
||||
|
||||
|
||||
def test_derive_subject_status_marks_completed_when_last_standard_visit_has_actual_date():
|
||||
subject = SimpleNamespace(screening_date=date(2026, 5, 1), enrollment_date=date(2026, 5, 3))
|
||||
visits = [
|
||||
SimpleNamespace(visit_code="V1", planned_date=date(2026, 5, 10), actual_date=date(2026, 5, 10), status="DONE", created_at=None),
|
||||
SimpleNamespace(visit_code="V2", planned_date=date(2026, 5, 17), actual_date=date(2026, 5, 17), status="DONE", created_at=None),
|
||||
]
|
||||
|
||||
assert derive_subject_status(subject, visits, [{"visit_code": "V1"}, {"visit_code": "V2"}]) == "COMPLETED"
|
||||
|
||||
|
||||
def test_derive_subject_status_rolls_back_to_enrolled_when_followup_after_standard_end_is_unfinished():
|
||||
subject = SimpleNamespace(screening_date=date(2026, 5, 1), enrollment_date=date(2026, 5, 3))
|
||||
visits = [
|
||||
SimpleNamespace(visit_code="V1", planned_date=date(2026, 5, 10), actual_date=date(2026, 5, 10), status="DONE", created_at=None),
|
||||
SimpleNamespace(visit_code="V2", planned_date=date(2026, 5, 17), actual_date=date(2026, 5, 17), status="DONE", created_at=None),
|
||||
SimpleNamespace(visit_code="安全性随访", planned_date=date(2026, 5, 24), actual_date=None, status="PLANNED", created_at=None),
|
||||
]
|
||||
|
||||
assert derive_subject_status(subject, visits, [{"visit_code": "V1"}, {"visit_code": "V2"}]) == "ENROLLED"
|
||||
|
||||
|
||||
def test_derive_subject_status_uses_completed_standard_visit_when_extra_visit_is_before_standard_end():
|
||||
subject = SimpleNamespace(screening_date=date(2026, 5, 1), enrollment_date=date(2026, 5, 3))
|
||||
visits = [
|
||||
SimpleNamespace(visit_code="V1", planned_date=date(2026, 5, 10), actual_date=date(2026, 5, 10), status="DONE", created_at=None),
|
||||
SimpleNamespace(visit_code="安全性随访", planned_date=date(2026, 5, 12), actual_date=None, status="PLANNED", created_at=None),
|
||||
SimpleNamespace(visit_code="V2", planned_date=date(2026, 5, 17), actual_date=date(2026, 5, 17), status="DONE", created_at=None),
|
||||
]
|
||||
|
||||
assert derive_subject_status(subject, visits, [{"visit_code": "V1"}, {"visit_code": "V2"}]) == "COMPLETED"
|
||||
|
||||
|
||||
def test_derive_subject_status_marks_screening_until_enrollment_date_is_filled():
|
||||
subject = SimpleNamespace(screening_date=date(2026, 5, 1), enrollment_date=None)
|
||||
|
||||
assert derive_subject_status(subject, [], []) == "SCREENING"
|
||||
|
||||
Reference in New Issue
Block a user