pdf-quiz-generator/backend/scripts/seed_sample_content.py
Daniel 91ec6a501c feat: key points smart links, difficulty tags, adaptive sessions, educator-only question management
Key points on questions link into article sections (AMBOSS-style) with samples; difficulty tagging with builder/bank filters; adaptive session algorithm prefers unanswered questions then recycles older incorrect ones, weakest categories first with damping; question create/edit is now admin/educator only; expired exams no longer auto-submit on resume; exam suspend messaging updated. Migrations k4f5a6b7c819, l5a6b7c8d920, m6a7b8c9d031. 63 backend and 97 frontend tests pass.
2026-09-09 02:26:45 +02:00

199 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Seed clearly-marked sample articles, cards and lab deep links for demonstration.
Every created row is labeled as sample content and links existing bank questions,
cards and cited lab references so the linking features are visible end to end.
Idempotent: existing sample rows are left untouched.
"""
import sys
from app.database import SessionLocal
from app.models.article import Article, QuestionArticleLink
from app.models.flashcard import Flashcard, FlashcardDeck, FlashcardArticleLink, FlashcardQuestionLink
from app.models.lab_reference import LabReference, LabReferenceCardLink
from app.models.question import Question
from app.models.user import User
SAMPLE_NOTE = "Sample content for demonstration — replace with educator-reviewed material."
SECTION_JAUNDICE = "000000000000000000000000000000a1"
SECTION_JAUNDICE_BILI = "000000000000000000000000000000a2"
SECTION_SEIZURE = "000000000000000000000000000000b1"
SECTION_SEIZURE_LABS = "000000000000000000000000000000b2"
def main():
db = SessionLocal()
try:
moderator = db.query(User.id).filter(User.role.in_(["admin", "moderator"])).order_by(User.id).first()
moderator = moderator[0] if moderator else None
# ── Sample articles ────────────────────────────────────────────
articles = []
for slug, title, summary, content, sections, category_hint in [
("sample-febrile-seizures", "Febrile seizures — sample", SAMPLE_NOTE,
"This **sample article** demonstrates article sections, question links and card links.",
[
{"id": SECTION_SEIZURE, "slug": "definition", "title": "Definition and evaluation",
"content": "A febrile seizure is a seizure associated with fever in a child 6 months5 years without CNS infection or metabolic cause.\n\n**Sample text** — verify against your local guidance."},
{"id": SECTION_SEIZURE_LABS, "slug": "reference-values", "title": "Reference values",
"content": "Linked laboratory reference values appear in the quiz lab panel and deep-link back to this section.\n\n**Sample text** — verify against your local guidance."},
], None),
("sample-neonatal-jaundice", "Neonatal jaundice — sample", SAMPLE_NOTE,
"This **sample article** demonstrates deep links from lab references into a specific section.",
[
{"id": SECTION_JAUNDICE, "slug": "overview", "title": "Overview",
"content": "Physiologic jaundice appears after 24 hours of life and peaks at 35 days in term infants.\n\n**Sample text** — verify against your local guidance."},
{"id": SECTION_JAUNDICE_BILI, "slug": "bilirubin-reference-values", "title": "Bilirubin reference values",
"content": "Total bilirubin reference values used in the lab panel link to this section.\n\n**Sample text** — verify against your local guidance."},
], None),
]:
if not db.query(Article.id).filter(Article.slug == slug).first():
article = Article(slug=slug, title=title, summary=summary, content=content,
sections=sections, user_id=moderator, status="published")
db.add(article)
db.flush()
articles.append(article)
print(f"Created sample article: {title}")
db.commit()
# ── Link bank questions ─────────────────────────────────────────
from app.services.quiz_builder import shareable_question_predicate
bank = db.query(Question.id).filter(
shareable_question_predicate(),
Question.source_quiz_id.is_(None),
).order_by(Question.id).limit(6).all()
bank_ids = [row[0] for row in bank]
by_slug = {a.slug: a for a in db.query(Article).filter(Article.slug.in_(
["sample-febrile-seizures", "sample-neonatal-jaundice"])).all()}
pairs = [
("sample-febrile-seizures", None, 0), ("sample-febrile-seizures", SECTION_SEIZURE, 1),
("sample-febrile-seizures", SECTION_SEIZURE_LABS, 2),
("sample-neonatal-jaundice", None, 3), ("sample-neonatal-jaundice", SECTION_JAUNDICE, 4),
("sample-neonatal-jaundice", SECTION_JAUNDICE_BILI, 5),
]
for slug, section_id, bank_index in pairs:
article = by_slug.get(slug)
if not article or not bank_ids:
continue
question_id = bank_ids[bank_index % len(bank_ids)]
if not db.query(QuestionArticleLink.id).filter_by(
question_id=question_id, article_id=article.id, section_id=section_id).first():
db.add(QuestionArticleLink(question_id=question_id, article_id=article.id,
section_id=section_id, user_id=moderator))
print(f"Linked question {question_id} -> {slug} {section_id or 'whole article'}")
db.commit()
# Sample question demonstrating key points that smart-link to the sample articles.
sample_q = db.query(Question).filter(Question.question_text.like("Sample: febrile seizure definition%")).first()
if not sample_q:
sample_q = Question(
question_text="Sample: febrile seizure definition — which feature defines a SIMPLE febrile seizure?",
question_type="mcq",
options=["Generalized, lasts under 15 minutes, once in 24 hours",
"Focal seizure lasting 25 minutes",
"Two seizures within 24 hours",
"Seizure with CSF pleocytosis"],
correct_answer="Generalized, lasts under 15 minutes, once in 24 hours",
explanation="**Sample content — verify clinically.** A simple febrile seizure is generalized, brief (<15 min), "
"and does not recur within 24 hours, in a child 6 months5 years with fever and no CNS infection.",
option_explanations={
"Generalized, lasts under 15 minutes, once in 24 hours": "This matches the definition of a simple febrile seizure.",
"Focal seizure lasting 25 minutes": "Focal or prolonged seizures are complex febrile seizures.",
"Two seizures within 24 hours": "Recurrence within 24 hours makes the seizure complex.",
"Seizure with CSF pleocytosis": "CSF pleocytosis suggests CNS infection, not a febrile seizure.",
},
user_id=moderator, is_shared=1,
)
db.add(sample_q)
db.flush()
print("Created sample smart-link question")
seizure_article = by_slug.get("sample-febrile-seizures")
jaundice_article = by_slug.get("sample-neonatal-jaundice")
sample_q.key_points = [
{"text": "A febrile seizure is a seizure with fever in a child 6 months5 years without CNS infection.",
"article_id": seizure_article.id if seizure_article else None,
"article_section_id": SECTION_SEIZURE},
{"text": "Physiologic jaundice appears after 24 hours and peaks at 35 days in term infants.",
"article_id": jaundice_article.id if jaundice_article else None,
"article_section_id": SECTION_JAUNDICE},
]
db.commit()
# ── Sample deck and cards ───────────────────────────────────────
deck = db.query(FlashcardDeck).filter_by(title="Sample cards", user_id=moderator).first()
if not deck:
deck = FlashcardDeck(title="Sample cards", user_id=moderator, card_count=0, is_shared=1)
db.add(deck)
db.flush()
sample_cards = [
("Sample: febrile seizure definition",
"Seizure with fever in a child 6 months5 years without CNS infection or metabolic cause. **Sample content** — verify clinically."),
("Sample: jaundice timeline",
"Physiologic jaundice appears after 24 h and peaks at 35 days in term infants. **Sample content** — verify clinically."),
]
created_cards = []
for front, back in sample_cards:
card = db.query(Flashcard).filter_by(deck_id=deck.id, front=front).first()
if not card:
card = Flashcard(deck_id=deck.id, front=front, back=back)
db.add(card)
db.flush()
created_cards.append(card)
if created_cards:
deck.card_count = db.query(Flashcard).filter(Flashcard.deck_id == deck.id).count()
db.commit()
print(f"Sample deck cards: {len(created_cards)}")
cards = db.query(Flashcard).filter_by(deck_id=deck.id).order_by(Flashcard.id).all()
seizure_article = by_slug.get("sample-febrile-seizures")
jaundice_article = by_slug.get("sample-neonatal-jaundice")
if cards and seizure_article and bank_ids:
card = cards[0]
if not db.query(FlashcardArticleLink.id).filter_by(
flashcard_id=card.id, article_id=seizure_article.id, article_section_id=SECTION_SEIZURE).first():
db.add(FlashcardArticleLink(flashcard_id=card.id, article_id=seizure_article.id,
article_section_id=SECTION_SEIZURE))
if not db.query(FlashcardQuestionLink.id).filter_by(flashcard_id=card.id, question_id=bank_ids[0]).first():
db.add(FlashcardQuestionLink(flashcard_id=card.id, question_id=bank_ids[0]))
print(f"Card {card.id} linked to question {bank_ids[0]}")
if len(cards) > 1 and jaundice_article:
card = cards[1]
if not db.query(FlashcardArticleLink.id).filter_by(
flashcard_id=card.id, article_id=jaundice_article.id, article_section_id=SECTION_JAUNDICE_BILI).first():
db.add(FlashcardArticleLink(flashcard_id=card.id, article_id=jaundice_article.id,
article_section_id=SECTION_JAUNDICE_BILI))
if bank_ids and not db.query(FlashcardQuestionLink.id).filter_by(
flashcard_id=card.id, question_id=bank_ids[1 % len(bank_ids)]).first():
db.add(FlashcardQuestionLink(flashcard_id=card.id, question_id=bank_ids[1 % len(bank_ids)]))
db.commit()
# ── Deep-link lab references to the sample sections ─────────────
lab_targets = [
("Bilirubin (total)", SECTION_JAUNDICE_BILI, "sample-neonatal-jaundice"),
("CSF white cell count", SECTION_SEIZURE_LABS, "sample-febrile-seizures"),
("CSF protein", SECTION_SEIZURE_LABS, "sample-febrile-seizures"),
]
for name, section_id, slug in lab_targets:
article = by_slug.get(slug)
if not article:
continue
for entry in db.query(LabReference).filter(LabReference.name == name).all():
if entry.article_id is None:
entry.article_id = article.id
entry.article_section_id = section_id
print(f"Lab '{name}' deep-links to {slug}#{section_id[:8]}")
# Link a sample card to one lab reference.
lab = db.query(LabReference).filter(LabReference.name == "CSF white cell count").first()
if lab and cards:
if not db.query(LabReferenceCardLink.id).filter_by(
lab_reference_id=lab.id, flashcard_id=cards[0].id).first():
db.add(LabReferenceCardLink(lab_reference_id=lab.id, flashcard_id=cards[0].id))
print(f"Lab '{lab.name}' linked to sample card")
db.commit()
print("Sample content ready.")
finally:
db.close()
if __name__ == "__main__":
sys.exit(main())