fix(tradein/quality): витрина перестаёт рапортовать «доля с кадастром» (#2674) #2850

Merged
bot-backend merged 1 commit from fix/2674-quality-cadastre-column into main 2026-08-12 20:37:17 +00:00
Collaborator

Summary

v_data_quality.pct_cadastr = 0.000000 у всех прогонов. Разбор показал: это НЕ дефект измерителя и НЕ «смотрит не на ту колонку» — это третий показатель класса 214 (outliers_flagged) и 216 (price_disagreements_count): ноль, гарантированный устройством системы, читается как «проверили — чисто». Поэтому показатель снят, а не переключён на соседнюю колонку.

Числа с прода 2026-08-13 (точный count):

pct_cadastr 0.000000000000000000000000
знаменатель витрины (listings_active) 45 198 (в listings всего 99 304)
listings.cadastral_number IS NOT NULL 0 из 99 304 (и 0 из 45 198 активных)
deals.cadastral_number 0 из 96 974
houses.cadastral_number (DaData) 2 648 из 9 468 ← другой объект
listings.building_cadastral_number 30 970 из 99 304 ← другой объект

Измеритель здоров (контроль на заведомо здоровом образце в тех же данных): тот же CTE active_listings и тот же шаблон count(*) WHERE <col> IS NOT NULL * 100.0 / NULLIF(count(*),0) в соседних строках даёт pct_geocoded 95.61%, pct_description 39.82%, pct_year_built 65.10%. Ровно 0% — про колонку, а не про арифметику.

Почему ноль структурный. listings.cadastral_number — кадастр КВАРТИРЫ. Единственное место в коде, которое его читает, — providers/cian/serp.py:886; в парсерах avito/yandex/domclick/n1 слов cadastr/kadastr нет ни разу, т.е. для 4 площадок из 5 ноль гарантирован НАШИМ кодом и о предметной области не говорит ничего. Это уже записано и в matching/houses.py: «площадки кадастр не отдают».

Почему НЕ «правка на одну строку» (переключить на соседнюю колонку). listings.building_cadastral_number — кадастр ЗДАНИЯ, и в listings у него ровно один писатель: наш ночной KNN ≤50 м по локальному зеркалу ЕГРН (tasks/cadastral_geo_match.py:161; других INSERT/UPDATE этой колонки на origin/main нет). #2674 замерил ключ как неинъективный (656/3 260 значений накрывают >1 здание ГАР, 20.1%) и запретил считать его идентичностью здания. Под подписью «доля объявлений с кадастром» он мерил бы покрытие нашего геокодера и охват зеркала (разброс по площадкам 20.3–49.7%) — новая ложь вместо старой. Авторитетный кадастр здания есть (houses.cadastral_number, DaData 2 648/9 468), но он про ДОМА, а витрина считает ОБЪЯВЛЕНИЯ.

Цена правки. Читателей у витрины в коде нет (grep по /app/app в живом контейнере пуст; /api/v1/admin/scraper/data-quality считает свои метрики сам и кадастр не показывает) — это ручной psql-снимок. Зависимых объектов у view нет (pg_depend, прод 13.08: 0 строк) → CASCADE не нужен.

Что в диффе

  • data/sql/259_data_quality_drop_pct_cadastr.sqlDROP VIEWCREATE VIEW без строки pct_cadastr (CREATE OR REPLACE колонку удалить не может; тот же порядок, что 214/216). Тело скопировано из 222 и сверено с живым pg_get_viewdef. Из CTE убран ставший ненужным cadastral_number — 222 завела явный список колонок ровно затем, чтобы view не держал column-level зависимость на то, чего не показывает. SET LOCAL lock_timeout = '5s' перед DROP. COMMENT ON VIEW выставлен заново (DROP уносит комментарий вместе с объектом — в отличие от 222 с её CREATE OR REPLACE).
  • _manifest_applied.txt — своя строка в своём PR.
  • tests/test_2674_dead_admin_metrics.py — гейт против возврата, рядом с таким же для outliers_flagged; общий хелпер _latest_v_data_quality() вместо копии парсера.

Test plan

  • Красный прогон. Новый тест против дерева origin/main (детач-worktree, тест скопирован в него): test_latest_v_data_quality_no_longer_reports_flat_cadastre FAILED — «показатель вернулся в v_data_quality», последний DDL там 222 со строкой AS pct_cadastr. Соседний ..._no_longer_reports_outliers в том же прогоне PASSED (сама механика теста исправна). На ветке: 16 passed.
  • Оба состояния новых проверок доказаны мутациями: (1) убрать COMMENT ON VIEW из 259 → красный на ветке про потерянный комментарий; (2) снять pct_cadastr, но подставить building_cadastral_number под другим именем → красный «в витрину подставили другой кадастр»; исходная 259 → зелёный.
  • Тело нового view прогнано на проде в BEGIN READ ONLY — парсится, возвращает те же 15 колонок и те же значения минус pct_cadastr.
  • scripts/check-migration-lock-timeout.py → «блокирующий DDL прикрыт lock_timeout (проверено новых миграций: 9)», --selftest OK.
  • test_migrations_manifest.py, test_dead_code_sweep_2674.py — зелёные (34 passed суммарно), ruff check/format чисто.

Refs #2674

## Summary `v_data_quality.pct_cadastr` = **0.000000** у всех прогонов. Разбор показал: это НЕ дефект измерителя и НЕ «смотрит не на ту колонку» — это третий показатель класса 214 (`outliers_flagged`) и 216 (`price_disagreements_count`): ноль, гарантированный устройством системы, читается как «проверили — чисто». Поэтому показатель **снят**, а не переключён на соседнюю колонку. **Числа с прода 2026-08-13 (точный count):** | | | |---|---| | `pct_cadastr` | 0.000000000000000000000000 | | знаменатель витрины (`listings_active`) | 45 198 (в `listings` всего 99 304) | | `listings.cadastral_number IS NOT NULL` | 0 из 99 304 (и 0 из 45 198 активных) | | `deals.cadastral_number` | 0 из 96 974 | | `houses.cadastral_number` (DaData) | 2 648 из 9 468 ← другой объект | | `listings.building_cadastral_number` | 30 970 из 99 304 ← другой объект | **Измеритель здоров** (контроль на заведомо здоровом образце в тех же данных): тот же CTE `active_listings` и тот же шаблон `count(*) WHERE <col> IS NOT NULL * 100.0 / NULLIF(count(*),0)` в соседних строках даёт `pct_geocoded` 95.61%, `pct_description` 39.82%, `pct_year_built` 65.10%. Ровно 0% — про колонку, а не про арифметику. **Почему ноль структурный.** `listings.cadastral_number` — кадастр КВАРТИРЫ. Единственное место в коде, которое его читает, — `providers/cian/serp.py:886`; в парсерах avito/yandex/domclick/n1 слов `cadastr`/`kadastr` нет ни разу, т.е. для 4 площадок из 5 ноль гарантирован НАШИМ кодом и о предметной области не говорит ничего. Это уже записано и в `matching/houses.py`: «площадки кадастр не отдают». **Почему НЕ «правка на одну строку» (переключить на соседнюю колонку).** `listings.building_cadastral_number` — кадастр ЗДАНИЯ, и в `listings` у него **ровно один писатель**: наш ночной KNN ≤50 м по локальному зеркалу ЕГРН (`tasks/cadastral_geo_match.py:161`; других INSERT/UPDATE этой колонки на `origin/main` нет). #2674 замерил ключ как неинъективный (656/3 260 значений накрывают >1 здание ГАР, 20.1%) и запретил считать его идентичностью здания. Под подписью «доля объявлений с кадастром» он мерил бы покрытие нашего геокодера и охват зеркала (разброс по площадкам 20.3–49.7%) — новая ложь вместо старой. Авторитетный кадастр здания есть (`houses.cadastral_number`, DaData 2 648/9 468), но он про ДОМА, а витрина считает ОБЪЯВЛЕНИЯ. **Цена правки.** Читателей у витрины в коде нет (grep по `/app/app` в живом контейнере пуст; `/api/v1/admin/scraper/data-quality` считает свои метрики сам и кадастр не показывает) — это ручной psql-снимок. Зависимых объектов у view нет (`pg_depend`, прод 13.08: 0 строк) → CASCADE не нужен. ## Что в диффе - `data/sql/259_data_quality_drop_pct_cadastr.sql` — `DROP VIEW` → `CREATE VIEW` без строки `pct_cadastr` (CREATE OR REPLACE колонку удалить не может; тот же порядок, что 214/216). Тело скопировано из 222 и сверено с живым `pg_get_viewdef`. Из CTE убран ставший ненужным `cadastral_number` — 222 завела явный список колонок ровно затем, чтобы view не держал column-level зависимость на то, чего не показывает. `SET LOCAL lock_timeout = '5s'` перед DROP. `COMMENT ON VIEW` выставлен **заново** (DROP уносит комментарий вместе с объектом — в отличие от 222 с её CREATE OR REPLACE). - `_manifest_applied.txt` — своя строка в своём PR. - `tests/test_2674_dead_admin_metrics.py` — гейт против возврата, рядом с таким же для `outliers_flagged`; общий хелпер `_latest_v_data_quality()` вместо копии парсера. ## Test plan - [x] **Красный прогон.** Новый тест против дерева `origin/main` (детач-worktree, тест скопирован в него): `test_latest_v_data_quality_no_longer_reports_flat_cadastre` FAILED — «показатель вернулся в v_data_quality», последний DDL там 222 со строкой `AS pct_cadastr`. Соседний `..._no_longer_reports_outliers` в том же прогоне PASSED (сама механика теста исправна). На ветке: 16 passed. - [x] **Оба состояния новых проверок доказаны мутациями:** (1) убрать `COMMENT ON VIEW` из 259 → красный на ветке про потерянный комментарий; (2) снять `pct_cadastr`, но подставить `building_cadastral_number` под другим именем → красный «в витрину подставили другой кадастр»; исходная 259 → зелёный. - [x] Тело нового view прогнано на проде в `BEGIN READ ONLY` — парсится, возвращает те же 15 колонок и те же значения минус `pct_cadastr`. - [x] `scripts/check-migration-lock-timeout.py` → «блокирующий DDL прикрыт lock_timeout (проверено новых миграций: 9)», `--selftest OK`. - [x] `test_migrations_manifest.py`, `test_dead_code_sweep_2674.py` — зелёные (34 passed суммарно), ruff check/format чисто. Refs #2674
bot-backend added 1 commit 2026-08-12 20:01:11 +00:00
fix(tradein/quality): витрина перестаёт рапортовать «доля с кадастром» (#2674)
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 4m28s
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / browser-tests (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
5fd03827dd
v_data_quality.pct_cadastr считался по listings.cadastral_number — кадастру
КВАРТИРЫ, которого не отдаёт ни одна площадка (прод 13.08: 0 из 99 304
объявлений, 0 из 96 974 deals, 0 из 53 house_metadata). Ноль, гарантированный
устройством системы, читался рядом с pct_geocoded 95.61% как измеренное
качество данных.

Это не дефект измерителя: тот же CTE и тот же шаблон count в соседних строках
дают 95.61% / 39.82% / 65.10%. Ноль настоящий и означает «неприменимо».

Источник НЕ переключён на listings.building_cadastral_number: та колонка про
ЗДАНИЕ, её единственный писатель — наш ночной KNN ≤50 м по зеркалу ЕГРН
(tasks/cadastral_geo_match.py:161), и #2674 замерил ключ как неинъективный
(20.1% значений накрывают >1 здание ГАР). Под подписью «доля объявлений с
кадастром» она мерила бы покрытие геокодера — новая ложь вместо старой.

Третий показатель этого класса после 214 (outliers_flagged) и 216
(price_disagreements_count), поэтому действие то же: снять, а не чинить.
bot-backend merged commit 3eabbd0186 into main 2026-08-12 20:37:17 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#2850
No description provided.