seubert-claims/ondeck/docs/task-automation-alignment-plan.md
lorentz 835267c333 feat(automation): move task auto-generate to post-sync + admin config UI
Extracts the cron-only auto-generate logic into shared modules
(lib/sync/auto-generate.ts, automation-config.ts) and adds
group-auto-assign.ts, which attaches ungrouped policies to an existing
renewal group when their renewal dates are close. Both now also run
automatically after every AMS sync (sync-engine.ts), guarded by a
Postgres advisory lock so overlapping syncs cannot double-generate.
The external /api/cron/auto-generate endpoint remains for manual/
out-of-band triggering, delegating to the same shared engine.

Adds an admin settings UI + /api/admin/automation-settings route to
configure grace periods and toggles without a redeploy, and surfaces
the new audit actions (AUTO_GENERATE_TASKS_CLIENT, AUTO_ASSIGN_POLICY_GROUP)
in the audit log filters.

Includes a fixed AUTOMATION_GO_LIVE_AT floor (2026-07-07) in
automation-config.ts, applied to every candidate query in
auto-generate.ts and group-auto-assign.ts. Without it, the first run
after enabling this treated every historical policy/client/group that
happened to be old enough as newly eligible, generating tasks anchored
to old renewal/expiration dates in one burst (incident: 6,552 tasks
created 2026-07-07 02:01, most already 1-2 years overdue for Jeanne
Strong and Luke Billman; cleaned up same day after a verified backup).
The floor ensures automation only ever processes records created from
its go-live date forward.
2026-07-07 18:26:12 +00:00

9 KiB
Raw Blame History

Task Automation — Alignment Plan

Date: 2026-06-25 Source: Code review of /opt/projects/OnDeck/ondeck against the Seubert task-automation meeting (Lorentz Hinrichsen, Luke Billman, Dawn Boland). Status: Planning — to be executed at a later date.


Executive Summary

The meeting reaffirmed the design for SHAPE task automation: tasks should only generate for active SHAPE / SHAPE 2 clients with active policies, the manager setup queue should be drivable to zero, and client active/inactive status changes should be logged and surfaced in a daily briefing.

The Horizon data model and the core task mechanics already match the agreed design. Tasks are policy-specific (not carrier-specific), loss-run tasks follow the policy renewal date, and clients are never auto-deactivated — the advocate stays on the profile so returning clients resume cleanly. These need no change.

Three decisions from the call are not yet built, and one likely defect can mask half the SHAPE population. In priority order:

  1. Generation is not gated on active policies. Tasks generate for any client that has policies, regardless of whether those policies are Cancelled/Expired/Non-Renewed. The dead-policy filter only hides tasks at display time — they are still created. This contradicts the meeting's central rule: "no active policies → no tasks of any kind."
  2. No way to clear non-P&C-SHAPE accounts from the manager queue. Client setup still requires a claims advocate before it can be completed, with no "NA / no advocate / other department" option. Accounts like surety/bond-shape (in-transit example) therefore cannot be cleared, so the queue can never reach zero.
  3. No status-change logging or daily briefing. Client active↔inactive transitions are not recorded anywhere, and no briefing/digest feature exists.
  4. (Likely bug) Shape2 vs Shape 2 name mismatch. The secondary designation is created/synced as Shape2 (no space) but nearly every read query filters for Shape 2 (with a space). If the stored name is Shape2, all SHAPE-2 clients are silently excluded from the manager queue, dashboard, and metrics.

None of the proposed work changes the database schema meaningfully — it is logic-only — so it is safe to deploy in the agreed 7 PM5 AM window.


What already matches (no action needed)

Meeting decision Code reality
Tasks are policy-specific, not carrier-specific Task.policyId / policyGroupId; carrier only on Policy.carrierName; generation filters by policyTypeFilter, never carrier (src/lib/sync/auto-generate.ts:179)
Loss-run tasks follow the policy renewal date Negative daysOffset anchored to policy.expirationDate + 1 (auto-generate.ts:175-185)
Don't auto-mark clients inactive; advocate persists No isActive flag on Client; nothing auto-deactivates; claimsAdvocateId persists
Logic-only change, safe maintenance window Auto-generation writes no schema; scheduled sync runs 02:00 (0 2 * * *)

Items to address (priority order)

Priority 1 — Gate task generation on active policies

Problem: src/lib/sync/auto-generate.ts selects clients/policies/groups by existence of policies, not status. Dead-policy filtering (['Cancelled','Expired','Non-Renewed','Rewritten','Not taken']) only runs at display time (api/clients/[id]/tasks/route.ts:29), so tasks for hibernating/departed clients are still created and merely hidden. Target behavior: A client with no active policy generates no client-, policy-, or group-level tasks.

Priority 2 — Client-level "NA / no advocate" path

