ДКП-коридор по Москве не строился: имя улицы не извлекалось из московского формата адреса #3473

Merged
lekss361 merged 1 commit from fix/msk-street-name-suffix into main 2026-09-12 10:53:47 +00:00
Owner

Найдено сквозным тестом оценки по московскому адресу на проде 12.09: ответ 200 за 1,5 с, 25 аналогов, но dkp_corridornull при 212 937 московских ДКП в базе. Коридор сделок по Москве не строился ни разу.

Причина

extract_street_name возвращал None для любого московского адреса. Парсер ждёт тип улицы перед названием («ул. Малышева»), а в Москве он стоит после: «Тверская улица, 6». Keyword-регекс требует пробел сразу за типом, там запятая — совпадения нет вовсе. Дальше fallback брал первый токен с большой буквы, получал «Москва» из стоп-списка и отдавал None.

Фикс парсера

Второй проход: ищем тип улицы без требования пробела и берём 1-3 слова до него в пределах той же запятой-секции. Прежний путь не тронут — «ул. X» и реверс-формат Nominatim разбираются как раньше. Непустые результаты не меняются, новый проход даёт значение только там, где раньше был None.

Списки типов улиц вынесены в общую константу, чтобы два регекса не разъехались при добавлении нового типа.

Нумерованные проезды («Проектируемый проезд № 4062») намеренно остаются None: имя «Проектируемый» собрало бы коридор по сотням разных проездов.

Регион-скоуп двух ручек (нашло adversarial-ревью, подтверждено замером)

Непустое имя улицы включает /street-deals и /sales-vs-listings, где раньше для Москвы был ранний выход. Обе скоупятся только по _resolve_target_city — словарю городов Свердловской области, — поэтому для Москвы фильтр города пуст, и остаётся один ILIKE по улице.

Замер на проде: улица «Ясная» — 168 сделок в регионе 66 и 80 в 77; «Советская» — 1202 и 17. Без фильтра региона московский запрос смешал бы екатеринбургские сделки в медиану, то есть фикс парсера сам по себе открыл бы дыру.

Поэтому в обе ручки добавлен обязательный фильтр по region_code. Регион выводится из адреса через реестр регионов точным сравнением сегмента, а не подстрокой — иначе екатеринбургская «Московская улица» уехала бы в регион 77. В deals регион заполнен у всех строк (66 → 108 623, 77 → 212 937, NULL нет), фильтр ничего не отрезает у существующих запросов.

Известный компромисс

У /sales-vs-listings табличная функция параметра региона не знает, её миграция в этот фикс не входит. Фильтр применён снаружи, соединением с deals по идентификатору сделки: сторона объявлений остаётся без регион-скоупа. Это описано в коде; полный фикс — отдельная миграция с параметром региона внутри функции.

Тесты

535 passed во всех файлах, затрагивающих коридор и уличную статистику, из них 13 новых: московские форматы адреса, негативные кейсы, вывод региона из адреса, регион-скоуп обеих ручек. Ruff чистый, pre-commit прогнан вручную и зелёный.

После деплоя проверю на проде, что коридор по московскому адресу перестал быть null.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NQb6WeJtagZwZnUsSjDizs

