fix(yandex-valuation): relax area regex lookbehind (2-комн regression) (#542)
All checks were successful
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 52s
Deploy Trade-In / deploy (push) Successful in 35s

This commit is contained in:
lekss361 2026-05-24 16:02:06 +00:00
parent 5b1fb6633f
commit 3ed58e3e7a
2 changed files with 40 additions and 4 deletions

View file

@ -120,10 +120,12 @@ RE_FLOORS = re.compile(r"(\d+)\s+этажей", re.IGNORECASE)
RE_CEILING = re.compile(r"([\d,]+)\s*м\s+потолки", re.IGNORECASE)
RE_TOTAL_OBJECTS = re.compile(r"(\d+)\s+объект", re.IGNORECASE)
# Negative lookbehind on digit/comma/dot prevents matching the trailing digits of
# adjacent tokens — e.g. publish year "2024" right before "42,6 м²" must NOT
# yield "20242,6". Cap whole-number part at 4 digits and decimal part at 2.
RE_ITEM_AREA = re.compile(r"(?<![\d.,])(\d{1,4}(?:[.,]\d{1,2})?)\s*м²")
# Lookbehind blocks digit-only adjacency (rejects year-concat like '20244,6')
# while still allowing tokens preceded by punctuation/letter. Min 2 digits
# rejects sub-fragments like '2,2' that come from inside '52,2'. Max 4 digits
# whole part catches obvious junk (penthouse extremes are <500 m² in practice;
# sanity cap from PR #538 backs this up).
RE_ITEM_AREA = re.compile(r"(?<!\d)(\d{2,4}(?:[.,]\d{1,2})?)\s*м²")
RE_ITEM_ROOMS = re.compile(r"(\d+)\s*-\s*комнатн", re.IGNORECASE)
RE_ITEM_STUDIO = re.compile(r"студи[яюй]", re.IGNORECASE)
RE_ITEM_FLOOR = re.compile(r"(\d+)\s*этаж", re.IGNORECASE)

View file

@ -448,3 +448,37 @@ def test_single_date_is_publish_only():
assert item.publish_date == _d(2024, 3, 15)
assert item.removed_date is None
def test_area_regex_two_komn_chunk():
"""2-комн chunks were 100% rejected by PR #541 lookbehind. Real bug from
Базовый 52 sweep: 'rooms=2 → area_m2 is None' for every 2-комн row.
"""
# Approximate the chunked text Yandex emits for a 2-room item
text = "8 000 000 ₽ за м²2-комнатная 52,2 м² 5 этаж 10.05.2024 8 000 000 ₽ В продаже"
item = YandexValuationScraper._parse_item_text(text)
assert item is not None
assert item.area_m2 == 52.2, (
f"2-комн area must parse despite preceding tokens, got {item.area_m2}"
)
def test_area_regex_still_blocks_year_concat():
"""The relaxed regex must still reject year+area concat."""
# '2024' directly fused to '42,6 м²' as can happen after DOM strip
text = "квартира 202442,6 м² 3 этаж 10.05.2024 9 000 000 ₽ В продаже"
item = YandexValuationScraper._parse_item_text(text)
# The '202442,6' token: a `\\d{2,4}` greedy match starting at first '2'
# gives '2024', then needs '[.,]\\d{1,2}' but '4' follows → no match.
# Any internal start is blocked by `(?<!\\d)`. So no area parsed.
assert item is None or item.area_m2 is None
def test_area_regex_still_blocks_subfragment():
"""Min 2 digits blocks '2,2' fragment from inside '52,2'."""
# Pure single-digit start without preceding non-digit context
text = "квартира ,2,2 м² 10.05.2024 5 000 000 ₽ В продаже"
item = YandexValuationScraper._parse_item_text(text)
# '2,2' would need (?<!\\d) — `,` is not digit, so lookbehind allows start.
# But `\\d{2,4}` needs ≥2 leading digits — '2,' only has one. No match.
assert item is None or item.area_m2 is None