Files
MathApp/docs/content-authoring.md
Zareian 93b451d422 list a user's own support tickets, Persian digits in completion notifications, rewrite .env.example to match the code
GET /api/support/tickets returns the caller's last 20 tickets with their
support-reply count, on the read throttle so listing never spends the
30/hour create quota. Before it the app kept ticket ids only in one
browser's localStorage, so a ticket and its answer vanished with the device.

Completion notifications now use Persian digits and drop the
'with 0 stars ... 0 points' sentence when nothing was earned.

.env.example: NUM_PROXIES=2 (ArvanCloud + nginx), KAVENEGAR_OTP_TEMPLATE,
BAZAAR_PISHKHAN_API_SECRET, sandbox off by default; removed variables the
code no longer reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:18:12 +03:30

8.1 KiB
Raw Blame History

ساختن و ویرایشِ محتوا

هدفِ این مسیر یک جمله است: محتوا باید بدونِ توسعه‌دهنده قابلِ ویرایش باشد. اجرای یک management command روی SSH آخرین راهِ چاره است، نه روالِ کار.

محتوا کجا زندگی می‌کند

content/seed/ کلِ برنامه‌ی درسی و بانکِ سوال را به‌صورتِ JSONِ کامیت‌شده نگه می‌دارد:

فایل محتوا
curriculum.json ۸ پایه، ۵۸ فصل، ۲۶۳ نود، ۲٬۶۱۷ درس
questions.json ۲٬۵۴۵ سوالِ چندگزینه‌ای

deploy.sh در هر انتشار seed_content را اجرا می‌کند، پس یک سرورِ تازه خودش را با یک فرمان پر می‌کند — بدونِ Qdrant، بدونِ اعتبارِ API، بدونِ نشستِ SSH.

کد و محتوا دو مسیرِ جدا دارند. db.sqlite3 در gitignore است. سوالی که فقط در دیتابیسِ محلیِ شماست به سرور نمی‌رسد مگر از راهِ seed_content --export --only questions و کامیتِ content/seed/questions.json. پوش کردنِ کد به‌تنهایی محتوا را نمی‌برد.

دو رفتارِ seed_content که دانستنشان لازم است:

  • idempotent است و هرگز چیزی پاک نمی‌کند. هر چه در فایل هست ساخته یا به‌روز می‌شود؛ هر چه نیست دست‌نخورده می‌ماند. پس یک فایلِ ناقص محتوای سرور را از بین نمی‌برد.
  • سوال‌ها با مسیرِ عنوان به درس وصل می‌شوند (پایه › فصل › نود › درس)، نه با کلیدِ اصلی. شناسه‌ها بینِ هر دو دیتابیس فرق دارند و اتصال با id، سوال‌ها را بی‌صدا به درسِ اشتباه می‌چسباند.

seed_content --export فایل‌ها را از دیتابیسِ محلی بازنویسی می‌کند. اگر دیتابیس سوالی نداشته باشد از بازنویسیِ یک questions.json غیرخالی خودداری می‌کند (این قبلاً فایل را بی‌صدا پاک می‌کرد)؛ وقتی فقط برنامه‌ی درسی را می‌خواهید --export --only curriculum بزنید.

از پنلِ ادمین (روالِ عادی)

کار کجا
افزودن یا ویرایشِ یک سوال /admin/content/question/ → «افزودن» (انتخابِ درس با جستجو)
وارد کردنِ فله‌ای سوال همان صفحه → «وارد کردنِ سوال‌ها از فایل»
گرفتنِ خروجیِ سوال‌ها چند سوال را انتخاب کنید → اکشنِ «دریافتِ فایل»
وارد کردنِ برنامه‌ی درسی /admin/content/grade/ → «وارد کردنِ برنامه‌ی درسی از فایل»
گرفتنِ خروجیِ برنامه‌ی درسی پایه‌ها را انتخاب کنید → اکشنِ «دریافتِ فایلِ برنامه‌ی درسی»
افزودنِ یک پایه‌ی تازه /admin/content/grade/add/ — عنوان به شکلِ «پایه چهارم»؛ شماره و id خودکار ۴ می‌شوند

خروجیِ هر کدام دقیقاً همان قالبی‌ست که ورودی‌اش می‌پذیرد، پس چرخه‌ی «بگیر → ویرایش کن → برگردان» بسته است و برای دیدنِ یک نمونه لازم نیست کسی مستندات بخواند.

