let a finished island quiz open the next island, and ground ask-AI in the student's book

The frontend plays node quizzes, but a node only completed once every one of its
8-13 lessons was completed separately, so students were stuck after the first
island of each unit. POST /api/nodes/{id}/quiz/complete now completes the node
(any score; score only sets stars/xp) and returns nextNodeId.

Also closes the other backend gaps found while auditing the frontend:
- /api/ai/ask without lessonId now picks the book from the user's grade instead
  of skipping retrieval, so the "ask Pi" tab is grounded in the textbook.
- guest sessions return a refreshToken; they used to go silently 401 after 1h.
- lesson_complete throttle 10/hour -> 120/hour (a lesson here is 1-2 questions).
- progressPercent is per grade, not over all 2,617 lessons of every grade.
- a one-year plan (299,000 toman) is created by migration when no plan exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 02:49:02 +03:30
parent 0c6b08c9ff
commit b32194497c
21 changed files with 803 additions and 12 deletions

View File

@@ -79,6 +79,26 @@ PaymentRequest را می‌زند، `Order.gateway_authority` را ذخیره م
برمی‌گرداند. `questionCount` جدا از `requested` گزارش می‌شود تا یک نودِ کم‌سوال
شبیهِ خطا به نظر نرسد.
**پایانِ آزمون، پایانِ جزیره است.** `POST /api/nodes/{id}/quiz/complete` با
`{correctCount, totalQuestions}` جزیره را تمام می‌کند و جزیره‌ی بعدی روی نقشه باز
می‌شود — با هر نمره‌ای؛ نمره فقط ستاره و امتیاز را تعیین می‌کند (تصمیمِ صاحبِ
پروژه، ۲۰۲۶-۰۹-۲۴: ساده نگهش داریم). پاسخ `nextNodeId` را هم می‌دهد. امتیاز و
سکه فقط بارِ اول. ردیف‌ها در `UserNodeCompletion`اند؛ `_node_status` نودی را که
آزمونش تمام شده `completed` می‌بیند، و مسیرِ قدیمیِ «همه‌ی درس‌ها تک‌تک» هم هنوز
کار می‌کند.
`progressPercent` در `/api/home` برای همان پایه‌ای است که نقشه‌اش نشان داده
می‌شود: درس‌های تمام‌شده به‌اضافه‌ی همه‌ی درس‌های جزیره‌های تمام‌شده، تقسیم بر
درس‌های آن پایه. (قبلاً مخرج همه‌ی ۲٬۶۱۷ درسِ همه‌ی پایه‌ها بود.)
## دستیار بدونِ درس
صفحه‌ی «از پی بپرس» از نوارِ پایین باز می‌شود و `lessonId` ندارد. در این حالت
`/api/ai/ask` کتاب را از پایه‌ی ذخیره‌شده‌ی خودِ کاربر درمی‌آورد
(`rag.services.lesson_context.resolve_from_grade`) و در کلِ همان کتاب جستجو
می‌کند؛ قبلاً retrieval رد می‌شد و جواب عمومی بود. `lessonId` اگر بیاید همچنان
دقیق‌تر است، چون فصل را هم معلوم می‌کند.
## دیتابیس
هنوز SQLite است (`OPTIONS.timeout=20` تا قفل‌های هم‌زمانیِ نوشتن را تحمل کند).