Тема
ТЗ — DaData/ФИАС (cms/dadata)
Слой: 🟡 функц-модуль · Зрелость доноров: ★★ · Донор: — Статус: ТЗ к разработке
Назначение и возможности
Подсказки адресов/ФИО/email на формах сайта через DaData (синхронный вызов с таймаутом и graceful fallback на ручной ввод) и получение реквизитов юрлица по ИНН для B2B.
- Подсказки адресов (на базе ФИАС), ФИО, email на формах ядра при вводе
- Синхронный вызов с коротким таймаутом — при недоступности DaData поле остаётся обычным текстовым вводом без подсказок, форма не блокируется
- Реквизиты юрлица по ИНН — источник данных для
cms/commerce-b2bпри регистрации контрагента - Стандартизация адреса доставки (приведение к единому формату для
cms/commerce-delivery) - Кеш подсказок — повторный запрос одинаковой строки не бьёт в API повторно
- Массовая стандартизация существующей базы адресов клиента через DaData Clean API — батч-джобой по требованию, с отчётом (не на горячем пути)
provides: suggest-provider— единый контракт подсказок для форм ядра
Зависимости и выключение
requires: cms/integrations-bus · provides: suggest-provider
Поведение при выключении: поля адреса/ФИО/email на формах переходят в обычный текстовый ввод без подсказок, реквизиты юрлица по ИНН вводятся вручную — оформление форм не блокируется.
Экстренное отключение подсказок без выключения модуля целиком — настройка dadata.kill_switch (см. «Настройки»): реквизиты по ИНН и батч-стандартизация продолжают работать, отключается только автодополнение на формах.
Модель данных
| Таблица | Ключевые поля | Примечание |
|---|---|---|
cms_dadata_suggest_cache | id, type, query_hash, response (json), subject_hash (nullable), cached_at | кеш подсказок по типу (address/fio/email/party); subject_hash — обезличенный указатель для очистки по «забыть по запросу» |
cms_dadata_standardize_runs | id, status, total, updated, not_found, started_at, finished_at | журнал прогонов батч-стандартизации адресов (§16), append-only |
Индексы: type — PHP Enum; уникальный индекс на type+query_hash; response — json() + cast array; cms_dadata_standardize_runs.status — PHP Enum (pending/running/finished/failed).
ПДн в модели: response кеша подсказок типа address/fio может содержать ФИО и адрес — это персональные данные субъекта, а не только служебные данные модуля (матрица v2.2, см. «Безопасность»).
Входные и выходные данные
Входы (whitelist — всё не перечисленное отвергается FormRequest):
| Источник | Поля | Валидация |
|---|---|---|
GET /api/v1/dadata/suggest/{type} | query (строка), count (int, ≤20) | type ∈ whitelist dadata.enabled_types; query — минимальная длина dadata.min_query_length |
GET /api/v1/dadata/party/{inn} | inn (route param) | формат ИНН (10/12 цифр), контрольная сумма |
cms:dadata:standardize-addresses (команда) | --source=<профиль> | профиль маппинга полей адреса, --dry-run |
Форма ядра (движок полей, тип address/fio/email) | значение поля при вводе | те же правила, что и у публичного suggest |
Выходы:
| Куда | Формат |
|---|---|
200 ответ suggest | {"data": [{value, unrestricted_value, meta:{…}}], "meta": {…}} |
200 ответ suggest при деградации | {"data": [], "meta": {"degraded": true, "degraded_reason": "quota_exceeded"|"timeout"|"provider_unavailable"}} |
200 ответ party | {"data": {inn, name, address, status}, "meta": {…}} |
200 ответ party, организация не найдена | {"data": null, "meta": {}} — не ошибка |
Событие DaDataSuggestTimedOut | type, query_hash |
Событие DaDataPartyResolved | inn, party_name |
Отчёт standardize-addresses | total, updated, not_found, errors[] (построчно) — скачиваемый CSV в Filament |
Whitelist-принцип: типы подсказок ограничены dadata.enabled_types, поля стандартизации — профилем --source; произвольные поля в запросе игнорируются на уровне FormRequest (422 при неизвестном параметре — конвенция ядра §7 стандарта).
Настройки (группа dadata)
| Ключ | Тип | Дефолт | affectsPageCache | Описание |
|---|---|---|---|---|
dadata.enabled_types | array | ["address"] | нет | Типы подсказок (address, fio, email, party) |
dadata.timeout_ms | int | 800 | нет | Таймаут синхронного запроса подсказки (≤2000 по правилу §9 стандарта) |
dadata.suggest_cache_ttl_days | int | 30 | нет | Срок жизни кеша подсказок — обязан укладываться в срок, разрешённый договором с DaData (см. «Крайние случаи») |
dadata.min_query_length | int | 3 | нет | Минимальная длина строки для запроса подсказки — защита квоты от запроса на каждое нажатие |
dadata.daily_quota | int | 10000 | нет | Дневной лимит запросов подсказок (ориентир — сверяться с актуальным тарифом DaData) |
dadata.kill_switch | bool | false | нет | Аварийное отключение подсказок без выключения модуля (реквизиты по ИНН продолжают работать) |
Секреты (API-ключ DaData) — только .env.
API
| Метод | Путь | Доступ | Назначение |
|---|---|---|---|
| GET | /api/v1/dadata/suggest/{type} | rate-limit | Подсказка по типу (адрес/ФИО/email) с кешем; 200 + meta.degraded при исчерпанной квоте/таймауте/kill_switch |
| GET | /api/v1/dadata/party/{inn} | rate-limit | Реквизиты юрлица по ИНН для cms/commerce-b2b |
| POST | /api/v1/admin/dadata/standardize-addresses | admin (dadata.manage) | Запуск батч-стандартизации адресов (ставит job в очередь, отвечает 202 + id прогона) |
| GET | /api/v1/admin/dadata/standardize-addresses/{runId} | admin (dadata.view) | Статус и отчёт прогона стандартизации |
Деградация по конвенции ядра (ревизия 14.07.2026, п. 2): suggest никогда не отвечает 429/5xx на публичном чтении — только 200 + meta.degraded.
Компоненты
Отдельного публичного Filament-ресурса на подсказки нет — используется как сервис форм ядра. Есть служебный ресурс:
- Filament: список прогонов
cms_dadata_standardize_runs(только чтение статуса и отчёта, запуск — через API/команду), индикатор остатка дневной квоты на странице здоровья модуля. - Команды:
cms:dadata:doctor --json(доступность API, остатокdadata.daily_quota, время сброса лимита);cms:dadata:standardize-addresses --source=<профиль> [--dry-run] --json(батч-стандартизация, идемпотентна — см. «Фоновая работа»).
События и обмен
| Событие | Когда | Payload |
|---|---|---|
DaDataSuggestTimedOut | подсказка не успела ответить в пределах таймаута | type, query_hash |
DaDataPartyResolved | реквизиты юрлица получены по ИНН | inn, party_name |
DaDataStandardizeRunFinished | батч-прогон стандартизации завершён | run_id, total, updated, not_found |
Взаимодействия:
| Сущность/модуль | Канал | Направление | Что происходит |
|---|---|---|---|
| Формы ядра (движок полей) | 3 — provides: suggest-provider | dadata → форма | Резолв DI активного suggest-provider; поле получает подсказки. Провайдер не активен/kill_switch/квота исчерпана → поле работает как обычный текстовый input, ошибки не показываются |
cms/commerce-b2b | 4 — requires | commerce-b2b → dadata | Регистрация контрагента запрашивает реквизиты по ИНН синхронно (короткий таймаут, тот же принцип, что и у подсказок) |
cms/commerce-delivery | 4 — requires | commerce-delivery → dadata | Стандартизация адреса доставки при оформлении заказа |
cms/integrations-bus | 4 — requires | dadata → шина | Единственный выход наружу к API DaData (креды, ретраи, circuit breaker шины) |
cms/consents | внешний (согласие фиксируется на форме) | форма → consents | Форма с полем-подсказкой адреса/ФИО декларирует consent_type на передачу данных третьей стороне (DaData) — без согласия подсказка не показывается, поле остаётся обычным input |
cms/health | 1 — событие | dadata → health | DaDataSuggestTimedOut/исчерпание квоты агрегируются в health-отчёт, алерт при систематических сбоях |
Вызов DaData — только через cms/integrations-bus (канал 4).
Фоновая работа
Синхронные вызовы подсказок (suggest, party) — единственное разрешённое исключение из правила «внешние HTTP-вызовы только из очереди» (§9 стандарта: «кроме синхронных подсказок с таймаутом и graceful fallback»): короткий timeout_ms (≤2000 мс), при недоступности/таймауте — graceful fallback на обычный текстовый ввод, форма не блокируется.
Батч-стандартизация адресов (cms:dadata:standardize-addresses) — наоборот, никогда не синхронно: именованная очередь dadata, Bus::batch чанками (по адресам), прогресс пишется в cms_dadata_standardize_runs. Идемпотентность повторного прогона: ключ — хеш нормализованного исходного адреса, уже стандартизированные записи (по хешу) пропускаются повторно, а не переписываются — повторный прогон безопасен и не тратит квоту на уже обработанные строки.
Производительность и кеш
- Горячий путь — поле подсказки на форме оформления заказа/лида: синхронный вызов ограничен
timeout_ms, кешdadata:suggest(тег) поtype+query_hashс TTLsuggest_cache_ttl_daysснижает повторные обращения к API на популярные строки. - Бюджет запросов к DaData — дневной лимит
dadata.daily_quota;min_query_lengthи debounce на клиенте — обязательная защита от расхода квоты на каждое нажатие клавиши. - Батч-стандартизация — фоновый путь, не считается в бюджет горячих запросов; идёт чанками, чтобы не упереться в дневную квоту за один прогон.
- Индексы: уникальный
type+query_hashна кеше подсказок;cms_dadata_standardize_runsиндексируется поstarted_at(списки прогонов — keyset). - Кеш подсказок — не самостоятельная база данных: TTL и объём кеша ограничены сроком, разрешённым договором с DaData (см. «Крайние случаи»).
Безопасность
Таймаут
timeout_msне блокирует отправку формы — graceful fallback на ручной ввод.ИНН без результата (несуществующая организация) не роняет форму, возвращает пустой ответ.
Секреты (API-ключ) — только
.env, rate-limit на входящие запросы подсказок.ПДн-паспорт (матрица v2.2): хранит ФИО/адрес/телефон в кеше подсказок (
cms_dadata_suggest_cache.response), срок хранения —suggest_cache_ttl_days(одновременно и лимит ретеншна, и ограничение договора с DaData). Модуль реализует хук «забыть по запросу» ядра — очистка строк кеша поsubject_hashприDataRemovalRequestedотcms/consents; «выгрузить всё по субъекту» — не применимо к кешу подсказок (не хранит устойчивый идентификатор субъекта, только обезличенный хеш для целевой очистки).Третья сторона обработки ПДн: ФИО/адрес/телефон, переданные на подсказку или стандартизацию, физически уходят на сервер DaData. Форма обязана получить согласие пользователя на передачу данных третьей стороне (через
cms/consents,consent_typeна форме) до показа подсказок; сайт обязан упомянуть DaData как получателя ПДн в политике конфиденциальности — ответственность интегратора при подключении модуля.Матрица ролей:
Роль Подсказки на формах (публично) Реквизиты по ИНН (публично) Запуск стандартизации Просмотр отчётов/квоты посетитель ✅ ✅ — — редактор ✅ ✅ — ✅ менеджер ✅ ✅ ✅ ✅ админ ✅ ✅ ✅ ✅ studio ✅ ✅ ✅ ✅ Права:
dadata.view(просмотр отчётов и квоты),dadata.manage(запуск стандартизации — необратимо меняет адреса клиентов, повышенная роль).Kill-switch:
dadata.kill_switchотключает подсказки на формах без выключения модуля целиком (реквизиты по ИНН и стандартизация продолжают работать).
UX-требования
Админ:
- Остаток дневной квоты и время её сброса видны в
cms:dadata:doctor --jsonи на странице здоровья модуля; исчерпание квоты — понятное сообщение «дневной лимит подсказок DaData исчерпан, подсказки временно недоступны до <время сброса>, формы продолжают работать как обычные поля», не «ошибка». - Запуск батч-стандартизации — подтверждение необратимой операции (меняет адреса клиентов); отчёт прогона — «обработано / изменено / не найдено», построчные ошибки скачиваемы (не молчаливый пропуск).
- Пустое состояние списка прогонов — подсказка «запустите первую стандартизацию».
Посетитель:
- При деградации (квота/таймаут/
kill_switch) поле подсказки визуально ничем не отличается от обычного текстового — без красной рамки, без сообщения об ошибке. - Подсказка появляется быстро (в пределах
timeout_ms) или не появляется вовсе — ввод не блокируется в любом случае. - Отправка формы никогда не ждёт результата подсказки дольше
timeout_ms.
Крайние случаи и типовые баги
- Лимит подсказок исчерпан посреди дня → деградация: поле — обычный текстовый input, форма не ломается, API отвечает
200 + meta.degraded: true, meta.degraded_reason: "quota_exceeded"(конвенция ядра, не 429/5xx). - Кеширование подсказок и ToS DaData → договор DaData обычно ограничивает срок кеширования результатов подсказок и запрещает использовать их как самостоятельную базу данных;
suggest_cache_ttl_daysобязан укладываться в разрешённый договором срок — это ответственность интегратора при заключении договора, зафиксировано как явное предупреждение в докс модуля. - Батч-стандартизация базы адресов → только через очередь (
cms:dadata:standardize-addresses), не синхронно; отчёт «обработано/изменено/не найдено»; повторный прогон идемпотентен (пропускает уже обработанные по хешу адреса). - ПДн в запросах к DaData → ФИО/адрес/телефон уходят третьей стороне — требует явного согласия (
cms/consents) и упоминания DaData в политике конфиденциальности сайта; без согласия — подсказки не показываются. - Таймаут превышен ровно на границе (гонка: ответ пришёл через миллисекунды после истечения
timeout_ms) → результат отбрасывается как поздний, форма уже задеградировала на обычный input, повторная отправка формы не блокируется. - Смена схемы ответа DaData (провайдер меняет формат JSON) → парсинг конкретного поля падает изолированно (обработчик на уровне маппера поля), не роняет весь ответ и не роняет форму; алерт в health о несовпадении схемы.
- ИНН формально валиден, но организация ликвидирована/не найдена в реестре → пустой (
data: null), но не ошибочный ответ (200, не404). - Быстрый ввод без debounce на клиенте → каждое нажатие клавиши не должно бить в API отдельным запросом:
min_query_length+ обязательный debounce на фронте — без них модуль не проходит приёмку (сжигает дневную квоту за минуты). - Кеш подсказок содержит ПДн и подпадает под «забыть по запросу» → при
DataRemovalRequestedотcms/consentsстрокиcms_dadata_suggest_cacheпоsubject_hashсубъекта удаляются вместе с остальными его данными.
Донорский код
Донор: — (новая разработка). Legacy-импорт (§16 стандарта) — не применимо: исторических данных подсказок для переноса не существует (кеш — эфемерный, не бизнес-данные). Связанная, но отдельная операция — разовая батч-стандартизация уже существующей базы адресов клиента через DaData Clean API (cms:dadata:standardize-addresses, см. «Фоновая работа») — это не legacy-импорт в строгом смысле (не перенос между схемами), а массовая очистка действующих данных.
Тесты и приёмка
- [ ] Мок API DaData: тест подсказки адреса/ФИО/email и получения реквизитов по ИНН
- [ ] Таймаут
timeout_msне блокирует отправку формы — graceful fallback на ручной ввод (тест) - [ ] Кеш подсказок снижает число вызовов API при повторных запросах одинаковой строки
- [ ] При выключении модуля формы работают с обычными текстовыми полями без ошибок
- [ ] ИНН без результата (несуществующая организация) не роняет форму, возвращает пустой ответ
- [ ] Исчерпание
daily_quota→suggestотвечает200 + meta.degraded: true/degraded_reason: quota_exceeded, не 429/5xx (контрактный тест ревизии ядра п.2) - [ ]
dadata.kill_switch=trueотключает подсказки без выключения модуля целиком - [ ] Батч-стандартизация: идемпотентность повторного прогона (второй прогон не меняет уже обработанные строки), отчёт содержит total/updated/not_found
- [ ]
DataRemovalRequestedочищает строки кеша подсказок поsubject_hashсубъекта - [ ] Матрица ролей:
dadata.manageтребуется для запуска стандартизации,dadata.viewдостаточно для просмотра отчётов - [ ] Смена схемы ответа DaData (мок с изменённым JSON) — изолированный сбой парсинга одного поля, форма и остальной ответ не падают
- [ ] Контрактный набор
cms-testingзелёный, пакет тестируется в testbench-изоляции - [ ] Feature-тест на каждый роут; тестовая БД только
dadata_test,migrate:freshзапрещён