Skip to content

Премортем: DEV-402 geo address flow frontend

Контекст

  • Предмет: Фронт-эпик DEV-402 — миграция геолокации partizap.ru с ручного справочника (СПб + Москва) на полную РФ через ГАР (85 регионов, ~1100 городов, дома); 4 сценария фронта: форма продавца с автокомплитом, шапка/фильтр с иерархическим picker'ом, IP-баннер на главной, расширение admin-references; зависит от бэк-эпиков DEV-379 (импорт ГАР) и DEV-380 (suggest API + GeoProvider абстракция)
  • Аудитория: продавцы автозапчастей (вендоры размещают карточки) и покупатели на partizap.ru
  • Успех: все 4 сценария работают + 0 регрессий в существующих фичах + полное прохождение manual regression checklist

Дыры

H-001: Принудительный перевыбор адреса ломает legacy-вендоров без миграции

  • Угол зрения: Клиент
  • Найдена: 2026-06-01 (запуск 1)
  • Важность: высокая
  • Уверенность: подкреплено контекстом
  • Статус: ОТЛОЖЕНО
  • Режим: краткий
  • Описание: План блокирует submit редактирования legacy-карточек до выбора адреса через autocomplete, но не описывает массовую миграцию старых карточек. Активный продавец с 30-50 карточками, открыв одну на редактирование, обнаруживает блокирующее требование «выбрать адрес заново» — для каждой карточки.
  • Решение: Перевыбор остаётся как в плане; добавляется триггер-условие на bulk-миграцию
    • Почему: На момент 2026-06-01 база реально пуста (юзеров нет), перевыбор 1-3 карточек на продавца не блокер. Решение зафиксировано на текущем снапшоте состояния.
    • Первый шаг: Зафиксировать в спеке: если на момент мержа DEV-380 в проде > 50 продавцов с legacy-продуктами, перед релизом фронт-эпика запускается bulk-миграция (вариант А) как отдельный таск.
    • Исполнитель: оба

H-002: Свой suggest на pg_trgm/Meilisearch проиграет DaData по качеству ввода

  • Угол зрения: Конкурент
  • Найдена: 2026-06-01 (запуск 1)
  • Важность: высокая
  • Уверенность: требует проверки
  • Статус: ПРИНЯТО
  • Режим: полный
  • Описание: Avito/Авто.ру/Drom используют DaData или эквивалент с обработкой опечаток, сокращений, синонимов и народных названий. Свой suggest на ГАР+pg_trgm без runtime DaData даёт заметно худшие подсказки: «Питер», «СПб», «Сосновый бор» (без пробела), «Мск» не найдутся. Продавец сравнит и уйдёт.
  • Решение: Acceptance-критерий качества suggest в контракте бэка + gold-set 50 типичных запросов
    • Зачем выбрали: Ставим планку в SLA до старта работ, не пытаемся починить кривое позже
    • Первые шаги (prevent):
      1. Сформировать gold-set из 50 типичных запросов (Питер/СПб/Мск/Спб/опечатки/сокращения ул./пр-кт/мкр/б-р/р-н)
      2. Добавить требование в openapi.yaml: словарь синонимов на бэке + fuzzy ≥ 0.7 на pg_trgm или typo-tolerance в Meilisearch
      3. Manual prove на dev-стенде до мержа фронт-эпика: каждый запрос gold-set возвращает релевантный fias_id в топ-3
    • Исполнитель: оба
    • Сигнал успеха (detect): 50/50 запросов gold-set дают релевантный топ-3 на dev-стенде; после релиза метрика «% suggest-запросов с пустым ответом» < 5%
    • Стоп-условие: Бэк не выходит на gold-set качество → откат к варианту Б (возврат DaData fallback после 2 пустых ответов своего suggest)
    • Если уже случилось (limit damage): После релиза собирать метрику пустых suggest по запросам; при > 5% — escalation на расширение синонимического словаря
    • Открытые вопросы:
      • Словарь синонимов хранится в БД или в коде бэка?
      • Кто отвечает за поддержку gold-set после релиза?

H-003: Координаты домов для гибрида метро не определены в плане

  • Угол зрения: Исполнитель
  • Найдена: 2026-06-01 (запуск 1)
  • Важность: высокая
  • Уверенность: подкреплено контекстом
  • Статус: ПРИНЯТО
  • Режим: полный
  • Описание: Сценарий 1 предполагает автозаполнение ближайшей станции метро по выбранному дому. План фронта требует координаты дома в /store/geo/resolve, но backend-скоуп (DEV-379/380) описан как ГАР-импорт без явного геокодинга домов. ГАР не содержит координат уровня здания — нужен отдельный batch-геокодинг через DaData/OSM/Nominatim, которого нет в видимых тикетах.
  • Решение: Отдельный backend-таск — batch-геокодинг домов (OSM Nominatim self-host или DaData геокод-API), таблица houses.coords
    • Зачем выбрали: Убираем скрытую зависимость, делаем её явной частью эпика. Без координат гибрид метро не запустится и сценарий 1 деградирует до ручного выбора.
    • Первые шаги (prevent):
      1. Создать YouTrack-таск как subtask DEV-402 на batch-геокодинг домов; DEV-380 → blockedBy на него
      2. Выбрать источник OSM Nominatim self-host vs DaData геокод-API на основе budget + квоты + качества покрытия малых городов
      3. Фронт-спека: /store/geo/resolve обязан возвращать coords дома и предзаполненный metro_station_id если станция < 1500m
    • Исполнитель: оба
    • Сигнал успеха (detect): Для топ-10 случайных домов в МСК и СПб гибрид-метро на dev-стенде предзаполняется верной станцией (manual проверка по карте)
    • Стоп-условие: Геокодинг не уложится в сроки или качество покрытия низкое → откат к варианту В (метро вручную в этой версии, гибрид в следующий эпик)
    • Если уже случилось (limit damage): Релиз без авто-метро: баннер «Уточните метро вручную» в продуктовой форме; метрика «% продавцов выбравших метро» — если падает > 30%, гибрид P0 на следующий цикл
    • Открытые вопросы:
      • Достаточно ли покрытия OSM в малых городах РФ?
      • Стоимость и time-to-bootstrap self-host Nominatim для всей РФ?

H-004: Debounce 250ms заложен без SLO на suggest от бэка

  • Угол зрения: Допущения
  • Найдена: 2026-06-01 (запуск 1)
  • Важность: высокая
  • Уверенность: подкреплено контекстом
  • Статус: ПРИНЯТО
  • Режим: полный
  • Описание: useGeoSuggest зафиксирован на debounce 250ms + LRU 20 — работает только при P95 ответа suggest < 200ms на полном ГАР. Выбор Postgres pg_trgm vs Meilisearch открыт в DEV-380, SLO в контракте не зафиксирован, нагрузочных тестов на полный ГАР в плане нет. При P95 800мс-2с автокомплит «прыгает», AbortController не спасает от ощущения «не работает».
  • Решение: SLO в контракте: P95 ≤ 200ms, P99 ≤ 500ms на /store/geo/suggest при 100 RPS; нагрузочный тест бэка до мержа фронт-эпика
    • Зачем выбрали: Фронт-debounce 250ms работает только при этом SLO; без зафиксированного контракта UX ломается на ровном месте
    • Первые шаги (prevent):
      1. Добавить SLO в openapi.yaml для /store/geo/suggest
      2. Согласовать с бэком инструмент (k6 или wrk) и сценарий нагрузочного теста: gold-set queries × 100 RPS × 5 мин
      3. Acceptance до мержа фронт-эпика: pass нагрузочного теста на dev-стенде с evidence в отчёте
    • Исполнитель: оба
    • Сигнал успеха (detect): P95 ≤ 200ms на k6-репорте; продакшен-метрика в Sentry/Grafana держит SLO 7 дней подряд после релиза
    • Стоп-условие: Бэк не выходит на P95 500ms даже с Meilisearch → откат к варианту В (debounce 400ms + skeleton-row в dropdown)
    • Если уже случилось (limit damage): Хотфикс: адаптивный debounce (мерим P95 последних 20 запросов, подстраиваем 150-600ms); оптимизация индекса как P1
    • Открытые вопросы:
      • Работает ли pg_trgm на полном ГАР (~30M строк) или сразу Meilisearch?
      • Где хранится Meilisearch контейнер на инфраструктуре?

