From 3547598d37643d15b0f4eb4744443ae7db1ed2ed Mon Sep 17 00:00:00 2001 From: IK Date: Sun, 13 Sep 2026 08:46:42 +0700 Subject: [PATCH] fix: prioritize media URL classification --- README.md | 4 ++-- data/media/manifest.json | 6 +++--- docs/ROADMAP.md | 2 +- docs/data-sources.md | 2 +- rf4_research/media_assets.py | 6 ++++-- tests/test_media_assets.py | 8 ++++++++ 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a96b92a..00e2496 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ RF4DB/RF4-STAT/RF4MAP/RF4 Posts сначала принимаются в изо Медиасборщик индексирует разрешённые изображения отдельно от публичного каталога: manifest хранит исходную страницу, URL, предполагаемый тип сущности и время обнаружения, а оригиналы сохраняются по SHA-256 без hotlink. Индексация страницы и загрузка каждого файла используют общий 30-минутный cooldown домена; непроверенный asset не публикуется автоматически. Локальный `media_cli --audit` без сетевых запросов проверяет хэши, файлы, MIME, размеры, approved-сопоставления и отсутствие бесхозных оригиналов. -`python -m rf4_research.media_cli --coverage` сравнивает manifest с датированным `data/media/catalog-baseline.json`: отдельно считает файлы, уникальные нормализованные подписи и кандидатов без подписи, поэтому дубли не завышают покрытие. Сейчас не покрыты минимум 21 рыба и 17 водоёмов, а до ручного review не подтверждены 251 рыба и все 19 водоёмов. Общий target снастей остаётся `null`, пока разрешённый источник не отдаст проверяемый полный счётчик. +`python -m rf4_research.media_cli --coverage` сравнивает manifest с датированным `data/media/catalog-baseline.json`: отдельно считает файлы, уникальные нормализованные подписи и кандидатов без подписи, поэтому дубли не завышают покрытие. Сейчас не покрыты минимум 24 рыбы и 17 водоёмов, а до ручного review не подтверждены 251 рыба и все 19 водоёмов. Общий target снастей остаётся `null`, пока разрешённый источник не отдаст проверяемый полный счётчик. -Актуальный внешний ориентир — 19 водоёмов и 252 вида рыб; локальная альфа пока содержит 2+2 сущности. Media-manifest включает 452 кандидата: 231 изображение рыбы, 146 приманок, 2 карты и 73 справочных изображения. Вручную проверены и сопоставлены 1 рыба, 2 приманки и 1 официальная схема; 446 файлов остаются в очереди, 1 URL признан невалидным. Полное число «снастей» пока не заявляется: приманки — лишь одна часть каталога наряду с удилищами, катушками, лесками, крючками и оснастками. +Актуальный внешний ориентир — 19 водоёмов и 252 вида рыб; локальная альфа пока содержит 2+2 сущности. Media-manifest включает 452 кандидата: 228 изображений рыб, 149 приманок, 2 карты и 73 справочных изображения. Вручную проверены и сопоставлены 1 рыба, 2 приманки и 1 официальная схема; 446 файлов остаются в очереди, 1 URL признан невалидным. Полное число «снастей» пока не заявляется: приманки — лишь одна часть каталога наряду с удилищами, катушками, лесками, крючками и оснастками. Для измерений на собственном сервере подготовлен read-only `deploy/load-smoke.py`: он считает p50/p95/max и HTTP-коды для activity/records, а при наличии `ADMIN_TOKEN` — staging/moderation. Методика и безопасные ступени нагрузки описаны в [docs/load-testing.md](docs/load-testing.md); локальные цифры не выдаются за production baseline. diff --git a/data/media/manifest.json b/data/media/manifest.json index bdaa8e8..4f16d8b 100644 --- a/data/media/manifest.json +++ b/data/media/manifest.json @@ -607,7 +607,7 @@ { "source_page": "https://rf4map.ru/fishes", "asset_url": "https://hb.ru-msk.vkcloud-storage.ru/fisher.bucket/bait/42637c6a481ba7f149aeefbc023e619a45c102c5dbeb5b3425897478995d1d34.png", - "entity_type": "fish", + "entity_type": "tackle", "label": "Краб и рыба 14", "external_id": "91", "source_pages": [ @@ -633,7 +633,7 @@ { "source_page": "https://rf4map.ru/fishes", "asset_url": "https://hb.ru-msk.vkcloud-storage.ru/fisher.bucket/bait/47c775e930a35c6b569cf32c9494e19889574ae30d5d2d7ce751374ecfd1f59b.png", - "entity_type": "fish", + "entity_type": "tackle", "label": "Чёрный перец и рыба 18", "external_id": "47", "source_pages": [ @@ -1036,7 +1036,7 @@ { "source_page": "https://rf4map.ru/fishes", "asset_url": "https://hb.ru-msk.vkcloud-storage.ru/fisher.bucket/bait/8169d3cc1771a70d466fa89b3dd55f4af422d09a5c2121ee46df983541dfff49.png", - "entity_type": "fish", + "entity_type": "tackle", "label": "Чёрный перец и рыба 16", "external_id": "46", "source_pages": [ diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 29b0378..b4ad5c0 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -30,7 +30,7 @@ - [x] **B08 · Семейства силуэтов рыб.** Три случайных hash-варианта заменены классификатором и отдельными формами `pike`, `salmonid`, `cyprinid`, `perch`, `catfish`, `eel`, `flatfish`, `marine`, плюс честный `generic`. Компонент допускает ручное переопределение family; название остаётся главным идентификатором. - [x] **B09 · Глифы снастей и приманок.** Добавлены SVG-глифы `spinner`, `wobbler`, `soft`, `boilie`, `worm`, `rig`, `unknown` и стабильная палитра по normalized name. Классификация срабатывает только по явным словам; глиф сопровождает текст в activity, лидере, уловах, рекордах и списке лучших приманок, не выдавая категорию за точную модель. - [x] **B10 · Визуальные отпечатки водоёмов.** Для каждого slug воспроизводимо выбираются один из восьми береговых контуров, число волн, положение точки и двухсимвольный индекс. Знак используется в каталоге и detail-hero; это явно абстрактный отпечаток, а не карта или игровая география. -- [ ] **B11 · Разрешённый media pipeline — в работе.** Версионированный baseline на 12.09.2026 содержит 19 водоёмов и 252 рыбы; полное число снастей неизвестно и остаётся `null`, а не подменяется числом приманок. Воспроизводимый `media_cli --coverage` считает уникальные нормализованные подписи, а не файлы: 231/252 fish-кандидатов (1 approved), 2/19 waterbody (0 approved), 146 tackle/bait (2 approved); ещё одна официальная Zig-Rig схема approved как reference. Следующие gaps: минимум 21 рыба, 17 водоёмов и все категории снастей за пределами найденных приманок; получить канонические перечни и устойчивые ID, затем загружать не более одного asset с домена за окно и вручную сопоставлять. RF4DB вернул 403 — повторять только после cooldown. Официальные `/media/fische/`, `/media/levels/` и `.bait_icon` records исследовать только через публичные контракты без обхода защиты. +- [ ] **B11 · Разрешённый media pipeline — в работе.** Версионированный baseline на 12.09.2026 содержит 19 водоёмов и 252 рыбы; полное число снастей неизвестно и остаётся `null`, а не подменяется числом приманок. Воспроизводимый `media_cli --coverage` считает уникальные нормализованные подписи, а не файлы: 228/252 fish-кандидатов (1 approved), 2/19 waterbody (0 approved), 149 tackle/bait (2 approved); ещё одна официальная Zig-Rig схема approved как reference. URL-классификация имеет приоритет над словами в названии, поэтому три приманки с «рыба» больше не искажают fish coverage. Следующие gaps: минимум 24 рыбы, 17 водоёмов и все категории снастей за пределами найденных приманок; получить канонические перечни и устойчивые ID, затем загружать не более одного asset с домена за окно и вручную сопоставлять. RF4DB вернул 403 — повторять только после cooldown. Официальные `/media/fische/`, `/media/levels/` и `.bait_icon` records исследовать только через публичные контракты без обхода защиты. - [x] **B12 · Эмблема сочетания.** Страница «водоём + рыба» получила составной атласный seal: собственный отпечаток водоёма пересекается со смысловым силуэтом рыбы. Так визуальная идентичность сопровождает всю иерархию каталога и не требует внешних изображений. - [x] **B13 · Навигационная леска атласа.** Разрозненные ссылки назад на detail-страницах заменены доступной breadcrumb-цепочкой с мотивом лески и узлов. Страница точки связывает главную, водоём и координаты; сочетание — каталог, водоём и рыбу. Текущий узел всегда подписан текстом и отмечен `aria-current`. - [x] **B14 · Атласные переходы сущностей.** Боковые списки рыб и водоёмов на detail-страницах получили компактные силуэты и отпечатки рядом с полным текстовым названием. Знаки продолжают систему каталога в рабочей навигации, а стрелка явно показывает переход к странице сочетания. diff --git a/docs/data-sources.md b/docs/data-sources.md index b31cc24..a1ab4f0 100644 --- a/docs/data-sources.md +++ b/docs/data-sources.md @@ -54,7 +54,7 @@ Прямой индекс `/media/fische/` вернул только общие изображения оформления (`flags/de.png`, `banner_de.png`): содержимое галереи загружается динамически. Эти файлы остаются `reference`, а не ошибочно маркируются как рыбы. Исследование допустимого gallery endpoint должно использовать обычный публичный контракт сайта и тот же общий cooldown, без обхода защит. -Контрольная сверка 12–13 сентября 2026 года: RF4DB показывает 19 водоёмов и 252 вида рыб. Страница RF4MAP `/fishes` дала 231 уникальный кандидат изображения рыбы и 146 изображений приманок. Это означает минимум 21 отсутствующую рыбью иконку; 146 нельзя считать числом всех снастей, поскольку в него не входят полные каталоги удилищ, катушек, лесок, крючков и прочих компонентов. Вручную подтверждены Ерш, Nasty Worm 4.5-002 и Личинка веснянки; остальные entity-кандидаты остаются карантинными. +Контрольная сверка 12–13 сентября 2026 года: RF4DB показывает 19 водоёмов и 252 вида рыб. Страница RF4MAP `/fishes` дала 228 уникальных кандидатов изображений рыб и 149 изображений приманок. Это означает минимум 24 отсутствующие рыбьи иконки; 149 нельзя считать числом всех снастей, поскольку в него не входят полные каталоги удилищ, катушек, лесок, крючков и прочих компонентов. Вручную подтверждены Ерш, Nasty Worm 4.5-002 и Личинка веснянки; остальные entity-кандидаты остаются карантинными. Страница рекордов визуально использует `.bait_icon`, но подтверждённый parser-контракт извлекает только `.bait_icon[title]`. Ни parser, ни `Fish`/`Waterbody`/`Bait` модели и API-схемы сейчас не сохраняют image URL. Даже если URL удастся извлечь из CSS или обновлённого DOM, он может быть presentation asset, меняться независимо от названия и покрывать только приманки, попавшие в рекорды. diff --git a/rf4_research/media_assets.py b/rf4_research/media_assets.py index 583d4a5..3775158 100644 --- a/rf4_research/media_assets.py +++ b/rf4_research/media_assets.py @@ -41,12 +41,14 @@ def _kind(url: str, label: str | None, context: str) -> str: value = f"{label or ''} {context}".casefold() if "/flags/" in path or "/themes/" in path or path.endswith(("/logo.png", "/logo.svg")): return "reference" + # A structural tackle path is stronger evidence than words in a product + # name (for example, bait named "Краб и рыба"). + if re.search(r"(?:^|[/_-])(?:bait|lure|rig|rod|reel|hook|tackle|köder|rute|rolle)(?:[/_.-]|$)", path): + return "tackle" if re.search(r"/(?:fish|fishes|fische|species)/", path) or re.search(r"/(?:fish|species)[_-]", path) or "/media/fische/" in value or any(word in value for word in ("рыба ", "fish: ", "fisch: ")): return "fish" if re.search(r"/(?:maps?|levels?|lakes?|waterbodies)/", path) or "/media/levels/" in value or any(word in value for word in ("карта ", "map of ", "gewässerkarte")): return "waterbody" - if re.search(r"(?:^|[/_-])(?:bait|lure|rig|rod|reel|hook|tackle|köder|rute|rolle)(?:[/_.-]|$)", path): - return "tackle" return "reference" diff --git a/tests/test_media_assets.py b/tests/test_media_assets.py index 3cf8685..4c13888 100644 --- a/tests/test_media_assets.py +++ b/tests/test_media_assets.py @@ -42,6 +42,14 @@ def test_rf4map_gateway_fish_filename_is_classified_as_fish() -> None: assert items[0].entity_type == "fish" +def test_tackle_path_wins_over_fish_word_in_product_name() -> None: + items = extract_media_candidates( + 'Краб и рыба 14', + source_page="https://example.test/fishes", + ) + assert items[0].entity_type == "tackle" + + def test_manifest_merges_and_binary_store_is_content_addressed(tmp_path: Path) -> None: item = extract_media_candidates('Bait worm', source_page="https://example.test")[0] first = merge_manifest(tmp_path / "manifest.json", [item])