deps: earned_time 0.6.1 (tutor cutover; new-gate rungs spare the floor)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Krzysztof Rudnicki 2026-10-10 08:24:48 +02:00
parent 3a2ad0245c
commit 2140ca5180
6 changed files with 42 additions and 21 deletions

View File

@ -134,7 +134,7 @@ repos:
# than merely an old one.
- gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.9.0#subdirectory=gatelock
- crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.2#subdirectory=crdt-sync
- earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.0#subdirectory=earned_time
- earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.1#subdirectory=earned_time
- freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.1#subdirectory=freedays
- mcp>=2,<3
- pydantic
@ -155,7 +155,7 @@ repos:
# than merely an old one.
- gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.9.0#subdirectory=gatelock
- crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.2#subdirectory=crdt-sync
- earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.0#subdirectory=earned_time
- earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.1#subdirectory=earned_time
- freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.1#subdirectory=freedays
- mcp>=2,<3
- pydantic

View File

@ -10,7 +10,7 @@ dependencies = [
"gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.9.0#subdirectory=gatelock",
"crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.2#subdirectory=crdt-sync",
"freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.1#subdirectory=freedays",
"earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.0#subdirectory=earned_time",
"earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.1#subdirectory=earned_time",
]
[project.scripts]

View File

@ -4,7 +4,7 @@ bandit==1.9.4
codespell==2.4.3
coverage==7.16.2
crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.2#subdirectory=crdt-sync
earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.0#subdirectory=earned_time
earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.6.1#subdirectory=earned_time
freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.1#subdirectory=freedays
gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.9.0#subdirectory=gatelock
mcp==2.3.0

View File

@ -199,8 +199,14 @@ class TestProjectionFollowsTheCut:
def base(day: date) -> int:
# From the sleep ladder (Sat 2026-10-10) the registry prices the
# day itself; before it, the cut from Tuesday holds.
# The registry in force that day (the tutor cutover switches it).
return earned_time.base_for(day, _earned.registry(day)).shutdown_minutes
# The registry in force that day (the tutor cutover switches it),
# and from today on its first credits: a never-paid tutor spares
# the floor (earned_time 0.6.1).
earners = _earned.registry(day)
starts = _earned.first_credits(earners, day)
return earned_time.base_for(
day, earners, first_credits=starts
).shutdown_minutes
week = [date(2026, 10, 5 + i) for i in range(7)]
assert [base(d) for d in week[1:4]] == [lowered] * 3

View File

@ -17,7 +17,7 @@ import earned_time
import pytest
from screen_locker._day import today_str
from screen_locker._earned import flat_earners, hhmm, registry
from screen_locker._earned import first_credits, gate_earners, hhmm, registry
from screen_locker._shutdown_base import (
_apply_flat_bonus,
apply_flat_bonuses_if_new,
@ -70,12 +70,14 @@ class TestBaseHour:
minus every earner's first unit, not 20:00 minus the cuts, so the day
is priced by the registry and only the "stays lowered" part is ours.
The Anki waiver (``ANKI_WAIVED_FROM``) raises that floor to 19:50, so
"lowered" means below the uncut 20:00, not at or below 19:00.
"lowered" means below the uncut 20:00, not at or below 19:00; a gate
that never paid out (the tutor) spares it (earned_time 0.6.1).
"""
later = date(2030, 1, 1)
expected = earned_time.base_for(later, registry(later)).shutdown_minutes
assert base_minutes(later) == expected
assert expected < base_minutes(date(2026, 9, 30))
starts = first_credits(registry(later), later)
base = earned_time.base_for(later, registry(later), first_credits=starts)
assert base_minutes(later) == base.shutdown_minutes
assert base.shutdown_minutes < base_minutes(date(2026, 9, 30))
def test_default_is_the_local_today(self) -> None:
assert base_minutes() == base_minutes(datetime.now().astimezone().date())
@ -163,7 +165,7 @@ class TestResetIncludesReading:
reset_to_base_if_new_day(tmp_path / "state.json", _mixin())
base = base_minutes()
later = "".join(
f" + 0h {e.label}" for e in flat_earners() if e.name not in _NAMED
f" + 0h {e.label}" for e in gate_earners() if e.name not in _NAMED
)
assert (
f"Daily base reset: {hhmm(base + 60)} (base {hhmm(base)} + 0h workout + "

View File

@ -1,9 +1,10 @@
"""The Automation tutor on a simulated cutover: penalty start and grace floor.
``earned_time.TUTOR_FROM`` is a far-future sentinel, so the cutover is
simulated on a fixed day: the registry's cutover (bound by value in
``_registry`` and ``_ladder``) and the tutor's own ``penalty_from`` move to
:data:`_CUTOVER`. Nothing is written outside the suite's redirected home.
``earned_time.TUTOR_FROM`` is 2026-10-10 since earned_time 0.6.1; the
fixture still pins the registry's cutover (bound by value in ``_registry`` and
``_ladder``) and the tutor's own ``penalty_from`` to :data:`_CUTOVER`, so the
suite does not move if a later release moves the cutover. Nothing is written
outside the suite's redirected home.
"""
from __future__ import annotations
@ -92,13 +93,25 @@ class TestNeverCredited:
assert wired.resolution.base.gaming_minutes == (
unwired.resolution.base.gaming_minutes + cut
)
# KNOWN GAP (earned_time 0.6.0): on the sleep ladder the shutdown
# floor is the ceiling minus every registered earner's full pay and
# never asks whether a penalty is in force, so a never-credited tutor
# still costs its shutdown minutes. A fix there flips this assertion.
def test_no_tutor_rung_off_the_shutdown_floor(self) -> None:
"""earned_time 0.6.1: a gate whose penalty has not started spares the floor.
Unwired, the tutor's penalty starts at ``penalty_from`` and its full
rung (13+13+12+12) comes off the ladder floor; wired, the never-paid
tutor costs no shutdown either, so the floor stays at 19:50.
"""
answers = {e.name: 0 for e in registry(_CUTOVER)}
starts = first_credits(registry(_CUTOVER), _CUTOVER)
wired = derive(DayInputs(_CUTOVER, answers, first_credits=starts))
unwired = derive(DayInputs(_CUTOVER, answers))
tutor = earned_time.AUTOMATION_TUTOR
rung = tutor.shutdown_for(tutor.max_units or 1, _CUTOVER)
assert rung == 50
assert wired.resolution.base.shutdown_minutes == (
unwired.resolution.base.shutdown_minutes
unwired.resolution.base.shutdown_minutes + rung
)
assert wired.resolution.base.shutdown_minutes == 19 * 60 + 50
@pytest.mark.usefixtures("tutor")