tutor-v7-diagram-kit: anchor short follow-ups to the previous question, new diagram pieces in the prompt

A real chat answered "what if 6 were 4?" with a semicircle problem from the
retrieved book excerpt instead of the parabola asked a moment before. A short
follow-up now carries the previous user question next to it. The catalog
teaches line equations as written, regularPolygon, exteriorAngle, graphs only
through function items and tangents through tangent; fraction words (یک‌پنجم،
نُه‌دهم) pull the fraction section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 02:37:50 +03:30
parent 14a4d9cbf1
commit 731dea9bb1
4 changed files with 85 additions and 10 deletions

View File

@@ -58,18 +58,24 @@ _PLANE = (
'items (همه color، dashed و label اختیاری دارند): {"type":"point","point":"A"} · '
'{"type":"segment","from":"A","to":"B","label":"۵","ticks":1} (ticks علامتِ ضلعِ مساوی ۱ تا ۳) · '
'{"type":"vector","from":"A","to":"B"} · {"type":"line","through":["A","B"]} (خطِ بی‌پایان؛ ضلع و قطر segment‌اند) · '
'{"type":"line","eq":[3,5,4]} یعنی خطِ 3x + 5y = 4 (ضریب‌ها را عیناً از صورتِ مسئله بردار؛ خودت شیب حساب نکن) · '
'{"type":"line","equation":"3x + 5y = 4"} خط از روی معادله، عیناً همان‌طور که در مسئله یا جوابت نوشته‌ای '
'(«y = 12x − 16»، «x = 2.5» هم درست است؛ خودت شیب و نقطه حساب نکن) · '
'{"type":"ray","from":"A","through":"B"} · {"type":"circle","center":"O","r":2} یا "through":"A" · '
'{"type":"arc","center":"O","r":2,"start":0,"end":60} · {"type":"sector",…} · '
'{"type":"angle","vertex":"B","from":"A","to":"C","label":"۴۰°","arcs":1} (arcs ۱ تا ۳ برای زاویه‌های مساوی؛ '
'زاویه‌ی خارجیِ رأسِ B: نقطه‌ی "E":{"along":["A","B",1.5]} روی امتدادِ AB، بعد angle از E تا C) · '
'{"type":"angle","vertex":"B","from":"A","to":"C","label":"۴۰°","arcs":1} (زاویه‌ی داخلی؛ arcs ۱ تا ۳ برای زاویه‌های مساوی) · '
'{"type":"rightAngle","vertex":"H","from":"A","to":"B"} · {"type":"polygon","points":["A","B","C"]} · '
'{"type":"regularPolygon","n":5,"r":2,"names":["A","B","C","D","E"]} (چندضلعیِ منتظم؛ رأس‌ها را اپ می‌سازد و '
'با همین نام‌ها قابلِ ارجاع‌اند؛ مختصاتِ رأسِ چندضلعیِ منتظم را هرگز خودت ننویس) · '
'{"type":"exteriorAngle","vertex":"B","from":"A","to":"C","label":"72°"} (زاویه‌ی خارجیِ رأسِ B: اپ ضلعِ AB را از B '
'امتداد می‌دهد و زاویه‌ی بینِ امتداد و BC را می‌کشد) · '
'{"type":"image","of":["A","B","C"],"transform":{"rotate":{"center":"C","deg":-90}}} (نامِ تصویرها A′؛ '
'rotate مثبت=پادساعتگرد و «در جهتِ عقربه‌ها» منفی؛ یا {"reflect":"x"|"y"|"y=x"|["A","B"]}، {"translate":[2,1]}، '
'{"scale":{"center":"O","k":2}}) · {"type":"text","at":[1,2],"text":"…"}'
)
_GRAPH = (
'نمودارِ تابع (در plane با "axes":true؛ "trig":true یعنی محورِ x با مضربِ π؛ "xRange"/"yRange" اختیاری): '
'نمودارِ تابع (در plane با "axes":true؛ "trig":true یعنی محورِ x با مضربِ π؛ "xRange"/"yRange" اختیاری). '
'هر نمودار با item نوعِ function کشیده می‌شود؛ ریشه، رأس و اکسترمم را با marks بخواه، نه با نقطه‌ی دستی. '
'خطِ مماس همیشه با tangent: '
'{"type":"function","expr":"x^2-2*x-3","from":-2,"to":4,"label":"y = x² − 2x − 3",'
'"marks":["roots","vertex","axis","yIntercept"]} (marks: roots ریشه، vertex رأس، extrema ماکزیمم/مینیمم، axis '
'محورِ تقارن؛ اپ دقیق حساب می‌کند) · {"type":"piecewise","pieces":[{"expr":"x+1","from":-2,"to":1,'
@@ -132,7 +138,9 @@ SECTIONS = [
(_W(_GEOMETRY_WORDS + _GRAPH_WORDS), _PLANE),
(_W(_GRAPH_WORDS), _GRAPH),
(_W((r'محور', r'بازه', r'نامعادله', r'واحد به')), _NUMBERLINE),
(_W((r'کسر', r'تکه', r'پیتزا', r'کیک', r'درصد', r'خانه', r'گروه', r'نصف', r'چهارم')), _PARTS),
(_W((r'کسر', r'تکه', r'پیتزا', r'کیک', r'درصد', r'خانه', r'گروه', r'نصف',
# «یک‌پنجم»، «نُه‌دهم»، «سه‌چهارم»: کسر با کلمه، که کتابِ دبستان همین‌طور می‌نویسد
r'(?:یک|دو|سه|چهار|پنج|شش|هفت|هشت|نه|نُه|ده)[\u200c\s]?(?:دوم|سوم|چهارم|پنجم|ششم|هفتم|هشتم|نهم|دهم)(?!ین)')), _PARTS),
# «۶ ساعت مطالعه» مدت است، نه ساعتِ دیواری؛ فقط عقربه یا «ساعت … و … دقیقه»
(_W((r'عقربه', r'ساعت\s*[0-9۰-۹]+\s*(?::|و)\s*[0-9۰-۹]+', r'ساعت\s*چند')), _CLOCK),
(_W((r'ستونی', r'میله‌ای', r'دایره‌ای', r'خط\s*شکسته', r'میانگین', r'آمار', r'فراوانی')), _CHARTS),
@@ -243,6 +251,19 @@ def _expr(value) -> str:
return value.strip()
_LINEAR_OK = re.compile(r'^[0-9xy+\-=.\s/]*$')
def _linear_equation(value) -> str:
"""«3x + 5y = 4»، «y = 12x − 16»: فقط x، y، عدد، + − = و ممیز؛ خواندنِ دقیقش با فرانت است."""
if not isinstance(value, str):
raise ValueError('equation')
text = value.translate(_DIGITS).replace('−', '-').replace('–', '-').strip()
if not text or len(text) > 60 or text.count('=') != 1 or not _LINEAR_OK.match(text) or not re.search('[xy]', text):
raise ValueError('equation chars')
return text
def _pair(value, conv):
if not (isinstance(value, (list, tuple)) and len(value) == 2):
raise ValueError('pair')
@@ -327,7 +348,11 @@ def _plane_item(item: dict) -> dict:
if kind == 'segment' and item.get('ticks') is not None:
out['ticks'] = _clamp(item['ticks'], 1, 3)
elif kind == 'line':
if item.get('eq') is not None:
if item.get('equation') is not None:
out['equation'] = _linear_equation(item['equation'])
elif item.get('eq') is not None:
# ax + by + c = 0 — همان صورتی که gpt-4.1-mini هر سه بار نوشت (سنجشِ ۲۰۲۶-۰۹-۲۹)؛
# قراردادِ قبلیِ ax + by = c علامتِ c را برعکس می‌کرد و خط جابه‌جا می‌افتاد.
eq = item['eq']
if not (isinstance(eq, (list, tuple)) and len(eq) == 3):
raise ValueError('eq')
@@ -360,6 +385,20 @@ def _plane_item(item: dict) -> dict:
out.update({'vertex': _ref(item['vertex']), 'from': _ref(item['from']), 'to': _ref(item['to'])})
if kind == 'angle' and item.get('arcs') is not None:
out['arcs'] = _clamp(item['arcs'], 1, 3)
elif kind == 'regularPolygon':
out['n'] = _clamp(item.get('n', 5), 3, 12)
out['r'] = _num(item.get('r', 2), 0.01, MAX_COORD)
if item.get('center') is not None:
out['center'] = _ref(item['center'])
if item.get('start') is not None:
out['start'] = _num(item['start'], -360, 360)
names = item.get('names')
if isinstance(names, list):
clean = [n for n in names[:out['n']] if isinstance(n, str) and _NAME_OK.match(n)]
if clean:
out['names'] = clean
elif kind == 'exteriorAngle':
out.update({'vertex': _ref(item['vertex']), 'from': _ref(item['from']), 'to': _ref(item['to'])})
elif kind == 'polygon':
points = item['points']
if not isinstance(points, list) or not 3 <= len(points) <= 12: