Grades 4 and 5, Bazaar purchase from the app, pack SKUs

- rag: MATH_4 / MATH_5 textbooks (TOC and page offsets read from the PDFs)
- content: grades 4 and 5 in the seed files: 7 units / 28 nodes each,
  204 + 194 questions (every node >= 4), all re-solved blind once
- subscription: Plan.bazaar_sku (admin); POST /api/subscription/purchase
  accepts paymentMethod=bazaar for packs, rejects unknown methods (was ignored)
- payment: idempotency key cannot switch provider; discount codes refused
  on Bazaar orders (Bazaar charges the fixed SKU price)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 20:01:44 +03:30
parent ab5e53730a
commit 8cb326ba94
15 changed files with 16699 additions and 3897 deletions

View File

@@ -95,6 +95,112 @@ MATH_3 = Textbook(
),
)
# چهارم و پنجم: درخواستِ دکتر (۲۰۲۶-۱۰-۰۳) که «از سوم به بعد» همه‌ی پایه‌ها باشند.
# فهرست و offset از خودِ PDFها خوانده شد: در چهارم PDF ۸ = صفحه‌ی چاپیِ ۲، در
# پنجم (چاپِ تازه‌ی ۱۴۰۵) شماره‌ی PDF همان شماره‌ی چاپی است. «حلِ مسئله»ی آغازِ
# هر فصل جزءِ درسِ اول حساب می‌شود (`_sectioned` درسِ اول را تا ابتدای فصل
# عقب می‌کشد) و «مرور فصل» جزءِ درسِ آخر.
MATH_4 = Textbook(
subject='ریاضی', grade='چهارم', grade_label='چهارم دبستان',
filename='ریاضی چهارم.pdf', page_offset=6,
chapters=_ch(
(1, 'فصل ۱: اعداد و الگوها', 1, 23, (
(1, 'عددنویسی', 4),
(2, 'الگوها', 8),
(3, 'ماشین ورودی ـ خروجی', 12),
(4, 'معرفی میلیون', 16),
)),
(2, 'فصل ۲: کسر', 24, 45, (
(1, 'شناخت کسرها', 26),
(2, 'جمع و تفریق کسرها', 30),
(3, 'تساوی کسرها', 34),
(4, 'ضرب عدد در کسر', 38),
)),
(3, 'فصل ۳: ضرب و تقسیم', 46, 75, (
(1, 'ضرب دو عدد دورقمی', 48),
(2, 'محاسبهٔ حاصل‌ضرب', 52),
(3, 'محاسبه‌های تقریبی', 56),
(4, 'تقسیم و بخش‌پذیری', 60),
(5, 'تقسیم بر عددهای یک‌رقمی', 64),
(6, 'تقسیم بر عددهای دورقمی', 68),
)),
(4, 'فصل ۴: اندازه‌گیری', 76, 97, (
(1, 'زاویه', 78),
(2, 'اندازه‌گیری زاویه', 82),
(3, 'اندازه‌گیری زمان', 86),
(4, 'اندازه‌گیری طول', 90),
)),
(5, 'فصل ۵: عدد مخلوط و عدد اعشاری', 98, 119, (
(1, 'عدد مخلوط', 100),
(2, 'عدد اعشاری', 104),
(3, 'جمع و تفریق اعشاری', 108),
(4, 'ارزش مکانی عددهای اعشاری', 112),
)),
(6, 'فصل ۶: شکل‌های هندسی', 120, 141, (
(1, 'عمود و موازی', 122),
(2, 'چهارضلعی‌ها', 126),
(3, 'مساحت متوازی‌الاضلاع و مثلث', 130),
(4, 'محیط و مساحت', 134),
)),
(7, 'فصل ۷: آمار و احتمال', 142, 154, (
(1, 'نمودار خط شکسته', 144),
(2, 'احتمال', 148),
)),
),
)
# شروعِ هر فصل یک صفحه پیش از درسِ اولش گرفته شد (صفحه‌ی جداکننده)؛ فقط برای
# فصل ۱ با چشم دیده شد (PDF ۱۱). جداکننده‌ی بقیه اگر دو صفحه باشد، یک صفحه‌ی
# تصویریِ بی‌مسئله به درسِ آخرِ فصلِ قبل می‌افتد. ۲۰۷–۲۰۸ پشتِ جلد است.
MATH_5 = Textbook(
subject='ریاضی', grade='پنجم', grade_label='پنجم دبستان',
filename='ریاضی پنجم.pdf', page_offset=0,
chapters=_ch(
(1, 'فصل ۱: عددنویسی و الگوها', 11, 38, (
(1, 'یادآوری عددنویسی و محاسبات عددی', 12),
(2, 'معرفی میلیارد', 18),
(3, 'جمع و تفریق عددهای مرکب', 25),
(4, 'الگوها', 30),
)),
(2, 'فصل ۲: کسر', 39, 76, (
(1, 'کسرهای بزرگ‌تر از واحد', 40),
(2, 'جمع و تفریق عددهای مخلوط', 46),
(3, 'ضرب کسرها', 53),
(4, 'تقسیم کسرها', 59),
(5, 'ضرب عددهای مخلوط', 66),
)),
(3, 'فصل ۳: نسبت، تناسب و درصد', 77, 102, (
(1, 'نسبت', 78),
(2, 'نسبت‌های مساوی', 83),
(3, 'تناسب', 88),
(4, 'درصد', 93),
)),
(4, 'فصل ۴: تقارن و چندضلعی‌ها', 103, 128, (
(1, 'تقارن محوری', 104),
(2, 'تقارن مرکزی', 110),
(3, 'زاویه و نیمساز', 113),
(4, 'چندضلعی‌ها و مجموع زاویه‌های آن‌ها', 118),
)),
(5, 'فصل ۵: عددهای اعشاری', 129, 156, (
(1, 'عددهای اعشاری', 130),
(2, 'جمع عددهای اعشاری', 139),
(3, 'تفریق عددهای اعشاری', 143),
(4, 'ضرب عددهای اعشاری', 147),
)),
(6, 'فصل ۶: اندازه‌گیری', 157, 184, (
(1, 'مساحت لوزی و ذوزنقه', 158),
(2, 'محیط دایره', 165),
(3, 'حجم', 170),
(4, 'گنجایش', 177),
)),
(7, 'فصل ۷: آمار و احتمال', 185, 206, (
(1, 'جمع‌آوری و نمایش داده‌ها', 186),
(2, 'میانگین', 193),
(3, 'احتمال', 197),
)),
),
)
# هشتم تنها کتابی‌ست که هم PDF دارد و هم روی Qdrant نیست — یعنی فصل‌بندی و
# درس‌هایش کاملاً از فهرستِ خودِ کتاب می‌آید و ingest دقیقاً همین رشته‌ها را
# می‌نویسد. «مرور فصل» جزء آخرین درسِ هر فصل حساب شده است.
@@ -365,7 +471,7 @@ CALCULUS_12 = Textbook(
TEXTBOOKS: dict[str, Textbook] = {
b.key: b for b in (MATH_3, MATH_6, MATH_7, MATH_8, MATH_9, MATH_10,
b.key: b for b in (MATH_3, MATH_4, MATH_5, MATH_6, MATH_7, MATH_8, MATH_9, MATH_10,
CALCULUS_11, CALCULUS_12)
}