دو تضمین:

  • سوال‌ها: فایلِ نامعتبر هیچ چیزی نمی‌نویسد — یا همه وارد می‌شوند یا هیچ‌کدام.
  • برنامه‌ی درسی: چیزی پاک نمی‌شود، به همان دلیلِ بالا.

ساختِ انبوهِ سوال از یک کتابِ تازه

وقتی کتابی تازه روی Qdrant آمده، مسیرِ رایگان این است:

# اسکلتِ برنامه‌ی درسی از فهرستِ خودِ کتاب (بدونِ هزینه)
python manage.py seed_curriculum_from_textbook --subject ریاضی --grade نهم --dry-run
python manage.py seed_curriculum_from_textbook --subject ریاضی --grade نهم

# جفت‌های (درس، مسئله‌ی تأییدشده) را برای سوال‌نویسی بیرون بده (بدونِ هزینه)
python manage.py generate_questions_from_chunks --subject ریاضی --grade نهم \
  --export /tmp/batches

--dry-run روی هر دو کار می‌کند و پیش از هر نوشتنی نشان می‌دهد چه ساخته خواهد شد.

خروجی چند فایلِ JSON است به‌همراهِ یک *-RULES.txt که عینِ همان پرامپتی را دارد که به مدلِ پولی می‌رفت — تا کیفیتِ دو مسیر از هم جدا نیفتد. سوال‌ها را با آن قواعد بنویسید و از «وارد کردنِ فله‌ای سوال» در جدولِ بالا واردشان کنید.

همان فرمان بدونِ --export کار را به مدلِ پولی می‌سپارد. این کار را نکنید مگر دلیلِ خاصی داشته باشید: اندازه‌گیری‌شده، گران‌تر است و کیفیتِ بهتری نمی‌دهد. جزئیات و هزینه‌های واقعی در extraction-playbook.md.

کیفیتِ محتوا

کتاب‌ها با یک مدلِ ویژن از تصویرِ صفحه خوانده می‌شوند، پس خطای استخراج از همان‌جا وارد می‌شود: رقمِ بدخوانده‌شده، جداکننده‌ی اعشارِ افتاده، راه‌حلی که به مسئله‌ی بغلی چسبیده. این‌ها در بازیابی دیده نمی‌شوند و فقط وقتی لو می‌روند که چیزی به متن اعتماد کند.

rag_verify_solutions هر مسئله‌ی استخراج‌شده را مستقلاً دوباره حل می‌کند و نتیجه را مقایسه می‌کند. LessonChunk.solution_verdict سه‌حالته است — agrees / disagrees / unjudgeable. ساختِ سوال فقط از chunkهای agrees می‌خواند.

دو درسی که گران تمام شدند و حالا در پرامپت‌ها کدگذاری شده‌اند:

  • رأیِ دوحالته غلط است. با دو حالت، هر «نمی‌توانم قضاوت کنم» تبدیل به «غلط» می‌شود و نرخِ خطا را حدودِ سه برابرِ واقعیت گزارش می‌کند. پیش از اعتماد به هر اجرای انبوه، چند نمونه را با چشم بخوانید.
  • ۳/۱۰۴ در این کتاب‌ها عددِ اعشاریِ ۳٫۱۰۴ است، نه کسرِ ۳ تقسیم بر ۱۰۴.

خودِ solution هرگز بازنویسی نمی‌شود — متنِ استخراج‌شده به‌عنوانِ سند می‌ماند و پاسخِ مستقل جداگانه در checked_answer ذخیره می‌شود.

solution_verdict='agrees' تضمینِ کامل نیست، فقط فیلترِ اول: چند بار خطای حسابِ خودِ کتاب در همین مرحله گرفته شده است.

داده‌ی ساختگی برای توسعه

seed_test_data یک دیتاستِ کوچک و idempotent می‌سازد که همه‌ی اپ‌ها را پوشش می‌دهد. هرگز روی سروری که کاربرِ واقعی دارد اجرا نشود — ردیف‌هایی با پیشوندِ [SEED]/[DEMO] می‌سازد که در API دیده می‌شوند. محتوای واقعی از مسیرِ بالا می‌رود، نه از این فرمان.

python manage.py seed_test_data              # فقط با DEBUG=True اجرا می‌شود
python manage.py seed_test_data --force      # staging، با DEBUG=False
python manage.py seed_test_data --reset      # پاک کردنِ داده‌ی seedِ قبلی و ساختِ دوباره