feat(etl): objective_complex_mapping backfill via pg_trgm (#203) #214
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#214
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/203-objective-backfill"
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
domrf_kn_objects.comm_name↔objective_corpus_room_month.project_nameobjective_complex_mappingсmatch_method='pg_trgm_auto',match_score,is_reviewed=falsePOST /api/v1/admin/etl/objective-backfill?dry_run&refresh_mv(требуетX-Admin-Token)domrf_kn_objects.comm_name WHERE is_ekb=true+ B-tree наobjective_corpus_room_month.project_nameFiles
data/sql/97_objective_backfill_indexes.sql(+31) — pg_trgm extension + indexes (idempotent)backend/app/services/etl/__init__.py(NEW, empty)backend/app/services/etl/objective_backfill.py(+220) — ETL service: find_match_candidates / auto_apply_matches / trigger_mv_refreshbackend/app/api/v1/admin_etl.py(+70) — admin endpointbackend/app/main.py(+6) — router registrationbackend/tests/services/test_objective_backfill.py(+179, 5 tests pass)Test plan
SELECT COUNT(*) FROM objective_complex_mapping;→ ~260Pre-push checks
match_method,match_score,is_reviewed,objective_group,objective_complex_name,domrf_obj_id) verified против live DBFollow-up
Closes #203
Deep Code Review — verdict
Summary
4c68645Critical (BLOCK)
None.
High (must-fix before merge)
None.
Medium (желательно, не блокирующее)
None.
Low / nits (можно отдельным PR, либо игнорировать)
backend/app/api/v1/admin_etl.py:30—refresh_mv=Truedefault + синхронныйREFRESH MATERIALIZED VIEW CONCURRENTLYвнутри HTTP request. Сейчасmv_layout_velocity= 459 rows, после backfill ожидается ~1000-1500 — refresh time prod < 10 sec, приемлемо. Для будущего follow-up можно вынести в Celery task если MV вырастет.backend/app/services/etl/objective_backfill.py:151—match_method='fuzzy_trgm', но существующие 114 rows на проде имеютmatch_method='fuzzy'. Это намеренная дифференциация для audit trail (новый авто-method vs старый ручной импорт) — корректно. Только PR body упоминает'pg_trgm_auto', а код использует'fuzzy_trgm'— рассинхрон описания, не кода.backend/app/services/etl/objective_backfill.py:180—db.commit()вызывается даже когдаinserted=0(все ON CONFLICT skip). Безвредно, но можно guard:if inserted > 0 or skipped > 0: db.commit().Cross-file impact analysis
backend/app/main.py:10,62— router registration корректный, prefix/api/v1/admin/etl/+ endpoint/objective-backfill→ final path/api/v1/admin/etl/objective-backfillсоответствует PR body curl examples.objective_complex_mappingwriters — проверил через vault: ранее использовался ручной импорт (114 rowsmatch_method='fuzzy'). Этот PR — единственный automated writer, conflict scenarios ограничены повторными запусками самого backfill (handled viaON CONFLICT DO NOTHING).mv_layout_velocityREFRESH — после backfill ожидается +146 mapped rows; current MV 459 rows, refresh CONCURRENTLY должен оставаться < 10 sec.refresh_layout_velocity(db, concurrently=True)уже имеет fallback на non-concurrent при первом populate (защищено try/except OperationalError).mcp__obsidianподтвердил —velocity.py,domains/api/endpoints/Endpoint_Parcel_BestLayouts.md,Bug_Kn_API_Obj_Class_Always_Null_OPEN.mdвсе JOIN'ят черезdomrf_obj_id— добавление новых rows только улучшает их coverage, breaking changes нет.Schema phantom-check (verified via
mcp__postgres-gendesign)objective_complex_mappingcolumns:objective_complex_name TEXT NOT NULL,objective_group TEXT NOT NULL,domrf_obj_id BIGINT,match_method TEXT,match_score NUMERIC,is_reviewed BOOLEAN NOT NULL DEFAULT false— все 6 совпадают с INSERT вauto_apply_matches.(objective_complex_name, objective_group)существует —ON CONFLICTtarget корректен.domrf_kn_objects.is_ekb,comm_name,obj_id,dev_name— все присутствуют.objective_corpus_room_month.project_name,group_name— присутствуют;group_nameимеет единственное значение'Екатеринбург'(263 distinct projects), что подтверждает безопасность хардкода в INSERT.pg_extension:pg_trgm v1.6уже установлен —CREATE EXTENSION IF NOT EXISTSидемпотентен.SQL / migration check
data/sql/97_*— корректный next free slot (на Forgejo main: 90/91/92/93/94/95/96 заняты; 94-96 merged уже после local clone, поэтому local Glob показывал 93).BEGIN; ... COMMIT;обёртка ✅CREATE EXTENSION IF NOT EXISTS,CREATE INDEX IF NOT EXISTS✅ идемпотентноWHERE is_ekb = trueкорректен — фильтрует ~1285 строк из ~50k полной таблицы, что снижает размер GIST tree.CREATE INDEX(без CONCURRENTLY) — на таблице 50k строк acceptable (~ms блокировка). При партиционировании в будущем — переходить на CONCURRENTLY.Performance findings (EXPLAIN на live prod)
Pre-index query plan (текущий, без GIST):
Cost 743K — высокий, но это one-shot backfill (не recurring query). После merge + apply migration GIST index
idx_domrf_kn_objects_comm_name_trgmпозволитsimilarity()использовать index — cost упадёт на 1-2 порядка. Single execution time ожидается < 30s.Coverage prediction (verified):
Conventions check
CAST(:name AS type)везде, ни одного:name::typetext()parametrized — нет SQL injectionlogger.*, нетprint()with db.begin_nested():) в loop INSERTasync defне применимо (sync ETL endpoint, OK для admin)AdminTokenAuth = Annotated[None, Depends(verify_admin_token)]— корректно типизированVault cross-check
fixes/fixes-MOC.md— pg_trgm +is_ekb=truefilter уже задокументирован как правильный подход.code/modules/modules-MOC.md— есть запись о PR #203 с goal "3% → 40%+ coverage".Session_End_May15_2026.md— текущая проблемаobjective_complex_mapping coverage 2.5%явно отмечена как next priority. Этот PR её адресует.Module_Velocity_Service.md— schema linkagedomrf_obj_id ↔ objective_complex_nameподтверждена.Tests coverage
find_match_candidatesrow→MatchCandidate transformation + parametrized SQLauto_apply_matchesdry_run no-side-effectstrigger_mv_refreshlazy import + concurrently=True delegationON CONFLICTrowcount=0 → skipped counterPositive observations
dry_run=Falsedefault — намеренно: admin endpoint, expected explicit invocation; но рекомендую при первом запуске вручную?dry_run=true.district_name ILIKEreturns 0 — anti-trap для будущих maintainers).ON CONFLICT DO NOTHINGкорректен — backfill safe to re-run.WHERE is_ekb = true— минимизирует index size.refresh_layout_velocityвtrigger_mv_refresh— избегает circular import risk.Pre-flight checks
4c68645— на feature branchfeat/203-objective-backfill, не mainmergeable=true,state=open, не draftComplexity / blast radius score
DELETE FROM objective_complex_mapping WHERE match_method = 'fuzzy_trgm'+DROP INDEX idx_domrf_kn_objects_comm_name_trgm, idx_objective_corpus_room_month_project_name. Migration файлы идемпотентны (можно оставить).?dry_run=trueдля верификации coverage gain.Verdict: APPROVE — но scope blocked для auto-merge per
deep-code-reviewer.mdAuto-merge policy (touchesdata/sql/**+backend/app/api/v1/**+backend/app/services/**). Нужен human merge.After merge, recommended runbook:
🤖 Posted by
deep-code-reviewersubagent.