H-005: Suggest-API без rate-limit становится бесплатным ГАР-as-a-Service для скрейперов

  • Угол зрения: Противник
  • Найдена: 2026-06-01 (запуск 1)
  • Важность: высокая
  • Уверенность: подкреплено контекстом
  • Статус: ПРИНЯТО
  • Режим: полный
  • Описание: Эндпоинт /store/geo/suggest публичный (нужен в форме продавца и в picker'е гостевого каталога), вызывается с каждой клавиши, отдаёт структурированный ГАР с fias_id. В плане нет rate-limiting, токена сессии, CAPTCHA, ограничения глубины. Конкуренты и скрейперы выкачивают весь нормализованный справочник; DDoS на suggest положит каталог и форму продавца одновременно — единая точка отказа.
  • Решение: Rate-limit на /store/geo/suggest: 60 req/min/session + 600 req/min/IP; обязательная cookie PARTIZAP_SESSION (HTTP-only, выдаётся гостям при первом GET страницы)
    • Зачем выбрали: Скрейпинг невыгоден, DDoS гасится на уровне приложения, фронт ничего не меняет (cookie всегда есть после SSR)
    • Первые шаги (prevent):
      1. В спеку фронта добавить раздел Backend dependencies: rate-limit policy + anon session cookie
      2. Согласовать с бэком расширение DEV-380 на anon-session middleware + rate-limit middleware
      3. Acceptance до релиза: load test 1000 RPS не валит сервис, при превышении лимита 429 с Retry-After
    • Исполнитель: оба
    • Сигнал успеха (detect): На проде: 0% 5xx на /store/geo/suggest, % 429 ответов < 0.1% от нормальных юзеров
    • Стоп-условие: Бэк отказывается от rate-limit → откат к варианту Г (Cloudflare-level защита уровня LB)
    • Если уже случилось (limit damage): При DDoS экстренно прячем suggest за более жёсткий middleware, прозрачно для фронта; при утечке справочника — обращение в правовую плоскость не требуется (ГАР публичен), но usage-метрики дают раннее предупреждение
    • Открытые вопросы:
      • Rate-limit state хранится в Redis или DB?
      • Анонимная сессия создаётся на SSR (Nuxt) или на первом API-запросе?

H-006: GeoProvider абстракция фиктивна — fias_id размазан по фронту

  • Угол зрения: Будущий поддерживающий
  • Найдена: 2026-06-01 (запуск 1)
  • Важность: высокая
  • Уверенность: требует проверки
  • Статус: ПРИНЯТО
  • Режим: полный
  • Описание: План декларирует GeoProvider абстракцию, но fias_id уходит в payload POST /vendor/products как address_fias_id, в legacy-флоу, в IP-баннер, в resolve-схему. Через 8-12 месяцев захочется добавить КЛАДР-fallback, вернуть DaData для метро, или OSM для радиусного поиска — fias_id нельзя выкинуть из БД (legacy продукты + обязательное поле формы). Абстракция превращается в фикцию. WYSIATI: нет голоса бэкенд-инженера / ГИС-архитектора в комнате — фронт фиксирует fias_id в публичном контракте до подтверждения, что абстракция реально позволит подменить источник.
  • Решение: Internal partizap_address_id (UUID, генерируется бэком при resolve) как ключ адреса в нашей БД и в продукте; fias_id хранится рядом в таблице addresses как метаданные источника; payload фронта — address_id
    • Зачем выбрали: GeoProvider становится реальной абстракцией; смена ключа (КЛАДР/OSM/DaData) не требует переписывать БД и фронт. Обратимое архитектурное решение перестаёт маскироваться под необратимое.
    • Первые шаги (prevent):
      1. Написать ADR «Address Identifier Architecture» в partizap-docs/docs/dev/adr/ до фриза бэк-схемы
      2. Согласовать с бэком расширение схемы DEV-379: таблица addresses (address_id, source, source_value); миграция текущих СПб/Москва записей
      3. Обновить фронт-спеку и openapi.yaml: payload address_id вместо address_fias_id; entities/geo.schema на address_id
    • Исполнитель: оба
    • Сигнал успеха (detect): Ни один фронт-файл не содержит fias_id кроме entities/geo/model/geo.schema.ts (метаданные) и admin-просмотра; grep -r 'fias_id' apps/main/app/features = 0; lint:endpoints проходит
    • Стоп-условие: Бэк отказывает в смене схемы → возврат к варианту Г (fias_id в payload, ADR как deferred-tech-debt с явным roadmap миграции)
    • Если уже случилось (limit damage): Через 12 мес миграция через bridging-таблицу legacy_fias_to_address_id; 2-фазный rollout: бэк принимает оба ключа → фронт переезжает → бэк дропает fias_id-ключ
    • Открытые вопросы:
      • UUID генерируется на бэке при resolve или на этапе миграции схемы?
      • Нужна ли версионность таблицы addresses (history) для аудита смены источников?

Топ 1–3 — с чего начать

  1. H-006 — GeoProvider абстракция фиктивна — fias_id размазан по фронту. Первый шаг: Написать ADR «Address Identifier Architecture» в partizap-docs/docs/dev/adr/ до фриза бэк-схемы
  2. H-003 — Координаты домов для гибрида метро не определены в плане. Первый шаг: Создать YouTrack-таск как subtask DEV-402 на batch-геокодинг домов; DEV-380 → blockedBy на него
  3. H-004 — Debounce 250ms заложен без SLO на suggest от бэка. Первый шаг: Добавить SLO в openapi.yaml для /store/geo/suggest

Bias-проверка топа

  • Главный риск искажения: planning fallacy — все три варианта 'А' в топе требуют расширения scope бэк-эпиков (новая схема БД, batch-геокодинг домов, SLO с нагрузочным тестом); выглядит как простой выбор, но это +2-3 месяца к DEV-379/DEV-380
  • Коррекция: явно зафиксировать в спеке раздел Backend dependencies с тикетами, owner и ожидаемой готовностью; не начинать фронт-работы до письменного подтверждения от бэк-команды

Reverse премортем (если запущен)

Заполняется только если финальная рекомендация — отложить/отказаться.

«Прошёл горизонт. Не сделали. Оказалось зря — почему?»

  1. Optimisation theatre на пустых данных: все 5 принятых А-вариантов — защиты от теоретических проблем при нулевых юзерах; готовились к скрейпингу, DDoS и необратимым схемам которыми никто не пользовался
  2. Бэк-команда не вытянула расширенный scope (геокодинг + новая схема + acceptance + rate-limit = +2-3 месяца поверх DEV-379/380); выбрали минимум, фронт ждал ещё дольше или работал с компромиссами
  3. Decision rot без owner и дедлайна: ADR, backend-таски, SLO зависли в чате; через 3 месяца переключились на другие приоритеты, эпик формально жив, фактически забыт

Что перевешивает: H-003 (UX метро без координат ломается) и H-006 (необратимая схема БД через 12 мес) — реальные дыры, не теоретические; отсрочка на 2-4 недели для ADR и backend-тасков пропорциональна; митигация главного риска reverse (decision rot) — в финальной записи зафиксировать owner и дедлайн на каждый блокер