Нормализация адресов в накопленной базе 1С:Предприятие 8.3 редко укладывается в одну функцию платформы: пользователи вводят строки с сокращениями, опечатками и полями в произвольном порядке, а десятки тысяч записей руками или цепочкой замен не выправить. Ниже - практическая схема через API clean сервиса DaData и справочники ФИАС: для каждой строки или пакета собираем НТТРЗапрос с POST на стандартизацию, разбираем JСОН-массив ответа в таблицу значений по именам полей и по коду qc отбираем то, что ещё должен проверить человек.
Когда адреса пора отдавать во внешний сервис
Перед первым НТТР-вызовом я решаю, где в конфигурации лежит код интеграции: иначе ключи оказываются в обработке, а из регламента их не переиспользовать без копипаста.

Расширение: общий модуль, константы ключей, обработка прогона
В типовых конфигурациях адрес контрагента, сотрудника или объекта учёта часто хранится одной текстовой строкой в контактной информации. Пока записей сотни, можно договориться о маске ввода или править вручную. Когда счёт идёт на десятки и сотни тысяч, самодельные проверки и замены подстрок превращаются в хрупкий зоопарк правил: каждое новое сокращение ломает очередной шаблон, а «Москва» и «мск» в одной базе живут параллельно.
У платформы 1С нет встроенной «серебряной пули» для произвольного русскоязычного ввода. Геокодеры вроде Яндекса решают близкую задачу, но для массового приведения к административному делению и полям КЛАДР/ФИАС мне удобнее DaData: стандартизация завязана на те же идентификаторы, с которыми дальше работают отчёты и интеграции с доставкой.
Ориентир по качеству я беру из своего прогона порядка 84 тысяч адресов: около 4 тысяч сервис пометил как кандидатов на ручную проверку (поле qc), и среди них примерно пятая часть оказалась уже корректной, а остальное чаще было изначально бессмысленным вводом (один город без улицы и дома). Это не обещание полной автоматизации, а порядок цифр, с которым можно сверить свой объём после пилота на нескольких тысячах строк.
Ограничения лучше заложить в ТЗ сразу. Стандартизация в DaData платная, в тарифах обычно фигурирует порядка 5–10 копеек за один адрес; после регистрации дают около 100 бесплатных вызовов для проверки кода. Обрабатываются только российские адреса. Документацию по методу clean и полному составу полей ответа смотрите на сайте DaData в разделе API clean (pricing и описание ответ там же).
Примеры «грязных» строк, ради которых вообще стоит звать сервис:
мск сухонска 11/-89(город сокращён, дом и квартира слеплены через слэш);москва Сухонская улица 11 89(без индекса и типов в каноническом виде);г. Москва, ул. Сухонская, д. 11, кв. 89(формально близко к норме, но индекс и qc всё равно полезны для отбора);Москва Сухонская 11 стр 2 оф 305(нестандартные части здания, их разбор зависит от полноты строки).
После пилота я обычно фиксирую порог: если ручная проверка qc занимает больше времени, чем экономия на доставке или отчётах, расширяем набор колонок (fias_id, geo_lat) и не перезаписываем исходник до подписи ответственного.
Отдельный общий модуль под clean и константы под ключи проще сдать на ревью без страха, что секрет уже в diff.
Ключи и endpoint clean/address
Доступ к API начинается с регистрации в личном кабинете DaData. Там же выдают пару ключей: АРИ-ключ для заголовка Authorization в формате Token и секретный ключ для X-Secret. Оба участвуют в каждом вызове стандартизации; без секрета сервис отвечает отказом, и это первая вещь, которую я проверяю при «внезапно пустом» ответе на тестовой базе.

Контекст: Ключи и endpoint clean/address
Рабочий путь метода: хост dadata.ru, порт 443, ресурс /api/v2/clean/address, метод POST, тело application/json в виде массива строк. Одна строка в массиве даёт массив из одного объекта в ответе; до 100 строк в одном теле допустимы по документации clean, и порядок элементов ответа совпадает с порядком адресов в запросе.
Ключи в коде не держу строками в общем модуле, который попадёт в хранилище. Варианты для типовой без снятия с поддержки: константы, доступные только администратору; регистр или настройки интеграции в расширении; защищённое хранилище платформы, если оно уже используется в проекте. Для тестового контура завожу отдельную пару ключей и отдельную константу «контур DaData», чтобы случайно не прогнать полмиллиона строк по боевому тарифу из копии базы.
Таблица значений под JSON ответа
Рабочая модель у меня одна: таблица значений, где есть колонка исходного адреса и набор колонок с именами, совпадающими с полями JСОН-объекта в ответе clean. Тогда после ПрочитатьJСОН достаточно ЗаполнитьЗначенияСвойств без ручного сопоставления десятков реквизитов.

Контекст: Таблица значений под JSON ответа
Минимальный набор колонок, который закрывает учёт и отбор на проверку:
ИсходныйАдрес(ваше поле, в ответе сервиса его нет);result(нормализованная одна строка);postal_code,region_with_type,city_with_type,settlement_with_type;city_district_with_type,street_with_type,house,flat;qc(код качества для фильтра).
Если дальше нужны доставка или привязка к ФИАС, добавляю fias_id, kladr_id, geo_lat, geo_lon тем же правилом: имя колонки совпадает с именем свойства в ответе. Лишние поля из JSON просто некуда писать, пока не добавите колонку; лишние колонки без одноимённого свойства останутся пустыми после ЗаполнитьЗначенияСвойств.
Источник строк для таблицы зависит от конфигурации. Универсально: выгрузить текст адреса и ссылку на владельца запросом, не таща всю контактную информацию в память лишний раз. Запрос ниже выбирает контрагентов с непустым представлением адреса; имена таблиц и реквизитов подставьте под свою схему хранения контактов.
ВЫБРАТЬ
КонтактнаяИнформация.Объект КАК Контрагент,
КонтактнаяИнформация.Представление КАК АдресСтрокой
ИЗ
РегистрСведений.КонтактнаяИнформация КАК КонтактнаяИнформация
ГДЕ
КонтактнаяИнформация.Тип = &ТипАдрес
И КонтактнаяИнформация.Вид = &ВидАдресКонтрагента
И КонтактнаяИнформация.Представление <> &ПустаяСтрока
В результате смотрите на длину представления и явные дубликаты: одинаковая строка у разных контрагентов нормализуется одинаково, но писать результат всё равно придётся в каждую строку таблицы или в регистр с измерением «владелец».
НТТРЗапрос и разбор JSON на 8.3.6+
На пилоте половина сбоев у меня была не в JSON, а в забытых Token или X-Secret; поэтому в модуле я держу отдельную проверку заголовков до отправки тела.

Контекст: НТТРЗапрос и разбор JSON на 8.3.6+

