senior's post-demo changes: parent report in the student's own account, Jalali birth date, invoices, math-only tutor, priced quotas

- remove the family app from the root (separate parent accounts, invite codes, parental
  settings); progress.0006 drops its tables, permissions and migration history, and
  Profile.parent_phone, the two family notification types and ?userId= on
  /api/progress/nodes go with it
- GET /api/user/weekly-report: last 7 days next to the 7 before (estimated minutes,
  questions, accuracy, islands, tutor questions) for the "parent report" page
- profile birthDate in Jalali (accounts/jalali.py), ages 5-99, birthYear derived from it
- GET /api/payment/invoices; orders now snapshot list price and plan title
- tutor answers only math: server pre-filter, scope rule in the prompt (tutor-v11),
  refusal marker replaced with fixed text; self-harm gets a help line instead
- streamed answers now record token usage (it was always 0)
- daily tutor quota: AI_DAILY_QUOTA_FREE (default 3) vs Plan.ai_daily_quota
- delete_users management command for wiping test accounts across every app
- docs/ai-cost-and-guardrails.md: measured cost per question and guardrail results

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 02:38:43 +03:30
parent d5dbd1b23d
commit 39247f6d53
67 changed files with 1619 additions and 1140 deletions

View File

@@ -11,7 +11,7 @@ from django.shortcuts import get_object_or_404
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework import status
from rest_framework.exceptions import NotFound, PermissionDenied, ValidationError
from rest_framework.exceptions import NotFound, ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.throttling import ScopedRateThrottle
@@ -51,7 +51,7 @@ logger = logging.getLogger(__name__)
class GuestAwareContentThrottle(ScopedRateThrottle):
"""
مشترک با GuestAwareAIThrottle/GuestAwareFamilyThrottle/GuestAwareDiscountThrottle
مشترک با GuestAwareAIThrottle/GuestAwareDiscountThrottle
و مشابه‌هاش: چون GuestSessionView با هر deviceId جدید یک pk تازه می‌سازه،
throttleِ استانداردِ بر اساسِ user.pk با چرخشِ حسابِ مهمان دور زده می‌شه —
درحالی‌که answer/complete اثرِ جانبیِ ماندگار (هارت، xp/coins/progress) دارن.
@@ -688,37 +688,24 @@ class LessonCompleteView(APIView):
class NodeProgressListView(APIView):
"""
GET /api/progress/nodes?userId=&unit= — لیست وضعیتِ کاربر روی هر Node.
GET /api/progress/nodes?unit= — لیست وضعیتِ خودِ کاربر روی هر Node.
توکن اجباری است. userId قابل قبول است (والد می‌تواند پیشرفت فرزندش را ببیند)،
اما فقط اگر برابر خودِ request.user باشد یا درخواست‌دهنده والدِ تأییدشده‌ی
همان کاربر باشد (FamilyLink با status='accepted')؛ در غیر این صورت 403.
توکن اجباری است و فقط دیتای صاحبِ توکن برمی‌گردد. تا ۲۰۲۶-۱۰-۰۱ پارامترِ
userId داشت تا والدِ وصل‌شده پیشرفتِ فرزند را ببیند؛ بخشِ والدین از ریشه حذف
شد و والد گزارش را در حسابِ خودِ دانش‌آموز می‌بیند (GET /api/user/weekly-report).
"""
permission_classes = [IsAuthenticated]
@extend_schema(
parameters=[
OpenApiParameter('userId', OpenApiTypes.INT, required=False),
OpenApiParameter('unit', OpenApiTypes.INT, required=False),
],
responses=NodeProgressListResponseSerializer,
tags=['Curriculum (content)'],
)
def get(self, request):
requested_user_id = request.query_params.get('userId')
user_id = int(requested_user_id) if requested_user_id else request.user.id
if user_id != request.user.id:
from family.models import FamilyLink
is_verified_parent = FamilyLink.objects.filter(
parent_user_id=request.user.id, child_user_id=user_id, status='accepted',
).exists()
if not is_verified_parent:
raise PermissionDenied('اجازه‌ی مشاهده‌ی پیشرفتِ این کاربر را ندارید.')
qs = NodeProgress.objects.filter(user_id=user_id).select_related('node')
qs = NodeProgress.objects.filter(user_id=request.user.id).select_related('node')
unit_id = request.query_params.get('unit')
if unit_id:
qs = qs.filter(node__unit_id=unit_id)