ground grade 9's questions in the textbook, and catch the book's own errors

پایه نهم تا دیروز ناقص بود: ۲۸ صفحه از کتاب ingest نشده بود و فصل ۸ (حجم و
مساحت) اصلاً وجود نداشت. حالا ۱۴۳ از ۱۴۳ صفحه رفته و برنامه‌ی درسی ۸ فصل،
۲۸ مبحث و ۲۵۸ درس دارد.

۱۷۱ سوالِ تازه از روی مسئله‌های واقعیِ همان کتاب ساخته شد (جمعِ فایل: ۳۳۹).
برخلافِ سوال‌های نمونه‌ی قبلی که از عنوانِ مبحث ساخته شده بودند، این‌ها به
مسئله‌ی مشخصی در کتاب وصل‌اند.

مهم‌تر از خودِ سوال‌ها، چیزی که وسطِ کار معلوم شد: **بخشی از راه‌حل‌هایی که
vision از PDF استخراج کرده غلط است.** ۱۷ مورد پیدا شد، از جمله:

    شمارنده‌های ۶۰ که نه اول‌اند و نه مرکب → کتاب: {۱, ۶۰}   (۶۰ مرکب است)
    x−y=۵ و y=x−۳ → کتاب: (۸,۵)            (خطوط موازی‌اند، بی‌جواب)
    نور در ۱۰۰ ساعت → کتاب: ۱۰۸×۱۰¹³        (ده برابر؛ درست ۱٫۰۸×۱۰¹⁴)
    |۱−√۳| → کتاب: ۱−√۳                     (منفی است؛ درست √۳−۱)
    استوانه ۶۰cm×۱m → کتاب: ۲۸۲٫۷ m³        (خطای واحد؛ درست ۰٫۲۸۳)

اجرای قبلی با gpt-4o-mini همین جواب‌های غلط را بی‌صدا به‌عنوانِ گزینه‌ی درست
علامت می‌زد، چون فقط بازنویسی می‌کرد. مسیرِ تازه اول مسئله را مستقل حل می‌کند
و بعد با راه‌حلِ کتاب مقایسه می‌کند؛ هر اختلاف علامت می‌خورد و دستی بررسی شد.

۷۵ مسئله عمداً سوال نشدند — به شکل یا جدولی در کتاب ارجاع می‌دادند که در متن
نیست. حدس زدن در این موارد یعنی سوالِ بی‌جواب.

`seed_content --export` هم دیگر نمی‌تواند سوال‌ها را بی‌صدا پاک کند: فایل
دست‌نویس است و در دیتابیسِ هر ماشینی نیست، و یک export ساده آن را با آرایه‌ی
خالی بازنویسی می‌کرد. حالا خطا می‌دهد و `--only curriculum` را پیشنهاد می‌کند.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 20:40:09 +03:30
parent 0c374fed99
commit 1131569b76
3 changed files with 3172 additions and 8 deletions

View File

@@ -44,10 +44,13 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--export', action='store_true',
help='به‌جای وارد کردن، از دیتابیسِ فعلی خروجی بگیر.')
parser.add_argument('--only', choices=('curriculum', 'questions'), default=None,
help='فقط یکی از دو فایل را خروجی بگیر.')
parser.add_argument('--dry-run', action='store_true',
help='فقط گزارش بده؛ چیزی ننویس.')
def handle(self, *args, **options):
self.only = options.get('only')
if options['export']:
return self._export()
return self._load(dry_run=options['dry_run'])
@@ -66,21 +69,37 @@ class Command(BaseCommand):
curriculum = curriculum_io.export_curriculum(grades)
rows = question_io.export_questions_by_path(questions)
_write_json(CURRICULUM_FILE, curriculum)
_write_json(QUESTIONS_FILE, {'questions': rows})
# محافظ در برابرِ پاک شدنِ خاموشِ سوال‌ها: فایلِ سوال‌ها دست‌نویس است و
# ممکن است در دیتابیسِ این ماشین نباشد (مثلاً وقتی فقط کوریکولوم را
# محلی ساخته‌ایم). یک export بی‌احتیاط آن را با آرایه‌ی خالی بازنویسی
# می‌کرد و کلِ کار از بین می‌رفت — بدونِ خطا.
only = self.only
if only != 'questions':
_write_json(CURRICULUM_FILE, curriculum)
if only != 'curriculum':
if not rows and _has_questions(QUESTIONS_FILE):
raise CommandError(
f'{QUESTIONS_FILE.name} سوال دارد ولی دیتابیسِ این ماشین ندارد؛ '
'بازنویسی‌اش یعنی پاک شدنشان. اگر کوریکولوم را می‌خواهید: '
'--export --only curriculum'
)
_write_json(QUESTIONS_FILE, {'questions': rows})
lessons = Lesson.objects.filter(node__unit__grade__in=grades).count()
self.stdout.write(self.style.SUCCESS(
f'نوشته شد: {CURRICULUM_FILE.name} '
f'({len(curriculum["grades"])} پایه، {lessons} درس) '
f'و {QUESTIONS_FILE.name} ({len(rows)} سوال)'
))
written = []
if only != 'questions':
written.append(f'{CURRICULUM_FILE.name} '
f'({len(curriculum["grades"])} پایه، {lessons} درس)')
if only != 'curriculum':
written.append(f'{QUESTIONS_FILE.name} ({len(rows)} سوال)')
self.stdout.write(self.style.SUCCESS('نوشته شد: ' + ' و '.join(written)))
skipped = Grade.objects.count() - grades.count()
if skipped:
self.stdout.write(
f' {skipped} پایه‌ی نمونه ([DEMO]/[SEED]) کنار گذاشته شد.'
)
if not rows:
if not rows and only != 'curriculum':
self.stdout.write(self.style.WARNING(
' هشدار: هیچ سوالِ واقعی‌ای برای خروجی نبود — '
'برنامه‌ی درسی بدونِ سوال منتشر می‌شود.'
@@ -134,6 +153,16 @@ class Command(BaseCommand):
return question_io.import_questions(parsed, skip_existing=True)
def _has_questions(path: Path) -> bool:
if not path.exists():
return False
try:
data = json.loads(path.read_text(encoding='utf-8'))
except json.JSONDecodeError:
return False
return bool(data.get('questions') if isinstance(data, dict) else data)
def _write_json(path: Path, payload) -> None:
# ensure_ascii=False تا فارسی در دیفِ گیت خوانا بماند، و newline انتهایی
# تا هر بار export یک دیفِ تمیز بدهد نه یک خطِ تغییرکرده‌ی الکی.