screen-locker/screen_locker/_sick_floor.py
Krzysztof kuhy Rudnicki f2ddf3a491
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (flutter, flutter-analyze, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint,flutter-analyze) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (0) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (1) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (2) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (3) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (4) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (5) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (6) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (7) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (8) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (9) (push) Waiting to run
Tests / flutter-coverage (push) Blocked by required conditions
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
sick day: shutdown = 20:00 + gaming pay of today's credits (earned_time 0.13.1); credits raise to it, no absorb
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 17:38:24 +02:00

86 lines
3.3 KiB
Python

"""Sick-day shutdown, applied live: 20:00 plus the day's gaming pay.
The daily reset already resolves a sick day with ``earned_time.resolve(...,
sick=True)``. But a day is usually marked sick *after* that reset ran --
at 10:00 from the lock screen or the phone -- and the reset is stamped once
per day. This pass closes the gap: on every 5-minute locker tick, every
``--sync-only`` / ``--apply-bonuses`` pass and right after the lock screen's
"I'm sick" tap, it raises tonight's shutdown to the derived sick target
(:func:`sick_target`: 20:00 plus every credit's gaming pay, earned_time 0.13.0).
Credits stack on 20:00 (kuhy, 2026-10-17: LeetCode + reading is 22:00, not
the 20:10 that 0.11.0's non-stacking floor gave). The target is absolute,
re-derived from the ledgers, so on a sick day a live credit raises to it
instead of adding (``ShutdownMixin``): the new row is already in the sum.
No lift is recorded, so nothing is absorbed.
Raise-only. Un-marking today does not lower the config until the next
daily reset re-derives it.
"""
from __future__ import annotations
from datetime import date
import logging
from typing import TYPE_CHECKING
from screen_locker._bonus_lock import bonus_lock
from screen_locker._constants import GRACE_STATE_FILE
from screen_locker._day import today_str
from screen_locker._earned import hhmm
from screen_locker._grace_floor import save_lift, tonight_minutes
from screen_locker._shutdown_target import gather
from screen_locker._sick import is_sick
if TYPE_CHECKING:
from pathlib import Path
_logger = logging.getLogger(__name__)
def sick_target(day: date, log_file: Path | None) -> int:
"""Tonight's sick-day shutdown, re-derived from the ledgers and the log."""
return gather(day, log_file).minutes
class SickFloorMixin:
"""Lifts tonight's shutdown to the sick floor; composed into ShutdownMixin."""
if TYPE_CHECKING:
def _read_shutdown_config(self) -> tuple[int, int, int] | None: ...
def _write_shutdown_config(
self, mon_wed: int, thu_sun: int, morning_end: int, *, restore: bool = ...
) -> bool: ...
def _apply_sick_floor(self) -> bool:
"""Raise tonight's shutdown to the sick target if today is sick.
Returns True when the config was raised on this call.
"""
today = today_str()
day = date.fromisoformat(today)
if not is_sick(day):
return False
with bonus_lock(GRACE_STATE_FILE):
config = self._read_shutdown_config()
if config is None:
_logger.warning("Sick floor: shutdown config unreadable; not applied")
return False
current = tonight_minutes(config, day)
floor = sick_target(day, getattr(self, "log_file", None))
if floor <= current:
return False
mon_wed, thu_sun, morning = config
if not self._write_shutdown_config(
max(mon_wed, floor), max(thu_sun, floor), morning, restore=True
):
_logger.warning("Sick floor: failed to write shutdown config")
return False
save_lift(0, GRACE_STATE_FILE) # stacked: nothing to absorb
_logger.info(
"Sick floor: shutdown %s -> %s (sick day).", hhmm(current), hhmm(floor)
)
return True