Files
MathApp/docs/content-authoring.md
Zareian 0c6b08c9ff make every grade's id its class number and sort grades by it everywhere
Grade ids were the order the books were imported in (ninth=1, eighth=5), so
gradeId: 5 meant eighth grade and the admin, the question filter and the API
all listed grades out of order. Grade.number (1-12) is now read from the title,
new grades take it as their id, and migration 0008 moves existing ids plus every
reference to them: Unit, the loose grade_id in accounts/ai_tutor/rag, and the
grade target inside campaign JSON. Unnumbered demo grades move above 100 and the
id sequence is raised past 12 so they never collide with a real grade.

The frontend needs no change: it always reads gradeId from the server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 22:47:02 +03:30

120 lines
8.1 KiB
Markdown
Raw 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.
# ساختن و ویرایشِ محتوا
هدفِ این مسیر یک جمله است: **محتوا باید بدونِ توسعه‌دهنده قابلِ ویرایش باشد.**
اجرای یک 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 آمده، مسیرِ رایگان این است:
```bash
# اسکلتِ برنامه‌ی درسی از فهرستِ خودِ کتاب (بدونِ هزینه)
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`](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 دیده می‌شوند. محتوای واقعی از
مسیرِ بالا می‌رود، نه از این فرمان.
```bash
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ِ قبلی و ساختِ دوباره
```