Tutor/answers: bare subscripts (S_n, a_1) become Unicode; migration 0022 re-cleans DB text

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 12:18:46 +03:30
parent e662914efc
commit b075b93f2a
5 changed files with 31 additions and 4 deletions

View File

@@ -23,3 +23,11 @@ class HistoryNeverShowsLatexTests(TestCase):
self.assertNotIn('^', t)
self.assertNotIn('$', t)
self.assertIn('x²', texts[0])
class ToPlainSubscriptTests(TestCase):
def test_bare_subscripts_are_unicode(self):
from rag.services.mathtext import to_plain
out = to_plain('S_n = n/2 × (a_1 + a_n)')
self.assertNotIn('_', out)
self.assertIn('Sₙ', out)

View File

@@ -0,0 +1,16 @@
"""
0021 پیش از تکمیلِ تبدیل (اندیسِ ساده‌ی S_n، a_1) اجرا شد؛ پیام‌های دستیار و متنِ سوال/درس را با نسخه‌ی کامل دوباره
می‌گذراند. همان تبدیل، idempotent. `0021.forwards` عیناً دوباره اجرا می‌شود (تکراریِ سوال را هم پاک می‌کند).
"""
import importlib
from django.db import migrations
def forwards(apps, schema_editor):
importlib.import_module('content.migrations.0021_plain_math_everywhere').forwards(apps, schema_editor)
class Migration(migrations.Migration):
dependencies = [('content', '0021_plain_math_everywhere')]
operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]

View File

@@ -99,7 +99,8 @@ def convert(text: str) -> str:
s = re.sub(r'\^\(((?:[^()]|\([^()]*\))*)\)', lambda m: _try(m[1], _SUP) or f' به توانِ ({m[1].strip()}) ', s)
s = re.sub(r'(?<=[A-Za-z0-9۰-۹\)₀-₉ₐ-ₜᵢⱼ])\^([A-Za-zα-ωΑ-Ω□▢])(?![A-Za-z0-9۰-۹])', lambda m: _try(m[1], _SUP) or f' به توانِ {m[1]} ', s)
s = re.sub(r'\^([+\-−]?[0-9۰-۹]+)', lambda m: _try(m[1], _SUP) or m[0], s)
s = s = re.sub(r'(?<=[A-Za-z\)∫Σ∏])_([0-9۰-۹]+|[A-Za-z]{1,4}[0-9۰-۹]?)', lambda m: ''.join(_SUB.get(ch, ch) for ch in m[1]), s)
s = s = re.sub(r'((?<![A-Za-z0-9_])[A-Za-z]|log|ln|lim|[)∫Σ∏])_([0-9۰-۹]+[A-Za-z]?|[A-Za-z]{1,4}[0-9۰-۹]?)(?![A-Za-z0-9_])',
lambda m: (f'log پایه‌ی {m[2]}' if m[1] == 'log' and any(ch not in _SUB for ch in m[2]) else m[1] + ''.join(_SUB.get(ch, ch) for ch in m[2])), s)
s = re.sub(r'log_([A-Za-z])(?![A-Za-z0-9])', lambda m: m[0] if m[1] in _SUB else f'log پایه‌ی {m[1]}', s)
s = re.sub(r'(?<=\S) {2,}(?=\S)', ' ', s)
s = re.sub(r'(?<=[A-Za-z])_\(([^()]*)\)', r'(\1)', s)

View File

@@ -158704,7 +158704,7 @@
"lessons": [
{
"title": "مقدارِ لگاریتم",
"introText": "logb(bᵏ) = k.",
"introText": "log پایه‌ی b(bᵏ) = k.",
"example": "",
"steps": []
},
@@ -158716,7 +158716,7 @@
},
{
"title": "معادله‌ی لگاریتمی",
"introText": "logb x = k ⇔ x = bᵏ.",
"introText": "log پایه‌ی b x = k ⇔ x = bᵏ.",
"example": "",
"steps": []
},

View File

@@ -255,7 +255,9 @@ def _to_plain(text: str) -> str:
text = _strip_markdown(text)
text = re.sub(r'[ \t]+\n', '\n', text)
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
# S_n، a_1، x^2 و توانِ پیچیده که بالا رد شد: همان تبدیلِ seed/دیتابیس (یک رفتار در همه‌جا).
from content.plain_math import convert as _plain
return _plain(text).strip()
class StreamSanitizer: