stop losing the pages that matter most to a stray backslash
مدلِ vision در متنِ ریاضی LaTeX مینویسد (\frac، \sqrt، \vec) و بکاسلشِ تنها در JSON نامعتبر است، پس json.loads کلِ صفحه را دور میریخت — دقیقاً صفحههای پرفرمول، یعنی باارزشترینها. روی حسابان یازدهم/دوازدهم چهار صفحه هرچه تلاش شد «خطادار» ماندند تا همینجا پیدا شد. ⚠️ نسخهی اولِ تعمیر خودش تله داشت: \f، \t، \b، \r و \n از نظرِ JSON معتبرند، پس «سالم» رد میشدند و \frac به formfeed+«rac» و \theta به tab+«heta» تبدیل میشد — خرابیِ بیصدا بهجای خطای بلند. قاعدهی تفکیک: دستورِ LaTeX همیشه با حرفِ ASCII ادامه مییابد، ولی escapeِ واقعی با رقم، فارسی یا پایانِ رشته. کنارش: پایههای یازدهم و دوازدهم به persona و تشخیصِ پایه اضافه شدند («دوازدهم» پیش از «دهم» بررسی میشود چون زیررشتهاش را دارد)، سقفِ max_tokens از ۳۰۰۰ به ۶۰۰۰ رفت چون قاعدهی self-contained خروجی را بلندتر کرده، و حسابان یازدهم و دوازدهم با نگاشتِ فصلبهصفحه ثبت شدند. «حسابان» به MATH_SUBJECTS اضافه شد: بدونِ آن retrieval این کتابها را «درسِ متنی» میدید و بهجای chapter روی lesson_number فیلتر میکرد — بیهیچ خطایی همیشه صفر نتیجه. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,11 @@ TEXT_SUBJECT_LESSON_FILTER_KEY = 'lesson_number'
|
||||
MATH_LESSON_FILTER_KEY = 'chapter'
|
||||
|
||||
# subjectهایی که ساختارِ «ریاضی» دارند (فیلتر با chapter، متن از generalSolution)
|
||||
MATH_SUBJECTS = {'ریاضی'}
|
||||
# «حسابان» کتابِ رشتهی ریاضیفیزیک در پایههای یازدهم/دوازدهم است و دقیقاً
|
||||
# همان شکلِ payload را دارد. اگر اینجا نباشد، retrieval آن را درسِ متنی حساب
|
||||
# میکند و بهجای `chapter` روی `lesson_number` فیلتر میزند — بدونِ هیچ خطایی
|
||||
# همیشه صفر نتیجه.
|
||||
MATH_SUBJECTS = {'ریاضی', 'حسابان'}
|
||||
|
||||
# فصول ریاضی به تفکیکِ (subject, grade) — کلیدِ داخلی هم شمارهی فصل و هم
|
||||
# رشتهی کامل رو میپذیره.
|
||||
|
||||
@@ -45,12 +45,17 @@ def detect_grade_and_start_page(pdf_path: str) -> tuple[int, str | None]:
|
||||
raise RagNotConfigured('pymupdf (fitz) نصب نیست.') from exc
|
||||
|
||||
# صفحهی شروعِ محتوای واقعی؛ هرچه اینجا نباشد پیشفرضِ ۷ میگیرد.
|
||||
start_pages = {'پنجم دبستان': 11, 'ششم دبستان': 9, 'نهم': 9, 'دهم': 7}
|
||||
start_pages = {'پنجم دبستان': 11, 'ششم دبستان': 9, 'نهم': 9, 'دهم': 7,
|
||||
'یازدهم': 7, 'دوازدهم': 7}
|
||||
# ترتیب مهم است: «دهم» زیررشتهی «نهم» نیست ولی «هفتم»/«هشتم» در متنِ
|
||||
# شناسنامهی کتابِ نهم هم میآیند («در ادامهٔ پایههای هفتم و هشتم»)، پس
|
||||
# پایههای بالاتر باید زودتر بررسی شوند.
|
||||
grades = ['دهم', 'نهم', 'هشتم', 'هفتم', 'اول دبستان', 'دوم دبستان',
|
||||
'سوم دبستان', 'چهارم دبستان', 'پنجم دبستان', 'ششم دبستان']
|
||||
# ترتیب از بالا به پایین است و «دوازدهم»/«یازدهم» باید پیش از «دهم» بیایند:
|
||||
# هر دو رشتهی «دهم» را در خود دارند، پس با ترتیبِ برعکس کتابِ دوازدهم
|
||||
# «دهم» تشخیص داده میشد و صفحهی شروع و لحنِ معلمش غلط میافتاد.
|
||||
grades = ['دوازدهم', 'یازدهم', 'دهم', 'نهم', 'هشتم', 'هفتم', 'اول دبستان',
|
||||
'دوم دبستان', 'سوم دبستان', 'چهارم دبستان', 'پنجم دبستان',
|
||||
'ششم دبستان']
|
||||
|
||||
doc = fitz.open(pdf_path)
|
||||
try:
|
||||
@@ -89,6 +94,9 @@ _TEACHER_BY_GRADE = {
|
||||
'هشتم': 'a specialist middle-school math teacher for students aged 13 to 14',
|
||||
'نهم': 'a specialist middle-school math teacher for students aged 14 to 15',
|
||||
'دهم': 'a specialist high-school math teacher for students aged 15 to 16',
|
||||
'یازدهم': 'a specialist high-school math teacher for students aged 16 to 17',
|
||||
'دوازدهم': 'a specialist high-school math teacher for students aged 17 to 18, '
|
||||
'preparing them for the university entrance exam',
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +107,53 @@ def teacher_persona(grade: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
# بکاسلشهایی که همیشه escapeِ واقعیاند و نباید دست بخورند.
|
||||
_JSON_SAFE_ESCAPES = set('"\\/u')
|
||||
# اینها هم escapeِ معتبرِ JSONاند و هم آغازِ دستورهای پرکاربردِ LaTeX:
|
||||
# \frac \times \theta \beta \rightarrow \nabla. تفکیک از روی نویسهی بعدی.
|
||||
_JSON_AMBIGUOUS_ESCAPES = set('bfnrt')
|
||||
|
||||
|
||||
def repair_json_escapes(raw: str) -> str:
|
||||
"""
|
||||
بکاسلشِ نامعتبر در رشتههای JSON را دوبرابر میکند تا قابلِ تجزیه شود.
|
||||
|
||||
مدل در متنِ ریاضی LaTeX تولید میکند و `\sqrt{2}` از نظرِ JSON خطاست، پس
|
||||
json.loads کلِ صفحه را دور میریخت — دقیقاً صفحههای پرفرمول، یعنی
|
||||
باارزشترینها.
|
||||
|
||||
⚠️ نکتهای که نسخهی اولِ همین تابع را غلط کرد: `\f`، `\t`، `\b`، `\r` و
|
||||
`\n` از نظرِ JSON معتبرند، پس «سالم» رد میشدند و `\frac` به formfeed+«rac»
|
||||
و `\theta` به tab+«heta» تبدیل میشد — خرابیِ بیصدا بهجای خطای بلند.
|
||||
قاعدهی تفکیک: دستورِ LaTeX همیشه با حرفِ ASCII ادامه پیدا میکند
|
||||
(`\frac`, `\times`)، ولی escapeِ واقعی با رقم، فاصله، فارسی یا پایانِ
|
||||
رشته میآید (`\n۲.`، `\nحالت`). پس فقط وقتی control character حساب
|
||||
میشود که نویسهی بعدی حرفِ ASCII نباشد.
|
||||
"""
|
||||
out: list[str] = []
|
||||
i, n = 0, len(raw)
|
||||
while i < n:
|
||||
ch = raw[i]
|
||||
if ch != '\\':
|
||||
out.append(ch)
|
||||
i += 1
|
||||
continue
|
||||
nxt = raw[i + 1] if i + 1 < n else ''
|
||||
after = raw[i + 2] if i + 2 < n else ''
|
||||
looks_like_latex = after.isascii() and after.isalpha()
|
||||
keep = nxt in _JSON_SAFE_ESCAPES or (
|
||||
nxt in _JSON_AMBIGUOUS_ESCAPES and not looks_like_latex
|
||||
)
|
||||
if keep:
|
||||
out.append(ch)
|
||||
out.append(nxt)
|
||||
i += 2
|
||||
else:
|
||||
out.append('\\\\')
|
||||
i += 1
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def extract_problem_types(image, grade: str, chapter_name: str) -> dict:
|
||||
"""
|
||||
یک عکسِ صفحهی کتاب → ساختارِ JSONِ انواعِ مسئله (فارسی)، با مدلِ vision.
|
||||
@@ -159,7 +214,11 @@ Rules:
|
||||
{'type': 'image_url', 'image_url': {'url': f'data:image/jpeg;base64,{img_b64}', 'detail': 'high'}},
|
||||
]},
|
||||
],
|
||||
max_tokens=3000,
|
||||
# ۳۰۰۰ برای صفحهی متراکم کم است: خروجی وسطِ JSON قطع میشود،
|
||||
# json.loads خطا میدهد و کلِ صفحه در failed_pages میافتد. قاعدهی
|
||||
# self-contained هم خروجی را بلندتر کرده (توصیفِ شکل داخلِ متن)، پس
|
||||
# سقف بالا رفت. هزینهی واقعی به توکنِ تولیدشده است نه به این سقف.
|
||||
max_tokens=6000,
|
||||
temperature=0.1,
|
||||
)
|
||||
raw = (response.choices[0].message.content or '').strip()
|
||||
@@ -170,7 +229,14 @@ Rules:
|
||||
if raw.endswith('```'):
|
||||
raw = raw[:-3].strip()
|
||||
|
||||
try:
|
||||
parsed = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
# مدل در متنِ ریاضی LaTeX مینویسد (\frac، \sqrt، \vec) و بکاسلشِ تنها
|
||||
# در JSON نامعتبر است، پس json.loads کلِ صفحه را دور میریخت. این دقیقاً
|
||||
# صفحههای پرفرمول — باارزشترینها — را بیسروصدا حذف میکرد؛ روی حسابان
|
||||
# یازدهم/دوازدهم چهار صفحه هرچه تلاش شد «خطادار» ماندند تا همینجا پیدا شد.
|
||||
parsed = json.loads(repair_json_escapes(raw))
|
||||
# مصرفِ واقعیِ همین فراخوانی را همراهِ نتیجه برمیگردانیم تا ingest بتواند
|
||||
# هزینهی واقعی را گزارش کند. تا پیش از این فقط تخمینِ سرانگشتی داشتیم و
|
||||
# آن تخمین ۲.۳ برابرِ واقعیت بود.
|
||||
|
||||
@@ -286,8 +286,87 @@ MATH_7 = Textbook(
|
||||
),
|
||||
)
|
||||
|
||||
# ── حسابان ─────────────────────────────────────────────────────────────
|
||||
# «حسابان» کتابِ رشتهی ریاضیفیزیک است، نه «ریاضی» عمومی. 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),
|
||||
)),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
TEXTBOOKS: dict[str, Textbook] = {
|
||||
b.key: b for b in (MATH_3, MATH_6, MATH_7, MATH_8, MATH_9, MATH_10)
|
||||
b.key: b for b in (MATH_3, MATH_6, MATH_7, MATH_8, MATH_9, MATH_10,
|
||||
CALCULUS_11, CALCULUS_12)
|
||||
}
|
||||
|
||||
# کتابهایی که واقعاً فایلِ PDF دارند و `rag_ingest_book` رویشان کار میکند.
|
||||
|
||||
Reference in New Issue
Block a user