Show undetermined study series count

This commit is contained in:
Codex
2026-05-27 21:22:15 +08:00
parent bfa2436327
commit e788f08037
2 changed files with 51 additions and 3 deletions

View File

@@ -313,6 +313,7 @@ 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,
undetermined_series integer NOT NULL DEFAULT 0,
body_parts jsonb NOT NULL DEFAULT '[]'::jsonb,
completed boolean NOT NULL DEFAULT false,
completed_by text,
@@ -322,6 +323,8 @@ def ensure_study_summary_table() -> None:
);
ALTER TABLE public.pacs_dicom_study_summaries
ADD COLUMN IF NOT EXISTS body_parts jsonb NOT NULL DEFAULT '[]'::jsonb;
ALTER TABLE public.pacs_dicom_study_summaries
ADD COLUMN IF NOT EXISTS undetermined_series integer NOT NULL DEFAULT 0;
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
@@ -511,6 +514,19 @@ def annotation_overview_by_study() -> dict[str, dict[str, Any]]:
OR NULLIF(a.notes, '') IS NOT NULL
OR a.ai_result IS NOT NULL
)::int AS annotated_series,
count(DISTINCT a.series_instance_uid) FILTER (
WHERE a.skipped IS NOT TRUE
AND (
(
a.body_parts ? 'upper_abdomen'
AND COALESCE(NULLIF(a.upper_abdomen_phase, ''), 'unknown') = 'unknown'
)
OR (
a.body_parts ? 'chest'
AND COALESCE(NULLIF(a.chest_window, ''), 'unknown') = 'unknown'
)
)
)::int AS undetermined_series,
COALESCE(
jsonb_agg(DISTINCT part.value) FILTER (WHERE part.value IS NOT NULL),
'[]'::jsonb
@@ -534,7 +550,7 @@ def summary_rows_by_study() -> dict[str, dict[str, Any]]:
"""
SELECT
ct_number, series_count, annotated_series, unannotated_series,
body_parts, completed, completed_by, completed_at, completion_updated_at, refreshed_at
undetermined_series, body_parts, completed, completed_by, completed_at, completion_updated_at, refreshed_at
FROM public.pacs_dicom_study_summaries
""",
timeout=8,
@@ -594,6 +610,8 @@ 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_summary.get("annotated_series", 0)))
row["unannotated_series"] = max(0, total_series - int(row["annotated_series"]))
undetermined_source = annotation_summary.get("undetermined_series", (summary or {}).get("undetermined_series", 0))
row["undetermined_series"] = min(total_series, int(undetermined_source or 0))
row["body_parts"] = valid_parts((summary or {}).get("body_parts") or annotation_summary.get("body_parts") or [])
row["completed"] = bool((summary or {}).get("completed", False))
row["completed_by"] = (summary or {}).get("completed_by", "")
@@ -1122,11 +1140,27 @@ def study_summary_payload(data: dict[str, Any]) -> dict[str, Any]:
or row.get("annotation", {}).get("notes")
or row.get("annotation", {}).get("ai_model")
)
undetermined = sum(
1
for row in series_list
if not row.get("annotation", {}).get("skipped")
and (
(
"upper_abdomen" in valid_parts(row.get("annotation", {}).get("body_parts") or [])
and row.get("annotation", {}).get("upper_abdomen_phase") == "unknown"
)
or (
"chest" in valid_parts(row.get("annotation", {}).get("body_parts") or [])
and row.get("annotation", {}).get("chest_window") == "unknown"
)
)
)
return {
"ct_number": data.get("study", {}).get("ct_number", ""),
"series_count": total,
"annotated_series": annotated,
"unannotated_series": max(0, total - annotated),
"undetermined_series": undetermined,
"body_parts": body_parts,
"summary_updated_at": time.strftime("%Y-%m-%d %H:%M:%S"),
}
@@ -1138,13 +1172,14 @@ def save_study_summary(summary: dict[str, Any]) -> None:
pg_scalar(
f"""
INSERT INTO public.pacs_dicom_study_summaries (
ct_number, series_count, annotated_series, unannotated_series, body_parts, refreshed_at
ct_number, series_count, annotated_series, unannotated_series, undetermined_series, body_parts, refreshed_at
)
VALUES (
{sql_literal(summary.get('ct_number', ''))},
{int(summary.get('series_count') or 0)},
{int(summary.get('annotated_series') or 0)},
{int(summary.get('unannotated_series') or 0)},
{int(summary.get('undetermined_series') or 0)},
{sql_literal(json.dumps(valid_parts(summary.get('body_parts') or []), ensure_ascii=False))}::jsonb,
now()
)
@@ -1152,6 +1187,7 @@ def save_study_summary(summary: dict[str, Any]) -> None:
series_count = EXCLUDED.series_count,
annotated_series = EXCLUDED.annotated_series,
unannotated_series = EXCLUDED.unannotated_series,
undetermined_series = EXCLUDED.undetermined_series,
body_parts = EXCLUDED.body_parts,
refreshed_at = now()
""",