68 lines
3.9 KiB
Python
68 lines
3.9 KiB
Python
"""Нормализация repair_state → каноничный enum для listings.repair_state.
|
||
|
||
Целевой enum: needs_repair / standard / good / excellent.
|
||
Используется во всех инgest-путях: avito_detail, cian SERP, cian_detail.
|
||
|
||
Источники raw-значений:
|
||
- Avito HTML: «Ремонт: евро» → avito_detail._REPAIR_MAP → «euro» → good
|
||
- Cian SERP JSON: offer.decoration → «euro» / «cosmetic» / «without» / ...
|
||
- Cian Detail JSON: offer.repairType → «cosmetic» / «design» / «no» / «euro» / ...
|
||
|
||
Маппинг основан на _IMV_REPAIR_MAP из estimator.py (обратная форма):
|
||
needs_repair→«required», standard→«cosmetic», good→«euro», excellent→«designer».
|
||
|
||
Связь с estimator._REPAIR_COEF:
|
||
needs_repair=0.94 / standard=1.00 / good=1.05 / excellent=1.10
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Все известные raw-значения с источником в комментарии
|
||
_RAW_TO_ENUM: dict[str, str] = {
|
||
# ── needs_repair ────────────────────────────────────────────────────────────
|
||
"required": "needs_repair", # Avito: «требуется» / «без ремонта» (через _REPAIR_MAP)
|
||
"without": "needs_repair", # Cian: без ремонта
|
||
"no": "needs_repair", # Cian: нет ремонта (синоним without)
|
||
"rough": "needs_repair", # Cian/общий: черновая отделка
|
||
|
||
# ── standard ────────────────────────────────────────────────────────────────
|
||
"cosmetic": "standard", # Avito/Cian: косметический
|
||
|
||
# ── good ────────────────────────────────────────────────────────────────────
|
||
"euro": "good", # Avito/Cian: евро
|
||
"fine": "good", # Cian: хорошая отделка (предчистовая / whitebox)
|
||
|
||
# ── excellent ───────────────────────────────────────────────────────────────
|
||
"designer": "excellent", # Avito: дизайнерский
|
||
"design": "excellent", # Cian: дизайнерский (camelCase вариант)
|
||
}
|
||
|
||
# Допустимые значения целевого enum — для pass-through защиты
|
||
_VALID_ENUM: frozenset[str] = frozenset({"needs_repair", "standard", "good", "excellent"})
|
||
|
||
|
||
def normalize_repair_state(raw: str | None) -> str | None:
|
||
"""Преобразовать raw repair-значение в каноничный enum.
|
||
|
||
Если raw уже является корректным enum-значением — вернуть as-is (идемпотентно).
|
||
Если raw не распознан — вернуть None + залогировать WARNING один раз.
|
||
|
||
Args:
|
||
raw: сырое значение из парсера (e.g. «euro», «without», «design»)
|
||
или None.
|
||
|
||
Returns:
|
||
Одно из needs_repair / standard / good / excellent, либо None.
|
||
"""
|
||
if raw is None:
|
||
return None
|
||
# Pass-through: уже нормализованный enum (idempotency — нужно при ре-обработке)
|
||
if raw in _VALID_ENUM:
|
||
return raw
|
||
result = _RAW_TO_ENUM.get(raw)
|
||
if result is None:
|
||
logger.warning("repair_state_normalizer: unknown raw value %r — stored as NULL", raw)
|
||
return result
|