gendesign/site-finder/db_init.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

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()