Skip to content

ТЗ — 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_cacheid, type, query_hash, response (json), subject_hash (nullable), cached_atкеш подсказок по типу (address/fio/email/party); subject_hash — обезличенный указатель для очистки по «забыть по запросу»
cms_dadata_standardize_runsid, status, total, updated, not_found, started_at, finished_atжурнал прогонов батч-стандартизации адресов (§16), append-only

Индексы: type — PHP Enum; уникальный индекс на type+query_hash; responsejson() + 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": {}} — не ошибка
Событие DaDataSuggestTimedOuttype, query_hash
Событие DaDataPartyResolvedinn, party_name
Отчёт standardize-addressestotal, updated, not_found, errors[] (построчно) — скачиваемый CSV в Filament

Whitelist-принцип: типы подсказок ограничены dadata.enabled_types, поля стандартизации — профилем --source; произвольные поля в запросе игнорируются на уровне FormRequest (422 при неизвестном параметре — конвенция ядра §7 стандарта).

Настройки (группа dadata)

КлючТипДефолтaffectsPageCacheОписание
dadata.enabled_typesarray["address"]нетТипы подсказок (address, fio, email, party)
dadata.timeout_msint800нетТаймаут синхронного запроса подсказки (≤2000 по правилу §9 стандарта)
dadata.suggest_cache_ttl_daysint30нетСрок жизни кеша подсказок — обязан укладываться в срок, разрешённый договором с DaData (см. «Крайние случаи»)
dadata.min_query_lengthint3нетМинимальная длина строки для запроса подсказки — защита квоты от запроса на каждое нажатие
dadata.daily_quotaint10000нетДневной лимит запросов подсказок (ориентир — сверяться с актуальным тарифом DaData)
dadata.kill_switchboolfalseнетАварийное отключение подсказок без выключения модуля (реквизиты по ИНН продолжают работать)

Секреты (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-addressesadmin (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-providerdadata → формаРезолв DI активного suggest-provider; поле получает подсказки. Провайдер не активен/kill_switch/квота исчерпана → поле работает как обычный текстовый input, ошибки не показываются
cms/commerce-b2b4 — requirescommerce-b2b → dadataРегистрация контрагента запрашивает реквизиты по ИНН синхронно (короткий таймаут, тот же принцип, что и у подсказок)
cms/commerce-delivery4 — requirescommerce-delivery → dadataСтандартизация адреса доставки при оформлении заказа
cms/integrations-bus4 — requiresdadata → шинаЕдинственный выход наружу к API DaData (креды, ретраи, circuit breaker шины)
cms/consentsвнешний (согласие фиксируется на форме)форма → consentsФорма с полем-подсказкой адреса/ФИО декларирует consent_type на передачу данных третьей стороне (DaData) — без согласия подсказка не показывается, поле остаётся обычным input
cms/health1 — событиеdadata → healthDaDataSuggestTimedOut/исчерпание квоты агрегируются в 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 с TTL suggest_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_quotasuggest отвечает 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 запрещён

Внутренняя база знаний студии