Zareian ac93e17305 new app — cleaned-up RAG pipeline (Qdrant retrieval + GPT), ported from Rag_backend
Self-contained app; wired into settings/urls in the next commit. Data
stays in the parallel team's live Qdrant — nothing is copied into our DB.

apps.py
  - RagConfig.
models.py
  - LessonChunk: DB-of-record for one summarized lesson chunk
    (original_text + summary + embedding_vector) mirrored as a single
    Qdrant point; loose lesson_id/grade_id ints, no cross-app FK.
    LessonChunkImage for future multimodal lessons.
migrations/0001_initial.py
  - initial tables for the two models.
services/clients.py
  - lazy, lru_cache'd OpenAI + Qdrant clients (Rag_backend builds them at
    import time and crashes without keys). RagNotConfigured raised when a
    key/lib is missing; Qdrant client uses check_compatibility=False for
    the 1.19-client / 1.17-server minor version gap.
services/config.py
  - rag_is_configured(), collection-name template, grade-title resolution
    (RAG_GRADE_TITLE_OVERRIDES -> content.Grade.title), retrieval top-k.
services/known_collections.py
  - exact live Qdrant catalog: 6 collection names, math chapter-number ->
    exact "فصل N: ..." string tables per (subject, grade), and the
    علوم/اجتماعی lesson-number -> title maps. filter_key_for_subject()
    picks `chapter` for math, `lesson_number` otherwise;
    resolve_math_chapter() maps a plain chapter number (Persian or Latin
    digits) to the stored string.
services/embedding.py
  - embed_text() pinned to OPENAI_EMBEDDING_MODEL (text-embedding-3-small,
    1536d) so vectors match the other team's existing collections.
services/chunking.py
  - split_text_into_chunks(), summarize_chunk() (gpt-4o-mini, JSON out);
    plain exceptions instead of ninja HttpError.
services/qdrant_store.py
  - ensure_collection(), upsert_points(), search(): the lesson filter
    matches the value both as str and native, since Qdrant stores
    lesson_number as int and chapter as string.
services/retrieval.py
  - resolve_target() -> (collection, filter_key, filter_value), reused by
    the cache key and debug block.
  - retrieve_context() -> (chunks, meta): embed -> Qdrant search scoped to
    one lesson/chapter -> top-k. Math payloads (no `summary`) fall back to
    generalSolution/problem/solution; a 404 collection returns empty.
services/generation.py
  - generate_answer() and stream_answer() (token iterator for SSE);
    context-grounded Persian prompt that explains from retrieved text
    rather than refusing.
services/pdf.py
  - textbook PDF -> page images -> gpt-4o vision problem-type extraction
    (adapted from Rag_backend mathapp); returns dicts, no on-disk JSON.
    pymupdf/pdf2image optional — missing libs raise RagNotConfigured.
services/ingest.py
  - ingest_text() / ingest_pdf(): chunk -> summarize -> embed -> persist
    LessonChunk + upsert Qdrant, in one transaction.
services/answer_cache.py
  - short-TTL cache of the final answer keyed by
    sha1(collection | filter_value | normalized-question).
services/tutor.py
  - thin answer_question() (retrieve -> generate) for direct callers.
serializers.py, urls.py, views.py
  - POST /api/rag/ingest/text, POST /api/rag/ingest/pdf (HasInternalServiceKey,
    throttle scope rag_ingest), GET /api/rag/chunks review list.
    RagProviderUnavailable -> clean 503.
admin.py
  - LessonChunk admin with an inline for its images.
management/commands/rag_catalog.py
  - read-only live Qdrant listing (--values, --json); never writes.
tests.py
  - mocked-client tests: ask-with-retrieval, ingest, SSE stream, answer
    cache, and rag_is_configured() gating.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 02:29:54 +03:30

Tadrisino API

بک‌اند اپلیکیشن آموزشی Tadrisino — Django + Django REST Framework.

احراز هویت با OTP، محتوای درسی و پیشرفت کاربر، دستیار هوش مصنوعی، اشتراک و پرداخت، تخفیف، اعلان‌ها، همکاری در فروش، پشتیبانی و حساب خانواده.

اپ مسئولیت
accounts احراز هویت، session، پروفایل کاربر
content پایه‌ها، مباحث، درس‌ها، سوال‌ها، پیشرفت
progress آمار، لیدربرد، streak
ai_tutor مکالمات دستیار هوش مصنوعی
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:

  1. POST /api/auth/session با {"type":"guest","deviceId":"d_test1"} → یک token بگیرید.
  2. روی دکمه‌ی Authorize بزنید و فقط خودِ token رو (بدون کلمه‌ی Bearer) وارد کنید.
  3. برای تست لاگین با شماره: POST /api/auth/otp/request بزنید، یا در محیط توسعه کد 0000 رو با هر شماره‌ای در POST /api/auth/login امتحان کنید (dev-only، نیاز به DEBUG=True).

دستورهای پرکاربرد

python manage.py createsuperuser   # اختیاری، برای /admin
python manage.py test              # اجرای کل تست‌سوییت

۲. راهنمای دیپلوی پروداکشن

متغیرهای محیطی موردنیاز

متغیر لازم در پروداکشن توضیح
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 خودش این رو مدیریت می‌کنه:

  1. اول loki بالا میاد.
  2. alloy و grafana بعد از loki بالا میان (depends_on).
  3. 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 فعلاً پاسخ stub برمی‌گردونه؛ محلِ اتصالِ مدلِ واقعی generate_tutor_answer در ai_tutor/views.py است.
  • ارسالِ OTP از طریقِ کاوه‌نگار (KAVENEGAR_API_KEY) انجام می‌شه؛ شکستِ ارسال به‌جای موفقیتِ ساختگی، خطای صریحِ 503 برمی‌گردونه.
  • هر خطای پیش‌بینی‌نشده (نه فقط خطاهای شناخته‌شده‌ی API) همیشه پاسخِ JSON برمی‌گردونه، نه صفحه‌ی HTML پیش‌فرضِ جنگو، و کامل با traceback لاگ می‌شه (config/exceptions.py).
Description
No description provided
Readme 67 MiB
Languages
Python 96.8%
Shell 2.2%
HTML 0.9%