mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 20:27:11 +01:00
Ninth batch of the 250-line sweep. Every Python file in the repo is now under cap (over-cap count 25 -> 18; the 18 remaining are all Dart). _foodbank.py 365 -> 210 (+ _foodbank_search, _foodbank_rebuild) test_averages.py 351 -> 222 (+ test_averages_report.py) _estimator.py 325 -> 137 (+ _estimator_off.py) _budget.py 323 -> 227 (+ _budget_biometrics, _budget_derived) test_foodbank.py 311 -> 117 (+ test_foodbank_rebuild.py) _averages.py 298 -> 174 (+ _averages_periods, _averages_report) test_budget_history.py 288 -> 162 (+ test_budget_history_io.py) The split boundaries are constrained by which module owns each conftest-patched state path, not by line count. BUDGET_FILE stays in _budget, FOOD_BANK_FILE in _foodbank: the derived/search/rebuild halves reach the file only through their parent's private helpers. Two of the new modules had copied FOOD_BANK_FILE into their headers as an incidental import, which is exactly the leak the guard is for -- both cleaned. test_state_redirect.py now covers all five redirected paths (FOOD_LOG_FILE, BUDGET_FILE, BUDGET_HISTORY_FILE, FOOD_BANK_FILE, MANUAL_BANK_FILE) rather than just the food log, in both directions: only the owning module may name each constant, and each redirect demonstrably moves a real write off the live path. Two rewires fail-closed rather than silently: `_budget.budget_weight` and `_estimator.requests` were module-attribute references no import rewrite touches, and both raised AttributeError immediately. The latter appeared only after ruff removed the now-unused `requests` import from _estimator -- the import was load-bearing solely as a patch target. Verified: 707 Python tests / 100% branch coverage, 5 layout-fits tests under xvfb, check_patch_targets 0, pre-commit clean, ~/.local/share/diet_guard byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RA9ynjqXPnexRQnR5oAuCC
87 lines
2.7 KiB
Python
87 lines
2.7 KiB
Python
"""CLI handler for the ``init`` subcommand.
|
|
|
|
Split out of :mod:`diet_guard._cli` to hold the repo's 250-line cap, following
|
|
the same thin-per-subcommand-handler shape as ``_cli_gate.py`` (``gate``),
|
|
``_cli_log.py`` (``ate``) and ``_cli_sync.py`` (``sync``).
|
|
|
|
The biometrics prompted for here are used **once** and discarded: only the
|
|
computed budget (and the body weight, which the protein target needs) is ever
|
|
persisted. See ``CLAUDE.md``'s "Biometrics are used once and discarded".
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from diet_guard._budget import write_budget
|
|
from diet_guard._budget_biometrics import Biometrics, compute_target_budget
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
# Accepted answers for the sex prompt that map to the male BMR constant.
|
|
MALE_ANSWERS = {"m", "male"}
|
|
FEMALE_ANSWERS = {"f", "female"}
|
|
|
|
|
|
def read_init_inputs(
|
|
emit: Callable[[str], None],
|
|
ask: Callable[[str], str],
|
|
) -> tuple[Biometrics, float, float] | None:
|
|
"""Prompt for biometrics on stdin; return (bio, activity, deficit) or None.
|
|
|
|
Returns None (after printing why) on any unparsable or out-of-range input,
|
|
so a typo never sets a wrong budget.
|
|
"""
|
|
try:
|
|
weight = float(ask("weight in kg:"))
|
|
height = float(ask("height in cm:"))
|
|
age = float(ask("age in years:"))
|
|
sex_raw = ask("sex (m/f):").lower()
|
|
activity = float(
|
|
ask(
|
|
"activity factor "
|
|
"(1.2 sedentary / 1.375 light / 1.55 moderate / 1.725 active):",
|
|
),
|
|
)
|
|
deficit = float(ask("daily deficit in kcal (e.g. 200):"))
|
|
except ValueError:
|
|
emit("that was not a number; nothing was set.")
|
|
return None
|
|
|
|
if sex_raw in MALE_ANSWERS:
|
|
is_male = True
|
|
elif sex_raw in FEMALE_ANSWERS:
|
|
is_male = False
|
|
else:
|
|
emit('sex must be "m" or "f"; nothing was set.')
|
|
return None
|
|
|
|
bio = Biometrics(
|
|
weight_kg=weight,
|
|
height_cm=height,
|
|
age_years=age,
|
|
is_male=is_male,
|
|
)
|
|
return bio, activity, deficit
|
|
|
|
|
|
def cmd_init(
|
|
emit: Callable[[str], None],
|
|
ask: Callable[[str], str],
|
|
) -> int:
|
|
"""Compute the starting budget from biometrics and write it."""
|
|
inputs = read_init_inputs(emit, ask)
|
|
if inputs is None:
|
|
return 2
|
|
bio, activity, deficit = inputs
|
|
budget = compute_target_budget(
|
|
bio,
|
|
activity_factor=activity,
|
|
deficit_kcal=deficit,
|
|
)
|
|
write_budget(budget, weight_kg=bio.weight_kg)
|
|
emit(f"budget computed from your biometrics: {budget:g} kcal/day.")
|
|
emit("edit it any time from the gate's calendar tab or the phone app.")
|
|
return 0
|