Problem: Setup completion requires an advocate (src/components/renewal-groups/setup-wizard.tsx:422-426, disabled button at :436). A client leaves the manager queue only when both claimsAdvocateId and setupCompletedAt are set (manager/setup/page.tsx:40-43). There is no client-level NA concept (the existing NA is a task status). Target behavior: A manager can mark a client as NA / no-advocate / other-department, which clears it from the setup queue without assigning an advocate.

Priority 3 — Status-change logging + daily briefing

Problem: No client active↔inactive transition is written to AuditLog; Notification.create is never called; no briefing/digest/summary feature exists. Target behavior: When a client crosses the active/inactive line (e.g. a new policy reactivates them, or their last active policy lapses), log it; roll those events into a daily briefing for managers.

Priority 4 (verify first) — Shape2 vs Shape 2 naming

Problem: Created/synced as Shape2 (api/admin/sync-designations/route.ts:22,84,92; scripts/import-shape-tasks.ts:68) but read as Shape 2 across manager queue, dashboard, metrics, workload. Import scripts hedge with both forms; runtime read queries do not. Target behavior: One canonical name used consistently everywhere.


Step-by-step plan (for later execution)

Phase 0 — Verify the designation name (do this first; ~15 min)

  1. Confirm the stored name with: SELECT name FROM designations WHERE name ILIKE 'shape%';
  2. If it returns Shape2: either (a) rename the row to Shape 2, or (b) normalize every read filter to match the import scripts' ['Shape','Shape 2','Shape2']. Pick one canonical form and apply it everywhere.
  3. Re-check manager queue / dashboard / metrics counts before vs. after to confirm SHAPE-2 clients now appear.

Phase 1 — Active-policy gate on task generation (Priority 1)

  1. Define a single shared ACTIVE_POLICY_STATUSES (or reuse the existing DEAD_STATUSES exclusion) in one module so generation and display agree.
  2. In auto-generate.ts:
    • Client-level query (~:228-232): require at least one active policy (status not in dead set), not merely any policy/group.
    • Policy-level generation (~:149-221): skip policies whose status is dead.
    • Group-level generation (~:80-146): skip groups with no active policies.
  3. Write tests covering: all-dead-policies client → 0 tasks; mixed active/dead → tasks only for active; reactivation (dead → active policy added) → tasks resume.
  4. Decide and document the policy on already-generated tasks for now-inactive clients (leave hidden vs. mark NA/cancelled). Confirm with Luke/Lorentz.

Phase 2 — Client NA / no-advocate path (Priority 2)

  1. Decide the data representation (recommend a nullable Client.setupStatus enum or a clearedReason + boolean, avoiding a heavy schema change). Confirm naming with stakeholders.
  2. Update the setup wizard so a manager can choose "NA / no advocate / other department" instead of an advocate, and allow completion in that case (relax setup-wizard.tsx:422-426 / the canFinish guard).
  3. Update the manager setup queue (manager/setup/page.tsx:38-43, api/clients/setup-queue/route.ts) so NA-marked clients drop off the queue.
  4. Ensure NA-marked clients are excluded from (or clearly distinguished in) generation per the Phase 1 rules.
  5. Tests: NA client clears the queue; NA client generates no advocate-assigned tasks; queue can reach zero.

Phase 3 — Status-change logging + daily briefing (Priority 3)

  1. Add active↔inactive transition detection at the point policies change (post-sync and/or on policy create/cancel). Derive status from active-policy presence (consistent with Phase 1).
  2. Write transitions to AuditLog (and/or Notification) with old/new status and trigger.
  3. Build the daily briefing aggregation (new clients needing setup, status changes, queue size) and choose delivery: in-app notification first; Teams/email later (explicitly deferred in the meeting).
  4. Schedule within the 7 PM5 AM window; align with the existing 02:00 sync.
  5. Tests: reactivation logs an event; lapse logs an event; briefing aggregates a day's events.

Phase 4 — Non-SHAPE generation gate (follow-on to Priority 1/2)

  1. Decide whether non-SHAPE clients should generate zero tasks (meeting implied yes) or only generic templates.
  2. If zero: apply the SHAPE/SHAPE 2 designation gate (already used by the manager queue) to auto-generate.ts as well.
  3. Tests: non-SHAPE client → 0 generated tasks.

Cross-cutting

  • All changes are logic-only (Phase 2/3 may add a small column/enum — coordinate a prisma db push per CLAUDE.md). Deploy in the 7 PM5 AM window.
  • After each phase: rebuild and restart per CLAUDE.md (docker compose build horizon-app && docker compose up -d horizon-app).
  • Validate against a sanitized/dev copy before production where possible.

Open questions for Luke / Lorentz

  1. For clients that go inactive, what happens to tasks already generated — leave hidden, auto-NA, or cancel?
  2. Exact set of "NA" reasons for the setup queue (other department? surety/bond? no rep?) and whether each should still appear anywhere for reference.
  3. Daily briefing delivery channel and recipients for v1 (in-app vs. Teams vs. email).
  4. Should non-SHAPE clients generate no tasks at all, or just no SHAPE-specific tasks?