class_filter подставляется ВНУТРЬ latest_obj, где FROM — голый
domrf_kn_objects, а алиас `o` появляется только во внешнем SELECT.
Прод-EXPLAIN 13.08: `missing FROM-clause entry for table "o"`.
Ветка мёртвая — единственный вызывающий (analyze_parcel) obj_class не
передаёт, поэтому в проде это не стреляло. Стрельнуло бы тихо: исключение
глотает except в compute_velocity, функция возвращает None, и блок
velocity просто исчезает из отчёта с одной строкой в логе.
SQL вынесен в модульную константу _COMPETITORS_SQL_TMPL — чтобы
integration-тест мог прогнать EXPLAIN по ОБЕИМ подстановкам, а не только
по той, что сегодня исполняется.
Один хунк в тесте — не мой: pre-commit ruff v0.7.4 против 0.15.12 в venv (#2864).
Refs #2464
PR #1195 ввёл CTE alias `overlaps` (PG keyword для time-period operator)
→ каждый POST /analyze падал с syntax error ~10ч до hotfix PR #1197. Mock-БД
в tests/api/v1/test_*.py не поймала: MagicMock на db.execute() не парсит SQL.
Расширяет существующий phantom-column gate (tests/integration/) на
_neighbors_summary и parcel_ird_overlaps SQL:
1. _NEIGHBORS_SUMMARY_SQL вынесен из inline-text в module-level constant
в parcels.py (паттерн как в best_layouts/ird_overlay_lookup/krt_lookup).
Заодно сконвертирован на canonical CAST(:wkt AS text) — backend.md rule.
2. EXPLAIN-тесты через phantom_check_session (SSH-туннель к prod-PG) —
skip без TEST_DATABASE_URL, run в спец CI job.
3. Compile-time guards (всегда работают, без БД):
- CTE-alias не должен быть PG-keyword (overlaps/user/current_date/select/where)
- SQL не должен содержать :bind::type (psycopg v3 antipattern)
Поймал бы:
- #1195 (CTE alias overlaps) — оба EXPLAIN-тест + compile-time keyword guard
- ❌:type recurring class — compile-time parametrized guard
- Phantom column / typo PostGIS function — EXPLAIN parses + plans
Refactor backward-compatible: _neighbors_summary использует ту же константу,
behavior идентичен. 26 unit-тестов parcels analyze продолжают зелёные.
Refs #1198