fix(analytics): de-inflate domrf_kn snapshot counts (8.49×) + backfill UPSERT columns (#2464) #2470

Merged
bot-backend merged 1 commit from worktree-agent-aab4625f7363b7d87 into main 2026-07-08 07:30:14 +00:00
Collaborator

Что

Волна 3 эпика #2464, честность данных — кластеры E + F. domrf_kn_objects хранит ~9 исторических снапшотов на obj_id (UNIQUE(obj_id, snapshot_date), без ретенции).

⚠️ Видимое изменение чисел: прод-замер — 4090 строк / 482 distinct obj_id для site_status='Строящиеся' = 8.49× инфляция. Счётчики активных конкурентов и портфели девелоперов упадут ~8× до реальных значений. Это корректная де-инфляция (не потеря данных), но в отчёте числа заметно снизятся — прошу иметь в виду при деплое, чтобы не приняли за регресс.

Read-side де-инфляция (cluster E)

Наивный COUNT(*)/SELECT ... WHERE dev_id считал каждый снапшот отдельным объектом. Фикс — DISTINCT ON (obj_id) ORDER BY snapshot_date DESC NULLS LAST (latest-snapshot-per-obj_id, сиблинг-паттерн supply_layers._L3_FUTURE_SQL + прецедент 0de32cd2).

  • _active_competitors_count._q() — де-инфляция + волатильные фильтры наружу CTE. Прод-замер per-obj_id variance (authoritative): dev_id=0, region_cd=0 стабильны; district_name=1, obj_class=29, site_status=11 волатильны. Поэтому site_status, district_name, COALESCE(obj_class,obj_class_fallback) применяются после DISTINCT ON (на истинно-последней строке), в CTE остаётся только стабильный region_cd как partition-scope. Иначе для 29 объектов со сменившимся классом взяли бы «последний снапшот, где класс совпал», а не актуальный.
  • developer_portfolio() — тот же дедуп; dev_id (variance 0) остаётся в CTE; форма колонок неизменна.
  • _competitors_two_dim active CTE — тот же анти-паттерн (site_status внутри DISTINCT ON), разбит на latest(дедуп)→active(волатильные фильтры), форма вывода сохранена. (follow-up из ревью, свёрнут сюда.)

Write-side UPSERT-пропуски (clusters E + F)

  • UPSERT_OBJECT_SQL ON CONFLICT DO UPDATE SET терял 8 свежевычисляемых колонок при ре-скрейпе (problem_flag, green_house, floor_min, floor_max, hobj_id, short_addr, dev_inn, region_cd) → устаревали. Добавлены как прямой EXCLUDED.<col> (не COALESCE — чтобы problem_flag мог сброситься в NULL при исчезновении проблемы).
  • UPSERT_PHOTO_SQL — добавлен build_type = EXCLUDED.build_type.

Индекс: существующий uq_kn_objects_id_snap UNIQUE (obj_id, snapshot_date) покрывает DISTINCT ON — миграция не нужна.

Тесты

24 новых (SQL-shape + algorithm-spec): дедуп присутствует · волатильные предикаты post-DISTINCT-ON (site_status + district + class) · sold-out объект со старым Строящиеся-снапшотом НЕ считается активным · UPSERT покрывает все INSERT-колонки (generic-guard от рецидива). analytics+scrapers = 24 passed; wider (recommend/forecasting/site_finder) = 1746 passed, ruff чисто.

Ревью

code-reviewer: APPROVE (по core), + tightening после моего прод-замера (obj_class волатилен у 29 obj_id — reviewer предполагал стабильность абстрактно, замер решающий) и свёрнутый сиблинг _competitors_two_dim. Прецедент дедупа 0de32cd2 подтверждён.

Post-deploy: сверю Строящиеся-счётчик региона на проде (ожидаю ~8× падение).

Refs #2464 (clusters E + F).

## Что Волна 3 эпика **#2464**, честность данных — кластеры **E + F**. `domrf_kn_objects` хранит ~9 исторических снапшотов на `obj_id` (UNIQUE(obj_id, snapshot_date), без ретенции). > ⚠️ **Видимое изменение чисел:** прод-замер — 4090 строк / 482 distinct obj_id для `site_status='Строящиеся'` = **8.49× инфляция**. Счётчики активных конкурентов и портфели девелоперов упадут ~8× до реальных значений. Это корректная де-инфляция (не потеря данных), но в отчёте числа заметно снизятся — прошу иметь в виду при деплое, чтобы не приняли за регресс. ### Read-side де-инфляция (cluster E) Наивный `COUNT(*)`/`SELECT ... WHERE dev_id` считал каждый снапшот отдельным объектом. Фикс — `DISTINCT ON (obj_id) ORDER BY snapshot_date DESC NULLS LAST` (latest-snapshot-per-obj_id, сиблинг-паттерн `supply_layers._L3_FUTURE_SQL` + прецедент `0de32cd2`). - `_active_competitors_count._q()` — де-инфляция + **волатильные фильтры наружу CTE**. Прод-замер per-obj_id variance (authoritative): `dev_id=0, region_cd=0` стабильны; `district_name=1, obj_class=29, site_status=11` **волатильны**. Поэтому `site_status`, `district_name`, `COALESCE(obj_class,obj_class_fallback)` применяются **после** `DISTINCT ON` (на истинно-последней строке), в CTE остаётся только стабильный `region_cd` как partition-scope. Иначе для 29 объектов со сменившимся классом взяли бы «последний снапшот, где класс совпал», а не актуальный. - `developer_portfolio()` — тот же дедуп; `dev_id` (variance 0) остаётся в CTE; форма колонок неизменна. - `_competitors_two_dim` `active` CTE — тот же анти-паттерн (site_status внутри `DISTINCT ON`), разбит на `latest`(дедуп)→`active`(волатильные фильтры), форма вывода сохранена. *(follow-up из ревью, свёрнут сюда.)* ### Write-side UPSERT-пропуски (clusters E + F) - `UPSERT_OBJECT_SQL` `ON CONFLICT DO UPDATE SET` терял 8 свежевычисляемых колонок при ре-скрейпе (`problem_flag, green_house, floor_min, floor_max, hobj_id, short_addr, dev_inn, region_cd`) → устаревали. Добавлены как прямой `EXCLUDED.<col>` (**не COALESCE** — чтобы `problem_flag` мог сброситься в NULL при исчезновении проблемы). - `UPSERT_PHOTO_SQL` — добавлен `build_type = EXCLUDED.build_type`. Индекс: существующий `uq_kn_objects_id_snap UNIQUE (obj_id, snapshot_date)` покрывает `DISTINCT ON` — миграция не нужна. ## Тесты 24 новых (SQL-shape + algorithm-spec): дедуп присутствует · **волатильные предикаты post-DISTINCT-ON** (site_status + district + class) · sold-out объект со старым Строящиеся-снапшотом НЕ считается активным · UPSERT покрывает все INSERT-колонки (generic-guard от рецидива). `analytics`+`scrapers` = 24 passed; wider (`recommend`/`forecasting`/`site_finder`) = **1746 passed**, ruff чисто. ## Ревью code-reviewer: **✅ APPROVE** (по core), + tightening после моего прод-замера (obj_class волатилен у 29 obj_id — reviewer предполагал стабильность абстрактно, замер решающий) и свёрнутый сиблинг `_competitors_two_dim`. Прецедент дедупа `0de32cd2` подтверждён. **Post-deploy:** сверю `Строящиеся`-счётчик региона на проде (ожидаю ~8× падение). Refs #2464 (clusters E + F).
bot-backend added 1 commit 2026-07-08 07:14:19 +00:00
fix(analytics): de-inflate domrf_kn snapshot counts + backfill UPSERT columns (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-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 2m18s
CI / backend-tests (pull_request) Successful in 15m16s
7590b6467e
domrf_kn_objects retains ~9 snapshots per obj_id (prod: 4090 rows / 482
distinct obj_id for Строящиеся = 8.49× inflation). _active_competitors_count
and developer_portfolio counted every historical snapshot as a distinct
object; _competitors_two_dim's active CTE had the same class. Dedup via
DISTINCT ON (obj_id) ORDER BY snapshot_date DESC, applying VOLATILE filters
(site_status, district_name, obj_class — prod variance 11/1/29) in the outer
query after dedup so an object is counted by its TRUE-latest snapshot, while
stable scope (region_cd, dev_id — variance 0) stays in the CTE. Competitor
counts and developer portfolios drop ~8× — correct de-inflation.

Also backfill ON CONFLICT DO UPDATE SET: UPSERT_OBJECT_SQL dropped 8
freshly-computed columns on re-scrape, UPSERT_PHOTO_SQL dropped build_type —
added as direct EXCLUDED (not COALESCE, so problem_flag can clear to NULL).

Refs #2464 (clusters E + F).
bot-backend merged commit 9a6b56016b into main 2026-07-08 07:30:14 +00:00
bot-backend deleted branch worktree-agent-aab4625f7363b7d87 2026-07-08 07:30:14 +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#2470
No description provided.