mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-14 04:50:58 +01:00
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
Rows carry no earner name, so only the ledger path moves; the tutor copies its data dir (old rows included). Pre-cutover AUTOMATION (anki quota), LADDER and the waiver are unchanged. Same minutes, cap, ladder, TUTOR_FROM and confirmed_on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YADHKXzZhhePzQ2rNoXFcK
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
# Copyright (c) 2026 Krzysztof Rudnicki
|
|
"""Which registry is in force on a day: the tutor cutover (``TUTOR_FROM``).
|
|
|
|
Before ``TUTOR_FROM`` every day resolves on :data:`~earned_time.EARNERS`,
|
|
exactly as it always has; from it on :data:`~earned_time.TUTOR_EARNERS`
|
|
(Anki and Automation retired, the tutor paid per minute). The switch is a date, so it
|
|
never rewrites how an old day resolved.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import UTC, date, datetime
|
|
from functools import cache
|
|
import sys
|
|
from typing import TYPE_CHECKING, Final
|
|
|
|
from earned_time._ladder import ANKI_WAIVED_FROM, TUTOR_FROM
|
|
|
|
if TYPE_CHECKING:
|
|
from earned_time._policy import Earner
|
|
|
|
# Waived from ``ANKI_WAIVED_FROM`` until ``TUTOR_FROM``.
|
|
_WAIVED: Final = frozenset({"anki", "automation"})
|
|
|
|
|
|
def registries() -> tuple[tuple[Earner, ...], ...]:
|
|
"""Every registry, oldest first.
|
|
|
|
Read through the package, not this module: consumers' test suites patch
|
|
``earned_time.EARNERS`` to register a stand-in earner, and the patch must
|
|
keep reaching :func:`earners_for` and :func:`all_earners`.
|
|
"""
|
|
package = sys.modules["earned_time"] # loaded: it imports this module
|
|
return (package.EARNERS, package.TUTOR_EARNERS)
|
|
|
|
|
|
def earners_for(day: date | None = None) -> tuple[Earner, ...]:
|
|
"""The registry in force on ``day`` (default today).
|
|
|
|
Between ``ANKI_WAIVED_FROM`` and ``TUTOR_FROM`` the old registry without
|
|
Anki and the anki-backed Automation: neither penalised nor paid.
|
|
"""
|
|
target = day or datetime.now(tz=UTC).astimezone().date()
|
|
before, tutor = registries()
|
|
if target >= TUTOR_FROM:
|
|
return tutor
|
|
if target >= ANKI_WAIVED_FROM:
|
|
return _without_waived(before)
|
|
return before
|
|
|
|
|
|
@cache
|
|
def _without_waived(registry: tuple[Earner, ...]) -> tuple[Earner, ...]:
|
|
"""``registry`` minus the waived earners; one stable tuple per registry."""
|
|
return tuple(e for e in registry if e.name not in _WAIVED)
|
|
|
|
|
|
def all_earners() -> tuple[Earner, ...]:
|
|
"""Every earner of every registry, once each, oldest registry first.
|
|
|
|
What a reader that must not miss a ledger across a cutover watches
|
|
(screen-locker's path unit).
|
|
"""
|
|
seen: dict[Earner, None] = {}
|
|
for registry in registries():
|
|
seen.update(dict.fromkeys(registry))
|
|
return tuple(seen)
|