# -*- coding: utf-8 -*- """Детермінований роутер: голий рядок цифр -> читання ПО ЦИФРІ, без моделі. Навіщо коду, а не даними: поцифрове читання вимагає ТОЧНОГО збереження кількості цифр, а модель саме цього не вміє — виміряно на v42: «Код 0000.» -> «нуль нуль нуль» (3 з 4) «Код 000000.» -> «нуль нуль нуль нуль» (4 з 6) Навчити її поцифровому читанню = успадкувати цей баг тихо й на довгих ID. Тут кількість гарантована кодом. Спрацьовує ЛИШЕ якщо ВЕСЬ вхід — суцільні цифри від MIN_DIGITS знаків. Не спрацьовує: * якщо є роздільники («10 000 001» — пробіл є сигналом «це кількість»); * якщо число всередині речення (там модель читає кількістю, 17/19 правильно); * якщо цифр менше за поріг («42», «2026» модель читає правильно). """ import re MIN_DIGITS = 7 DIGIT = {"0": "нуль", "1": "один", "2": "два", "3": "три", "4": "чотири", "5": "п'ять", "6": "шість", "7": "сім", "8": "вісім", "9": "дев'ять"} _BARE = re.compile(r"\A\s*(\d+)\s*\Z") def match_bare_digits(text: str): """рядок цифр, якщо вхід — голе число, яке треба читати по цифрі, інакше None. Два випадки: * від MIN_DIGITS знаків — довгий рядок без контексту це ідентифікатор; * ПРОВІДНИЙ НУЛЬ за будь-якої довжини («007», «0000») — провідний нуль означає, що це не кількість; заразом лікує серії нулів, де модель губила цифри («000000» -> лише п'ять «нуль»). """ m = _BARE.match(text or "") if not m: return None digits = m.group(1) if len(digits) >= MIN_DIGITS: return digits if len(digits) >= 2 and digits[0] == "0": return digits return None def spell_digits(digits: str) -> str: return " ".join(DIGIT[d] for d in digits) def route(text: str): """(handled, output). handled=False -> віддати моделі як є.""" d = match_bare_digits(text) if d is None: return False, None return True, spell_digits(d) # -------------------------------------------------------------------------- if __name__ == "__main__": CASES = [ # (вхід, очікується_перехоплення, очікуваний_вихід_або_None) ("12345678", True, "один два три чотири п'ять шість сім вісім"), ("10000001", True, "один нуль нуль нуль нуль нуль нуль один"), ("000000", True, "нуль нуль нуль нуль нуль нуль"), # провідний нуль ("0000000", True, "нуль нуль нуль нуль нуль нуль нуль"), ("123456789012", True, None), # 12 цифр: поза межами рушія, роутер тягне (" 918273645 ", True, None), # пробіли по краях не заважають ("42", False, None), ("007", True, "нуль нуль сім"), ("0000", True, "нуль нуль нуль нуль"), ("2026", False, None), ("10 000 001", False, None), # роздільники -> кількість ("0800 500 000", False, None), # телефон -> наявна конвенція ("У звіті вказано число 12345678.", False, None), # у реченні -> кількість ("12345678.", False, None), # крапка = вже не голе число ("№1024", False, None), ("", False, None), ] bad = 0 for text, want_handled, want_out in CASES: handled, out = route(text) ok = handled == want_handled and (want_out is None or out == want_out) bad += not ok print(f" [{'OK ' if ok else 'FAIL'}] {text!r:34} -> handled={handled} {out or ''}") # інваріант: кількість слів завжди дорівнює кількості цифр for n in ("0000000", "1234567890", "9" * 20, "1000000000"): assert len(spell_digits(n).split()) == len(n), n print(f"\nінваріант «слів = цифр» тримається") print("ПРОВАЛЕНО" if bad else "ВСІ ТЕСТИ ОК")