fix(ptica): ключ gisogd_permits — id документа на портале, а не (группа, номер) (#2986) #2987

Merged
bot-backend merged 2 commits from fix/2986-permits-key into main 2026-08-20 18:03:49 +00:00
Collaborator

Закрывает #2986 (там же полный разбор с замерами).

Коротко

UNIQUE (doc_group, doc_num) вводился ради склейки одного документа из двух схем портала. Схемы пересекаются на 2 документа. Ключ при этом схлопывает 2243 разных документа, потому что docNum у ГИСОГД не уникален: разрешение и изменения к нему носят один номер.

На проде 9182 строки против 12 065 документов на портале — нет 23.9 % реестра.

Что именно теряется

docNum = 66-06-06-2026, группа DocRS — портал отдаёт два разных документа:

key 1000130002719586  «Разрешение на строительство № 66-06-06-2026»   dateReg 2026-02-26
key 1000130002752293  «Изменения в разрешение на строительство…»      dateReg 2026-08-04

На проде осталось одно — изменение (у него date_reg позже, UPSERT предпочитает поздний). Самого разрешения нет.

Не единичный случай: 598 из 4320 строк РНС (13.8 %) названы «Изменения…» — в этих случаях изменение вытеснило исходное разрешение, и §6 показывает его вместо разрешения без всякого признака подмены.

Правка

было стало
UNIQUE (doc_group, doc_num) UNIQUE (source_key)
GROUP_CODE = {DocRS, DocRV} + DocIZ → 'IZ' (548 документов не грузились вовсе)
CHECK doc_group IN ('RS','RV') + 'IZ'

source_key — идентификатор документа на портале. Он разделяет разрешение и изменения (разные key) и по-прежнему склеивает настоящие межсхемные дубли: у тех key ОБЩИЙ (ровно 7 записей по всем трём группам).

Дедуп перед сменой ключа не нужен — проверено на проде: source_key уже уникален, 9182 различных на 9182 строки, NOT NULL. (doc_group, doc_num) остаётся обычным индексом: как фильтр «все документы по номеру» он полезен, просто не уникален.

§6 сужена явно, а не молча

Агрегат permits_nearby обещает total_count = rs_count + rv_count. Строки 'IZ' попадали бы в total и ни в один из счётчиков. Поэтому запрос сужен до doc_group IN ('RS','RV') явно, с комментарием: показывать ли изменения отдельной строкой — вопрос продуктовый, и до его решения инвариант не должен держаться на том, что таких строк «пока нет».

То есть пользователь этого PR не заметит по составу групп, но увидит больше разрешений: вернутся 2243 документа, вытесненные ключом.

Как проверено

  • Двусторонние гейты без базы: на origin/main GROUP_CODE = {'DocRS': 'RS', 'DocRV': 'RV'} и в тексте UPSERT стоит ON CONFLICT (doc_group, doc_num) — конкретные неверные значения, не отсутствующие символы.
  • Гейт на §6 и контроль инварианта на данных (total == rs + rv) — красные на origin/main.
  • Герметичная репетиция миграции на временной копии в прод-форме: со старым ключом разрешение и изменение схлопываются в одну строку, и остаётся именно изменение — ровно как на проде; после миграции живут раздельно.
  • Контроль обратного направления: межсхемный дубль (общий key) по-прежнему склеивается в одну строку — то, ради чего старый ключ и вводился.
  • Контроль CHECK: 'IZ' принимается, мусорная группа отвергается с именем констрейнта.
  • 18 passed (SQL-репетиция + permits_nearby).

Контроль метода разведки: заведомо рабочая комбинация razdel13/DocRS тем же curl отдаёт 200 и валидный JSON — значит 400-е на других разделах означают отсутствие разделов, а не сломанную пробу.

После деплоя

Миграция только меняет ключ; недостающие документы вернутся следующим прогоном загрузчика. Отчитаюсь числами: строк в gisogd_permits ≈ 12 058, из них doc_group='IZ' ≈ 393, и по номеру 66-06-06-2026 должны появиться ДВЕ строки RS вместо одной.

Закрывает #2986 (там же полный разбор с замерами). ## Коротко `UNIQUE (doc_group, doc_num)` вводился ради склейки одного документа из двух схем портала. Схемы пересекаются **на 2 документа**. Ключ при этом схлопывает **2243 разных документа**, потому что `docNum` у ГИСОГД не уникален: разрешение и изменения к нему носят один номер. На проде **9182 строки против 12 065 документов** на портале — нет **23.9 %** реестра. ## Что именно теряется `docNum = 66-06-06-2026`, группа DocRS — портал отдаёт два разных документа: ``` key 1000130002719586 «Разрешение на строительство № 66-06-06-2026» dateReg 2026-02-26 key 1000130002752293 «Изменения в разрешение на строительство…» dateReg 2026-08-04 ``` На проде осталось **одно** — изменение (у него `date_reg` позже, UPSERT предпочитает поздний). Самого разрешения нет. Не единичный случай: **598 из 4320 строк РНС (13.8 %)** названы «Изменения…» — в этих случаях изменение вытеснило исходное разрешение, и §6 показывает его вместо разрешения без всякого признака подмены. ## Правка | было | стало | |---|---| | `UNIQUE (doc_group, doc_num)` | `UNIQUE (source_key)` | | `GROUP_CODE = {DocRS, DocRV}` | `+ DocIZ → 'IZ'` (548 документов не грузились вовсе) | | `CHECK doc_group IN ('RS','RV')` | `+ 'IZ'` | `source_key` — идентификатор документа на портале. Он разделяет разрешение и изменения (разные key) и **по-прежнему склеивает настоящие межсхемные дубли**: у тех key ОБЩИЙ (ровно 7 записей по всем трём группам). Дедуп перед сменой ключа не нужен — проверено на проде: `source_key` уже уникален, 9182 различных на 9182 строки, NOT NULL. `(doc_group, doc_num)` остаётся обычным индексом: как фильтр «все документы по номеру» он полезен, просто не уникален. ## §6 сужена явно, а не молча Агрегат `permits_nearby` обещает `total_count = rs_count + rv_count`. Строки `'IZ'` попадали бы в `total` и ни в один из счётчиков. Поэтому запрос сужен до `doc_group IN ('RS','RV')` **явно**, с комментарием: показывать ли изменения отдельной строкой — вопрос продуктовый, и до его решения инвариант не должен держаться на том, что таких строк «пока нет». То есть **пользователь этого PR не заметит по составу групп**, но увидит больше разрешений: вернутся 2243 документа, вытесненные ключом. ## Как проверено - **Двусторонние гейты без базы:** на `origin/main` `GROUP_CODE` = `{'DocRS': 'RS', 'DocRV': 'RV'}` и в тексте UPSERT стоит `ON CONFLICT (doc_group, doc_num)` — конкретные неверные значения, не отсутствующие символы. - **Гейт на §6** и **контроль инварианта на данных** (`total == rs + rv`) — красные на `origin/main`. - **Герметичная репетиция миграции** на временной копии в прод-форме: со старым ключом разрешение и изменение схлопываются в одну строку, и остаётся **именно изменение** — ровно как на проде; после миграции живут раздельно. - **Контроль обратного направления:** межсхемный дубль (общий `key`) по-прежнему склеивается в одну строку — то, ради чего старый ключ и вводился. - **Контроль CHECK:** `'IZ'` принимается, мусорная группа отвергается с именем констрейнта. - 18 passed (SQL-репетиция + `permits_nearby`). **Контроль метода разведки:** заведомо рабочая комбинация `razdel13/DocRS` тем же curl отдаёт 200 и валидный JSON — значит 400-е на других разделах означают отсутствие разделов, а не сломанную пробу. ## После деплоя Миграция только меняет ключ; недостающие документы вернутся следующим прогоном загрузчика. Отчитаюсь числами: строк в `gisogd_permits` ≈ 12 058, из них `doc_group='IZ'` ≈ 393, и по номеру 66-06-06-2026 должны появиться ДВЕ строки RS вместо одной.
bot-backend added 1 commit 2026-08-20 17:09:03 +00:00
fix(ptica): ключ gisogd_permits — id документа на портале, а не (группа, номер) (#2986)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
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 / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m8s
CI / backend-tests (pull_request) Successful in 17m21s
3325801355
`UNIQUE (doc_group, doc_num)` вводился, чтобы склеивать ОДИН документ,
пришедший из двух схем портала. Замер 20.08.2026 показал, что задача,
ради которой ключ введён, почти отсутствует, а побочный эффект огромен:
docNum у ГИСОГД НЕ уникален — разрешение и изменения к нему носят один
номер.

    группа   документов   различных key   различных docNum   схлопывается
    DocRS         6098          6096            4305             1793
    DocRV         5419          5415            4969              450
    DocIZ          548           547             393              155

    общих docNum между схемами (DocRS): 2   ← ради этого ключ и вводился
    общих key    между схемами (DocRS): 2   ← те же два

На проде 9182 строки против 12 065 документов на портале — нет 23.9 %
реестра. Пример 66-06-06-2026: портал отдаёт два документа (key …719586 —
само разрешение, key …752293 — изменения к нему), а UPSERT с
предпочтением позднего date_reg оставлял только изменение. Так вытеснено
598 из 4320 строк РНС (13.8 %) — в §6 на месте разрешения показывается
изменение к нему, без признака подмены.

Ключ стал `UNIQUE (source_key)`: разделяет разрешение и изменения (разные
key) и по-прежнему склеивает настоящие межсхемные дубли (у них key
ОБЩИЙ — ровно 7 записей по всем группам). Дедуп перед сменой не нужен:
source_key на проде уже уникален (9182 из 9182, NOT NULL).

Заодно группа DocIZ добавлена в GROUP_CODE — её не было вовсе, 548
документов не грузились. CHECK расширен значением 'IZ'.

§6 сужена до РНС/РВЭ ЯВНО: агрегат обещает total_count = rs_count +
rv_count, а строки 'IZ' попадали бы в total и ни в один счётчик.
Показывать ли изменения отдельной строкой — вопрос продуктовый (#2986);
до его решения сужение стоит в запросе, а не держится на том, что таких
строк «пока нет».

Проверки:
- два гейта на лоадер (GROUP_CODE и цель ON CONFLICT) — БЕЗ базы,
  двусторонние: на origin/main дают конкретные неверные значения
  ({'DocRS','DocRV'} и старый ON CONFLICT в тексте запроса);
- гейт на §6 и контроль инварианта total = rs + rv на данных — красные
  на origin/main;
- герметичная репетиция миграции на временной копии: со старым ключом
  разрешение и изменение схлопываются в одну строку (и остаётся именно
  изменение — как на проде), после миграции живут раздельно; межсхемный
  дубль по-прежнему склеивается; CHECK принимает 'IZ' и отвергает мусор.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Light1YT added 1 commit 2026-08-20 17:15:18 +00:00
chore(ptica): перенумеровать миграцию 191 → 192 (#2986)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m35s
CI / backend-tests (pull_request) Successful in 17m34s
4587a73f88
Номер 191 занят PR #2984 (backfill act_date), который уходит в main
раньше. Обе ветки прошли CI со своим 191 — проверка идёт по голове
ветки и о занятости номера соседом узнать не может.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Light1YT force-pushed fix/2986-permits-key from 4587a73f88 to 9e04086daf 2026-08-20 17:44:46 +00:00 Compare
bot-backend merged commit 0185889f72 into main 2026-08-20 18:03:49 +00:00
Author
Collaborator

Проверено на проде

Деплой 0185889f зелёный. Миграция 192 применилась — констрейнты на проде:

chk_gisogd_permits_doc_group   CHECK (doc_group = ANY (ARRAY['RS','RV','IZ']))
uq_gisogd_permits_source_key   UNIQUE (source_key)
gisogd_permits_pkey            PRIMARY KEY (id)

Прежнего UNIQUE (doc_group, doc_num) больше нет, 'IZ' теперь допустим.

Чего проверка ещё НЕ показывает

Строки на месте: RV 4862 / RS 4320 / IZ 0 — ровно как до миграции. Это ожидаемо: миграция меняет только ключ, а недостающие 2883 документа вернёт загрузчик. Отсутствие изменений в данных сейчас ничего не доказывает — их и не должно было быть.

gisogd-permits-weekly ходит по расписанию 30 6 * * tue (вторник 06:30 МСК), последний успех — 11.08. Ближайший прогон — вторник 25.08.2026. Вручную не запускаю: это ~2900 новых карточек к стороннему госпорталу, у загрузчика своя вежливая пауза и circuit breaker, и правильный момент для этого — его собственное расписание.

Критерий с датой — 25.08.2026

После вторничного прогона на проде должно быть:

строк в gisogd_permits     ≈ 12 058   (сейчас 9 182)
doc_group='IZ'             ≈   393    (сейчас 0)
строк RS по номеру 66-06-06-2026 = 2  (сейчас 1: только «Изменения…»)

Последняя строка — самая говорящая: рядом с изменением должно появиться само разрешение на строительство, которого в данных не было.

Если 26.08 числа не сойдутся — значит дело не в ключе, и разбираться надо дальше, а не считать пункт закрытым.

## Проверено на проде Деплой `0185889f` зелёный. Миграция 192 применилась — констрейнты на проде: ``` chk_gisogd_permits_doc_group CHECK (doc_group = ANY (ARRAY['RS','RV','IZ'])) uq_gisogd_permits_source_key UNIQUE (source_key) gisogd_permits_pkey PRIMARY KEY (id) ``` Прежнего `UNIQUE (doc_group, doc_num)` больше нет, `'IZ'` теперь допустим. ## Чего проверка ещё НЕ показывает Строки на месте: `RV 4862 / RS 4320 / IZ 0` — ровно как до миграции. Это ожидаемо: миграция меняет только ключ, а недостающие 2883 документа вернёт загрузчик. Отсутствие изменений в данных сейчас **ничего не доказывает** — их и не должно было быть. `gisogd-permits-weekly` ходит по расписанию `30 6 * * tue` (вторник 06:30 МСК), последний успех — 11.08. Ближайший прогон — **вторник 25.08.2026**. Вручную не запускаю: это ~2900 новых карточек к стороннему госпорталу, у загрузчика своя вежливая пауза и circuit breaker, и правильный момент для этого — его собственное расписание. ## Критерий с датой — 25.08.2026 После вторничного прогона на проде должно быть: ``` строк в gisogd_permits ≈ 12 058 (сейчас 9 182) doc_group='IZ' ≈ 393 (сейчас 0) строк RS по номеру 66-06-06-2026 = 2 (сейчас 1: только «Изменения…») ``` Последняя строка — самая говорящая: рядом с изменением должно появиться само разрешение на строительство, которого в данных не было. Если 26.08 числа не сойдутся — значит дело не в ключе, и разбираться надо дальше, а не считать пункт закрытым.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2987
No description provided.