feat(tradein): yandex_helpers.py — JSON-LD + regex + date + NLP + money helpers #456
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#456
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-yandex-helpers"
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?
Motivation
Stage 2 of 9 for YandexRealtyScraper v1 (Stage 1 = PR #453 SQL migrations 030-032, merged). Shared parsing utilities used by Stages 3-6 (SERP / detail / newbuilding / valuation parsers).
Public API
JSON-LD extraction
extract_json_ld(html) -> list[dict]— parses every<script type="application/ld+json">, tolerates JSON arrays, skips invalid blocksfind_ld_by_type(html, type_name) -> dict | None— handles both string@typeand list@typeRegex constants (compiled at module level)
RE_OFFER_ID,RE_JK_ID,RE_STREET_IDRE_TITLE_AREA,RE_TITLE_ROOMS,RE_FLOOR,RE_PRICE,RE_PPM2,RE_VIEWSRE_METRO_WALK,RE_YEAR,RE_AGENCY_FOUNDED,RE_AGENCY_OBJECTSDate parsing
parse_ru_date("9 мая 2026") → date(2026, 5, 9)— genitive month form (SERP / detail)parse_dmy("17.02.2026") → date(2026, 2, 17)— used by Valuation tool historyNLP helpers
parse_house_type→panel | monolith_brick | monolith | brick | block | wood | None(order-sensitive; monolith_brick checked before monolith and brick alone)parse_house_class→elite | premium | business | comfort_plus | comfort | economy | NoneMoney
parse_rub→ handles"4 399 000 ₽","4,4 млн ₽","4.4 млн ₽", raw digits,None/ emptySpec fixes applied by worker
RE_TITLE_ROOMS— the original spec regex(\d+|студи[яюй])\s*-?\s*комнатнcould never match standalone "Студия 28 м²" because "Студия" is not followed by "комнатн". Split into two alternations:(\d+)\s*-?\s*комнатн|(студи[яюй])— group(1) = numbered rooms, group(2) = studio token.parse_house_classpremium — original patternкласса?\s+премиумmissed the reversed phrasing "премиум-класса". Added|премиум.{0,5}классto cover both word orders.Test results
Coverage: house_type (5 cases), house_class (6 cases), parse_rub (6 cases), parse_ru_date (5 cases), parse_dmy (4 cases), JSON-LD (5 cases — single / multiple / array wrapper / invalid skipped / @type list form / missing), regex sanity (3 cases — offer_id / jk_id / title parsing incl. studio).
Lint
Clean — 0 warnings. Line length ≤ 100, PEP 604 type unions (
str | None), noprint(), noimport psycopg2, norequests.Files
tradein-mvp/backend/app/services/scrapers/yandex_helpers.py(new)tradein-mvp/backend/tests/test_yandex_helpers.py(new)Reviewer note
Reviewer: deep-code-reviewer — only merge after APPROVE.
Merged via deep-code-reviewer — verdict ✅ APPROVE.
Verification performed locally
app.services.scrapers.yandex_helpers— 0.12s).__all__consistency — все 22 exported names резолвятся (import не falls).RE_OFFER_ID✅ короткий и длинный (19-digit) offer idRE_JK_ID✅tatlin-1592987→ slug + idRE_STREET_ID✅st-lenina-12345→ slug + idRE_TITLE_AREA✅ integer /75,5/75.5с decimalRE_TITLE_ROOMS✅ N-комнатная (group 1) + студи[яюй] (group 2) — 3 формы (я/ю/й/ий черезСтудий)RE_FLOOR✅6 этаж из 15(NOT5/10 этаж)RE_PRICE✅4 399 000 ₽/4399000 ₽RE_PPM2✅117 500 ₽ за м²(captures trailing space —parse_rubcleanup устраняет)RE_VIEWS✅ просмотр/просмотра/просмотров (все 3 формы — словарь содержит "просмотр")RE_METRO_WALK✅ ходьбы / пешком / неспешной прогулкиRE_YEAR✅ 1900-2029 (strict 4-digit,\b)RE_AGENCY_FOUNDED✅Год основания 1995RE_AGENCY_OBJECTS✅ "1234 объекта" + "150 объектов"parse_ru_date("9 мая 2026")✅,parse_ru_date("9 МАЯ 2026")✅ (lower-case lookup), invalid month → None, nominative form"9 май 2026"→ None (correct strict behavior), short year"9 мая 26"→ None.parse_dmy— strict\d{2}\.\d{2}\.\d{4}— отвергает1.2.2026и17.02.26(correct).panel>monolith_brick>monolith>brick>block>wood. Verified что "панельный дом из монолита" →panel(first match wins).monolith_brickкорректно матчит "кирпично-монолитный", "монолитно-кирпичный", "монолит-кирпич".parse_house_classpremium fix ✅ — оба word order работают: "класс премиум" + "премиум-класса".parse_rub—4,4 млн ₽/4.4 млн ₽→ 4_400_000, raw digits, prefix₽4399000, mixed sentence, "4 МЛН ₽" (uppercase) — все pass.tradein-mvp/backend/pyproject.toml(selectolax>=0.3.0).yandex_realty.pyсуществует, но helpers — отдельный модуль; импорта pollution нет.deploy-tradein.ymlтригернётся поtradein-mvp/backend/**paths-filter.Minor observations (non-blocking, для Stage 3-6)
RE_RUB_RAW/RE_RU_DATE/RE_DMY/RE_RUB_MLNкорректно НЕ в__all__— internal use.parse_rub(int_input)крашитсяAttributeError(нет.strip()). Defensive:if not isinstance(text, str): return None. Не критично — все callers будут передаватьstr | Noneper type hint.RE_AGENCY_FOUNDEDstrict pattern "Год основания NNNN" не покроет "Агентство основано в 2010 году" — может быть relevant если Yandex DOM эту форму использует. Reviewer Stage 4/5 проверит на real fixtures.extract_json_ldиспользуетselectolax(правильно — уже в deps, fast), не regex.Stage 2 unblocks: 3 (SERP refactor), 4 (detail), 5 (newbuilding), 6 (valuation) parsers.