Как ЗаңСарап вырос из проверки одного договора в сервис для работы с законодательством
Как ЗаңСарап вырос из проверки договора друга в сервис с собственным контуром обновления законодательства.
Спустя несколько месяцев после запуска я смотрел на довольно странный результат нескольких недель работы над новой версией.
В админке ЗаңСарап было 38 нормативных актов под наблюдением. Новый поисковый корпус был собран, эмбеддинги рассчитаны, тестовый поиск прошел восемь проверок из восьми. Но в production по-прежнему работала старая база.
Система не зависла и не сломалась. Она специально отказывалась включать новую версию, потому что юрист успел проверить только один источник из 37.
В этот момент я понял, что новый пайплайн делает именно то, ради чего я его строил: не торопиться.
Всё началось с обычного договора
Всё началось с договора аренды моего друга. Он собирался надолго снять квартиру, а арендодатель прислал договор на несколько страниц. Мы сели разбирать его вместе, и в какой-то момент я подумал: почему бы не проверить документ автоматически? Вроде бы, в эпоху ИИ это должно делаться в два клика.
Я загрузил договор в языковую модель и получил аккуратный, уверенный разбор: нормальная структура, понятные формулировки, ссылки на Гражданский кодекс.
Одна из указанных статей не существовала.
Это был не абсурдный набор слов, который легко заметить. Номер выглядел правдоподобно, формулировка тоже. Если не открыть кодекс и не проверить вручную, ошибку можно спокойно пропустить.
Так эксперимент на один вечер превратился в идею отдельного сервиса. Стало понятно, что обычного чата с инструкцией «проверь договор» недостаточно. Для развлечения такая ошибка неприятна. Для сервиса, которому человек отдаёт реальный договор перед подписанием, она обнуляет доверие ко всему отчёту.
При этом сама задача была понятной. У малого бизнеса, HR, закупщика или руководителя регулярно появляется договор, который нужно посмотреть сегодня. Юрист не всегда доступен, а полноценное заключение по каждой аренде, поставке или услуге стоит несоразмерно самой сделке.
Я не хотел делать «замену юристу». Идея была проще: дать человеку первую проверку. Показать перекос в ответственности, одностороннее изменение цены, странный порядок расторжения, пропущенные условия. После этого договор можно предметно обсуждать с контрагентом или передавать юристу уже с конкретными вопросами.
Первая версия собралась быстро. Продукт — нет
Первый рабочий прототип появился больше трёх месяцев назад. Снаружи механика простая: пользователь загружает PDF или DOCX, сервис извлекает текст и возвращает отчёт с оценкой риска, замечаниями и ссылками на нормы законодательства РК.
Примечание. Для иллюстраций использован отчёт по реальному договору, проверенному с разрешения пользователя. Персональные данные в публикации не раскрываются.
Но реальные документы быстро добавили задачи, которых не было в первом прототипе.
Во-первых, договоры содержат ИИН, БИН, телефоны, почту, банковские реквизиты и ФИО. Перед отправкой текста в модель появился отдельный модуль обезличивания. Сейчас это быстрый best-effort слой: он закрывает явные идентификаторы до анализа. Это не полноценная DLP-система, и я не выдаю его за абсолютную гарантию, но передавать исходный договор как есть было бы хуже.
Во-вторых, длинные договоры нельзя просто обрезать по лимиту контекста. Самый неприятный пункт может находиться в приложении или на последней странице. Поэтому документ режется на перекрывающиеся фрагменты, каждый анализируется отдельно, а результаты затем объединяются и заново ранжируются. Если часть сегментов не обработалась, сервис показывает, что проверка неполная, вместо того чтобы молча выдать частичный отчёт как окончательный.
В-третьих, модель продолжала предлагать неверные ссылки, даже когда получала хорошие статьи через поиск. Проблема возникала уже на генерации: LLM знает, как выглядит юридическая ссылка, и может уверенно дорисовать номер.
Так появился отдельный валидатор. Конкретная статья проверяется по базе законов. Если такого номера нет, ссылка удаляется. Если статья существует, второй этап проверяет, относится ли её текст к найденному риску. По production-логам от четверти до половины ссылок, изначально предложенных моделью, не доживают до итогового отчёта.
Это выглядит как потеря результата, но для legal-tech я считаю такой выбор правильным. Лучше показать меньше замечаний, чем оставить одно замечание с выдуманным основанием.
Что произошло после запуска
Я запустил сервис без рекламного бюджета. Первые пользователи пришли из поиска, публикаций, рекомендаций и бесплатных шаблонов договоров.
Более 200 человек провели бесплатные проверки. Часть пользователей вернулась, несколько человек купили дополнительные пакеты, а кто-то начал пользоваться сервисом регулярно в работе. На этом объёме продукт уже окупил прямые расходы и начал выходить в плюс.
Это пока не большая компания и не история о взрывном росте. Оплата ещё начисляется вручную, полноценное подключение Kaspi находится в работе. Но для меня важнее было другое: люди не просто открывали лендинг. Они загружали реальные документы, возвращались и платили за следующие проверки.
Именно после этого я решил провести неприятный аудит: насколько надёжен источник, с которым валидатор сравнивает ссылки.
Нельзя валидировать ссылку по устаревшей базе
Первая база законов собиралась как практичный MVP. Шесть кодексов были получены с сайтов-агрегаторов, остальные акты — из PDF. В базе находилось около 42 тысяч поисковых фрагментов.
Для демонстрации этого хватало. Для постоянно работающего продукта — нет.
Аудит показал несколько проблем.
В таблице не было даты и идентификатора редакции. Нельзя было доказать, на какую дату актуален текст.
Номер статьи хранился как целое число. Статьи вида 128-1 схема физически не представляла: ссылка могла ошибочно разрешиться в статью 128.
Самая неприятная ошибка находилась в старом скрипте загрузки. Если chunk_id уже существовал, скрипт считал его обработанным. При конфликте обновлялся embedding и время записи, но не сам текст. Можно было запустить «обновление», увидеть нормальный лог и оставить в базе прежнюю редакцию статьи.
Если новая статья становилась короче и разбивалась на меньшее число фрагментов, старые хвостовые chunks тоже никуда не исчезали. Поиск мог поднять текст, которого в действующей редакции уже нет.
Фактически безопасно обновить старую базу можно было только полным удалением и повторной загрузкой примерно 42 тысяч строк. Пока идёт такой процесс, production видит пустую или частично собранную таблицу.
Самое опасное здесь не техническая некрасивость. Сервис мог отбросить выдуманную статью, но подтвердить настоящую статью из устаревшей редакции. Для пользователя обе ситуации выглядят одинаково: рядом с замечанием есть солидная ссылка на кодекс.
Так задача «иногда скачивать свежие законы» превратилась в отдельную систему релизов.
Как теперь устроено обновление законов
Вторая версия начинается с источника.
Основной контур получает действующие редакции из официального Единого государственного банка нормативных правовых актов. У ЕКБ есть структурированное представление документа, стабильные идентификаторы, история версий и дата редакции. ИПС «Әділет» используется как независимый fallback и дополнительная сверка.
Я сознательно не сделал схему «cron скачал закон и сразу переписал production». Автоматика умеет обнаружить версию, скачать документ, разобрать структуру и показать изменения. Решение о том, что новая редакция корректна и готова к использованию, остаётся за человеком.
Пайплайн выглядит так.
1. Наблюдение за источниками
GitHub Actions регулярно проверяет историю версий 38 действующих источников. Полный документ скачивается по расписанию или когда меняется версия. Для каждого запуска сохраняются статус, время, ошибки и давность последнего успешного опроса.
2. Неизменяемый кандидат
Новая редакция нормализуется по статьям. Для неё считается SHA-256, а канонический снимок сохраняется в приватном хранилище. Сырые данные ЕКБ не публикуются и не попадают в общий лог: в метаданных источника могут находиться данные сертификата подписанта.
Каждая редакция становится отдельным immutable candidate. Старый снимок не перезаписывается.
3. Постатейное сравнение
Система отдельно показывает новые, удалённые и изменённые статьи. Номера вроде 128-1 теперь хранятся как строковые ключи. Кандидаты с удалениями, заменой целого акта или возможной перенумерацией поднимаются в начало очереди.
Юрист видит две версии статьи рядом и принимает решение: одобрить, отклонить или вернуть на повторное рассмотрение. Действие записывается в журнал.
Одобрение не меняет рабочую базу. Оно означает только: эту редакцию можно использовать при сборке следующего корпуса.
4. Изолированная сборка
Из одобренных редакций создаётся отдельный versioned release. Он живёт рядом с production, имеет собственные статьи, chunks, embeddings и manifest.
Если текст фрагмента не изменился, его embedding можно переиспользовать. Новые и изменённые fragments рассчитываются заново. При этом частичная сборка не может стать активной по определению.
Есть ещё одно fail-closed правило: если у источника появилась более свежая версия, но юрист её ещё не одобрил, сборщик не имеет права взять предыдущую approved-редакцию. Он останавливается и ждёт решения.
5. Проверки перед активацией
Полный release должен пройти несколько независимых гейтов:
- присутствуют все 37 источников, входящих в рабочий корпус;
- нет пустых статей, дублирующихся ключей и потерянных chunks;
- embeddings рассчитаны для 100% активных фрагментов;
- retrieval smoke находит нужную статью именно внутри staged release;
- golden-set eval не показывает регрессию анализа и цитирования;
- manifest в базе совпадает с тем, который подтверждает администратор.
Эти условия повторно проверяются внутри database activation function. Нельзя просто передать в API флаг ready=true и обойти проверки.
6. Одно переключение вместо перезаписи таблицы
После всех гейтов ответственный вручную запускает production promotion. Таблицы не очищаются и не переписываются. В одной транзакции меняется только указатель на активный release.
Старая база остаётся fallback. При проблеме приложение можно вернуть на неё feature flag, а между последующими v2-релизами — переключить указатель на предыдущую версию. Каждая активация и каждый rollback остаются в истории.
Почему первая сборка всё ещё не включена
На момент написания материала новый контур уже работает, но production продолжает использовать legacy-корпус.
Сейчас под наблюдением находятся 38 НПА. В будущий полнотекстовый корпус входят 37: ещё один переходный акт нужен только для мониторинга. Проверена последняя редакция одного закона — «О противодействии коррупции».
На её основе собран частичный release: 33 статьи, 63 поисковых фрагмента, embeddings для всех chunks. Детерминированный smoke-test прошёл восемь случаев из восьми, минимальная similarity составила 0,84.
И всё равно этот release нельзя активировать. У него нет покрытия 37 из 37, не завершена структурная проверка полного корпуса и не запускался финальный eval.
С технической точки зрения хотелось бы показать быстрый результат и переключить хотя бы один закон. С продуктовой точки зрения это было бы повторением старой ошибки: выдать частичную готовность за полную.
Поэтому главный bottleneck сейчас не модель и не сервер. Это ручная проверка ещё 36 источников.
Что в итоге оказалось продуктом
Когда я начинал ЗаңСарап, мне казалось, что ценность находится в самой модели: насколько хорошо она понимает договор и насколько понятно пишет замечания.
Сейчас я смотрю на это иначе.
Модель важна, но пользователь не может проверить её внутреннюю логику. Он видит только итог: конкретный риск и конкретное правовое основание. Поэтому продуктом становится весь путь до этой строки:
- документ не должен утечь в исходном виде;
- длинный текст нельзя молча обрезать;
- ссылка должна существовать;
- статья должна относиться к замечанию;
- редакция должна быть действующей;
- новая база не должна включаться без проверки и возможности отката.
Это в основном не эффектная работа. Здесь больше парсеров, хешей, миграций, журналов и защитных ограничений, чем красивых промптов.
Но именно эта часть отличает рабочий legal-tech от демонстрации, которая хорошо выглядит первые пять минут.
ЗаңСарап можно попробовать на zansarap.kz. Первая проверка бесплатная. Сервис остаётся инструментом первичного скрининга и не заменяет юридическое заключение по сложной сделке.