Прод-verify #2113 вскрыл: avg_price_per_m2_rub и avg_area_pd приходят с бэкенда
СТРОКОЙ («160345», «47.5») — Pydantic v2 сериализует Decimal (ROUND(::numeric))
в строку. Гварды `typeof === "number"` / `Number.isFinite(str)` молча роняли поля:
цена конкурента вообще не показывалась, а добавленная в #2113 «Ср. площадь кв.»
не рендерилась.
- toFiniteNumber(v) — приводит number|string→number|null.
- formatPrice использует его (цена снова показывается).
- avg_area_pd рендерится через toFiniteNumber.
- Типы avg_price_per_m2_rub/avg_area_pd расширены до number|string|null (честно
отражает рантайм).
Frontend-only, контракт не меняется.
Render the §12.1-13 geometry already exposed on /analyze but previously
dropped by the MiniMap adapter:
- Перспективные ЗУ (nspd_opportunity_parcels.geom_wkt) — viz-3 polygons
- Красные линии застройки (nspd_red_lines.geom_wkt) — warn dashed lines
Wired as toggles in the existing CpLayerControlPanel (Рынок group),
reusing wkt.ts (extended with LINESTRING/MULTILINESTRING for red lines).
Empty/invalid geometry renders nothing gracefully; popups RU plain-text.
§22 forecast (future_market/special_indices), ППТ-ПМТ planning polygons
and future-ЖК points carry no map-able geometry on the frontend yet — left
as backend follow-ups, not faked.
Refs #958
Add Конкуренты / Будущие проекты / Зоны риска Leaflet layers to the Site Finder
analysis map (MiniMap→SiteMap) with per-layer toggles in CpLayerControlPanel
(Конкуренты on by default; pipeline + risk off). Competitors/pipeline render as
distinct CircleMarkers from analyze lat/lon (#1074); risk zones as GeoJSON
polygons parsed from geom_wkt via a small inline WKT parser (no new deps).
Graceful on missing coords / empty arrays / thin responses. Coordinate order
verified (GeoJSON lon-lat for <GeoJSON>, Leaflet lat-lon for CircleMarker).
3 of 4 layers; future-ЖК (newbuilding) pending newbuilding_listings API exposure
(#1108 built the table). Part of #999.