feat(obj-2): multi-feature mapping backfill (name+dev+district) — 142 → 200+ #323
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#323
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/obj-2-multi-feature-matcher"
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-2 — массовый backfill
objective_complex_mappingчерез multi-feature scoring.Context
project_name= dev internal name, DOM.РФcomm_name= публичное название ЖК.What this PR does
data/sql/116_obj2_multi_feature_matcher.sql:1. GIST trgm index (perf fix)
Database-expert worker нашёл что
EXPLAINпоказал seq-scan-per-row наobjective_lots.project_name(~961 unmapped × 320 projects = seq scan 89M cost).2. Composite scoring
composite = name_sim*0.6 + dev_match*0.25 + district_match*0.15name_sim— pg_trgm similarity(comm_name, project_name)dev_match— equality afterregexp_replace(lower(...), '[^а-яa-z0-9]', '', 'g')normalization (strips ООО/ЗАО/spaces/case)district_match— bidirectional ILIKE substring (district_name ILIKE %district% OR vice versa)LATERAL top-3 per
obj_id, ROW_NUMBER pick best per obj_id, threshold composite ≥ 0.75.3. Idempotent INSERT
ON CONFLICT (objective_complex_name, objective_group) DO NOTHING— safe для повторного применения.match_method='multi_feature_v1',is_reviewed=false(для последующей manual review слабых матчей).4. REFRESH mv_layout_velocity
CONCURRENTLYпосле COMMIT (outside tx — required для CONCURRENTLY) → Site Finder видит новые mappings.Acceptance
После apply:
SELECT COUNT(*) FROM objective_complex_mapping;→ expected 200+SELECT * FROM objective_complex_mapping WHERE domrf_obj_id=64701;→ Малевич still mapped (was manual)SELECT match_method, COUNT(*) FROM objective_complex_mapping GROUP BY 1;→ новая категорияmulti_feature_v1Why not 800
Issue #307 estimated target ~800. Real distinct Objective project_names для ЕКБ = 320 (verified). После backfill реалистичный потолок = ~200-250 (около 65-80% от max). Остаток — naming gap который требует address-based matcher (отдельный PR — OBJ-2b если нужно).
Files
data/sql/116_obj2_multi_feature_matcher.sql(+96, new)Notes
Part of issue #307 (Wave 1 — OBJ-2). Next: OBJ-3 (Site Finder migration на Objective ground truth) после merge.
Deep Code Review — verdict: APPROVE
Files reviewed: 1 (P0:
data/sql/116_obj2_multi_feature_matcher.sql)Lines: +96 / -0
Risk: Low · Reversibility: Easy (INSERT-only with
ON CONFLICT DO NOTHING, can DELETE WHERE match_method='multi_feature_v1' to roll back)Critical checks (all pass)
116_is the next free slot. Last applied:115_trade_in_estimates.sql. No collision.REFRESH MATERIALIZED VIEW CONCURRENTLY mv_layout_velocityis placed afterCOMMIT;, not inside theBEGIN/COMMITblock. Postgres requirement satisfied. Themv_layout_velocity_pkunique index exists (verified via MCP), so CONCURRENTLY refresh is permitted.objective_complex_mapping_objective_complex_name_objective__key UNIQUE (objective_complex_name, objective_group)exists.ON CONFLICT (objective_complex_name, objective_group)matches.CREATE INDEX IF NOT EXISTS+INSERT ... ON CONFLICT DO NOTHING. Existing 142 mappings untouched.(objective_complex_name, domrf_obj_id, objective_group, match_method, match_score, is_reviewed, note)aligns with the SELECT projection in correct positional order. All NOT NULL columns supplied;objective_project_idis nullable (left NULL, consistent with103_seed_prinzip_mappings.sqlpattern).CREATE INDEX IF NOT EXISTS+ON CONFLICT DO NOTHING+REFRESH ... CONCURRENTLYall rerun-safe.Index correctness
CREATE INDEX IF NOT EXISTS objective_lots_project_name_trgm_idx ON objective_lots USING gist (project_name gist_trgm_ops)— correct operator class for pg_trgm + GIST.CONCURRENTLY. Acceptable trade-off —objective_lotsis small enough that the AccessExclusiveLock during build is negligible, and the migration runs in deploy.yml without concurrent writes. If this were >10M rows I'd push for CONCURRENTLY, but at current scale it's fine.idx_domrf_kn_objects_comm_name_trgmalready on the other side of the join. Together the two GIST trgm indexes make the LATERALsimilarity() > 0.3prefilter fast.Scoring formula
regexp_replace(lower(...), '[^а-яa-z0-9]', '', 'g')correctly strips ООО/ЗАО/whitespace/punctuation. Minor: pattern excludesё(U+0451 falls outsideа-яrange U+0430–U+044F), so devs containingёlose those chars. Acceptable for fuzzy matching — symmetric on both sides.coalesce(..., '')on both sides ofregexp_replaceand ILIKE — verified.composite DESC, name_sim DESC— deterministic for ties.LATERAL + ROW_NUMBER pattern
CROSS JOIN LATERAL (... LIMIT 3)perobj_idreduces fanout from 961 × 320 to 961 × 3 candidates.ROW_NUMBER() OVER (PARTITION BY obj_id ORDER BY composite DESC, name_sim DESC)+ finalWHERE rn = 1 AND composite >= 0.75picks best candidate per obj_id. Correct pattern.Positive observations
project_nameand DOM.РФcomm_name).match_methoddiscriminator).EXPLAIN sanity check
Ran a representative LATERAL probe via MCP — confirms that without the new GIST trgm index, the inner
similarity() > 0.3filter currently usesIndex Only Scanfiltering 73k rows per outer iteration (no trgm operator usage). Author's perf claim is justified; the new index enables index-supported similarity prefiltering.Approving and merging. After deploy, run the verification queries from the file header to confirm the 200+ target.