---
id: TASK-220
title: Schedule public snapshot refreshes
status: done
type: backend
team: Backend Senior
supporting_teams: ["Arquitecto Python"]
roadmap_item: foundation
priority: high
---

# TASK-220 - Schedule public snapshot refreshes

## Goal

Configure automatic regeneration of public snapshots and read models at appropriate times, so public pages do not depend on heavy runtime calculations.

## Context

Public historical, ranking and stats pages should read precomputed data. Heavy regeneration should happen inside the internal runner during low-load windows, while weekly/monthly ranking and recent matches stay fresh at shorter intervals.

Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution.

## Steps

1. Inspect the listed files first.
2. Document how the current refresh paths work.
3. Add environment-driven scheduling for daily full refresh, frequent ranking refresh and recent match snapshot refresh.
4. Keep refreshes idempotent and non-overlapping.
5. Document Portainer configuration and manual emergency commands.
6. Validate Python compilation and scheduler helpers.

## Files to Read First

- `AGENTS.md`
- `ai/repo-context.md`
- `ai/architecture-index.md`
- `ai/orchestrator/backend-senior.md`
- `ai/orchestrator/python-architect.md`
- `backend/app/historical_runner.py`
- `backend/app/rcon_historical_leaderboards.py`
- `backend/app/rcon_annual_rankings.py`
- `backend/app/rcon_historical_player_stats.py`
- `backend/app/historical_snapshot_storage.py`
- `backend/app/historical_snapshots.py`
- `backend/app/payloads.py`
- `backend/app/config.py`
- `backend/app/main.py`
- `docker-compose.yml`
- `README.md`
- `docs/PERFORMANCE_PUBLIC_QUERY_AUDIT.md`

## Expected Files to Modify

- `backend/app/config.py`
- `backend/app/historical_runner.py`
- `backend/app/historical_snapshots.py`
- `backend/app/routes.py`
- `backend/tests/test_historical_snapshot_refresh.py`
- `docker-compose.yml`
- `docs/public-snapshot-refresh-schedule.md`
- `ai/tasks/in-progress/TASK-220-schedule-public-snapshot-refreshes.md`

## Constraints

- Do not execute `ai-platform run`.
- Do not push or commit.
- Do not touch frontend except documentation if unavoidable.
- Do not touch weapon assets, SVGs or physical images.
- Do not touch `ai/system-metrics.md`.
- Do not reactivate Elo/MMR.
- Do not reintroduce Comunidad Hispana #03.
- Keep public requests on read models/snapshots for rankings.
- Prefer the internal runner over host cron.

## Validation

Before completing the task ensure:

- Run `python -m compileall` for modified backend modules.
- Run relevant existing backend tests if applicable.
- Validate scheduler helper behavior for the next 06:00 Europe/Madrid.
- Confirm public route modules still compile.
- Review `git diff --name-only`.
- Confirm no frontend, weapon assets, SVGs, physical images or `ai/system-metrics.md` were modified by this task.

## Outcome

Completed.

Current-state analysis:

- `ranking_snapshots` weekly/monthly were generated by `refresh_ranking_snapshots()` and invoked from `historical_runner` once per historical runner cycle. Compose forced `--hourly`, so public rankings could wait up to an hour despite the code recommending 5-15 minute freshness.
- Annual ranking snapshots existed through `generate_annual_ranking_snapshot()` but were only manual/CLI-driven, not part of the runner schedule.
- `player_search_index` and `player_period_stats` were refreshed from `historical_runner` once per runner cycle.
- Recent matches were included in full/priority historical snapshots, but there was no dedicated short-cadence refresh. RCON capture exposed `materialized_matches_inserted`, which can be used as a finished-match hook.
- `historical_runner` already existed and is the right place for internal scheduling; no host cron is required.
- Public historical snapshot leaderboard routes in RCON mode were using a runtime materialized fast path despite being snapshot endpoints.

Implemented strategy:

- Added public refresh env configuration with safe defaults.
- Kept the internal runner as scheduler and removed the Compose `--hourly` override so the runner can tick at the shortest public interval while still throttling the heavier historical cycle separately.
- Added daily public full refresh logic, due once per local day after `06:00 Europe/Madrid`.
- Added frequent weekly/monthly ranking snapshot refresh at `HLL_PUBLIC_RANKING_REFRESH_INTERVAL_SECONDS`.
- Added recent-match-only snapshot generation and scheduled it at `HLL_PUBLIC_RECENT_MATCHES_REFRESH_INTERVAL_SECONDS`.
- Added immediate recent-match refresh when RCON capture reports newly materialized finished matches.
- Added structured start/end logs, durations and per-step results. Per-step failures are captured without stopping neighboring refreshes or the runner.
- Added in-process non-overlap guards for public refresh classes.
- Changed public historical leaderboard snapshot routes to read persisted snapshots instead of the RCON runtime fast path.
- Documented Portainer configuration and emergency commands in `docs/public-snapshot-refresh-schedule.md`.

Validation:

- `python -m compileall backend/app/config.py backend/app/historical_runner.py backend/app/historical_snapshots.py backend/app/routes.py`
- `python -m compileall backend/app backend/tests`
- `$env:PYTHONPATH='backend'; python -m unittest backend.tests.test_historical_snapshot_refresh`
- `$env:PYTHONPATH='backend'; python -m unittest backend.tests.test_annual_ranking_payload`
- Dry-run scheduler helper check:
  - `2026-06-10T03:30:00Z` -> next refresh `2026-06-10T04:00:00Z` (`06:00 Europe/Madrid`)
  - `2026-06-10T05:00:00Z` -> next refresh `2026-06-11T04:00:00Z`
- Route dry-run with patched builder confirmed `/api/historical/snapshots/leaderboard` resolves through `build_leaderboard_snapshot_payload`.
- `git diff --name-only` reviewed.

Exclusions:

- Did not execute `ai-platform run`.
- Did not push or commit.
- Did not modify frontend files as part of this task.
- Did not modify physical images, weapon assets, SVGs or `ai/system-metrics.md`.
- Did not reactivate Elo/MMR.
- Did not reintroduce Comunidad Hispana #03.

Note: the worktree still contains pre-existing unrelated changes in `ai/system-metrics.md`, `frontend/assets/css/styles.css`, clan image files and weapon/SVG assets. They were not touched by this task.

## Change Budget

- Prefer fewer than 5 modified files when feasible.
- Prefer changes under 200 lines when feasible.
- Split follow-up tasks if scope grows.
