|
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Failing after 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
ВНИМАНИЕ: миграция 189 УДАЛЯЕТ строки на проде (см. «Что удаляется»). _UPSERT_NO_ACT_SQL заканчивается ON CONFLICT DO NOTHING, а единственный подходящий констрейнт — uq_land_reservation_cad_act UNIQUE (cad_num, act_number) с обычной NULL-семантикой. В Postgres NULL != NULL, поэтому у записей БЕЗ номера акта конфликт не наступает никогда: DO NOTHING не срабатывает, каждый недельный прогон вставляет копию. То есть ON CONFLICT здесь был декорацией. Замер прода 20.08.2026: строк всего 297 из них act_number IS NULL 297 (все) групп (cad_num, doc_url) с дублями 27 максимум копий одной записи 11 лишних строк 270 (91% таблицы) Что удаляется: копии сверх первой (минимальный id) в каждой группе. Это порождение бага, а не пользовательские данные: таблица — кэш OCR-разбора PDF с сайта, пересобираемый прогоном таски. Проверено, что ключ подходит: ни у одного cad_num нет более одного doc_url (max = 1), дубли внутри групп — точные копии. Прецеденты NULLS NOT DISTINCT в репо: м.110, м.125, м.140, м.158. Prod = PG16.4. Документация приведена к реальности. Docstring обещал python-дедуп по (cad_num, doc_url) и «двухшаговый UPSERT ниже» — ни того, ни другого в коде не было. Комментарий у варианта B был честнее, но его оценка «rare, data audit OK» не подтвердилась: 91% таблицы. Отложенный там вариант (уникальный индекс на NULL) и реализован этой миграцией. Тест репетирует миграцию на ВРЕМЕННОЙ копии, засеянной как прод (11 копий одной записи + соседний участок): исполняет РЕАЛЬНЫЕ выражения из файла, проверяет 12 строк → 2 и что повторная вставка стала no-op. Плюс фальсификация: со СТАРЫМ констрейнтом дубль обязан появиться — без неё зелёный тест неотличим от «оно и так работало». Плюс два контроля: записи С номером акта дедуплицировались и раньше, разные участки не схлопываются. Первая версия репетиции упала на ADD CONSTRAINT — я разбивал миграцию по «;» и молча выбрасывал куски, начинающиеся с комментария, вместе с DELETE. Это ровно то, что репетиция и должна ловить; разбор исправлен. Прогоны: tests/sql (живой Postgres) 6 passed rc=0; -k "izyatie or reservation" 76 passed rc=0. Шесть nodeid в skip_allowlist.txt — нужен Postgres, в CI идут. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| backfill_zone_regulations.py | ||
| cbr_macro_sync.py | ||
| connection_capacity_sync.py | ||
| developer_registry_refresh.py | ||
| ekb_krt_sync.py | ||
| ekb_ppt_tep_sync.py | ||
| ekburg_permits_sync.py | ||
| etl_newbuilding_crossload.py | ||
| forecast.py | ||
| full_report.py | ||
| genplan_zones_sync.py | ||
| gisogd_permits_sync.py | ||
| gknspecial_harvest.py | ||
| ird_harvest.py | ||
| izyatie_ocr_ingest.py | ||
| krt_geometry_sync.py | ||
| location_refresh.py | ||
| mv_sales_tracker_refresh.py | ||
| noise_sync.py | ||
| nspd_denorm_backfill.py | ||
| nspd_geo.py | ||
| nspd_sync.py | ||
| objective_etl.py | ||
| okn_objects_sync.py | ||
| opportunity_harvest.py | ||
| pat_subzones_load.py | ||
| planning_harvest.py | ||
| poi_sync.py | ||
| pzz_sync.py | ||
| refresh_analytics.py | ||
| refresh_layout_velocity.py | ||
| refresh_quarter_price_index.py | ||
| reservation_ingest.py | ||
| riasurt_sverdl_harvest.py | ||
| rosstat_macro_sync.py | ||
| scrape_cadastre.py | ||
| scrape_freshness_check.py | ||
| scrape_kn.py | ||
| scrape_kn_catalog_flats.py | ||
| scrape_kn_catalog_objects.py | ||
| scrape_objective.py | ||
| supply_layers_refresh.py | ||
| utility_infrastructure_sync.py | ||
| zone_regulation_refresh.py | ||