Commit graph

316 commits

Author SHA1 Message Date
15e75c20dc Merge pull request 'fix(sf-01): time_window honest velocity — inline SQL с реальным фильтром report_month' (#275) from fix/sf-01-velocity-time-window into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m33s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m38s
Deploy / deploy (push) Successful in 55s
2026-05-17 09:24:57 +00:00
lekss361
c701636515 Merge forgejo/main into fix/sf-01-velocity-time-window
Resolve conflict in best_layouts.py: keep SF-01 inline velocity
(deals_window numerator) + add SF-05 sold_pct clamp at 100% and
is_oversold flag from main.
2026-05-17 12:15:40 +03:00
lekss361
5a4408e0ab fix(sf-01): time_window honest velocity — inline SQL с реальным фильтром report_month
Раньше _VELOCITY_DIVISORS делил агрегаты mv_layout_velocity (24 мес)
на 4/12 для quarter/year, не меняя реальное окно данных. Теперь
inline SQL из objective_corpus_room_month с CAST(:window_interval AS interval).

velocity_per_month = deals_window / months_in_window (1.0/3.0/12.0).
Разные time_window → разные строки из БД → разный mix/velocity/jk_count.

Closes (epic part) #271 item 1
2026-05-17 12:12:19 +03:00
ac1da79946 Merge pull request 'fix(sf-05): clamp sold_pct_of_supply на 100% + is_oversold flag' (#274) from fix/sf-05-sold-pct-normalize into main
All checks were successful
Deploy / changes (push) Successful in 4s
Deploy / build-backend (push) Successful in 1m32s
Deploy / build-worker (push) Successful in 2m39s
Deploy / build-frontend (push) Successful in 3m3s
Deploy / deploy (push) Successful in 55s
2026-05-17 09:04:21 +00:00
9372af4494 Merge pull request 'fix(sf-04): seed PRINZIP project mappings в objective_complex_mapping (Малевич + Ньютон парк)' (#273) from fix/sf-04-malevich-mapping into main
Some checks failed
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / build-backend (push) Blocked by required conditions
Deploy / changes (push) Has been cancelled
2026-05-17 09:04:18 +00:00
lekss361
247c252b04 fix(sf-05): clamp sold_pct_of_supply at 100% + add is_oversold flag
Symptom: sold_pct_of_supply = sum_deals_24mo / supply_count_snapshot * 100
yields >100% (e.g. 199%) for fast-selling small formats due to incompatible
time windows (24-month deals vs point-in-time supply snapshot).

Option A (hotfix): clamp to 100.0, expose is_oversold=True for UI badge.

- best_layouts.py: compute sold_pct_raw, clamp with min(..., 100.0),
  set is_oversold = raw > 100%
- parcel.py TopLayoutRow: add is_oversold: bool field
- best-layouts.ts TopLayoutRow: add is_oversold: boolean
- BestLayoutsBlock.tsx: show warn badge ">100%" when is_oversold=True
- tests: two new cases — raw 199% clamps + is_oversold=True;
  raw 50% passes through + is_oversold=False

Closes (epic part) #271 item 5
2026-05-17 11:57:29 +03:00
9127cf2060 Merge pull request 'fix(sf-06): MarketTab reorder — Competitors → BestLayouts → Velocity первыми' (#272) from fix/sf-06-market-tab-reorder into main
All checks were successful
Deploy / changes (push) Successful in 4s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m19s
Deploy / deploy (push) Successful in 49s
2026-05-17 08:57:18 +00:00
lekss361
31b13c18b1 fix(sf-04): seed PRINZIP project mappings в objective_complex_mapping
Audit 2026-05-17: objective_corpus_room_month содержал активные сделки
для Малевич (98/12m) и Ньютон парк (30/12m), но записей в
objective_complex_mapping не было → mv_layout_velocity пропускал оба
проекта в velocity calculations.

Добавлены маппинги:
- Малевич → domrf_obj_id 64701 (Строящиеся, 244кв, сдача 2027-09)
- Ньютон парк → domrf_obj_id 47390 (Сданные, 411кв, крупнейший corpus)

ON CONFLICT DO NOTHING — идемпотентно. REFRESH mv_layout_velocity
выполняется вручную post-deploy (runbooks/Refresh_MV_Layout_Velocity.md).

Closes (epic part) #271 item 4
2026-05-17 11:57:06 +03:00
lekss361
81bf798c27 fix(sf-06): MarketTab reorder — Competitors → BestLayouts → Velocity первыми 2026-05-17 11:52:45 +03:00
a4247e1d96 Merge pull request 'chore(preview): remove basic auth от /preview/*' (#270) from chore/preview-remove-auth into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 31s
Deploy / build-worker (push) Successful in 30s
Deploy / build-frontend (push) Successful in 25s
Deploy / deploy (push) Successful in 47s
2026-05-17 08:39:28 +00:00
lekss361
7a2ba4d35a chore(preview): remove basic auth от /preview/* — публичный доступ 2026-05-17 11:37:41 +03:00
744faa1f80 Merge pull request 'fix(deploy): force-recreate caddy для подхвата новых compose mounts' (#269) from fix/deploy-caddy-force-recreate into main
All checks were successful
Deploy / build-backend (push) Successful in 30s
Deploy / changes (push) Successful in 6s
Deploy / build-worker (push) Successful in 31s
Deploy / build-frontend (push) Successful in 26s
Deploy / deploy (push) Successful in 1m5s
2026-05-17 08:33:15 +00:00
lekss361
11e78d738e fix(deploy): force-recreate caddy чтобы новые compose mounts применились
Раньше deploy.yml делал `docker compose exec caddy caddy reload` — это
только reload Caddyfile, но НЕ пересоздаёт container. Когда в
docker-compose.prod.yml добавлялся новый volume mount (например
./preview:/srv/preview:ro в PR #268), он не появлялся в running container
до manual `docker compose up -d --force-recreate caddy` через SSH.

Теперь deploy всегда вызывает force-recreate caddy после image pull —
идемпотентно, +~5 сек к deploy time, гарантирует что новые mounts +
Caddyfile блоки активны.
2026-05-17 11:30:45 +03:00
fa76e6ac9f Merge pull request 'feat(preview): static HTML mockups на /preview/* (basic auth)' (#268) from feat/preview-mockups-static into main
Some checks failed
Deploy / changes (push) Successful in 5s
Deploy / build-worker (push) Failing after 16s
Deploy / build-backend (push) Successful in 33s
Deploy / build-frontend (push) Successful in 25s
Deploy / deploy (push) Has been skipped
2026-05-17 08:19:24 +00:00
lekss361
f1bc821c2b feat(preview): add static mockups behind basic auth at /preview/*
4 HTML mockups (landing/site-finder/analytics/monitoring) — alternative
designs для audit-review без замены существующих React-страниц.

- Caddy file_server на handle_path /preview/* (внутри gendsgn.ru block)
- Bind mount ./preview:/srv/preview:ro в caddy container
- basic auth: user=preview / pwd shared via private channel
- Static, isolated — не влияет на /, /site-finder, /analytics (Next.js routes)
2026-05-17 11:15:13 +03:00
ee0bad445d Merge pull request 'fix(#231): re-enable nspd-harvest-stale-quarters beat schedule (Sub-PR D)' (#267) from fix/231-nspd-beat-reenable into main
All checks were successful
Deploy / build-worker (push) Successful in 2m38s
Deploy / deploy (push) Successful in 47s
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m33s
Deploy / build-frontend (push) Has been skipped
2026-05-17 07:50:34 +00:00
f160c5efaf Merge pull request 'feat(#263 sub-A): cad_* overlay schemas + cadastre territorial_zones (ПЗЗ)' (#265) from feat/263-sub-a-cad-nspd-overlay-schemas-and-pzz into main
Some checks failed
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 6s
Deploy / build-worker (push) Has been cancelled
Deploy / build-backend (push) Has been cancelled
2026-05-17 07:50:08 +00:00
lekss361
8e377059a4 fix(#265 review): CRS 3857→4326 + stable zone_id hash + rename 102 + tx comment
- #1 (BLOCK): ST_GeogFromGeoJSON → ST_Transform(ST_SetSRID(..., 3857), 4326)::geography
  so EPSG:3857 features from get_territorial_zones_in_bbox are correctly
  stored in GEOGRAPHY(POLYGON, 4326) column (matches cad_parcels/cad_buildings pattern)
- #2: zone_id fallback changed from counter-based f"{quarter_cad}_{inserted}"
  to md5(json(props))[:12] — stable between runs, prevents wrong-row ON CONFLICT
- #3: rename 101_cad_nspd_overlay_tables.sql → 102_* (101_user_custom_pois.sql exists)
- #4: add comment clarifying SQLAlchemy autobegin guarantees outer tx before begin_nested()
- Tests: updated test_feature_without_zone_id_uses_fallback to assert md5 format + idempotency
2026-05-17 10:45:52 +03:00
9bac676b98 Merge pull request 'fix(#264): apiFetch — global X-Session-Id header (custom POI scoring в UI)' (#266) from fix/264-session-id-global-apifetch into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 2m51s
Deploy / deploy (push) Successful in 38s
2026-05-17 07:42:00 +00:00
lekss361
d4da1b26c6 fix(#231): re-enable nspd-harvest-stale-quarters beat schedule (Sub-PR D)
После Sub-PR B (#260, merged 8ebcd15) переключения search_by_quarter на
grid-walk dispatch — beat schedule безопасно re-enable'ить:

- harvest_quarter возвращает полноценные dumps (territorial_zones + ЗОУИТ +
  risk zones + engineering structures) вместо 0-3 features
- per-instance semaphore (PR #260) защищает от cross-loop binding bug
- soft/hard time_limit 600/900s — задача успевает завершиться

Schedule: Mon 01:00 UTC (04:00 МСК), batch_size=50 ограничивает WAF burst,
max_age_days=90 для регулярного refresh + новые quarters (fetched_at IS NULL).

Полный ЕКБ back-fill (11k+ quarters в cad_quarters_geom) займёт несколько
недель при batch_size=50/week; в production будет ускорено manual trigger
admin endpoint'ом если нужно.

Closes #231.
2026-05-17 10:37:07 +03:00
lekss361
2d58c3a203 fix(#264): apiFetch — global X-Session-Id header (custom POI scoring в UI)
apiFetch и apiFetchWithStatus не передавали X-Session-Id header, из-за
чего POST /analyze возвращал custom_poi_score_items: [] и score без учёта
пользовательских POI. Добавлен withSessionHeader() helper с SSR guard;
user-supplied headers имеют приоритет (backward compat).
2026-05-17 10:31:54 +03:00
lekss361
f08db43145 feat(#263 sub-A): cad_* overlay schemas + cadastre territorial_zones (ПЗЗ)
Phase 1 (migration 101): создаёт 5 новых таблиц cad_territorial_zones,
cad_red_lines, cad_engineering_structures, cad_risk_zones,
cad_opportunity_parcels. Idempotent (IF NOT EXISTS), GIST-индексы.

Phase 2 partial (ПЗЗ only):
- NSPDBulkClient.get_features_in_bbox_grid — async grid-walk WMS GetFeatureInfo
- NSPDBulkClient.get_territorial_zones_in_bbox — convenience wrapper layer 875838
- harvest_quarter Phase 2.5 — вызов get_territorial_zones_in_bbox после grid-walk
- _save_territorial_zones — UPSERT loop с SAVEPOINT per-row, ON CONFLICT zone_id

9 unit tests (mock DB): insert/upsert/no-geom/no-id/error-isolation/savepoint pattern.
2026-05-17 10:28:22 +03:00
26ee3b015c Merge pull request 'fix(#261): custom_pois — regression tests для db.commit() в create/update/delete (P0 persistence)' (#262) from fix/261-custom-pois-missing-commit into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-worker (push) Successful in 2m23s
Deploy / deploy (push) Successful in 49s
Deploy / build-backend (push) Successful in 27s
Deploy / build-frontend (push) Has been skipped
2026-05-17 07:26:54 +00:00
8ebcd15ed6 Merge pull request 'fix(#231): switch search_by_quarter area layers to grid-walk (Sub-PR B)' (#260) from fix/231-nspd-search-by-quarter-grid into main
Some checks failed
Deploy / build-worker (push) Has been cancelled
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been cancelled
2026-05-17 07:26:08 +00:00
lekss361
570c987e78 fix(#231): repair test_rate_limit_semaphore regression + docstring sync
Re-review feedback (PR #260 head 8191a85):

1. tests/scrapers/test_nspd_bulk_client.py:360 — test referenced bulk_mod._SEMAPHORE
   which was renamed to _SEMAPHORE_LIMIT in previous commit. AttributeError на
   полном pytest run. Заменено: assert bulk_mod._SEMAPHORE_LIMIT == 3 (smoke).
   max_concurrent <= 3 уже валидирует throttling — capacity check был лишним.

2. app/services/scrapers/nspd_client.py docstrings (lines 483, 506) — обновлены
   ссылки NSPDBulkClient._SEMAPHORE(3) → NSPDBulkClient._sem (per-instance,
   capacity=3) чтобы соответствовать новой архитектуре.
2026-05-17 10:22:16 +03:00
lekss361
c94545f0eb fix(#261): add db.commit() regression tests для custom_pois service
Проблема: POST /custom-pois возвращал 201 с id=N, но row не сохранялась
в БД — user_custom_pois оставалась пустой после request close.

Симптом возникает если service-функция не вызывает db.commit() явно:
get_db.finally → db.close() без commit → psycopg3 rollbacks pending tx.

Код (#257) содержит db.commit() во всех 3 мутациях, но тесты полностью
мокировали service layer через patch() — regression в commit не ловился.

Fix: добавлены 6 service-level тестов с паттерном "два сеанса":
- MagicMock(db) + db.commit.assert_called_once() для create/update/delete
- db.commit.assert_not_called() для not-found и empty-payload случаев

Closes #261
2026-05-17 10:16:36 +03:00
lekss361
8191a85102 fix(#231): address re-review blockers — per-instance semaphore + time_limit + test mocks
PR #260 second-opinion review 🔴 BLOCK → fixes:

1. nspd_bulk_client.py: _SEMAPHORE module-level → per-instance self._sem
   создаётся в __aenter__ под текущий event loop. Sequential asyncio.run()
   в search_by_quarter (11 layers × asyncio.run() per layer) bind'ил
   module-level semaphore к first loop'у → RuntimeError на втором вызове →
   все cells return_exceptions=True проглатывали → grid возвращал 0 features.

2. nspd_sync.py: harvest_quarter добавлен time_limit=900 (hard) рядом с
   soft_time_limit=600. Safety net если task игнорит SoftTimeLimitExceeded.

3. tests/test_nspd_client.py: добавлен _make_fake_grid_walk helper +
   monkeypatch NSPDClient.get_features_in_bbox_grid в 3 тестах
   (search_by_quarter_core_only / _with_zouit / _layers_fetched_with_risks).
   Без этого area layers били бы по живому NSPD API → CI flaky.

Tests: 37 passed, 1 skipped (3 area-dispatch + 34 grid-walk).
2026-05-17 10:13:34 +03:00
e3eb8c873f Merge pull request 'feat(#254,#114): custom POI UI (Leaflet click-to-add) + score breakdown stacked-bar' (#258) from feat/254-custom-pois-ui into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m2s
Deploy / deploy (push) Successful in 39s
2026-05-17 06:47:08 +00:00
lekss361
fdced94626 fix(#231): switch search_by_quarter area layers to grid-walk dispatch (Sub-PR B)
- Add _GRID_WALK_LAYERS frozenset (territorial_zones, red_lines,
  engineering_structures, zouit_*, risk_*)
- _fetch_layer dispatch: area layers → get_features_in_bbox_grid(grid_n=7,
  step_m=50) [foundation из PR #247], EGRN layers (parcels, buildings) →
  legacy get_features_in_bbox
- Enrich engineering_structures features с classified_kind через
  classify_engineering_kind (PR #247 classifier)
- Per-layer logger.info heartbeat (method + count) для прогресса
- Bump harvest_quarter soft_time_limit 120 → 600s
  (grid-walk: 11 layers × 49 cells × ~70ms ≈ 40s + retries margin)
- TestFetchLayerDispatch (3 tests): 34 passed, 1 skipped

Pre-push deep-code-reviewer: APPROVE (1 medium nit — opportunity_oopt не в
grid-walk, защищено через include_opportunity=False default; follow-up issue
рекомендован).
2026-05-17 09:46:42 +03:00
8cc94472f7 Merge pull request 'feat(#254): user_custom_pois — backend schema + CRUD + scoring integration' (#257) from feat/254-custom-pois-backend into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m40s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m49s
Deploy / deploy (push) Successful in 50s
2026-05-17 06:42:29 +00:00
9dd72ece0c Merge pull request 'fix(#234): NSPD harvest ETA badge + SETNX dedupe (Sub-PR C)' (#250) from fix/234-nspd-harvest-eta-badge into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m42s
Deploy / build-worker (push) Successful in 2m44s
Deploy / build-frontend (push) Successful in 3m4s
Deploy / deploy (push) Successful in 55s
2026-05-17 06:35:40 +00:00
lekss361
6ecd7a9269 feat(#254,#114): custom POI UI + score breakdown stacked-bar viz
Task A (#254): Leaflet click-to-add custom POIs
- sessionId.ts: getOrCreateSessionId() from localStorage
- types/customPoi.ts: CustomPoi, CustomPoiCreate, CustomPoiUpdate
- hooks/useCustomPois.ts: useCustomPois / useAddCustomPoi / useUpdateCustomPoi / useDeleteCustomPoi (TanStack Query, X-Session-Id header)
- CustomPoiLayer.tsx: LayerGroup with CircleMarker (green/red/gray by weight), popup with edit/delete
- CustomPoiToggleButton.tsx: crosshair mode toggle button
- CustomPoiAddModal.tsx: modal form (name, 26 categories, weight -5..+5, notes)
- CustomPoiEditModal.tsx: same form for editing existing POI
- SiteMap.tsx: MapClickHandler (useMapEvents), integrated all custom POI components
- page.tsx: useCustomPois hook + passes customPois + parcelCad to SiteMap

Task B (#114): ScoreBreakdownStackedBar
- ScoreBreakdownStackedBar.tsx: horizontal stacked bar by category with positive/negative split, toggle between category and factor views, custom POIs highlighted orange with dashed outline
- OverviewTab.tsx: renders ScoreBreakdownStackedBar below ScoreBreakdownPanel

Requires backend PR with /api/v1/custom-pois endpoints merged before POI CRUD works.
2026-05-17 09:35:35 +03:00
lekss361
485f489bd2 feat(#254): user_custom_pois — backend schema + CRUD + scoring integration
- SQL migration 101_user_custom_pois.sql: table with GEOGRAPHY(POINT) + GIST index
- Pydantic schemas: CustomPoiCreate / CustomPoiUpdate / CustomPoiOut
- Service custom_pois.py: CRUD + get_overlaps_for_scoring (1km radius, psycopg v3 CAST)
- API /api/v1/custom-pois: POST(201)/GET/PATCH/DELETE, X-Session-Id header auth
- Scoring in analyze_parcel: custom POI block after OSM POI loop,
  decay weight * max(0, 1 - dist/1000), custom_poi_score_items in response
- Tests: CRUD + validation (weight/lon/lat 422) + scoring integration (mock-based)
- Vault: Schema_User_Custom_Pois.md + Endpoint_Parcel_Analyze v3.9 section
2026-05-17 09:34:33 +03:00
lekss361
81cf274e80 fix(#234): address re-review nits — Settings singleton + docstring sync
1. _acquire_harvest_lock: Settings() instance → singleton 'settings' import
   from app.core.config — соответствует convention (scrape_kn.py, objective.py,
   celery_app.py все используют singleton).
2. _trigger_harvest docstring: убрана incorrect фраза «остальные ... видят
   harvest_eta_seconds от первого запроса» (код возвращает eta=None для
   denied-lock callers). Заменена на actual behavior: graceful no-data UI
   с ПКК fallback на FE.

PR #250 re-review followup (issues #3 + docstring sync).
2026-05-17 09:31:43 +03:00
lekss361
b99ac76463 Merge remote-tracking branch 'forgejo/main' into fix/234-nspd-harvest-eta-badge
# Conflicts:
#	backend/app/services/site_finder/quarter_dump_lookup.py
2026-05-17 09:19:12 +03:00
lekss361
990c109756 fix(#234): NSPD harvest ETA badge + SETNX dedupe (Sub-PR C)
Backend (quarter_dump_lookup.py):
- _acquire_harvest_lock: Redis SETNX TTL=120s на quarter, защищает от burst
  N concurrent analyze, ставящих N одинаковых harvest task в очередь
- _trigger_harvest: использует lock перед apply_async, возвращает False если
  lock уже взят (другой запрос триггернул раньше)
- make_empty_result/EMPTY_DUMP_RESULT: новое поле harvest_eta_seconds в
  nspd_dump dict, типичный harvest_quarter = 60с
- /analyze: пробрасывает поле через nspd_dump dict (нет typed schema —
  response_model=None для /analyze endpoint, dict уходит как есть)

Frontend (NspdFreshnessBadge, NspdZoningBlock):
- Countdown «НСПД: загрузка ~Nс» вместо бесконечного спиннера
- После остановки countdown (remaining=0) NspdZoningBlock показывает
  «загрузка дольше обычного» + ссылку на ПКК вместо infinite skeleton

Tests: 5 новых unit + 2 для empty_result schema (всего +7, pass)

Closes #234 (UX-side; data-side resolves когда Sub-PR B + D merged).
2026-05-17 09:13:06 +03:00
10b79da2e9 fix(#243): cad_zouit fallback срабатывает когда nspd dump полностью отсутствует (#248)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / deploy (push) Successful in 46s
Deploy / build-backend (push) Successful in 1m36s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m44s
2026-05-17 06:03:19 +00:00
95e97da713 Merge pull request 'fix(#242): ekburg_permits SSL — verify=False для CA Минцифры РФ (clean re-roll PR #245)' (#249) from fix/242-ekburg-permits-ssl-clean into main
Some checks failed
Deploy / changes (push) Successful in 5s
Deploy / deploy (push) Blocked by required conditions
Deploy / build-backend (push) Successful in 1m34s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Has been cancelled
2026-05-17 06:01:22 +00:00
lekss361
4a48d49474 fix(#242): ekburg_permits SSL — verify=False для CA Минцифры РФ 2026-05-17 08:57:42 +03:00
4519daf149 feat(#126): Sub-PR A — NSPDClient.get_features_in_bbox_grid + engineering classifier (#247)
All checks were successful
Deploy / build-backend (push) Successful in 1m23s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy (push) Successful in 48s
Deploy / changes (push) Successful in 5s
Deploy / build-worker (push) Successful in 2m18s
2026-05-17 05:54:30 +00:00
992477c8f8 fix(#244): harvest_quarter — CAST CASE WHEN params (psycopg3 AmbiguousParameter) (#246)
Some checks failed
Deploy / build-backend (push) Has been cancelled
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 5s
Deploy / build-worker (push) Has been cancelled
2026-05-17 05:53:08 +00:00
6f565c19fa Merge pull request 'fix(#233): add pzz_sync to beat schedule (monthly)' (#241) from fix/issue233-pzz-sync-beat into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m38s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m40s
Deploy / deploy (push) Successful in 44s
2026-05-17 05:32:52 +00:00
lekss361
232dbc9285 fix(#233): add pzz_sync to beat schedule (monthly)
PKK6 ArcGIS sync для pzz_zones_ekb (0 rows live) — задача и admin endpoint
уже существуют, но beat schedule отсутствовал. Schedule: 1st of month, 03:00 МСК.

Closes #233
2026-05-17 08:29:40 +03:00
7d0bc4bb6b Merge pull request 'feat(#29,#232): wire cad_parcels.permitted_use в analyze + cad_zouit fallback (G2+G3)' (#240) from feat/g2-g3-analyze-parcel-meta-zouit-fallback into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m29s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m26s
Deploy / deploy (push) Successful in 48s
2026-05-17 05:26:02 +00:00
b8a15cac09 Merge pull request 'feat(#104): admin endpoint POST /admin/scrape/ekburg-permits для manual trigger' (#239) from feat/104-ekburg-permits-admin-endpoint into main
Some checks failed
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Failing after 17s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m31s
Deploy / deploy (push) Has been skipped
2026-05-17 05:21:16 +00:00
lekss361
f70be684da feat(#29,#232): wire cad_parcels.permitted_use in analyze + cad_zouit fallback (G2+G3)
TASK A (#29 G2): add parcel_meta to analyze response
- New ParcelMeta Pydantic schema in app/schemas/parcel.py
- SELECT from cad_parcels WHERE cad_num=:c in analyze_parcel() (step 9f)
- Returns permitted_use_established_by_document, land_record_category_type,
  land_record_subtype, cost_value; None when row absent
- Tests: test_analyze_parcel_meta.py (found + not-found cases)

TASK B (#232 G3): cad_zouit fallback in _get_zouit_overlaps
- When nspd_quarter_dumps has zouit_count==0, fall back to ST_Intersects
  query on cad_zouit (3483 rows, GIST indexed)
- Overlaps tagged with source='cad_zouit'; format compatible with NSPD path
- gate_verdict.py: BLOCKER_TYPE_ZONE_KEYWORDS tuple for keyword-based
  classification (охранная зона / трубопровод / электр / газ -> blocker;
  СЗЗ -> warning); NSPD subcategory path preserved backward-compat
- Tests: 6 new test cases in test_gate_verdict.py covering cad_zouit path
  and backward-compat for NSPD subcategory path

Updated db.execute call sequence in test_analyze_*.py (index shift +1 at pos 10).
2026-05-17 08:17:22 +03:00
lekss361
0135f3c7c4 feat(#104): add POST /admin/scrape/ekburg-permits for manual trigger
Wraps refresh_all / refresh_year Celery tasks behind the existing
AdminTokenAuth gate so the table can be populated on demand after
first deploy instead of waiting for the monthly beat (1st of month).

- TriggerEkburgPermitsRequest: year int|None, ge=2022 le=2030
- year=None  -> refresh_all.apply_async()  (scope all_years_2022_2026)
- year=N     -> refresh_year.apply_async(args=[N])  (scope year_N)
- 4 smoke tests: all/year/invalid_year/no_token
2026-05-17 08:13:32 +03:00
56bf28758c Merge pull request 'feat(#115): Leaflet layer toggle для connection points (Макс KILLER)' (#230) from feat/connection-points-leaflet-layer into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m29s
Deploy / build-worker (push) Successful in 3m30s
Deploy / build-frontend (push) Successful in 3m6s
Deploy / deploy (push) Successful in 45s
2026-05-16 20:00:55 +00:00
e7a5ba8f81 Merge pull request 'fix(#112): competitors avg_price_per_m2 всегда None — убран WHERE status='sold'' (#228) from fix/issue112-sold-filter-avg-price into main
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m39s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m42s
Deploy / deploy (push) Successful in 45s
2026-05-16 19:52:44 +00:00
lekss361
a7671513c9 feat(#115): Leaflet layer toggle для connection points (Макс KILLER)
- ConnectionPointsLayer.tsx — per-category CircleMarker слой внутри MapContainer
  (electricity/gas/water/heat/sewage/telecom/other, классификация по keywords)
- CpLayerControlPanel.tsx — toggle panel под картой: checkbox per-category + count,
  toggle-all, summary badges (ближайший, охранная зона)
- SiteMap.tsx — принимает connectionPoints?: ConnectionPointsResponse,
  управляет visibleCategories state, рендерит оба новых компонента
- page.tsx — useConnectionPoints(data?.cad_num) -> передаёт в SiteMap
- Empty-state: "0 точек подключения" при dump_available=false или пустом ответе
2026-05-16 22:51:12 +03:00