feat(imageright): finalize two-pass audit engine and task-audit UI polish
Uncommitted work-in-progress from 2026-07-08, carried forward: - audit-engine: refine two-pass metadata + content-inspection audit model - audit-matching, content-inspector, shape-audit-spec: supporting matching and spec updates for the two-pass model - task-audit route/panel: UI and API polish - add @napi-rs/canvas dependency, Dockerfile/next.config adjustments for it - test updates and additions (audit-engine, audit-decision-logic, run-task-audit) - add loose dev notes (clone runbook, issues implementation, overdue filter bug report, shared count helper plan)
This commit is contained in:
parent
6dd7e3e836
commit
01ecfacd04
17 changed files with 1278 additions and 112 deletions
51
dev/horizon-dev-clone-runbook-2026-06-25.md
Normal file
51
dev/horizon-dev-clone-runbook-2026-06-25.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Horizon Dev-Clone Runbook — 2026-06-25
|
||||
|
||||
Stand up a development instance of Horizon on a **separate Linux host** that runs the
|
||||
**latest (working-tree) code** against an **exact copy of live production data**.
|
||||
|
||||
- **Target host:** `root@dev02.nb.wulf.cloud`
|
||||
- **Dev domain:** `https://horizon-dev.seubert.cloud`
|
||||
- **Source of truth (code):** `/opt/projects/OnDeck/ondeck` (working tree, incl. uncommitted changes)
|
||||
- **Source of truth (stack):** `/opt/stacks/horizon`
|
||||
- **Prod DB:** container `horizon-db`, database `horizon`, user `horizon_user`
|
||||
|
||||
## Decisions (locked)
|
||||
| Decision | Choice |
|
||||
|---|---|
|
||||
| Where dev runs | Separate host `dev02.nb.wulf.cloud`, executed hands-on this session |
|
||||
| Code version | Working tree as-is (includes uncommitted automation-settings / sync changes) |
|
||||
| Auth | Entra (Azure AD) SSO — dev redirect URI added to app `f6c778cf-c850-47d0-85e7-aff1d1c3288b` |
|
||||
| Data | Exact `pg_dump` restore of live `horizon` DB (real client PII on dev box) |
|
||||
| Routing/TLS | New Pangolin **site** = `newt` client on dev host + resource for the dev domain |
|
||||
|
||||
## Target architecture
|
||||
| Concern | Prod | Dev clone |
|
||||
|---|---|---|
|
||||
| App container | `horizon-app` :3000 | `horizon-dev-app` :3000 |
|
||||
| DB container | `horizon-db` | `horizon-dev-db` (fresh volume) |
|
||||
| Backup sidecar | `horizon-backup` | optional / off initially |
|
||||
| Domain | `horizon.seubert.cloud` | `horizon-dev.seubert.cloud` |
|
||||
| Routing | Pangolin via `newt` | new Pangolin site (`newt` on dev host) + resource |
|
||||
| Auth | Entra SSO | Entra SSO + dev redirect URI |
|
||||
| Secrets | prod | regenerated `NEXTAUTH_SECRET`, `DB_PASSWORD` |
|
||||
|
||||
## Execution sequence
|
||||
1. **Pre-flight (dev host):** Docker + compose v2 present; ≥ ~10 GB free on `/`; outbound 443 reachable (newt → Pangolin).
|
||||
2. **Pangolin:** create a new site; install/run `newt` on the dev host; add resource `horizon-dev.seubert.cloud` → `http://horizon-dev-app:3000`; verify DNS + cert. (Use the `pangolin` skill.)
|
||||
3. **Azure AD:** add redirect URI `https://horizon-dev.seubert.cloud/api/auth/callback/azure-ad` to Entra app `f6c778cf-c850-47d0-85e7-aff1d1c3288b`.
|
||||
4. **Ship code:** rsync `/opt/projects/OnDeck/ondeck` → dev host, excluding `node_modules`, `.next`, `.git`.
|
||||
5. **Stack + env:** copy `/opt/stacks/horizon`; rename containers to `horizon-dev-*`; set `NEXTAUTH_URL=https://horizon-dev.seubert.cloud`; regenerate `NEXTAUTH_SECRET` and `DB_PASSWORD`; keep Azure creds; keep **read-only** AFW/AMS creds; leave `GRAPH_*` unset.
|
||||
6. **Data copy:** `pg_dump` live `horizon-db` → gzip → transfer → restore into freshly-initialized `horizon-dev-db`; then `prisma migrate deploy` (or `db push`) to reconcile schema with working-tree code.
|
||||
7. **Build & start:** `docker compose up -d horizon-dev-db` (wait healthy) → restore dump → `docker compose build horizon-dev-app && docker compose up -d`.
|
||||
8. **Smoke test:** health endpoint; Entra login; spot-check real client/policy/task data present.
|
||||
|
||||
## Safety guards (dev must not perturb prod or external systems)
|
||||
- **No external cron trigger** wired on dev → `/api/cron/*` stays gated by `CRON_SECRET`, nothing calls it (no auto-generated tasks / scheduled jobs firing).
|
||||
- AFW/AMS uses **read-only** user `1100080_RO` → sync cannot mutate the AMS.
|
||||
- `GRAPH_*` unset (as in prod) → no SharePoint/email writes.
|
||||
- Dev `NEXTAUTH_SECRET` / `DB_PASSWORD` regenerated so dev secrets ≠ prod.
|
||||
|
||||
## Obligations / cleanup
|
||||
- **PII:** dev box now holds a full copy of real client PII — ensure `dev02` is secured commensurately and the copy is removed when the dev instance is decommissioned.
|
||||
- **Temp SSH key:** `claude-horizon-dev-clone-2026-06-25` added to `root@dev02:~/.ssh/authorized_keys`. Remove that line when finished.
|
||||
- Consider a teardown step (drop dev DB volume, remove Pangolin resource + site, remove Azure redirect URI) when dev is no longer needed.
|
||||
151
dev/horizon-issues-implementation-2026-05-28.md
Normal file
151
dev/horizon-issues-implementation-2026-05-28.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# Horizon Issues Implementation — May 28, 2026
|
||||
|
||||
**Commit:** `20f7ec1`
|
||||
**Deployed:** May 28, 2026 ~11:50 PM UTC
|
||||
**Container rebuild:** Yes — `horizon-app` recreated and restarted
|
||||
|
||||
---
|
||||
|
||||
## What Was Implemented
|
||||
|
||||
### 1. Tasks Disappearing (High — Bug Fix)
|
||||
**Symptom:** Mimi's IN_PROGRESS task and other open tasks were vanishing from task lists.
|
||||
|
||||
**Root cause:** The task visibility filter included `policyGroup: { renewalDate: { gte: cutoff } }` with no exemption for open tasks. Any task on a renewal group whose date was older than 7 days was hidden — even if the task was `IN_PROGRESS` or `NOT_STARTED`.
|
||||
|
||||
**Fix:** Added `{ status: { notIn: TERMINAL_STATUSES } }` as a third OR branch so open tasks always show regardless of group renewal date. Only terminal (`COMPLETED`, `CANCELLED`, `NA`) tasks on old groups are filtered out.
|
||||
|
||||
**Files changed:**
|
||||
- `src/app/(dashboard)/tasks/page.tsx`
|
||||
- `src/app/(dashboard)/clients/[id]/page.tsx`
|
||||
- `src/app/api/clients/[id]/tasks/route.ts`
|
||||
|
||||
**Data impact:** None. No task records were modified. The fix only changes what the query returns.
|
||||
|
||||
---
|
||||
|
||||
### 2. Star Date Change Doesn't Update Task Due Dates (High — Bug Fix)
|
||||
**Symptom:** When a manager changed the renewal date on a policy group, all open tasks retained their old due dates.
|
||||
|
||||
**Root cause:** `PATCH /api/policy-groups/[id]` saved the new `renewalDate` but never recalculated `dueDate` on existing tasks. Since `dueDate = renewalDate + daysOffset`, the dates silently became wrong.
|
||||
|
||||
**Fix:** After saving the group, the API now fetches all open (`NOT_STARTED`, `IN_PROGRESS`, `BLOCKED`) template-generated tasks in the group and recalculates their `dueDate`. The count of rescheduled tasks is included in the audit log entry and the API response.
|
||||
|
||||
**Files changed:**
|
||||
- `src/app/api/policy-groups/[id]/route.ts`
|
||||
|
||||
**Data impact:** Future — will update task due dates whenever a manager changes a group's renewal date going forward. Did not retroactively update any existing tasks.
|
||||
|
||||
---
|
||||
|
||||
### 3. Auto Task Assignment Not Working (BEK, Noor) (High — Feature)
|
||||
**Symptom:** BEK and Noor were set up 3 days ago but had zero tasks. The auto-generate cron requires all policies to be ≥20 days old before generating tasks.
|
||||
|
||||
**Fix:** When a manager clicks "Save & Complete" in the setup wizard, it now automatically calls `POST /api/policy-groups/[id]/generate-tasks` for every saved group. A loading toast ("Generating tasks…") shows during this step, and the success toast confirms completion.
|
||||
|
||||
**Files changed:**
|
||||
- `src/app/api/clients/[id]/setup/route.ts` — now returns `groups` array with IDs
|
||||
- `src/components/renewal-groups/setup-wizard.tsx` — calls generate-tasks after non-draft save
|
||||
|
||||
**Data impact:** BEK and Noor still need to have their setup wizard re-saved (or re-completed) to trigger task generation for existing groups, since the fix only fires on new completions going forward. Alternative: generate tasks manually via the existing group detail page.
|
||||
|
||||
---
|
||||
|
||||
### 4. Manual Sync Hardcoded Date Range (Medium — Bug Fix)
|
||||
**Symptom:** The Manual Sync panel defaulted to `2026-01-01` – `2026-12-31`, silently skipping policies outside that window.
|
||||
|
||||
**Fix:** Defaults are now dynamic: start = January 1 of the prior year, end = December 31 two years out. This auto-adjusts every calendar year.
|
||||
|
||||
**Files changed:**
|
||||
- `src/components/admin/sync-trigger.tsx`
|
||||
|
||||
**Data impact:** None.
|
||||
|
||||
---
|
||||
|
||||
### 5. Setup Queue Showing Inactive Clients (Medium — Bug Fix)
|
||||
**Symptom:** 26 clients were showing in the setup queue, including many with only cancelled/expired/non-renewed policies that don't need setup.
|
||||
|
||||
**Fix:** The queue now filters to clients with at least one active (non-dead) policy. A note in the subtitle shows how many clients were excluded so managers have full visibility.
|
||||
|
||||
Dead statuses filtered: `Cancelled`, `Expired`, `Non-Renewed`, `Rewritten`, `Not taken`.
|
||||
|
||||
**Files changed:**
|
||||
- `src/app/(dashboard)/manager/setup/page.tsx`
|
||||
|
||||
**Data impact:** None. Queue count will be lower — that's correct.
|
||||
|
||||
---
|
||||
|
||||
### 6. Audit Logging Gap — Notes Route (Medium — Bug Fix)
|
||||
**Symptom:** When a user added a note and simultaneously changed task status, the audit log recorded `TASK_NOTE_ADDED` with only the new status. The old status was never captured, making it impossible to know what a task was before the change.
|
||||
|
||||
**Fix:** When a status change accompanies a note, the route now also emits a dedicated `TASK_STATUS_CHANGED` audit entry with `oldValues.status` and `newValues.status`. The existing `TASK_NOTE_ADDED` entry was also updated to include `oldValues.status`.
|
||||
|
||||
**Files changed:**
|
||||
- `src/app/api/tasks/[id]/notes/route.ts`
|
||||
|
||||
**Data impact:** No historical backfill. New entries going forward will be complete. Verification query:
|
||||
```sql
|
||||
SELECT action, old_values->>'status' AS old, new_values->>'status' AS new, created_at
|
||||
FROM audit_logs WHERE action = 'TASK_STATUS_CHANGED'
|
||||
ORDER BY created_at DESC LIMIT 10;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Renewal Warnings Not Showing on Policy Cards (Low — Enhancement)
|
||||
**Symptom:** Day-count warning badges (e.g. "26d", "34d") appeared on client-level cards and lists but not on individual policy cards inside the client detail view.
|
||||
|
||||
**Fix:** Each policy card in the client detail tab now shows:
|
||||
- Red badge `Xd` if renewal is within 30 days
|
||||
- Grey badge `Xd` if renewal is within 90 days
|
||||
|
||||
**Files changed:**
|
||||
- `src/components/clients/client-detail.tsx`
|
||||
|
||||
---
|
||||
|
||||
### 8. Client Notes — No Save Attribution (Low — Enhancement)
|
||||
**Symptom:** The Client Notes section had no indication of when notes were last saved or by whom.
|
||||
|
||||
**Fix:** After saving notes, a "Saved [timestamp]" label appears next to the Save button for the duration of the session.
|
||||
|
||||
**Note:** A persistent "last saved by" tracker would require a DB migration (adding `notesUpdatedAt` / `notesUpdatedBy` to the `clients` table). This was not added. If persistent tracking is desired, a migration can be written.
|
||||
|
||||
**Files changed:**
|
||||
- `src/components/clients/client-detail.tsx`
|
||||
|
||||
---
|
||||
|
||||
## Verification — User Task Counts (Post-Deploy)
|
||||
|
||||
Queried immediately after deploy. No tasks were modified by the code changes.
|
||||
|
||||
| User | NOT_STARTED | IN_PROGRESS | COMPLETED | NA |
|
||||
|---|---|---|---|---|
|
||||
| Luke Billman | 236 | 7 | 164 | 43 |
|
||||
| Mimi Rawlings | 516 | 5 | 602 | 38 |
|
||||
| Christine Gove | 1,722 | — | 2,087 | 96 |
|
||||
|
||||
All status changes since May 21 were made by the users themselves (no system reversions).
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
No database migrations were required. All changes are application-layer only.
|
||||
|
||||
```
|
||||
git commit: 20f7ec1
|
||||
git push: main → forgejo.wulfconsulting.cloud/lorentz/seubert-claims
|
||||
docker: horizon-app Recreated → Started (Up as of ~11:50 PM UTC May 28)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Still Pending
|
||||
|
||||
- **SHAPE import audit logging** — The import directly creates/updates tasks with `COMPLETED`/`NA` status but emits no per-task audit entries. Low risk (admin-only, rare operation). Plan documented in `dev/audit-logging-fix-plan.md`.
|
||||
- **BEK/Noor task generation** — Needs manual trigger via group detail page, or re-completing setup, since they were set up before this fix.
|
||||
- **Client notes persistent attribution** — Requires a DB migration to add `notesUpdatedAt` / `notesUpdatedBy` columns.
|
||||
97
dev/overdue-filter-bug-report-2026-06-01.md
Normal file
97
dev/overdue-filter-bug-report-2026-06-01.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Overdue Task Count Discrepancy
|
||||
**Bug Report — June 1, 2026**
|
||||
|
||||
---
|
||||
|
||||
## What's Happening
|
||||
|
||||
The app has two different sets of rules for deciding which tasks are "visible" — and only one of them is correct.
|
||||
|
||||
| Where | Rule Applied | Correct? |
|
||||
|---|---|---|
|
||||
| My Tasks page (direct load) | Show all open tasks, regardless of due date age | ✅ Yes |
|
||||
| Manager "view as user" switcher | Hide open tasks whose due date is > 7 days old | ❌ No |
|
||||
| Team Management dashboard | No visibility filters at all | ❌ No |
|
||||
| Workload KPI dashboard | No visibility filters at all | ❌ No |
|
||||
| Bulk Assign / Manage Tasks page | Hide open tasks whose due date is > 7 days old | ❌ No |
|
||||
| Prometheus monitoring metrics | No visibility filters at all | ❌ No (low impact) |
|
||||
|
||||
The correct rule is: **if a task is still open, always show it — no matter how old its due date is.** Only completed/closed tasks should age out of view.
|
||||
|
||||
---
|
||||
|
||||
## Reported Symptoms Explained
|
||||
|
||||
### Symptom 1 — "My Tasks shows 45 overdue, drops to 8 after switching users"
|
||||
|
||||
When the page first loads, it uses the correct rules — 45 overdue tasks shown accurately. When a manager uses the **"Viewing as"** dropdown to switch to another user and then returns to themselves, the page now fetches tasks through the API instead of the server — which uses the broken rules. Tasks overdue by more than 7 days silently disappear, leaving only 8.
|
||||
|
||||
> **Root cause:** `/api/tasks` (used by the user-switcher) was never updated to match the fix applied to the My Tasks page in May.
|
||||
|
||||
---
|
||||
|
||||
### Symptom 2 — "Jeanne shows 131 overdue in her advocate view, but only 21 when logged in as her"
|
||||
|
||||
The Team Management page and Workload KPI panels count overdue tasks with **no filtering at all** — they include tasks on cancelled/expired policies, tasks in old renewal groups, and tasks that the user's own page would correctly hide. Jeanne's actual My Tasks page applies the right filters and shows 21.
|
||||
|
||||
> **Root cause:** The manager dashboard and workload API were never given the same visibility rules as the My Tasks page.
|
||||
|
||||
---
|
||||
|
||||
### Symptom 3 — "Chris Gove shows 0 tasks in all timeframes (past due, this week, next week)"
|
||||
|
||||
Christine has **1,722 open tasks** in the database. All of them are overdue by more than 7 days. When a manager views her tasks through the "Viewing as" switcher, the broken API filter hides every single one. All three buckets — overdue, this week, next week — show zero.
|
||||
|
||||
Her tasks exist. They are simply invisible due to the filter bug.
|
||||
|
||||
> **Root cause:** Same as Symptom 1 — the API's broken filter applied to Christine's tasks.
|
||||
|
||||
---
|
||||
|
||||
## Impact After the Fix
|
||||
|
||||
| User | Before Fix | After Fix |
|
||||
|---|---|---|
|
||||
| Any user (own tasks, direct load) | ✅ Correct — no change | ✅ No change |
|
||||
| Manager viewing another user | ❌ Hidden tasks | ✅ Accurate count |
|
||||
| Christine Gove | 0 visible tasks (manager view) | All 1,722 open tasks visible |
|
||||
| Jeanne Strong | 131 shown in dashboard | Accurate, lower number |
|
||||
| Team-wide overdue dashboard | Inflated by dead-policy tasks | Accurate, lower number |
|
||||
|
||||
> **No data will change.** Only what is displayed changes. No tasks will be created, deleted, or modified. Christine's tasks surfacing as overdue reflects real work that needs attention — it is not a new problem created by the fix.
|
||||
|
||||
---
|
||||
|
||||
## Fixes Required
|
||||
|
||||
Four code changes are needed, all in existing API files. No database changes, no migrations, zero downtime.
|
||||
|
||||
### Fix 1 — Task API filter (High Priority)
|
||||
**File:** `src/app/api/tasks/route.ts`
|
||||
|
||||
The "view as user" switcher, the Bulk Assign page, and the Manage Tasks page all use this API. Replacing its two broken filter conditions with the correct logic fixes Symptoms 1 and 3 immediately.
|
||||
|
||||
### Fix 2 — Workload dashboard (High Priority)
|
||||
**File:** `src/app/api/dashboard/workload/route.ts`
|
||||
|
||||
Adds dead-policy and expired-group exclusions to the overdue counts shown in the manager dashboard and per-user workload rows. Fixes Symptom 2.
|
||||
|
||||
### Fix 3 — Team Management page (Medium Priority)
|
||||
**File:** `src/app/(dashboard)/manager/page.tsx`
|
||||
|
||||
Adds the same visibility filters to the per-user active/overdue counts shown on the Team Management page.
|
||||
|
||||
### Fix 4 — Prometheus metrics (Low Priority)
|
||||
**File:** `src/app/api/metrics/route.ts`
|
||||
|
||||
Corrects the `horizon_tasks_overdue` metric shown in monitoring. Does not affect anything users see in the application.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Plan
|
||||
|
||||
1. Apply all four code fixes
|
||||
2. TypeScript check: `npx tsc --noEmit`
|
||||
3. Commit: `git commit -m "Fix: align task visibility filters across all API routes and dashboards"`
|
||||
4. Deploy: `docker compose -f /opt/stacks/horizon/docker-compose.yml up -d --build`
|
||||
5. Verify with spot-check SQL queries on Luke, Mimi, Chris, and Jeanne
|
||||
118
dev/shared-count-helper-plan-2026-06-02.md
Normal file
118
dev/shared-count-helper-plan-2026-06-02.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# Plan — Shared Task-Count Engine (single source of truth)
|
||||
|
||||
**Author:** Claude · **Date:** 2026-06-02 · **Status:** Draft for review
|
||||
**Deploy window:** after hours, this week (TBD date)
|
||||
|
||||
---
|
||||
|
||||
## 1. Why
|
||||
|
||||
The overnight fix (`b1b0dfa`) resolved the headline bugs — Christine Gove now shows **258** overdue (was 0), Jeanne Strong **133** (was 21), and the "drops to 8 when switching users" glitch is gone. Verified live 2026-06-02.
|
||||
|
||||
But the numbers still don't reconcile across screens, because **each surface computes "overdue / this week / next week" with its own formula.** Verified live today:
|
||||
|
||||
| Symptom (live) | Numbers | Cause |
|
||||
|---|---|---|
|
||||
| Same person, different per screen | Christine 257 (team page) vs 258 (task view); Jeanne 131 vs 133 | "overdue" boundary = midnight on one screen, current moment on the other |
|
||||
| Overlapping buckets | "1 Week Out" (days 8–14) ⊂ "2 Weeks Out" (days 8–21) | bucket windows overlap; same tasks counted in both |
|
||||
| Dashboard self-contradiction | top "Overdue" = 519, sum of per-person overdue = 493 (Δ26) | aggregate counts a different population than the per-member rows |
|
||||
| One person > whole team | Christine "This Week" = 90 > team "Due This Week" = 81 | different week definition + scope (all-clients vs Shape-only) |
|
||||
|
||||
There are currently **6 independent implementations** of these counts:
|
||||
|
||||
| # | File | Scope | "overdue" boundary | "now" basis |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `app/(dashboard)/tasks/page.tsx` (SSR initial) | all clients | n/a (returns rows) | — |
|
||||
| 2 | `app/(dashboard)/tasks/page-client.tsx:709-732` (cards) | loaded rows (cap 1000) | `dueDate < now` (current time) | **browser** |
|
||||
| 3 | `app/api/tasks/route.ts` (switcher/bulk) | all clients | n/a (returns rows) | — |
|
||||
| 4 | `app/api/dashboard/workload/route.ts:93-121` | **Shape only** | `dueDate < today` (midnight) | **server** |
|
||||
| 5 | `app/(dashboard)/manager/page.tsx` + `components/manager/team-members-by-department.tsx:108` | **Shape only** | `dueDate < today` (midnight) | server-render, browser-eval |
|
||||
| 6 | `app/api/metrics/route.ts:26-41` | all clients | `dueDate < todayStart` | server |
|
||||
|
||||
## 2. Goal
|
||||
|
||||
One shared module that owns **(a)** the task-visibility filter, **(b)** the "now"/week-boundary math, and **(c)** the bucket definitions. Every surface calls it. Numbers reconcile by construction.
|
||||
|
||||
---
|
||||
|
||||
## 3. Decisions to lock before coding
|
||||
|
||||
These are product decisions, not code details. Recommended defaults in **bold**; please confirm or override.
|
||||
|
||||
1. **"Overdue" boundary** — count a task overdue when `dueDate < start of today`. **Recommend: start-of-day** (a task due *today* is "due today," not "overdue"). Apply identically everywhere. Resolves the 257-vs-258 and 131-vs-133 drift.
|
||||
2. **Time zone** — all day/week math computed in **`America/New_York`** (Seubert's business zone), server-side, never browser-local. Resolves off-by-a-day at midnight and the browser-vs-server split. (Today's cards use the viewer's browser clock — a user in a different zone sees different buckets.)
|
||||
3. **Bucket definitions** — non-overlapping, contiguous:
|
||||
- **Overdue** = `dueDate < today`
|
||||
- **This week** = `today ≤ dueDate ≤ today+6` (days 1–7)
|
||||
- **Next week** = `today+7 ≤ dueDate ≤ today+13` (days 8–14)
|
||||
- (drop or relabel the current "2 Weeks Out = days 8–21" card, which overlaps)
|
||||
- All exclude terminal statuses (COMPLETED/CANCELLED/NA) and dead-policy tasks; open tasks are never aged out.
|
||||
4. **Scope on manager/dashboard surfaces** — *decision needed.* Today the team dashboard and team page count **Shape clients only**, while a user's own task list counts **all clients**. For users with non-Shape work these will never match. **Recommend: make the per-user task view also Shape-scoped** so "what the manager sees" == "what the employee sees," OR add an explicit "Shape only" toggle. Flagging for your call — this is the one behavior change end-users might notice.
|
||||
5. **Aggregate vs sum** — the dashboard "Overdue" total should equal the sum of the per-person rows shown beneath it. **Recommend:** define the aggregate as "overdue tasks assigned to listed active team members," so top number == sum of rows (no orphan/unassigned tasks silently inflating the header).
|
||||
6. **Counts come from the server, not the browser** — cards must request counts from an endpoint, not compute them from a capped 1,000-row array. Fixes the latent truncation bug (Christine has 1,719 open tasks; "this week"/"next week" can silently undercount once a user exceeds 1,000).
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation
|
||||
|
||||
### 4.1 New shared module — `src/lib/task-buckets.ts`
|
||||
- `businessNow()` / `startOfBusinessDay()` — fixed `America/New_York` "today" and week edges.
|
||||
- `taskVisibilityWhere(opts)` — the canonical Prisma `where` (dead-policy exclusion, expired-group rule that never hides open tasks, terminal-recency rule). Single definition; surfaces 1/3 import it instead of inlining.
|
||||
- `bucketWhere(bucket, base)` — returns the `where` for `overdue | thisWeek | nextWeek | dueToday`, built from the agreed boundaries.
|
||||
- `BUCKETS` — ordered, non-overlapping definitions + labels, consumed by the cards.
|
||||
- Unit tests (Vitest/Jest) covering boundary cases: due exactly at midnight, DST transitions, terminal-but-recent, open-but-old, dead policy, expired group.
|
||||
|
||||
### 4.2 New counts endpoint — `src/app/api/tasks/counts/route.ts`
|
||||
- `GET ?userId=…&scope=shape|all` → `{ overdue, thisWeek, nextWeek }` computed server-side via `bucketWhere`. Authz mirrors `/api/tasks` (Admin/Manager may pass `userId`).
|
||||
- Cards call this; they stop deriving counts from the loaded task array.
|
||||
|
||||
### 4.3 Refactor each surface to call the shared code
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `tasks/page-client.tsx` | Replace `now`/`week*End` consts and the `overdue/dueToday/due1Week/due2Weeks` filters (lines 709–732) with values from the counts endpoint; remove the overlapping "2 Weeks Out" card or relabel per decision #3. |
|
||||
| `api/tasks/route.ts` | Import `taskVisibilityWhere` instead of the inline AND-block (lines 31–60). |
|
||||
| `tasks/page.tsx` | Import `taskVisibilityWhere` (lines 23–53). Keep `take` but raise/paginate, or rely on counts endpoint for totals. |
|
||||
| `api/dashboard/workload/route.ts` | Use `bucketWhere`/boundaries; align scope + aggregate-vs-sum per decisions #4/#5 (lines 93–177). |
|
||||
| `manager/page.tsx` + `team-members-by-department.tsx` | Use shared boundaries/`startOfBusinessDay` instead of local `today` (line 108). |
|
||||
| `api/metrics/route.ts` | Use shared overdue definition (lines 26–41). |
|
||||
|
||||
No DB schema change. No migration. No data is modified — only what's displayed.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification (before & after, gated by evidence)
|
||||
|
||||
**A. SQL ground truth** (read-only, against `horizon-db`) — compute overdue/thisWeek/nextWeek per the agreed definitions for a fixed sample: Christine Gove, Jeanne Strong, Mimi Rawlings, Luke Billman, + one all-clients user. Record expected numbers.
|
||||
|
||||
**B. Reconciliation assertions** — must all hold post-deploy:
|
||||
1. Manager team-page overdue for user X **==** that user's task-view Overdue card **==** SQL truth.
|
||||
2. Dashboard top "Overdue" **==** sum of per-member overdue rows.
|
||||
3. No single user's bucket exceeds the team aggregate for that bucket.
|
||||
4. "This week" and "next week" are disjoint; no task appears in both.
|
||||
5. A user with >1,000 tasks (Christine) has correct thisWeek/nextWeek (truncation gone).
|
||||
|
||||
**C. Live Playwright walkthrough** (as today): manager team page → switcher for each sample user → dashboard KPIs; capture screenshots; confirm B1–B5 on screen.
|
||||
|
||||
**D. `npx tsc --noEmit`** clean; unit tests green.
|
||||
|
||||
---
|
||||
|
||||
## 6. Rollout (after-hours, this week)
|
||||
|
||||
1. Branch `fix/shared-task-counts`; implement §4; run §5-A/D on the branch.
|
||||
2. Open PR; self-review + `/code-review`.
|
||||
3. In the window: `docker compose -f /opt/stacks/horizon/docker-compose.yml up -d --build`.
|
||||
4. Post-deploy: run §5-B/C; attach screenshots.
|
||||
5. **Rollback:** revert the merge commit and rebuild (no schema/data change, so rollback is a clean redeploy of the prior image).
|
||||
|
||||
**User-facing note for the window:** "Brief maintenance — task counts will be momentarily unavailable. No tasks change; only how totals are displayed is being made consistent across screens."
|
||||
|
||||
---
|
||||
|
||||
## 7. Open items needing your sign-off
|
||||
- Decision #1 (overdue = start of day) ✅ recommend
|
||||
- Decision #2 (America/New_York) ✅ recommend
|
||||
- Decision #3 (drop "2 Weeks Out" overlap) ✅ recommend
|
||||
- **Decision #4 (Shape-only vs all-clients on user task view)** ⬅ needs your call — only item with visible behavior change
|
||||
- Decision #5 (aggregate == sum of rows) ✅ recommend
|
||||
- Decision #6 (server-side counts) ✅ recommend
|
||||
Loading…
Add table
Add a link
Reference in a new issue