Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
"""Extra benefits for exceeding the weekly workout minimum.
|
|
|
|
|
|
|
|
|
|
Tracks:
|
|
|
|
|
- Consecutive weeks with 5+ workouts (streak counter).
|
2026-07-03 15:27:08 +02:00
|
|
|
- Banked shutdown-time bonus hours earned from extra workouts, applied
|
|
|
|
|
every day of the following week. This never reduces enforcement (unlike
|
|
|
|
|
a banked "skip a workout" credit would) — it only grants extra comfort
|
|
|
|
|
time on top of a floor you still have to earn each day.
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
- ISO weeks in which the early-bird window is extended to 09:00.
|
|
|
|
|
|
|
|
|
|
State is persisted in ``extra_benefits_state.json`` next to this file.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
|
import json
|
|
|
|
|
import logging
|
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
|
|
|
|
|
|
from screen_locker._weekly_check import count_weekly_workouts
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-07-03 15:27:08 +02:00
|
|
|
_MILESTONE_INTERVAL = 4 # every 4-week streak → +1h extra shutdown bonus
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
_BONUS_THRESHOLD = 5 # workouts/week required to earn extra rewards
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_state(state_file: Path) -> dict[str, Any]:
|
|
|
|
|
"""Load benefits state, returning defaults if missing or corrupt."""
|
|
|
|
|
if not state_file.exists():
|
|
|
|
|
return {}
|
|
|
|
|
try:
|
|
|
|
|
with state_file.open() as f:
|
|
|
|
|
return json.load(f)
|
|
|
|
|
except (OSError, json.JSONDecodeError):
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _save_state(state_file: Path, state: dict[str, Any]) -> None:
|
|
|
|
|
"""Persist benefits state to disk."""
|
|
|
|
|
try:
|
|
|
|
|
with state_file.open("w") as f:
|
|
|
|
|
json.dump(state, f, indent=2)
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
_logger.warning("Failed to save extra benefits state: %s", exc)
|
|
|
|
|
|
|
|
|
|
|
2026-07-03 15:27:08 +02:00
|
|
|
def _current_iso_week(now: datetime) -> str:
|
|
|
|
|
"""Return *now*'s ISO week as ``YYYY-Www``."""
|
|
|
|
|
year, week, _ = now.isocalendar()
|
|
|
|
|
return f"{year}-W{week:02d}"
|
|
|
|
|
|
|
|
|
|
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
def process_week_transition(log_file: Path, state_file: Path) -> list[str]:
|
|
|
|
|
"""Process last week's results if we've entered a new ISO week.
|
|
|
|
|
|
|
|
|
|
Counts workouts from the previous ISO week. If count >= 5:
|
|
|
|
|
- Increments the consecutive-streak counter.
|
2026-07-03 15:27:08 +02:00
|
|
|
- Banks (count - 4) hours of shutdown-time bonus for the current week,
|
|
|
|
|
applied once per day on top of the daily base reset.
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
- Marks the *current* ISO week as having extended early-bird (09:00).
|
2026-07-03 15:27:08 +02:00
|
|
|
- Adds +1h more bonus every ``_MILESTONE_INTERVAL`` streak weeks.
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
|
|
|
|
|
Returns a list of human-readable reward strings (empty if no transition).
|
|
|
|
|
"""
|
|
|
|
|
now = datetime.now(tz=timezone.utc).astimezone()
|
2026-07-03 15:27:08 +02:00
|
|
|
current_week_str = _current_iso_week(now)
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
|
|
|
|
|
state = _load_state(state_file)
|
|
|
|
|
if state.get("last_processed_iso_week") == current_week_str:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
# Count workouts in the previous ISO week (Mon through Sun).
|
|
|
|
|
monday_this_week = now.date() - timedelta(days=now.weekday())
|
|
|
|
|
sunday_prev_week = monday_this_week - timedelta(days=1)
|
|
|
|
|
prev_week_dt = datetime(
|
|
|
|
|
sunday_prev_week.year,
|
|
|
|
|
sunday_prev_week.month,
|
|
|
|
|
sunday_prev_week.day,
|
|
|
|
|
23,
|
|
|
|
|
59,
|
|
|
|
|
59,
|
|
|
|
|
tzinfo=timezone.utc,
|
|
|
|
|
)
|
|
|
|
|
prev_week_count = count_weekly_workouts(log_file, today=prev_week_dt)
|
|
|
|
|
|
|
|
|
|
streak = int(state.get("consecutive_5plus_weeks", 0))
|
2026-07-03 15:27:08 +02:00
|
|
|
weekly_bonus_hours: dict[str, int] = dict(
|
|
|
|
|
state.get("weekly_shutdown_bonus_hours", {})
|
|
|
|
|
)
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
eb_weeks: list[str] = list(state.get("extended_early_bird_iso_weeks", []))
|
|
|
|
|
|
|
|
|
|
rewards: list[str] = []
|
|
|
|
|
prev_year, prev_week, _ = sunday_prev_week.isocalendar()
|
|
|
|
|
prev_week_str = f"{prev_year}-W{prev_week:02d}"
|
|
|
|
|
|
|
|
|
|
if prev_week_count >= _BONUS_THRESHOLD:
|
|
|
|
|
extra = prev_week_count - 4
|
|
|
|
|
streak += 1
|
2026-07-03 15:27:08 +02:00
|
|
|
bonus_hours = extra
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
if current_week_str not in eb_weeks:
|
|
|
|
|
eb_weeks.append(current_week_str)
|
|
|
|
|
rewards.append(
|
|
|
|
|
f"{prev_week_count} workouts in {prev_week_str}! "
|
2026-07-03 15:27:08 +02:00
|
|
|
f"+{extra}h shutdown bonus this week, early-bird extended to 09:00"
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
)
|
|
|
|
|
if streak % _MILESTONE_INTERVAL == 0:
|
2026-07-03 15:27:08 +02:00
|
|
|
bonus_hours += 1
|
|
|
|
|
rewards.append(f"{streak}-week streak milestone! +1h extra shutdown bonus")
|
|
|
|
|
weekly_bonus_hours[current_week_str] = bonus_hours
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
else:
|
|
|
|
|
if streak > 0:
|
|
|
|
|
rewards.append(f"Streak reset (was {streak} weeks of 5+ workouts)")
|
|
|
|
|
streak = 0
|
|
|
|
|
|
|
|
|
|
_save_state(
|
|
|
|
|
state_file,
|
|
|
|
|
{
|
|
|
|
|
"consecutive_5plus_weeks": streak,
|
|
|
|
|
"last_processed_iso_week": current_week_str,
|
2026-07-03 15:27:08 +02:00
|
|
|
"weekly_shutdown_bonus_hours": weekly_bonus_hours,
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
"extended_early_bird_iso_weeks": eb_weeks,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
return rewards
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def current_streak(state_file: Path) -> int:
|
|
|
|
|
"""Return the current consecutive-5plus-weeks streak count."""
|
|
|
|
|
return int(_load_state(state_file).get("consecutive_5plus_weeks", 0))
|
|
|
|
|
|
|
|
|
|
|
2026-07-03 15:27:08 +02:00
|
|
|
def weekly_shutdown_bonus_hours(
|
|
|
|
|
state_file: Path, *, today: datetime | None = None
|
|
|
|
|
) -> int:
|
|
|
|
|
"""Return the banked shutdown-time bonus (hours) for the current ISO week."""
|
|
|
|
|
now = today if today is not None else datetime.now(tz=timezone.utc).astimezone()
|
|
|
|
|
current_week_str = _current_iso_week(now)
|
|
|
|
|
bonus_hours: dict[str, int] = _load_state(state_file).get(
|
|
|
|
|
"weekly_shutdown_bonus_hours", {}
|
|
|
|
|
)
|
|
|
|
|
return int(bonus_hours.get(current_week_str, 0))
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def has_extended_early_bird(state_file: Path) -> bool:
|
|
|
|
|
"""Return True if the current ISO week has an extended early-bird window (09:00)."""
|
|
|
|
|
now = datetime.now(tz=timezone.utc).astimezone()
|
2026-07-03 15:27:08 +02:00
|
|
|
current_week_str = _current_iso_week(now)
|
Add auto-fill RunnerUp scan, carrot bonuses, and --status interface
- Refactor RunnerUp verification: extract RunnerUpDbMixin (_runnerup_db.py),
split _scan_and_fill_week_runnerup into a helper _try_fill_runnerup_for_date
to keep cyclomatic complexity ≤10
- Generalise TCX lookup to any date in the ISO week (was today-only); all gap
days Mon→today auto-filled on every startup and 08:30 timer firing
- Add _adjust_shutdown_time_by(): +1h per extra workout beyond the 4-workout
minimum, capped at midnight (hour=24)
- Add _shutdown_base.py: daily reset of shutdown config to a stored base so
the bonus doesn't silently accumulate across days
- Add _extra_benefits.py: streak tracking, skip credits (earn (n-4) credits
for 5+ workout weeks), early-bird extension to 09:00 for eligible weeks
- Add --status mode (_status.py): non-locking CLI view showing per-day
breakdown (✓/✗), RunnerUp auto-scan, bonus status, shutdown time, streak,
skip credits, and early-bird status
- Hook carrot into _check_non_verify_exits: bonus applied whenever auto-fill
pushes weekly count above the minimum
- Pass all pre-commit hooks (ruff, mypy, pylint, bandit, shellcheck,
codespell, max-file-length); 508 tests at 100% branch coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017auyHmf2ZwQcDAwXaSo7KX
2026-06-28 08:08:35 +02:00
|
|
|
eb_weeks: list[str] = _load_state(state_file).get(
|
|
|
|
|
"extended_early_bird_iso_weeks", []
|
|
|
|
|
)
|
|
|
|
|
return current_week_str in eb_weeks
|