Fix EndOf off-by-one + add Recurring cycle kind#69
Conversation
EndOf#final_date subtracted 1 period before adding period_count, causing V1E12M to expire at end of month 11 instead of month 12. Remove the offset so the notation matches user expectations: V1E12M from Dec 1 now correctly expires Dec 31 (not Nov 30). Fixed: EndOf cycle expiration was 1 month early (QUAL-6189)
New cycle type for recurring windows anchored to a from_date. V1R24MF2026-03-31 = complete 1 within 24 months from March 31, 2026. After completion, next window starts from the completion date. Unlike EndOf: no end-of-month rounding. Unlike Lookback: anchored to from_date, not sliding from today. Added: Recurring cycle kind with R notation (QUAL-6317)
Open Question Resolved: Next Window Anchors from Completion DateReviewed AFMAN 10-3500V1 (3 June 2025), Attachment 2 to confirm the Recurring cycle's anchoring behavior. A2.3.3: "For tasks with a minimum frequency determined by months, currency on the task is maintained through the last day of the expiration month." Table A2.2 frequencies (12 mos, 24 mos, etc.) are measured from when the task was performed. No mention of fixed calendar windows for general tasks. Further confirmed by Note 2 (p.34): "if member receives an Unsatisfactory on 15 April, then member's due month is July" — window starts from event date. The one exception is NRP Recertification (Note 5) which uses a fixed calendar window ending 31 March — handled by the existing Calendar ( Decision: Next window starts from the completion date as currently implemented. This PR is ready for review. |
There was a problem hiding this comment.
@rymiwe This should be 2 PRs.
I think the change to EndOf is a good one. It may open up a disconnect with users, though. JTACs talked about their Evaluations being due on an 18m cycle but meant "end of 17th subsequent month". I think I'd rather have that disconnect than the one you just fixed, which had always bugged me.
[EDIT] If SOFJTAC ever gets turned back on we'll need a data migration to fix all of the JTAC EndOf cycles, which will now be wrong per their instructions.
I also like Recurring, but a few thoughts:
-
The current name collides with the existing
Cycle#recurring?predicate -
How are you shifting the
FROMdate, once the cycle is satisfied? If that's the responsibility of the consuming app than you may not need this class at all...you might be able to have the consuming app just useWithin, and then move the From date. But then consuming app would be starting to encroach on SOF-cycle's responsibilities...
https://github.com/SOFware/sof-cycle/blob/main/lib/sof/cycles/within.rb#L5-L40 -
If you keep
Recurringyou should improve the disambiguation withWithin. E.g. their#examplesand#to_sbehavior should surface their differences.
You also should be able to get the dormant behavior you're looking for by modifying the Parser rather than dormant-checking here. See
Line 23 in 38917eb
I don't love that the dormant-capability is only revealed in Parser, but we should have a single pattern for this...stick with the existing one for Recurring or refactor the approach for all.
Ping me today to discuss if you get a chance.
Summary
Two changes for 0.1.13:
Fix: EndOf cycle
final_dateoff-by-one (QUAL-6189)EndOf#final_datesubtracted 1 period before addingperiod_count, causingV1E12Mto expire at the end of month 11 instead of month 12- 1.send(period)offset so the notation matches user expectationsExample — V1E12M from Dec 1, 2025:
Confirmed by AFMAN 10-3500V1 A2.3.3: "For tasks with a minimum frequency determined by months, currency on the task is maintained through the last day of the expiration month."
Added: Recurring cycle kind —
Rnotation (QUAL-6317)New cycle type for recurring windows anchored to a from_date.
V1R24MF2026-03-31= complete 1 within 24 months from March 31, 2026. After completion, the next window starts from the completion date.Fprefix like EndOf and Within)Anchoring behavior: Next window starts from the completion date, confirmed by AFMAN 10-3500V1 Table A2.2 — frequencies are measured from task performance date. (See comment for full analysis.)
Test plan
Fixes QUAL-6189
Addresses QUAL-6317