"""Валидация позиций прайса — правила ТЗ §4.4. Каждое правило возвращает флаги; по их уровню определяется статус позиции: ошибка → error, предупреждение → needs_review, иначе → done. Конвертация валют и сравнение с прошлой версией (детектор аномалий) тоже здесь. """ from __future__ import annotations from dataclasses import dataclass from datetime import date # Ориентировочные курсы к тенге. В проде — курс Нацбанка на дату прайса. RATES_TO_KZT: dict[str, float] = {"KZT": 1.0, "USD": 480.0, "RUB": 5.3} ANOMALY_RATIO = 0.5 # изменение цены более чем на 50% к прошлой версии — аномалия @dataclass class Flag: """Замечание валидации по одной позиции.""" level: str # info / warning / error code: str message: str def validate_name(name: str | None) -> list[Flag]: if name and name.strip(): return [] return [Flag("error", "empty_name", "пустое название услуги")] def validate_prices(prices: dict[str, float]) -> list[Flag]: flags: list[Flag] = [] for tier, value in prices.items(): if value is None or value <= 0: flags.append( Flag("warning", "price_nonpositive", f"тариф «{tier}»: цена не положительна") ) resident, nonresident = prices.get("resident"), prices.get("nonresident") if resident and nonresident and nonresident < resident: flags.append( Flag("warning", "nonresident_below_resident", "цена нерезидента ниже цены резидента") ) return flags def validate_date(effective_date: date | None, today: date | None = None) -> list[Flag]: if effective_date and today and effective_date > today: return [Flag("warning", "future_date", "дата прайса в будущем")] return [] def validate_anomaly(new_price: float | None, previous_price: float | None) -> list[Flag]: if ( previous_price and new_price and abs(new_price - previous_price) / previous_price > ANOMALY_RATIO ): return [ Flag( "warning", "price_anomaly", f"цена изменилась более чем на {int(ANOMALY_RATIO * 100)}% " f"(было {previous_price:g}, стало {new_price:g})", ) ] return [] def to_kzt(amount: float, currency: str, rates: dict[str, float] | None = None) -> float: """Перевести сумму в тенге по курсу (оригинал сохраняется отдельно).""" rates = rates or RATES_TO_KZT return amount * rates.get(currency.upper(), 1.0) def validate_item( name: str | None, prices: dict[str, float], *, currency: str = "KZT", effective_date: date | None = None, previous_resident: float | None = None, today: date | None = None, ) -> tuple[str, list[Flag]]: """Проверить позицию и вернуть (статус, флаги). Статус: error — нельзя сохранять как есть; needs_review — на ручную проверку; done — чисто. """ flags: list[Flag] = [] flags += validate_name(name) flags += validate_prices(prices) flags += validate_date(effective_date, today) if previous_resident is not None: flags += validate_anomaly(prices.get("resident"), previous_resident) if any(f.level == "error" for f in flags): return "error", flags if any(f.level == "warning" for f in flags): return "needs_review", flags return "done", flags