fix(goals): months_remaining uses day-precision

PF audit edge case. Calendar-month math undercounted near the
deadline: May 30 with a June 1 target returned `1` ("save $5k
this month"), then June 1 morning returned `0` (falls through to
`remaining_amount` charged as one-month-required). Users saw a
$5k/mo required rate for a 2-day window, then $5k flat on
deadline day — a cliff that doesn't match reality.

Replace calendar-month delta with `(target_date - Date.current) / 30.0`
so a 2-day-out deadline reports ~0.07 months and `monthly_target_amount`
scales proportionally. `[..., 0.0].max` keeps the past-due case
zero-clamped.
This commit is contained in:
Guillem Arias
2026-05-14 21:50:54 +02:00
parent 51fca464b5
commit 8e9a697b1f

View File

@@ -92,11 +92,16 @@ class Goal < ApplicationRecord
end
end
# Day-precision so the near-deadline cliff doesn't kick in: at
# calendar-month precision, May 30 → June 1 returned 1 ("save $5k this
# month") then June 1 → June 1 returned 0 (falls through to
# "remaining_amount in one month"). Now a 2-day-out deadline reports
# ~0.07 months and `monthly_target_amount` scales accordingly.
def months_remaining
return nil unless target_date
months = (target_date.year - Date.current.year) * 12 + (target_date.month - Date.current.month)
[ months, 0 ].max
days = (target_date - Date.current).to_i
[ (days / 30.0), 0.0 ].max
end
def monthly_target_amount
@@ -107,7 +112,7 @@ class Goal < ApplicationRecord
elsif months_remaining.zero?
remaining_amount
else
(remaining_amount.to_d / months_remaining).ceil(2)
(remaining_amount.to_d / months_remaining.to_d).ceil(2)
end
end