feat(obj-3): SF backend migrate to objective_lots ground truth (price 0.3% → 81%) #324
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#324
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/obj-3-sf-migrate-objective"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Issue #307 sub-task OBJ-3 — Site Finder competitors блок мигрирован на
objective_lotsground truth (81% price coverage vs 0.3% вdomrf_kn_flats).Backbone JOIN
domrf_kn_objects.obj_id→objective_complex_mapping.domrf_obj_id→objective_complex_mapping.objective_complex_name = objective_lots.project_nameПосле OBJ-2 PR #323 (mapping backfill 142 → 200+) этот JOIN покрывает большинство ЕКБ ЖК.
Files
backend/app/api/v1/parcels.py— competitor query (section 5,analyze_parcel) расширенobj_pricingCTE с LEFT JOIN на mapping → objective_lotsbackend/tests/test_velocity.py— fix pre-existing breakage от OBJ-2 migrationNew competitor fields
avg_price_per_m2_rub— средняя ₽/м² (81% coverage)avg_area_pd— средняя площадь лотаunits_sold/units_available— динамика продажlots_with_price— UI coverage indicatorLEFT JOIN сохраняет ЖК без mapping.
Tests
Все 33 теста проходят.
VelocityResultобновлён сvelocity_data_available/velocity_sourcesemantics.Acceptance
После deploy на участке
66:41:0204016:10:avg_price_per_m2_rub∈ [110000, 220000]avg_area_pd∈ [28, 80]units_sold> 0 для активных ЖКPart of issue #307 (Wave 2). Depends on OBJ-2 #323 (merged).
Deep code review — verdict: APPROVE
Files: 2 (P1: parcels.py SQL, P3: test_velocity.py mocks). +60/-16. Mergeable, no conflicts, no prior reviews.
SQL JOIN correctness — verified
latest_obj(DISTINCT ON obj_id) LEFT JOINobj_pricingCTE (cm.domrf_obj_id, group-by aggregate ofobjective_complex_mapping ⋈ objective_lots ON cm.objective_complex_name = ol.project_name).obj_pricingCTE uses INNER JOIN (mapping ⋈ lots) — non-mapped names are simply absent; LEFT JOIN on outside handles that.100_fix_mv_layout_velocity_weighted_avg.sqland thepipeline_rowsblock lines 1392-1399.Aggregate weighting — NOT a regression of SF Bug #21
The #21 bug (PR #290/#291) was specific to
objective_corpus_room_month: per-month aggregate rows withdeals_total_countwhere months with zero sales contributedarea=0, price=0rows, dragging AVG down 2-14×. Different data shape here:objective_lots= per-flat snapshot (UNIQUE objective_lot_id, current state UPSERT per lot)price_per_m2_rubor NULLAVG()ignores NULLs by default → only the ~81% of lots with a price contributeAcceptance band in PR body (
avg_price_per_m2_rub ∈ [110000, 220000]) is consistent with current EKB market.psycopg v3 style — clean
ROUND(AVG(...)::numeric, N)—::numericis attached to theAVG(...)expression (right of)), not to a:bindparam. Matches the documented exception in.claude/rules/backend.md. No:wkt::geography-style bugs. All params parametrized via:wkt.Backward compat
comm_name,obj_class,distance_metc. still present.dict(c)overmappings()picks up new columns automatically.ParcelAnalysisCompetitor(frontend/src/types/site-finder.ts:34) does NOT yet haveavg_price_per_m2_rub/units_sold/ etc. — additive backend, no TS break (existing fields untouched). UI consumption +npm run codegenis a fair separate PR (precedent: line 42-45 comment forsite_status).Tests
test_velocity.pyaddshas_mappingfield + newvelocity_data_available/velocity_sourceassertions, consistent with OBJ-2 #323.test_score_zero_when_no_sales_sqmsemantics flipped (None → VelocityResult withvelocity_data_available=False) is intentional and documented. Mock-only, no live DB needed.Minor / non-blocking notes (defer to follow-up)
objective_group = 'Екатеринбург'filter inobj_pricingCTE. Currently EKB-scoped because the mapping table itself is EKB-scoped (per103_seed_prinzip_mappings.sql,104_seed_bulk_mappings.sql,116_obj2_multi_feature_matcher.sql), so practically a no-op. Ifobjective_lotsever ingests multi-city data alongside MSK/SPb mappings, the agg would silently mix cities. AddWHERE cm.objective_group = 'Екатеринбург'defensively in a follow-up.AVG(price_per_m2_rub)mixes sold lots (registered at older prices) with currently-available lots. If sold history skews lower, this can drag the avg ~3-8% below current ask. ConsiderAVG(price_per_m2_rub) FILTER (WHERE NOT is_sold)for an "ask price" variant in a future iteration; current behavior is acceptable as a blended "realised market" metric.Verdict
APPROVE — merging now via squash.