utils/earned_time/earned_time/tests/test_waiver.py
Krzysztof kuhy Rudnicki fd6c772eaf
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
earned_time pre-commit / pre-commit (push) Waiting to run
earned_time tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
earned_time 0.6.1: tutor cutover 2026-10-10; new-gate rungs spare the shutdown floor
TUTOR_FROM = 2026-10-10 (the day after the deploy). The automation tutor
keeps confirmed_on=None, so it stays "new".

Ladder floor: a rung comes off the floor only for a pure bonus (no
penalty_from) or an earner whose penalty has started -- the same
_penalised() rule (first_credits -> penalty_start, confirmed_on fail-closed
fallback, penalty_from without first_credits) the gaming base uses. Before
this, a never-credited tutor would have lowered 2026-10-10's floor by its
full 50-minute rung (19:50 -> 19:00) even with first_credits. Maturity can
only raise time: new property test against the 0.6.0 floor formula.

ruff 0.17.0 (dependency-freshness): CPY001 ignored -- personal repo, no
copyright policy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdfmsGMht9ypyvxGPmNF1V
2026-10-09 22:11:35 +02:00

71 lines
2.4 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki
"""The gap-day waiver: Anki and the old Automation, from ANKI_WAIVED_FROM."""
from __future__ import annotations
from datetime import timedelta
import pytest
from earned_time import (
ANKI_WAIVED_FROM,
EARNERS,
LADDER_FROM,
TUTOR_EARNERS,
TUTOR_FROM,
earners_for,
resolve,
)
BEFORE = ANKI_WAIVED_FROM - timedelta(days=1)
NAMES = ("workout", "leetcode", "reading", "anki", "automation")
def test_the_waiver_sits_between_the_old_registry_and_the_tutor() -> None:
assert BEFORE < ANKI_WAIVED_FROM < TUTOR_FROM
assert earners_for(BEFORE) is EARNERS
assert [e.name for e in earners_for(ANKI_WAIVED_FROM)] == list(NAMES[:3])
assert earners_for(TUTOR_FROM) is TUTOR_EARNERS
def test_nothing_done_is_raised_by_exactly_what_the_two_paid() -> None:
zero = dict.fromkeys(NAMES, 0)
# TUTOR_FROM == LADDER_FROM: the waiver's only day is pre-ladder; the first
# ladder day's floor is in test_maturity_floor.py.
assert ANKI_WAIVED_FROM < LADDER_FROM == TUTOR_FROM
gap = resolve(zero, ANKI_WAIVED_FROM)
unwaived = resolve(zero, ANKI_WAIVED_FROM, EARNERS)
assert (gap.base.gaming_minutes, gap.gaming_minutes) == (240, 240)
assert gap.shutdown_minutes == 19 * 60 # 20:00 minus reading's 60
assert gap.gaming_minutes - unwaived.gaming_minutes == 30 + 30
assert gap.shutdown_minutes - unwaived.shutdown_minutes == 30 + 30
@pytest.mark.parametrize("workout", [0, 1])
@pytest.mark.parametrize("leetcode", [0, 1])
@pytest.mark.parametrize("reading", [0, 1])
@pytest.mark.parametrize("anki", [0, 1])
def test_raise_only_on_every_combination(
workout: int, leetcode: int, reading: int, anki: int
) -> None:
answers = {
"workout": workout,
"leetcode": leetcode,
"reading": reading,
"anki": anki,
"automation": anki,
}
day = ANKI_WAIVED_FROM
waived = resolve(answers, day)
unwaived = resolve(answers, day, EARNERS)
assert waived.gaming_minutes >= unwaived.gaming_minutes
assert waived.shutdown_minutes >= unwaived.shutdown_minutes
if anki:
assert waived.gaming_minutes == unwaived.gaming_minutes
assert waived.shutdown_minutes == unwaived.shutdown_minutes
def test_all_done_still_8h_and_23() -> None:
done = resolve(dict.fromkeys(NAMES, 1), ANKI_WAIVED_FROM)
assert (done.gaming_minutes, done.shutdown_minutes) == (480, 23 * 60)