utils/earned_time/earned_time/__init__.py
Krzysztof kuhy Rudnicki 9733a29a65
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
earned_time pre-commit / pre-commit (push) Has been cancelled
earned_time tests / test (push) Has been cancelled
file length / file-length (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
palette drift / palette (push) Has been cancelled
repo-contract / repo contract (push) Has been cancelled
shared gate tests / gate unit tests (push) Has been cancelled
earned_time 0.16.0: leisure=None|True|False, so the gaming day decides (C6)
resolve()/base_for(leisure=) is now tri-state: None (default) decides by
the switch file on `day`, True forces leisure pricing (the shadow budget),
False forces the old base and cap. A consumer billing on the 06:00 gaming
day passes leisure_active(gaming_day), so 00:00-05:59 on the go-live date
no longer prices the eve with the +2 h leisure base/cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
2026-10-19 16:58:27 +02:00

169 lines
4.3 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki
"""Penalty-then-reward for gate apps: one registry, one sum, two consumers.
A gate (leetcode-guard, book-guard, ...) never touches gaming time or the
shutdown schedule. It publishes a fact -- HMAC-signed ``credit`` rows in its
ledger -- and registers one :class:`Earner` here. The two consumers read the
registry and apply the result:
* steam-backlog-enforcer enforces ``Resolution.gaming_minutes``;
* screen-locker writes ``Resolution.shutdown_minutes`` to the schedule.
A consumer's whole integration::
import earned_time
answers = {
e.name: earned_time.done_today(e, home / e.ledger, KEY_FILE)
for e in earned_time.EARNERS
if e.ledger is not None
}
answers["workout"] = my_workout_count()
day = earned_time.resolve(answers, sick=sickdays.is_sick_day())
apply(day.gaming_minutes)
``sick`` (0.11.0) only raises shutdown to at least 20:00; gaming is unchanged.
``leisure`` (0.16.0): ``None`` follows the switch file on ``day``, ``True``
prices gaming with the leisure base and cap (the shadow budget), ``False``
with the old ones. The first real leisure day is :func:`leisure_from`
(0.15.0, the switch file); ask :func:`leisure_active` for the gaming day.
How far to trust a gate is computed, not listed: :func:`maturity` (and
``python -m earned_time maturity``).
Adding a gate is one :class:`Earner` in :mod:`earned_time._policy`; with
``penalty_from`` set, the base drops by what it pays back from that day on.
"""
from __future__ import annotations
from earned_time._credits import (
credit_units,
day_window,
first_credit_at,
)
from earned_time._evidence import real_credit
from earned_time._ladder import (
ANKI_WAIVED_FROM,
LADDER,
LADDER_FROM,
SHUTDOWN_CEILING_MINUTES,
TUTOR_FROM,
TUTOR_LADDER,
UNIFIED_FROM,
UNIFIED_LADDER,
WAKE_MINUTES,
Rung,
extra_shutdown_minutes_for,
ladder_for,
on_ladder,
shutdown_ceiling_for,
shutdown_minutes_for,
)
from earned_time._ledger import done_today, entry_signature, today_window, verified
from earned_time._leisure import (
LEISURE_BASE_MINUTES,
LEISURE_CEILING_MINUTES,
LEISURE_FROM,
LEISURE_FROM_PATH,
gaming_base_minutes_for,
gaming_ceiling_minutes_for,
leisure_active,
leisure_from,
)
from earned_time._maturity import (
MATURE_MIN_AGE_DAYS,
MATURE_MIN_CREDIT_DAYS,
Level,
Maturity,
confirmed_start,
maturity,
penalty_start,
)
from earned_time._policy import (
ANKI,
AUTOMATION,
EARNERS,
GAMING_BASE_MINUTES,
GAMING_CEILING_MINUTES,
LEETCODE,
READING,
SHUTDOWN_BASE_MINUTES,
TUTOR,
TUTOR_EARNERS,
WORKOUT,
Earner,
earner,
)
from earned_time._quota import TUTOR_QUOTA_FROM, TUTOR_QUOTA_MINUTES, tutor_quota_for
from earned_time._registry import all_earners, earners_for, registries
from earned_time._resolve import Base, Resolution, Term, base_for, resolve
from earned_time._sick import SICK_SHUTDOWN_FLOOR_MINUTES, sick_shutdown_from
__all__ = [
"ANKI",
"ANKI_WAIVED_FROM",
"AUTOMATION",
"EARNERS",
"GAMING_BASE_MINUTES",
"GAMING_CEILING_MINUTES",
"LADDER",
"LADDER_FROM",
"LEETCODE",
"LEISURE_BASE_MINUTES",
"LEISURE_CEILING_MINUTES",
"LEISURE_FROM",
"LEISURE_FROM_PATH",
"MATURE_MIN_AGE_DAYS",
"MATURE_MIN_CREDIT_DAYS",
"READING",
"SHUTDOWN_BASE_MINUTES",
"SHUTDOWN_CEILING_MINUTES",
"SICK_SHUTDOWN_FLOOR_MINUTES",
"TUTOR",
"TUTOR_EARNERS",
"TUTOR_FROM",
"TUTOR_LADDER",
"TUTOR_QUOTA_FROM",
"TUTOR_QUOTA_MINUTES",
"UNIFIED_FROM",
"UNIFIED_LADDER",
"WAKE_MINUTES",
"WORKOUT",
"Base",
"Earner",
"Level",
"Maturity",
"Resolution",
"Rung",
"Term",
"all_earners",
"base_for",
"confirmed_start",
"credit_units",
"day_window",
"done_today",
"earner",
"earners_for",
"entry_signature",
"extra_shutdown_minutes_for",
"first_credit_at",
"gaming_base_minutes_for",
"gaming_ceiling_minutes_for",
"ladder_for",
"leisure_active",
"leisure_from",
"maturity",
"on_ladder",
"penalty_start",
"real_credit",
"registries",
"resolve",
"shutdown_ceiling_for",
"shutdown_minutes_for",
"sick_shutdown_from",
"today_window",
"tutor_quota_for",
"verified",
]