diagrams for every grade, sectioned prompt, repair of real model slips; fill lesson texts and thin islands

Diagrams: 14 kinds drawn by the app (plane with constructed points, graphs with
computed roots/extrema/tangents, number lines with intervals, solids, charts,
tables, Venn, mapping, trees, fractions, grids, groups, clock). The catalog is
split by topic and only the matching sections go next to a question (200-1073
tokens instead of 1854). parse() repairs slips seen on 96 real book problems:
Persian digits in JSON numbers, 2*pi, // comments, bare items, Venn with
Persian set names. Measured: 45/48 valid on the second round, 0/8 on plain
arithmetic; visual review of 83: 64 right, 3 weak, 16 wrong.

Content: 82 new questions so no island has fewer than four; fill_lesson_texts
writes Unit.blurb (58) and Lesson.example/steps (590) from verified book
problems, never the lesson's own quiz problem. seed_content only fills empty
fields, so admin edits survive a deploy. Tedi unlocks at 500 xp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 23:22:13 +03:30
parent 33d8f43d74
commit 0917ab487f
10 changed files with 7434 additions and 745 deletions

View File

@@ -93,6 +93,8 @@ def parse(raw: bytes) -> list[dict]:
{
'title': _text(lesson.get('title'), 'title', f'{nwhere} › نوع {li + 1}'),
'intro_text': (lesson.get('introText') or lesson.get('intro_text') or ''),
'example': str(lesson.get('example') or ''),
'steps': [str(x) for x in (lesson.get('steps') or []) if str(x).strip()][:12],
}
for li, lesson in enumerate(lessons)
],
@@ -100,6 +102,7 @@ def parse(raw: bytes) -> list[dict]:
clean_units.append({
'title': _text(unit.get('title'), 'title', uwhere),
'blurb': str(unit.get('blurb') or '')[:120],
'order': int(unit.get('order') or ui + 1),
'nodes': clean_nodes,
})
@@ -136,6 +139,10 @@ def import_curriculum(grades: list[dict]) -> dict:
defaults={'order': unit_row['order']},
)
counts['units'] += 1 if created else 0
# متنِ ادمین برنده است: فقط خانه‌ی خالی از فایل پر می‌شود
if unit_row.get('blurb') and not unit.blurb:
unit.blurb = unit_row['blurb']
unit.save(update_fields=['blurb'])
for node_row in unit_row['nodes']:
node, created = Node.objects.get_or_create(
@@ -149,12 +156,24 @@ def import_curriculum(grades: list[dict]) -> dict:
node=node, title=lesson_row['title'],
defaults={'intro_text': lesson_row['intro_text']},
)
changed = []
if created:
counts['lessons'] += 1
elif lesson_row['intro_text'] and lesson.intro_text != lesson_row['intro_text']:
lesson.intro_text = lesson_row['intro_text']
lesson.save(update_fields=['intro_text'])
counts['updated'] += 1
changed.append('intro_text')
# مثال و قدم‌ها فقط وقتی از فایل می‌آیند که در دیتابیس خالی‌اند؛ آنچه
# کسی از پنلِ ادمین نوشته با دیپلوی بازنویسی نمی‌شود.
if lesson_row.get('example') and not lesson.example:
lesson.example = lesson_row['example']
changed.append('example')
if lesson_row.get('steps') and not lesson.steps:
lesson.steps = lesson_row['steps']
changed.append('steps')
if changed:
lesson.save(update_fields=changed)
if not created:
counts['updated'] += 1
return counts
@@ -171,12 +190,17 @@ def export_curriculum(grades_queryset) -> dict:
{
'title': unit.title,
'order': unit.order,
**({'blurb': unit.blurb} if unit.blurb else {}),
'nodes': [
{
'title': node.title,
'order': node.order,
'lessons': [
{'title': lesson.title, 'introText': lesson.intro_text}
{
'title': lesson.title, 'introText': lesson.intro_text,
**({'example': lesson.example} if lesson.example else {}),
**({'steps': lesson.steps} if lesson.steps else {}),
}
for lesson in node.lessons.all()
],
}