feat(obj-2): multi-feature mapping backfill (name+dev+district), threshold 0.75
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).
This commit is contained in:
parent
f2983c86d7
commit
970c42a471
1 changed files with 96 additions and 0 deletions
96
data/sql/116_obj2_multi_feature_matcher.sql
Normal file
96
data/sql/116_obj2_multi_feature_matcher.sql
Normal file
|
|
@ -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;
|
||||
Loading…
Add table
Reference in a new issue