From 96bda905db91604bc651253f50141e162fc4fa7a Mon Sep 17 00:00:00 2001 From: Zareian Date: Sun, 20 Sep 2026 12:36:16 +0330 Subject: [PATCH] stop the figure guard from throwing away four fifths of what it caught MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit محافظِ «وابسته به شکل» یک regexِ تخت بود (شکل|جدول|روبه‌رو|مقابل|زیر را) و مسئله‌ها را بدونِ هیچ فراخوانی کنار می‌گذاشت. فاجعه‌بار گشاد بود: «زاویه‌های روبه‌رو در لوزی برابرند؟» ← «روبه‌رو» یعنی مقابل، نه شکل «کسرهای زیر را گویا کنید: ۲/√۳» ← داده همان‌جاست «ظرفی به شکل مخروطی با شعاع ۴» ← «به شکلِ» یعنی شبیهِ اندازه‌گیری روی نمونه‌ی ۵۰تایی از کتاب‌های بازخوانی‌شده: ۸۴٪ از رد‌شده‌ها واقعاً قابلِ قضاوت بودند — یعنی ~۷۸۴ مسئله‌ی سالم بی‌صدا از چرخه‌ی ساختِ سوال بیرون مانده بود، بدونِ هیچ خطایی در هیچ لاگی. قاعده‌ی تازه دو شرط دارد و هر دو باید برقرار باشند: ارجاعِ صریح به چیزی بیرونِ متن، و نیامدنِ هیچ توصیفی بعد از آن. شرطِ دوم همان چیزی را می‌گیرد که قاعده‌ی self-contained تولید می‌کند: «با توجه به شکل مقابل که شامل {a,1} است». اثرش روی دادهٔ واقعی: unjudgeable از ۱٬۴۴۱ به ۵۰۸ و agrees از ۷۹۱ به ۲٬۴۶۶. Co-Authored-By: Claude Opus 5 --- .../commands/rag_verify_solutions.py | 48 ++++++++++++++++--- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/rag/management/commands/rag_verify_solutions.py b/rag/management/commands/rag_verify_solutions.py index 9e4164c..62efa3f 100644 --- a/rag/management/commands/rag_verify_solutions.py +++ b/rag/management/commands/rag_verify_solutions.py @@ -82,12 +82,46 @@ def is_account_limit(exc) -> bool: return '403' in text and 'insufficient' in text -# مسئله‌ای که به شکل/جدولِ کتاب ارجاع می‌دهد بدونِ آن شکل قابلِ حل نیست. -# فرستادنش هم پول می‌سوزاند هم آمار را خراب می‌کند. -NEEDS_FIGURE = re.compile( - r'شکل|جدول|نمودار|روبه‌رو|روبرو|مقابل|تصویر|نقشه|رسم کنید|بکشید|' - r'رنگ[‌ ]?آمیزی|زیر را|بالا را|وِن' +# مسئله‌ای که به شکل/جدولِ کتاب ارجاع می‌دهد و **محتوای آن را ننوشته** بدونِ +# تصویر قابلِ حل نیست؛ فرستادنش هم پول می‌سوزاند هم آمار را خراب می‌کند. +# +# ⚠️ نسخه‌ی اولِ این محافظ یک regexِ تخت بود: +# شکل|جدول|نمودار|روبه‌رو|روبرو|مقابل|...|زیر را|بالا را +# و فاجعه‌بار گشاد بود. سه نمونه از چیزهایی که بی‌دلیل رد می‌کرد: +# «زاویه‌های روبه‌رو در لوزی برابرند؟» ← «روبه‌رو» یعنی مقابل، نه شکل +# «کسرهای زیر را گویا کنید: ۲/√۳» ← داده همان‌جاست +# «به شکل مخروطی با شعاع ۴ و ارتفاع ۱۲» ← «به شکلِ ...» یعنی شبیهِ +# اندازه‌گیری (۱۴۰۵/۰۶/۲۹، نمونه‌ی ۵۰تایی از کتاب‌های بازخوانی‌شده): از +# مسئله‌هایی که این محافظ رد کرده بود، **۸۴٪ واقعاً قابلِ قضاوت بودند** — +# یعنی ~۷۸۴ مسئله‌ی سالم بی‌صدا از چرخه‌ی ساختِ سوال بیرون مانده بودند. +# +# قاعده‌ی تازه دو شرط دارد، و هر دو باید برقرار باشند: +# ۱. ارجاعِ صریح به چیزی بیرونِ متن باشد («شکل زیر»، «جدول روبه‌رو») +# ۲. بعد از آن ارجاع، توصیفی نیامده باشد (نه «که ...»، نه عدد، نه نماد) +# شرطِ دوم همان چیزی است که قاعده‌ی self-contained تولید می‌کند: مدل حالا +# می‌نویسد «با توجه به شکل مقابل **که شامل {a,1} است**». +_FIGURE_REFERENCE = re.compile( + r'(شکل|جدول|نمودار|تصویر|نقشه)\s*(ِ|های|‌های)?\s*' + r'(زیر|بالا|روبه\u200cرو|روبرو|مقابل|فوق|داده\s*شده)' + r'|با توجه به\s*(شکل|جدول|نمودار|تصویر)' ) +# نشانه‌ی اینکه محتوای شکل داخلِ متن آمده. +_FIGURE_DESCRIBED = re.compile(r'که\s|شامل|اعضای|مقادیر|برابر|=|\{|\d') +# کارهایی که ذاتاً تصویری‌اند و هیچ متنی نجاتشان نمی‌دهد. +_ART_TASK = re.compile(r'رنگ[‌ ]?آمیزی|رنگ کنید|رنگ\u200cآمیزی') + + +def needs_figure(problem: str) -> bool: + """آیا این مسئله بدونِ دیدنِ صفحه‌ی کتاب واقعاً قابلِ قضاوت نیست؟""" + text = problem or '' + if _ART_TASK.search(text): + return True + match = _FIGURE_REFERENCE.search(text) + if not match: + return False + # ۱۶۰ نویسه بعد از ارجاع: جایی که توصیفِ شکل اگر بیاید، می‌آید. + return not _FIGURE_DESCRIBED.search(text[match.end():match.end() + 160]) + PROMPT = """تو یک ریاضی‌دانِ دقیق هستی که راه‌حل‌های کتابِ درسیِ ایران را بازبینی می‌کند. @@ -183,7 +217,7 @@ class Command(BaseCommand): rows = list(qs.order_by('collection', 'source_page', 'order')) skipped = self._mark_figure_dependent(rows) - rows = [r for r in rows if not NEEDS_FIGURE.search(r.problem or '')] + rows = [r for r in rows if not needs_figure(r.problem or '')] if opts['limit']: rows = rows[:opts['limit']] if skipped: @@ -203,7 +237,7 @@ class Command(BaseCommand): """قبل از خرجِ یک ریال، آن‌هایی که ذاتاً قابلِ قضاوت نیستند کنار می‌روند.""" marked = [] for chunk in rows: - if NEEDS_FIGURE.search(chunk.problem or ''): + if needs_figure(chunk.problem or ''): chunk.solution_verdict = LessonChunk.VERDICT_UNJUDGEABLE chunk.check_note = 'به شکل/جدولِ کتاب ارجاع می‌دهد؛ از متن قابلِ قضاوت نیست.' chunk.checked_by = 'pre-filter'