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/).
87 lines
2.9 KiB
Python
87 lines
2.9 KiB
Python
"""Local SQLite DB for parcel scoring analysis.
|
|
|
|
Mirrors the logic the prod gendesign server uses:
|
|
- domrf_kn_objects (subset: under-construction ЖК in Ekb with coords)
|
|
- domrf_kn_infrastructure (POI cache)
|
|
- ekb_districts (geometry, median price)
|
|
- recommend_mix output (per-parcel score)
|
|
|
|
We don't need the full ~3.2GB warehouse — only what the scorer touches.
|
|
"""
|
|
import sqlite3, pathlib
|
|
|
|
DB = pathlib.Path(__file__).parent / "analysis.db"
|
|
|
|
SCHEMA = """
|
|
CREATE TABLE IF NOT EXISTS sites (
|
|
site_id TEXT PRIMARY KEY, -- 'parcel:66:41:0204016:10' or 'obj:NNN'
|
|
kind TEXT NOT NULL, -- 'parcel' | 'jk'
|
|
name TEXT,
|
|
address TEXT,
|
|
district TEXT,
|
|
obj_class TEXT,
|
|
developer TEXT,
|
|
flat_count INTEGER,
|
|
square_living REAL,
|
|
ready_dt TEXT,
|
|
obj_status TEXT,
|
|
lat REAL NOT NULL,
|
|
lon REAL NOT NULL,
|
|
geom_geojson TEXT, -- nullable polygon as raw GeoJSON
|
|
obj_id INTEGER -- domrf_kn obj_id when kind='jk'
|
|
);
|
|
CREATE INDEX IF NOT EXISTS sites_kind_idx ON sites(kind);
|
|
CREATE INDEX IF NOT EXISTS sites_district_idx ON sites(district);
|
|
|
|
CREATE TABLE IF NOT EXISTS pois (
|
|
poi_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
site_id TEXT NOT NULL REFERENCES sites(site_id),
|
|
category TEXT NOT NULL, -- 'kindergarten','school','shop_supermarket', ...
|
|
osm_type TEXT,
|
|
osm_id TEXT,
|
|
name TEXT,
|
|
lat REAL NOT NULL,
|
|
lon REAL NOT NULL,
|
|
distance_m REAL NOT NULL,
|
|
raw_tags TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS pois_site_cat ON pois(site_id, category);
|
|
|
|
CREATE TABLE IF NOT EXISTS features (
|
|
site_id TEXT NOT NULL REFERENCES sites(site_id),
|
|
feature TEXT NOT NULL, -- 'kindergarten_nearest_m','schools_in_1km','transit_500m', ...
|
|
value REAL,
|
|
PRIMARY KEY (site_id, feature)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS scores (
|
|
site_id TEXT NOT NULL REFERENCES sites(site_id),
|
|
component TEXT NOT NULL, -- 'education','retail','health','transit','leisure'
|
|
score_0_100 REAL NOT NULL,
|
|
PRIMARY KEY (site_id, component)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS scores_total (
|
|
site_id TEXT PRIMARY KEY REFERENCES sites(site_id),
|
|
weighted REAL NOT NULL,
|
|
rank_overall INTEGER,
|
|
rank_district INTEGER
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS run_log (
|
|
run_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
started_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
finished_at TEXT,
|
|
notes TEXT
|
|
);
|
|
"""
|
|
|
|
if __name__ == "__main__":
|
|
conn = sqlite3.connect(DB)
|
|
conn.executescript(SCHEMA)
|
|
conn.commit()
|
|
print(f"DB ready at {DB}")
|
|
for tbl in ['sites','pois','features','scores','scores_total','run_log']:
|
|
n = conn.execute(f"SELECT count(*) FROM {tbl}").fetchone()[0]
|
|
print(f" {tbl}: {n} rows")
|
|
conn.close()
|