docs/extraction-playbook.md: چهار تصمیمی که تفاوتِ چند برابری در هزینه و کیفیت ساختند، با عددهای اندازهگیریشدهی واقعی نه تخمین. سرفصلها: انتخابِ مدل از روی اندازهگیری (gpt-4.1-mini هم دقیقتر از gpt-4o بود هم ۳٫۵ برابر ارزانتر، و gemini برخلافِ جدولِ قیمت سه برابر گرانتر)، قاعدهی self-contained، تعمیرِ بکاسلشِ LaTeX، محافظِ شکل، و ساختِ سوال با subagent بهجای API. وجهِ مشترکِ هر چهار مورد: هیچکدام خطا تولید نمیکردند — نه در لاگ، نه در تست. فقط دادهٔ کمتر یا بدتر میساختند. برای همین توصیهی محوریِ فایل این است که قبل از اعتماد به هر ران انبوه، نمونهای ۴۰-۵۰تایی با چشم خوانده شود. تستهای rag هم اینجا میآیند: بازخوانی، تعمیرِ JSON، و محافظِ شکل. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tadrisino API
بکاند اپلیکیشن آموزشی Tadrisino — Django + Django REST Framework.
احراز هویت با OTP، محتوای درسی و پیشرفت کاربر، دستیار هوش مصنوعی، اشتراک و پرداخت، تخفیف، اعلانها، همکاری در فروش، پشتیبانی و حساب خانواده.
| اپ | مسئولیت |
|---|---|
accounts |
احراز هویت، session، پروفایل کاربر |
content |
پایهها، مباحث، درسها، سوالها، پیشرفت |
progress |
آمار، لیدربرد، streak |
ai_tutor |
مکالمات دستیار هوش مصنوعی |
rag |
لایهی RAG: بازیابی از Qdrant + تولید با GPT، پشتِ POST /api/ai/ask |
subscription / payment / discount |
پلن، سفارش، تراکنش، کد تخفیف |
notifications, affiliate, support, family |
امکانات جانبی |
statuslog |
لاگ عمومی تغییر status، مستقل از بقیهی اپها |
۱. راهاندازی محیط توسعه
پیشنیازها
- پایتون ۳.۱۲
- pip
تنظیم محیط
python3 -m venv venv
source venv/bin/activate # ویندوز: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
.env.example (تمام متغیرهایی که واقعاً توسط اپ خونده میشن):
DJANGO_DEBUG=1
DJANGO_SECRET_KEY=
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
CACHE_URL=
KAVENEGAR_API_KEY=
GRAFANA_ADMIN_PASSWORD=admin
مقادیر .env رو به شل خودتون export کنید (یا از direnv/python-dotenv استفاده کنید):
export DJANGO_DEBUG=1 # ویندوز (PowerShell): $env:DJANGO_DEBUG=1
DEBUGدیفالتشFalseاست. برای فعالکردنِ کمککنندههای dev-only در Swagger (DEV_MASTER_OTPو فیلدِdevOtpتوی پاسخها)، محلیDJANGO_DEBUG=1رو ست کنید.
سریعترین راه اجرای پروژه
python manage.py migrate
python manage.py runserver
اپ روی http://127.0.0.1:8000 بالا میاد.
مایگریشنها
python manage.py migrate
دیتای تستی (Seed)
یک دیتاست کوچک و idempotent میسازه که همهی اپها رو پوشش میده (کاربر، محتوا، پیشرفت،
اشتراک، پرداخت، تخفیف، دستیار هوش مصنوعی، اعلان، افیلیت، پشتیبانی، خانواده). اجرای مکرر
امن است — با get_or_create هیچ رکورد تکراریای نمیسازه. پیشفرض فقط وقتی DEBUG=True
اجرا میشه، مگر با --force.
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 و ساختِ دوباره
مستندات API
| Swagger UI | http://127.0.0.1:8000/api/docs/ |
| Redoc | http://127.0.0.1:8000/api/redoc/ |
| OpenAPI schema | http://127.0.0.1:8000/api/schema/ |
| پنل ادمین | http://127.0.0.1:8000/admin/ |
شروع سریع در Swagger:
POST /api/auth/sessionبا{"type":"guest","deviceId":"d_test1"}→ یکtokenبگیرید.- روی دکمهی Authorize بزنید و فقط خودِ
tokenرو (بدون کلمهی Bearer) وارد کنید. - برای تست لاگین با شماره:
POST /api/auth/otp/requestبزنید، یا در محیط توسعه کد 0000 رو با هر شمارهای درPOST /api/auth/loginامتحان کنید (dev-only، نیاز بهDEBUG=True).
دستورهای پرکاربرد
python manage.py createsuperuser # اختیاری، برای /admin
python manage.py test # اجرای کل تستسوییت
دستیار هوش مصنوعی (RAG)
POST /api/ai/ask پاسخ را از روی محتوای واقعیِ کتاب میسازد: embedding سوال →
جستجوی Qdrant محدود به همان درس/فصل → تولید با GPT. تیکههای استفادهشده در
usedChunks برمیگردند.
- نیازمندِ
OPENAI_API_KEYوQDRANT_API_KEYدر.env(اپrag؛ تنظیماتِOPENAI_*/RAG_*درconfig/settings.py). بدونِ کلید، همان پاسخِ stub قبلی. - تست در Swagger:
POST /api/ai/askبا{"userQuestion":"...","subject":"ریاضی","grade":"ششم","lessonNumber":2}. "stream": trueپاسخ را بهصورت SSE توکنبهتوکن میفرستد (قراردادِ JSON بدونِ این فلگ دستنخورده).python manage.py rag_catalogفهرستِ زندهی collectionهای Qdrant را نشان میدهد (فقطخواندنی).- افزودنِ محتوای خودمان:
POST /api/rag/ingest/textو/api/rag/ingest/pdf(داخلی، هدرِX-Internal-Key).
۲. راهنمای دیپلوی پروداکشن
بالا آوردنِ نسخهی جدید — یک دستور
./deploy.sh
همین. اسکریپت به ترتیب کدِ تازه را میگیرد، وابستگیها را نصب میکند، مهاجرتها را اعمال میکند، فایلهای استاتیک را جمع میکند، سرویس را ریاستارت میکند، و در پایان خودش بررسی میکند که واقعاً بالا آمده یا نه. اگر بالا نیامد، علتهای رایج را چاپ میکند.
چرا این مهم است: قبلاً اینها چند کارِ دستی به ترتیبِ درست بودند و فراموش شدنِ
هر کدام اپ را میخواباند، با خطایی که گویا نبود. مثلاً اضافه شدنِ یک پکیجِ جدید
به requirements.txt بدونِ pip install باعثِ ModuleNotFoundError موقعِ بالا
آمدن میشود و کاربر فقط 502 Bad Gateway میبیند. اسکریپت همیشه pip install
را اجرا میکند تا این حالت اصلاً پیش نیاید.
بارِ اول، نامِ سرویس را بدهید تا دفعههای بعد خودکار ریاستارت شود:
TADRISINO_SERVICE=<نامِ سرویس> ./deploy.sh
متغیرهای اختیاری: SKIP_PULL=1 (بدونِ git pull)، TADRISINO_HEALTH_URL،
PIP_INDEX_URL (اگر pypi.org تایماوت میدهد).
بررسیِ سلامت از بیرون
curl https://<host>/api/health
بدونِ احرازِ هویت و بدونِ SSH میگوید دیتابیس سالم است یا نه، مهاجرتِ
اعمالنشده مانده یا نه، کش کار میکند یا نه، و چند پایه/درس/سوال روی آن سرور
هست. 200 یعنی سالم، 503 یعنی چیزی واقعاً خراب است. هیچ سکرتی برنمیگرداند.
این همان چیزیست که جلوی «کد رفته ولی مهاجرت اجرا نشده» را میگیرد — حالتی که اپ ظاهراً بالا میآید و بعد زیرِ بارِ واقعی میشکند.
مدیریتِ محتوا بدونِ ترمینال
هیچ محتوایی لازم نیست با دستور روی سرور گذاشته شود.
| کار | کجا |
|---|---|
| افزودن/ویرایشِ یک سوال | /admin/content/question/ → «افزودن» (انتخابِ درس با جستجو) |
| وارد کردنِ فلهای سوال | همان صفحه → «وارد کردنِ سوالها از فایل» |
| گرفتنِ خروجیِ سوالها | چند سوال را انتخاب کنید → اکشنِ «دریافتِ فایل» |
| وارد کردنِ برنامهی درسی (پایه/فصل/درس) | /admin/content/grade/ → «وارد کردنِ برنامهی درسی از فایل» |
| گرفتنِ خروجیِ برنامهی درسی | پایهها را انتخاب کنید → اکشنِ «دریافتِ فایلِ برنامهی درسی» |
خروجیِ هر کدام دقیقاً همان قالبیست که ورودیاش میپذیرد، پس چرخهی «بگیر → ویرایش کن → برگردان» بسته است و برای گرفتنِ نمونه لازم نیست کسی مستندات بخواند.
دو تضمین:
- سوالها: فایلِ نامعتبر هیچ چیزی نمینویسد — یا همه وارد میشوند یا هیچکدام.
- برنامهی درسی: چیزی پاک نمیشود. هر چه در فایل هست ساخته/بهروز میشود و هر چه نیست دستنخورده میماند، پس یک فایلِ ناقص محتوای سرور را از بین نمیبرد.
دادهی تستی روی سرور
seed_test_data فقط برای محیطِ لوکال است و نباید روی سروری که کاربرِ واقعی
دارد اجرا شود؛ ردیفهایی با پیشوندِ [SEED]/[DEMO] میسازد که در API دیده
میشوند. محتوای واقعی از مسیرِ جدولِ بالا میرود، نه از این دستور.
متغیرهای محیطی موردنیاز
| متغیر | لازم در پروداکشن | توضیح |
|---|---|---|
DJANGO_DEBUG |
باید ستنشده یا 0 باشه |
دیفالتهای امنیتیِ پروداکشن رو فعال میکنه |
DJANGO_SECRET_KEY |
بله | بدونش وقتی DEBUG=False است اپ اصلاً بالا نمیاد |
DJANGO_ALLOWED_HOSTS |
بله | لیستِ host ها، کاما-جدا |
CACHE_URL |
بله | آدرسِ Redis، مثلاً redis://host:6379/0 — برای rate limitِ مشترک وقتی DEBUG=False لازمه |
KAVENEGAR_API_KEY |
بله | ارسال واقعیِ OTP از طریق کاوهنگار |
GRAFANA_ADMIN_PASSWORD |
فقط اگه از استکِ Grafana استفاده میکنید | بخشِ Docker پایین رو ببینید |
تنظیمات پروداکشن
DEBUGباید ست نشه (یا0باشه). باDEBUG=False:SECRET_KEYاجباریه — هیچ fallback ناامنی وجود نداره.CACHE_URL(Redis) اجباریه —LocMemCacheفقط برای dev هست.DEV_MASTER_OTPو فیلدِdevOtpتوی پاسخها غیرفعال میشن.
- برای ترافیک واقعی بهجای SQLیت پیشفرض، از یک دیتابیسِ پروداکشنگرید استفاده کنید (پروژه با همون SQLite شیپ میشه؛ عوضکردنِ engine یک تصمیمِ زیرساختیه، خارج از این ریپو).
- از طریق یک سرورِ WSGI/ASGI (مثلاً gunicorn) جلوی اپ سرو کنید — تنظیماتِ Docker موجود
همچنان
runserverاجرا میکنه و فقط برای dev/local مستند شده (پایین رو ببینید).
تنظیمات Docker/دیپلوی
docker-compose.yml موجود، اپ رو بههمراه یک استکِ لاگِ متمرکز
(Loki + Grafana Alloy + Grafana) اجرا میکنه. این یک تنظیمِ dev/local هست، نه دیپلویِ
پروداکشن (سرویسِ django همچنان runserver اجرا میکنه، نه یک سرورِ پروداکشن).
cp .env.example .env # اختیاری — بدونش compose از دیفالتهای dev استفاده میکنه
docker compose up --build
| سرویس | آدرس | توضیح |
|---|---|---|
| اپ Django | http://localhost:8000 |
|
| Grafana | http://localhost:3000 |
دسترسیِ anonymous admin، فقط محیطِ محلی |
| Loki | http://localhost:3100 |
ذخیرهسازیِ لاگ، در معرضِ کاربر نیست |
| Alloy | http://localhost:12345 |
رابطِ دیباگِ اختیاری |
ترتیبِ لازمِ بالا آمدن
Compose خودش این رو مدیریت میکنه:
- اول
lokiبالا میاد. alloyوgrafanaبعد ازlokiبالا میان (depends_on).djangoهیچ وابستگیای به استکِ لاگ نداره — خودشmigrateبعدrunserverرو اجرا میکنه و منتظرِ Loki/Alloy/Grafana نمیمونه.
دستورهای manage.py داخلِ Docker
اگه فقط از طریقِ Docker اجرا میکنید (بدونِ venv محلی)، python manage.py ... رو مستقیم
روی سیستم خودتون اجرا نکنید — باید داخلِ کانتینرِ django اجرا بشه:
docker compose exec django python manage.py createsuperuser
docker compose exec django python manage.py seed_test_data
docker compose exec django python manage.py test
(migrate نیازی به این نیست — طبقِ بالا، خودکار قبل از runserver اجرا میشه.)
مایگریشنها
python manage.py migrate --noinput
در Docker، این بهصورت خودکار بهعنوان بخشی از دستورِ سرویسِ django، قبل از runserver اجرا میشه.
راهنمای seed برای staging/تست
- هرگز
seed_test_dataرو روی یک دیتابیسِ پروداکشنِ واقعی اجرا نکنید. - روی staging (
DEBUG=False)، صریحاً با--forceاجرا کنید:python manage.py seed_test_data --force - برای پاککردن و ساختِ دوبارهی دیتای seed بین چرخههای تست، از
--resetاستفاده کنید. همهی رکوردهای seedشده در فیلدهای متنیِ قابلمشاهده با تگِ[SEED]مشخص شدن، برای شناساییِ راحتتر و پاکسازی.
چطور API دیپلویشده رو verify کنیم
curl -i https://<host>/api/schema/ # باید 200 برگردونه (OpenAPI schema)
curl -i https://<host>/api/docs/ # باید 200 برگردونه (Swagger UI)
curl -i -X POST https://<host>/api/auth/session \
-H "Content-Type: application/json" \
-d '{"type":"guest","deviceId":"verify-1"}' # باید یک token برگردونه
نکات مهمِ پروداکشن
DEV_MASTER_OTP(کد0000) و فیلدِdevOtpتوی پاسخها فقط وقتیDEBUG=Trueوجود دارن — قبل از رفتن روی محیطِ واقعی مطمئن بشیدDEBUGخاموشه.- احراز هویت با Bearer token امضاشده و stateless (
django.core.signing)، هماهنگ با مدلSession— بدون کتابخانهی JWT خارجی. accounts.Userیک مدل بیزنسیِ مستقله، نهAUTH_USER_MODELجنگو.- همهی لاگهای ساختاریافتهی درخواست (
method/path/status/duration/user، بدونِ بدنه/هدر/سکرت) روی stdout و — وقتیLOG_DIRست باشه (مثلِ داخلِ Docker) — در/var/log/django/app.logنوشته میشن؛ قابلِ مشاهده در Grafana از طریقِ استکِ بالا. POST /api/ai/askبا تنظیمِOPENAI_API_KEY/QDRANT_API_KEYپاسخِ واقعیِ RAG میدهد (اپrag)؛ بدونِ کلید همان stub. جزئیات: بخشِ «دستیار هوش مصنوعی (RAG)» بالاتر.- ارسالِ OTP از طریقِ کاوهنگار (
KAVENEGAR_API_KEY) انجام میشه؛ شکستِ ارسال بهجای موفقیتِ ساختگی، خطای صریحِ503برمیگردونه. - هر خطای پیشبینینشده (نه فقط خطاهای شناختهشدهی API) همیشه پاسخِ JSON برمیگردونه،
نه صفحهی HTML پیشفرضِ جنگو، و کامل با traceback لاگ میشه (
config/exceptions.py).