fix(recommend): cad_buildings.floors is INTEGER not TEXT (HOTFIX) #218

Merged
lekss361 merged 1 commit from fix/recommend-mix-floors-integer into main 2026-05-16 16:00:24 +00:00
Owner

🔴 Hotfix — PROD 500

Endpoint /api/v1/analytics/recommend/mix падает на любом районе ЕКБ:

psycopg.errors.UndefinedFunction: operator does not exist: integer ~ unknown
LINE 19: AND ((cb.floors ~ '^[0-9]+$' AND cb.f...

Root cause

_district_cadastre_baseline (analytics_queries.py:1354) использует regex ~ против cb.floors. Comment утверждает что floors — TEXT, но реальный schema:

SELECT column_name, data_type FROM information_schema.columns
WHERE table_name='cad_buildings' AND column_name='floors';
-- floors | integer

Regex op ~ не работает с integer → SQL crash → 500 response.

Fix

Удаляем regex defensive check (предназначался для значений вроде '1-2', '2-3'), заменяем на прямое сравнение cb.floors >= 3. NULL safety сохраняется через OR cb.purpose ILIKE '%многокв%'.

-AND ((cb.floors ~ '^[0-9]+$' AND cb.floors::int >= 3)
-     OR cb.purpose ILIKE '%многокв%')
+AND (cb.floors >= 3 OR cb.purpose ILIKE '%многокв%')

Verify

EXPLAIN на проде после fix:

Aggregate (cost=74.58..74.59 rows=1 width=16)
  -> Nested Loop (cost=0.28..74.56 rows=1 width=15)
       -> Limit (cost=0.00..4.11 rows=1 width=32)
          Filter: ((floors >= 3) OR (purpose ~~* '%многокв%'))

Test plan

  • Deploy → curl /api/v1/analytics/recommend/mix body {"district_name":"Кировский","target_class":"Comfort",...} → HTTP 200 с JSON
  • UI /analytics/recommend на сайте → квартирография анализ работает

Phantom column gate (PR #216)

Этот bug — именно тот класс ошибок что #197/#216 призван ловить, но _district_cadastre_baseline SQL не покрыт integration тестами (нет в test_phantom_columns.py). Должен быть добавлен в follow-up (тест регрессии) — отдельный issue.

Closes (no issue — direct prod report by user)

## 🔴 Hotfix — PROD 500 Endpoint `/api/v1/analytics/recommend/mix` падает на любом районе ЕКБ: ``` psycopg.errors.UndefinedFunction: operator does not exist: integer ~ unknown LINE 19: AND ((cb.floors ~ '^[0-9]+$' AND cb.f... ``` ## Root cause `_district_cadastre_baseline` (analytics_queries.py:1354) использует regex `~` против `cb.floors`. Comment утверждает что `floors` — TEXT, но реальный schema: ```sql SELECT column_name, data_type FROM information_schema.columns WHERE table_name='cad_buildings' AND column_name='floors'; -- floors | integer ``` Regex op `~` не работает с integer → SQL crash → 500 response. ## Fix Удаляем regex defensive check (предназначался для значений вроде `'1-2'`, `'2-3'`), заменяем на прямое сравнение `cb.floors >= 3`. NULL safety сохраняется через `OR cb.purpose ILIKE '%многокв%'`. ```diff -AND ((cb.floors ~ '^[0-9]+$' AND cb.floors::int >= 3) - OR cb.purpose ILIKE '%многокв%') +AND (cb.floors >= 3 OR cb.purpose ILIKE '%многокв%') ``` ## Verify EXPLAIN на проде после fix: ``` Aggregate (cost=74.58..74.59 rows=1 width=16) -> Nested Loop (cost=0.28..74.56 rows=1 width=15) -> Limit (cost=0.00..4.11 rows=1 width=32) Filter: ((floors >= 3) OR (purpose ~~* '%многокв%')) ``` ## Test plan - [ ] Deploy → curl `/api/v1/analytics/recommend/mix` body `{"district_name":"Кировский","target_class":"Comfort",...}` → HTTP 200 с JSON - [ ] UI /analytics/recommend на сайте → квартирография анализ работает ## Phantom column gate (PR #216) Этот bug — **именно тот класс ошибок** что #197/#216 призван ловить, но `_district_cadastre_baseline` SQL **не покрыт** integration тестами (нет в `test_phantom_columns.py`). Должен быть добавлен в follow-up (тест регрессии) — отдельный issue. Closes (no issue — direct prod report by user)
lekss361 added 1 commit 2026-05-16 15:30:08 +00:00
PROD 500 on /api/v1/analytics/recommend/mix:
  psycopg.errors.UndefinedFunction: operator does not exist: integer ~ unknown
  LINE 19: AND ((cb.floors ~ '^[0-9]+$' AND cb.f...

_district_cadastre_baseline use regex \ on cb.floors. Schema is INTEGER
(verified information_schema), so the regex defence against text values
('1-2', '2-3' etc) is misplaced — simplify to floors >= 3 with NULL safety
via OR purpose ILIKE '%многокв%' fallback.
Author
Owner

Verdict: APPROVE (hotfix, ship it)

Summary

1-line hotfix для prod 500 на /api/v1/analytics/recommend/mix. Заменён неработающий regex (cb.floors ~ '^[0-9]+$' AND cb.floors::int >= 3) на прямое сравнение cb.floors >= 3. Корректно — floors в текущей schema это integer NULL (verified via get_object_details на prod), а regex-оператор ~ для INTEGER не определён → 500.

Schema reality check

cad_buildings.floors → integer, NULLABLE, default NULL

Migration #169 уже привела колонку к INT (см. parcels.py:658 docstring и test_prod_smoke.py:65). Так что defensive regex был мёртвый код, который начал throw'ить после того как Postgres стал планировать этот путь.

Correctness analysis

  • cb.floors >= 3 для NULL rows → NULL → falsy в WHERE → строка отбрасывается.
  • OR cb.purpose ILIKE '%многокв%' — rescue branch для NULL floors с известным purpose. NULL safety сохранена.
  • Семантика идентична старой intent (МКД ≥3 этажа), но без regex-крэша.
  • Legacy TEXT-значений типа '1-2' в схеме INTEGER никогда не было — regex был theoretical guard для воображаемой схемы.

Codebase scan

  • git grep "floors ~" → 0 matches ( единственное место)
  • git grep "floors::int" → 0 matches ( единственное место)
  • cb.floors встречается только в этой fixed строке (analytics_queries.py:1354)
  • Других INTEGER-vs-regex багов нет.

Nit / nice-to-have (non-blocking)

PR body упоминает путь backend/app/services/site_finder/analytics_queries.py, но реальный diff правит backend/app/services/analytics_queries.py (без site_finder/). Просто косметика в описании.

Follow-up (HIGH priority, отдельным PR)

Phantom-column gate (PR #216) НЕ покрывает _district_cadastre_baseline — verified, в backend/tests/integration/test_phantom_columns.py нет упоминания floors/recommend/mix/cadastre_baseline. Этот баг проскочил все pre-merge проверки и долетел до prod. Рекомендую добавить EXPLAIN-тест на SQL из _district_cadastre_baseline() (и заодно на все аналитические queries без покрытия) — это поймало бы текущий баг + любые будущие type-mismatch / phantom-column regression.


Blocked for auto-merge

Blocked for auto-merge per scope policy (touches backend/app/services/**). Human merge required.

@lekss361 — verdict APPROVE, prod-hotfix корректен. Прошу:

  1. Смержить вручную (squash) — production падает.
  2. Создать follow-up issue: добавить _district_cadastre_baseline в phantom-column gate.
<!-- gendesign-review-bot: sha=fbb7681 verdict=approve --> ## Verdict: APPROVE (hotfix, ship it) ### Summary 1-line hotfix для prod 500 на `/api/v1/analytics/recommend/mix`. Заменён неработающий regex (`cb.floors ~ '^[0-9]+$' AND cb.floors::int >= 3`) на прямое сравнение `cb.floors >= 3`. Корректно — `floors` в текущей schema это `integer NULL` (verified via `get_object_details` на prod), а regex-оператор `~` для INTEGER не определён → 500. ### Schema reality check ``` cad_buildings.floors → integer, NULLABLE, default NULL ``` Migration #169 уже привела колонку к INT (см. `parcels.py:658` docstring и `test_prod_smoke.py:65`). Так что defensive regex был мёртвый код, который начал throw'ить после того как Postgres стал планировать этот путь. ### Correctness analysis - `cb.floors >= 3` для NULL rows → NULL → falsy в WHERE → строка отбрасывается. ✅ - `OR cb.purpose ILIKE '%многокв%'` — rescue branch для NULL floors с известным purpose. ✅ NULL safety сохранена. - Семантика идентична старой intent (МКД ≥3 этажа), но без regex-крэша. - Legacy TEXT-значений типа `'1-2'` в схеме INTEGER никогда не было — regex был theoretical guard для воображаемой схемы. ### Codebase scan - `git grep "floors ~"` → 0 matches (✅ единственное место) - `git grep "floors::int"` → 0 matches (✅ единственное место) - `cb.floors` встречается только в этой fixed строке (analytics_queries.py:1354) - Других INTEGER-vs-regex багов нет. ### Nit / nice-to-have (non-blocking) PR body упоминает путь `backend/app/services/site_finder/analytics_queries.py`, но реальный diff правит `backend/app/services/analytics_queries.py` (без `site_finder/`). Просто косметика в описании. ### Follow-up (HIGH priority, отдельным PR) **Phantom-column gate (PR #216) НЕ покрывает `_district_cadastre_baseline`** — verified, в `backend/tests/integration/test_phantom_columns.py` нет упоминания floors/recommend/mix/cadastre_baseline. Этот баг проскочил все pre-merge проверки и долетел до prod. Рекомендую добавить `EXPLAIN`-тест на SQL из `_district_cadastre_baseline()` (и заодно на все аналитические queries без покрытия) — это поймало бы текущий баг + любые будущие type-mismatch / phantom-column regression. --- ### Blocked for auto-merge Blocked for auto-merge per scope policy (touches `backend/app/services/**`). Human merge required. @lekss361 — verdict APPROVE, prod-hotfix корректен. Прошу: 1. Смержить вручную (squash) — production падает. 2. Создать follow-up issue: добавить `_district_cadastre_baseline` в phantom-column gate.
lekss361 merged commit 99372a211f into main 2026-05-16 16:00:24 +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#218
No description provided.