feat: Phase 5 & 6 — usability, compliance hardening, analytics

Phase 5 (usability + compliance):
- In-app notification bell/badge: migration 016 adds read state + per-user
  RLS + create_in_app_notification SECURITY DEFINER RPC; /api/notifications;
  wired into incident creation, CAPA assign/verify, escalation cron
- Incident closure: new POST /api/incidents/[id]/close (requires verification
  status + all CAPAs verified); migration 017 locks closed incidents at DB
  level (update/delete triggers) with append-only incident_addenda + UI panel
- Server-side pagination on HSE/supervisor incident inboxes (.range, 25/page)
- Investigation form: alcohol/urine test result + witness statement refs
  (existing schema columns, now editable)
- Type-specific intake fields: migration 018 adds incidents.type_details
  JSONB; whitelist validation; environmental/asset/security/fire field
  groups in report form; EN/MS/ZH labels; offline queue support
- JKKP 8 annual register CSV export (/api/reports/jkkp8) + dashboard button
  + January statutory deadline banner
- Admin page: user invite (service-role client), role/site/active management,
  site + zone CRUD with QR report links — replaces Phase 0 stub
- Evidence gallery thumbnails via Supabase render transform with fallback

Phase 6 (analytics):
- 12-month stacked trend chart (leading/lagging/other) + top root causes
  (lib/dashboard/trends.ts pure helpers)
- AI rising-risk zones: /api/dashboard/ai/risk-flags aggregates 90-day
  zone stats, claude-opus-4-8 forced tool_use, panel on HSE + management
  dashboards, suggestion audit-logged

Also fixes 9 pre-existing missing /ims basePath prefixes in client fetches
and download links.

