Lesson content complete: examples/steps for all 3,778 lessons, 1,775 lessons with fixed figures, 38 quiz fixes

- curriculum.json: example/steps for every real lesson, figures (2,224) hand-written and visually checked
- questions.json: 3,703 questions (24 added, 14 keys fixed)
- gen_lesson_figures: strict check, thin-figure filter, recheck, --max-usd; API figures limited to plane/fraction/numberline/solid

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 01:29:36 +03:30
parent 4821df3914
commit 293b916544
3 changed files with 97270 additions and 5268 deletions

View File

@@ -1,16 +1,28 @@
""" """
شکلِ ثابتِ درس‌ها را یک بار می‌سازد و در `Lesson.figures` می‌نویسد. شکلِ ثابتِ درس‌ها را یک بار می‌سازد و در `Lesson.figures` می‌نویسد.
همان مسیرِ ارزانِ دستیار: همان مدلِ چت، همان کاتالوگِ <diagram>، همان همان مسیرِ ارزانِ دستیار: همان مدلِ چت، همان کاتالوگِ <diagram>، همان اعتبارسنجِ
اعتبارسنجِ `rag.services.diagram.validate`. فرقش این است که نتیجه ذخیره می‌شود و `rag.services.diagram.validate`. فرقش این است که نتیجه ذخیره می‌شود و بعد از تأییدِ چشمی با
بعد از تأییدِ چشمی با `seed_content --export` به curriculum.json می‌رود. `seed_content --export` به curriculum.json می‌رود.
فقط درس‌هایی که شکل لازم دارند شکل می‌گیرند؛ مدل می‌تواند `[]` بدهد.
آزموده نشده: درستیِ هندسی/ریاضیِ شکل؛ باید با چشم دید.
python manage.py gen_lesson_figures --grade 12 --limit 4 [--redo] [--dry-run] سه چیز که هزینه و خطا را پایین می‌آورد:
1. **پیش‌صافیِ رایگان:** اگر متنِ درس به هیچ بخشِ کاتالوگ نمی‌خورد (حسابِ عددیِ ساده)، مدل صدا
زده نمی‌شود و درس «بی‌نیازِ شکل» ثبت می‌شود.
2. **فقط بخش‌های مربوطِ کاتالوگ** (نه کلِ آن، ~۳٬۵۰۰ توکن) کنارِ درس می‌رود.
3. **گامِ وارسیِ مستقل:** هر شکلی که مدل ساخت، مدلِ دوم با متنِ درس مقایسه می‌کند؛ شکلی که
همان چیزِ متن را نشان نمی‌دهد (عدد/برچسبِ ناهمخوان، قدمِ اشتباه) ذخیره نمی‌شود.
سقفِ هزینه با فرمولِ رسمیِ gpt-4.1-mini محاسبه می‌شود (۰٫۴۰ و ۱٫۶۰ دلار در هر میلیون توکن). صورتحسابِ
واقعیِ GapGPT در سنجش‌های قبلی چند برابر کمتر بود، پس سقف محافظه‌کارانه است. آزموده نشده: درستیِ
هندسی/ریاضیِ شکل؛ وارسیِ مدلِ دوم جایگزینِ دیدنِ چشمی نیست.
python manage.py gen_lesson_figures --grade 12 --limit 20 --max-usd 2 --ledger fig_ledger.json
""" """
import json import json
import re import re
import threading
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
@@ -18,85 +30,259 @@ from content.models import Lesson
from rag.services import clients, diagram as diagram_mod from rag.services import clients, diagram as diagram_mod
from rag.services.generation import chat_model_for, completion_kwargs from rag.services.generation import chat_model_for, completion_kwargs
USD_PER_M_IN = 0.40
USD_PER_M_OUT = 1.60
MAX_FIGURES = 3
PROMPT = ( PROMPT = (
'تو برای یک درسِ ریاضی (اپِ آموزشی) شکل‌های آموزشی می‌سازی. متنِ درس: «توضیح»، «مثال» و ' 'تو برای یک درسِ ریاضی (اپِ آموزشی) شکل‌های آموزشی می‌سازی. متنِ درس: «توضیح»، «مثال» و «قدم‌ها» '
'«قدم‌ها» (شماره‌ی قدم‌ها از ۱، همان شماره‌ای که کاربر می‌بیند). فقط جاهایی شکل بگذار که دیدنش مفهوم را روشن می‌کند (نمودار، ' '(شماره‌ی قدم‌ها از ۱، همان شماره‌ای که کاربر می‌بیند).\n'
'انتقال، تقارن، محور، جدول، هندسه)؛ هر شکل باید دقیقاً همان چیزی را نشان دهد که آن بخش ' 'قاعده‌ها:\n'
'می‌گوید، با همان اعداد. بخشِ بی‌نیاز شکل نمی‌گیرد؛ اگر هیچ‌جا لازم نبود []. حداکثر ۵ شکل.\n' f'- فقط جایی شکل بگذار که دیدنش مفهوم یا مسئله را روشن می‌کند؛ حداکثر {MAX_FIGURES} شکل. '
'خروجی فقط یک آرایه‌ی JSON: [{"where":"intro"|"example"|"step","index":0,"diagram":{...}}] ' 'شکلِ تزیینی یا تکراری نساز. اگر هیچ‌جا لازم نبود []. ترجیح با «example» و «step» است؛ '
'(index فقط برای step و همان شماره‌ی ۱‌به‌بعدِ قدم). بدونِ توضیح و بدونِ ```.\n' '«intro» فقط وقتی متنِ توضیح خودش عدد یا نمادِ مشخص می‌دهد.\n'
'مثال‌های کاتالوگ فقط قالب‌اند: نقطه، تابع یا آیتمی را که این درس لازم ندارد کپی نکن؛ فقط ' '- هر شکل باید دقیقاً همان را نشان دهد که آن بخش می‌گوید، با همان اعداد و نام‌ها؛ چیزی که در '
'چیزهایی بگذار که همین شکل نشان می‌دهد (برای نمودارِ تابع معمولاً فقط items با type=function). نمودارِ خط‌شکسته یا تکه‌ای از نقطه‌ها را ' 'متن نیست اختراع نکن. برای بخشی که در متن نیست (مثال/قدم) شکل نساز.\n'
'با type=segment بین نقطه‌های متوالی بکش، نه type=line (آن خطِ بی‌پایان است). نقطه‌هایی که ' '- نمودارِ خط‌شکسته یا تکه‌ای را با type=segment بین نقطه‌های متوالی بکش، نه type=line. '
'در items نمی‌آیند تعریف نکن. شکلِ هر قدم باید همان چیزی را نشان دهد که متنِ همان قدم می‌گوید ' 'برای نمودارِ تابع بازه‌ی x را کوچک بگیر (مقدارِ y از حدود ۹ بیشتر نشود) و فقط items لازم را بگذار؛ '
'(نه قدمِ قبل یا بعد). بازه‌ی x نمودارِ تابع را کوچک بگیر (مثلاً دو واحد دور از رأس/نقطه‌های مهم) تا ' 'marks فقط برای یک منحنی. نقطه‌هایی که در items نمی‌آیند تعریف نکن.\n'
'مقدارِ y از حدود ۹ بیشتر نشود و منحنی له نشود؛ marks فقط برای یک منحنی بگذار تا برچسب‌ها روی هم نیفتند.\n\n' '- شکلِ فقط‌نقطه (چند نقطه‌ی بی‌ارتباط) نساز؛ نقطه را روی نمودار یا شکلِ مربوطش بگذار. ضابطه‌ی تابع را '
'عیناً از متن بردار (ضریب و انتقال را حذف نکن). برای مثلثِ متساوی‌الاضلاع/متساوی‌الساقین و مانندش '
'طول‌ها را با نوعش سازگار بده. نوعِ pattern/groups را فقط وقتی بگذار که خودِ متن شکل یا شمارشِ مشخصی می‌دهد.\n'
'- شکلِ هر قدم همان را نشان دهد که متنِ همان قدم می‌گوید (نه قدمِ قبل یا بعد).\n'
'- مثال‌های کاتالوگ فقط قالب‌اند؛ نقطه، تابع یا آیتمی را که این درس لازم ندارد کپی نکن.\n'
'خروجی فقط یک آرایه‌ی JSON: [{"where":"intro"|"example"|"step","index":1,"diagram":{...}}] '
'(index فقط برای step و همان شماره‌ی ۱‌به‌بعدِ قدم). بدونِ توضیح و بدونِ ```.\n\n'
) )
CHECK = (
'بازبینِ سخت‌گیرِ ریاضی هستی. یک درس و شکل‌هایی که برایش ساخته‌اند (هر شکل یک JSON) داده می‌شود. '
'برای هر شکل ابتدا در یک جمله بنویس «shows»: دقیقاً چه چیزی می‌کشد (ضابطه‌ی هر تابع، مختصات نقطه‌ها، '
'طول ضلع‌ها و زاویه‌ها، بازه‌ها، عددهای جدول، موقعیت و برچسب‌ها). بعد با متنِ همان بخشِ درس مقایسه کن. '
'ok=false بده اگر: ضابطه یا عددی با متن نمی‌خواند (مثلاً ضریب یا انتقال گم شده)؛ شکل از نظر ریاضی/هندسی '
'ناسازگار است (مثلاً مثلثِ متساوی‌الاضلاع با ضلع‌های نابرابر)؛ موقعیتِ نقطه‌ها با برچسبشان نمی‌خواند؛ '
'شکل مفهومِ اصلیِ آن بخش را نشان نمی‌دهد یا فقط نقطه‌های پراکنده/برچسبِ بی‌معنی است؛ چیزی اختراع شده؛ '
'شکلِ قدم به همان قدم مربوط نیست. هر شکِ جدی → ok=false. سلیقه‌ای ایراد نگیر. '
'خروجی فقط JSON: {"verdicts":[{"i":0,"shows":"...","ok":true|false,"why":"یک جمله"}]} به ترتیبِ شکل‌ها.'
)
def _lesson_text(lesson: Lesson) -> str:
steps = '\n'.join(f'قدم {i}: {s}' for i, s in enumerate(lesson.steps or [], 1))
return f'عنوان: {lesson.title}\nتوضیح: {lesson.intro_text}\nمثال: {lesson.example}\n{steps}'
def _spec_for(text: str) -> str:
"""فقط بخش‌هایی از کاتالوگ که کلمه‌هایشان در متن هست؛ بی‌بخش → '' (مدل صدا زده نمی‌شود)."""
parts = [section for pattern, section in diagram_mod.SECTIONS if pattern.search(text)]
return '\n'.join([diagram_mod._HEAD, *parts]) if parts else ''
_SUBSTANTIVE = {'function', 'segment', 'line', 'ray', 'polygon', 'triangle', 'quad', 'regularPolygon', 'circle',
'angle', 'exteriorAngle', 'arc', 'vector'}
def _is_thin(diagram: dict) -> bool:
"""
شکلِ صفحه‌ای که هیچ منحنی، پاره‌خط یا شکلی ندارد و فقط نقطه‌ی پراکنده است چیزی نشان نمی‌دهد
(مثلاً چند نقطه‌ی A و B و C بی‌ارتباط). نوعِ تازه‌ای که این فهرست نمی‌شناسد «محتوادار» حساب می‌شود.
"""
if diagram.get('kind') != 'plane':
return False
items = diagram.get('items') or []
if any(i.get('type') in _SUBSTANTIVE or i.get('type') not in ('point', 'label', None) for i in items):
return False
return True
def _json_list(raw: str):
raw = re.sub(r'^```(?:json)?|```$', '', (raw or '').strip()).strip()
start = raw.index('[')
items, _ = json.JSONDecoder().raw_decode(raw[start:])
if not isinstance(items, list):
raise ValueError('not a list')
return items
class Command(BaseCommand): class Command(BaseCommand):
help = 'ساختِ شکلِ ثابتِ درس‌ها (یک بار، ذخیره در Lesson.figures).' help = 'ساختِ شکلِ ثابتِ درس‌ها (یک بار، ذخیره در Lesson.figures).'
def add_arguments(self, p): def add_arguments(self, p):
p.add_argument('--grade', type=int, required=True) p.add_argument('--grade', type=int, help='بدونِ آن همه‌ی پایه‌ها')
p.add_argument('--limit', type=int, default=4) p.add_argument('--limit', type=int, default=4)
p.add_argument('--lesson', type=int, help='فقط همین شناسه') p.add_argument('--lesson', type=int, help='فقط همین شناسه')
p.add_argument('--redo', action='store_true', help='درس‌هایی که شکل دارند را هم بساز') p.add_argument('--redo', action='store_true', help='درس‌هایی که شکل دارند هم')
p.add_argument('--dry-run', action='store_true') p.add_argument('--dry-run', action='store_true')
p.add_argument('--workers', type=int, default=4)
p.add_argument('--max-usd', type=float, default=2.0, help='سقفِ هزینه (فرمولِ رسمی)؛ رسید، می‌ایستد')
p.add_argument('--ledger', help='JSON از درس‌های بررسی‌شده ({id: نتیجه}) تا دوباره پول نگیرند')
p.add_argument('--quiet', action='store_true')
p.add_argument('--recheck', action='store_true',
help='شکل‌های ذخیره‌شده را دوباره با وارسیِ سخت‌گیر بسنج و ناموفق‌ها را پاک کن (بی‌ساخت)')
p.add_argument('--no-check', action='store_true', help='گامِ وارسیِ مستقل را رد کن (فقط برای آزمایش)')
def handle(self, *a, **o): def handle(self, *a, **o):
qs = Lesson.objects.filter(node__unit__grade__number=o['grade']).order_by( if o['recheck']:
'node__unit__order', 'node__order', 'id') return self._recheck(o)
qs = Lesson.objects.exclude(example='').order_by(
'node__unit__grade__number', 'node__unit__order', 'node__order', 'id')
if o['grade']:
qs = qs.filter(node__unit__grade__number=o['grade'])
if o['lesson']: if o['lesson']:
qs = qs.filter(pk=o['lesson']) qs = qs.filter(pk=o['lesson'])
if not o['redo']: if not o['redo']:
qs = qs.filter(figures=[]) qs = qs.filter(figures=[])
ledger_path = Path(o['ledger']) if o['ledger'] else None
ledger = json.loads(ledger_path.read_text()) if ledger_path and ledger_path.exists() else {}
if not o['redo']:
qs = qs.exclude(pk__in=[int(k) for k in ledger])
lessons = list(qs[:o['limit']]) lessons = list(qs[:o['limit']])
if not lessons: if not lessons:
raise CommandError('درسی پیدا نشد.') raise CommandError('درسی پیدا نشد.')
model = chat_model_for('') model = chat_model_for('')
client = clients.get_openai_client() client = clients.get_openai_client()
spent = prompt_t = out_t = 0 lock = threading.Lock()
for lesson in lessons: usage = {'in': 0, 'out': 0}
body = (f'عنوان: {lesson.title}\nتوضیح: {lesson.intro_text}\nمثال: {lesson.example}\n' stats = {'no_trigger': 0, 'none': 0, 'saved': 0, 'figures': 0, 'rejected': 0, 'invalid': 0, 'stopped': 0}
+ '\n'.join(f'قدم {i}: {s}' for i, s in enumerate(lesson.steps or [], 1)))
def spent():
return usage['in'] / 1e6 * USD_PER_M_IN + usage['out'] / 1e6 * USD_PER_M_OUT
def ask(system, user, **extra):
r = client.chat.completions.create( r = client.chat.completions.create(
model=model, **completion_kwargs(model), model=model, **completion_kwargs(model), **extra,
messages=[{'role': 'system', 'content': PROMPT + diagram_mod.DIAGRAM_SPEC}, messages=[{'role': 'system', 'content': system}, {'role': 'user', 'content': user}])
{'role': 'user', 'content': body}])
u = getattr(r, 'usage', None) u = getattr(r, 'usage', None)
spent += (getattr(u, 'total_tokens', 0) or 0) with lock:
prompt_t += (getattr(u, 'prompt_tokens', 0) or 0) usage['in'] += getattr(u, 'prompt_tokens', 0) or 0
out_t += (getattr(u, 'completion_tokens', 0) or 0) usage['out'] += getattr(u, 'completion_tokens', 0) or 0
raw = re.sub(r'^```(?:json)?|```$', '', (r.choices[0].message.content or '').strip()).strip() return r.choices[0].message.content or ''
def record(lesson, outcome):
with lock:
if ledger_path and not o['dry_run']:
ledger[str(lesson.id)] = outcome
ledger_path.write_text(json.dumps(ledger, ensure_ascii=False))
def work(lesson):
text = _lesson_text(lesson)
spec = _spec_for(text)
if not spec:
with lock:
stats['no_trigger'] += 1
record(lesson, 'no_trigger')
return
if spent() >= o['max_usd']:
with lock:
stats['stopped'] += 1
return
try: try:
start = raw.index('[') items = _json_list(ask(PROMPT + spec, text))
items, _ = json.JSONDecoder().raw_decode(raw[start:]) except (ValueError, KeyError) as e:
if not isinstance(items, list): with lock:
raise ValueError('not a list') stats['invalid'] += 1
except ValueError as e:
self.stderr.write(f'درس {lesson.id}: خروجیِ نامعتبر ({e}); ذخیره نشد.') self.stderr.write(f'درس {lesson.id}: خروجیِ نامعتبر ({e}); ذخیره نشد.')
continue return
figures, dropped = [], 0 figures, dropped = [], 0
for it in items: for it in items[:MAX_FIGURES + 2]:
where = it.get('where') if isinstance(it, dict) else None where = it.get('where') if isinstance(it, dict) else None
spec = diagram_mod.validate(it.get('diagram')) if where else None s = diagram_mod.validate(it.get('diagram')) if where else None
idx = it.get('index') idx = it.get('index')
if where == 'step' and isinstance(idx, int): if where == 'step' and isinstance(idx, int):
idx -= 1 # مدل ۱‌به‌بعد می‌نویسد؛ ذخیره از صفر idx -= 1 # مدل ۱‌به‌بعد می‌نویسد؛ ذخیره از صفر
if (spec is None or where not in ('intro', 'example', 'step') if (s is None or where not in ('intro', 'example', 'step')
or (where == 'step' and not (isinstance(idx, int) and 0 <= idx < len(lesson.steps or []))) or (where == 'step' and not (isinstance(idx, int) and 0 <= idx < len(lesson.steps or [])))
or (where == 'example' and not lesson.example)): or (where == 'example' and not lesson.example)):
dropped += 1 dropped += 1
continue continue
for item in spec.get('items', []): if _is_thin(s):
dropped += 1
continue
for item in s.get('items', []):
if item.get('type') == 'segment': if item.get('type') == 'segment':
item.pop('ticks', None) # علامتِ «ضلع مساوی» روی نمودارِ تابع فقط شلوغی است item.pop('ticks', None) # علامتِ «ضلع مساوی» روی نمودارِ تابع فقط شلوغی است
figures.append({'where': where, **({'index': idx} if where == 'step' else {}), 'diagram': spec}) figures.append({'where': where, **({'index': idx} if where == 'step' else {}), 'diagram': s})
self.stdout.write(f'درس {lesson.id} «{lesson.title}»: {len(figures)} شکل، {dropped} ردشده') figures = figures[:MAX_FIGURES]
for f in figures: if not figures:
self.stdout.write(' ' + json.dumps(f, ensure_ascii=False)) with lock:
if figures and not o['dry_run']: stats['none'] += 1
lesson.figures = figures record(lesson, 'none' if not items else 'invalid_all')
return
kept, notes = figures, []
if not o['no_check']:
try:
verdicts = json.loads(ask(CHECK, text + '\n\nشکل‌ها:\n' + '\n'.join(
f'{i}: ' + json.dumps(f, ensure_ascii=False) for i, f in enumerate(figures)),
response_format={'type': 'json_object'})).get('verdicts', [])
ok = {v.get('i'): v for v in verdicts if isinstance(v, dict)}
kept = [f for i, f in enumerate(figures) if ok.get(i, {}).get('ok') is True]
notes = [f"{i}: {v.get('why', '')}" for i, v in ok.items() if v.get('ok') is not True]
except (ValueError, KeyError, AttributeError) as e:
kept, notes = [], [f'وارسی ناموفق: {e}']
with lock:
stats['rejected'] += len(figures) - len(kept)
stats['figures'] += len(kept)
if kept:
stats['saved'] += 1
self.stdout.write(f'درس {lesson.id} «{lesson.title}»: {len(kept)} شکل از {len(figures)} '
f'({dropped} ردشده در اعتبارسنج)' + (f' | رد: {"; ".join(notes)}' if notes else ''))
if not o['quiet']:
for f in kept:
self.stdout.write(' ' + json.dumps(f, ensure_ascii=False))
if kept and not o['dry_run']:
lesson.figures = kept
lesson.save(update_fields=['figures']) lesson.save(update_fields=['figures'])
self.stdout.write(f'توکنِ مصرفی: {spent} (ورودی {prompt_t}، خروجی {out_t})') record(lesson, f'figures:{len(kept)}' if kept else 'rejected')
with ThreadPoolExecutor(max_workers=max(1, o['workers'])) as ex:
list(ex.map(work, lessons))
self.stdout.write(f'{stats}\nتوکن: ورودی {usage["in"]}، خروجی {usage["out"]}؛ هزینه‌ی فرمولی ≈ ${spent():.3f}')
def _recheck(self, o):
qs = Lesson.objects.exclude(figures=[]).order_by('node__unit__grade__number', 'id')
if o['grade']:
qs = qs.filter(node__unit__grade__number=o['grade'])
lessons = list(qs[:o['limit']])
model = chat_model_for('')
client = clients.get_openai_client()
lock = threading.Lock()
usage = {'in': 0, 'out': 0}
stats = {'lessons': 0, 'kept': 0, 'dropped': 0, 'errors': 0}
def work(lesson):
figures = [f for f in lesson.figures if not _is_thin(f['diagram'])]
thin = len(lesson.figures) - len(figures)
text = _lesson_text(lesson)
try:
r = client.chat.completions.create(
model=model, **completion_kwargs(model), response_format={'type': 'json_object'},
messages=[{'role': 'system', 'content': CHECK}, {'role': 'user', 'content': text + '\n\nشکل‌ها:\n' + '\n'.join(
f'{i}: ' + json.dumps(f, ensure_ascii=False) for i, f in enumerate(figures))}])
u = getattr(r, 'usage', None)
ok = {v.get('i'): v for v in json.loads(r.choices[0].message.content or '{}').get('verdicts', [])
if isinstance(v, dict)} if figures else {}
except (ValueError, KeyError, AttributeError) as e:
with lock:
stats['errors'] += 1
self.stderr.write(f'درس {lesson.id}: وارسی ناموفق ({e}); دست نخورد.')
return
kept = [f for i, f in enumerate(figures) if ok.get(i, {}).get('ok') is True]
with lock:
usage['in'] += getattr(u, 'prompt_tokens', 0) or 0
usage['out'] += getattr(u, 'completion_tokens', 0) or 0
stats['lessons'] += 1
stats['kept'] += len(kept)
stats['dropped'] += len(lesson.figures) - len(kept)
why = '; '.join(f"{i}: {v.get('why', '')}" for i, v in ok.items() if v.get('ok') is not True)
if len(kept) != len(lesson.figures) and not o['quiet']:
self.stdout.write(f'درس {lesson.id} «{lesson.title}»: {len(kept)}/{len(lesson.figures)}'
f' (بی‌محتوا {thin}) | {why}')
if len(kept) != len(lesson.figures) and not o['dry_run']:
lesson.figures = kept
lesson.save(update_fields=['figures'])
with ThreadPoolExecutor(max_workers=max(1, o['workers'])) as ex:
list(ex.map(work, lessons))
cost = usage['in'] / 1e6 * USD_PER_M_IN + usage['out'] / 1e6 * USD_PER_M_OUT
self.stdout.write(f'{stats}\nتوکن: ورودی {usage["in"]}، خروجی {usage["out"]}؛ هزینه‌ی فرمولی ≈ ${cost:.3f}')

