fix(tradein/avito): консолидировать house_type normalizer + актуализировать docstring (#2009) #2340
3 changed files with 44 additions and 15 deletions
|
|
@ -6,7 +6,9 @@
|
|||
bathroom_type, windows_view, repair_state, sale_type, mortgage_available)
|
||||
- Location (lat, lon, avito_location_id, metro_stations[], address_full)
|
||||
- House params (house_type, total_floors_house, lifts, concierge, closed_yard,
|
||||
house_catalog_url) — собираются но НЕ сохраняются в БД (Stage 2c)
|
||||
house_catalog_url) — house_type + house_catalog_url сохраняются в listings
|
||||
(#2009); total_floors_house/lifts/concierge/closed_yard остаются canonical
|
||||
из Houses Catalog (Stage 2c, avito_houses.py → houses table)
|
||||
- Description
|
||||
- Domoteka 5 полей (owners_count, owners_at_least, last_owner_change_date,
|
||||
encumbrances_clean, registry_match)
|
||||
|
|
@ -42,6 +44,7 @@ from app.services.scrapers.avito_exceptions import (
|
|||
AvitoRateLimitedError,
|
||||
)
|
||||
from app.services.scrapers.avito_shared import RUS_MONTHS
|
||||
from app.services.scrapers.house_type_normalizer import normalize_house_type
|
||||
from app.services.scrapers.repair_state_normalizer import infer_repair_state_from_text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -173,14 +176,10 @@ _SALE_TYPE_MAP: dict[str, str] = {
|
|||
"переуступка": "assignment",
|
||||
}
|
||||
|
||||
_HOUSE_TYPE_MAP: dict[str, str] = {
|
||||
"монолитный": "monolith",
|
||||
"панельный": "panel",
|
||||
"кирпичный": "brick",
|
||||
"монолитно-кирпичный": "monolith_brick",
|
||||
"блочный": "block",
|
||||
"деревянный": "wood",
|
||||
}
|
||||
# house_type: канон panel/brick/monolith/monolith_brick/block/wood — через shared
|
||||
# normalize_house_type() (#2009, консолидация с #2007/#2008; см.
|
||||
# house_type_normalizer.py — русские лейблы avito добавлены туда же вокабуляром,
|
||||
# НЕ дублируются локально).
|
||||
|
||||
|
||||
# ── DetailEnrichment dataclass ────────────────────────────────────────────────
|
||||
|
|
@ -189,8 +188,9 @@ class DetailEnrichment:
|
|||
"""Результат парсинга detail page Avito.
|
||||
|
||||
Содержит 30+ полей из страницы объявления.
|
||||
save_detail_enrichment() пишет подмножество в listings (house-level поля пропускаются
|
||||
— они canonical из Houses Catalog Stage 2c).
|
||||
save_detail_enrichment() пишет подмножество в listings; house_type + house_catalog_url
|
||||
сохраняются (#2009), остальные house-level поля (total_floors_house, elevators,
|
||||
concierge, closed_yard) пропускаются — они canonical из Houses Catalog Stage 2c.
|
||||
"""
|
||||
|
||||
# Identity
|
||||
|
|
@ -219,7 +219,8 @@ class DetailEnrichment:
|
|||
sale_type: str | None = None
|
||||
mortgage_available: bool | None = None
|
||||
|
||||
# House params (собираем, но НЕ сохраняем в listings — Stage 2c)
|
||||
# House params — house_type/house_catalog_url сохраняются в listings (#2009);
|
||||
# остальные пропускаются, canonical источник — Houses Catalog (Stage 2c)
|
||||
house_type: str | None = None
|
||||
total_floors_house: int | None = None
|
||||
passenger_elevators: int | None = None
|
||||
|
|
@ -683,7 +684,7 @@ def parse_detail_html(html: str, source_url: str) -> DetailEnrichment:
|
|||
if "возможна ипотека" in val.lower():
|
||||
mortgage_available = True
|
||||
elif field_name == "house_type":
|
||||
house_type = _map_lower(val, _HOUSE_TYPE_MAP)
|
||||
house_type = normalize_house_type(val)
|
||||
elif field_name == "total_floors_house":
|
||||
total_floors_house = _try_int(val)
|
||||
elif field_name == "passenger_elevators":
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ house_type != target штрафуется. Yandex SERP отдаёт SCREAMING-з
|
|||
- yandex SERP: building.buildingType — SCREAMING_SNAKE (MONOLIT, MONOLIT_BRICK, ...)
|
||||
- cian SERP: building.materialType — camelCase (monolith, monolithBrick,
|
||||
gasSilicateBlock, ...) — переиспользуется в #2008 (cian.py:815).
|
||||
- avito detail page: «Тип дома» — русские лейблы (монолитный, панельный, ...) —
|
||||
переиспользуется в #2009 (avito_detail.py) вместо локального дубля-мэппинга.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -52,6 +54,13 @@ _RAW_TO_CANON: dict[str, str] = {
|
|||
"aerocreteBlock": "block",
|
||||
"foamConcreteBlock": "block",
|
||||
"stalin": "brick", # «сталинка» — кирпич
|
||||
# ── avito detail page («Тип дома», русские лейблы) — reuse в #2009 ───────
|
||||
"монолитный": "monolith",
|
||||
"панельный": "panel",
|
||||
"кирпичный": "brick",
|
||||
"монолитно-кирпичный": "monolith_brick",
|
||||
"блочный": "block",
|
||||
"деревянный": "wood",
|
||||
}
|
||||
|
||||
# Регистронезависимый lookup. Лоуэркейс-ключи не коллизят между вокабулярами:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
"""Unit-тесты для house_type_normalizer (#2007).
|
||||
"""Unit-тесты для house_type_normalizer (#2007, #2009).
|
||||
|
||||
Покрывают:
|
||||
- normalize_house_type(): yandex SCREAMING + cian camelCase вокабуляры → канон,
|
||||
- normalize_house_type(): yandex SCREAMING + cian camelCase + avito русские
|
||||
лейблы вокабуляры → канон,
|
||||
- идемпотентность (normalize(normalize(x)) == normalize(x)),
|
||||
- None / '' / whitespace / 'other' / unknown → None.
|
||||
"""
|
||||
|
|
@ -53,6 +54,24 @@ def test_cian_camelcase_vocab(raw: str, expected: str) -> None:
|
|||
assert normalize_house_type(raw) == expected
|
||||
|
||||
|
||||
# ── avito detail page («Тип дома», русские лейблы) — reuse #2009 ────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raw", "expected"),
|
||||
[
|
||||
("монолитный", "monolith"),
|
||||
("панельный", "panel"),
|
||||
("кирпичный", "brick"),
|
||||
("монолитно-кирпичный", "monolith_brick"),
|
||||
("блочный", "block"),
|
||||
("деревянный", "wood"),
|
||||
],
|
||||
)
|
||||
def test_avito_russian_vocab(raw: str, expected: str) -> None:
|
||||
assert normalize_house_type(raw) == expected
|
||||
|
||||
|
||||
# ── регистронезависимость ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue