backfill button and usage
Build and Push Docker Image / build (push) Successful in 16s

This commit is contained in:
2026-05-19 09:29:41 -05:00
parent 6ddc09aa71
commit 2d4920bd15
7 changed files with 171 additions and 5 deletions
+8 -2
View File
@@ -92,7 +92,13 @@ Violations are **never hard-deleted** in normal workflow. Use the `negated` flag
Every `INSERT` into `violations` must compute and store `prior_active_points` (the employee's current active score before this violation is added). This snapshot ensures PDFs always reflect the accurate historical tier state regardless of subsequent negate/restore actions.
**Back-dated inserts are the one exception to snapshot immutability.** If a new violation's `incident_date` precedes existing violations within the 90-day window, those existing violations' snapshots are recomputed via `recomputeSnapshotsAfter()` inside the same transaction as the insert, and a `violation_snapshots_recomputed` audit entry is written. A back-dated insert is a *timeline rewrite* — the prior violations genuinely had an earlier event in their 90-day window — so their PDFs must reflect that. Negate/restore are NOT timeline rewrites and must never recompute snapshots.
**There are exactly two sanctioned paths that may modify `prior_active_points` after insert; both are audit-logged as `violation_snapshots_recomputed`:**
1. **Back-dated insert (automatic).** If a new violation's `incident_date` precedes existing violations within the 90-day window, those existing violations' snapshots are recomputed via `recomputeSnapshotsAfter()` inside the same transaction as the insert. Logged with `reason: "backdated_insert"`. A back-dated insert is a *timeline rewrite* — the prior violations genuinely had an earlier event in their 90-day window — so their PDFs must reflect that.
2. **Manual backfill (admin-triggered).** `POST /api/employees/:id/recompute-snapshots` calls `recomputeAllSnapshotsForEmployee()` and rewrites every row for that employee from current data. Logged with `reason: "manual_backfill"`. This exists to repair drift from back-dated inserts that happened under older code (before path #1 existed) or any other case where the snapshot diverged from current truth. It is exposed in the UI as the **↻ Backfill Snapshots** button in the Employee Profile Modal next to the Active Violations header. Treat it as a targeted repair tool, not a routine maintenance step.
Negate/restore/amend/hard-delete are NOT timeline rewrites and must never recompute snapshots — PDFs remain stable through those operations by design.
### Audit Log
@@ -249,7 +255,7 @@ docker run -d --name cpas -p 3001:3001 -v cpas-data:/data cpas
### What NOT to Do
- Do not compute active CPAS scores in JavaScript by summing violations client-side. Always fetch from the `active_cpas_scores` view.
- Do not modify `prior_active_points` after a violation is inserted, EXCEPT when a back-dated insert retroactively places a new earlier event into another violation's 90-day prior window. That path is handled by `recomputeSnapshotsAfter()` in `server.js` and is audit-logged. Never recompute snapshots on negate, restore, amend, or hard delete.
- Do not modify `prior_active_points` after a violation is inserted, EXCEPT via one of the two sanctioned paths: automatic recompute on a back-dated insert (`recomputeSnapshotsAfter()`), or manual admin backfill (`recomputeAllSnapshotsForEmployee()` behind `POST /api/employees/:id/recompute-snapshots` and the **↻ Backfill Snapshots** UI button). Both are audit-logged as `violation_snapshots_recomputed`. Never recompute snapshots on negate, restore, amend, or hard delete.
- Do not add columns to `audit_log`. It is append-only with a fixed schema.
- Do not add a framework or ORM. Raw SQL with prepared statements is intentional — it keeps the query behavior explicit and the dependency surface small.
- Do not add a build step beyond `vite build`. The backend is plain CommonJS `require()`; do not transpile it.