**Missing views.** The note said 10 articles without a clinical view and 13 without a short one; the real numbers were 8 and 11, and the eight originals turned out to carry no `variant` key at all, so everything in them was being read as the long view. 33 sections written across 11 articles, every sentence traceable to that article's own body, and the legacy sections stamped with the variant they were always being treated as. No doses appear anywhere, because not one of those eight states one. Every article now has a high-yield view and `mdm_pass check` reads 330 of 331 conforming with no placeholder sections. **Pediatric Dentistry** was neither an educator's job nor a delete. Its body is real — scope, disparities, records and consent, public health, biomaterials — and only its clinical view was the disclaimer. The three placeholder sections are gone and the article stays: the reader hides a view an article does not have, and a discipline overview about consent and public health has no bedside path to write. Inventing one was the thing not to do. **The 15 tied conditions were stale**, ticked with the evidence rather than worked: the taxonomy rebuild gave every one of them a single home, and the duplicate-name query now returns nothing. **The 31 tied questions were 3**, and reading all three showed none was a real tie. The best of them: a question about acute flaccid paralysis after minor trauma was filed between "Nursemaid's Elbow" and "Radial Head Subluxation" — two names for one condition, neither anything to do with the question. It is under Spinal Cord Injury now, cross-filed to Acute Flaccid Paralysis, and the two elbow links are removed rather than cross-filed: a wrong link surfaces a question under a topic it does not belong to, which is the opposite of what cross-filing is for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
143 lines
6.3 KiB
Python
143 lines
6.3 KiB
Python
"""Give the three questions whose topics tied a primary, by reading them.
|
|
|
|
`promote_primary_categories` promotes a question from its system to a condition
|
|
when one candidate is the most specific. Where two candidates sit at the same
|
|
depth and carry the same number of questions there is nothing in the data to
|
|
separate them, so it leaves them alone and reports the count. That count was 31
|
|
before today's taxonomy rebuild and is 3 after it.
|
|
|
|
Three is small enough to read. Doing so shows that none of them is the even
|
|
contest the counts imply:
|
|
|
|
* Q1201 — a nonsurvivable head injury where the apnea test cannot be performed
|
|
and the next step is an ancillary cerebral blood flow scan. Brain Death is
|
|
what the question teaches; Intracranial Hemorrhage is how the child got there.
|
|
* Q853 — painless, large-volume rectal bleeding in a well 8-year-old whose
|
|
answer is heterotopic gastric mucosa. Gastrointestinal Bleeding is the
|
|
presentation; Juvenile Polyp is one of the wrong options, and stays as a
|
|
cross-link because the differential the explanation walks through includes it.
|
|
* Q826 — acute flaccid paralysis of both arms after minor trauma, answered by
|
|
MRI of the spine. Neither Nursemaid's Elbow nor Radial Head Subluxation has
|
|
anything to do with it; they are two names for one condition and the
|
|
classifier reached both from "wasn't moving his arms" in a toddler. Those two
|
|
links are removed rather than cross-filed, because a wrong link makes the
|
|
question surface under a topic it does not belong to.
|
|
|
|
Whatever a question moves off stays reachable: the system it was filed under
|
|
becomes a `question_category_links` row, and so does the second topic wherever
|
|
that topic is genuinely about the question.
|
|
|
|
Idempotent, and a dry run by default:
|
|
|
|
docker compose exec backend python -m scripts.settle_tied_primaries
|
|
docker compose exec backend python -m scripts.settle_tied_primaries --apply
|
|
"""
|
|
import sys
|
|
|
|
from sqlalchemy import text as sa_text
|
|
|
|
from app.database import SessionLocal
|
|
|
|
#: One decision per question. `system` is the primary it must still be filed
|
|
#: under for the decision to apply, `primary` the topic it moves to, `cross` the
|
|
#: topics kept as links, and `drop` the links that are simply wrong. Names are
|
|
#: carried beside every id so a renumbered taxonomy fails the guard instead of
|
|
#: silently filing a question under whatever now holds that id.
|
|
DECISIONS = [
|
|
{
|
|
"question": 1201,
|
|
"system": (14876, "Critical Care"),
|
|
"primary": (15297, "Brain Death"),
|
|
"cross": [(15438, "Intracranial Hemorrhage")],
|
|
"drop": [],
|
|
},
|
|
{
|
|
"question": 853,
|
|
"system": (14881, "Gastroenterology & Nutrition"),
|
|
"primary": (15526, "Gastrointestinal Bleeding"),
|
|
"cross": [(15891, "Juvenile Polyp")],
|
|
"drop": [],
|
|
},
|
|
{
|
|
"question": 826,
|
|
"system": (14879, "Emergency Medicine"),
|
|
"primary": (15606, "Spinal Cord Injury"),
|
|
"cross": [(15460, "Acute Flaccid Paralysis")],
|
|
"drop": [(15697, "Nursemaid's Elbow"), (15699, "Radial Head Subluxation")],
|
|
},
|
|
]
|
|
|
|
|
|
def category_name(db, category_id: int) -> str | None:
|
|
return db.execute(sa_text("SELECT name FROM question_categories WHERE id = :c"),
|
|
{"c": category_id}).scalar()
|
|
|
|
|
|
def main(apply: bool) -> int:
|
|
db = SessionLocal()
|
|
try:
|
|
planned, skipped = [], []
|
|
for decision in DECISIONS:
|
|
qid = decision["question"]
|
|
current = db.execute(sa_text(
|
|
"SELECT question_category_id FROM questions WHERE id = :q"), {"q": qid}).scalar()
|
|
if current is None:
|
|
skipped.append((qid, "no such question, or nothing filed"))
|
|
continue
|
|
if current == decision["primary"][0]:
|
|
skipped.append((qid, "already settled"))
|
|
continue
|
|
if current != decision["system"][0]:
|
|
skipped.append((qid, f"filed under {category_name(db, current)}, "
|
|
f"not {decision['system'][1]}"))
|
|
continue
|
|
wrong_name = next(
|
|
(f"{cid} is now {category_name(db, cid)!r}, expected {name!r}"
|
|
for cid, name in [decision["primary"], decision["system"],
|
|
*decision["cross"], *decision["drop"]]
|
|
if category_name(db, cid) != name), None)
|
|
if wrong_name:
|
|
skipped.append((qid, f"taxonomy has moved: {wrong_name}"))
|
|
continue
|
|
planned.append(decision)
|
|
|
|
for decision in planned:
|
|
qid = decision["question"]
|
|
print(f" Q{qid}: {decision['system'][1]} -> {decision['primary'][1]}")
|
|
print(f" cross-filed : {', '.join(n for _c, n in decision['cross'])}, "
|
|
f"{decision['system'][1]}")
|
|
if decision["drop"]:
|
|
print(f" links removed: {', '.join(n for _c, n in decision['drop'])}")
|
|
for qid, why in skipped:
|
|
print(f" Q{qid} skipped — {why}")
|
|
|
|
print(f"\n{len(planned)} questions to settle, {len(skipped)} left alone")
|
|
if not apply:
|
|
print("\ndry run. Pass --apply to write.")
|
|
return 0
|
|
|
|
for decision in planned:
|
|
qid = decision["question"]
|
|
db.execute(sa_text("UPDATE questions SET question_category_id = :c WHERE id = :q"),
|
|
{"c": decision["primary"][0], "q": qid})
|
|
for category_id, _name in [decision["system"], *decision["cross"]]:
|
|
db.execute(sa_text("""
|
|
INSERT INTO question_category_links (question_id, category_id)
|
|
SELECT :q, :c WHERE NOT EXISTS (
|
|
SELECT 1 FROM question_category_links
|
|
WHERE question_id = :q AND category_id = :c)
|
|
"""), {"q": qid, "c": category_id})
|
|
for category_id, _name in decision["drop"]:
|
|
db.execute(sa_text("""
|
|
DELETE FROM question_category_links
|
|
WHERE question_id = :q AND category_id = :c
|
|
"""), {"q": qid, "c": category_id})
|
|
db.commit()
|
|
print(f"\nsettled. {len(planned)} questions updated.")
|
|
return 0
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main("--apply" in sys.argv))
|