Files
cpas/README.md
2026-03-08 00:42:48 -06:00

20 KiB
Executable File
Raw Permalink Blame History

CPAS Violation Tracker

Single-container Dockerized web app for CPAS violation documentation and workforce standing management. Built with React + Vite (frontend), Node.js + Express (backend), SQLite (database), and Puppeteer (PDF generation).

© Jason Stedwell · git.alwisp.com/jason/cpas


The only requirement on your machine: Docker Desktop

Everything else — Node.js, npm, React build, Chromium for PDF — happens inside Docker.


Quickstart (Local)

# 1. Build the image (installs all deps + compiles React inside Docker)
docker build -t cpas .

# 2. Run it
docker run -d --name cpas \
  -p 3001:3001 \
  -v cpas-data:/data \
  cpas

# 3. Open
# http://localhost:3001

Update After Code Changes

docker build -t cpas .
docker stop cpas && docker rm cpas
docker run -d --name cpas -p 3001:3001 -v cpas-data:/data cpas

Deploying on Unraid

Step 1 — Build and export the image on your dev machine

docker build -t cpas:latest .
docker save cpas:latest | gzip > cpas-latest.tar.gz

Step 2 — Load the image on Unraid

Transfer cpas-latest.tar.gz to your Unraid server, then load it via the Unraid terminal:

docker load < /path/to/cpas-latest.tar.gz

Confirm the image is present:

docker images | grep cpas

Step 3 — Create the appdata directory

mkdir -p /mnt/user/appdata/cpas/db

Step 4 — Run the container

This is the verified working docker run command for Unraid (bridge networking with static IP):

docker run \
  -d \
  --name='cpas' \
  --net='br0' \
  --ip='10.2.0.14' \
  --pids-limit 2048 \
  -e TZ="America/Chicago" \
  -e HOST_OS="Unraid" \
  -e HOST_HOSTNAME="ALPHA" \
  -e HOST_CONTAINERNAME="cpas" \
  -e 'PORT'='3001' \
  -e 'DB_PATH'='/data/cpas.db' \
  -l net.unraid.docker.managed=dockerman \
  -l net.unraid.docker.webui='http://[IP]:[PORT:3001]' \
  -v '/mnt/user/appdata/cpas/db':'/data':'rw' \
  cpas:latest

Access the app at http://10.2.0.14:3001 (or whatever static IP you assigned).

Key settings explained

Setting Value Notes
--net br0 Unraid custom bridge network — gives the container its own LAN IP
--ip 10.2.0.14 Static IP on your LAN — adjust to match your subnet
--pids-limit 2048 Required — Puppeteer/Chromium spawns many processes for PDF generation; default Unraid limit is too low and will cause PDF failures
PORT 3001 Express listen port inside the container
DB_PATH /data/cpas.db SQLite database path inside the container
Volume /mnt/user/appdata/cpas/db/data Persists the database across container restarts and rebuilds

Updating on Unraid

  1. Build and export the new image on your dev machine (Step 1 above)
  2. Load it on Unraid: docker load < cpas-latest.tar.gz
  3. Stop and remove the old container: docker stop cpas && docker rm cpas
  4. Re-run the docker run command from Step 4 — the volume mount preserves all data

Note: The --pids-limit 2048 flag is critical. Without it, Chromium hits Unraid's default PID limit and PDF generation silently fails or crashes the container.


Stakeholder Demo

A standalone demo page with synthetic data is available at /demo (e.g. http://localhost:3001/demo). It is served as a static route before the SPA catch-all and requires no authentication. Useful for showing the app to stakeholders without exposing live employee data.


Features

Company Dashboard

  • Live table of all employees sorted by active CPAS points (highest risk first)
  • Summary stat cards: total employees, elite standing (0 pts), with active points, at-risk count, highest active score
  • At-risk badge: flags employees within 2 points of the next tier escalation
  • Search/filter by name, department, or supervisor
  • Department filter: pre-loaded dropdown of all departments for quick scoped views
  • Click any employee name to open their full profile modal
  • 📋 Audit Log button — filterable, paginated view of all system write actions

Violation Form

  • Select existing employee or enter new employee by name
  • Employee intelligence: shows current CPAS standing badge and 90-day violation count before submitting
  • Violation type dropdown grouped by category; shows prior 90-day counts inline
  • Recidivist auto-escalation: if an employee has prior violations of the same type, points slider auto-sets to maximum per policy
  • Repeat offense badge with prior count displayed
  • Context-sensitive fields (time, minutes late, amount, location, description) shown only when relevant to violation type
  • Tier crossing warning (TierWarning component): previews what tier the new points would push the employee into before submission
  • Point slider for discretionary adjustments within the violation's min/max range
  • Employee Acknowledgment section: optional "received by employee" name and date fields; when filled, the PDF signature block shows the recorded acknowledgment instead of a blank signature line
  • One-click PDF download immediately after submission
  • Toast notifications: success/error/warning feedback for form submissions, validation, and PDF downloads

Employee Profile Modal

  • Full violation history with resolution status and amendment count badge per record
  • ✎ Edit Employee button — update name, department, supervisor, or notes inline
  • Merge Duplicate tab — reassign all violations from a duplicate record and delete it
  • Amend button per active violation — edit non-scoring fields (location, notes, witness, acknowledgment, etc.) with a full field-level diff history
  • Negate / restore individual violations (soft delete with resolution type + notes)
  • Hard delete option for data entry errors
  • PDF download for any historical violation record
  • Notes & Flags — free-text notes (e.g. "on PIP", "union member") with quick-add tag buttons; visible in the profile modal without affecting scoring
  • Point Expiration Timeline — shows when each active violation rolls off the 90-day window, with a progress bar, days-remaining countdown, and projected tier-drop indicators
  • Toast notifications for all actions: negate, restore, delete, amend, PDF download, employee edit

Audit Log

  • Append-only log of every write action: employee created/edited/merged, violation logged/amended/negated/restored/deleted
  • Filterable by entity type (employee / violation) and action
  • Paginated with load-more; accessible from the Dashboard toolbar

Violation Amendment

  • Edit submitted violations' non-scoring fields without delete-and-resubmit
  • Point values, violation type, and incident date are immutable
  • Every change is stored as a field-level diff (old → new value) with timestamp and actor

In-App Documentation

  • ? Docs button in the navbar opens a slide-in admin reference panel
  • Covers feature map, CPAS tier system, workflow guidance, and roadmap
  • No external link required; always reflects current deployed version

Toast Notification System

  • Global toast notifications for all user actions across the application
  • Four variants: success (green), error (red), warning (gold), info (blue)
  • Auto-dismiss with configurable duration and visual progress bar countdown
  • Slide-in animation; stacks up to 5 notifications simultaneously
  • Consistent dark theme styling matching the rest of the UI
  • © Jason Stedwell copyright with auto-advancing year
  • Live dev ticker: real-time elapsed counter since first commit (2026-03-06), ticking every second in Xd HHh MMm SSs format with a pulsing green dot
  • Gitea repo link with icon — links directly to git.alwisp.com/jason/cpas

CPAS Tier System

Points Tier Label
04 01 Elite Standing
59 1 Realignment
1014 2 Administrative Lockdown
1519 3 Verification
2024 4 Risk Mitigation
2529 5 Final Decision
30+ 6 Separation

Scores are computed over a rolling 90-day window (negated violations excluded).

PDF Generation

  • Puppeteer + system Chromium (bundled in Docker image)
  • Logo loaded from disk at startup (no hardcoded base64); falls back gracefully if not found
  • Generated on-demand per violation via GET /api/violations/:id/pdf
  • Filename: CPAS_<EmployeeName>_<IncidentDate>.pdf
  • PDF captures prior active points at the time of the incident (snapshot stored on insert)
  • Acknowledgment rendering: if the violation has an acknowledged_by value, the employee signature block on the PDF shows the recorded name and date with an "Acknowledged" badge; otherwise, blank signature lines are rendered for wet-ink signing

API Reference

Method Endpoint Description
GET /api/health Health check
GET /api/employees List all employees (includes notes)
POST /api/employees Create or upsert employee
PATCH /api/employees/:id Edit name, department, supervisor, or notes
POST /api/employees/:id/merge Merge duplicate employee; reassigns all violations
GET /api/employees/:id/score Get active CPAS score for employee
GET /api/employees/:id/expiration Active violation roll-off timeline with days remaining
PATCH /api/employees/:id/notes Save employee notes only (shorthand)
GET /api/dashboard All employees with active points + violation counts
POST /api/violations Log a new violation (accepts acknowledged_by, acknowledged_date)
GET /api/violations/employee/:id Violation history with resolutions + amendment counts
PATCH /api/violations/:id/negated Negate a violation (soft delete + resolution record)
PATCH /api/violations/:id/restore Restore a negated violation
PATCH /api/violations/:id/amend Amend non-scoring fields with field-level diff logging
GET /api/violations/:id/amendments Get amendment history for a violation
DELETE /api/violations/:id Hard delete a violation
GET /api/violations/:id/pdf Download violation PDF
GET /api/audit Paginated audit log (filterable by entity_type, entity_id)

Project Structure

cpas/
├── Dockerfile                                  # Multi-stage: builds React + runs Express w/ Chromium
├── .dockerignore
├── package.json                                # Backend (Express) deps
├── server.js                                   # API + static file server
├── db/
│   ├── schema.sql                              # Tables + 90-day active score view
│   └── database.js                            # SQLite connection (better-sqlite3) + auto-migrations
├── pdf/
│   ├── generator.js                           # Puppeteer PDF generation
│   └── template.js                            # HTML template (loads logo from disk, ack signature rendering)
├── demo/                                       # Static stakeholder demo page (served at /demo)
└── client/                                    # React frontend (Vite)
    ├── package.json
    ├── vite.config.js
    ├── index.html
    └── src/
        ├── main.jsx
        ├── App.jsx                            # Root app + AppFooter (copyright, dev ticker, Gitea link)
        ├── data/
        │   └── violations.js                 # All CPAS violation definitions + groups
        ├── hooks/
        │   └── useEmployeeIntelligence.js    # Score + history hook
        └── components/
            ├── CpasBadge.jsx                 # Tier badge + color logic
            ├── TierWarning.jsx               # Pre-submit tier crossing alert
            ├── Dashboard.jsx                 # Company-wide leaderboard + audit log trigger
            ├── ViolationForm.jsx             # Violation entry form + ack signature fields
            ├── EmployeeModal.jsx             # Employee profile + history modal
            ├── EditEmployeeModal.jsx         # Employee edit + merge duplicate
            ├── AmendViolationModal.jsx       # Non-scoring field amendment + diff history
            ├── AuditLog.jsx                  # Filterable audit log panel
            ├── NegateModal.jsx               # Negate/resolve violation dialog
            ├── ViolationHistory.jsx          # Violation list component
            ├── ExpirationTimeline.jsx        # Per-violation 90-day roll-off countdown
            ├── EmployeeNotes.jsx             # Inline notes editor with quick-add HR tags
            ├── ToastProvider.jsx             # Global toast notification system + useToast hook
            └── ReadmeModal.jsx              # In-app admin documentation panel

Database Schema

Six tables + one view:

  • employees — id, name, department, supervisor, notes
  • violations — full incident record including prior_active_points snapshot at time of logging, acknowledged_by and acknowledged_date for employee acknowledgment
  • violation_resolutions — resolution type, details, resolved_by (linked to violations)
  • violation_amendments — field-level diff log for violation edits; one row per changed field per amendment
  • audit_log — append-only record of every write action (action, entity_type, entity_id, performed_by, details, timestamp)
  • active_cpas_scores (view) — sum of points for non-negated violations in rolling 90 days, grouped by employee

Amendable Fields

Point values, violation type, and incident date are immutable after submission. The following fields can be amended:

Field Notes
incident_time Time of day the incident occurred
location Where the incident took place
details Narrative description
submitted_by Supervisor who submitted
witness_name Witness on record
acknowledged_by Employee who acknowledged receipt
acknowledged_date Date of employee acknowledgment

Roadmap

Completed

Phase Feature Description
1 Container scaffold Docker multi-stage build, Express server, SQLite schema
1 Base violation form Employee fields, violation type, incident date, point submission
2 Employee intelligence Live CPAS standing badge and 90-day count shown before submitting
2 Prior violation highlighting Violation dropdown annotates types with 90-day recurrence counts
2 Recidivist auto-escalation Points slider auto-maximizes on repeat same-type violations
2 Violation history Per-employee history list with resolution status
3 PDF generation Puppeteer/Chromium PDF per violation, downloadable immediately post-submit
3 Prior-points snapshot prior_active_points captured at insert time for accurate historical PDFs
4 Company dashboard Sortable employee table with live tier badges and at-risk flags
4 Stat cards Summary counts: total, clean, active, at-risk, highest score
4 Tier crossing warning Pre-submit alert when new points push employee to next tier
4 Employee profile modal Full history, negate/restore, hard delete, per-record PDF download
4 Negate & restore Soft-delete violations with resolution type + notes, fully reversible
5 Employee edit / merge Update employee name/dept/supervisor; merge duplicate records without losing history
5 Violation amendment Edit non-scoring fields with field-level audit trail
5 Audit log Append-only log of all system writes; filterable panel in the dashboard
6 Employee notes / flags Free-text notes on employee record with quick-add HR tags; does not affect scoring
6 Point expiration timeline Per-violation roll-off countdown with tier-drop projections
6 In-app documentation Admin usage guide and feature map accessible from the navbar
7 Acknowledgment signature field "Received by employee" name + date on the violation form; renders on the PDF replacing blank signature lines with recorded acknowledgment
7 Toast notification system Global success/error/warning/info notifications for all user actions; auto-dismiss with progress bar; consistent dark theme
7 Department dropdown Pre-loaded select on the violation form replacing free-text department input; shared DEPARTMENTS constant
8 Stakeholder demo page Standalone /demo route with synthetic data; static HTML served before SPA catch-all; useful for non-live presentations
8 App footer Copyright (© Jason Stedwell), live dev ticker since first commit, Gitea repo icon+link

📋 Proposed

Effort ratings: 🟢 Low · 🟡 Medium · 🔴 High

Quick Wins (High value, low effort)

Feature Effort Description
Column sort on dashboard 🟢 Click Tier, Active Points, or Department headers to sort in-place; one useState + comparator, no API changes
Department filter on dashboard 🟢 Multi-select dropdown to scope the employee table by department; DEPARTMENTS constant already exists
Keyboard shortcut: New Violation 🟢 N key triggers tab switch to the violation form; ~5 lines of code

Reporting & Analytics

Feature Effort Description
Violation trend chart 🟡 Line/bar chart of violations per day/week/month, filterable by department or supervisor; useful for identifying systemic patterns
Department heat map 🟡 Grid view showing violation density and average CPAS score by department; helps supervisors identify team-level risk
Violation sparklines per employee 🟡 Tiny inline bar chart of points over the last 6 months in the employee modal

Employee Management

Feature Effort Description
Supervisor scoped view 🟡 Dashboard filtered to a supervisor's direct reports, accessible via URL param (?supervisor=Name); no schema changes required
Employee photo / avatar 🟢 Optional avatar upload stored alongside the employee record; shown in the profile modal and dashboard row

Violation Workflow

Feature Effort Description
Draft / pending violations 🟡 Save a violation as draft before finalizing; useful when incidents need review before being officially logged
Violation templates 🟢 Pre-fill the form with a saved violation type + common details for frequently logged incidents

Notifications & Escalation

Feature Effort Description
Tier escalation alerts 🟡 Email or in-app notification when an employee crosses into Tier 2+ so the relevant supervisor is automatically informed
At-risk threshold config 🟢 Make the "at-risk" warning threshold (currently hardcoded at 2 pts) configurable per deployment via an env var
version.json / build badge 🟢 Inject git SHA + build timestamp into a static file during docker build; surfaced in the footer and /api/health

Infrastructure & Ops

Feature Effort Description
Multi-user auth 🔴 Simple login with role-based access (admin, supervisor, read-only); currently the app runs on a trusted internal network with no auth
Automated DB backup 🟡 Cron job or Docker health hook to snapshot /data/cpas.db to a mounted backup volume or remote location on a schedule
Dark/light theme toggle 🟡 The UI is currently dark-only; a toggle would improve usability in bright environments

Proposed features are suggestions based on common HR documentation workflows. Priority and implementation order should be driven by actual operational needs.