From ec7f4805f798bfa3b1508b3ffc633b051c23fcfc Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Mon, 5 Oct 2026 15:20:10 +0200 Subject: [PATCH] pin earned-time-v0.3.0 (registers the automation earner) Co-Authored-By: Claude Opus 5 (1M context) --- .pre-commit-config.yaml | 4 +-- pyproject.toml | 2 +- requirements.txt | 2 +- screen_locker/tests/test_earned.py | 26 ++++++++----------- .../tests/test_shutdown_flat_earners.py | 7 +++-- screen_locker/tests/test_shutdown_reading.py | 20 +++++++++++--- 6 files changed, 36 insertions(+), 25 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fd7162e..2d8f068 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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.1#subdirectory=crdt-sync - - earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.2.0#subdirectory=earned_time + - earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.3.0#subdirectory=earned_time - freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.0#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.1#subdirectory=crdt-sync - - earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.2.0#subdirectory=earned_time + - earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.3.0#subdirectory=earned_time - freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.0#subdirectory=freedays - mcp>=2,<3 - pydantic diff --git a/pyproject.toml b/pyproject.toml index 56a3df0..3e02e6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.1#subdirectory=crdt-sync", "freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.0#subdirectory=freedays", - "earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.2.0#subdirectory=earned_time", + "earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.3.0#subdirectory=earned_time", ] [project.scripts] diff --git a/requirements.txt b/requirements.txt index c277008..7cc6735 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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.1#subdirectory=crdt-sync -earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.2.0#subdirectory=earned_time +earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.3.0#subdirectory=earned_time freedays @ git+https://github.com/kuhyx/utils@freedays-v0.3.0#subdirectory=freedays gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.9.0#subdirectory=gatelock mcp==2.3.0 diff --git a/screen_locker/tests/test_earned.py b/screen_locker/tests/test_earned.py index 8695d75..36772e3 100644 --- a/screen_locker/tests/test_earned.py +++ b/screen_locker/tests/test_earned.py @@ -101,11 +101,9 @@ class TestLedgerFile: class TestFlatEarners: def test_registry_order_without_the_counted_workout(self) -> None: - assert flat_earners() == ( - earned_time.LEETCODE, - earned_time.READING, - earned_time.ANKI, - ) + flat = tuple(e for e in earned_time.EARNERS if e.kind == "flat") + assert flat_earners() == flat + assert flat[:3] == (earned_time.LEETCODE, earned_time.READING, earned_time.ANKI) def test_a_newly_registered_earner_is_picked_up( self, monkeypatch: pytest.MonkeyPatch @@ -215,7 +213,8 @@ class TestCannotCheck: class TestFlatAnswers: def test_every_flat_earner_answers_by_name(self, key: Path) -> None: write_ledger(earned_time.LEETCODE, [credit(earned_time.LEETCODE)]) - assert flat_answers() == {"leetcode": True, "reading": False, "anki": False} + nos = dict.fromkeys((e.name for e in flat_earners()), False) + assert flat_answers() == {**nos, "leetcode": True} def test_an_unknown_answer_is_none_and_warned( self, @@ -225,19 +224,16 @@ class TestFlatAnswers: ) -> None: monkeypatch.setattr(_earned, "HMAC_KEY_FILE", tmp_path / "no.key") with caplog.at_level(logging.WARNING): - assert flat_answers() == {"leetcode": None, "reading": None, "anki": None} - assert "LeetCode state could not be checked" in caplog.text - assert "reading state could not be checked" in caplog.text - assert "Anki state could not be checked" in caplog.text + assert flat_answers() == dict.fromkeys(e.name for e in flat_earners()) + for earner in flat_earners(): + assert f"{earner.label} state could not be checked" in caplog.text def test_a_no_is_not_warned( self, key: Path, caplog: pytest.LogCaptureFixture ) -> None: write_ledger(earned_time.LEETCODE, []) with caplog.at_level(logging.WARNING): - assert flat_answers() == { - "leetcode": False, - "reading": False, - "anki": False, - } + assert flat_answers() == dict.fromkeys( + (e.name for e in flat_earners()), False + ) assert "could not be checked" not in caplog.text diff --git a/screen_locker/tests/test_shutdown_flat_earners.py b/screen_locker/tests/test_shutdown_flat_earners.py index 0fd8d4a..d5624dd 100644 --- a/screen_locker/tests/test_shutdown_flat_earners.py +++ b/screen_locker/tests/test_shutdown_flat_earners.py @@ -180,8 +180,11 @@ class TestProjectionFollowsTheCut: def test_next_week_preview_includes_a_cut_dated_after_today( self, tmp_path: Path ) -> None: - """On Mon 2026-10-05, Anki's cut from Tue already shows; next week has it.""" + """On Mon 2026-10-05, the cuts from Tue already show; next week has them.""" shutdown = self._shutdown(tmp_path, datetime(2026, 10, 5, 12, 0, tzinfo=UTC)) - lowered = 19 * 60 - earned_time.ANKI.shutdown_minutes + tuesday = date(2026, 10, 6) + lowered = 19 * 60 - sum( + e.shutdown_minutes for e in earned_time.EARNERS if e.penalty_from == tuesday + ) assert [d.minutes for d in shutdown.rest_of_week] == [19 * 60] + [lowered] * 6 assert [d.minutes for d in shutdown.next_week_preview] == [lowered] * 7 diff --git a/screen_locker/tests/test_shutdown_reading.py b/screen_locker/tests/test_shutdown_reading.py index e6fdaa4..3523f66 100644 --- a/screen_locker/tests/test_shutdown_reading.py +++ b/screen_locker/tests/test_shutdown_reading.py @@ -18,7 +18,7 @@ import pytest from screen_locker import _shutdown_base from screen_locker._day import today_str -from screen_locker._earned import hhmm +from screen_locker._earned import flat_earners, hhmm from screen_locker._shutdown_base import ( _apply_flat_bonus, apply_flat_bonuses_if_new, @@ -31,6 +31,9 @@ if TYPE_CHECKING: from collections.abc import Iterator from pathlib import Path +# Terms the reset log spells out by hand; every later earner follows them. +_NAMED = frozenset({"leetcode", "reading"}) + def _mixin(*, adjust_ok: bool = True) -> MagicMock: mixin = MagicMock() @@ -62,8 +65,14 @@ class TestBaseHour: assert base_minutes(date(2026, 10, 1)) == 19 * 60 def test_long_after_the_cut_stays_lowered(self) -> None: - """No drift back to 20:00; Anki's cut (from 2026-10-06) holds too.""" - later_cuts = earned_time.ANKI.shutdown_minutes + """No drift back to 20:00; every later earner's cut holds too.""" + cut = earned_time.READING.penalty_from + assert cut is not None + later_cuts = sum( + e.shutdown_minutes + for e in earned_time.EARNERS + if e.penalty_from is not None and e.penalty_from > cut + ) assert base_minutes(date(2030, 1, 1)) == 19 * 60 - later_cuts def test_default_is_the_local_today(self) -> None: @@ -148,9 +157,12 @@ class TestResetIncludesReading: with caplog.at_level("INFO"): 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 + ) assert ( f"Daily base reset: {hhmm(base + 60)} (base {hhmm(base)} + 0h workout + " - "0h LeetCode + 1h reading + 0h Anki already earned today)." + f"0h LeetCode + 1h reading{later} already earned today)." ) in caplog.text