Subjects per grade (math, calculus, geometry, stats, discrete), 5 new textbooks, 202 new islands, long islands split, hand-written figures and lesson texts for the new books

- content.Subject + Unit.subject, /api/home?subject=, tutor grounded in the chosen book
- 5 new books ingested; ~1150 hand-written questions, islands named by hand
- node_split: islands over 7 lessons split into parts of <=6 (lesson ids and progress kept)
- Lesson.figures hand-authored and visually checked for 653 lessons of the new books;
  gen_lesson_figures got --subject and survives network errors
- seed files re-exported

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 05:35:18 +03:30
parent 3d51b3b83a
commit 8cb1ecb85e
32 changed files with 81218 additions and 4486 deletions

View File

@@ -102,6 +102,8 @@ def parse(raw: bytes) -> list[dict]:
})
clean_units.append({
# کتاب صریح است: فایلِ بی‌کتاب رد می‌شود تا فصلِ حسابان بی‌صدا «ریاضی» نشود.
'subject': _text(unit.get('subject'), 'subject', uwhere),
'title': _text(unit.get('title'), 'title', uwhere),
'blurb': str(unit.get('blurb') or '')[:120],
'order': int(unit.get('order') or ui + 1),
@@ -117,6 +119,16 @@ def parse(raw: bytes) -> list[dict]:
return cleaned
def _subject_or_error(title: str):
"""کتابِ فایل باید از قبل در جدولِ Subject باشد (ادمین ← کتاب‌ها)؛ نامِ ناشناخته خطا است، نه ساختنِ بی‌صدا."""
from .models import Subject
subject = Subject.objects.filter(title=title).first()
if subject is None:
known = '، '.join(Subject.objects.values_list('title', flat=True))
raise CurriculumError(f'کتابِ «{title}» در جدولِ کتاب‌ها نیست. موجود: {known}')
return subject
@transaction.atomic
def import_curriculum(grades: list[dict]) -> dict:
"""
@@ -135,9 +147,10 @@ def import_curriculum(grades: list[dict]) -> dict:
counts['grades'] += 1 if created else 0
for unit_row in grade_row['units']:
subject = _subject_or_error(unit_row['subject'])
unit, created = Unit.objects.get_or_create(
grade=grade, title=unit_row['title'],
defaults={'order': unit_row['order']},
defaults={'order': unit_row['order'], 'subject': subject},
)
counts['units'] += 1 if created else 0
# متنِ ادمین برنده است: فقط خانه‌ی خالی از فایل پر می‌شود
@@ -185,13 +198,14 @@ def import_curriculum(grades: list[dict]) -> dict:
def export_curriculum(grades_queryset) -> dict:
"""خروجی دقیقاً همان قالبی‌ست که import می‌خواند."""
out = []
for grade in grades_queryset.prefetch_related('units__nodes__lessons'):
for grade in grades_queryset.prefetch_related('units__subject', 'units__nodes__lessons'):
out.append({
'title': grade.title,
'icon': grade.icon,
'color': grade.color,
'units': [
{
'subject': unit.subject.title,
'title': unit.title,
'order': unit.order,
**({'blurb': unit.blurb} if unit.blurb else {}),