API output without Persian diacritics or written ezafe ی (company decision); tutor answers too

plain_persian() strips kasra/fatha/damma/shadda/sukun/hamza-above and the written «ه‌ی» ezafe, keeping tanwin. Applied
at output, not in data: PlainPersianJSONRenderer runs one byte-level pass over every JSON response (~5 ms per MB
locally), and to_plain() cleans tutor answers including the stream. Rewriting titles in the DB/seed would have broken
question binding by title path and SeedRecord keys. Tests updated to expect the cleaned text; config/tests_plain_persian.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 16:57:17 +03:30
parent 7b241c1aac
commit 1c7c4b8eca
16 changed files with 181 additions and 64 deletions

View File

@@ -0,0 +1,49 @@
"""
اعراب و «ی»ِ اضافه به کاربر نمی‌رسد (تصمیمِ شرکت، ۲۰۲۶-۱۰-۱۴): قاعده (`content.plain_math.plain_persian`) و لایه‌ی
خروجیِ API (`config.renderers.PlainPersianJSONRenderer`). دیتابیس متنِ اصلی را نگه می‌دارد.
"""
from django.core.cache import cache
from django.test import SimpleTestCase, TestCase
from rest_framework.test import APIClient
from content.models import Grade
from content.plain_math import plain_persian, plain_persian_utf8
class PlainPersianRuleTests(SimpleTestCase):
def test_kasra_and_written_ezafe_ye_go(self):
self.assertEqual(plain_persian('از پایه‌ی سوم، ریاضیِ کتابِ خودت'), 'از پایه سوم، ریاضی کتاب خودت')
def test_fatha_damma_shadda_sukun_and_hamza_above_go(self):
self.assertEqual(plain_persian('اَوّلُ خانهٔ مْ'), 'اول خانه م')
def test_tanwin_stays_because_it_is_spelling(self):
self.assertEqual(plain_persian('قبلاً، موقتاً'), 'قبلاً، موقتاً')
def test_ye_that_is_part_of_a_word_stays(self):
# «ه‌ای» (نکره) و «ه‌یی…» که حرفِ دیگری به «ی» چسبیده، اضافه نیستند.
self.assertEqual(plain_persian('جزیره‌ای تازه'), 'جزیره‌ای تازه')
self.assertEqual(plain_persian('خانه‌یمان'), 'خانه‌یمان')
def test_idempotent_and_non_strings_untouched(self):
once = plain_persian('همه‌ی جزیره‌های پایه‌ی هشتمِ')
self.assertEqual(plain_persian(once), once)
self.assertIsNone(plain_persian(None))
self.assertEqual(plain_persian(5), 5)
def test_utf8_version_matches_the_text_version(self):
sample = '{"t": "همه‌ی جزیره‌ی", "u": "خانه‌یمان، قبلاً، اَوّلُ، پایه‌ی ۸", "id": 12}'
self.assertEqual(plain_persian_utf8(sample.encode()).decode(), plain_persian(sample))
class ApiOutputTests(TestCase):
def setUp(self):
cache.clear()
def test_api_json_is_clean_while_the_database_keeps_the_original(self):
Grade.objects.create(id=8, number=8, title='پایه‌ی هشتمِ', icon='x', color='#000')
body = APIClient().get('/api/grades').content.decode('utf-8')
self.assertIn('پایه هشتم', body)
self.assertNotIn('ِ', body)
self.assertNotIn('ه‌ی', body)
self.assertEqual(Grade.objects.get(id=8).title, 'پایه‌ی هشتمِ')