132 tests passing, tsc clean, next build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
This commit is contained in:
2026-07-12 10:25:08 +08:00
co-authored by Claude Fable 5
parent 98c38c3716
commit 576557181a
51 changed files with 2394 additions and 38 deletions
@@ -0,0 +1,107 @@
# Phase 5 & 6 — Usability, Compliance Hardening, Analytics
## Context
Phases 04 complete. Gap audit against PRD (`docs/01_PRD_HSE_Incident_Management_System.md`) found 10 specced features missing or partial. User approved all four gap groups: core usability, compliance hardening, evidence UX, and analytics. Split into **Phase 5** (usability + compliance + evidence) and **Phase 6** (analytics/AI), matching the existing phase-plan convention in `docs/superpowers/plans/`.
Verified current state (Explore audit, 2026-07-11):
- `notifications_log` table exists (`supabase/migrations/20260709000006_notifications_audit.sql`) — no UI
- `app/(protected)/admin/page.tsx` is a stub
- `app/api/incidents/[id]/route.ts` has no PATCH; no closure lock
- `app/api/incidents/[id]/jkkp-pdf/route.ts` supports jkkp6/jkkp7 only; `lib/incidents/dosh.ts:45` already computes `requires_jkkp8`
- `components/incidents/report-form.tsx` branches only on `injury_involved`
- Investigation schema has `alcohol_test_result`, `witness_statement_refs` (`migrations/...004:27-28`) — no UI
- `app/(protected)/hse/incidents/page.tsx:18` hard `.limit(100)`, no pagination
- `lib/supabase/storage.ts` stores SHA-256 `file_hash`; no thumbnails
- Dashboard has type/site breakdown + zone heatmap; no time-series, no root-cause trend
## Conventions to follow (established, do not deviate)
- Every DB mutation writes `audit_log`; RLS at DB level for all new tables/columns
- AI routes: `claude-opus-4-8`, `thinking: {type:"adaptive"}`, forced `tool_choice`, validate tool output, 503 on model failure; `getApiKey(supabase, key)` (`lib/settings.ts`) before client construction (`lib/claude/client.ts`)
- Notifications: reuse `lib/notifications/email.ts`, `lib/notifications/whatsapp.ts`
- i18n: add strings to `lib/i18n/locales.ts` (EN/MS/ZH)
- Client fetches prefixed `/ims` basePath
- TDD per repo practice; tests in `tests/` mirroring lib paths (61 passing currently)
---
## Phase 5 — Usability & Compliance Completion
### Task 1: In-app notification bell + badge
- Migration: extend `notifications_log` if needed (add `read_at TIMESTAMPTZ`, index on recipient+read)
- `app/api/notifications/route.ts` — GET unread list (RLS: own rows), POST mark-read
- `components/notifications/bell.tsx` — badge count, dropdown list; mount in `app/(protected)/layout.tsx`
- Write in-app rows at the same points email/WhatsApp fire (new incident, CAPA assign/escalate, verification)
### Task 2: Admin user + site/zone management
- Replace stub `app/(protected)/admin/page.tsx`
- User list: invite (Supabase admin API server-side), role assign, deactivate — admin-only RLS + server-side role check via `lib/auth/roles.ts`
- Site/zone CRUD against `sites`/`zones` tables (migration 001)
- All mutations audit-logged
### Task 3: Incident closure lock + addenda
- Migration: `incident_addenda` table (incident_id, author, body, created_at) with RLS
- Add PATCH guard in `app/api/incidents/[id]/route.ts` and triage/investigation/capa routes: reject mutation when `closed_at IS NOT NULL` (DB trigger preferred — belt and braces)
- Addenda UI on `components/incidents/incident-detail.tsx` for closed incidents
### Task 4: JKKP 8 annual register export
- `lib/pdf/jkkp8.ts` (or Excel via existing export route pattern `app/api/dashboard/export/route.ts`) — annual register of all reportable incidents for a chosen year
- `app/api/reports/jkkp8/route.ts` — HSE/admin only; reuse `lib/incidents/dosh.ts` flags
- Download button on HSE dashboard; reminder banner in January while register unsubmitted
### Task 5: Type-specific intake forms
- Extend `components/incidents/report-form.tsx`: conditional field groups per type — environmental (spill volume/substance/containment), MHE/asset (equipment id, LOTO applied), security, fire; near-miss stays minimal by design
- Store in existing JSONB detail column if present, else migration adds one
- Validate in `lib/incidents/validate.ts`; translate new labels
### Task 6: Witness statement + alcohol test UI
- Add fields to `components/incidents/investigation-form.tsx` wired to existing `alcohol_test_result`, `witness_statement_refs` columns
- Witness statement file uploads reuse `components/incidents/file-upload.tsx` with stage tag
### Task 7: Incident list pagination
- Server-side range pagination in `app/(protected)/hse/incidents/page.tsx` + `components/incidents/incident-list.tsx` (searchParams page/pageSize, `.range()`, count)
- Same for supervisor incident list
### Task 8: Evidence thumbnails
- Supabase Storage image transform (`getPublicUrl` with `transform: {width}`) for image types in `components/incidents/evidence-gallery.tsx`; icon fallback for video/pdf/doc
- No new infra if Supabase transform available on plan; else client-side `next/image` sizing of signed URL
### Task 9: Housekeeping
- Delete stray duplicates `lib/notifications/whatsapp 2.ts`, `tests/lib/notifications/whatsapp.test 2.ts` (verify identical/stale vs canonical first)
---
## Phase 6 — Analytics & Predictive Safety
### Task 1: Dashboard time-series
- Extend `app/api/dashboard/stats/route.ts`: monthly incident counts by type (12 mo), leading vs lagging trend, CAPA on-time trend
- Chart components on `app/(protected)/hse/dashboard/page.tsx` (lightweight — no heavy chart lib unless one already present)
### Task 2: Top root cause trended
- Aggregate `root_cause_category` (phase-2 RCA data) by month; top-5 table + trend on dashboard
### Task 3: AI rising-risk heatmap
- `app/api/dashboard/ai/risk-flags/route.ts` — server-side Claude call per AI-route conventions; input: 90d near-miss + incident + zone aggregates; output: flagged zones/shifts with rationale (forced tool_use schema)
- Panel on management + HSE dashboards, clearly labelled as AI suggestion; log suggestion to `audit_log` per convention
---
## Verification
- `npm test` — all existing 61 tests plus new unit tests per task (escalation-style pattern in `tests/lib/`)
- Manual: run app, walk lifecycle — report (each type) → triage → investigate (witness/alcohol) → CAPA → verify → close → confirm lock + addenda; bell badge increments; admin creates user + zone; JKKP 8 downloads; pagination past 100 rows (seed if needed)
- `graphify update .` after code changes
- Update `docs/superpowers/plans/` with phase-5/6 plan docs and memory `phases.md` on completion
---
## Completion Record (2026-07-12)
All Phase 5 and Phase 6 tasks implemented on branch `phase-5-6`. 112 tests passing (23 files), `tsc --noEmit` clean, `next build` clean.
Notable deviations/additions vs plan:
- Incident **close endpoint did not exist at all** — added `POST /api/incidents/[id]/close` (verification status + all CAPAs verified required) alongside the lock.
- In-app notification inserts go through `create_in_app_notification` SECURITY DEFINER RPC (reporters are not covered by the elevated INSERT policy).
- Fixed 9 pre-existing missing `/ims` basePath prefixes across fetch calls and download links (similar-incidents-panel, investigation-form ×3, triage-form ×2, verify-form, JKKP PDF links ×2, dashboard/management export links).
- New migrations: 20260712000016_in_app_notifications, 20260712000017_closure_lock_addenda, 20260712000018_type_details.