# RF4 Spotter — идеи и техническое задание для MVP > Рабочий документ для передачи в Codex. Его задача — дать Codex достаточно контекста, чтобы начать проектирование и разработку без пересказа всей переписки. ## 1. Идея продукта **RF4 Spotter** — неофициальный информационный сайт для игроков «Русской Рыбалки 4» (Russian Fishing 4), который отвечает на практический вопрос: > **Куда мне пойти ловить прямо сейчас, какую снасть или приманку взять и насколько свежа эта информация?** Сайт должен объединять: - официальные рекорды RF4; - пользовательские сообщения об уловах; - координаты точек; - приманки, наживки, оснастки и способы проводки; - время поимки; - историю активности; - простой и понятный индекс клёва. Главная ценность — не вечный справочник старых точек, а **оценка текущей активности с указанием свежести и надёжности данных**. ## 2. Что известно об источниках данных ### Официальные данные У RF4, по предварительным данным, нет документированного публичного API со всеми уловами и координатами. Однако официальный сайт публикует таблицы рекордов и рейтингов, которые можно разбирать автоматически. Предположительно из таблиц рекордов можно получать: - регион; - категорию рекорда; - игрока; - вид рыбы; - вес; - водоём; - приманку или наживку; - дату. Исходные точки для исследования: - официальный раздел рекордов: ; - пример открытого парсера: ; - существующий статистический сервис: ; - пример пользовательской базы: . Перед реализацией парсера нужно проверить актуальную HTML-структуру, сетевые запросы страницы и условия использования сайта. Нельзя считать приведённые URL и структуру полей неизменными. ### Пользовательские данные Официальные таблицы, вероятно, не содержат точных координат, проводки и всех обычных уловов. Их нужно собирать отдельно: - через собственную форму на сайте; - позднее — из разрешённых Telegram-, Discord- или VK-источников; - позднее — из скриншотов с подтверждением распознанных полей пользователем. ### Важное ограничение В MVP нельзя перехватывать трафик игрового клиента, внедряться в процесс игры, обходить античит или заниматься reverse engineering закрытого протокола. Проект должен работать только с публичными веб-данными и добровольно переданной пользователями информацией. ## 3. Для кого делаем Основной пользователь — игрок RF4, который не хочет просматривать десятки сообщений в сообществах перед каждой игровой сессией. Типичный запрос: ```text Уровень: 19 Способ ловли: спиннинг Доступные водоёмы: до Куори включительно Цель: серебро ``` Ожидаемый ответ: ```text Сейчас активна щука на Вьюнке Точка: 110:103 Приманка: Spiker #2 01-015 Последнее подтверждение: 34 минуты назад Уловов за 12 часов: 27 от 11 игроков Активность: высокая Уверенность: 87 из 100 ``` ## 4. Главная гипотеза MVP Игроку полезнее несколько свежих и объяснимых рекомендаций, чем большая база точек без даты и источника. MVP считается полезным, если пользователь может: 1. открыть главную страницу; 2. выбрать водоём, рыбу или способ ловли; 3. увидеть активные комбинации «водоём + рыба + точка»; 4. понять, на чём основана оценка; 5. открыть карточку точки с приманками и динамикой; 6. отправить свой улов через простую форму. ## 5. Состав MVP ### 5.1. Главная страница «Что клюёт сейчас» На странице должны быть: - фильтр по водоёму; - фильтр по рыбе; - фильтр по способу ловли; - фильтр по периоду: 6, 12, 24, 72 часа; - сортировка по активности, уверенности и свежести; - карточки активных точек; - явное время последнего обновления данных. Карточка содержит: - водоём; - рыбу; - координаты; - лучшую приманку или наживку; - количество уловов за выбранный период; - количество разных авторов; - средний и максимальный вес; - время последнего подтверждения; - `activity_score` от 0 до 100; - `confidence_score` от 0 до 100; - короткое текстовое объяснение оценки. ### 5.2. Страница точки URL вида: ```text /spots/vyunok/110-103/pike ``` Содержимое: - координаты и описание места; - статистика уловов за 24 часа, 3 дня и 7 дней; - список наиболее результативных приманок или наживок; - способы проводки и скорость, если указаны; - распределение уловов по игровому времени; - последние подтверждённые уловы; - источник и свежесть каждой записи; - предупреждение, если данных мало или они устарели. ### 5.3. Страница официальных рекордов - таблица импортированных рекордов; - фильтры по рыбе, водоёму, категории и периоду; - дата последнего успешного импорта; - отметка, что это данные официального сайта RF4; - ссылка на исходную страницу. ### 5.4. Форма добавления улова Обязательные поля: - рыба; - вес; - водоём; - координаты `x:y`; - дата и время отправки. Необязательные поля: - приманка или наживка; - тип оснастки; - проводка; - скорость проводки; - игровое время; - комментарий; - имя или ник игрока; - ссылка на исходную публикацию; - скриншот. Для первой версии скриншот хранится как подтверждение, но **не распознаётся автоматически**. ### 5.5. Простая модерация Минимальная закрытая страница администратора: - список новых записей; - просмотр всех полей и скриншота; - действия «одобрить», «отклонить», «исправить»; - причина отклонения; - журнал изменения статуса. До одобрения пользовательский улов не влияет на публичный индекс клёва. ## 6. Что не входит в MVP - перехват данных из запущенной игры; - reverse engineering протокола RF4; - OCR и компьютерное зрение для скриншотов; - автоматический сбор всех публикаций Telegram, Discord и VK; - мобильное приложение; - сложная персонализация по снастям и бюджету; - прогнозирование клёва с помощью ML; - комментарии и социальная сеть; - полноценная интерактивная карта каждого водоёма; - автоматическое определение игровых обновлений и миграций рыбы. Эти возможности допустимы после проверки основного сценария. ## 7. Предлагаемый стек ### Вариант для первого релиза - **Frontend:** Astro + TypeScript; - **интерактивные компоненты:** React или Svelte islands только там, где они нужны; - **стили:** Tailwind CSS либо обычный CSS с дизайн-токенами; - **Backend API:** Python 3.12 + FastAPI; - **ORM и миграции:** SQLAlchemy 2 + Alembic; - **БД:** PostgreSQL; - **фоновые задания:** APScheduler или отдельный CLI, запускаемый cron; - **парсер:** сначала `httpx` + BeautifulSoup; Playwright только если обычного HTTP недостаточно; - **хранение скриншотов:** S3-совместимое объектное хранилище; - **локальный запуск:** Docker Compose; - **тесты:** pytest для backend, Vitest для frontend, Playwright для ключевого end-to-end сценария. На старте Celery и Redis не нужны: периодический импорт можно сделать отдельной идемпотентной CLI-командой. ## 8. Архитектура ```text Официальный сайт RF4 ──> импортёр рекордов ──┐ │ Форма пользователя ──> модерация ───────────┼──> PostgreSQL │ │ Ручной импорт CSV/JSON ──────────────────────┘ │ v расчёт активности │ v FastAPI JSON API │ v Astro-сайт ``` Предлагаемая структура репозитория: ```text rf4-spotter/ ├── README.md ├── .env.example ├── compose.yaml ├── apps/ │ ├── web/ # Astro │ └── api/ # FastAPI ├── packages/ │ └── contracts/ # OpenAPI/types, если понадобится ├── data/ │ └── fixtures/ # обезличенные тестовые HTML/JSON ├── docs/ │ ├── RF4_MVP_SPEC.md │ └── data-sources.md └── scripts/ └── seed_demo_data.* ``` ## 9. Модель данных Названия таблиц и полей можно скорректировать после прототипа, но модель должна отделять справочники, исходные наблюдения и рассчитанные агрегаты. ### `fish` | Поле | Тип | Назначение | |---|---|---| | `id` | UUID | идентификатор | | `slug` | text unique | значение для URL | | `name_ru` | text unique | название рыбы | | `trophy_weight_g` | integer nullable | порог трофея, если известен | ### `waterbody` | Поле | Тип | Назначение | |---|---|---| | `id` | UUID | идентификатор | | `slug` | text unique | значение для URL | | `name_ru` | text unique | название водоёма | | `unlock_level` | integer nullable | уровень открытия | ### `bait` Объединённый справочник наживок и искусственных приманок для MVP. | Поле | Тип | Назначение | |---|---|---| | `id` | UUID | идентификатор | | `name` | text | отображаемое название | | `normalized_name` | text unique | ключ для сопоставления | | `kind` | enum | `bait`, `lure`, `unknown` | ### `spot` | Поле | Тип | Назначение | |---|---|---| | `id` | UUID | идентификатор | | `waterbody_id` | UUID FK | водоём | | `x` | integer | координата X | | `y` | integer | координата Y | | `description` | text nullable | заметка о месте | Уникальный ключ MVP: `(waterbody_id, x, y)`. ### `catch_report` | Поле | Тип | Назначение | |---|---|---| | `id` | UUID | идентификатор | | `fish_id` | UUID FK | рыба | | `spot_id` | UUID FK nullable | точка; у официального рекорда её может не быть | | `waterbody_id` | UUID FK | водоём хранится явно | | `bait_id` | UUID FK nullable | приманка или наживка | | `weight_g` | integer | вес в граммах | | `rig_type` | text nullable | тип оснастки | | `retrieve_method` | text nullable | способ проводки | | `retrieve_speed` | integer nullable | скорость проводки | | `game_time` | time nullable | игровое время | | `caught_at` | timestamptz nullable | время поимки, если известно | | `reported_at` | timestamptz | время попадания в систему | | `player_name` | text nullable | ник игрока | | `source_type` | enum | `official_record`, `user`, `manual_import` | | `source_url` | text nullable | ссылка на источник | | `source_external_id` | text nullable | ключ для дедупликации | | `source_confidence` | smallint | 0–100 | | `moderation_status` | enum | `pending`, `approved`, `rejected` | | `screenshot_key` | text nullable | ключ объекта в хранилище | | `raw_payload` | jsonb nullable | исходные данные импортёра | ### `official_record_import` | Поле | Тип | Назначение | |---|---|---| | `id` | UUID | запуск импортёра | | `started_at` | timestamptz | начало | | `finished_at` | timestamptz nullable | завершение | | `status` | enum | `running`, `success`, `partial`, `failed` | | `source_url` | text | источник | | `rows_seen` | integer | найдено строк | | `rows_created` | integer | добавлено записей | | `rows_updated` | integer | обновлено записей | | `error_summary` | text nullable | краткая ошибка | ### `moderation_event` Хранит историю решений по пользовательскому сообщению: кто, когда, какой статус установил и почему. ## 10. Нормализация и дедупликация Это критическая часть проекта. Нужно: - хранить исходное значение из источника в `raw_payload`; - нормализовать пробелы, регистр, дефисы и десятичные разделители; - вести таблицу алиасов для рыб, водоёмов и приманок; - вес всегда приводить к граммам; - время хранить с явным указанием, реальное оно или игровое; - официальный импорт делать идемпотентным; - не считать две одинаковые строки двумя независимыми подтверждениями. Для официальных рекордов ключ дедупликации можно сначала строить из нормализованной комбинации: ```text region + category + player + fish + weight_g + waterbody + bait + record_date ``` Этот ключ следует хешировать и хранить в `source_external_id`. ## 11. Индекс активности ### Требование Алгоритм MVP должен быть простым, детерминированным и объяснимым. Нельзя показывать псевдоточную оценку без расшифровки. Расчёт производится для группы: ```text водоём + точка + рыба + выбранный временной интервал ``` Предлагаемый первый вариант: ```text freshness_i = exp(-age_hours_i / 18) weighted_reports = sum(freshness_i * source_confidence_i / 100) unique_players = количество уникальных непустых player_name trophy_bonus = min(1, trophy_count / 3) activity_raw = 55 * min(1, weighted_reports / 12) + 25 * min(1, unique_players / 6) + 20 * trophy_bonus activity_score = round(activity_raw) ``` Начальная оценка уверенности: ```text confidence_score = round( 45 * min(1, approved_reports / 10) + 35 * min(1, unique_players / 5) + 20 * average_source_confidence / 100 ) ``` Правила отображения: - меньше трёх одобренных пользовательских наблюдений — пометка «данных мало»; - нет уловов за 72 часа — пометка «данные устарели»; - один игрок не может создать высокую уверенность большим количеством сообщений; - официальный рекорд без координат нельзя автоматически приписывать конкретной точке; - значения 0–100 — сравнительные индексы сервиса, а не вероятность поймать рыбу. Карточка должна объяснять результат, например: ```text Высокая активность: 18 свежих уловов от 7 игроков. Последнее подтверждение 42 минуты назад. Уверенность средняя: часть сообщений без скриншотов. ``` Формулу после накопления реальных данных нужно откалибровать. ## 12. API MVP Публичные методы: ```http GET /api/v1/activity GET /api/v1/spots/{spot_id} GET /api/v1/spots/{spot_id}/catches GET /api/v1/records GET /api/v1/fishes GET /api/v1/waterbodies GET /api/v1/baits POST /api/v1/catch-reports ``` Пример фильтров активности: ```http GET /api/v1/activity?waterbody=vyunok&fish=pike&hours=24&method=spinning ``` Административные методы: ```http GET /api/v1/admin/catch-reports?status=pending PATCH /api/v1/admin/catch-reports/{id} POST /api/v1/admin/imports/official-records GET /api/v1/admin/imports ``` Все списочные методы должны иметь пагинацию, предсказуемую сортировку и валидацию фильтров. ## 13. Импорт официальных рекордов Импортёр должен быть отдельным адаптером, чтобы изменение вёрстки RF4 не затронуло остальное приложение. Интерфейс адаптера: ```python class OfficialRecordsSource(Protocol): async def fetch(self, region: str, category: str) -> list[RawRecord]: ... ``` Этапы: 1. загрузить страницу или JSON/XHR, если он существует; 2. сохранить диагностические метаданные ответа; 3. разобрать строки в `RawRecord`; 4. нормализовать значения; 5. вычислить ключ дедупликации; 6. добавить или обновить записи транзакционно; 7. записать результат запуска в `official_record_import`. Требования: - таймауты и повторы с ограничением; - понятный User-Agent с названием проекта и контактным URL, когда он появится; - умеренная частота запросов; - кэширование; - фикстуры реальных HTML-ответов для unit-тестов; - отказ с понятной ошибкой, если ожидаемые колонки исчезли; - никакого молчаливого импорта перепутанных полей; - возможность запустить командой вроде `python -m app.cli import-records`; - расписание не чаще необходимого; начать с одного раза в 30–60 минут. Если официальный сайт отдаёт данные через внутренний JSON/XHR endpoint, нужно предпочесть его HTML-парсингу, но только если он доступен без обхода защиты. ## 14. Интерфейс и визуальный принцип Сайт должен выглядеть как современный полезный инструмент, а не как форум или перегруженная игровая база. Принципы: - тёмная и светлая темы допустимы, но первая версия может иметь одну хорошо сделанную тему; - важнее всего: рыба, водоём, точка, свежесть и рабочая приманка; - цветовая шкала активности не должна быть единственным носителем смысла; - на мобильном карточки должны читаться без горизонтальной прокрутки; - таблица рекордов на мобильном превращается в карточки; - возле любой оценки показывать, на каких данных она основана; - не смешивать реальное время и игровое время; - не выдавать старую точку за текущий клёв. Минимальные состояния каждой страницы: - загрузка; - данные есть; - данных нет; - данных мало; - источник временно недоступен; - ошибка валидации. ## 15. Безопасность и приватность - не принимать исполняемые файлы; - проверять MIME-тип, расширение и размер скриншотов; - удалять EXIF из загруженных изображений; - генерировать серверные имена объектов; - ограничить частоту отправки формы; - добавить honeypot или CAPTCHA после появления спама; - экранировать пользовательский текст; - не публиковать IP и технические идентификаторы; - ник игрока сделать необязательным; - предусмотреть удаление пользовательского сообщения; - административную часть защитить аутентификацией; - секреты хранить только в переменных окружения; - не коммитить `.env` и реальные скриншоты пользователей. ## 16. Критерии готовности MVP MVP готов, когда: - проект поднимается одной документированной командой через Docker Compose; - миграции создают чистую БД; - seed-команда добавляет демонстрационные водоёмы, рыб, приманки и уловы; - импортёр получает или разбирает официальные рекорды из актуального источника; - повторный импорт не создаёт дубликаты; - сбой источника не удаляет ранее загруженные данные; - главная страница показывает активные точки и фильтруется; - карточка точки объясняет активность и уверенность; - пользователь может отправить улов; - администратор может его одобрить или отклонить; - одобренный улов появляется в публичной статистике; - есть автоматические тесты главного сценария; - README описывает запуск, настройку, импорт, тесты и ограничения проекта. ## 17. Этапы разработки ### Этап 0. Исследование источника - проверить официальный сайт RF4 и его сетевые запросы; - зафиксировать доступные регионы и категории; - проверить актуальность проекта `hurfy/rf4-api`; - сохранить небольшие HTML/JSON-фикстуры; - описать риски и ограничения в `docs/data-sources.md`; - не писать весь продукт, пока не доказано, что хотя бы один источник стабильно разбирается. Результат: маленький исследовательский скрипт и документ с подтверждённой структурой данных. ### Этап 1. Каркас и демонстрационные данные - создать монорепозиторий; - настроить Astro, FastAPI, PostgreSQL и миграции; - создать модель данных; - добавить seed; - реализовать read-only API; - сверстать главную и страницу точки на демоданных. ### Этап 2. Официальные рекорды - реализовать адаптер источника; - добавить нормализацию и дедупликацию; - добавить журнал запусков; - сделать страницу рекордов; - добавить периодический запуск. ### Этап 3. Пользовательские уловы - форма; - загрузка скриншота; - модерация; - rate limit; - включение одобренных записей в статистику. ### Этап 4. Индекс клёва - агрегаты; - активность и уверенность; - человекочитаемое объяснение; - тесты на свежесть, дубликаты и вклад разных игроков. ### Этап 5. Пилот - наполнить базу небольшим набором реальных данных с разрешёнными источниками; - дать нескольким игрокам протестировать сценарий; - собрать обратную связь; - только после этого решать, нужен ли OCR и импорт сообществ. ## 18. Идеи после MVP Приоритет определять по реальному использованию: - распознавание скриншота с обязательным подтверждением пользователем; - Telegram-бот для отправки улова; - импорт из разрешённых каналов и сообществ; - персональный профиль: уровень, открытые водоёмы, снасти, бюджет; - режим «куда пойти прямо сейчас»; - сравнение приманок; - динамика клёва по игровому времени; - обнаружение смены рабочих точек после обновлений; - уведомление, когда активизировалась выбранная рыба; - карта водоёма; - публичный API для сообщества; - репутация источников и авторов; - мультиязычность и поддержка разных регионов RF4. ## 19. Открытые вопросы Codex не должен молча принимать решения по этим пунктам, если они становятся блокирующими: 1. Как будет называться проект и какой домен использовать? 2. Нужна ли авторизация обычных пользователей в первом релизе? 3. Где размещать приложение и объектное хранилище? 4. Можно ли использовать реальные ники игроков в публичной выдаче? 5. Какие именно официальные категории рекордов импортировать первыми? 6. Какие водоёмы и рыбы нужны в пилотной базе? 7. Какая лицензия будет у кода и данных? 8. Разрешают ли правила и `robots.txt` выбранную частоту автоматического сбора? Для начала разработки допустимы безопасные значения по умолчанию: - рабочее название `RF4 Spotter`; - без регистрации обычных пользователей; - один регион RU; - одна или две основные категории рекордов; - локальное S3-совместимое хранилище MinIO; - демонстрационные ники и данные в seed; - импорт раз в 60 минут. ## 20. Как работать над проектом в Codex 1. Создать пустой репозиторий и положить этот файл в `docs/RF4_MVP_SPEC.md`. 2. Открыть корень репозитория в Codex. 3. Дать Codex сначала исследовательскую задачу из блока ниже. 4. Попросить фиксировать решения в `docs/` и обновлять README. 5. Делить работу на небольшие проверяемые этапы, а не просить сразу «сделать весь сайт». 6. После каждого этапа просить запускать тесты и показывать, что именно готово. 7. Не передавать Codex пароли, токены и игровые учётные данные; использовать `.env.example`. ### Первый промпт для Codex ```text Прочитай docs/RF4_MVP_SPEC.md целиком. Пока не создавай весь сайт. Выполни только этап 0 — исследование источников официальных рекордов RF4. Задачи: 1. Изучи текущую структуру официальной страницы рекордов и её сетевые запросы. 2. Проверь, существует ли доступный JSON/XHR endpoint, который можно использовать без обхода защиты. 3. Изучи архитектурные идеи проекта https://github.com/hurfy/rf4-api, но не копируй код вслепую и проверь его актуальность. 4. Создай минимальный исследовательский скрипт, который получает одну страницу/категорию и преобразует записи в типизированную внутреннюю структуру. 5. Добавь обезличенную небольшую фикстуру и unit-тест парсера. 6. Создай docs/data-sources.md: подтверждённые поля, URL, параметры, ограничения, рекомендуемую частоту опроса и риски поломки. 7. Не исследуй сетевой протокол игры, не запускай игровой клиент и не обходи защиту сайтов. Перед изменениями изучи репозиторий и предложи краткий план. После работы запусти тесты и сообщи, что подтверждено фактически, а что осталось предположением. ``` ### Второй промпт после успешного исследования ```text Прочитай docs/RF4_MVP_SPEC.md и docs/data-sources.md. Реализуй этап 1: каркас приложения на Astro + TypeScript, FastAPI, PostgreSQL, SQLAlchemy 2 и Alembic. Требования: - локальный запуск через Docker Compose; - модели и миграции для основных сущностей MVP; - seed с демонстрационными данными; - read-only API для активности, точек и справочников; - главная страница и страница точки; - адаптивная вёрстка; - тесты backend и одного главного пользовательского сценария; - README с точными командами запуска и проверки. Используй данные только из seed: реальный импортёр будет отдельным этапом. Сначала предложи план и список файлов, затем реализуй и проверь результат. ``` ## 21. Короткое описание проекта для README ```text RF4 Spotter — неофициальный сервис свежих точек и статистики клёва для Russian Fishing 4. Он объединяет публичные рекорды и подтверждённые пользовательские уловы, показывает свежесть источников и рассчитывает объяснимые индексы активности и уверенности. Проект не взаимодействует с игровым клиентом и не является официальным продуктом RF4. ``` ## 22. Главное продуктовое правило > Если данных недостаточно или они устарели, сайт должен честно сказать об этом. «Не знаем» полезнее, чем красивая, но выдуманная рекомендация.