""" از یک `lessonId` به مقصدِ retrieval در Qdrant. چرا لازم است: تا پیش از این، برای اینکه پاسخِ دستیار به کتاب استناد کند، اپ باید `subject` + `grade` («نهم») + `lessonNumber` (شماره‌ی فصل) را خودش می‌فرستاد — یعنی فرانت باید از جزئیاتِ داخلیِ نام‌گذاریِ collectionهای Qdrant خبر می‌داشت. اگر نمی‌فرستاد (که حالتِ طبیعی است: اپ فقط می‌داند دانش‌آموز روی کدام درس است)، retrieval کلاً رد می‌شد و مدل بدونِ پشتوانه‌ی کتاب جواب می‌داد — بی‌هیچ خطایی، فقط جوابِ ضعیف‌تر. حالا که Grade/Unit از روی ساختارِ خودِ کتاب ساخته می‌شوند (`seed_curriculum_from_textbook`)، همین زنجیره کافی‌ست: Lesson → Node → Unit(عنوان = «فصل ۴: توان و ریشه») → Grade(عنوان = «پایه نهم») پس اپ فقط `lessonId` می‌فرستد و بقیه اینجا درمی‌آید. مقادیرِ صریحِ درخواست همیشه اولویت دارند؛ این فقط جای خالی را پر می‌کند. """ import logging import re from .known_collections import MATH_CHAPTERS logger = logging.getLogger(__name__) # «پایه نهم» / «پایهٔ نهم» / «نهم دبستان» → «نهم» _GRADE_PREFIX = re.compile(r'^\s*پایه[‌ٔ\s]*') def normalize_grade_title(title: str) -> str: """عنوانِ Grade در دیتابیس → همان رشته‌ای که در نامِ collection می‌آید.""" return _GRADE_PREFIX.sub('', (title or '').strip()).strip() def resolve_from_lesson(lesson_id) -> dict: """ {'subject', 'gradeTitle', 'chapter'} — هر کدام که پیدا شود. هیچ‌وقت استثنا نمی‌دهد: نبودنِ درس یا نخوردنِ عنوانِ فصل با کاتالوگ، یعنی «نتوانستیم حدس بزنیم»، نه خطا — همان مسیرِ قبلی (پاسخِ عمومی) ادامه می‌یابد. """ if lesson_id in (None, ''): return {} try: lesson_id = int(lesson_id) except (TypeError, ValueError): return {} try: from content.models import Lesson lesson = ( Lesson.objects .select_related('node__unit__grade') .filter(pk=lesson_id) .first() ) except Exception: # noqa: BLE001 — اپ content ممکن است در دسترس نباشد logger.exception('rag.lesson_context: خواندنِ درس %s شکست خورد', lesson_id) return {} if lesson is None: return {} unit = lesson.node.unit grade_title = normalize_grade_title(unit.grade.title) if not grade_title: return {} # عنوانِ Unit همان رشته‌ی دقیقِ فصل است؛ در کاتالوگ دنبالش می‌گردیم تا هم # subject و هم شماره‌ی فصل دربیاید. برابریِ رشته‌ای عمدی‌ست: فیلترِ 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} logger.info( 'rag.lesson_context: عنوانِ فصل «%s» (پایه %s) در کاتالوگ نبود', unit_title, grade_title, ) return {'gradeTitle': grade_title}