Files
comunidadhll/ai/tasks/done/TASK-201-automate-player-read-model-refresh.md
2026-06-09 11:59:31 +02:00

224 lines
9.8 KiB
Markdown

---
id: TASK-201-automate-player-read-model-refresh
title: Automate player read model refresh
status: done
type: backend
team: Backend Senior
supporting_teams:
- Arquitecto de Base de Datos
- Arquitecto Python
roadmap_item: foundation
priority: high
---
# TASK-201 - Automate player read model refresh
## Goal
Automate the periodic refresh of `player_search_index` and `player_period_stats` inside the existing backend historical runner so the public stats endpoints can keep using their read models without depending on manual operator refreshes.
## Context
Production already validates the full read-model chain required by the public player stats flows:
- `player_search_index` exists and `/api/stats/players/search` already uses `read_model=player-search-index` with `fallback_used=false`
- `player_period_stats` exists and `/api/stats/players/{player_id}` already uses `read_model=player-period-stats` with `fallback_used=false`
- `ranking_snapshots` / `ranking_snapshot_items` already exist
- `refresh-ranking-snapshots --limit 30` already runs in the periodic historical runner
- PostgreSQL is the operational storage target
- runtime fallback must remain preserved for player stats and ranking
The remaining gap is operational automation for the two player read models. The existing periodic runner in `backend/app/historical_runner.py` is the intended scheduler surface, so this task must extend that cycle instead of inventing a new scheduler. The cycle should keep `refresh-ranking-snapshots` and add dedicated, separately reported refresh steps for player search and player period stats.
Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution.
## Steps
1. Read the listed files first.
2. Inspect the current historical runner flow and the existing manual read-model refresh entrypoints.
3. Integrate `refresh_player_search_index(...)` into the periodic backend cycle.
4. Integrate `refresh_player_period_stats(...)` into the periodic backend cycle.
5. Keep `refresh-ranking-snapshots` in the same cycle and preserve its current behavior.
6. Run the cycle in this order unless the current runner architecture safely requires a different invariant:
- existing RCON ingestion/materialization cycle
- `player_search_index`
- `player_period_stats`
- `ranking_snapshots`
7. Report `player_search_index_result`, `player_period_stats_result` and `ranking_snapshot_result` separately.
8. Preserve visibility of failures and avoid hiding one read-model failure behind another. One failure should not necessarily prevent the remaining refresh attempts unless the runner already enforces a stricter global policy.
9. Preserve the manual CLIs and the public contracts of:
- `/api/stats/players/search`
- `/api/stats/players/{player_id}`
- `/api/ranking`
10. Document the automatic runner refresh, inherited cadence, emergency manual commands, fallback preservation and final cycle order.
## Files to Read First
- `AGENTS.md`
- `ai/repo-context.md`
- `ai/architecture-index.md`
- `backend/app/historical_runner.py`
- `backend/app/rcon_historical_player_stats.py`
- `backend/app/rcon_historical_leaderboards.py`
- `backend/app/postgres_rcon_storage.py`
- `backend/app/payloads.py`
- `docs/player-search-read-model-plan.md`
- `docs/player-period-stats-read-model-plan.md`
- `docs/ranking-snapshot-read-model-plan.md`
- `scripts/run-stats-validation.ps1`
- `ai/tasks/done/TASK-197-automate-ranking-snapshot-refresh.md`
- `ai/tasks/done/TASK-198-add-player-search-read-model.md`
- `ai/tasks/done/TASK-199-add-player-period-stats-read-model.md`
- `ai/tasks/done/TASK-200-fix-player-profile-runtime-postgres-grouping.md`
## Expected Files to Modify
- `backend/app/historical_runner.py`
- `docs/player-search-read-model-plan.md`
- `docs/player-period-stats-read-model-plan.md`
- `docs/ranking-snapshot-read-model-plan.md`
- `scripts/run-stats-validation.ps1`
- `ai/tasks/in-progress/TASK-201-automate-player-read-model-refresh.md`
## Constraints
- Keep the change minimal and backend/documentation only.
- Do not execute `ai-platform run`.
- Do not modify frontend.
- Do not change design.
- Do not touch images or assets.
- Do not reactivate Elo/MMR.
- Do not reintroduce Comunidad Hispana `#03`.
- Do not mix visual corrections into this task.
- Preserve PostgreSQL as the operational storage target.
- Preserve runtime fallback for player stats and ranking.
- Do not break the manual CLIs:
- `refresh-player-search-index`
- `refresh-player-period-stats`
- `refresh-ranking-snapshots`
- `generate-ranking-snapshot`
- Do not change public contracts of:
- `/api/stats/players/search`
- `/api/stats/players/{player_id}`
- `/api/ranking`
## Validation
Before completing the task ensure:
- `python -m py_compile backend/app/historical_runner.py backend/app/rcon_historical_player_stats.py`
- `powershell -ExecutionPolicy Bypass -File scripts/run-stats-validation.ps1`
- `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
- local/import validation proves the runner calls:
- `refresh_player_search_index(...)`
- `refresh_player_period_stats(...)`
- `refresh_ranking_snapshots(...)`
- validation proves the periodic cycle reports these results separately:
- `player_search_index_result`
- `player_period_stats_result`
- `ranking_snapshot_result`
- validation proves the manual CLIs still work:
- `refresh-player-search-index`
- `refresh-player-period-stats`
- `refresh-ranking-snapshots`
- validation proves `/api/stats/players/search` can still use `player-search-index`
- validation proves `/api/stats/players/{player_id}` can still use `player-period-stats`
- validation proves ranking snapshots are not broken
- `git diff --name-only` matches the expected scope
## Outcome
Implemented:
- `backend/app/historical_runner.py`
- integrated automatic periodic refresh of `player_search_index`
- integrated automatic periodic refresh of `player_period_stats`
- preserved the existing `ranking_snapshots` refresh
- the runner now reports:
- `player_search_index_result`
- `player_period_stats_result`
- `ranking_snapshot_result`
- one read-model failure is now reported explicitly and does not prevent the runner from attempting the remaining refresh steps in the same cycle
- the runner returns `status=partial` when one of those periodic read-model refresh steps fails but the cycle continues
- `docs/player-search-read-model-plan.md`
- documents automatic runner refresh
- documents inherited cadence from `HLL_HISTORICAL_REFRESH_INTERVAL_SECONDS`
- documents the emergency manual command
- documents fallback preservation
- `docs/player-period-stats-read-model-plan.md`
- documents automatic runner refresh
- documents inherited cadence from `HLL_HISTORICAL_REFRESH_INTERVAL_SECONDS`
- documents the emergency manual command
- documents fallback preservation
- `docs/ranking-snapshot-read-model-plan.md`
- documents the final runner order
- documents that ranking refresh remains part of the same periodic cycle
- documents cadence inheritance and fallback preservation
- `scripts/run-stats-validation.ps1`
- validates that the runner calls the player read-model refreshes before ranking snapshots
- validates separate result reporting
- validates that a `player_search_index` refresh failure remains visible and does not stop the remaining periodic refresh attempts
Modified files:
- `backend/app/historical_runner.py`
- `docs/player-search-read-model-plan.md`
- `docs/player-period-stats-read-model-plan.md`
- `docs/ranking-snapshot-read-model-plan.md`
- `scripts/run-stats-validation.ps1`
- `ai/tasks/done/TASK-201-automate-player-read-model-refresh.md`
Final runner order:
1. existing RCON ingestion/materialization cycle
2. `player_search_index`
3. `player_period_stats`
4. `ranking_snapshots`
Added cycle results/logs:
- `player_search_index_result`
- `player_period_stats_result`
- `ranking_snapshot_result`
- start events:
- `player-search-index-refresh-started`
- `player-period-stats-refresh-started`
- failure events:
- `player-search-index-refresh-failed`
- `player-period-stats-refresh-failed`
Validations executed:
- `python -m py_compile backend/app/historical_runner.py backend/app/rcon_historical_player_stats.py`
- `powershell -ExecutionPolicy Bypass -File scripts/run-stats-validation.ps1`
- `powershell -ExecutionPolicy Bypass -File scripts/run-integration-tests.ps1`
Production validation path:
- run one controlled backend cycle:
- `python -m app.historical_runner --max-runs 1`
- verify the cycle output includes:
- `player_search_index_result`
- `player_period_stats_result`
- `ranking_snapshot_result`
- confirm the player stats endpoints still report:
- `/api/stats/players/search` -> `source.read_model=player-search-index`
- `/api/stats/players/{player_id}` -> `source.read_model=player-period-stats`
- confirm `/api/ranking` still serves ranking snapshots when ready and preserves fallback behavior when a snapshot is missing
- if emergency rebuild is needed, operators can still run:
- `python -m app.rcon_historical_player_stats refresh-player-search-index`
- `python -m app.rcon_historical_player_stats refresh-player-period-stats`
- `python -m app.rcon_historical_leaderboards refresh-ranking-snapshots --limit 30`
Pending limitations kept out of scope:
- the runner still refreshes the player read models for all supported public scopes even when a manual runner execution is limited with `--server`
- this task does not introduce a separate external scheduler or deployment-specific cron wiring
- live HTTP validation at `http://127.0.0.1:8000` was not available in this environment; route-contract validation passed via local Python imports instead
## Change Budget
- Prefer fewer than 5 modified files.
- Prefer changes under 200 lines when feasible.
- Split the work into follow-up tasks if limits are exceeded.