diff --git a/backend/app/tasks/quiz_tasks.py b/backend/app/tasks/quiz_tasks.py index afb6c80..4ea6888 100644 --- a/backend/app/tasks/quiz_tasks.py +++ b/backend/app/tasks/quiz_tasks.py @@ -395,16 +395,30 @@ FIGURE_AUDIT_PROMPT = """This figure is attached to the exam question below. Dec to it. It belongs if a candidate would need it, or could reasonably use it, to answer -*this* question — a radiograph of the finding, a growth chart the question asks -you to read, a table of the schedule being asked about. It does not belong if it -is about some other topic entirely, which happens when a figure was lifted from -the wrong page of a source document. +*this* question. It does not belong if it is about some other topic entirely, +which happens when a figure is lifted from the wrong page of a source document. -Be strict about "no" and generous about "unsure": detaching a figure a question -needs is worse than leaving a stray one attached for somebody to notice. +**The rule that matters, learned by getting it wrong.** Two kinds of figure sit +on these questions and they are not judged the same way. + +A *clinical* figure — a photograph, radiograph, ultrasound, ECG, fundoscopy, +otoscopy, a microscopy slide, a specimen — is almost always the right one, and +the connection is often indirect: a tick on a leaf against a July fever, fungal +hyphae against a scaly rash, a recessed chin in the notes of a two-week-old. +Answer "no" for one of these ONLY when it is anatomically or clinically +impossible — a shoulder radiograph on a knee injury. Otherwise "yes", or +"unsure" if you truly cannot tell. Detaching a clinical figure a question needs +is the worst outcome here. + +A *non-clinical* figure — a table, a citation, a reference list, a bar chart, a +nomogram, a page of text — belongs only when it is about the same subject as the +question. These are where mis-extraction shows up, and where "no" is usually +right. Answer ONLY with JSON: -{"belongs": "yes" | "no" | "unsure", "shows": "one sentence describing the figure"} +{"belongs": "yes" | "no" | "unsure", + "kind": "clinical" | "table" | "other", + "shows": "one sentence describing the figure"} QUESTION """ @@ -486,8 +500,17 @@ def audit_question_figures(self, job_id: str = "", limit: int | None = None, continue belongs = str(verdict.get("belongs", "unsure")).strip().lower() + kind = str(verdict.get("kind", "")).strip().lower() shows = str(verdict.get("shows", "")).strip()[:600] + # The prompt says it and this enforces it: a clinical figure is + # never detached on the model's say-so alone. Sixteen good ones went + # in the first run — a tick on a leaf, fungal hyphae, an ECG on a + # tachypnoeic neonate — because a judgement about relevance was + # allowed to act on a photograph. It becomes a flag for a person. + if belongs == "no" and kind == "clinical": + belongs = "unsure" + # The description is worth keeping whatever the verdict: "Figure # from question #1206" is a filename with extra steps. asset = db.query(MediaAsset).filter(MediaAsset.path == question.image_path).first() @@ -501,7 +524,8 @@ def audit_question_figures(self, job_id: str = "", limit: int | None = None, if belongs == "no": mismatches.append({"question_id": question.id, "path": question.image_path, - "shows": shows, "stem": stem[:160], "verdict": "no"}) + "shows": shows, "stem": stem[:160], "kind": kind, + "verdict": "no"}) if detach: question.image_path = None db.query(QuestionMedia).filter( @@ -518,7 +542,8 @@ def audit_question_figures(self, job_id: str = "", limit: int | None = None, # Named, not just counted: "2 unsure" is a number nobody can # act on. These stay attached and go on the list for a person. mismatches.append({"question_id": question.id, "path": question.image_path, - "shows": shows, "stem": stem[:160], "verdict": "unsure"}) + "shows": shows, "stem": stem[:160], "kind": kind, + "verdict": "unsure"}) else: kept += 1