Compare commits

..

No commits in common. "40c58c15b1032563a38f4e7bd04a8a5cae8d6fa6" and "86d0475f45aea2c227906d6d51f7ef08327cbac8" have entirely different histories.

View file

@ -5,10 +5,7 @@ Output tables:
district_economics per Objective-район aggregates over last 90 days district_economics per Objective-район aggregates over last 90 days
site_district which Objective-район each site belongs to site_district which Objective-район each site belongs to
""" """
import math import sqlite3, pathlib, re, math
import pathlib
import re
import sqlite3
from difflib import SequenceMatcher from difflib import SequenceMatcher
DB = pathlib.Path(__file__).parent / "analysis.db" DB = pathlib.Path(__file__).parent / "analysis.db"
@ -46,8 +43,7 @@ CREATE TABLE IF NOT EXISTS site_district (
""" """
def normalize(s): def normalize(s):
if not s: if not s: return ""
return ""
s = s.lower().strip() s = s.lower().strip()
s = re.sub(r'^(жк|жилой\s+комплекс|жилой\s+квартал|жилые\s+кварталы)\s+', '', s) s = re.sub(r'^(жк|жилой\s+комплекс|жилой\s+квартал|жилые\s+кварталы)\s+', '', s)
s = re.sub(r'["«»\'`]+', '', s) s = re.sub(r'["«»\'`]+', '', s)
@ -57,13 +53,11 @@ def normalize(s):
def fuzzy(a, b): def fuzzy(a, b):
return SequenceMatcher(None, normalize(a), normalize(b)).ratio() return SequenceMatcher(None, normalize(a), normalize(b)).ratio()
def hav(la1, lo1, la2, lo2): def hav(la1,lo1,la2,lo2):
R = 6371000 R=6371000; p1,p2=math.radians(la1),math.radians(la2)
p1, p2 = math.radians(la1), math.radians(la2) dp=math.radians(la2-la1); dl=math.radians(lo2-lo1)
dp = math.radians(la2 - la1) a=math.sin(dp/2)**2+math.cos(p1)*math.cos(p2)*math.sin(dl/2)**2
dl = math.radians(lo2 - lo1) return 2*R*math.asin(math.sqrt(a))
a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
return 2 * R * math.asin(math.sqrt(a))
def main(): def main():
conn = sqlite3.connect(DB) conn = sqlite3.connect(DB)
@ -80,8 +74,7 @@ def main():
best = (0.0, None) best = (0.0, None)
for proj in objective_projects: for proj in objective_projects:
s = fuzzy(name, proj) s = fuzzy(name, proj)
if s > best[0]: if s > best[0]: best = (s, proj)
best = (s, proj)
if best[0] >= 0.80: if best[0] >= 0.80:
conn.execute("INSERT INTO jk_objective_match VALUES (?,?,?,?)", conn.execute("INSERT INTO jk_objective_match VALUES (?,?,?,?)",
(site_id, best[1], best[0], "fuzzy")) (site_id, best[1], best[0], "fuzzy"))
@ -97,17 +90,17 @@ def main():
conn.execute("DELETE FROM district_economics") conn.execute("DELETE FROM district_economics")
placeholders = ",".join(["?"]*len(last3)) placeholders = ",".join(["?"]*len(last3))
# Latest stock per (project, corpus) — taken from the single most-recent month in the window # Latest stock per (project, corpus) — taken from the latest month each corpus appears
# (#1512): using a per-corpus latest caused mixed periods (stale stock for inactive corpuses).
# Fix: pin stock to last3[0] (the most recent month) so numerator and denominator of MTS
# are aligned to the same period for all corpuses in the district.
latest_month = last3[0]
latest_stock = {} latest_stock = {}
for r in conn.execute(""" for r in conn.execute(f"""
SELECT project, corpus, district, stock_m2, stock_lots WITH ranked AS (
FROM objective_corp_month SELECT project, corpus, district, month, stock_m2, stock_lots,
WHERE month = ? ROW_NUMBER() OVER (PARTITION BY project, corpus ORDER BY month DESC) rn
""", (latest_month,)).fetchall(): FROM objective_corp_month
WHERE month IN ({placeholders})
)
SELECT project, corpus, district, stock_m2, stock_lots FROM ranked WHERE rn=1
""", last3).fetchall():
latest_stock[(r[0], r[1])] = (r[2], r[3] or 0, r[4] or 0) latest_stock[(r[0], r[1])] = (r[2], r[3] or 0, r[4] or 0)
# Per-district aggregates from window # Per-district aggregates from window
@ -121,8 +114,7 @@ def main():
by_dist = {} by_dist = {}
for r in rows: for r in rows:
d = r[0] d = r[0]
if not d: if not d: continue
continue
by_dist.setdefault(d, []).append(r) by_dist.setdefault(d, []).append(r)
for d, rs in by_dist.items(): for d, rs in by_dist.items():
@ -132,24 +124,9 @@ def main():
wn = sum((r[6] or 0) * (r[5] or 0) for r in rs) wn = sum((r[6] or 0) * (r[5] or 0) for r in rs)
wd = sum((r[5] or 0) for r in rs) wd = sum((r[5] or 0) for r in rs)
wp = wn / wd if wd else None wp = wn / wd if wd else None
# median price — volume-weighted by sold_volume_m2 (#1511) # median price (priced deals only)
# Each corpus×month row contributes avg_price_m2 weighted by sold_volume_m2 so that prices = sorted([r[6] for r in rs if r[6] and (r[4] or 0) > 0])
# high-volume months/rooms count proportionally (not equal-weight per row). med = prices[len(prices)//2] if prices else None
# Falls back to weight=1 for rows with priced deals but missing volume figure.
pw = [(r[6], max(r[5] or 0, 1.0)) for r in rs if r[6] and (r[4] or 0) > 0]
if pw:
pw.sort(key=lambda x: x[0])
total_w = sum(w for _, w in pw)
half = total_w / 2.0
cum = 0.0
med = None
for price, w in pw:
cum += w
if cum >= half:
med = price
break
else:
med = None
# offer prices # offer prices
offers = [r[8] for r in rs if r[8] and r[8] > 0] offers = [r[8] for r in rs if r[8] and r[8] > 0]
avg_off = sum(offers)/len(offers) if offers else None avg_off = sum(offers)/len(offers) if offers else None
@ -198,8 +175,7 @@ def main():
# vote among k=5 nearest matched ЖК within 2 km # vote among k=5 nearest matched ЖК within 2 km
cands = [] cands = []
for sid2, (lat2, lon2, dist2) in matched_idx.items(): for sid2, (lat2, lon2, dist2) in matched_idx.items():
if sid2 == sid: if sid2 == sid: continue
continue
d = hav(lat, lon, lat2, lon2) d = hav(lat, lon, lat2, lon2)
cands.append((d, sid2, dist2)) cands.append((d, sid2, dist2))
cands.sort() cands.sort()
@ -211,10 +187,8 @@ def main():
rep = next(c for c in top if c[2] == top_dist) rep = next(c for c in top if c[2] == top_dist)
obj_id = None obj_id = None
if rep[1] and rep[1].startswith("jk:"): if rep[1] and rep[1].startswith("jk:"):
try: try: obj_id = int(rep[1][3:])
obj_id = int(rep[1][3:]) except: pass
except ValueError:
pass
conn.execute("INSERT INTO site_district VALUES (?,?,?,?,?)", conn.execute("INSERT INTO site_district VALUES (?,?,?,?,?)",
(sid, top_dist, "knn_vote_5", obj_id, rep[0])) (sid, top_dist, "knn_vote_5", obj_id, rep[0]))
conn.commit() conn.commit()