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

@@ -48,8 +48,53 @@ class Grade(models.Model):
return self.title
class Subject(models.Model):
"""
کتابِ درسیِ یک پایه: «ریاضی»، «حسابان»، «هندسه»، «آمار و احتمال»، «گسسته».
چرا جدا از Grade: از دهم به بعد یک پایه چند کتابِ ریاضی دارد (دوازدهم: حسابان +
هندسه + گسسته) و دانش‌آموز بینشان جابه‌جا می‌شود. `title` برابرِ `subject`ِ
کتاب در `rag/services/textbooks.py` است (همان رشته‌ای که در نامِ collectionِ
Qdrant می‌آید)؛ ثبت‌نامِ کتابِ تازه یک ردیفِ اینجا هم می‌خواهد.
"""
key = models.SlugField(max_length=30, unique=True)
title = models.CharField(max_length=50, unique=True)
icon = models.CharField(max_length=100, blank=True)
color = models.CharField(max_length=10, blank=True)
# ترتیبِ دکمه‌ها در نوارِ کتاب‌ها؛ کتابِ اولِ هر پایه همان است که بی‌انتخاب باز می‌شود.
order = models.PositiveSmallIntegerField(default=0)
class Meta:
ordering = ['order', 'id']
def __str__(self):
return self.title
DEFAULT_SUBJECT_KEY = 'math'
def default_subject_pk():
"""
کتابِ پیش‌فرضِ یک فصل: «ریاضی» (کتابِ عمومیِ همه‌ی پایه‌ها).
انتخابِ محصولی است، نه «محض احتیاط»: تا پیش از وجودِ Subject همه‌ی فصل‌ها
ریاضی بودند. مسیرهای سیدِ برنامه‌ی درسی کتاب را صریح می‌دهند و فایلِ
ناقص را رد می‌کنند (`curriculum_io.parse`)؛ فقط ساختنِ دستیِ Unit بی‌کتاب
(ادمین، تست) به اینجا می‌رسد. ردیفِ ریاضی را migrationِ ۰۰۱۵ می‌سازد؛
get_or_create فقط دیتابیسی را پوشش می‌دهد که تستِ TransactionTestCase پاکش کرده.
"""
return Subject.objects.get_or_create(
key=DEFAULT_SUBJECT_KEY, defaults={'title': 'ریاضی', 'order': 1},
)[0].pk
class Unit(models.Model):
grade = models.ForeignKey(Grade, on_delete=models.CASCADE, related_name='units')
subject = models.ForeignKey(
Subject, on_delete=models.PROTECT, related_name='units', default=default_subject_pk,
)
title = models.CharField(max_length=100)
# یک جمله‌ی کوتاه زیرِ عنوانِ فصل در صفحه‌ی دسته‌بندیِ درس‌ها.
blurb = models.CharField(max_length=120, blank=True)