* fix(velocity): :window_interval::interval cast syntax — same bug as PR #152 SQLAlchemy text() parser confuses ':window_interval' (named param) followed by '::interval' (cast operator) — exactly как было в PR #152 с :weights::jsonb. Result: psycopg видит literal ':window_interval' в SQL → syntax error → exception caught by velocity catch → return None → UI shows null. ## Fix ':window_interval::interval' → 'CAST(:window_interval AS interval)' (2 мест: sales_rows query + _get_ekb_median percentile). Pre-push code-reviewer должен был catch — добавим в feedback что для каждого text() SQL grep ':[a-z]*::' before push. Refs: PR #158 deploy verify, e2e velocity = null root cause * feat(velocity): add per-room-bucket breakdown to VelocityResult Add third SQL query (bucket_rows) aggregating deals_total_count by room_bucket ('студия','1','2','3','4+') across mapped competitors. New fields: - VelocityResult.by_room_bucket: aggregate {units, sqm, complexes_count} - sample_competitors[].by_room_bucket: {bucket: units} Bucket query wrapped в SAVEPOINT — failure degrades gracefully к empty dict без abort outer tx. CAST(:window_interval AS interval) pattern per PR #160 lesson. Test coverage: 3 new tests (aggregation, empty fallback, sample entries). Closes part of #161 --------- Co-authored-by: lekss361 <claudestars@proton.me> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| test_admin_weight_profiles.py | ||
| test_concepts_stub.py | ||
| test_gate_verdict.py | ||
| test_health.py | ||
| test_nspd_client.py | ||
| test_nspd_sync.py | ||
| test_quarter_dump_lookup.py | ||
| test_velocity.py | ||
| test_weight_profiles.py | ||