Add manual study completion toggle
This commit is contained in:
@@ -116,6 +116,10 @@ class AISettingsIn(BaseModel):
|
||||
enabled: bool = True
|
||||
|
||||
|
||||
class StudyCompletionIn(BaseModel):
|
||||
completed: bool = False
|
||||
|
||||
|
||||
class UserIn(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
@@ -296,8 +300,17 @@ def ensure_study_summary_table() -> None:
|
||||
series_count integer NOT NULL DEFAULT 0,
|
||||
annotated_series integer NOT NULL DEFAULT 0,
|
||||
unannotated_series integer NOT NULL DEFAULT 0,
|
||||
completed boolean NOT NULL DEFAULT false,
|
||||
completed_by text,
|
||||
completed_at timestamptz,
|
||||
refreshed_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
ALTER TABLE public.pacs_dicom_study_summaries
|
||||
ADD COLUMN IF NOT EXISTS completed boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE public.pacs_dicom_study_summaries
|
||||
ADD COLUMN IF NOT EXISTS completed_by text;
|
||||
ALTER TABLE public.pacs_dicom_study_summaries
|
||||
ADD COLUMN IF NOT EXISTS completed_at timestamptz;
|
||||
"""
|
||||
pg_scalar(sql)
|
||||
|
||||
@@ -492,7 +505,9 @@ def summary_rows_by_study() -> dict[str, dict[str, Any]]:
|
||||
ensure_study_summary_table()
|
||||
rows = pg_json_rows(
|
||||
"""
|
||||
SELECT ct_number, series_count, annotated_series, unannotated_series, refreshed_at
|
||||
SELECT
|
||||
ct_number, series_count, annotated_series, unannotated_series,
|
||||
completed, completed_by, completed_at, refreshed_at
|
||||
FROM public.pacs_dicom_study_summaries
|
||||
""",
|
||||
timeout=8,
|
||||
@@ -502,6 +517,28 @@ def summary_rows_by_study() -> dict[str, dict[str, Any]]:
|
||||
return {row["ct_number"]: row for row in rows}
|
||||
|
||||
|
||||
def study_completion_fields(ct_number: str) -> dict[str, Any]:
|
||||
try:
|
||||
ensure_study_summary_table()
|
||||
rows = pg_json_rows(
|
||||
f"""
|
||||
SELECT completed, completed_by, completed_at
|
||||
FROM public.pacs_dicom_study_summaries
|
||||
WHERE ct_number = {sql_literal(ct_number)}
|
||||
LIMIT 1
|
||||
""",
|
||||
timeout=8,
|
||||
)
|
||||
except Exception:
|
||||
rows = []
|
||||
row = rows[0] if rows else {}
|
||||
return {
|
||||
"completed": bool(row.get("completed", False)),
|
||||
"completed_by": row.get("completed_by", ""),
|
||||
"completed_at": row.get("completed_at", ""),
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/studies")
|
||||
def studies(_: str = Depends(require_auth), q: str = "", limit: int = 200) -> list[dict[str, Any]]:
|
||||
where = ""
|
||||
@@ -528,6 +565,9 @@ def studies(_: str = Depends(require_auth), q: str = "", limit: int = 200) -> li
|
||||
row["series_count"] = total_series
|
||||
row["annotated_series"] = min(total_series, int((summary or {}).get("annotated_series") if summary else annotation_map.get(row["ct_number"], 0)))
|
||||
row["unannotated_series"] = max(0, total_series - int(row["annotated_series"]))
|
||||
row["completed"] = bool((summary or {}).get("completed", False))
|
||||
row["completed_by"] = (summary or {}).get("completed_by", "")
|
||||
row["completed_at"] = (summary or {}).get("completed_at", "")
|
||||
row["summary_updated_at"] = (summary or {}).get("refreshed_at", "")
|
||||
return rows
|
||||
|
||||
@@ -1035,12 +1075,38 @@ def summary_scheduler_loop() -> None:
|
||||
@app.get("/api/studies/{ct_number}/series")
|
||||
def series(ct_number: str, _: str = Depends(require_auth)) -> dict[str, Any]:
|
||||
data = scan_study(ct_number)
|
||||
return {"study": data["study"], "series": data["series"]}
|
||||
study = {**data["study"], **study_completion_fields(ct_number)}
|
||||
return {"study": study, "series": data["series"]}
|
||||
|
||||
|
||||
@app.get("/api/studies/{ct_number}/summary")
|
||||
def study_summary(ct_number: str, _: str = Depends(require_auth)) -> dict[str, Any]:
|
||||
return study_summary_payload(scan_study(ct_number))
|
||||
return {**study_summary_payload(scan_study(ct_number)), **study_completion_fields(ct_number)}
|
||||
|
||||
|
||||
@app.put("/api/studies/{ct_number}/completion")
|
||||
def update_study_completion(ct_number: str, data: StudyCompletionIn, user: str = Depends(require_auth)) -> dict[str, Any]:
|
||||
get_study_record(ct_number)
|
||||
ensure_study_summary_table()
|
||||
pg_scalar(
|
||||
f"""
|
||||
INSERT INTO public.pacs_dicom_study_summaries (
|
||||
ct_number, completed, completed_by, completed_at, refreshed_at
|
||||
)
|
||||
VALUES (
|
||||
{sql_literal(ct_number)},
|
||||
{'true' if data.completed else 'false'},
|
||||
{sql_literal(user)},
|
||||
{'now()' if data.completed else 'NULL'},
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (ct_number) DO UPDATE SET
|
||||
completed = EXCLUDED.completed,
|
||||
completed_by = EXCLUDED.completed_by,
|
||||
completed_at = EXCLUDED.completed_at
|
||||
"""
|
||||
)
|
||||
return {"ok": True, "ct_number": ct_number, **study_completion_fields(ct_number)}
|
||||
|
||||
|
||||
def get_series_files(ct_number: str, series_uid: str) -> list[Path]:
|
||||
|
||||
Reference in New Issue
Block a user