Complete stage 0 source research
This commit is contained in:
@@ -0,0 +1,703 @@
|
||||
# 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 | 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. Главное продуктовое правило
|
||||
|
||||
> Если данных недостаточно или они устарели, сайт должен честно сказать об этом. «Не знаем» полезнее, чем красивая, но выдуманная рекомендация.
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Источники данных RF4: исследование этапа 0
|
||||
|
||||
Дата проверки: **2 сентября 2026 года**. Исследовалась только публичная веб-страница; игровой клиент, его трафик и закрытые протоколы не исследовались.
|
||||
|
||||
## Краткий вывод
|
||||
|
||||
Для первого адаптера следует получать серверный HTML страницы `https://rf4game.de/records/region/RU/`. На момент проверки таблица уже находится в исходном HTML и не требует JavaScript, авторизации или обхода защиты. Доступного JSON/XHR endpoint именно для рекордов не обнаружено.
|
||||
|
||||
Страница содержит общий WordPress AJAX-клиент с адресом `/wp-admin/admin-ajax.php`, но опубликованные на странице действия `gajax_Ratings` относятся к рейтингу игроков (`rating`, `rating_list`, `best`). Действия для таблицы рекордов не объявлены. Поэтому использовать или подбирать недокументированные AJAX-параметры не рекомендуется.
|
||||
|
||||
## Подтверждённые URL и параметры
|
||||
|
||||
- Абсолютные рекорды региона RU: `https://rf4game.de/records/region/RU/`.
|
||||
- Недельная форма URL: `https://rf4game.de/records/weekly/region/RU/`.
|
||||
- В навигации страницы перечислены регионы: `GL`, `RU`, `DE`, `US`, `FR`, `CN`, `PL`, `KR`, `JP`, `EN`.
|
||||
- В навигации категорий видны: `records`, `ultralight`, `recordslight`, `bottomlight`, `sea`, `telestick`.
|
||||
|
||||
Категория и регион кодируются сегментами URL, а не query-параметрами. Фактический успешный ответ абсолютной страницы был `200`, `text/html; charset=UTF-8`, с `Cache-Control: no-cache`. Запрос `GET /robots.txt` вернул `404`, то есть явных инструкций для роботов на этом пути при проверке не было. Это не является разрешением на интенсивный сбор.
|
||||
|
||||
Серия быстрых проверок категорий позже получила одинаковые небольшие HTML-ответы вместо полных таблиц. Это может быть временной защитой или ограничением частоты; вывод требует повторной осторожной проверки. Именно поэтому исследовательский скрипт проверяет наличие таблицы и завершает работу с ошибкой, а не принимает произвольный HTML за пустой результат.
|
||||
|
||||
## Подтверждённый DOM-контракт
|
||||
|
||||
Корень таблицы: `div.records.flex_table`. Верхняя строка имеет шесть семантических классов в таком порядке:
|
||||
|
||||
1. `fish` — рыба;
|
||||
2. `weight` — вес;
|
||||
3. `location` — водоём;
|
||||
4. `bait` — приманка/наживка (отображаемое значение находится в атрибуте `title` у `.bait_icon`);
|
||||
5. `gamername` — игрок;
|
||||
6. `data` — дата (именно `data`, не `date`).
|
||||
|
||||
Каждый `div.records_subtable` объединяет до нескольких результатов одной рыбы. Название рыбы находится только в заголовочной строке группы (`.fish .text`); вложенные строки имеют пустую ячейку рыбы и наследуют название группы.
|
||||
|
||||
Подтверждённые внутренние поля `OfficialRecord`:
|
||||
|
||||
| Поле | Источник/нормализация |
|
||||
|---|---|
|
||||
| `region` | сегмент URL, верхний регистр |
|
||||
| `category` | сегмент URL/аргумент запуска |
|
||||
| `fish` | `.fish .text` заголовка группы |
|
||||
| `weight_g` | `.weight`; `kg`/`g` приводятся к целым граммам |
|
||||
| `waterbody` | `.location` |
|
||||
| `bait` | `.bait_icon[title]`, nullable |
|
||||
| `player` | `.gamername`, nullable |
|
||||
| `record_date` | `.data`, формат `D.MM.YY`/`DD.MM.YY` |
|
||||
| `source_url` | URL полученной страницы |
|
||||
|
||||
Координат, проводки, времени поимки и устойчивого внешнего идентификатора записи в этой таблице нет. Локализация зависит от домена/языка страницы: проверенная `.de`-страница возвращает немецкие названия рыб, водоёмов и приманок даже для региона RU.
|
||||
|
||||
## Рекомендуемый режим получения
|
||||
|
||||
- Один понятный `User-Agent`, таймаут 20 секунд.
|
||||
- Начать не чаще одного раза в 60 минут на выбранную комбинацию категории и региона.
|
||||
- Не запускать параллельный обход всех регионов/категорий.
|
||||
- Добавить кэш, ограниченные повторы с экспоненциальной задержкой и общий лимит запросов перед продуктивным импортом.
|
||||
- При исчезновении таблицы, изменении порядка классов или ошибке HTTP считать запуск неуспешным и сохранять прежние данные.
|
||||
- Повторно проверить условия использования и связаться с владельцем сайта до регулярного производственного сбора; отсутствие `robots.txt` не заменяет разрешения.
|
||||
|
||||
## Сравнение с `hurfy/rf4-api`
|
||||
|
||||
Репозиторий `hurfy/rf4-api` создан в августе 2024 года; последний push, видимый через GitHub API на дату исследования, был 14 января 2025 года. README прямо называет проект находящимся в разработке. Он использует Django, Celery и браузерный WebDriver, перебирает регионы и категории, затем разбирает те же классы `records_wrapper`, `records_subtable`, `gamername`, `weight`, `location`, `bait_icon`, `data`.
|
||||
|
||||
Полезные архитектурные идеи:
|
||||
|
||||
- отделить построение URL, получение HTML, разбор и сохранение;
|
||||
- передавать регион и категорию вместе с сырой страницей;
|
||||
- нормализовать вес и текст до записи в БД.
|
||||
|
||||
Что нельзя переносить без проверки:
|
||||
|
||||
- список категорий проекта ограничен `records`, `ultralight`, `telestick` и уже не отражает всю текущую навигацию;
|
||||
- WebDriver избыточен для подтверждённой серверной HTML-страницы;
|
||||
- парсер опирается на позиционный поиск `.rows` и не проверяет контракт колонок;
|
||||
- заявленные в README охват и расписание сами по себе не доказывают текущую работоспособность.
|
||||
|
||||
## Фикстура и исследовательский код
|
||||
|
||||
`tests/fixtures/records_ru_sample.html` — сокращённая обезличенная фикстура, сохраняющая подтверждённую вложенность и классы. Реальные ники и реальные сочетания уловов в неё не переносились. `rf4_research/records.py` получает ровно одну переданную страницу, проверяет контракт колонок и выдаёт список типизированных `OfficialRecord`.
|
||||
|
||||
Unit-тест покрывает заголовочную и вложенную записи, килограммы/граммы, большой вес с разделителем тысяч и отказ при изменении колонок.
|
||||
|
||||
## Что остаётся предположением
|
||||
|
||||
- Все перечисленные категории и регионы стабильно отдают таблицу при умеренной частоте запросов.
|
||||
- Формат двухзначного года сохранится; сейчас он интерпретируется стандартным правилом Python как 2000-е для наблюдаемых значений.
|
||||
- Сайт разрешит регулярный производственный опрос раз в час.
|
||||
- DOM-классы останутся стабильнее локализованных заголовков.
|
||||
- Отсутствие найденного JSON endpoint не доказывает, что внутреннего endpoint вообще нет; подтверждено лишь, что для публичного сценария он не нужен и на странице не объявлен.
|
||||
|
||||
## Источники
|
||||
|
||||
- Официальная страница: <https://rf4game.de/records/region/RU/>
|
||||
- Исследованный сторонний проект: <https://github.com/hurfy/rf4-api>
|
||||
- Метаданные репозитория GitHub API: <https://api.github.com/repos/hurfy/rf4-api>
|
||||
Reference in New Issue
Block a user