Validate month input and document cycle labeling convention

Review finding BE-06 (suspected off-by-one in get_cycle_range) is a
FALSE POSITIVE: budget month M composes get_previous_cycle with
get_cycle_range to cover [(M-1)/18, M/18), matching both the code
comment and the frontend convention. Verified against prod data: May
2026 cycle-window SQL sum matches the API (95 txs exactly, refunds to
the colón, 0.13%% delta from one EUR tx). The docstring now spells out
the labeling convention, and month is validated 1-12 (BE-07).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-06-09 19:46:54 -06:00
parent fa19ac1988
commit 0a4c3e9436

View File

@@ -65,8 +65,14 @@ def get_effective_amount(item: RecurringItem, month: int, year: int) -> float |
return None return None
def _validate_month(month: int) -> None:
if not 1 <= month <= 12:
raise ValueError(f"month must be 1-12, got {month}")
def get_month_range(year: int, month: int) -> tuple[datetime, datetime]: def get_month_range(year: int, month: int) -> tuple[datetime, datetime]:
"""Return (start, end) for a calendar month.""" """Return (start, end) for a calendar month."""
_validate_month(month)
start = datetime(year, month, 1) start = datetime(year, month, 1)
if month == 12: if month == 12:
end = datetime(year + 1, 1, 1) end = datetime(year + 1, 1, 1)
@@ -76,7 +82,15 @@ def get_month_range(year: int, month: int) -> tuple[datetime, datetime]:
def get_cycle_range(year: int, month: int) -> tuple[datetime, datetime]: def get_cycle_range(year: int, month: int) -> tuple[datetime, datetime]:
"""Return (start, end) for billing cycle: month/18 to month+1/18.""" """Return (start, end) of the billing cycle that STARTS on the 18th of
`month`: [month/18, month+1/18).
Note the labeling convention: budget month M covers the cycle that ENDS
on the 18th of M, so callers compose get_previous_cycle(year, M) with
this function — see compute_actuals_by_source. Pinned by
tests/test_cycle_math.py.
"""
_validate_month(month)
start = datetime(year, month, 18) start = datetime(year, month, 18)
if month == 12: if month == 12:
end = datetime(year + 1, 1, 18) end = datetime(year + 1, 1, 18)