File diff suppressed because it is too large Load Diff

View File

@@ -7232,15 +7232,15 @@
"معادله", "معادله",
"حل معادله با استفاده از رسم شکل" "حل معادله با استفاده از رسم شکل"
], ],
"prompt": "حاصل جمع پنج عدد متوالی طبیعی ۶۵ شده است. عدد وسط چه عددی است؟", "prompt": "جواب معادلهٔ ۵x + ۱۰ = ۶۵ کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۱۴", "۱۱",
"۹",
"۱۳", "۱۳",
"۱۲", "۱۲"
"۲۶"
], ],
"correct": 1 "correct": 0
}, },
{ {
"path": [ "path": [
@@ -7249,15 +7249,15 @@
"معادله", "معادله",
"حل معادله با استفاده از روش‌های نمادین" "حل معادله با استفاده از روش‌های نمادین"
], ],
"prompt": "حاصل جمع پنج عدد متوالی طبیعی ۶۵ شده است. عدد وسط چه عددی است؟", "prompt": "سه برابر عددی را با ۵ جمع کرده‌ایم و ۲۰ شده است. آن عدد کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۱۳", "۴",
"۱۴", "۵",
"۲۶", "۶",
"۱۲" "۱۵"
], ],
"correct": 0 "correct": 1
}, },
{ {
"path": [ "path": [
@@ -9187,7 +9187,7 @@
"توان و جذر — گروه 1", "توان و جذر — گروه 1",
"ترتیب انجام عملیات" "ترتیب انجام عملیات"
], ],
"prompt": "۲ × (۳۲ + ۲) = ؟", "prompt": "۲ × (۳² + ۲) = ؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۱۸", "۱۸",
@@ -17194,13 +17194,13 @@
"ضرب عددها — گروه 5", "ضرب عددها — گروه 5",
"محاسبه ضرب اعداد" "محاسبه ضرب اعداد"
], ],
"prompt": "۲۰ × ۲۰ = ؟", "prompt": "۳۰ × ۴۰ = ؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۴۰", "۱۲۰",
"۴۰۰", "۱۲۰۰",
"۲۰۰", "۷۰",
"۸۰" "۱۲٬۰۰۰"
], ],
"correct": 1 "correct": 1
}, },
@@ -17670,15 +17670,15 @@
"ضرب عددها — گروه 5", "ضرب عددها — گروه 5",
"محاسبه ضرب چند رقمی" "محاسبه ضرب چند رقمی"
], ],
"prompt": "۸۰ × ۷۰ = ؟", "prompt": "۶۰ × ۹۰ = ؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۵۶۰", "۵۴۰",
"۵۶۰۰", "۱۵۰",
"۶۳۰۰", "۵۴۰۰",
"۴۹۰۰" "۵۴٬۰۰۰"
], ],
"correct": 1 "correct": 2
}, },
{ {
"path": [ "path": [
@@ -20033,15 +20033,15 @@
"معادله", "معادله",
"حل معادله با استفاده از حدس و آزمایش" "حل معادله با استفاده از حدس و آزمایش"
], ],
"prompt": "حاصل جمع پنج عدد متوالی طبیعی برابر ۶۵ است. عدد وسط کدام است؟", "prompt": "عددی که سه برابرش ۲۷ شود کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۱۱", "۶",
"۱۳",
"۱۲", "۱۲",
"۱۴" "۹",
"۸"
], ],
"correct": 1 "correct": 2
}, },
{ {
"path": [ "path": [
@@ -20050,13 +20050,13 @@
"معادله", "معادله",
"حل معادله با استفاده از رسم شکل" "حل معادله با استفاده از رسم شکل"
], ],
"prompt": "حاصل جمع پنج عدد متوالی طبیعی برابر ۶۵ است. عدد وسط کدام است؟", "prompt": "جواب معادلهٔ ۲x + ۳ = ۱۵ کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۱۱", "۹",
"۱۳", "۶",
"۱۲", "۸",
"۱۴" "۷"
], ],
"correct": 1 "correct": 1
}, },
@@ -20067,15 +20067,15 @@
"معادله", "معادله",
"حل معادله با استفاده از روش‌های نمادین" "حل معادله با استفاده از روش‌های نمادین"
], ],
"prompt": "حاصل جمع پنج عدد متوالی طبیعی برابر ۶۵ است. عدد وسط کدام است؟", "prompt": "اگر x − ۴ = ۹ باشد ، x کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۱۱", "۵",
"۳۶",
"۱۳", "۱۳",
"۱۲", "−۵"
"۱۴"
], ],
"correct": 1 "correct": 2
}, },
{ {
"path": [ "path": [
@@ -31576,15 +31576,15 @@
"تناوب و تانژانت", "تناوب و تانژانت",
"تشخیص درست یا نادرست بودن جملات مربوط به تابع تناوبی" "تشخیص درست یا نادرست بودن جملات مربوط به تابع تناوبی"
], ],
"prompt": "کدام یک از جملات زیر درباره‌ی توابع تناوبی درست است؟", "prompt": "آیا sin(x + ۲π) = sin x است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"تابع تناوبی در هر بازه‌ی تعریف‌شده صعودی است", "بله ، چون ۲π دورهٔ تناوب sin است",
"تابع تناوبی در تمام دامنه‌اش صعودی است", "خیر ، همیشه نادرست است",
"تابع تناوبی می‌تواند در بازه‌ای نزولی باشد", "فقط برای x = ۰ درست است",
"هر تابع تناوبی حتماً زوج است" "فقط برای x = π درست است"
], ],
"correct": 2 "correct": 0
}, },
{ {
"path": [ "path": [
@@ -39464,15 +39464,15 @@
"محاسبه حد توابع کسری", "محاسبه حد توابع کسری",
"محاسبه حد توابع مثلثاتی" "محاسبه حد توابع مثلثاتی"
], ],
"prompt": "مقدار حدِ زیر را بیابید:\nlim (x→π/۲) (۱ - sin x) / cos x", "prompt": "مقدار lim(x→۰) tan x / x کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۰", "۰",
"۱", "۱",
"-۱", "∞",
"وجود ندارد" "π"
], ],
"correct": 0 "correct": 1
}, },
{ {
"path": [ "path": [
@@ -39600,15 +39600,15 @@
"پیوستگی", "پیوستگی",
"رسم نمودار توابع با شرایط پیوستگی و نقاط تعریف نشده" "رسم نمودار توابع با شرایط پیوستگی و نقاط تعریف نشده"
], ],
"prompt": "تابعی در x=۳ تعریف نشده، اما حدِ آن در x=۳ وجود دارد. این تابع در x=۳ چگونه است؟", "prompt": "مقدار lim(x→۱) (x² − ۱)/(x − ۱) کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"پیوسته نیست، چون تعریف نشده است.", "۰",
"پیوسته است، چون حد وجود دارد.", "۱",
"پیوسته است، چون همه‌جا تعریف شده.", "۲",
"نمی‌توان گفت؛ نمودار لازم است." "وجود ندارد"
], ],
"correct": 0 "correct": 2
}, },
{ {
"path": [ "path": [
@@ -39787,15 +39787,15 @@
"پیوستگی", "پیوستگی",
"ارائه بازه پیوستگی تابع شامل رادیکال" "ارائه بازه پیوستگی تابع شامل رادیکال"
], ],
"prompt": "تابعِ f(x) = ۲ - √(۷-x) را در نظر بگیرید. کدام‌یک بازه‌ای بسته است که تابع بر آن پیوسته باشد؟", "prompt": "تابع f(x) = √(x − ۳) − ۴ روی کدام بازه پیوسته است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"[-۲,۸]", "[−۳ ، ∞)",
"[۷,۱۰]", "(−∞ ، ۳]",
"[۸,۱۲]", "[۳ ، ∞)",
"[-۲,۷]" "(−∞ ، ∞)"
], ],
"correct": 3 "correct": 2
}, },
{ {
"path": [ "path": [
@@ -40569,15 +40569,15 @@
"جهت تقعر نمودار یک تابع و نقطه‌ی عطف آن", "جهت تقعر نمودار یک تابع و نقطه‌ی عطف آن",
"تشخیص درست یا نادرست بودن گزاره‌های مربوط به نقاط عطف" "تشخیص درست یا نادرست بودن گزاره‌های مربوط به نقاط عطف"
], ],
"prompt": "کدام گزاره درباره‌ی نقطه‌ی عطف نادرست است؟", "prompt": "نقطهٔ عطف f(x) = x³ − ۳x + ۲ کدام است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"تابعِ صعودیِ اکید عطف ندارد", "(۱ ، ۰)",
"عطف به تغییرِ تقعر مربوط است", "(۰ ، ۲)",
"تابع می‌تواند چند عطف داشته باشد", "(−۱ ، ۴)",
"در عطف علامتِ f'' تغییر می‌کند" "(۰ ، ۰)"
], ],
"correct": 0 "correct": 1
}, },
{ {
"path": [ "path": [
@@ -54152,13 +54152,13 @@
"ضرب عددهای مخلوط", "ضرب عددهای مخلوط",
"مقایسه کسرها و تعیین نادرستی" "مقایسه کسرها و تعیین نادرستی"
], ],
"prompt": "کدام مقایسه درست است؟", "prompt": "آیا ۳/۵ از ۱/۲ بزرگ‌تر است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"۵ و (۱)/(۳) < ۶", "بله ، چون ۶/۱۰ > ۵/۱۰",
"۵ و (۱)/(۳) > ۶", "خیر ، چون ۳ < ۵",
"۵ و (۱)/(۳) = ۶", "برابرند",
"۵ و (۱)/(۳) < ۵" "نمی‌توان مقایسه کرد"
], ],
"correct": 0 "correct": 0
}, },
@@ -61972,13 +61972,13 @@
"چهارضلعی‌ها", "چهارضلعی‌ها",
"تشخیص و تکمیل ویژگی‌های چهارضلعی‌ها" "تشخیص و تکمیل ویژگی‌های چهارضلعی‌ها"
], ],
"prompt": "مستطیل علاوه بر ویژگی‌های متوازی‌الاضلاع چه ویژگی دیگری دارد؟", "prompt": "کدام ویژگی در لوزی همیشه برقرار است؟",
"type": "multiple_choice", "type": "multiple_choice",
"choices": [ "choices": [
"همه‌ی ضلع‌هایش برابر است", "چهار زاویهٔ قائمه",
"همه‌ی زاویه‌هایش قائمه است", "چهار ضلع برابر",
"فقط یک جفت ضلع موازی دارد", "قطرهای برابر",
"سه ضلعش برابر است" "فقط یک جفت ضلع موازی"
], ],
"correct": 1 "correct": 1
}, },
@@ -62542,6 +62542,414 @@
"به طور حتم اتفاق نمی‌افتد" "به طور حتم اتفاق نمی‌افتد"
], ],
"correct": 2 "correct": 2
},
{
"path": [
"پایه ششم",
"فصل ۱: عدد و الگوهای عددی",
"عدد و الگوهای عددی — گروه 6",
"رابطه بین تعداد مربع‌ها و شماره شکل‌ها"
],
"prompt": "شکل n از یک الگو ۲n مربع دارد. شکل پنجم چند مربع دارد؟",
"type": "multiple_choice",
"choices": [
"۷",
"۱۰",
"۱۲",
"۲۵"
],
"correct": 1
},
{
"path": [
"پایه ششم",
"فصل ۴: تقارن و مختصات",
"تقارن و مختصات — گروه 1",
"بررسی تقارن چرخشی"
],
"prompt": "مربع را حول مرکزش حداقل چند درجه بچرخانیم تا روی خودش بیفتد؟",
"type": "multiple_choice",
"choices": [
"۴۵",
"۹۰",
"۱۸۰",
"۳۶۰"
],
"correct": 1
},
{
"path": [
"پایه ششم",
"فصل ۴: تقارن و مختصات",
"تقارن و مختصات — گروه 2",
"تقارن محوری"
],
"prompt": "قرینهٔ نقطهٔ (۲ ، ۳) نسبت به محور y کدام است؟",
"type": "multiple_choice",
"choices": [
"(۲ ، −۳)",
"(−۲ ، ۳)",
"(−۲ ، −۳)",
"(۳ ، ۲)"
],
"correct": 1
},
{
"path": [
"پایه ششم",
"فصل ۴: تقارن و مختصات",
"تقارن و مختصات — گروه 2",
"تقارن چرخشی"
],
"prompt": "مثلث متساوی‌الاضلاع را حداقل چند درجه بچرخانیم تا روی خودش بیفتد؟",
"type": "multiple_choice",
"choices": [
"۶۰",
"۹۰",
"۱۲۰",
"۱۸۰"
],
"correct": 2
},
{
"path": [
"پایه ششم",
"فصل ۴: تقارن و مختصات",
"تقارن و مختصات — گروه 6",
"یافتن نقطه تقارن"
],
"prompt": "نقطه‌ای که وسط پاره‌خط به سرهای (۲ ، ۲) و (۶ ، ۴) است کدام است؟",
"type": "multiple_choice",
"choices": [
"(۴ ، ۲)",
"(۴ ، ۳)",
"(۳ ، ۴)",
"(۸ ، ۶)"
],
"correct": 1
},
{
"path": [
"پایه ششم",
"فصل ۵: اندازه گیری",
"اندازه گیری — گروه 3",
"زاویه‌های متقابل به رأس"
],
"prompt": "دو خط متقاطع زاویه‌ای ۵۰ درجه می‌سازند. زاویهٔ متقابل به رأس آن چند درجه است؟",
"type": "multiple_choice",
"choices": [
"۵۰",
"۱۳۰",
"۴۰",
"۳۱۰"
],
"correct": 0
},
{
"path": [
"پایه ششم",
"فصل ۵: اندازه گیری",
"اندازه گیری — گروه 6",
"محاسبه مساحت اشکال ترکیبی"
],
"prompt": "شکلی از یک مستطیل ۶×۴ و یک مربع ۳×۳ (جدا از هم) ساخته شده است. مساحت کل چقدر است؟",
"type": "multiple_choice",
"choices": [
"۳۰",
"۲۷",
"۳۳",
"۶۰"
],
"correct": 2
},
{
"path": [
"پایه هفتم",
"فصل ۳: جبر و معادله",
"جبر و معادله — گروه 6",
"نمودارهای ورودی و خروجی"
],
"prompt": "رابطهٔ ماشین y = ۲x + ۱ است. اگر ورودی ۵ باشد خروجی چند است؟",
"type": "multiple_choice",
"choices": [
"۱۰",
"۱۱",
"۱۲",
"۷"
],
"correct": 1
},
{
"path": [
"پایه هفتم",
"فصل ۴: هندسه و استدلال",
"هندسه و استدلال — گروه 4",
"محاسبه زاویه‌های مثلث"
],
"prompt": "زاویه‌های یک مثلث ۳x ، ۴x و ۵x هستند. بزرگ‌ترین زاویه چند درجه است؟",
"type": "multiple_choice",
"choices": [
"۶۰",
"۴۵",
"۹۰",
"۷۵"
],
"correct": 3
},
{
"path": [
"پایه هفتم",
"فصل ۶: سطح و حجم",
"سطح و حجم — گروه 4",
"گسترده‌سازی استوانه"
],
"prompt": "گستردهٔ یک استوانه از چه شکل‌هایی ساخته شده است؟",
"type": "multiple_choice",
"choices": [
"یک دایره و یک مثلث",
"دو دایره و یک مستطیل",
"شش مربع",
"دو مربع و دو مستطیل"
],
"correct": 1
},
{
"path": [
"پایه هفتم",
"فصل ۶: سطح و حجم",
"سطح و حجم — گروه 4",
"مقاطع اشکال هندسی"
],
"prompt": "اگر مکعب را با صفحه‌ای افقی ببریم ، مقطع چه شکلی است؟",
"type": "multiple_choice",
"choices": [
"دایره",
"مثلث",
"مربع",
"ذوزنقه"
],
"correct": 2
},
{
"path": [
"پایه هفتم",
"فصل ۸: بردار و مختصات",
"بردار و مختصات — گروه 5",
"پاسخ به سوالات مختصات"
],
"prompt": "نقطهٔ A(۲ ، ۳) چقدر از محور y فاصله دارد؟",
"type": "multiple_choice",
"choices": [
"۳",
"۲",
"۵",
"۱"
],
"correct": 1
},
{
"path": [
"پایه هفتم",
"فصل ۹: آمار و احتمال",
"آمار و احتمال — گروه 4",
"مقایسه داده‌ها"
],
"prompt": "میانگین نمرهٔ کلاس A برابر ۱۵ و کلاس B برابر ۱۷ است. میانگین کدام کلاس بیشتر است؟",
"type": "multiple_choice",
"choices": [
"کلاس A",
"کلاس B",
"برابرند",
"نمی‌توان گفت"
],
"correct": 1
},
{
"path": [
"پایه سوم",
"فصل ۱: الگوها",
"الگوها — گروه 3",
"جمع ذهنی دسته‌ها"
],
"prompt": "در باغی ۱۲ سیب و ۸ پرتقال هست. تعداد کل میوه‌ها چند است؟",
"type": "multiple_choice",
"choices": [
"۴",
"۹۶",
"۲۰",
"۱۸"
],
"correct": 2
},
{
"path": [
"پایه سوم",
"فصل ۱: الگوها",
"الگوها — گروه 3",
"جمع رنگ‌ها در اشکال"
],
"prompt": "شکل اول ۳ مربع قرمز و ۲ مربع آبی و شکل دوم ۴ مربع قرمز و ۵ مربع آبی دارد. روی هم چند مربع قرمز داریم؟",
"type": "multiple_choice",
"choices": [
"۹",
"۷",
"۱۴",
"۱۲"
],
"correct": 1
},
{
"path": [
"پایه سوم",
"فصل ۱: الگوها",
"الگوها — گروه 5",
"ماشین حساب ساده"
],
"prompt": "ماشینی هر عدد را ۵ تا افزایش می‌دهد. اگر ورودی ۷ باشد خروجی چند است؟",
"type": "multiple_choice",
"choices": [
"۲",
"۳۵",
"۱۲",
"۷"
],
"correct": 2
},
{
"path": [
"پایه سوم",
"فصل ۱: الگوها",
"الگوها — گروه 5",
"شناسایی الگوهای عددی"
],
"prompt": "در الگوی ۲ ، ۴ ، ۶ ، ۸ ، … عدد بعدی کدام است؟",
"type": "multiple_choice",
"choices": [
"۹",
"۱۰",
"۱۲",
"۱۶"
],
"correct": 1
},
{
"path": [
"پایه سوم",
"فصل ۳: عددهای کسری",
"عددهای کسری — گروه 1",
"اندازه‌گیری طول با کسر"
],
"prompt": "نواری ۸ سانتی‌متری است و بخش رنگی آن ۲ سانتی‌متر است. بخش رنگی چه کسری از نوار است؟",
"type": "multiple_choice",
"choices": [
"۱/۲",
"۳/۴",
"۱/۴",
"۱/۳"
],
"correct": 2
},
{
"path": [
"پایه سوم",
"فصل ۳: عددهای کسری",
"عددهای کسری — گروه 2",
"تشخیص کسر از شکل"
],
"prompt": "در یک دایره ۸ بخش مساوی هست و ۳ بخش رنگی شده است. کسر بخش رنگی چیست؟",
"type": "multiple_choice",
"choices": [
"۵/۸",
"۳/۸",
"۳/۵",
"۸/۳"
],
"correct": 1
},
{
"path": [
"پایه سوم",
"فصل ۵: محیط و مساحت",
"محیط و مساحت — گروه 4",
"محاسبه مساحت با شمارش مربع‌ها"
],
"prompt": "مستطیلی با طول ۴ مربع و عرض ۳ مربع مساحتش چند مربع است؟",
"type": "multiple_choice",
"choices": [
"۷",
"۱۴",
"۱۲",
"۱۰"
],
"correct": 2
},
{
"path": [
"پایه سوم",
"فصل ۷: آمار و احتمال",
"آمار و احتمال — گروه 3",
"تکمیل جدول و نمودار دایره‌ای"
],
"prompt": "۴ نفر رنگ آبی و ۴ نفر رنگ قرمز را دوست دارند. در نمودار دایره‌ای هر رنگ چه بخشی از دایره است؟",
"type": "multiple_choice",
"choices": [
"یک‌چهارم",
"نصف",
"یک‌سوم",
"سه‌چهارم"
],
"correct": 1
},
{
"path": [
"پایه سوم",
"فصل ۷: آمار و احتمال",
"آمار و احتمال — گروه 4",
"رسم نمودار ستونی"
],
"prompt": "جدول: شنبه ۳ کتاب ، یکشنبه ۵ کتاب. در نمودار ستونی کدام روز ستون بلندتر دارد؟",
"type": "multiple_choice",
"choices": [
"شنبه",
"یکشنبه",
"هر دو برابر",
"هیچ‌کدام"
],
"correct": 1
},
{
"path": [
"پایه سوم",
"فصل ۷: آمار و احتمال",
"آمار و احتمال — گروه 6",
"نمایش داده‌ها با نمودار تصویری"
],
"prompt": "در نمودار تصویری هر 🙂 یعنی ۲ کتاب. برای ۴ کتاب چند 🙂 می‌گذاریم؟",
"type": "multiple_choice",
"choices": [
"۴",
"۲",
"۱",
"۸"
],
"correct": 1
},
{
"path": [
"پایه سوم",
"فصل ۸: ضرب عددها",
"ضرب عددها — گروه 2",
"ضرب با استفاده از شکل‌های نمایشی"
],
"prompt": "در یک باغ ۳ ردیف درخت هست و در هر ردیف ۴ درخت. روی هم چند درخت است؟",
"type": "multiple_choice",
"choices": [
"۷",
"۱۶",
"۳۴",
"۱۲"
],
"correct": 3
} }
] ]
} }