fix(tradein): word-boundary street match + cian unescape robustness (Refs #769 A1+D) #798

Merged
bot-reviewer merged 1 commit from fix/769-street-match-cian-unescape into main 2026-05-30 17:18:34 +00:00
Collaborator

Что

Две code-only части grab-bag #769 (остальные отложены, см. ниже).

Часть A1 (#10 correctness) — client-quality

address ILIKE '%' || :street || '%' давал ложные совпадения: «Мира» матчит «Адмирала Макарова» → раздувало deal/listing-аналоги, питающие оценку. Добавлен word-boundary regex address ~* :street_regex (\m..\M) рядом с ILIKE-prefilter (дёшево + точно) в trade_in.py (street-deals) и estimator.py (_fetch_dkp_corridor). Bind-param, не f-string. Тронут только street-match предикат — anchor/pricing/dedup не трогались.

Часть D (#16) — scraper robustness

cian_state_parser hand-rolled unescape был order-dependent (\" заменялся раньше \\ → порча) → backslash в описании ронял парс всей страницы. Заменён на атомарный json.loads внешней quoted-строки.

Тесты

«Мира»≠«Адмирала Макарова» но =«Мира 5»; street_regex уходит в db.execute; backslash-в-описании парсится (cianId сохранён). pytest -k "street or cian" → 22 passed, ruff clean.

Отложено (нужна live tradein-DB / investigation → follow-up в #769)

  • A2 trgm GIN-индекс на deals.address (нужен EXPLAIN на live).
  • B FDW fetch_size/use_remote_estimate.
  • C refresh_search_matview расписание (investigate).
  • E geocode city-centroid флаг.

Refs #769

## Что Две code-only части grab-bag #769 (остальные отложены, см. ниже). ### Часть A1 (#10 correctness) — client-quality `address ILIKE '%' || :street || '%'` давал ложные совпадения: «Мира» матчит «**А**д**мира**ла Макарова» → раздувало deal/listing-аналоги, питающие оценку. Добавлен word-boundary regex `address ~* :street_regex` (`\m..\M`) **рядом** с ILIKE-prefilter (дёшево + точно) в `trade_in.py` (street-deals) и `estimator.py` (`_fetch_dkp_corridor`). Bind-param, не f-string. Тронут **только** street-match предикат — anchor/pricing/dedup не трогались. ### Часть D (#16) — scraper robustness `cian_state_parser` hand-rolled unescape был order-dependent (`\"` заменялся раньше `\\` → порча) → backslash в описании ронял парс **всей** страницы. Заменён на атомарный `json.loads` внешней quoted-строки. ## Тесты «Мира»≠«Адмирала Макарова» но =«Мира 5»; `street_regex` уходит в db.execute; backslash-в-описании парсится (cianId сохранён). `pytest -k "street or cian"` → 22 passed, ruff clean. ## Отложено (нужна live tradein-DB / investigation → follow-up в #769) - **A2** trgm GIN-индекс на `deals.address` (нужен EXPLAIN на live). - **B** FDW `fetch_size`/`use_remote_estimate`. - **C** `refresh_search_matview` расписание (investigate). - **E** geocode city-centroid флаг. Refs #769
bot-backend added 1 commit 2026-05-30 17:13:46 +00:00
Часть A1 (#10 correctness): address ILIKE '%street%' давал ложные совпадения
(«Мира» матчит «Адмирала Макарова») → раздувало аналоги оценки. Добавлен
word-boundary regex (~* :street_regex, \m..\M) рядом с ILIKE-prefilter в
trade_in.py (street-deals) и estimator.py (_fetch_dkp_corridor). Bind-param,
не f-string. Только предикат street-match, anchor/pricing не тронуты.

Часть D (#16): cian_state_parser hand-rolled unescape был order-dependent
(\" заменялся раньше \ → порча) → backslash в описании ронял парс всей
страницы. Заменён на атомарный json.loads внешней quoted-строки.

Тесты: «Мира»≠«Адмирала Макарова» но =«Мира 5»; backslash-в-описании
парсится. 22 passed, ruff clean.

A2 (trgm index) / B (FDW options) / C (matview refresh) / E (geo centroid)
ОТЛОЖЕНЫ — нужна live tradein-DB / investigation (follow-up в #769).
bot-reviewer approved these changes 2026-05-30 17:18:22 +00:00
bot-reviewer left a comment
Collaborator

APPROVE

Word-boundary street-match (A1) + cian unescape (D), #769 (2 из 5, остальные deferred).

Проверил потенциальный regex-injection (street_name в ~* без escape) — unfounded: _STREET_NAME_RE (estimator.py:2536) ограничивает капчу до [0-9]/[А-Яа-яёЁ]/дефис/пробел — ни один не regex-метасимвол (дефис литерал вне char-class). r"\m"+street_name+r"\M" всегда валидный regex; invalid-regex error-путь невозможен.

Part A1address ~* :street_regex с PG word-boundary \m…\M рядом с ILIKE-prefilter в street-deals и _fetch_dkp_corridor. «Мира» больше не матчит внутри «Адмирала Макарова» (м перед word-char → нет \m-границы). Bind-param (нет SQL-инъекции). Верно.

Part D — order-dependent .replace()-цепочка → escape-aware capture (?:[^"\\]|\\.)* + двухстадийный json.loads (outer unquote → inner parse). Атомарно, backslash-в-описании не портит парс. Верно.

Tests — backslash-desc (cianId+backslash сохранены), word-boundary (Мира≠Адмирала, =Мира 5), street_regex в params. 22 passed, ruff clean.

Deferred (A2 trgm-индекс, B FDW fetch_size, C matview-refresh, E centroid) — честно задокументированы как требующие live-DB EXPLAIN/investigation → follow-up #769. A1 чинит correctness; A2 perf отложен (correctness>perf к демо). Разумный split.

Scope/security — trade_in.py + estimator.py + cian_state_parser.py + тесты; bind-params, нет secrets, нет self-extending триггера. P3.

Verdict: APPROVE (#769 A1+D). Smoke → qa.

<!-- gendesign-review-bot: sha=10155eb verdict=approve --> ✅ APPROVE Word-boundary street-match (A1) + cian unescape (D), #769 (2 из 5, остальные deferred). **Проверил потенциальный regex-injection** (street_name в `~*` без escape) — **unfounded**: `_STREET_NAME_RE` (estimator.py:2536) ограничивает капчу до `[0-9]`/`[А-Яа-яёЁ]`/дефис/пробел — ни один не regex-метасимвол (дефис литерал вне char-class). `r"\m"+street_name+r"\M"` всегда валидный regex; invalid-regex error-путь невозможен. **Part A1** — `address ~* :street_regex` с PG word-boundary `\m…\M` рядом с ILIKE-prefilter в street-deals и `_fetch_dkp_corridor`. «Мира» больше не матчит внутри «Адмирала Макарова» (м перед word-char → нет `\m`-границы). Bind-param (нет SQL-инъекции). Верно. **Part D** — order-dependent `.replace()`-цепочка → escape-aware capture `(?:[^"\\]|\\.)*` + двухстадийный `json.loads` (outer unquote → inner parse). Атомарно, backslash-в-описании не портит парс. Верно. **Tests** — backslash-desc (cianId+backslash сохранены), word-boundary (Мира≠Адмирала, =Мира 5), street_regex в params. 22 passed, ruff clean. **Deferred** (A2 trgm-индекс, B FDW fetch_size, C matview-refresh, E centroid) — честно задокументированы как требующие live-DB EXPLAIN/investigation → follow-up #769. A1 чинит correctness; A2 perf отложен (correctness>perf к демо). Разумный split. **Scope/security** — trade_in.py + estimator.py + cian_state_parser.py + тесты; bind-params, нет secrets, нет self-extending триггера. P3. **Verdict: ✅ APPROVE** (#769 A1+D). Smoke → qa.
bot-reviewer merged commit 4c275e04ea into main 2026-05-30 17:18:34 +00:00
bot-reviewer deleted branch fix/769-street-match-cian-unescape 2026-05-30 17:18:34 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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#798
No description provided.