mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 20:27:13 +01:00
The workout lock, its explanation, the status view and the workday penalty's penalised day now ask _sick.stands_down_workout: a sick day that keeps the workout locker behaves as a normal day. The penalty's ring day is judged by the alarm switch. Shutdown (20:00 floor, grace floor, base, target, preview scripts) keeps is_sick: the floor belongs to the day, not to a locker. The one-tap button still keeps nothing. Test pool fixture and preview harness now redirect every sickdays resolve_paths binding, so keep edits never reach the real pool. Pins bumped to sickdays-v0.2.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAhbNszs5JKdtnD4uBi3xp
83 lines
3.1 KiB
Python
83 lines
3.1 KiB
Python
"""Read the workday-stick penalty wake-alarm signed for today.
|
|
|
|
A missed Tuesday/Wednesday/Thursday wake-alarm ring costs tomorrow's
|
|
relaxed-day leniency (:func:`screen_locker._weekly_check.is_relaxed_day`)
|
|
instead of just resetting to normal. This module never re-derives that
|
|
policy: the file carries a signed ``penalty_date``, and the only decision
|
|
here is *HMAC ok, dated today*. Anything else -- missing file, stale date,
|
|
bad signature -- is "no penalty", i.e. today's ordinary relaxed-day rule.
|
|
|
|
The file is void when EITHER its ``issued_date`` (the day of the missed
|
|
ring) belongs to a sick day whose ``alarm`` switch is off, OR its
|
|
``penalty_date`` belongs to a sick day whose ``workout`` lock is off
|
|
(``sickdays``). Each date is judged by what it owns: the ring is the alarm's,
|
|
and the penalty it costs -- the relaxed-day leniency -- is the workout lock's.
|
|
A sick day that keeps that locker leaves its half of the penalty in force.
|
|
Wake-alarm's own retraction of the file can lag its sync by ~15 min, so it
|
|
is not relied on here.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
import json
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from gatelock.log_integrity import verify_entry_hmac
|
|
import sickdays
|
|
|
|
from screen_locker._constants import WORKDAY_PENALTY_FILE
|
|
from screen_locker._day import today_str
|
|
from screen_locker._sick import stands_down
|
|
|
|
if TYPE_CHECKING:
|
|
from datetime import datetime
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _read_verified() -> dict[str, object] | None:
|
|
"""The file's entry when it exists, parses and verifies; else None."""
|
|
if not WORKDAY_PENALTY_FILE.exists():
|
|
_logger.debug("No workday penalty file at %s", WORKDAY_PENALTY_FILE)
|
|
return None
|
|
try:
|
|
entry = json.loads(WORKDAY_PENALTY_FILE.read_text())
|
|
except (OSError, json.JSONDecodeError) as exc:
|
|
_logger.warning("Cannot read %s: %s", WORKDAY_PENALTY_FILE, exc)
|
|
return None
|
|
if not isinstance(entry, dict) or not verify_entry_hmac(entry):
|
|
_logger.warning("Workday penalty file is not a signed object")
|
|
return None
|
|
return entry
|
|
|
|
|
|
def workday_penalty_today(now: datetime | None = None) -> bool:
|
|
"""Whether today lost its relaxed-day leniency to a missed prior ring."""
|
|
entry = _read_verified()
|
|
if entry is None or entry.get("penalty_date") != today_str(now):
|
|
return False
|
|
for field, locker in (
|
|
("issued_date", sickdays.LOCKER_ALARM),
|
|
("penalty_date", sickdays.LOCKER_WORKOUT),
|
|
):
|
|
if _stands_down_iso(entry.get(field), locker):
|
|
_logger.warning(
|
|
"Workday penalty void: its %s is a sick day with %s off", field, locker
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
def _stands_down_iso(value: object, locker: str) -> bool:
|
|
"""Whether ``value`` is a ``YYYY-MM-DD`` on which ``locker`` is switched off."""
|
|
if value is None:
|
|
return False # an older file without issued_date: penalty_date rules
|
|
try:
|
|
day = date.fromisoformat(str(value))
|
|
except ValueError:
|
|
_logger.warning("Workday penalty: %r is not a date; not a sick day", value)
|
|
return False
|
|
return stands_down(locker, day)
|