screen-locker/screen_locker/_sick_floor.py
Krzysztof kuhy Rudnicki f3875302d7 Fix pylint findings in source and scripts
Real fixes: split over-long functions (lock-decision chain, manual
sync, validation, phone verification, status), hoist a dead lazy
import, context-manage Xvfb and temp dirs, add encodings, share the
silent-failure argv loop.

lock_grab_handoff read lock._recovery, which gatelock's LockWindow
does not have (its public property is recovery); a contract test now
checks every attribute the handoff reads against the real class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
2026-11-01 21:11:34 +01:00

88 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:
raise NotImplementedError
def _write_shutdown_config(
self, mon_wed: int, thu_sun: int, morning_end: int, *, restore: bool = ...
) -> bool:
raise NotImplementedError
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