pdf-quiz-generator/backend/scripts/backfill_section_index.py
Daniel d509d3db52 feat: real organ systems, symptoms grouped by system, and admin taxonomy CRUD
Systems were never systems
The 27 top-level rows were disciplines and care settings — Cardiology,
Emergency Medicine, Neonatology, and a stray condition (Sepsis) — not organ
systems. Cardiology is a discipline; Cardiovascular System is a system. So the
facet was mislabelled, and there was no organ-system axis at all.

Both fixes, as asked:
  * that tree is now the "Topics" facet, which is what it always was;
  * "Systems" is a new flat axis of 16 organ systems, matching how AMBOSS keeps
    Systems flat while nesting Disciplines and Symptoms.

Tags can nest (migration e3f4a5b6c7d8)
`question_tags` gains parent_id and sort_order. A tag may sit under one of the
same kind (Surgery > Hand surgery) or under a system, which is how symptoms are
grouped by where they present. 726 symptoms are now filed under the system they
appear in; the remaining 3,536 stay top-level rather than being forced into an
approximate bucket. A false positive the dry run caught: "vision" was matching
"Health Supervision" — the same trap as erythema/erythematosus earlier, fixed
with a word boundary.

Admin can grow the taxonomy without a migration
POST /tags creates a top-level entry or a child; PATCH renames, reorders and
reparents, refusing a cycle; DELETE reparents children to the deleted tag's
parent rather than orphaning them, and can move its questions elsewhere;
POST /tags/{id}/questions attaches questions. Everything appears in every picker
immediately, because they all read the same endpoint.

Article sections were indexed but empty — `_rebuild_section_index` only runs on
save, so articles written before it existed had no rows. Backfilled: 10 articles,
28 sections, now embedded and searchable. Section-scoped question links already
worked (7 of 34 links name a section).

Tests: 10 new backend covering the tree shape, adding top-level and child
entries, kind rules, duplicate refusal, cycle refusal, rename/reparent, question
attachment, delete-reparents-children, delete-with-move, and the moderator gate.
141 backend, 136 frontend green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017acfNLsJpnkvH3sCZSjMJM
2026-09-10 10:44:39 +02:00

37 lines
1.2 KiB
Python

"""Project existing article sections into the section index.
`_rebuild_section_index` runs when an article is saved, so articles written
before that existed have no rows — the index was empty and section-level
retrieval had nothing to search.
docker compose exec backend python -m scripts.backfill_section_index
docker compose exec backend python -m scripts.backfill_section_index --apply
"""
import sys
from app.database import SessionLocal
from app.models.article import Article
from app.routers.articles import _rebuild_section_index
def main():
apply_changes = "--apply" in sys.argv
db = SessionLocal()
try:
articles = db.query(Article).all()
sections = sum(len(a.sections or []) for a in articles)
print(f" articles : {len(articles)}")
print(f" sections : {sections}")
if apply_changes:
for article in articles:
_rebuild_section_index(db, article)
db.commit()
print(" indexed and embedded.")
else:
print("\n Re-run with --apply to build the index.")
finally:
db.close()
if __name__ == "__main__":
sys.exit(main())