Files
echo/.references/references-only/reference vault/memory/decisions/2026-05-27-cpas-rolloff-clean-cycle-model.md
T
2026-06-05 00:49:20 -05:00

38 lines
2.0 KiB
Markdown

---
type: decision
status: shipped
tags:
- cpas
- scoring
- roll-off
- mpm
updated: 2026-05-27
---
# [[CPAS]] roll-off: clean-cycle model, JS as source of truth
## Context
System was implementing a literal per-violation rolling 90-day window. Jason reported that the handbook actually says "5 points roll off after 90 consecutive days of no violations" — i.e. any new violation should reset the countdown for everyone, not just for itself.
## Decision
Adopt the clean-cycle model:
1. Roll-off amount per cycle: **5 points, oldest first**
2. Repeat cadence: **another full 90 clean days required** for each successive 5-point roll-off
3. Reset trigger: **any new non-negated violation** (negated violations don't reset)
Implement all standing math in JS (`lib/rolloff.js` `computeStanding()`). Drop the `active_cpas_scores` SQL view — oldest-first partial allocation isn't cleanly SQL-expressible, and having both a SQL and JS implementation invites divergence.
## Alternatives considered
- Keeping the SQL view with the aggregate formula `total - 5*cycles_since_last_violation` and computing per-violation breakdown only in JS. Rejected: two implementations of the same rule = drift risk; dashboard scale at MPM is small enough that per-request JS computation is fine.
- "All points roll off at once after 90 clean days" or "oldest entire violation rolls off." Rejected by Jason in clarifying questions.
## Consequences
- `/api/employees/:id/expiration` response shape changed (was per-violation list, now `{ schedule: [...] }`). Frontend `ExpirationTimeline.jsx` updated to match — any third-party consumer of that endpoint would break.
- Existing `prior_active_points` snapshots on pre-fix violation rows are stale; existing "Backfill Snapshots" admin button recomputes them under the new model on demand.
- `recomputeSnapshotsAfter` no longer caps the affected window at +90 days — backdated inserts now scan all later violations, since under the new model an earlier total shift propagates indefinitely.