183 lines
16 KiB
Markdown
183 lines
16 KiB
Markdown
# ساختن و ویرایشِ محتوا
|
||
|
||
هدفِ این مسیر یک جمله است: **محتوا باید بدونِ توسعهدهنده قابلِ ویرایش باشد.**
|
||
اجرای یک management command روی SSH آخرین راهِ چاره است، نه روالِ کار.
|
||
|
||
## محتوا کجا زندگی میکند
|
||
|
||
`content/seed/` کلِ برنامهی درسی و بانکِ سوال را بهصورتِ JSONِ کامیتشده نگه
|
||
میدارد:
|
||
|
||
| فایل | محتوا |
|
||
|---|---|
|
||
| `curriculum.json` | ۱۰ پایه (سوم تا دوازدهم)، ۵ کتاب، ۸۹ فصل، ۱٬۱۴۵ جزیره، ۵٬۴۰۶ درس؛ با `subject`، متنِ درس، مثال، قدمها و شکلِ درس (`figures`) |
|
||
| `questions.json` | ۲۳٬۹۶۴ سوالِ چهارگزینهای، بعضی با شکل (`figure`) |
|
||
|
||
هر فصل باید `subject` داشته باشد (ریاضی، حسابان، هندسه، آمار و احتمال، گسسته)؛ فایلِ بیآن رد میشود. شکلها
|
||
(`Lesson.figures`، `Question.figure`) با `rag.services.diagram.validate` در ورود اعتبارسنجی میشوند و شکلِ نامعتبر کلِ
|
||
فایل را رد میکند. هیچ LaTeX یا markdownی وارد نمیشود: `content/plain_math.py` متن را در ورود ساده میکند.
|
||
|
||
`deploy.sh` در هر انتشار `seed_content` را اجرا میکند، پس یک سرورِ تازه خودش
|
||
را با یک فرمان پر میکند — **بدونِ Qdrant، بدونِ اعتبارِ API، بدونِ نشستِ SSH.**
|
||
|
||
> **کد و محتوا دو مسیرِ جدا دارند.** دیتابیس در گیت نیست. سوالی که
|
||
> فقط در دیتابیسِ محلیِ شماست به سرور نمیرسد مگر از راهِ
|
||
> `seed_content --export --only questions` و کامیتِ `content/seed/questions.json`.
|
||
> پوش کردنِ کد بهتنهایی محتوا را نمیبرد.
|
||
|
||
دو رفتارِ `seed_content` که دانستنشان لازم است:
|
||
|
||
- **idempotent است و هرگز چیزی پاک نمیکند.** هر چه در فایل هست ساخته یا
|
||
بهروز میشود؛ هر چه نیست دستنخورده میماند. پس یک فایلِ ناقص محتوای سرور را
|
||
از بین نمیبرد.
|
||
- **سوالها با مسیرِ عنوان به درس وصل میشوند** (پایه › فصل › نود › درس)، نه با
|
||
کلیدِ اصلی. شناسهها بینِ هر دو دیتابیس فرق دارند و اتصال با id، سوالها را
|
||
بیصدا به درسِ اشتباه میچسباند.
|
||
- **کارِ ادمین را برنمیگرداند** (از ۲۰۲۶-۱۰-۰۳، `content/seed_sync.py`). هر چه
|
||
سید یک بار تحویل داده در دفترِ `SeedRecord` ثبت میشود. اگر ادمین بعداً سوالی را
|
||
پاک کند، صورتش را اصلاح کند یا نامِ فصل و درسی را عوض کند، دیپلوی آن را دوباره
|
||
نمیسازد. متنِ توضیحِ درس فقط وقتی از فایل بهروز میشود که در ادمین دست نخورده
|
||
باشد. محتوای *تازه*ی فایل مثلِ قبل میرسد. پیش از این، هر دیپلوی سوالِ پاکشده را
|
||
برمیگرداند و تغییرِ نامِ یک فصل همهی درسها و سوالهایش را تکراری میکرد.
|
||
دیتابیسِ بیمحتوا (سرورِ تازه) دفتر را نادیده میگیرد و همهچیز را از فایل میسازد.
|
||
نتیجهاش این است که برای برگرداندنِ چیزی که در ادمین پاک شده، همانجا دوباره بسازیدش
|
||
(یا ردیفِ `SeedRecord`ش را پاک کنید)؛ دیپلوی دیگر آن را برنمیگرداند.
|
||
- **مثال، قدمها و شکلِ درس فقط در خانهی خالی نشانده میشوند.** برای عوض کردنِ شکلِ درسی که روی سرور هست، `figures`
|
||
آن درس را در ادمین خالی کنید و دوباره دیپلو کنید. شناسهی درس روی سرور با لپتاپ فرق دارد.
|
||
- **جزیرههای تمرین («تمرین: …») استثنا هستند:** گزینههای سوالِ موجودشان در جا (با همان شناسه) با فایل یکی میشود و
|
||
سوالی که از فایل حذف شده و هنوز جوابی ندارد پاک میشود (`seed_sync._refresh_practice`). سوالِ دستیِ ادمین هرگز.
|
||
|
||
`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ِ قبلی و ساختِ دوباره
|
||
```
|
||
|
||
|
||
## سوالِ شکلدار (از ۲۰۲۶-۱۰-۱۴)
|
||
|
||
آزمونِ جزیره از هر درس یک سوال برمیدارد و اگر درس سوالِ شکلدار داشته باشد، همان را برمیدارد؛ پس افزودنِ سوالِ
|
||
شکلدار، سوالِ بیشکلِ همان درس را از آزمون کنار میزند، بیآنکه چیزی پاک شود. برنامه این است که
|
||
شکلها و سوالهای بیشتری ساخته و کلِ بانک بازبینی شود؛ این سازوکار همان را بیکارِ اضافه ممکن میکند.
|
||
|
||
```bash
|
||
python tools/figures/batch.py spec # کاتالوگِ نوعهای شکل
|
||
python tools/figures/batch.py export "پایه دوازدهم" "فصل ۲" هندسه --out f.json # درسهای فصل
|
||
python tools/figures/batch.py check batch.json # سختگیرانه: آیتمِ حذفشده = رد
|
||
python tools/figures/batch.py merge batch.json # افزودن به questions.json
|
||
```
|
||
|
||
درسهایی که ذاتاً شکل ندارند (تعریف، گزاره، نقیض) را شکل نمیدهیم. بیضی، سهمی و هذلولی نوعِ جدا ندارند:
|
||
با دو `function` (نیمهی بالا و پایین) کشیده میشوند. معادلهی خط را به شکلِ `ax + by = c` بنویسید (`y = -x/3 + 1`
|
||
خوانده نمیشود و شکلِ بیخط درمیآید). بعد از `check`، شکل را حتماً رندر و با چشم ببینید؛ در رندرِ آزمایشیِ
|
||
چند شکل در یک صفحه، به `renderToString` با `identifierPrefix` یکتا برای هر شکل نیاز است، وگرنه `useId` در همهی شکلها
|
||
یکی میشود و clipPathها روی هم میافتند و منحنیها نصفه دیده میشوند (خطای ابزارِ آزمایش، نه اپ).
|
||
فضای سهبعدی: `{"kind":"space",…}` با نقطه، بردار، پارهخط، چندضلعی (صفحه)، جعبه و خطچینهای تصویر؛ تصویرِ مایلِ ثابت.
|
||
|
||
### شکل نباید جواب را لو بدهد
|
||
|
||
گزارشِ مالک (۲۰۲۶-۱۰-۱۵): سوالِ «نقطهی A(۰، ۵، ۰) روی کدام محور است؟» شکلی داشت که نقطه را با همان مختصات روی محور
|
||
نشان میداد؛ شکل جواب را میداد. قاعدهها برای ساختنِ هر سوالِ شکلدار:
|
||
|
||
1. **متنِ شکل** (`title`، برچسبها، `label`) جواب یا چیزی مساوی با آن نباشد. عنوان را خالی بگذارید یا کلی («شکل»)؛
|
||
عنوانِ «هرم با قاعدهی مربع» زیرِ سوالِ «مقطع چیست؟» و «دو دایرهی مماس» زیرِ «چند نقطهی مشترک؟» جواب بود.
|
||
2. **چیزی را که سوال میپرسد نکشید.** اگر متن همهی داده را داده (معادله، مختصات) و شکل نتیجه را هم نشان میدهد،
|
||
شکل کارِ دانشآموز را میکند. یا داده را فقط در شکل بگذارید (و متن آن را نگوید)، یا شکل را برنداشته سوال را بیشکل بگذارید.
|
||
مثالهای بد: نقطهای که «روی دایره است یا نه» و روی دایره کشیده شده؛ «لوزی با یک زاویهی قائمه چه نام دارد» با مربع کشیدهشده؛
|
||
خطی که «وضعیتش نسبت به دایره» پرسیده میشود و مماس کشیده شده؛ «نمودار x = ۱ چیست» با همان خط.
|
||
3. **مقدار را روی مماس، `drop` و ابزارهایی که خودشان عدد مینویسند نگذارید** (ابزارِ `tangent` «شیب = …» را چاپ میکند؛
|
||
مماس را با معادلهی خط بکشید).
|
||
4. هر سوالِ شکلدار را قبل از ادغام یک بار «بهجای دانشآموز» بخوانید: اگر فقط با نگاه به شکل، بیهیچ فکر، جواب میشود،
|
||
دوباره بنویسید یا بیشکل بگذارید.
|
||
|
||
اصلاحِ سوالِ منتشرشده: `tools/figures/apply_fixups.py` (حذف و خالیکردنِ عنوان، هم روی seed هم با `--db` روی سرور،
|
||
پس از `pg_dump`)؛ seed خودش چیزی را بازنویسی نمیکند.
|