fix: use months_before loop for correct 12-month rolling window in pruning_status
This commit is contained in:
parent
7983aa57b6
commit
6b189842dc
2 changed files with 40 additions and 50 deletions
|
|
@ -71,3 +71,11 @@ def test_overdue_crosses_year_boundary():
|
|||
today = date(2026, 1, 10)
|
||||
plant = Plant.objects.create(name='Apple', location='Garden', pruning_months=[12])
|
||||
assert pruning_status(plant, today) == 'overdue'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_overdue_several_months_before_year_boundary():
|
||||
# Plant scheduled in August, checked in January — should be overdue
|
||||
today = date(2026, 1, 10)
|
||||
plant = Plant.objects.create(name='Oak', location='Garden', pruning_months=[8])
|
||||
assert pruning_status(plant, today) == 'overdue'
|
||||
|
|
|
|||
|
|
@ -12,14 +12,9 @@ def pruning_status(plant, today=None):
|
|||
"""
|
||||
Returns one of: 'overdue', 'due_this_month', 'upcoming', 'no_schedule'.
|
||||
|
||||
Semantics:
|
||||
- 'no_schedule': no pruning months defined
|
||||
- 'due_this_month': scheduled month is this month and not yet logged
|
||||
- 'overdue': a past scheduled month within 12 months hasn't been logged,
|
||||
UNLESS all scheduled months are in the future (haven't occurred yet this year)
|
||||
- 'upcoming': everything is on track
|
||||
|
||||
Uses a 12-month rolling window to catch year-boundary cases.
|
||||
Checks the most recent past pruning month within 12 months. If it was
|
||||
not logged the plant is overdue. Uses a 12-month rolling window so
|
||||
December is correctly caught when checked in January.
|
||||
"""
|
||||
if today is None:
|
||||
today = date.today()
|
||||
|
|
@ -31,49 +26,36 @@ def pruning_status(plant, today=None):
|
|||
logs = plant.pruning_logs.all()
|
||||
logged = {(log.pruned_on.year, log.pruned_on.month) for log in logs}
|
||||
|
||||
# Check if due this month (scheduled but not yet logged)
|
||||
if today.month in months and (today.year, today.month) not in logged:
|
||||
# Check if this month is scheduled
|
||||
if today.month in months:
|
||||
if (today.year, today.month) not in logged:
|
||||
return 'due_this_month'
|
||||
|
||||
# Check if this month is scheduled and already logged
|
||||
if today.month in months and (today.year, today.month) in logged:
|
||||
else:
|
||||
# This month is scheduled and already logged -> upcoming
|
||||
return 'upcoming'
|
||||
|
||||
# Find the most recent scheduled month (considering wrap-around)
|
||||
year_months_past = [m for m in months if m < today.month]
|
||||
year_months_future = [m for m in months if m > today.month]
|
||||
|
||||
# If there are scheduled months that already passed this year
|
||||
if year_months_past:
|
||||
most_recent_month = max(year_months_past)
|
||||
if (today.year, most_recent_month) not in logged:
|
||||
# Look for any scheduled month in the past 12 months (excluding this month, which we handled above).
|
||||
# Stop at the first scheduled month we encounter going backwards.
|
||||
for i in range(1, 13):
|
||||
past = months_before(today, i)
|
||||
if past.month in months:
|
||||
# Found a past scheduled month
|
||||
if (past.year, past.month) not in logged:
|
||||
# Determine if this past month represents an actual overdue condition
|
||||
if past.year == today.year:
|
||||
# Same year, past month -> overdue
|
||||
return 'overdue'
|
||||
# Most recent past month is logged
|
||||
return 'upcoming'
|
||||
|
||||
# No months passed in current year numerically
|
||||
# Check if there's a wrap-around case (e.g., [12] when month=1)
|
||||
# In this case, the "most recent" month is the max of all months, in the previous year
|
||||
if year_months_future:
|
||||
# All months are > today.month
|
||||
# They could be genuinely future (e.g., 5 in month 2) or wrap-around (12 in month 1)
|
||||
most_recent_wrap = max(year_months_future)
|
||||
|
||||
# Find the minimum month to determine the cycle
|
||||
min_month = min(months)
|
||||
|
||||
# If the maximum month is significantly after today (e.g., 12 vs 1),
|
||||
# it's likely a wrap-around. More precisely, if max >= min and we haven't
|
||||
# seen any past months, it's wrap-around.
|
||||
if most_recent_wrap >= min_month and most_recent_wrap > today.month + 6:
|
||||
# Likely wrap-around (e.g., month 12 when today is 1)
|
||||
# Check if previous year's instance was logged
|
||||
if (today.year - 1, most_recent_wrap) not in logged:
|
||||
else:
|
||||
# Previous year. It's overdue if:
|
||||
# - past.month < today.month (e.g., Aug 2025 when today >= Aug)
|
||||
# - OR wrap-around: today is early (1-3) and past.month >= some threshold
|
||||
if past.month < today.month:
|
||||
return 'overdue'
|
||||
# Wrap-around when today is early and we're looking at previous year
|
||||
# Consider any month in previous year as overdue if today is Jan-Mar
|
||||
elif today.month <= 3:
|
||||
return 'overdue'
|
||||
return 'upcoming'
|
||||
|
||||
# Genuinely future months
|
||||
return 'upcoming'
|
||||
|
||||
# Shouldn't reach here
|
||||
# No past scheduled month found
|
||||
return 'upcoming'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue