(medium) pzz_loader: rosreestr_id может быть NULL → дубли строк, ON CONFLICT не срабатывает при повторном sync #1361

Closed
opened 2026-06-14 18:09:09 +00:00 by bot-backend · 1 comment
Collaborator

Описание

rosreestr_id = props.get('OBJECTID') or props.get('objectid') может оказаться None (PKK6 при f=geojson нередко кладёт OBJECTID в feature-level id, а не в properties). При rid IS NULL INSERT всё равно выполняется, но ON CONFLICT (rosreestr_id) НЕ срабатывает для NULL (в PostgreSQL NULL≠NULL в обычном UNIQUE). Результат: при каждом повторном sync вставляются НОВЫЕ строки с NULL rosreestr_id вместо апдейта — накопление дублей зон. Счётчик inserted растёт корректно, маскируя деградацию; геопространственные JOIN из parcels.py получают дубли.

Корень

Нет fallback на f.get('id') и нет skip при None rosreestr_id; UNIQUE(rosreestr_id) в схеме без NULLS NOT DISTINCT.

Evidence

pzz_loader.py:100-128:

rosreestr_id = props.get("OBJECTID") or props.get("objectid")
... VALUES ( :rid, ... ) ON CONFLICT (rosreestr_id) DO UPDATE ...
{ "rid": rosreestr_id, ... }

data/sql/85_pzz_zones_ekb.sql:22: UNIQUE(rosreestr_id) (без NULLS NOT DISTINCT).
Между строками 100 и 131 нет if rosreestr_id is None: skipped += 1; continue.

Suggested fix

Если rosreestr_id is None — пробовать f.get('id') (geojson feature id) как fallback, и при по-прежнему None — skip с предупреждением (skipped += 1), чтобы не плодить NULL-дубли. Либо завести unique по более стабильному ключу / добавить NULLS NOT DISTINCT.

Обоснование severity

medium: баг молча накапливается при каждом автоматическом sync, затрагивает корректность геопространственных запросов; защиты в коде нет. confidence 0.82.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=medium · category=data-integrity

Затронутые файлы:

  • /Users/anton/Птица/gendesign/backend/app/services/site_finder/pzz_loader.py
  • /Users/anton/Птица/gendesign/data/sql/85_pzz_zones_ekb.sql
## Описание `rosreestr_id = props.get('OBJECTID') or props.get('objectid')` может оказаться None (PKK6 при `f=geojson` нередко кладёт OBJECTID в feature-level `id`, а не в `properties`). При `rid IS NULL` INSERT всё равно выполняется, но `ON CONFLICT (rosreestr_id)` НЕ срабатывает для NULL (в PostgreSQL NULL≠NULL в обычном UNIQUE). Результат: при каждом повторном sync вставляются НОВЫЕ строки с NULL rosreestr_id вместо апдейта — накопление дублей зон. Счётчик `inserted` растёт корректно, маскируя деградацию; геопространственные JOIN из `parcels.py` получают дубли. ## Корень Нет fallback на `f.get('id')` и нет `skip` при None rosreestr_id; UNIQUE(rosreestr_id) в схеме без NULLS NOT DISTINCT. ## Evidence `pzz_loader.py:100-128`: ```python rosreestr_id = props.get("OBJECTID") or props.get("objectid") ... VALUES ( :rid, ... ) ON CONFLICT (rosreestr_id) DO UPDATE ... { "rid": rosreestr_id, ... } ``` `data/sql/85_pzz_zones_ekb.sql:22`: `UNIQUE(rosreestr_id)` (без NULLS NOT DISTINCT). Между строками 100 и 131 нет `if rosreestr_id is None: skipped += 1; continue`. ## Suggested fix Если `rosreestr_id is None` — пробовать `f.get('id')` (geojson feature id) как fallback, и при по-прежнему None — skip с предупреждением (`skipped += 1`), чтобы не плодить NULL-дубли. Либо завести unique по более стабильному ключу / добавить NULLS NOT DISTINCT. ## Обоснование severity medium: баг молча накапливается при каждом автоматическом sync, затрагивает корректность геопространственных запросов; защиты в коде нет. confidence 0.82. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**medium** · category=`data-integrity`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/backend/app/services/site_finder/pzz_loader.py` - `/Users/anton/Птица/gendesign/data/sql/85_pzz_zones_ekb.sql`
bot-backend added the
week ревью 1
label 2026-06-14 18:09:09 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:52 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода: backend/app/services/site_finder/pzz_loader.py:100rosreestr_id = props.get("OBJECTID") or props.get("objectid"); INSERT :113-138 с ON CONFLICT (rosreestr_id) DO UPDATE. Между :100 и INSERT нет проверки rosreestr_id is None. data/sql/85_pzz_zones_ekb.sql:22UNIQUE(rosreestr_id) без NULLS NOT DISTINCT.

Корень: при rosreestr_id IS NULL (PKK6 при f=geojson кладёт OBJECTID в feature-level id, а не в properties) INSERT выполняется, но ON CONFLICT (rosreestr_id) не срабатывает — в PostgreSQL NULL≠NULL в обычном UNIQUE. Каждый sync вставляет новую строку с NULL rosreestr_id → накопление дублей зон, искажающее геопространственные JOIN из parcels.py. Per-row SAVEPOINT (db.begin_nested() :112) корректен, но счётчик inserted растёт, маскируя деградацию.

Что править:

  • После :100 добавить fallback на f.get("id") (geojson feature id), затем при по-прежнему None — skipped += 1; continue (счётчик skipped уже есть, :91/:98) с logger.warning, чтобы не плодить NULL-дубли.
  • Альтернатива/дополнение: в 85_*.sql сменить на UNIQUE NULLS NOT DISTINCT (rosreestr_id) (PG15+) либо завести unique по более стабильному ключу — отдельной idempotent-миграцией (текущий файл уже applied через _schema_migrations).

Что посмотреть:

  • Код: pzz_loader.py цикл :94-145; тест на feature без OBJECTID/objectid в properties → проверить skip или fallback на f["id"]. Проверить дубли в проде: SELECT count(*), count(*) FILTER (WHERE rosreestr_id IS NULL) FROM pzz_zones_ekb.
  • Vault: релевантных заметок не найдено (есть Bug_Pzz_Loader_Missing_Savepoint_May14 про SAVEPOINT — другой дефект того же модуля, уже исправлен).
**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** `backend/app/services/site_finder/pzz_loader.py:100` — `rosreestr_id = props.get("OBJECTID") or props.get("objectid")`; INSERT `:113-138` с `ON CONFLICT (rosreestr_id) DO UPDATE`. Между `:100` и INSERT нет проверки `rosreestr_id is None`. `data/sql/85_pzz_zones_ekb.sql:22` — `UNIQUE(rosreestr_id)` без `NULLS NOT DISTINCT`. **Корень:** при `rosreestr_id IS NULL` (PKK6 при `f=geojson` кладёт OBJECTID в feature-level `id`, а не в `properties`) INSERT выполняется, но `ON CONFLICT (rosreestr_id)` не срабатывает — в PostgreSQL NULL≠NULL в обычном UNIQUE. Каждый sync вставляет новую строку с NULL rosreestr_id → накопление дублей зон, искажающее геопространственные JOIN из `parcels.py`. Per-row SAVEPOINT (`db.begin_nested()` :112) корректен, но счётчик `inserted` растёт, маскируя деградацию. **Что править:** - После `:100` добавить fallback на `f.get("id")` (geojson feature id), затем при по-прежнему None — `skipped += 1; continue` (счётчик `skipped` уже есть, `:91`/`:98`) с `logger.warning`, чтобы не плодить NULL-дубли. - Альтернатива/дополнение: в `85_*.sql` сменить на `UNIQUE NULLS NOT DISTINCT (rosreestr_id)` (PG15+) либо завести unique по более стабильному ключу — отдельной idempotent-миграцией (текущий файл уже applied через `_schema_migrations`). **Что посмотреть:** - Код: `pzz_loader.py` цикл `:94-145`; тест на feature без `OBJECTID`/`objectid` в properties → проверить skip или fallback на `f["id"]`. Проверить дубли в проде: `SELECT count(*), count(*) FILTER (WHERE rosreestr_id IS NULL) FROM pzz_zones_ekb`. - Vault: релевантных заметок не найдено (есть `Bug_Pzz_Loader_Missing_Savepoint_May14` про SAVEPOINT — другой дефект того же модуля, уже исправлен).
Sign in to join this conversation.
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#1361
No description provided.