Files
2026-09-03 08:18:35 +07:00

704 lines
37 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RF4 Spotter — идеи и техническое задание для MVP
> Рабочий документ для передачи в Codex. Его задача — дать Codex достаточно контекста, чтобы начать проектирование и разработку без пересказа всей переписки.
## 1. Идея продукта
**RF4 Spotter** — неофициальный информационный сайт для игроков «Русской Рыбалки 4» (Russian Fishing 4), который отвечает на практический вопрос:
> **Куда мне пойти ловить прямо сейчас, какую снасть или приманку взять и насколько свежа эта информация?**
Сайт должен объединять:
- официальные рекорды RF4;
- пользовательские сообщения об уловах;
- координаты точек;
- приманки, наживки, оснастки и способы проводки;
- время поимки;
- историю активности;
- простой и понятный индекс клёва.
Главная ценность — не вечный справочник старых точек, а **оценка текущей активности с указанием свежести и надёжности данных**.
## 2. Что известно об источниках данных
### Официальные данные
У RF4, по предварительным данным, нет документированного публичного API со всеми уловами и координатами. Однако официальный сайт публикует таблицы рекордов и рейтингов, которые можно разбирать автоматически.
Предположительно из таблиц рекордов можно получать:
- регион;
- категорию рекорда;
- игрока;
- вид рыбы;
- вес;
- водоём;
- приманку или наживку;
- дату.
Исходные точки для исследования:
- официальный раздел рекордов: <https://rf4game.de/records/region/RU/>;
- пример открытого парсера: <https://github.com/hurfy/rf4-api>;
- существующий статистический сервис: <https://rf4-stat.ru/help/>;
- пример пользовательской базы: <https://download.rf4db.com/ru/catches>.
Перед реализацией парсера нужно проверить актуальную 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 | 0100 |
| `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. Главное продуктовое правило
> Если данных недостаточно или они устарели, сайт должен честно сказать об этом. «Не знаем» полезнее, чем красивая, но выдуманная рекомендация.