gendesign/site-finder/01_load_sites.py
Light1YT 97b19a0b85 Import Site Finder app from analysis/ vibe-coding session
Adds site-finder/ subfolder with:
  - server.py — FastAPI scoring service v2 (35 endpoints, ~85KB)
  - 01_load_sites.py … 12_more_pois.py — data ingest pipeline
  - db_init.py — SQLite schema bootstrap
  - static/ — Leaflet UI (index.html ~3500 lines + sw.js)
  - cache/ — small persistent caches (admin districts, jk polygons,
    geocode warm cache, parcel polygons drop-zone with README)
  - reports/ — sample generated parcel report (HTML+JSON)

Excluded via .gitignore (regeneratable, too big for git):
  - analysis.db (336MB SQLite — rebuild via 01_*..12_*.py)
  - cache/objective_raw/ (1.2GB Объектив raw dumps)
  - cache/overpass_raw.json, cache/osm_buildings_all.geojson
    (regen from Overpass API)

Production deploy: /opt/gendesign/site-finder/ on gendsgn.ru
(container gendesign-site-finder-1, served at /sf/).
2026-05-10 22:42:25 +05:00

75 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Load sites into local DB:
- target parcel 66:41:0204016:10 (approximated coords from neighbouring quarters)
- all under-construction ЖК in Ekb from prod (via SSH tunnel)
"""
import sqlite3, psycopg2, pathlib
DB = pathlib.Path(__file__).parent / "analysis.db"
PARCEL = {
"site_id": "parcel:66:41:0204016:10",
"kind": "parcel",
"name": "Участок 66:41:0204016:10",
"address": "г. Екатеринбург, ул. Маневровая, 31А (Старая Сортировка, Железнодорожный) · кад.№ 66:41:0204016:10 · 0.28 га · кад.ст. 23.7 млн ₽",
"district": None,
"obj_class": None,
"developer": None,
"flat_count": None,
"square_living": 2788.7, # parcel area m² (Rosreestr polygon)
"ready_dt": None,
"obj_status": "parcel",
"lat": 56.878730, # polygon centroid (rosreestr2coord)
"lon": 60.522677,
"obj_id": None,
}
def main():
conn = sqlite3.connect(DB)
pg = psycopg2.connect(host="127.0.0.1", port=15432, user="gendesign",
password="2J2SBPMKuS998fiwhtQqDhMI", dbname="gendesign")
cur = pg.cursor()
# latest snapshot per obj_id
cur.execute("""
WITH latest AS (
SELECT obj_id, MAX(snapshot_date) AS d FROM domrf_kn_objects
WHERE region_cd='66' AND is_ekb=true
GROUP BY obj_id
)
SELECT o.obj_id, o.comm_name, o.addr, o.district_name, o.obj_class,
o.dev_name, o.flat_count, o.square_living::float, o.ready_dt,
o.obj_status, o.latitude::float, o.longitude::float
FROM domrf_kn_objects o
JOIN latest l USING (obj_id)
WHERE o.snapshot_date = l.d
AND o.latitude IS NOT NULL AND o.longitude IS NOT NULL
AND o.site_status='Строящиеся'
ORDER BY o.obj_id;
""")
rows = cur.fetchall()
print(f"Pulled {len(rows)} ЖК from prod")
conn.execute("DELETE FROM sites;")
conn.execute("""INSERT INTO sites(site_id,kind,name,address,district,obj_class,developer,
flat_count,square_living,ready_dt,obj_status,lat,lon,obj_id) VALUES (
:site_id,:kind,:name,:address,:district,:obj_class,:developer,
:flat_count,:square_living,:ready_dt,:obj_status,:lat,:lon,:obj_id)""", PARCEL)
for r in rows:
(obj_id, name, addr, district, klass, dev, flat_count, sq_living, ready_dt,
obj_status, lat, lon) = r
if not (50 < lat < 65 and 55 < lon < 70): # sanity for Sverdl
continue
conn.execute("""INSERT INTO sites(site_id,kind,name,address,district,obj_class,
developer,flat_count,square_living,ready_dt,obj_status,lat,lon,obj_id)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(f"jk:{obj_id}", "jk", name, addr, district, klass, dev, flat_count,
sq_living, str(ready_dt) if ready_dt else None, obj_status, lat, lon, obj_id))
conn.commit()
n = conn.execute("SELECT count(*) FROM sites").fetchone()[0]
by_kind = dict(conn.execute("SELECT kind, count(*) FROM sites GROUP BY 1").fetchall())
print(f"Sites in local DB: {n} ({by_kind})")
conn.close(); pg.close()
if __name__ == "__main__":
main()