From 99a5a0f73b0ccf70d2e00a1acf4857004cd882b7 Mon Sep 17 00:00:00 2001 From: Light1YT Date: Wed, 27 May 2026 13:11:22 +0000 Subject: [PATCH] =?UTF-8?q?fix(tradein):=20Yandex=20listing=5Fdate=20?= =?UTF-8?q?=E2=80=94=20keyword=20+=20absolute-no-year=20(#602)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/services/scrapers/yandex_helpers.py | 62 ++++++++++++++++++- .../backend/tests/test_yandex_helpers.py | 36 +++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/tradein-mvp/backend/app/services/scrapers/yandex_helpers.py b/tradein-mvp/backend/app/services/scrapers/yandex_helpers.py index 573f6533..a33d4501 100644 --- a/tradein-mvp/backend/app/services/scrapers/yandex_helpers.py +++ b/tradein-mvp/backend/app/services/scrapers/yandex_helpers.py @@ -188,17 +188,73 @@ def _parse_relative_date_yandex(s: str | None) -> date | None: return None +RE_RU_DATE_NO_YEAR = re.compile( + r"(\d{1,2})\s+(январ\w*|феврал\w*|март\w*|апрел\w*|ма[яй]|" + r"июн\w*|июл\w*|август\w*|сентябр\w*|октябр\w*|ноябр\w*|декабр\w*)" + r"(?!\s+\d{4})", + flags=re.I, +) +_RU_MONTH_PREFIXES: dict[str, int] = { + "январ": 1, "феврал": 2, "март": 3, "апрел": 4, + "ма": 5, "июн": 6, "июл": 7, "август": 8, + "сентябр": 9, "октябр": 10, "ноябр": 11, "декабр": 12, +} + + def parse_listing_date(text: str | None) -> date | None: """Parse listing date from Yandex card text. Tries in order: - 1. Absolute Russian date: '9 мая 2026' → date(2026, 5, 9) - 2. Relative date: 'N дней назад' → date.today() - N days - Returns None if neither matches. + 1. Keyword shortcut: 'сегодня' / 'вчера' / 'позавчера' → relative day + 2. Absolute Russian date with year: '9 мая 2026' → date(2026, 5, 9) + 3. Absolute without year: '18 мая' → current year (rollback to prev + year if month >30 дней в будущем) + 4. Relative date: 'N дней назад' → date.today() - N days + + Returns None if none match. """ + if not text: + return None + text_lower = text.lower() + + # 1. Keyword shortcuts (common on Yandex SERP for recent cards). + if "позавчера" in text_lower: + return date.today() - timedelta(days=2) + if "вчера" in text_lower: + return date.today() - timedelta(days=1) + if "сегодня" in text_lower or "только что" in text_lower: + return date.today() + + # 2. Absolute with year (existing). result = parse_ru_date(text) if result is not None: return result + + # 3. Absolute without year — infer current, rollback to prev if future. + m = RE_RU_DATE_NO_YEAR.search(text) + if m: + day = int(m.group(1)) + month_raw = m.group(2).lower() + month: int | None = None + for prefix, num in _RU_MONTH_PREFIXES.items(): + if month_raw.startswith(prefix): + month = num + break + if month is not None and 1 <= day <= 31: + today = date.today() + try: + parsed = date(today.year, month, day) + except ValueError: + parsed = None + if parsed and (parsed - today).days > 30: + try: + parsed = date(today.year - 1, month, day) + except ValueError: + parsed = None + if parsed: + return parsed + + # 4. Relative numeric. return _parse_relative_date_yandex(text) diff --git a/tradein-mvp/backend/tests/test_yandex_helpers.py b/tradein-mvp/backend/tests/test_yandex_helpers.py index 8f494c6f..87dc6504 100644 --- a/tradein-mvp/backend/tests/test_yandex_helpers.py +++ b/tradein-mvp/backend/tests/test_yandex_helpers.py @@ -132,6 +132,42 @@ def test_parse_ru_date_empty(): assert parse_ru_date(None) is None +# --- parse_listing_date (extended formats) --- +def test_listing_date_today_keyword(): + from datetime import timedelta + + from app.services.scrapers.yandex_helpers import parse_listing_date + assert parse_listing_date("сегодня в 12:30") == date.today() + assert parse_listing_date("вчера в 18:45") == date.today() - timedelta(days=1) + assert parse_listing_date("позавчера") == date.today() - timedelta(days=2) + assert parse_listing_date("только что") == date.today() + + +def test_listing_date_absolute_no_year(): + """Yandex SERP формат «18 мая» без года — infer current year.""" + from app.services.scrapers.yandex_helpers import parse_listing_date + today = date.today() + # Pick a date в прошлом этого года, чтобы тест стабильный + if today.month >= 2: + target = date(today.year, today.month - 1, 15) + else: + target = date(today.year - 1, 12, 15) + ru_months = { + 1: "января", 2: "февраля", 3: "марта", 4: "апреля", + 5: "мая", 6: "июня", 7: "июля", 8: "августа", + 9: "сентября", 10: "октября", 11: "ноября", 12: "декабря", + } + text = f"{target.day} {ru_months[target.month]}" + assert parse_listing_date(text) == target + + +def test_listing_date_relative_still_works(): + from datetime import timedelta + + from app.services.scrapers.yandex_helpers import parse_listing_date + assert parse_listing_date("5 дней назад") == date.today() - timedelta(days=5) + + # --- parse_dmy --- def test_parse_dmy_basic(): assert parse_dmy("17.02.2026") == date(2026, 2, 17)