From 56ddaa1ed450d47a6e15c23d17a1f71074f49e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 7 Aug 2026 08:25:28 +0300 Subject: [PATCH] =?UTF-8?q?fix=20=D0=BE=D0=B1=D0=B7=D0=B2=D0=BE=D0=BD:=20?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D0=BD=20=D1=87=D0=B5=D0=BB=D0=BE=D0=B2=D0=B5?= =?UTF-8?q?=D0=BA,=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=208=20=D0=B8=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20+7,=20=D0=BF=D0=BE=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B0=D0=BB=20=D0=B4=D0=B2=D0=B0=20=D0=B7=D0=B2=D0=BE?= =?UTF-8?q?=D0=BD=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Решение владельца Р127. В `odinVidNomera` восьмёрка приводится к семёрке ТОЛЬКО когда цифр ровно одиннадцать. Короткие и длинные номера не трогаются вовсе: слить двух разных людей в один знак — ошибка тише и хуже, второму не позвонили бы никогда и молча. Замерено до правки: `8 999 000-00-01` и `+7 999 000-00-01` давали два разных знака и два разных тела. Сторожа смотрят на то, что вправду уехало роботу, и сравнивают дословно — так же, как сравнивает приёмник. Сторожа показаны красными тремя сломами: без починки, без проверки длины и с приведением к восьмёрке вместо семёрки. Возврат доказан слепком файла. Названная владельцу цена: иностранные одиннадцатизначные номера с кодом на восьмёрку это правило портит. Вьетнамские `8496…`/`8497…`/`8498…` станут похожи на живые коды Московской области — робот позвонит чужому человеку. Лечится чисткой номеров на входе, это отдельная задача. Подробности и незакрытые хвосты — в отчёте. Co-Authored-By: Claude Opus 5 --- app/app/Services/Obzvon/ObzvonClient.php | 44 ++- .../Feature/Obzvon/ObzvonClientShovTest.php | 117 ++++++++ ...t-pomoshchnika-r127-vosmerka-2026-08-07.md | 262 ++++++++++++++++++ 3 files changed, 416 insertions(+), 7 deletions(-) create mode 100644 docs/superpowers/priyomka/stroyka-6/otchyot-pomoshchnika-r127-vosmerka-2026-08-07.md diff --git a/app/app/Services/Obzvon/ObzvonClient.php b/app/app/Services/Obzvon/ObzvonClient.php index cad7cd0d..72f220eb 100644 --- a/app/app/Services/Obzvon/ObzvonClient.php +++ b/app/app/Services/Obzvon/ObzvonClient.php @@ -205,16 +205,46 @@ final class ObzvonClient * снимает отпечаток с тела ДОСЛОВНО и на такую пару отвечает 422 — то есть * честный повтор получал отказ вместо звонка. * - * 🪤 Что этот канон НЕ делает, говорю вслух: он снимает ЗАПИСЬ (плюсы, - * скобки, пробелы, тире), но не решает, один ли человек за `8999…` и - * `+7999…`. Для портала это сегодня РАЗНЫЕ люди — разный знак, два звонка. - * Считать ли их одним, решает владелец, и это отдельное решение: ошибись оно - * в другую сторону — два РАЗНЫХ человека слились бы в один знак, и второму - * не позвонили бы никогда. Молча угадывать такое нельзя. + * 🔴 Р127 (решение владельца 07.08.2026). Канон снимает не только ЗАПИСЬ + * (плюсы, скобки, пробелы, тире), но и разницу между `8999…` и `+7999…` — + * ровно при ОДИННАДЦАТИ цифрах. Это один и тот же живой человек, записанный + * двумя обычными способами; до Р127 он получал два разных знака, ему звонили + * дважды, и клиент платил за два звонка вместо одного. + * + * 🔴 Почему правило такое узкое, а не «любой номер с восьмёрки». Ошибка в + * обратную сторону тише и хуже: слей мы двух РАЗНЫХ людей в один знак, робот + * счёл бы второе задание повтором первого, и второму человеку не позвонили бы + * НИКОГДА и МОЛЧА. Ровно одиннадцать цифр с ведущей `8` — это российский + * междугородний набор (та же форма и у казахстанских `+7`), и второго + * толкования у него в наших списках нет. Короткие (`8495123`) и длинные + * (`8999000000012`) не трогаем ВОВСЕ: там `8` может значить что угодно. + * + * 🪤 Цена, названная владельцу вслух: одиннадцатизначные ИНОСТРАННЫЕ номера, + * начинающиеся на `8` (Вьетнам `+84`, Гонконг `+852`, Япония `+81`), это + * правило испортит — `84…` превратится в `74…`. Вьетнамский при этом может + * стать ПОХОЖИМ на живой российский, и позвонят чужому человеку. Лечится это + * чисткой номеров на входе, при загрузке списка, — отдельная задача, которой + * в плане сейчас нет. + * + * 🪤 И второе, тоже вслух: `9990000001` (десять цифр, без кода страны) для + * этого канона по-прежнему ДРУГОЙ человек, чем `79990000001`. А проверка + * запрета обзвона ({@see ObzvonZapretObrabotki}) сверяет ПОСЛЕДНИЕ ДЕСЯТЬ + * цифр и считает их одним. Расхождение узкое и намеренное — Р127 короткие + * номера трогать запретил. + * + * 🔴 Правило устойчиво к повтору: приведённый номер, поданный сюда второй + * раз, не меняется. На это опирается {@see self::pozvonit()} — он приводит + * номер сам, а {@see self::klyuchZadaniya()} приводит его ещё раз. */ private static function odinVidNomera(string $telefon): string { - return preg_replace('/\D+/', '', $telefon) ?? ''; + $cifry = preg_replace('/\D+/', '', $telefon) ?? ''; + + if (strlen($cifry) === 11 && str_starts_with($cifry, '8')) { + return '7'.substr($cifry, 1); + } + + return $cifry; } /** diff --git a/app/tests/Feature/Obzvon/ObzvonClientShovTest.php b/app/tests/Feature/Obzvon/ObzvonClientShovTest.php index 1208cbda..8a271147 100644 --- a/app/tests/Feature/Obzvon/ObzvonClientShovTest.php +++ b/app/tests/Feature/Obzvon/ObzvonClientShovTest.php @@ -442,6 +442,123 @@ it('🔴 Г: вторая попытка по тому же номеру — з ->and($otvet->poluchenLi())->toBeTrue(); }); +// ───────────────────────────────────────────────────────────────────────────── +// 🔴🔴 Р127 — ВОСЬМЁРКА К СЕМЁРКЕ, И ТОЛЬКО ПРИ ОДИННАДЦАТИ ЦИФРАХ +// +// Что было замерено ДО починки: `8 999 000-00-01` и `+7 999 000-00-01` — один и +// тот же живой человек, записанный двумя обычными способами, — давали ДВА разных +// знака и два разных тела. Ему позвонили бы дважды, и клиент заплатил бы за два +// звонка вместо одного. +// +// Решение владельца Р127: приводить `8` к `7` ТОЛЬКО когда цифр ровно +// одиннадцать. Короткие и длинные не трогать вовсе — там `8` может значить что +// угодно, и слияние двух РАЗНЫХ людей в один знак было бы ошибкой в обратную +// сторону: второму не позвонили бы никогда и МОЛЧА. +// +// 🪤 Сторожа ниже сверяют то, что и вправду уехало подставному роботу, и +// сверяют ДОСЛОВНО — так же, как сравнивает приёмник. Сверка «с точностью до +// приведения» была бы слепа ровно к той половине беды, ради которой пишется. +// ───────────────────────────────────────────────────────────────────────────── + +it('🔴 Р127 А (нож): восьмёрка и плюс-семь — ОДИН человек, один знак и одно тело', function () { + Http::fake(fn () => Http::response(['ok' => true, 'call_id' => 'x'], 200)); + + $klient = klientObzvona(); + $klient->pozvonit(...zadanieProbnoe(['telefon' => '8 999 000-00-01'])); + $klient->pozvonit(...zadanieProbnoe(['telefon' => '+7 999 000-00-01'])); + + $tela = telaUshedshih(); + + expect($tela)->toHaveCount(2) + ->and($tela[1]['zadanie'])->toBe($tela[0]['zadanie']) + ->and($tela[1]['telefon'])->toBe($tela[0]['telefon']) + ->and($tela[0]['telefon'])->toBe('79990000001'); +}); + +it('🔴 Р127 Б (контроль): что совпадало до починки — совпадает и после', function () { + Http::fake(fn () => Http::response(['ok' => true, 'call_id' => 'x'], 200)); + + $klient = klientObzvona(); + $klient->pozvonit(...zadanieProbnoe(['telefon' => '+7 (999) 000-00-01'])); + $klient->pozvonit(...zadanieProbnoe(['telefon' => '79990000001'])); + + $tela = telaUshedshih(); + + expect($tela)->toHaveCount(2) + ->and($tela[1]['zadanie'])->toBe($tela[0]['zadanie']) + ->and($tela[1]['telefon'])->toBe($tela[0]['telefon']); +}); + +it('🔴 Р127 В (граница): одиннадцать цифр НЕ с восьмёрки — не трогаем', function () { + Http::fake(fn () => Http::response(['ok' => true, 'call_id' => 'x'], 200)); + + klientObzvona()->pozvonit(...zadanieProbnoe(['telefon' => '79990000001'])); + + expect(telaUshedshih()[0]['telefon'])->toBe('79990000001'); +}); + +it('🔴 Р127 Г (граница): начинается на 8, но цифр НЕ одиннадцать — не трогаем вовсе', function () { + // 🔴 Ровно то, что владелец отрезал сознательно. Короткий `8495123` — это не + // «российский мобильный без кода»: подставь мы туда семёрку, два РАЗНЫХ + // человека могли бы слиться в один знак, и второму не позвонили бы МОЛЧА. + Http::fake(fn () => Http::response(['ok' => true, 'call_id' => 'x'], 200)); + + $klient = klientObzvona(); + $klient->pozvonit(...zadanieProbnoe(['telefon' => '8495123'])); + $klient->pozvonit(...zadanieProbnoe(['telefon' => '8999000000012'])); + + $tela = telaUshedshih(); + + expect($tela)->toHaveCount(2) + ->and($tela[0]['telefon'])->toBe('8495123') + ->and($tela[1]['telefon'])->toBe('8999000000012'); +}); + +it('🔴 Р127 Г: длинный номер с восьмёрки НЕ сливается со своим семёрочным двойником', function () { + // Сторож против «улучшения» правила: сними кто-нибудь проверку длины — эти + // два знака стали бы одним, и одному из двух людей не позвонили бы вовсе. + expect(ObzvonClient::klyuchZadaniya(1, 7, '8999000000012', 1)) + ->not->toBe(ObzvonClient::klyuchZadaniya(1, 7, '7999000000012', 1)); + + expect(ObzvonClient::klyuchZadaniya(1, 7, '8495123', 1)) + ->not->toBe(ObzvonClient::klyuchZadaniya(1, 7, '7495123', 1)); +}); + +it('🔴 Р127 Д (граница): строка без цифр — дверь по-прежнему закрыта', function () { + // Это уже было построено до Р127 и обязано пережить починку: восьмёрка не + // должна была открыть дверь тому, кому она закрыта. + Http::fake(); + + $otvet = klientObzvona()->pozvonit(...zadanieProbnoe(['telefon' => 'абв'])); + + expect($otvet->iskhod)->toBe(IskhodObrashcheniya::DverZakryta) + ->and($otvet->zvonokMogSostoyatsya())->toBeFalse(); + Http::assertNothingSent(); +}); + +it('🔴 Р127 Е: вторая попытка по восьмёрочному номеру — знак ДРУГОЙ, звонок законный', function () { + Http::fake(fn () => Http::response(['ok' => true, 'call_id' => 'x'], 200)); + + $klient = klientObzvona(); + $klient->pozvonit(...zadanieProbnoe(['telefon' => '8 999 000-00-01', 'popytkaNo' => 1])); + $otvet = $klient->pozvonit(...zadanieProbnoe(['telefon' => '8 999 000-00-01', 'popytkaNo' => 2])); + + $tela = telaUshedshih(); + + expect($tela)->toHaveCount(2) + ->and($tela[1]['zadanie'])->not->toBe($tela[0]['zadanie']) + ->and($tela[1]['telefon'])->toBe($tela[0]['telefon']) + ->and($otvet->poluchenLi())->toBeTrue(); +}); + +it('🔴 Р127: приведение НЕ портит знак дважды — второй прогон ничего не меняет', function () { + // 🪤 `pozvonit` приводит номер, а `klyuchZadaniya` приводит его ВТОРОЙ раз + // уже приведённым. Не будь правило устойчивым к повтору, знак в теле и знак, + // посчитанный от тела, разошлись бы — и робот ответил бы 422. + expect(ObzvonClient::klyuchZadaniya(1, 7, '8 999 000-00-01', 1)) + ->toBe(ObzvonClient::klyuchZadaniya(1, 7, '79990000001', 1)); +}); + it('🔴 знак в заголовках — тот же самый, что в теле', function () { // Приёмник сверяет тело с заголовком и на расхождении отвечает 400. Разойдись // они здесь — не дозвонились бы вовсе, и по коду 400 причину не узнать. diff --git a/docs/superpowers/priyomka/stroyka-6/otchyot-pomoshchnika-r127-vosmerka-2026-08-07.md b/docs/superpowers/priyomka/stroyka-6/otchyot-pomoshchnika-r127-vosmerka-2026-08-07.md new file mode 100644 index 00000000..933eaee4 --- /dev/null +++ b/docs/superpowers/priyomka/stroyka-6/otchyot-pomoshchnika-r127-vosmerka-2026-08-07.md @@ -0,0 +1,262 @@ +# Отчёт помощника — Р127: восьмёрка к семёрке, стройка 6 + +**Дата:** 07.08.2026 +**Ветка:** `feat/prospects-manual-testing-kp` +**Задача:** исполнить решение владельца Р127 в `odinVidNomera()` и ничего сверх. + +## Что было велено + +Ровно одиннадцать цифр, первая `8` → заменить первую на `7`. Короткие и длинные +не трогать вовсе. Всё остальное в клиенте не трогать. + +## Моя ошибка — называю первой + +Разбирая задание, я сперва принял на веру довод «одиннадцать цифр с восьмёркой +не имеют второго толкования» и собирался ответить «возражений нет». Проверив, я +нашёл, что второе толкование есть, — и нашёл его только потому, что владелец +попросил проверить именно это место. Сам бы я мимо прошёл. Подробности — в +разделе «Разбор задания». + +## Что построено + +Один файл, один метод, четыре строки работы: + +```php +$cifry = preg_replace('/\D+/', '', $telefon) ?? ''; + +if (strlen($cifry) === 11 && str_starts_with($cifry, '8')) { + return '7'.substr($cifry, 1); +} + +return $cifry; +``` + +Правило устойчиво к повтору (приведённый номер, поданный второй раз, не +меняется) — это обязательно: `pozvonit()` приводит номер сам, а +`klyuchZadaniya()` приводит его ещё раз. Не будь устойчивости, знак в теле и +знак, посчитанный от тела, разошлись бы, и робот ответил бы 422. + +Сверх этого не тронуто ничего: разбор ответа робота, оба предела времени, предел +одновременных, проверка шифрования (Р123), подпись `pozvonit` — как были. + +## Мерка 1 — сторожа, показанные КРАСНЫМИ + +Сторожа смотрят на то, что **вправду уехало подставному роботу** (поля `zadanie` +и `telefon` перехваченного запроса), и сравнивают **дословно** — так же, как +сравнивает приёмник. Сверка «с точностью до приведения» была бы слепа ровно к +той половине беды, ради которой пишется. + +Файл: `app/tests/Feature/Obzvon/ObzvonClientShovTest.php`, раздел «Р127». + +### Слом 1 — починки нет вовсе (исходное состояние, RED до правки) + +`63 теста, 61 зелёный, 2 красных` + +| Сторож | Что сказал дословно | +|---|---| +| `Р127 А (нож)` | `Failed asserting that two strings are identical. -'z55cd4b336662c0c967a469e193b10791fe7231d5' +'z3739fcb0392739d92d950b3b97eb31e145da4dce'` | +| `Р127: приведение НЕ портит знак дважды` | `Failed asserting that two strings are identical. -'z3739fcb0392739d92d950b3b97eb31e145da4dce' +'z55cd4b336662c0c967a469e193b10791fe7231d5'` | + +Это и есть замеренная беда: два знака на одного человека. + +### Слом 2 — снял проверку длины (`=== 11` → `>= 7`) + +`63 теста, 61 зелёный, 2 красных` + +| Сторож | Что сказал дословно | +|---|---| +| `Р127 Г (граница): начинается на 8, но цифр НЕ одиннадцать` | `Failed asserting that two strings are identical. -'8495123' +'7495123'` | +| `Р127 Г: длинный номер с восьмёрки НЕ сливается со своим семёрочным двойником` | `Expecting 'zc0a12c93a346ed7b0ee5f1849f90…53b662' not to be 'zc0a12c93a346ed7b0ee5f1849f90…53b662'.` | + +Второй сторож — тот самый, что охраняет от ошибки в обратную сторону: слияния +двух РАЗНЫХ людей в один знак. + +### Слом 3 — привёл к восьмёрке вместо семёрки + +`63 теста, 59 зелёных, 4 красных` + +| Сторож | Что сказал дословно | +|---|---| +| `Р127 А (нож)` | `-'79990000001' +'89990000001'` | +| `Р127 В (граница): одиннадцать цифр НЕ с восьмёрки — не трогаем` | `-'79990000001' +'89990000001'` | +| `в теле задания номер без украшений` (прежний сторож) | `-'79990000001' +'89990000001'` | +| `«что ещё сказать роботу» не может перебить знак или номер` (прежний) | `-'79991234567' +'89991234567'` | + +Этот слом доказывает, что случай В — не украшение: он ловит правило, которое +сливает пару правильно, но выбирает не тот канон. + +### Возврат доказан слепком + +```text +должно быть 72f220ebe9a26cb4545be92e83d14c672e96b00d +72f220ebe9a26cb4545be92e83d14c672e96b00d +``` + +(`tr -d '\r' < app/app/Services/Obzvon/ObzvonClient.php | git hash-object --stdin`) + +После возврата: `63 теста, 63 зелёных, 158 проверок`. + +## Мерка 2 — полный прогон + +Своя база `liderra_testing_vosmerka`, поднята с нуля через `migrate --force`. +Вывод в файл, не в трубу. + +```text +"result":"passed" +"tests":5301 +"passed":5297 +"skipped":4 +"assertions":21819 +"duration_ms":1177716 +``` + +**Датчик — число проверок, а не код возврата.** Арифметика сходится: +`5297 зелёных + 0 красных + 0 ошибок + 4 пропущено = 5301 всего`. + +На входе было **5293** проверки при нуле красных. Стало **5301** — ровно на +восемь больше, и восемь новых сторожей я и написал. Красных — **ноль**. +Красноты в `Render` и `Autopodbor` не было вовсе, расследовать нечего. + +Заодно: `composer stan` — **0 ошибок**; `vendor/bin/pint --test` по обоим моим +файлам — **passed**. + +## Мерка 3 — про соседей + +моего имени нет ни в одном выводе ни одного сторожа + +чужие файлы в дереве не тронуты + +## Мерка 4 — зовущие поимённо + +🔴 Прибор (`grep`) считает **упоминания строк**, а не вызовы: он одинаково +покажет и `self::odinVidNomera($telefon)`, и `{@see self::odinVidNomera()}` +внутри пояснения. По `app/app` он даёт **13 упоминаний**. Вызовов среди них +**три**, и все три — внутри самого `ObzvonClient.php`: + +| Кто зовёт | Кого | Где | +|---|---|---| +| `ObzvonClient::klyuchZadaniya()` | `odinVidNomera()` | `ObzvonClient.php:193` | +| `ObzvonClient::pozvonit()` | `odinVidNomera()` | `ObzvonClient.php:255` | +| `ObzvonClient::pozvonit()` | `klyuchZadaniya()` | `ObzvonClient.php:269` | + +Остальные десять — упоминания в пояснениях (`{@see …}`), в том числе +`ObzvonOtvet.php:84` и `Autopodbor/Agent/Fetch/DverRendera.php:21`. Ни одного +вызова оттуда нет. + +**За пределами класса `ObzvonClient` живых потребителей сегодня НЕТ ни одного** — +ни джоба, ни команды, ни контроллера. Из тестов зовёт один файл: +`app/tests/Feature/Obzvon/ObzvonClientShovTest.php`. Значит область поражения +правки замкнута двумя файлами. + +## Разбор задания + +### Картина беды верна + +Замер сходится: `'8 999 000-00-01'` и `'+7 999 000-00-01'` давали разные знаки и +разные тела. Я это увидел красным до правки (слом 1). Лечение лечит именно это. + +### 🔴 «Одиннадцать цифр с восьмёркой не имеют второго толкования» — НЕВЕРНО. Цена + +Владелец просил проверить это место, и оно не выдерживает проверки. Второе +толкование есть: **иностранные номера, у которых код страны начинается на `8` и +всего цифр ровно одиннадцать.** + +- **Вьетнам `+84`** — код 2 цифры + 9 цифр номера = **ровно 11**. +- **Гонконг `+852`**, **Макао `+853`**, **Камбоджа `+855`**, **Лаос `+856`** — + 3 + 8 = **11**. +- **Япония `+81`**, **Корея `+82`** — 2 + 9 = **11** (городские). + +Большинство из них после подмены дают номер, которого в России не существует +(`+852…` → `+7 52…`, а кода `52x` у нас нет) — такой звонок просто не состоится, +это ошибка громкая и неопасная. + +🔴 **А вот Вьетнам опасен по-настоящему.** Вьетнамские мобильные Viettel +`096/097/098` в международной записи выглядят `8496…`, `8497…`, `8498…`. +Р127 превратит их в `7496…`, `7497…`, `7498…` — а `496` и `498` это **живые коды +Московской области**. То есть иностранный номер молча станет похож на настоящий +российский, и робот позвонит **чужому человеку**, который в списке не значился. + +Это меняет род ошибки: **было** «номер не тот, звонок не состоится», **стало** +«звонок состоится, но не тому». Второе тише и хуже — тот самый род, ради +которого Р127 и делали узким. + +**Насколько это вероятно у нас.** Списки клиента — российские, иностранный номер +в них попадёт разве что опечаткой или мусором в выгрузке. Оценивать вероятность +я не берусь, это не мой замер. + +🔴 **И сразу поправлю сам себя, иначе цена названа нечестно.** Эта беда **не +новая и не моей правкой внесена**: у портала уже есть общий нормализатор +`App\Support\PhoneNormalizer::normalize()`, и он делает **ровно то же самое**: + +```php +if (strlen($digits) === 11 && ($digits[0] === '8' || $digits[0] === '7')) { + $digits = '7'.substr($digits, 1); +} +``` + +Им пользуются **двадцать файлов** — считано тем же прибором и с той же оговоркой: +это файлы, где имя встречается, а не число вызовов. Среди них загрузка баз СМС и телеграма, загрузка +номеров рекламной кампании, реквизиты, проекты, автоподбор — и, что важнее +всего, **сам обзвон** в `ObzvonZamorozka` при проверке номера менеджера. То есть +вьетнамский номер уже сегодня превращается в подмосковный на входе в любой +канал. Р127 не добавляет опасности — он **прекращает расхождение** обзвона с +остальным порталом. Это сильный довод ЗА Р127, и владельцу его стоит знать. + +**Чем лечится по-хорошему.** Отдельной чисткой на входе в обзвон, которая умеет +сказать «это не российский номер» и отбить его с внятной причиной. Владелец эту +задачу уже отрезал как отдельную. + +**Чем НЕ лечится.** Сузить правило по второй цифре (пускать только `3/4/8/9` — +первые цифры живых российских кодов) кажется дешёвым ходом, но Вьетнам он **не +отсекает**: у `84…` вторая цифра как раз `4`. Так что полумеры тут нет. + +**Решение Р127 я не отменяю и правку сделал.** Цена названа, чтобы владелец знал +её и решил сам. + +### Что Р127 делает правильно и стоит сказать вслух + +Казахстан тоже живёт на коде `+7` с междугородним `8` (`8 701 …` = 11 цифр). +Р127 приводит казахстанские номера верно, а не ломает. + +### Второе место того же класса, о котором в задании не сказано + +🔴 **В обзвоне сейчас живут ТРИ разных канона телефона, и `odinVidNomera` — +третий, самодельный.** + +| Канон | Что делает | Где | +|---|---|---| +| `PhoneNormalizer::normalize` | 11 цифр с `8`/`7` → `+7…`; **10 цифр → `+7…`**; всё прочее → `null`, то есть **отбить** | общий для портала, в обзвоне — `ObzvonZamorozka` | +| `ObzvonErasureAdapter::hvostCifr` | **последние 10 цифр**, короче — как есть | `ObzvonZapretObrabotki` — проверка запрета звонить | +| `ObzvonClient::odinVidNomera` | после Р127: 11 цифр с `8` → `7…`; всё прочее — **как есть** | знак и тело задания | + +После Р127 все три сходятся на главном случае — это и есть польза правки. Но два +расхождения остаются, и оба того же класса, что и починенная беда: + +1. **Десятизначный номер.** Пару `9990000001` и `79990000001` общий + нормализатор и проверка запрета считают **одним человеком**, а знак задания — + **двумя**. Значит на этой паре «позвонят дважды» **остаётся**. +2. **Мусор вместо номера.** `PhoneNormalizer` такую строку **отбивает** словом + «не читается как телефон». `odinVidNomera` пропускает её дальше, если в ней + есть хоть одна цифра, — и робот получит задание на несуществующий номер. + +Чинить я это не стал и чинить молча не буду: Р127 короткие номера трогать +запретил прямым словом, а замена самодельного канона на общий — это уже другая +правка, которая меняет поведение двери и объём отбоя. Записываю как незакрытое. + +## Что осталось незакрытым + +1. **Иностранные одиннадцатизначные номера с `8`** — цена Р127, описана выше. + Вьетнамский может стать похожим на подмосковный, и позвонят чужому. Беда не + новая: общий `PhoneNormalizer` ведёт себя так же уже сегодня во всех каналах. +2. **Пара «десять цифр / одиннадцать цифр»** — два знака на одного человека + остаются. Запрет обзвона и общий нормализатор считают их одним, знак задания + — нет. +3. **Мусорная строка с одной цифрой** проходит в задание. Общий нормализатор + такую отбивает, `odinVidNomera` — нет. +4. **Три разных канона телефона в одном модуле** — сведение их к общему + `PhoneNormalizer` не делалось: это шире Р127. +5. **Что робот и вправду читает знак** — как и раньше, здесь не доказывается. + Замыкает приёмник З-0.6. +6. **Отдельной чистки номеров на входе в обзвон нет** — отрезано владельцем как + отдельная задача. Пункты 1–3 закрылись бы там. +