Найдено сквозным тестом оценки по московскому адресу на проде 12.09: ответ 200 за 1,5 с, 25 аналогов, но `dkp_corridor` — `null` при 212 937 московских ДКП в базе. Коридор сделок по Москве не строился ни разу. ## Причина `extract_street_name` возвращал `None` для любого московского адреса. Парсер ждёт тип улицы перед названием («ул. Малышева»), а в Москве он стоит после: «Тверская улица, 6». Keyword-регекс требует пробел сразу за типом, там запятая — совпадения нет вовсе. Дальше fallback брал первый токен с большой буквы, получал «Москва» из стоп-списка и отдавал `None`. ## Фикс парсера Второй проход: ищем тип улицы без требования пробела и берём 1-3 слова до него в пределах той же запятой-секции. Прежний путь не тронут — «ул. X» и реверс-формат Nominatim разбираются как раньше. Непустые результаты не меняются, новый проход даёт значение только там, где раньше был `None`. Списки типов улиц вынесены в общую константу, чтобы два регекса не разъехались при добавлении нового типа. Нумерованные проезды («Проектируемый проезд № 4062») намеренно остаются `None`: имя «Проектируемый» собрало бы коридор по сотням разных проездов. ## Регион-скоуп двух ручек (нашло adversarial-ревью, подтверждено замером) Непустое имя улицы включает `/street-deals` и `/sales-vs-listings`, где раньше для Москвы был ранний выход. Обе скоупятся только по `_resolve_target_city` — словарю городов Свердловской области, — поэтому для Москвы фильтр города пуст, и остаётся один ILIKE по улице. Замер на проде: улица «Ясная» — 168 сделок в регионе 66 и 80 в 77; «Советская» — 1202 и 17. Без фильтра региона московский запрос смешал бы екатеринбургские сделки в медиану, то есть фикс парсера сам по себе открыл бы дыру. Поэтому в обе ручки добавлен обязательный фильтр по `region_code`. Регион выводится из адреса через реестр регионов точным сравнением сегмента, а не подстрокой — иначе екатеринбургская «Московская улица» уехала бы в регион 77. В `deals` регион заполнен у всех строк (66 → 108 623, 77 → 212 937, NULL нет), фильтр ничего не отрезает у существующих запросов. ## Известный компромисс У `/sales-vs-listings` табличная функция параметра региона не знает, её миграция в этот фикс не входит. Фильтр применён снаружи, соединением с `deals` по идентификатору сделки: сторона объявлений остаётся без регион-скоупа. Это описано в коде; полный фикс — отдельная миграция с параметром региона внутри функции. ## Тесты 535 passed во всех файлах, затрагивающих коридор и уличную статистику, из них 13 новых: московские форматы адреса, негативные кейсы, вывод региона из адреса, регион-скоуп обеих ручек. Ruff чистый, pre-commit прогнан вручную и зелёный. После деплоя проверю на проде, что коридор по московскому адресу перестал быть `null`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01NQb6WeJtagZwZnUsSjDizs
lekss361 added 1 commit 2026-09-12 10:44:28 +00:00
fix(tradein): ДКП-коридор по Москве не строился — имя улицы не извлекалось
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 6m5s
d78b1f8881
`extract_street_name` возвращал None для любого московского адреса, потому
что парсер ждёт тип улицы ПЕРЕД названием («ул. Малышева»), а в Москве он
стоит после: «Тверская улица, 6». Keyword-регекс требует пробел сразу за
типом, там запятая — совпадения нет вовсе; дальше fallback брал первый
токен с большой буквы, получал «Москва» из стоп-списка и отдавал None.

Следствие на проде (замер 12.09): оценка по московскому адресу отвечает
200 с 25 аналогами, но `dkp_corridor` в ответе — null, при 212 937
московских ДКП в базе. Коридор сделок по Москве не строился ни разу.

Добавлен второй проход: ищем тип улицы без требования пробела и берём
1-3 слова ДО него в пределах той же запятой-секции. Прежний путь не
тронут — «ул. X» и реверс-формат Nominatim разбираются как раньше;
непустые результаты не меняются, новый проход даёт значение только там,
где раньше был None. Списки типов улиц вынесены в общую константу, чтобы
два регекса не разъехались при добавлении нового типа.

Нумерованные проезды («Проектируемый проезд № 4062») намеренно остаются
None: имя «Проектируемый» собрало бы коридор по сотням разных проездов.

## Регион-скоуп двух ручек

Непустое имя улицы включает `/street-deals` и `/sales-vs-listings`, где
раньше для Москвы был ранний выход. Обе скоупятся только по
`_resolve_target_city` — словарю городов Свердловской области, — поэтому
для Москвы фильтр города пуст, и остаётся один ILIKE по улице.

Замер на проде: улица «Ясная» — 168 сделок в регионе 66 и 80 в 77,
«Советская» — 1202 и 17. Без фильтра региона московский запрос смешал бы
екатеринбургские сделки в медиану, то есть фикс парсера сам по себе
открыл бы дыру. Поэтому в обе ручки добавлен обязательный фильтр по
`region_code`; регион выводится из адреса через реестр регионов точным
сравнением сегмента, а не подстрокой — иначе екатеринбургская
«Московская улица» уехала бы в регион 77.

В `deals` регион заполнен у всех строк (66 → 108 623, 77 → 212 937,
NULL нет), так что фильтр ничего не отрезает у существующих запросов.

У `/sales-vs-listings` табличная функция параметра региона не знает, её
миграция в этот фикс не входит. Фильтр применён снаружи, соединением с
`deals` по идентификатору сделки: сторона объявлений остаётся без
регион-скоупа. Это осознанный компромисс, он описан в коде; полный фикс
— отдельная миграция с параметром региона внутри функции.

Тесты: 535 passed во всех файлах, затрагивающих коридор и уличную
статистику (+13 новых), ruff чистый.
lekss361 merged commit 1eee4b955d into main 2026-09-12 10:53:47 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3473
No description provided.