From 970c42a471a763fb50f58119667ceee7026ca990 Mon Sep 17 00:00:00 2001 From: lekss361 Date: Sun, 17 May 2026 22:48:25 +0300 Subject: [PATCH] feat(obj-2): multi-feature mapping backfill (name+dev+district), threshold 0.75 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #307 OBJ-2. Adds GIST trgm index on objective_lots.project_name so the LATERAL similarity() probe runs via bitmap index scan instead of seq-scan-per-row (database-expert worker identified this perf bottleneck via EXPLAIN). Composite score = name_sim*0.6 + dev_match*0.25 + district_match*0.15. Threshold composite >= 0.75 for auto-accept. ON CONFLICT (objective_complex_name, objective_group) DO NOTHING — idempotent. REFRESH mv_layout_velocity CONCURRENTLY after backfill (outside tx) so Site Finder sees new mappings immediately. Expected mapping count: 142 -> 200+ (realistic ceiling given Objective project_names vs DOM.РФ comm_names naming gap). --- data/sql/116_obj2_multi_feature_matcher.sql | 96 +++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 data/sql/116_obj2_multi_feature_matcher.sql diff --git a/data/sql/116_obj2_multi_feature_matcher.sql b/data/sql/116_obj2_multi_feature_matcher.sql new file mode 100644 index 00000000..434af084 --- /dev/null +++ b/data/sql/116_obj2_multi_feature_matcher.sql @@ -0,0 +1,96 @@ +-- 116_obj2_multi_feature_matcher.sql +-- Issue #307 OBJ-2: backfill objective_complex_mapping via multi-feature scoring +-- (name_sim 60% + dev_match 25% + district_match 15%). +-- +-- Adds trgm gist index on objective_lots.project_name FIRST so the LATERAL +-- similarity() probe runs as bitmap index scan instead of seq-scan-per-row. +-- +-- Inserts auto-accepted matches at composite >= 0.75 with match_method='multi_feature_v1'. +-- ON CONFLICT (objective_complex_name, objective_group) DO NOTHING — idempotent. +-- REFRESH mv_layout_velocity CONCURRENTLY at the end so Site Finder sees new mappings. +-- +-- Verification (run after COMMIT, outside migration): +-- SELECT COUNT(*) FROM objective_complex_mapping; -- expect 200+ +-- SELECT * FROM objective_complex_mapping WHERE domrf_obj_id=64701; -- Малевич still mapped +-- SELECT match_method, COUNT(*) FROM objective_complex_mapping GROUP BY 1; + +-- The pg_trgm extension is already installed (used by existing migrations 99/103/104). + +-- ── Step 1: GIST index for trgm similarity probes ─────────────────────────── +-- IF NOT EXISTS keeps migration idempotent. +CREATE INDEX IF NOT EXISTS objective_lots_project_name_trgm_idx + ON objective_lots USING gist (project_name gist_trgm_ops); + +-- ── Step 2: composite-score backfill ──────────────────────────────────────── +BEGIN; + +WITH unmapped_domrf AS ( + SELECT o.obj_id, o.comm_name, o.dev_name, o.district_name + FROM domrf_kn_objects o + WHERE o.is_ekb = true + AND o.comm_name IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM objective_complex_mapping m WHERE m.domrf_obj_id = o.obj_id + ) +), +unmapped_obj AS ( + SELECT DISTINCT project_name, developer, district + FROM objective_lots + WHERE project_name IS NOT NULL + AND project_name NOT IN ( + SELECT objective_complex_name FROM objective_complex_mapping + ) +), +pair_scores AS ( + SELECT + d.obj_id, + o.project_name, + similarity(d.comm_name, o.project_name) AS name_sim, + (regexp_replace(lower(coalesce(d.dev_name, '')), '[^а-яa-z0-9]', '', 'g') + = regexp_replace(lower(coalesce(o.developer, '')), '[^а-яa-z0-9]', '', 'g') + )::int AS dev_match, + ( + coalesce(d.district_name, '') ILIKE '%' || coalesce(o.district, '') || '%' + OR coalesce(o.district, '') ILIKE '%' || coalesce(d.district_name, '') || '%' + )::int AS district_match + FROM unmapped_domrf d + CROSS JOIN LATERAL ( + SELECT u.project_name, u.developer, u.district + FROM unmapped_obj u + WHERE similarity(d.comm_name, u.project_name) > 0.3 + ORDER BY similarity(d.comm_name, u.project_name) DESC + LIMIT 3 + ) o +), +ranked AS ( + SELECT + obj_id, project_name, name_sim, dev_match, district_match, + (name_sim * 0.6 + dev_match * 0.25 + district_match * 0.15) AS composite, + ROW_NUMBER() OVER ( + PARTITION BY obj_id + ORDER BY (name_sim * 0.6 + dev_match * 0.25 + district_match * 0.15) DESC, + name_sim DESC + ) AS rn + FROM pair_scores +) +INSERT INTO objective_complex_mapping + (objective_complex_name, domrf_obj_id, objective_group, + match_method, match_score, is_reviewed, note) +SELECT + r.project_name, + r.obj_id, + 'Екатеринбург', + 'multi_feature_v1', + r.composite, + false, + 'OBJ-2 multi-feature 2026-05-17 (name 0.6 + dev 0.25 + district 0.15)' + FROM ranked r + WHERE r.rn = 1 + AND r.composite >= 0.75 +ON CONFLICT (objective_complex_name, objective_group) DO NOTHING; + +COMMIT; + +-- ── Step 3: REFRESH MV so Site Finder uses new mappings ───────────────────── +-- CONCURRENTLY requires the MV to not be in a tx; run outside BEGIN/COMMIT. +REFRESH MATERIALIZED VIEW CONCURRENTLY mv_layout_velocity; -- 2.45.3