fix(site-finder): remove dead sat_factor (computed+written, never applied) (#1509)
Some checks failed
CI / changes (pull_request) Has been cancelled
CI / backend-tests (pull_request) Has been cancelled
CI / frontend-tests (pull_request) Has been cancelled
CI / openapi-codegen-check (pull_request) Has been cancelled
CI / changes (push) Successful in 8s
CI / backend-tests (push) Has been cancelled
CI / frontend-tests (push) Has been cancelled
CI / openapi-codegen-check (push) Has been cancelled
Some checks failed
CI / changes (pull_request) Has been cancelled
CI / backend-tests (pull_request) Has been cancelled
CI / frontend-tests (pull_request) Has been cancelled
CI / openapi-codegen-check (pull_request) Has been cancelled
CI / changes (push) Successful in 8s
CI / backend-tests (push) Has been cancelled
CI / frontend-tests (push) Has been cancelled
CI / openapi-codegen-check (push) Has been cancelled
sat_factor = 1 + ((sold_pct-50)/100)*0.30 was computed in 09_macro_and_trend.py, written to district_economics.sat_factor, and fetched in server.py and 10_score_v2.py — but never multiplied into any score. The live market sub-score uses a separate sat_score = min(100, sold_pct*100/70) directly, so sat_factor was dead code that would double-count absorption if ever wired in. - 09_macro_and_trend.py: remove sat_factor computation, ALTER TABLE column, UPDATE binding, and debug print column - 10_score_v2.py: remove sat_factor from SELECT and unpacking - server.py: remove sat_factor variable assignment and from macro_factors response - static/index.html: remove sat_factor documentation row - data/sql/162_drop_district_economics_sat_factor.sql: DROP COLUMN IF EXISTS
This commit is contained in:
parent
86d0475f45
commit
9e5086890f
5 changed files with 25 additions and 20 deletions
12
data/sql/162_drop_district_economics_sat_factor.sql
Normal file
12
data/sql/162_drop_district_economics_sat_factor.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
-- Remove dead column district_economics.sat_factor (#1509).
|
||||
--
|
||||
-- sat_factor = 1 + ((sold_pct - 50) / 100) * 0.30 was computed in 09_macro_and_trend.py
|
||||
-- and written here, but was never read by any scoring logic (10_score_v2.py, server.py).
|
||||
-- The live market sub-score uses sat_score = min(100, sold_pct * 100 / 70) directly,
|
||||
-- so sat_factor double-counts absorption without being applied anywhere.
|
||||
-- All code references removed in the same commit as this migration.
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE district_economics DROP COLUMN IF EXISTS sat_factor;
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
Adds these tables/columns:
|
||||
macro_context — single-row mortgage rate, city avg price, etc.
|
||||
district_economics + (real_velocity_6mo, real_velocity_prior_6mo, real_trend_ratio,
|
||||
sat_factor, trend_factor, price_factor)
|
||||
trend_factor, price_factor)
|
||||
scoring_weights — single-row config (so we can change weights from UI later)
|
||||
|
||||
Source for trend: per-flat register_date (objective_lots).
|
||||
|
|
@ -37,7 +37,6 @@ CREATE TABLE IF NOT EXISTS macro_context (
|
|||
ALTER TABLE district_economics ADD COLUMN real_velocity_6mo REAL;
|
||||
ALTER TABLE district_economics ADD COLUMN real_velocity_prior_6mo REAL;
|
||||
ALTER TABLE district_economics ADD COLUMN real_trend_ratio REAL;
|
||||
ALTER TABLE district_economics ADD COLUMN sat_factor REAL;
|
||||
ALTER TABLE district_economics ADD COLUMN trend_factor REAL;
|
||||
ALTER TABLE district_economics ADD COLUMN price_factor REAL;
|
||||
|
||||
|
|
@ -130,16 +129,11 @@ def main():
|
|||
ratio = 1.0 + min(1.0, v_rec / _REF_VELOCITY) * (_TREND_CAP_VPRIOR_ZERO - 1.0)
|
||||
# Prod-style clamping
|
||||
trend_factor = max(0.7, min(2.0, ratio)) if ratio else 1.0
|
||||
# sat_factor: sold_pct in district. >50% = mature market (prod logic +30%
|
||||
# multiplier on velocity for mature districts since absorption is proven)
|
||||
sold_pct = local.execute(
|
||||
"SELECT real_sold_pct FROM district_economics WHERE district=?", (d,)).fetchone()
|
||||
sat_factor = 1 + ((sold_pct[0] or 50) - 50) / 100 * 0.30 if sold_pct and sold_pct[0] else 1.0
|
||||
local.execute("""UPDATE district_economics
|
||||
SET real_velocity_6mo=?, real_velocity_prior_6mo=?,
|
||||
real_trend_ratio=?, sat_factor=?, trend_factor=?
|
||||
real_trend_ratio=?, trend_factor=?
|
||||
WHERE district=?""",
|
||||
(v_rec, v_prior, ratio, sat_factor, trend_factor, d))
|
||||
(v_rec, v_prior, ratio, trend_factor, d))
|
||||
|
||||
# price_factor = district median / city median
|
||||
city_med = local.execute(
|
||||
|
|
@ -181,14 +175,14 @@ def main():
|
|||
local.commit()
|
||||
|
||||
print("\nDistrict trend (top 10 by recent velocity):")
|
||||
print(f"{'район':<22}{'price_f':>8}{'sat':>6}{'trend':>6}{'v_rec':>6}{'v_pr':>6}{'ratio':>6}")
|
||||
for r in local.execute("""SELECT district, price_factor, sat_factor, trend_factor,
|
||||
print(f"{'район':<22}{'price_f':>8}{'trend':>6}{'v_rec':>6}{'v_pr':>6}{'ratio':>6}")
|
||||
for r in local.execute("""SELECT district, price_factor, trend_factor,
|
||||
real_velocity_6mo, real_velocity_prior_6mo, real_trend_ratio
|
||||
FROM district_economics
|
||||
WHERE real_velocity_6mo IS NOT NULL
|
||||
ORDER BY real_velocity_6mo DESC LIMIT 10""").fetchall():
|
||||
d,pf,sf,tf,vr,vp,rr = r
|
||||
print(f"{d:<22}{pf or 0:>8.2f}{sf or 0:>6.2f}{tf or 0:>6.2f}{vr or 0:>6.2f}{vp or 0:>6.2f}{rr or 0:>6.2f}")
|
||||
d, pf, tf, vr, vp, rr = r
|
||||
print(f"{d:<22}{pf or 0:>8.2f}{tf or 0:>6.2f}{vr or 0:>6.2f}{vp or 0:>6.2f}{rr or 0:>6.2f}")
|
||||
|
||||
local.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -113,12 +113,12 @@ def main():
|
|||
# Economic + market
|
||||
econ_row = conn.execute("""SELECT de.real_median_price_m2, de.real_velocity_6mo,
|
||||
de.real_trend_ratio, de.months_to_sellout,
|
||||
de.sat_factor, de.real_sold_pct, de.real_n_lots
|
||||
de.real_sold_pct, de.real_n_lots
|
||||
FROM site_district sd
|
||||
JOIN district_economics de USING (district)
|
||||
WHERE sd.site_id=?""", (sid,)).fetchone()
|
||||
if econ_row:
|
||||
price, v_rec, trend, mts, sat, sold_pct, n_lots = econ_row
|
||||
price, v_rec, trend, mts, sold_pct, n_lots = econ_row
|
||||
# price_score: linear by district median price percentile
|
||||
p_score = max(0, min(100, ((price or 0) - pmin) * 100 / (pmax - pmin)))
|
||||
# velocity_score: real recent (6mo), capped at p90
|
||||
|
|
@ -130,7 +130,7 @@ def main():
|
|||
liq_score = max(0, 100 - min(mts or 24, 24) * 100 / 24) if mts else 50
|
||||
economic = 0.50 * p_score + 0.25 * v_score + 0.25 * liq_score
|
||||
else:
|
||||
economic = 0; trend = None; sat = None; sold_pct = None; n_lots = None
|
||||
economic = 0; trend = None; sold_pct = None; n_lots = None
|
||||
|
||||
# Competitive density: number of OTHER ЖК within 1km
|
||||
n_jk_1km = sum(
|
||||
|
|
|
|||
|
|
@ -359,14 +359,13 @@ def score_point(lat, lon, weights, what_if=None, remove_cats=None):
|
|||
with _conn() as c:
|
||||
admin, obj_district = assign_district(lat, lon, c)
|
||||
economics = None
|
||||
trend_factor = sat_factor = 1.0
|
||||
trend_factor = 1.0
|
||||
if obj_district:
|
||||
row = c.execute("SELECT * FROM district_economics WHERE district=?",
|
||||
(obj_district,)).fetchone()
|
||||
economics = dict(row) if row else None
|
||||
if economics:
|
||||
trend_factor = economics.get("trend_factor") or 1.0
|
||||
sat_factor = economics.get("sat_factor") or 1.0
|
||||
|
||||
# bounds for normalization
|
||||
prices = sorted([r[0] for r in c.execute(
|
||||
|
|
@ -459,7 +458,7 @@ def score_point(lat, lon, weights, what_if=None, remove_cats=None):
|
|||
"admin_district": admin,
|
||||
"district": obj_district,
|
||||
"economics": economics,
|
||||
"macro_factors": {"trend_factor": trend_factor, "sat_factor": sat_factor,
|
||||
"macro_factors": {"trend_factor": trend_factor,
|
||||
"n_jk_1km": n_jk_1km, "city_pmin": pmin, "city_pmax": pmax,
|
||||
"city_vmax": vmax},
|
||||
"features": feats,
|
||||
|
|
|
|||
|
|
@ -892,7 +892,7 @@
|
|||
<tr><td><b>jk_count_1km</b></td><td>Число других строящихся ЖК в радиусе 1 км от точки.<br><span class="hint">Парцель: 3 (мало конкурентов рядом — плюс)</span></td></tr>
|
||||
<tr><td><b>density_score</b></td><td><code>max(0, 100 − jk_count_1km × 100/15)</code>. Чем меньше конкурентов — тем выше.<br><span class="hint">Парцель: max(0, 100 − 3×100/15) = 80</span></td></tr>
|
||||
<tr><td><b>saturation_score</b></td><td><code>min(100, real_sold_pct × 100/70)</code>. Высокий sold% подтверждает спрос, не штраф.<br>Плато на 70%+ — район proven, дальше плюсы не растут.<br><span class="hint">Старая Сортировка sold 48.8% → 69.7</span></td></tr>
|
||||
<tr><td><b>sat_factor</b></td><td>prod-style: <code>1 + (sold% − 50)/100 × 0.30</code>. Используется отдельно как мультипликатор. Не входит напрямую в market.<br><span class="hint">Зрелые рынки получают +30% к velocity</span></td></tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue