Files
MathApp/rag/services/textbooks.py
Zareian 8cb1ecb85e 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>
2026-10-12 05:35:18 +03:30

610 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
نقشه‌ی فیزیکیِ فایل‌های PDF کتاب‌های درسی در data/textbooks/.
چرا این فایل لازم است: `ingest_pdf` یک PDF را یک‌تکه و با یک نامِ فصل ingest
می‌کند. کتابِ کاملِ درسی چند فصل دارد، پس بدونِ نگاشتِ «فصل → بازه‌ی صفحه»
همه‌ی نقاطِ کتاب زیرِ یک فصلِ اشتباه ذخیره می‌شدند و فیلترِ `chapter` در
retrieval هیچ‌وقت درست جواب نمی‌داد.
دو نکته که موقعِ افزودنِ کتابِ جدید باید حواستان باشد:
- `page_offset`: شماره‌ی صفحه‌ای که روی کاغذ چاپ شده با شماره‌ی صفحه در PDF
یکی نیست (جلد، شناسنامه، فهرست و سخنی با معلم جلوترند). فهرستِ کتاب شماره‌ی
کاغذی می‌دهد، pdf2image شماره‌ی PDF می‌خواهد: pdf_page = book_page + offset.
- بازه‌ها بر اساسِ فهرستِ خودِ کتاب‌اند و شاملِ صفحه‌ی جداکننده‌ی فصل هم
می‌شوند؛ صفحه‌ی آخرِ هر فصل = یکی قبل از شروعِ فصلِ بعد.
مقادیرِ زیر با خواندنِ فهرست و بازبینیِ چشمیِ صفحه‌های مرزی از همان فایل‌ها
درآمده‌اند (۱۴۰۵/۰۶/۲۱).
"""
from dataclasses import dataclass
@dataclass(frozen=True)
class Section:
"""یک «درس» داخلِ فصل، همان‌طور که در فهرستِ کتاب آمده."""
number: int
title: str
first_page: int # شماره‌ی چاپ‌شده روی کاغذ
last_page: int = 0 # از روی درسِ بعدی پر می‌شود؛ صفر یعنی «تا آخرِ فصل»
@dataclass(frozen=True)
class Chapter:
number: int
title: str # عنوانِ کاملِ فصل، دقیقاً همان رشته‌ای که در payload می‌نشیند
first_page: int # شماره‌ی چاپ‌شده روی کاغذ
last_page: int
sections: tuple[Section, ...] = ()
def resolved_sections(self) -> tuple[Section, ...]:
"""درس‌ها با last_page پرشده (هر درس تا یکی قبل از درسِ بعدی)."""
out = []
for index, section in enumerate(self.sections):
end = (
self.sections[index + 1].first_page - 1
if index + 1 < len(self.sections) else self.last_page
)
out.append(Section(section.number, section.title, section.first_page, end))
return tuple(out)
@dataclass(frozen=True)
class Textbook:
subject: str
grade: str # همان رشته‌ای که در نامِ collection می‌آید («نهم»)
grade_label: str # همان رشته‌ای که در payloadِ `grade` می‌نشیند
filename: str
page_offset: int
chapters: tuple[Chapter, ...]
@property
def key(self) -> str:
return f'{self.subject} {self.grade}'.strip()
def pdf_pages(self, chapter: Chapter) -> tuple[int, int]:
return chapter.first_page + self.page_offset, chapter.last_page + self.page_offset
def chapter(self, number: int) -> Chapter | None:
return next((c for c in self.chapters if c.number == number), None)
def _ch(*rows) -> tuple[Chapter, ...]:
"""rows: (number, title, first_page, last_page, [(sec_no, sec_title, sec_page), ...])"""
chapters = []
for row in rows:
number, title, first, last = row[:4]
sections = tuple(Section(*s) for s in (row[4] if len(row) > 4 else ()))
chapters.append(Chapter(number, title, first, last, sections))
return tuple(chapters)
MATH_3 = Textbook(
subject='ریاضی', grade='سوم', grade_label='سوم دبستان',
filename='', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: الگوها', 0, 0),
(2, 'فصل ۲: عددهای چهار رقمی', 0, 0),
(3, 'فصل ۳: عددهای کسری', 0, 0),
(4, 'فصل ۴: ضرب و تقسیم', 0, 0),
(5, 'فصل ۵: محیط و مساحت', 0, 0),
(6, 'فصل ۶: جمع و تفریق', 0, 0),
(7, 'فصل ۷: آمار و احتمال', 0, 0),
(8, 'فصل ۸: ضرب عددها', 0, 0),
),
)
# چهارم و پنجم: درخواستِ دکتر (۲۰۲۶-۱۰-۰۳) که «از سوم به بعد» همه‌ی پایه‌ها باشند.
# فهرست و offset از خودِ PDFها خوانده شد: در چهارم PDF ۸ = صفحه‌ی چاپیِ ۲، در
# پنجم (چاپِ تازه‌ی ۱۴۰۵) شماره‌ی PDF همان شماره‌ی چاپی است. «حلِ مسئله»ی آغازِ
# هر فصل جزءِ درسِ اول حساب می‌شود (`_sectioned` درسِ اول را تا ابتدای فصل
# عقب می‌کشد) و «مرور فصل» جزءِ درسِ آخر.
MATH_4 = Textbook(
subject='ریاضی', grade='چهارم', grade_label='چهارم دبستان',
filename='ریاضی چهارم.pdf', page_offset=6,
chapters=_ch(
(1, 'فصل ۱: اعداد و الگوها', 1, 23, (
(1, 'عددنویسی', 4),
(2, 'الگوها', 8),
(3, 'ماشین ورودی ـ خروجی', 12),
(4, 'معرفی میلیون', 16),
)),
(2, 'فصل ۲: کسر', 24, 45, (
(1, 'شناخت کسرها', 26),
(2, 'جمع و تفریق کسرها', 30),
(3, 'تساوی کسرها', 34),
(4, 'ضرب عدد در کسر', 38),
)),
(3, 'فصل ۳: ضرب و تقسیم', 46, 75, (
(1, 'ضرب دو عدد دورقمی', 48),
(2, 'محاسبهٔ حاصل‌ضرب', 52),
(3, 'محاسبه‌های تقریبی', 56),
(4, 'تقسیم و بخش‌پذیری', 60),
(5, 'تقسیم بر عددهای یک‌رقمی', 64),
(6, 'تقسیم بر عددهای دورقمی', 68),
)),
(4, 'فصل ۴: اندازه‌گیری', 76, 97, (
(1, 'زاویه', 78),
(2, 'اندازه‌گیری زاویه', 82),
(3, 'اندازه‌گیری زمان', 86),
(4, 'اندازه‌گیری طول', 90),
)),
(5, 'فصل ۵: عدد مخلوط و عدد اعشاری', 98, 119, (
(1, 'عدد مخلوط', 100),
(2, 'عدد اعشاری', 104),
(3, 'جمع و تفریق اعشاری', 108),
(4, 'ارزش مکانی عددهای اعشاری', 112),
)),
(6, 'فصل ۶: شکل‌های هندسی', 120, 141, (
(1, 'عمود و موازی', 122),
(2, 'چهارضلعی‌ها', 126),
(3, 'مساحت متوازی‌الاضلاع و مثلث', 130),
(4, 'محیط و مساحت', 134),
)),
(7, 'فصل ۷: آمار و احتمال', 142, 154, (
(1, 'نمودار خط شکسته', 144),
(2, 'احتمال', 148),
)),
),
)
# شروعِ هر فصل یک صفحه پیش از درسِ اولش گرفته شد (صفحه‌ی جداکننده)؛ فقط برای
# فصل ۱ با چشم دیده شد (PDF ۱۱). جداکننده‌ی بقیه اگر دو صفحه باشد، یک صفحه‌ی
# تصویریِ بی‌مسئله به درسِ آخرِ فصلِ قبل می‌افتد. ۲۰۷–۲۰۸ پشتِ جلد است.
MATH_5 = Textbook(
subject='ریاضی', grade='پنجم', grade_label='پنجم دبستان',
filename='ریاضی پنجم.pdf', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: عددنویسی و الگوها', 11, 38, (
(1, 'یادآوری عددنویسی و محاسبات عددی', 12),
(2, 'معرفی میلیارد', 18),
(3, 'جمع و تفریق عددهای مرکب', 25),
(4, 'الگوها', 30),
)),
(2, 'فصل ۲: کسر', 39, 76, (
(1, 'کسرهای بزرگ‌تر از واحد', 40),
(2, 'جمع و تفریق عددهای مخلوط', 46),
(3, 'ضرب کسرها', 53),
(4, 'تقسیم کسرها', 59),
(5, 'ضرب عددهای مخلوط', 66),
)),
(3, 'فصل ۳: نسبت، تناسب و درصد', 77, 102, (
(1, 'نسبت', 78),
(2, 'نسبت‌های مساوی', 83),
(3, 'تناسب', 88),
(4, 'درصد', 93),
)),
(4, 'فصل ۴: تقارن و چندضلعی‌ها', 103, 128, (
(1, 'تقارن محوری', 104),
(2, 'تقارن مرکزی', 110),
(3, 'زاویه و نیمساز', 113),
(4, 'چندضلعی‌ها و مجموع زاویه‌های آن‌ها', 118),
)),
(5, 'فصل ۵: عددهای اعشاری', 129, 156, (
(1, 'عددهای اعشاری', 130),
(2, 'جمع عددهای اعشاری', 139),
(3, 'تفریق عددهای اعشاری', 143),
(4, 'ضرب عددهای اعشاری', 147),
)),
(6, 'فصل ۶: اندازه‌گیری', 157, 184, (
(1, 'مساحت لوزی و ذوزنقه', 158),
(2, 'محیط دایره', 165),
(3, 'حجم', 170),
(4, 'گنجایش', 177),
)),
(7, 'فصل ۷: آمار و احتمال', 185, 206, (
(1, 'جمع‌آوری و نمایش داده‌ها', 186),
(2, 'میانگین', 193),
(3, 'احتمال', 197),
)),
),
)
# هشتم تنها کتابی‌ست که هم PDF دارد و هم روی Qdrant نیست — یعنی فصل‌بندی و
# درس‌هایش کاملاً از فهرستِ خودِ کتاب می‌آید و ingest دقیقاً همین رشته‌ها را
# می‌نویسد. «مرور فصل» جزء آخرین درسِ هر فصل حساب شده است.
MATH_8 = Textbook(
subject='ریاضی', grade='هشتم', grade_label='هشتم',
filename='ریاضی هشتم.pdf', page_offset=8,
chapters=_ch(
(1, 'فصل ۱: عددهای صحیح و گویا', 1, 18, (
(1, 'یادآوری عددهای صحیح', 2),
(2, 'معرفی عددهای گویا', 6),
(3, 'جمع و تفریق عددهای گویا', 10),
(4, 'ضرب و تقسیم عددهای گویا', 14),
)),
(2, 'فصل ۲: عددهای اول', 19, 28, (
(1, 'یادآوری عددهای اول', 20),
(2, 'تعیین عددهای اول', 24),
)),
(3, 'فصل ۳: چندضلعی‌ها', 29, 50, (
(1, 'چندضلعی‌ها و تقارن', 30),
(2, 'توازی و تعامد', 34),
(3, 'چهارضلعی‌ها', 38),
(4, 'زاویه‌های داخلی', 42),
(5, 'زاویه‌های خارجی', 46),
)),
(4, 'فصل ۴: جبر و معادله', 51, 68, (
(1, 'ساده کردن عبارت‌های جبری', 52),
(2, 'پیدا کردن مقدار یک عبارت جبری', 56),
(3, 'تجزیه عبارت‌های جبری', 60),
(4, 'معادله', 64),
)),
(5, 'فصل ۵: بردار و مختصات', 69, 82, (
(1, 'جمع بردارها', 70),
(2, 'ضرب عدد در بردار', 74),
(3, 'بردارهای واحد مختصات', 78),
)),
(6, 'فصل ۶: مثلث', 83, 100, (
(1, 'رابطهٔ فیثاغورس', 84),
(2, 'شکل‌های هم‌نهشت', 88),
(3, 'مثلث‌های هم‌نهشت', 92),
(4, 'هم‌نهشتی مثلث‌های قائم‌الزاویه', 96),
)),
(7, 'فصل ۷: توان و جذر', 101, 118, (
(1, 'توان', 102),
(2, 'تقسیم اعداد توان‌دار', 106),
(3, 'جذر تقریبی', 110),
(4, 'نمایش اعداد رادیکالی روی محور اعداد', 114),
(5, 'خواص ضرب و تقسیم رادیکال‌ها', 115),
)),
(8, 'فصل ۸: آمار و احتمال', 119, 136, (
(1, 'دسته‌بندی داده‌ها', 120),
(2, 'میانگین داده‌ها', 124),
(3, 'احتمال یا اندازه‌گیری شانس', 128),
(4, 'بررسی حالت‌های ممکن', 132),
)),
(9, 'فصل ۹: دایره', 137, 150, (
(1, 'خط و دایره', 138),
(2, 'زاویه‌های مرکزی', 142),
(3, 'زاویه‌های محاطی', 146),
)),
),
)
MATH_9 = Textbook(
subject='ریاضی', grade='نهم', grade_label='نهم',
filename='ریاضی نهم.pdf', page_offset=8,
chapters=_ch(
(1, 'فصل ۱: مجموعه‌ها', 1, 17, (
(1, 'معرفی مجموعه', 2),
(2, 'مجموعه‌های برابر و نمایش مجموعه‌ها', 6),
(3, 'اجتماع، اشتراک و تفاضلِ مجموعه‌ها', 11),
(4, 'مجموعه‌ها و احتمال', 15),
)),
(2, 'فصل ۲: عددهای حقیقی', 18, 31, (
(1, 'عددهای گویا', 19),
(2, 'عددهای حقیقی', 23),
(3, 'قدر مطلق و محاسبهٔ تقریبی', 28),
)),
(3, 'فصل ۳: استدلال و اثبات در هندسه', 32, 58, (
(1, 'استدلال', 33),
(2, 'آشنایی با اثبات در هندسه', 37),
(3, 'هم‌نهشتی مثلث‌ها', 44),
(4, 'حل مسئله در هندسه', 49),
(5, 'شکل‌های متشابه', 53),
)),
(4, 'فصل ۴: توان و ریشه', 59, 77, (
(1, 'توان صحیح', 60),
(2, 'نماد علمی', 65),
(3, 'ریشه‌گیری', 68),
(4, 'جمع و تفریق رادیکال‌ها', 73),
)),
(5, 'فصل ۵: عبارت‌های جبری', 78, 94, (
(1, 'عبارت‌های جبری و مفهوم اتحاد', 79),
(2, 'چند اتحاد دیگر، تجزیه و کاربردها', 86),
(3, 'نابرابری‌ها و نامعادله‌ها', 90),
)),
(6, 'فصل ۶: خط و معادله‌های خطی', 95, 112, (
(1, 'معادلهٔ خط', 96),
(2, 'شیب خط و عرض از مبدأ', 102),
(3, 'دستگاه معادله‌های خطی', 108),
)),
(7, 'فصل ۷: عبارت‌های گویا', 113, 129, (
(1, 'معرفی و ساده کردن عبارت‌های گویا', 114),
(2, 'محاسبات عبارت‌های گویا', 119),
(3, 'تقسیم چندجمله‌ای‌ها', 126),
)),
(8, 'فصل ۸: حجم و مساحت', 130, 143, (
(1, 'حجم و مساحت کره', 131),
(2, 'حجم هرم و مخروط', 135),
(3, 'سطح و حجم', 140),
)),
),
)
MATH_10 = Textbook(
subject='ریاضی', grade='دهم', grade_label='دهم',
filename='ریاضی دهم.pdf', page_offset=6,
chapters=_ch(
(1, 'فصل ۱: مجموعه، الگو و دنباله', 1, 27, (
(1, 'مجموعه‌های متناهی و نامتناهی', 2),
(2, 'متمم یک مجموعه', 8),
(3, 'الگو و دنباله', 14),
(4, 'دنباله‌های حسابی و هندسی', 21),
)),
(2, 'فصل ۲: مثلثات', 28, 46, (
(1, 'نسبت‌های مثلثاتی', 29),
(2, 'دایرهٔ مثلثاتی', 36),
(3, 'روابط بین نسبت‌های مثلثاتی', 42),
)),
(3, 'فصل ۳: توان‌های گویا و عبارت‌های جبری', 47, 68, (
(1, 'ریشه و توان', 48),
(2, 'ریشهٔ nام', 54),
(3, 'توان‌های گویا', 59),
(4, 'عبارت‌های جبری', 62),
)),
(4, 'فصل ۴: معادله‌ها و نامعادله‌ها', 69, 93, (
(1, 'معادلهٔ درجه دوم و روش‌های مختلف حل آن', 70),
(2, 'سهمی', 78),
(3, 'تعیین علامت', 83),
)),
(5, 'فصل ۵: تابع', 94, 117, (
(1, 'مفهوم تابع و بازنمایی‌های آن', 95),
(2, 'دامنه و برد توابع', 101),
(3, 'انواع تابع', 109),
)),
(6, 'فصل ۶: شمارش، بدون شمردن', 118, 140, (
(1, 'شمارش', 119),
(2, 'جایگشت', 127),
(3, 'ترکیب', 133),
)),
(7, 'فصل ۷: آمار و احتمال', 141, 170, (
(1, 'احتمال یا اندازه‌گیری شانس', 142),
(2, 'مقدمه‌ای بر علم آمار، جامعه و نمونه', 152),
(3, 'متغیر و انواع آن', 159),
)),
),
)
# پایه‌های ششم و هفتم را تیمِ دیگر ingest کرده و روی Qdrant هستند؛ فایلِ PDF
# برایشان لازم نیست و بازه‌ی صفحه هم نداریم (نقاطشان شماره‌ی صفحه ندارند). فقط
# فصل‌بندی ثبت می‌شود تا `seed_curriculum_from_textbook` بتواند برایشان برنامه‌ی
# درسی بسازد؛ `filename` خالی یعنی «این کتاب از مسیرِ PDF ingest نمی‌شود».
MATH_6 = Textbook(
subject='ریاضی', grade='ششم', grade_label='ششم دبستان',
filename='', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: عدد و الگوهای عددی', 0, 0),
(2, 'فصل ۲: کسر', 0, 0),
(3, 'فصل ۳: اعداد اعشاری', 0, 0),
(4, 'فصل ۴: تقارن و مختصات', 0, 0),
(5, 'فصل ۵: اندازه گیری', 0, 0),
(6, 'فصل ۶: تناسب و درصد', 0, 0),
(7, 'فصل ۷: تقریب', 0, 0),
),
)
MATH_7 = Textbook(
subject='ریاضی', grade='هفتم', grade_label='هفتم',
filename='', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: راهبردهای حل مسئله', 0, 0),
(2, 'فصل ۲: عددهای صحیح', 0, 0),
(3, 'فصل ۳: جبر و معادله', 0, 0),
(4, 'فصل ۴: هندسه و استدلال', 0, 0),
(5, 'فصل ۵: شمارنده‌ها و اعداد اول', 0, 0),
(6, 'فصل ۶: سطح و حجم', 0, 0),
(7, 'فصل ۷: توان و جذر', 0, 0),
(8, 'فصل ۸: بردار و مختصات', 0, 0),
(9, 'فصل ۹: آمار و احتمال', 0, 0),
),
)
# ── حسابان ─────────────────────────────────────────────────────────────
# «حسابان» کتابِ رشته‌ی ریاضی‌فیزیک است، نه «ریاضی» عمومی. subject عمداً
# «حسابان» است تا collection جدا بماند: هم‌نامیِ آن با «ریاضی یازدهم»
# (کتابِ تجربی) دو کتابِ متفاوت را سرِ یک collection می‌انداخت.
# ⚠️ هر subjectِ تازه‌ی ریاضی‌وار باید به known_collections.MATH_SUBJECTS هم
# اضافه شود، وگرنه retrieval آن را «درسِ متنی» می‌بیند و به‌جای `chapter`
# روی `lesson_number` فیلتر می‌کند — یعنی بی‌سروصدا هیچ‌وقت چیزی برنمی‌گرداند.
#
# بازه‌ها از فهرستِ خودِ کتاب (PDF صفحه‌ی ۵) درآمده‌اند و page_offset با
# تطبیقِ صفحه‌ی ۱ کتاب روی صفحه‌ی ۷ PDF تأیید شده است.
CALCULUS_11 = Textbook(
subject='حسابان', grade='یازدهم', grade_label='یازدهم',
filename='حسابان یازدهم.pdf', page_offset=6,
chapters=_ch(
(1, 'فصل ۱: جبر و معادله', 1, 36, (
(1, 'مجموع جمله‌های دنباله‌های حسابی و هندسی', 2),
(2, 'معادله‌های درجه دوم', 7),
(3, 'معادله‌های گویا و گنگ', 17),
(4, 'قدر مطلق و ویژگی‌های آن', 23),
(5, 'آشنایی با هندسه تحلیلی', 29),
)),
(2, 'فصل ۲: تابع', 37, 70, (
(1, 'آشنایی بیشتر با تابع', 38),
(2, 'انواع توابع', 44),
(3, 'وارون تابع', 54),
(4, 'اعمال روی توابع', 63),
)),
(3, 'فصل ۳: توابع نمایی و لگاریتمی', 71, 90, (
(1, 'تابع نمایی', 72),
(2, 'تابع لگاریتمی و لگاریتم', 80),
(3, 'ویژگی‌های لگاریتم و حل معادله‌های لگاریتمی', 86),
)),
(4, 'فصل ۴: مثلثات', 91, 112, (
(1, 'رادیان', 92),
(2, 'نسبت‌های مثلثاتی برخی زوایا', 98),
(3, 'توابع مثلثاتی', 105),
(4, 'روابط مثلثاتی مجموع و تفاضل زوایا', 110),
)),
(5, 'فصل ۵: حد و پیوستگی', 113, 151, (
(1, 'مفهوم حد و فرایندهای حدی', 114),
(2, 'حدهای یک‌طرفه (حد چپ و حد راست)', 123),
(3, 'قضایای حد', 130),
(4, 'محاسبه حد توابع کسری', 141),
(5, 'پیوستگی', 145),
)),
),
)
CALCULUS_12 = Textbook(
subject='حسابان', grade='دوازدهم', grade_label='دوازدهم',
filename='حسابان دوازدهم.pdf', page_offset=6,
chapters=_ch(
(1, 'فصل ۱: تابع', 1, 22, (
(1, 'تبدیل نمودار توابع', 2),
(2, 'تابع درجه سوم، توابع یکنوا و بخش‌پذیری و تقسیم', 13),
)),
(2, 'فصل ۲: مثلثات', 23, 44, (
(1, 'تناوب و تانژانت', 24),
(2, 'معادله‌های مثلثاتی', 35),
)),
(3, 'فصل ۳: حدهای نامتناهی ـ حد در بی‌نهایت', 45, 70, (
(1, 'حدهای نامتناهی', 46),
(2, 'حد در بی‌نهایت', 59),
)),
(4, 'فصل ۴: مشتق', 71, 110, (
(1, 'آشنایی با مفهوم مشتق', 72),
(2, 'مشتق‌پذیری و پیوستگی', 84),
(3, 'آهنگ متوسط تغییر و آهنگ لحظه‌ای تغییر', 102),
)),
(5, 'فصل ۵: کاربردهای مشتق', 111, 146, (
(1, 'اکسترمم‌های یک تابع و توابع صعودی و نزولی', 112),
(2, 'جهت تقعر نمودار یک تابع و نقطه‌ی عطف آن', 127),
(3, 'رسم نمودار تابع', 137),
)),
),
)
# ── هندسه، آمار و احتمال، گسسته (۲۰۲۶-۱۰-۱۱؛ درخواستِ سینیور) ─────────────
# کتاب‌های رشته‌ی ریاضی‌فیزیک که کنارِ ریاضی/حسابانِ همان پایه می‌آیند، پس هر
# کدام subject و collectionِ خودش را دارد (همان دلیلِ «حسابان»).
# فهرست و آفست از خودِ PDFها خوانده شد (فهرست چشمی، آفست با تطبیقِ صفحه‌ی بازکننده‌ی
# فصل با متنِ همان صفحه‌ی PDF): هندسه‌ها و گسسته چاپی=PDF (۰) و (۸)؛ آمار صفحه‌ی ۱
# چاپی روی PDF ۷ است (آفست ۶، مثلِ حسابان). ⚠️ آخرین صفحه‌ی هر فصل «یکی قبل از فصل
# بعد» است و آخرین فصل تا قبل از «منابع»؛ مرزها با ingest و شمارشِ chunk وارسی شوند.
GEOMETRY_10 = Textbook(
subject='هندسه', grade='دهم', grade_label='دهم',
filename='هندسه دهم.pdf', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: ترسیم‌های هندسی و استدلال', 9, 28, (
(1, 'ترسیم‌های هندسی', 10),
(2, 'استدلال', 17),
)),
(2, 'فصل ۲: قضیه‌ی تالس، تشابه و کاربردهای آن', 29, 52, (
(1, 'نسبت و تناسب در هندسه', 30),
(2, 'قضیه‌ی تالس', 34),
(3, 'تشابه مثلث‌ها', 38),
(4, 'کاربردهایی از قضیه‌ی تالس و تشابه مثلث‌ها', 45),
)),
(3, 'فصل ۳: چندضلعی‌ها', 53, 76, (
(1, 'چندضلعی‌ها و ویژگی‌هایی از آن‌ها', 54),
(2, 'مساحت و کاربردهای آن', 65),
)),
(4, 'فصل ۴: تجسم فضایی', 77, 96, (
(1, 'خط، نقطه و صفحه', 78),
(2, 'تفکر تجسمی', 87),
)),
),
)
GEOMETRY_11 = Textbook(
subject='هندسه', grade='یازدهم', grade_label='یازدهم',
filename='هندسه یازدهم.pdf', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: دایره', 9, 30, (
(1, 'مفاهیم اولیه و زاویه‌ها در دایره', 10),
(2, 'رابطه‌های طولی در دایره', 18),
(3, 'چندضلعی‌های محاطی و محیطی', 24),
)),
(2, 'فصل ۲: تبدیل‌های هندسی و کاربردها', 31, 58, (
(1, 'تبدیل‌های هندسی', 32),
(2, 'کاربرد تبدیل‌ها', 50),
)),
(3, 'فصل ۳: روابط طولی در مثلث', 59, 80, (
(1, 'قضیه‌ی سینوس‌ها', 60),
(2, 'قضیه‌ی کسینوس‌ها', 64),
(3, 'قضیه‌ی نیمسازهای زوایای داخلی و محاسبه‌ی طول نیمسازها', 68),
(4, 'قضیه‌ی هرون (محاسبه‌ی ارتفاع‌ها و مساحت مثلث)', 71),
)),
),
)
GEOMETRY_12 = Textbook(
subject='هندسه', grade='دوازدهم', grade_label='دوازدهم',
filename='هندسه دوازدهم.pdf', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: ماتریس و کاربردها', 9, 32, (
(1, 'ماتریس و اعمال روی ماتریس‌ها', 10),
(2, 'وارون ماتریس و دترمینان', 22),
)),
(2, 'فصل ۲: آشنایی با مقاطع مخروطی', 33, 60, (
(1, 'آشنایی با مقاطع مخروطی و مکان هندسی', 34),
(2, 'دایره', 40),
(3, 'بیضی و سهمی', 47),
)),
(3, 'فصل ۳: بردارها', 61, 86, (
(1, 'معرفی فضای R³', 62),
(2, 'ضرب داخلی و ضرب خارجی بردارها', 77),
)),
),
)
STATS_11 = Textbook(
subject='آمار و احتمال', grade='یازدهم', grade_label='یازدهم',
filename='آمار و احتمال یازدهم.pdf', page_offset=6,
chapters=_ch(
(1, 'فصل ۱: آشنایی با مبانی ریاضیات', 1, 34, (
(1, 'آشنایی با منطق ریاضی', 2),
(2, 'جبر مجموعه‌ها', 16),
)),
(2, 'فصل ۲: احتمال', 35, 68, (
(1, 'مبانی احتمال', 36),
(2, 'احتمال غیرهم‌شانس', 44),
(3, 'احتمال شرطی', 48),
(4, 'پیشامدهای مستقل و وابسته', 63),
)),
(3, 'فصل ۳: آمار توصیفی', 69, 96, (
(1, 'توصیف و نمایش داده‌ها', 70),
(2, 'معیارهای گرایش به مرکز', 80),
(3, 'معیارهای پراکندگی', 87),
)),
(4, 'فصل ۴: آمار استنباطی', 97, 121, (
(1, 'گردآوری داده‌ها', 98),
(2, 'برآورد', 112),
)),
),
)
DISCRETE_12 = Textbook(
subject='گسسته', grade='دوازدهم', grade_label='دوازدهم',
filename='گسسته دوازدهم.pdf', page_offset=8,
chapters=_ch(
(1, 'فصل ۱: آشنایی با نظریه‌ی اعداد', 1, 30, (
(1, 'استدلال ریاضی', 2),
(2, 'بخش‌پذیری در اعداد صحیح', 9),
(3, 'هم‌نهشتی در اعداد صحیح و کاربردها', 18),
)),
(2, 'فصل ۲: گراف و مدل‌سازی', 31, 54, (
(1, 'معرفی گراف', 32),
(2, 'مدل‌سازی با گراف', 43),
)),
(3, 'فصل ۳: ترکیبیات (شمارش)', 55, 84, (
(1, 'مباحثی در ترکیبیات', 56),
(2, 'روش‌هایی برای شمارش', 73),
)),
),
)
TEXTBOOKS: dict[str, Textbook] = {
b.key: b for b in (MATH_3, MATH_4, MATH_5, MATH_6, MATH_7, MATH_8, MATH_9, MATH_10,
CALCULUS_11, CALCULUS_12, GEOMETRY_10, GEOMETRY_11, GEOMETRY_12,
STATS_11, DISCRETE_12)
}
# کتاب‌هایی که واقعاً فایلِ PDF دارند و `rag_ingest_book` رویشان کار می‌کند.
INGESTABLE = {b.key: b for b in TEXTBOOKS.values() if b.filename}
def get_textbook(subject: str, grade: str) -> Textbook | None:
return TEXTBOOKS.get(f'{(subject or "").strip()} {(grade or "").strip()}')
def chapter_titles(book: Textbook) -> dict[int, str]:
return {c.number: c.title for c in book.chapters}