init: Redis-backed Pokémon 雷達 API

- radar_fetcher.py:呼叫 Function Server 抓 twpkinfo.com 資料後寫入 Redis (TTL 300s)
- pokemon_radar_api.py:FastAPI /scan,從 Redis 讀取並依 request 即時二次篩選;Redis 不可用時回 200 空結果,不再 500
- pokemon_location_fetcher.py:舊版 CLI,仍由 fetch_and_upload.sh 使用
- 文件:README (參考手冊)、QUICKSTART (操作指南)、SUMMARY (故事線)、CLAUDE.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 09:13:59 +08:00
commit f7ef105436
18 changed files with 2919 additions and 0 deletions

70
CLAUDE.md Normal file
View File

@@ -0,0 +1,70 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 常用指令
套件管理使用 `uv`(見 `pyproject.toml``uv.lock``.python-version` = 3.12)。所有 Python 進入點都透過 `uv run` 執行。
- **啟動 API**`./run_pokemon_radar_api.sh`(內含 `uv run uvicorn pokemon_radar_api:app --host 0.0.0.0 --port 8008 --log-config logging.ini`
- **執行新版 fetcher寫入 Redis**`uv run python radar_fetcher.py`
- **執行舊版 fetcher產生本地 JSON/CSV/截圖)**`uv run python pokemon_location_fetcher.py --script twpk_radar.mjs --lat 25.0478 --lng 121.5170 --zoom 16 --state state.json --ua-from-state --min-remaining 300 --min-iv 90`
- **檢視 Redis 快取內容**`uv run python reader.py`rich 表格)或 `uv run python check_redis.py`raw JSON dump
- **生產環境排程腳本**`./fetch_and_upload.sh`(使用 `flock` 鎖定 `/var/lock/poke_radar.lock`;呼叫**舊版** `pokemon_location_fetcher.py`,然後 `scp``192.168.42.104:/opt/caddy/www/pokemon/spots.json`
本專案沒有測試、linter、build 流程。
## 架構
這是一個兩階段的 pipeline被一個 FastAPI 即時查詢層所包裝。有**兩條共存的 fetch pipeline**,修改時務必了解各自的用途:
### 資料流
```
twpkinfo.com (ipoke.aspx)
│ browser automation (Puppeteer)
Function Server (192.168.42.124:13000/function)
│ POST {code: <injected twpk_radar.mjs>}, 150s timeout
┌─────┴─────────────────────────────┐
│ │
│ (Pipeline A寫入 Redis) │ (Pipeline B寫入本地檔)
│ radar_fetcher.py │ pokemon_location_fetcher.py (CLI)
│ └─→ Redis SETEX │ └─→ pokemon_data.{json,csv}
│ pokemon:radar:latest │ + screenshot.png
│ TTL 300s │
└─────┬─────────────────────────────┘
pokemon_radar_api.py (FastAPI, :8008)
POST /scan → 即時篩選 Redis 內容回傳
```
### 關鍵設計
- **`twpk_radar.mjs` 是 Function Server 上跑的 worker**不是本地執行的模組。Python 端透過 regex 在字串層級注入變數,這個契約不能壞:
- `const TARGET = {...};` → 由 `_inject_target_and_shot` 覆寫
- `const DO_SCREENSHOT = true|false;` → 同上
- `globalThis.__POKE_STATE__` → 由 `_inject_state` 前置插入(含 cookies/localStorage/UA/langs
- 修 MJS 時,上述 3 個錨點的形狀必須維持,否則 regex 會失效
- **`pokemon_radar_api.py` 重用舊 client 的 `postprocess` 方法**,但**不**透過它去抓資料:`_filter_helper = PokemonRadarClient(url="", token="")`。Redis 存的是已篩選過一次(由 fetcher但仍保留較寬原始集的資料API 再依 request 參數做即時二次篩選(`min_remaining` / `min_iv` / `perfect_only` / `species`)。`ScanRequest` 裡的 `lat/lng/zoom/shot/...` 欄位在 Redis 模式下是**忽略的**,純粹保留給前端/n8n 相容。
- **兩個 fetcher 的篩選邏輯重複實作了兩次**`radar_fetcher._postprocess``PokemonRadarClient.postprocess`),修改篩選規則時要同步兩邊。
- **`fetch_and_upload.sh` 目前走的是 Pipeline B本地檔 + scp不是 Pipeline ARedis**。這是生產環境仍在使用的路徑;若改 fetcher 行為要確認影響哪一邊。
### 設定
- `.env`(本地)驅動 `radar_fetcher.py``pokemon_radar_api.py``reader.py``check_redis.py`。注意 `radar_fetcher.py` 預設 `REDIS_HOST=localhost`,但 API / reader / `.env``192.168.42.211` — fetcher 跑在 Redis 本機、API 跑遠端。
- `state.json` 是使用者登入後的 cookies/localStorage/UA 快照;用於繞過 twpkinfo.com 的人機驗證。過期時整條 pipeline 會靜默產 0 筆。`radar_fetcher._postprocess` 有一個診斷:當 `raw_items` 非空但篩完為 0 時會 log remaining/perfect 的分佈,優先看這個。
- `FUNCTION_TOKEN` 必填radar_fetcher 會直接 raise舊 CLI 則有 hard-coded default `6R0W53R135510`
## 專案慣例
- 全專案以**繁體中文(台灣用語)**溝通與註解。
- log 訊息不要使用 emoji現存檔案中的 emoji 是歷史遺留,新寫或修改時改成純文字。
- repo 根目錄的 `*_BACKUP_YYYYMMDDHHMMSS` 檔案是舊備份,可以清掉。