The README had grown into a 295-line mixture of onboarding, an ops runbook, a content-authoring workflow, bulk question generation and a Docker log stack. A reader arriving at the repo could not tell in ten seconds what this service is, whether it is live, or where to start, and five of its claims no longer matched the code: - it said to export .env values by hand; settings.py has loaded .env through python-dotenv for a while now - it listed six variables as "everything the app actually reads"; .env.example has carried many more than that for some time - it called DJANGO_SECRET_KEY, DJANGO_ALLOWED_HOSTS and CACHE_URL required in production; all three now have working defaults, and the app boots without them on purpose so a partial .env cannot take the service down - it claimed there is no insecure SECRET_KEY fallback; there is one, under DEBUG - it required Python 3.12 while the project runs on 3.11 (Docker uses 3.12) The README now states what the service is, where it runs, how healthy it is and how to get it running in six commands, then links onward. Procedures moved to docs/operations.md, docs/content-authoring.md, docs/architecture.md and docs/docker.md, which is where someone follows a procedure rather than skims it. Nothing was dropped; the deploy one-liner now includes the separate sudo restart that deploy.sh cannot perform itself, and backup/restore is written down for the first time. Every number in the README was measured rather than quoted: content counts from the live /api/health and content/seed/curriculum.json, 55 endpoints from the generated OpenAPI schema, 481 tests from a full run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
119 lines
8.0 KiB
Markdown
119 lines
8.0 KiB
Markdown
# ساختن و ویرایشِ محتوا
|
||
|
||
هدفِ این مسیر یک جمله است: **محتوا باید بدونِ توسعهدهنده قابلِ ویرایش باشد.**
|
||
اجرای یک 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/` → «وارد کردنِ برنامهی درسی از فایل» |
|
||
| گرفتنِ خروجیِ برنامهی درسی | پایهها را انتخاب کنید → اکشنِ «دریافتِ فایلِ برنامهی درسی» |
|
||
|
||
خروجیِ هر کدام **دقیقاً** همان قالبیست که ورودیاش میپذیرد، پس چرخهی
|
||
«بگیر → ویرایش کن → برگردان» بسته است و برای دیدنِ یک نمونه لازم نیست کسی
|
||
مستندات بخواند.
|
||
|
||
دو تضمین:
|
||
|
||
- **سوالها:** فایلِ نامعتبر هیچ چیزی نمینویسد — یا همه وارد میشوند یا هیچکدام.
|
||
- **برنامهی درسی:** چیزی پاک نمیشود، به همان دلیلِ بالا.
|
||
|
||
## ساختِ انبوهِ سوال از یک کتابِ تازه
|
||
|
||
وقتی کتابی تازه روی 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ِ قبلی و ساختِ دوباره
|
||
```
|