No LaTeX/markdown anywhere students read (seed, import, DB migration 0021, tutor history, frontend guard); quiz of an island is fixed per user again
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
107
content/migrations/0021_plain_math_everywhere.py
Normal file
107
content/migrations/0021_plain_math_everywhere.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
LaTeX و نشانهگذاریِ خام (`^(−۴)`، `**…**`، `\\vec`) را از همهی متنهایی که دانشآموز میبیند پاک میکند:
|
||||
سوالها و گزینهها، متنِ درسها و گفتگوهای دستیار. فایلهای seed همین تبدیل را دارند؛ بدونِ این migration
|
||||
`seed_content` سوالِ تمیز را «تازه» میدید (صورتش با دیتابیس فرق دارد) و کنارِ نسخهی قدیمی دوباره میساخت.
|
||||
|
||||
تبدیل `content/plain_math.py::convert` است (idempotent). معکوس ندارد. پاسخِ دستیار با
|
||||
`rag.services.mathtext.to_plain` (همان مسیرِ زندهی پاسخها).
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
from content.plain_math import convert, convert_figures
|
||||
|
||||
|
||||
def _h(text):
|
||||
return hashlib.sha1((text or '').encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def _key(*parts):
|
||||
return hashlib.sha1('\x1f'.join(('intro', *map(str, parts))).encode('utf-8')).hexdigest()
|
||||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
Question = apps.get_model('content', 'Question')
|
||||
Choice = apps.get_model('content', 'Choice')
|
||||
Lesson = apps.get_model('content', 'Lesson')
|
||||
SeedRecord = apps.get_model('content', 'SeedRecord')
|
||||
|
||||
batch = []
|
||||
for q in Question.objects.all().iterator(chunk_size=1000):
|
||||
new = convert(q.prompt)
|
||||
if new != q.prompt:
|
||||
q.prompt = new
|
||||
batch.append(q)
|
||||
if len(batch) >= 500:
|
||||
Question.objects.bulk_update(batch, ['prompt'])
|
||||
batch = []
|
||||
if batch:
|
||||
Question.objects.bulk_update(batch, ['prompt'])
|
||||
|
||||
batch = []
|
||||
for c in Choice.objects.all().iterator(chunk_size=2000):
|
||||
new = convert(c.value)
|
||||
if new != c.value:
|
||||
c.value = new
|
||||
batch.append(c)
|
||||
if len(batch) >= 500:
|
||||
Choice.objects.bulk_update(batch, ['value'])
|
||||
batch = []
|
||||
if batch:
|
||||
Choice.objects.bulk_update(batch, ['value'])
|
||||
|
||||
# بعد از تبدیل ممکن است دو سوالِ همصورت در یک درس بماند: نسخهی قدیمیِ LaTeXدار و نسخهای که seed
|
||||
# پیش از این migration با متنِ تمیز ساخته بود. یکی نگه داشته میشود: آنکه پاسخِ بیشتری دارد، وگرنه قدیمیتر.
|
||||
from django.db.models import Count
|
||||
UserAnswer = apps.get_model('content', 'UserAnswer')
|
||||
groups = {}
|
||||
for qid, lid, prompt in Question.objects.values_list('id', 'lesson_id', 'prompt').iterator(chunk_size=2000):
|
||||
groups.setdefault((lid, prompt), []).append(qid)
|
||||
drop = []
|
||||
for ids in groups.values():
|
||||
if len(ids) > 1:
|
||||
answered = dict(UserAnswer.objects.filter(question_id__in=ids).values_list('question_id').annotate(n=Count('id')))
|
||||
keep = max(ids, key=lambda i: (answered.get(i, 0), -i))
|
||||
drop.extend(i for i in ids if i != keep)
|
||||
if drop:
|
||||
Question.objects.filter(id__in=drop).delete()
|
||||
|
||||
for lesson in Lesson.objects.select_related('node__unit__grade').iterator(chunk_size=500):
|
||||
new = {
|
||||
'title': convert(lesson.title),
|
||||
'intro_text': convert(lesson.intro_text),
|
||||
'example': convert(lesson.example),
|
||||
'steps': [convert(s) for s in (lesson.steps or [])],
|
||||
'figures': convert_figures(lesson.figures or []),
|
||||
}
|
||||
changed = [f for f, v in new.items() if v != getattr(lesson, f)]
|
||||
if not changed:
|
||||
continue
|
||||
old_intro, old_title = lesson.intro_text, lesson.title
|
||||
for f in changed:
|
||||
setattr(lesson, f, new[f])
|
||||
lesson.save(update_fields=changed)
|
||||
if 'intro_text' in changed:
|
||||
n = lesson.node
|
||||
key = _key(n.unit.grade.title, n.unit.title, n.title, old_title)
|
||||
SeedRecord.objects.filter(key=key, value=_h(old_intro)).update(value=_h(new['intro_text']))
|
||||
|
||||
try:
|
||||
Message = apps.get_model('ai_tutor', 'Message')
|
||||
except LookupError:
|
||||
return
|
||||
from rag.services.mathtext import to_plain
|
||||
batch = []
|
||||
for m in Message.objects.all().iterator(chunk_size=500):
|
||||
new = to_plain(m.text) if m.role == 'assistant' else convert(m.text)
|
||||
if new != m.text:
|
||||
m.text = new
|
||||
batch.append(m)
|
||||
if batch:
|
||||
Message.objects.bulk_update(batch, ['text'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [('content', '0020_plain_math_lesson_texts'), ('ai_tutor', '0008_alter_message_created_at')]
|
||||
operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]
|
||||
Reference in New Issue
Block a user