Leaf-set extraction only, so every new import is one-way and no cycle is
possible:
_stats.py 523 -> 233 + _stats_types (the shared _GameTimes dataclass,
imported by parent and helpers alike),
_stats_gathering, _stats_display
scanning.py 521 -> 248 + _scanning_candidates, _scanning_assign,
_scanning_tampering
_GameTimes and the four moved scanning helpers are re-exported from their
original modules via __all__ (mypy --no-implicit-reexport needs them listed),
so callers and tests keep their existing import paths.
Test patch targets were retargeted per file from the pytest failure list —
the target is the module whose source now contains the function under test.
Note TestPrintScenario and TestDoCheck did NOT move, so they keep the
original target while the rest of their files change.
1344 tests, 100.00% coverage. ./run.sh status and ./run.sh stats verified
against the live install; service at 0 restarts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A1EYiv7e7HGhET84oa9M3
The pre-push ci-mirror runs the file-length gate over the whole tree, not
the diff, so ~30 files left it red and no push could land. This clears the
test half: 18 modules split into 60, plus the docstrings pylint wants on
every class and test method.
Splits are by responsibility, never mechanical partN names -- files are
named for the first class or test they contain (test_stats_sum_hours,
test_store_blocker_unblock_store, ...). Where a file was a single oversized
class, its test methods are partitioned into sibling classes instead, with
fixtures and helper classes copied into every part: a method-level split
that drops an autouse fixture fails at runtime, not at import, so it is easy
to miss.
test_polls_tracking_part2 keeps its name only because it still holds one
indivisible class; the rest of the partN files are gone.
1347 tests pass, unchanged in count and name from before the split -- the
splitter was checked by diffing test-function names, since a test silently
lost in a partition looks identical to one that never existed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016A1EYiv7e7HGhET84oa9M3
The `except SteamAPIError` branch in _refresh_recently_played_completions
was only covered incidentally, by cmd_stats tests that make a real
network call which fails — reachable only under certain pytest-randomly
orderings, so CI's 100% coverage gate flaked (missing _stats.py 368-370).
Add a focused test that mocks SteamAPIClient.get_owned_games to raise
SteamAPIError, covering the branch reliably regardless of order or
network.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RRCdR4D8SaVrN2xraMQxM
User can now pick any owned game by Steam app_id via `pick-manual <id>`.
The script resolves the game name, asks for YES confirmation, then locks
all other commands for 14 days or until the game is 100% complete.
Post-assignment steps (uninstall others, install, hide library) mirror
the automatic pick flow. Lock is checked before every command including
add-exception. Also fixes pre-existing test failures in hltb, stats,
and web_dataset modules and adds 100% coverage for all changed code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four bugs fixed:
- HLTB search returned 0 results for ~87 games with special chars (™, ®, &,
standalone -, (Legacy), RHCP, etc.) — add _sanitize_search_name() and
extend _build_search_variants() with Steam-suffix and edition stripping
- fetch_hltb_detail_missing returned immediately because `app_id not in rush`
was always False (all keys present with -1) — fix to `rush.get(id,-1) <= 0`
- save_hltb_cache overwrote rush/leisure on confidence-only partial saves —
now reads existing cache and preserves data when extras dicts are empty
- _filter_qualifying_games excluded 57 games with stale snapshot hours (-1)
even though HLTB hours cache had valid data — add cache fallback
Result: stats shows Rush 64,670h / Leisure 136,807h / Worst 228,594h for
all 785 qualifying games with full rush+leisure detail.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>