Merge pull request 'fix(site-finder): remove dead sat_factor (computed+written, never applied) (#1509)' (#1698) from fix/score-sat-factor-1509 into main
Some checks are pending
Deploy / changes (push) Waiting to run
Deploy / build-backend (push) Blocked by required conditions
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Some checks are pending
Deploy / changes (push) Waiting to run
Deploy / build-backend (push) Blocked by required conditions
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
This commit is contained in:
commit
c514b992ee
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:
|
Adds these tables/columns:
|
||||||
macro_context — single-row mortgage rate, city avg price, etc.
|
macro_context — single-row mortgage rate, city avg price, etc.
|
||||||
district_economics + (real_velocity_6mo, real_velocity_prior_6mo, real_trend_ratio,
|
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)
|
scoring_weights — single-row config (so we can change weights from UI later)
|
||||||
|
|
||||||
Source for trend: per-flat register_date (objective_lots).
|
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_6mo REAL;
|
||||||
ALTER TABLE district_economics ADD COLUMN real_velocity_prior_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 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 trend_factor REAL;
|
||||||
ALTER TABLE district_economics ADD COLUMN price_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)
|
ratio = 1.0 + min(1.0, v_rec / _REF_VELOCITY) * (_TREND_CAP_VPRIOR_ZERO - 1.0)
|
||||||
# Prod-style clamping
|
# Prod-style clamping
|
||||||
trend_factor = max(0.7, min(2.0, ratio)) if ratio else 1.0
|
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
|
local.execute("""UPDATE district_economics
|
||||||
SET real_velocity_6mo=?, real_velocity_prior_6mo=?,
|
SET real_velocity_6mo=?, real_velocity_prior_6mo=?,
|
||||||
real_trend_ratio=?, sat_factor=?, trend_factor=?
|
real_trend_ratio=?, trend_factor=?
|
||||||
WHERE district=?""",
|
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
|
# price_factor = district median / city median
|
||||||
city_med = local.execute(
|
city_med = local.execute(
|
||||||
|
|
@ -181,14 +175,14 @@ def main():
|
||||||
local.commit()
|
local.commit()
|
||||||
|
|
||||||
print("\nDistrict trend (top 10 by recent velocity):")
|
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}")
|
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, sat_factor, trend_factor,
|
for r in local.execute("""SELECT district, price_factor, trend_factor,
|
||||||
real_velocity_6mo, real_velocity_prior_6mo, real_trend_ratio
|
real_velocity_6mo, real_velocity_prior_6mo, real_trend_ratio
|
||||||
FROM district_economics
|
FROM district_economics
|
||||||
WHERE real_velocity_6mo IS NOT NULL
|
WHERE real_velocity_6mo IS NOT NULL
|
||||||
ORDER BY real_velocity_6mo DESC LIMIT 10""").fetchall():
|
ORDER BY real_velocity_6mo DESC LIMIT 10""").fetchall():
|
||||||
d,pf,sf,tf,vr,vp,rr = r
|
d, pf, 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}")
|
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()
|
local.close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,12 +113,12 @@ def main():
|
||||||
# Economic + market
|
# Economic + market
|
||||||
econ_row = conn.execute("""SELECT de.real_median_price_m2, de.real_velocity_6mo,
|
econ_row = conn.execute("""SELECT de.real_median_price_m2, de.real_velocity_6mo,
|
||||||
de.real_trend_ratio, de.months_to_sellout,
|
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
|
FROM site_district sd
|
||||||
JOIN district_economics de USING (district)
|
JOIN district_economics de USING (district)
|
||||||
WHERE sd.site_id=?""", (sid,)).fetchone()
|
WHERE sd.site_id=?""", (sid,)).fetchone()
|
||||||
if econ_row:
|
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
|
# price_score: linear by district median price percentile
|
||||||
p_score = max(0, min(100, ((price or 0) - pmin) * 100 / (pmax - pmin)))
|
p_score = max(0, min(100, ((price or 0) - pmin) * 100 / (pmax - pmin)))
|
||||||
# velocity_score: real recent (6mo), capped at p90
|
# 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
|
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
|
economic = 0.50 * p_score + 0.25 * v_score + 0.25 * liq_score
|
||||||
else:
|
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
|
# Competitive density: number of OTHER ЖК within 1km
|
||||||
n_jk_1km = sum(
|
n_jk_1km = sum(
|
||||||
|
|
|
||||||
|
|
@ -363,14 +363,13 @@ def score_point(lat, lon, weights, what_if=None, remove_cats=None):
|
||||||
with _conn() as c:
|
with _conn() as c:
|
||||||
admin, obj_district = assign_district(lat, lon, c)
|
admin, obj_district = assign_district(lat, lon, c)
|
||||||
economics = None
|
economics = None
|
||||||
trend_factor = sat_factor = 1.0
|
trend_factor = 1.0
|
||||||
if obj_district:
|
if obj_district:
|
||||||
row = c.execute("SELECT * FROM district_economics WHERE district=?",
|
row = c.execute("SELECT * FROM district_economics WHERE district=?",
|
||||||
(obj_district,)).fetchone()
|
(obj_district,)).fetchone()
|
||||||
economics = dict(row) if row else None
|
economics = dict(row) if row else None
|
||||||
if economics:
|
if economics:
|
||||||
trend_factor = economics.get("trend_factor") or 1.0
|
trend_factor = economics.get("trend_factor") or 1.0
|
||||||
sat_factor = economics.get("sat_factor") or 1.0
|
|
||||||
|
|
||||||
# bounds for normalization
|
# bounds for normalization
|
||||||
prices = sorted([r[0] for r in c.execute(
|
prices = sorted([r[0] for r in c.execute(
|
||||||
|
|
@ -463,7 +462,7 @@ def score_point(lat, lon, weights, what_if=None, remove_cats=None):
|
||||||
"admin_district": admin,
|
"admin_district": admin,
|
||||||
"district": obj_district,
|
"district": obj_district,
|
||||||
"economics": economics,
|
"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,
|
"n_jk_1km": n_jk_1km, "city_pmin": pmin, "city_pmax": pmax,
|
||||||
"city_vmax": vmax},
|
"city_vmax": vmax},
|
||||||
"features": feats,
|
"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>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>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>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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue