gendesign/data/sql/116_obj2_multi_feature_matcher.sql
lekss361 970c42a471 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).
2026-05-17 22:48:25 +03:00

96 lines
4.2 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 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;