Sample quiz demonstrates option explanations, key points with article links and linked cards. Bank study modal shows per-option explanations and key points. Performance shows main categories with an expandable hierarchy. AMBOSS-style picker polish (chevrons, search box, switch, auto title). Mobile spacing fixes. 97 frontend tests pass.
220 lines
13 KiB
Python
220 lines
13 KiB
Python
"""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 months–5 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 3–5 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 months–5 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 months–5 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 3–5 days in term infants.",
|
||
"article_id": jaundice_article.id if jaundice_article else None,
|
||
"article_section_id": SECTION_JAUNDICE},
|
||
]
|
||
db.commit()
|
||
|
||
# Sample quiz demonstrating the full study feedback (option explanations,
|
||
# key points with article links, linked cards).
|
||
from app.models.quiz import Quiz
|
||
from app.models.quiz_question_link import QuizQuestionLink
|
||
from app.models.course import Course # noqa — Quiz.course_id FK needs the table in metadata.
|
||
sample_quiz = db.query(Quiz).filter(Quiz.title == "Sample — smart links demo").first()
|
||
if not sample_quiz:
|
||
sample_quiz = Quiz(title="Sample — smart links demo", user_id=moderator, mode="learning",
|
||
questions_count=1, is_published=1, origin="sample")
|
||
db.add(sample_quiz)
|
||
db.flush()
|
||
if not db.query(QuizQuestionLink.question_id).filter_by(quiz_id=sample_quiz.id, question_id=sample_q.id).first():
|
||
db.add(QuizQuestionLink(quiz_id=sample_quiz.id, question_id=sample_q.id, position=0))
|
||
db.commit()
|
||
print("Created sample smart-links quiz")
|
||
|
||
# ── 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 months–5 years without CNS infection or metabolic cause. **Sample content** — verify clinically."),
|
||
("Sample: jaundice timeline",
|
||
"Physiologic jaundice appears after 24 h and peaks at 3–5 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)]))
|
||
# Link the first sample card to the sample smart-link question as well.
|
||
if cards and not db.query(FlashcardQuestionLink.id).filter_by(
|
||
flashcard_id=cards[0].id, question_id=sample_q.id).first():
|
||
db.add(FlashcardQuestionLink(flashcard_id=cards[0].id, question_id=sample_q.id))
|
||
print(f"Sample card linked to the sample question")
|
||
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())
|