feat(site-finder): v3.1 — cad_parcels_geom, analyze fallback, POI lat/lon, OSM expand
- nspd_geo: add _save_parcel() for thematic_id=1 → cad_parcels_geom (UPSERT, ST_Transform from Web Mercator); _persist_target now handles 1/2/5 - parcels.py: analyze endpoint geom lookup extended with cad_parcels_geom as 3rd fallback source (after cad_quarters_geom, cad_buildings); both SELECT and WKT subqueries updated - parcels.py: POI score_breakdown items now include lat/lon for map markers - poi_loader: OSM_CATEGORIES expanded — college+university→school, hypermarket→shop_supermarket; coverage +3 tag pairs
This commit is contained in:
parent
ea3d58fa7d
commit
f419900968
3 changed files with 48 additions and 4 deletions
|
|
@ -68,6 +68,12 @@ def analyze_parcel(
|
||||||
'cad_building' AS source
|
'cad_building' AS source
|
||||||
FROM cad_buildings b
|
FROM cad_buildings b
|
||||||
WHERE b.cad_num = :c
|
WHERE b.cad_num = :c
|
||||||
|
UNION ALL
|
||||||
|
SELECT ST_AsGeoJSON(p.geom) AS geom_geojson,
|
||||||
|
p.geom AS geom_wkb,
|
||||||
|
'cad_parcel' AS source
|
||||||
|
FROM cad_parcels_geom p
|
||||||
|
WHERE p.cad_num = :c
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
"""),
|
"""),
|
||||||
{"c": cad_num},
|
{"c": cad_num},
|
||||||
|
|
@ -95,6 +101,8 @@ def analyze_parcel(
|
||||||
SELECT g.geom FROM cad_quarters_geom g WHERE g.cad_number = :c
|
SELECT g.geom FROM cad_quarters_geom g WHERE g.cad_number = :c
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT b.geom FROM cad_buildings b WHERE b.cad_num = :c
|
SELECT b.geom FROM cad_buildings b WHERE b.cad_num = :c
|
||||||
|
UNION ALL
|
||||||
|
SELECT p.geom FROM cad_parcels_geom p WHERE p.cad_num = :c
|
||||||
) g
|
) g
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
"""),
|
"""),
|
||||||
|
|
@ -171,6 +179,8 @@ def analyze_parcel(
|
||||||
{
|
{
|
||||||
"name": p["name"],
|
"name": p["name"],
|
||||||
"distance_m": round(float(p["distance_m"])),
|
"distance_m": round(float(p["distance_m"])),
|
||||||
|
"lat": float(p["lat"]) if p["lat"] is not None else None,
|
||||||
|
"lon": float(p["lon"]) if p["lon"] is not None else None,
|
||||||
"last_edit": (
|
"last_edit": (
|
||||||
p["last_osm_edit_date"].isoformat() if p["last_osm_edit_date"] else None
|
p["last_osm_edit_date"].isoformat() if p["last_osm_edit_date"] else None
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -21,20 +21,23 @@ EKB_BBOX = (56.7, 60.5, 56.95, 60.75) # (south, west, north, east)
|
||||||
|
|
||||||
# Маппинг OSM-тег → нормализованная category
|
# Маппинг OSM-тег → нормализованная category
|
||||||
OSM_CATEGORIES: dict[tuple[str, str], str] = {
|
OSM_CATEGORIES: dict[tuple[str, str], str] = {
|
||||||
# amenity tags
|
# amenity tags — школы расширены (school/college/university)
|
||||||
("amenity", "school"): "school",
|
("amenity", "school"): "school",
|
||||||
|
("amenity", "college"): "school",
|
||||||
|
("amenity", "university"): "school",
|
||||||
("amenity", "kindergarten"): "kindergarten",
|
("amenity", "kindergarten"): "kindergarten",
|
||||||
("amenity", "pharmacy"): "pharmacy",
|
("amenity", "pharmacy"): "pharmacy",
|
||||||
("amenity", "hospital"): "hospital",
|
("amenity", "hospital"): "hospital",
|
||||||
("amenity", "clinic"): "hospital",
|
("amenity", "clinic"): "hospital",
|
||||||
# shop tags
|
# shop tags — supermarket расширен
|
||||||
("shop", "mall"): "shop_mall",
|
("shop", "mall"): "shop_mall",
|
||||||
("shop", "supermarket"): "shop_supermarket",
|
("shop", "supermarket"): "shop_supermarket",
|
||||||
|
("shop", "hypermarket"): "shop_supermarket",
|
||||||
("shop", "convenience"): "shop_small",
|
("shop", "convenience"): "shop_small",
|
||||||
("shop", "bakery"): "shop_small",
|
("shop", "bakery"): "shop_small",
|
||||||
# leisure
|
# leisure
|
||||||
("leisure", "park"): "park",
|
("leisure", "park"): "park",
|
||||||
# transit (railway tram_stop приоритет над public_transport)
|
# transit
|
||||||
("railway", "tram_stop"): "tram_stop",
|
("railway", "tram_stop"): "tram_stop",
|
||||||
("highway", "bus_stop"): "bus_stop",
|
("highway", "bus_stop"): "bus_stop",
|
||||||
# метро (одна линия в ЕКБ, но добавляем для полноты)
|
# метро (одна линия в ЕКБ, но добавляем для полноты)
|
||||||
|
|
|
||||||
|
|
@ -207,13 +207,44 @@ def _save_building(db: Session, payload: dict, cad_num: str) -> int:
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
def _save_parcel(db: Session, payload: dict, cad_num: str) -> int:
|
||||||
|
"""Парсит NSPD-response для земельного участка и UPSERT'ит в cad_parcels_geom."""
|
||||||
|
feats = (payload.get("data") or {}).get("features") or []
|
||||||
|
if not feats:
|
||||||
|
return 0
|
||||||
|
f = feats[0]
|
||||||
|
geom_geojson = f.get("geometry")
|
||||||
|
if not geom_geojson:
|
||||||
|
return 0
|
||||||
|
db.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
INSERT INTO cad_parcels_geom (cad_num, geom, raw_props, fetched_at)
|
||||||
|
VALUES (:cad, ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON(:g), 3857), 4326),
|
||||||
|
CAST(:props AS jsonb), NOW())
|
||||||
|
ON CONFLICT (cad_num) DO UPDATE
|
||||||
|
SET geom = EXCLUDED.geom,
|
||||||
|
raw_props = EXCLUDED.raw_props,
|
||||||
|
fetched_at = NOW()
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"cad": cad_num,
|
||||||
|
"g": json.dumps(geom_geojson, ensure_ascii=False),
|
||||||
|
"props": json.dumps(f.get("properties") or {}, ensure_ascii=False),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def _persist_target(db: Session, thematic_id: int, payload: dict, cad_num: str) -> tuple[int, str]:
|
def _persist_target(db: Session, thematic_id: int, payload: dict, cad_num: str) -> tuple[int, str]:
|
||||||
"""Returns (n_features_saved, table_name). Полиморфно по thematic_id."""
|
"""Returns (n_features_saved, table_name). Полиморфно по thematic_id."""
|
||||||
|
if thematic_id == 1:
|
||||||
|
return _save_parcel(db, payload, cad_num), "cad_parcels_geom"
|
||||||
if thematic_id == 2:
|
if thematic_id == 2:
|
||||||
return _save_quarter(db, payload, cad_num), "cad_quarters_geom"
|
return _save_quarter(db, payload, cad_num), "cad_quarters_geom"
|
||||||
if thematic_id == 5:
|
if thematic_id == 5:
|
||||||
return _save_building(db, payload, cad_num), "cad_buildings"
|
return _save_building(db, payload, cad_num), "cad_buildings"
|
||||||
# Для parcel (1) пока нет нашей normalized-таблицы — только raw в targets.result_features
|
|
||||||
return 0, ""
|
return 0, ""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue