fix periodic activity-check algorithm silently losing player activity
All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 38s

Errors from Lichess (timeouts/5xx/invalid tokens) were being disguised
as "no activity" (HTTP 200, games_count/puzzles_in_period=0), causing
the bot to silently advance its checkpoint past real, undetected
activity. Puzzle-fetch failures weren't counted as errors at all, and
the periodic task died permanently after 5 consecutive errors with no
way to recover short of a manual restart. /setperiod also unconditionally
reset the checkpoint, dropping the window between the last check and
the command.

- API now returns success=false/502 on real errors instead of masking
  them as zero activity (models.py, stats_service.py, main.py)
- Puzzle-fetch errors are now treated the same as game-fetch errors:
  retry the same window instead of reporting "no puzzles"
- Notification delivery failures no longer silently advance the
  checkpoint
- Replaced the hard 5-error kill switch with capped backoff that keeps
  retrying indefinitely, plus an admin-bot notification if a player's
  monitoring has been failing for a prolonged period (~2h+)
- /setperiod only clears the checkpoint when disabling notifications,
  preserving continuity when a period is just changed

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
vrubelroman 2026-07-02 19:29:06 +00:00
parent 75e62b57a1
commit 4a783225af
4 changed files with 118 additions and 58 deletions

View file

@ -186,6 +186,7 @@ class GamesOfPeriodResponse(BaseModel):
Содержит метаинформацию о запросе и агрегированную статистику игр.
"""
message: str = Field(..., description="Сообщение о результате запроса", example="Статистика игр за период")
success: bool = Field(True, description="False, если запрос к Lichess завершился ошибкой (а не легитимным нулевым результатом)", example=True)
username: str = Field(..., description="Имя пользователя", example="magnus")
period_start: int = Field(..., description="Начало периода (Unix timestamp)", example=1640995200)
period_end: int = Field(..., description="Конец периода (Unix timestamp)", example=1641081600)
@ -232,6 +233,7 @@ class PuzzleOfPeriodResponse(BaseModel):
Содержит метаинформацию о запросе и агрегированную статистику решения задач.
"""
message: str = Field(..., description="Сообщение о результате запроса", example="Статистика решения задач за период")
success: bool = Field(True, description="False, если запрос к Lichess завершился ошибкой (а не легитимным нулевым результатом)", example=True)
period_start: int = Field(..., description="Начало периода (Unix timestamp в миллисекундах)", example=1640995200000)
period_end: int = Field(..., description="Конец периода (Unix timestamp в миллисекундах)", example=1641081600000)
max_puzzles: int = Field(..., description="Максимальное количество задач для получения", example=50)