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

@@ -51,7 +51,7 @@ def resolve_from_lesson(lesson_id) -> dict:
lesson = (
Lesson.objects
.select_related('node__unit__grade')
.select_related('node__unit__grade', 'node__unit__subject')
.filter(pk=lesson_id)
.first()
)
@@ -67,16 +67,15 @@ def resolve_from_lesson(lesson_id) -> dict:
if not grade_title:
return {}
# عنوانِ Unit همان رشته‌ی دقیقِ فصل است؛ در کاتالوگ دنبالش می‌گردیم تا هم
# subject و هم شماره‌ی فصل دربیاید. برابریِ رشته‌ای عمدی‌ست: فیلترِ Qdrant
# هم روی همین رشته‌ی دقیق است، پس اگر اینجا نخورد آنجا هم نمی‌خورد.
# کتاب از خودِ فصل می‌آید (`Unit.subject`)، نه از حدسِ روی عنوان: یک پایه چند کتاب دارد و
# عنوانِ فصل بینِ کتاب‌ها می‌تواند تکرار شود. عنوانِ Unit همان رشته‌ی دقیقِ فصل است؛ در جدولِ
# فصل‌های همان کتاب دنبالش می‌گردیم تا شماره‌ی فصل دربیاید. برابریِ رشته‌ای عمدی‌ست: فیلترِ
# Qdrant هم روی همین رشته‌ی دقیق است، پس اگر اینجا نخورد آنجا هم نمی‌خورد.
unit_title = (unit.title or '').strip()
for (subject, grade), chapters in MATH_CHAPTERS.items():
if grade != grade_title:
continue
for number, title in chapters.items():
if title == unit_title:
return {'subject': subject, 'gradeTitle': grade_title, 'chapter': number}
subject = unit.subject.title
for number, title in MATH_CHAPTERS.get((subject, grade_title), {}).items():
if title == unit_title:
return {'subject': subject, 'gradeTitle': grade_title, 'chapter': number}
logger.info(
'rag.lesson_context: عنوانِ فصل «%s» (پایه %s) در کاتالوگ نبود',
@@ -90,10 +89,13 @@ def resolve_from_grade(grade_id) -> dict:
{'subject', 'gradeTitle'} برای وقتی که هیچ درسی در کار نیست.
صفحه‌ی «از پی بپرس» از نوارِ پایینِ اپ باز می‌شود، نه از داخلِ یک درس؛
پس نه lessonId دارد نه subject. بدونِ این تابع retrieval کلاً رد می‌شد و
همه‌ی جواب‌های آن صفحه عمومی بودند. هر پایه دقیقاً یک کتابِ ریاضی دارد،
پس پایه‌ی خودِ کاربر برای انتخابِ کتاب کافی‌ست؛ فصل معلوم نیست و جستجو
روی کلِ همان کتاب انجام می‌شود.
پس نه lessonId دارد نه (اگر فرانت نفرستد) subject. بدونِ این تابع retrieval
کلاً رد می‌شد و همه‌ی جواب‌های آن صفحه عمومی بودند.
⚠️ از ۲۰۲۶-۱۰-۱۱ یک پایه چند کتاب دارد (دوازدهم: حسابان، هندسه، گسسته). این تابع فقط
«اولین کتابِ پایه» را برمی‌گرداند (ترتیبِ ثبتِ کتاب‌ها در `TEXTBOOKS`: ریاضی/حسابان پیش از
هندسه…)؛ دانش‌آموزی که در اپ «هندسه» را انتخاب کرده باید `subject` را صریح بفرستد تا از
کتابِ خودش جواب بگیرد. فصل معلوم نیست و جستجو روی کلِ همان کتاب انجام می‌شود.
"""
from .config import resolve_grade_title