POST clean/address: заголовки Token и X-Secret, тело-массив
Для HTTP нужны заголовки Content-Type application/json, Authorization Token … и X-Secret. Тело - JСОН-массив строк адресов. На платформе 8.3.6 и новее я опираюсь на встроенные ЗаписьJСОН и ПрочитатьJСОН; на более старых релизах без них остаётся сторонний парсер, и это отдельная ветка сопровождения.
Главная ловушка при формировании тела вручную - кавычки и обратный слэш в исходном адресе: они ломают JSON. Сборка текста запроса конкатенацией вокруг адреса на строке с кавычкой в названии офиса даёт 400 от сервиса или пустой разбор на клиенте. Надёжнее положить адреса в массив платформы и сериализовать его через ЗаписьJСОН.
Ниже один серверный фрагмент, который закрывает и одиночный адрес, и пакет до 100 строк: запись тела, отправка, проверка кода состояния, разбор массива ответа. Имена процедур я выношу в общий модуль интеграции с флагом «вызов сервера».
&НаСервере
Функция НормализоватьПакетАдресов(МассивСтрокАдресов, КлючAPI, СекретAPI) Экспорт
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, МассивСтрокАдресов);
ТелоЗапроса = ЗаписьJSON.Закрыть();
Заголовки = Новый Соответствие;
Заголовки.Вставить("Content-Type", "application/json");
Заголовки.Вставить("Authorization", "Token " + КлючAPI);
Заголовки.Вставить("X-Secret", СекретAPI);
HTTPЗапрос = Новый HTTPЗапрос("/api/v2/clean/address", Заголовки);
HTTPЗапрос.УстановитьТелоИзСтроки(ТелоЗапроса, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Соединение = Новый HTTPСоединение("dadata.ru", 443,,,,,
Новый ЗащищенноеСоединениеOpenSSL(Неопределено, Неопределено));
HTTPОтвет = Соединение.ОтправитьДляОбработки(HTTPЗапрос);
Если HTTPОтвет.КодСостояния <> 200 Тогда
ВызватьИсключение "DaData clean: код " + HTTPОтвет.КодСостояния + ", тело: "
+ HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
КонецЕсли;
ТелоОтвета = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Если ПустаяСтрока(СокрЛП(ТелоОтвета)) Тогда
ВызватьИсключение "DaData clean: пустое тело при коде 200";
КонецЕсли;
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(ТелоОтвета);
МассивРезультатов = ПрочитатьJSON(ЧтениеJSON);
ЧтениеJSON.Закрыть();
Возврат МассивРезультатов;
КонецФункции
Ответ clean всегда приходит массивом объектов. Для одной строки контракт тот же, что и для пакета, поэтому один код чтения покрывает оба варианта.
Блок с ЗаписьJСОН отвечает за экранирование. Блок с НТТРСоединение и НТТРЗапрос не должен молча проглатывать ответы с кодом, отличным от 200: в теле часто лежит текст ошибки лимита или авторизации. После ПрочитатьJСОН для одной строки таблицы беру первый элемент массива; для пакета сопоставляю элемент ответа с тем же индексом, что и строка в пакете.
Заполнение строки таблицы для одного элемента массива:
&НаСервере
Процедура ПрименитьОтветCleanКСтроке(СтрокаТаблицы, ДанныеОтвета)
ЗаполнитьЗначенияСвойств(СтрокаТаблицы, ДанныеОтвета);
КонецПроцедуры
Если ПрочитатьJСОН падает, сохраняю сырое тело в журнал регистрации вместе с первыми символами исходного адреса. Иначе повторный прогон по тому же месту съедает лимит впустую.
Цикл, пакеты и обработка сбоев
Сквозной сценарий у меня замкнут: выборка, пакет, POST, qc, запись, следующий пакет. Параметр «максимум за сеанс» разрывает цикл, когда лимит или бюджет исчерпаны, а не крутит базу до бесконечности.

Контекст: Цикл, пакеты и обработка сбоев

От выборки адресов до записи по qc без затирания исходника
Цикл «на каждую строку свой POST» понятен и работает, но на объёме порядка 84 тысяч адресов каждый вызов добавляет задержку на установку защищённого соединения. Я режу таблицу на пакеты по 100 строк, формирую массив адресов пакета, один раз вызываю процедуру нормализации пакета и в цикле по индексу переношу свойства в строки. Между пакетами добавляю паузу, если в ответе мелькнул 429 или сеть дала таймаут: повтор с тем же телом, без перехода к следующему пакету, пока текущий не отработал.
Учёт лимитов и тарификации для меня часть логики, а не комментарий в коде. Перед ночным регламентным заданием смотрю остаток на счёте и прикидываю стоимость прогона по числу строк и цене за адрес; в обработке задаю параметр «максимум адресов за сеанс», чтобы тестовый прогон не съел весь дневной лимит. После ошибки авторизации или 402 цикл останавливаю, а не шлёт миллион одинаковых запросов.
Исходные данные в базе до проверки qc не затираю. Удобная схема: регистр сведений с измерениями «объект» и «вид контакта», ресурсы «исходная строка», «result», «qc», дата обработки; перенос в контактную информацию отдельной процедурой после отбора. Так можно откатиться и сравнить diff для спорных qc.
Остановка цикла при 402 или 429 для меня нормальное завершение сеанса, а не повод удваивать нагрузку на API в надежде «прорваться».
Точки встраивания: внешняя обработка для разового массового прогона; регламентное задание для новых или изменённых адресов за период; подписка на запись контакта с постановкой в очередь, если объём небольшой. В типовой без снятия с поддержки всё это ложится в расширение с собственным общим модулем и регламентом.
Фильтр qc и перенос в учёт
Поле qc в ответе clean - фильтр для ручной очереди, а не декорация. Расшифровки кодов и граничные значения перечислены в официальной документации DaData к методу clean; я не переписываю таблицу целиком, а завожу в конфигурации справочник или регистр: код qc и признак «нужна проверка», плюс отчёт по строкам из подозрительного диапазона.
Практика после прогона: автоматически принимаю строки с «хорошим» qc и заполненным result, остальное уходит в список оператору. Для доставки дополнительно смотрю geo_lat, geo_lon и fias_id: если координаты пустые при заполненном доме, это сигнал перепроверить исходник, а не слать курьера вслепую.
Запись в контактную информацию делаю осознанно: либо обновляю представление и структурированные поля, если конфигурация их поддерживает, либо пишу нормализованную строку в отдельный реквизит расширения «АдресДоставкиНормализованный», пока бухгалтерия не подтвердит смену юридического адреса.
Перед первым боевым прогоном я прогоняю десяток заведомо кривых и заведомо правильных строк в тестовой базе, сверяю qc и result, затем пакет из 100 строк с повторным чтением того же пакета (должен совпасть порядок). Если на платформе ниже 8.3.6, заранее решаю вопрос с JSON, иначе половина модуля окажется мёртвым грузом.
Нормализация адресов через DaData не отменяет человека на хвосте очереди qc, зато снимает с разработчика бесконечное дописывание правил для каждого нового сокращения. Держите ключи в настройках, тело запроса через ЗаписьJСОН, ответ в таблицу по именам полей, пакеты по 100 там, где это уместно, и не перезаписывайте исходник, пока qc и выборочная проверка не подтвердят, что строка стала адресом, а не красивой догадкой сервиса.
Вступайте в нашу телеграмм-группу Инфостарт