seubert-claims/ondeck/docs/superpowers/specs/2026-07-18-task-generation-preview-and-per-client-design.md
lorentz 270c38ff19 docs: design for per-client task generation + preview-before-generate
Adds a clientId targeting mode and a dryRun flag to the admin
generate-and-assign tool, plus a confirmation dialog showing summary
counts before any tasks are actually created.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 02:19:48 +00:00

5.6 KiB

Task Generation: Per-Client Mode & Preview-Before-Generate — Design

Status: Approved. Ready for implementation planning.

Problem

The admin "Generate & Assign Tasks" tool (/api/tasks/generate-and-assign, surfaced on the Tasks → Generate & Assign page) only targets clients by advocate + designation, which can silently sweep in every client that advocate/designation pair matches — as discovered this session, that's up to 23 clients for a single pair. There's no way to generate tasks for just one specific client. There's also no confirmation step anywhere in this flow: clicking the button immediately writes tasks to the database with no preview of what will be created or a chance to back out.

Goal: add a per-client targeting mode, and require a preview-and-confirm step before any tasks are actually created, for this admin tool.

Scope

In scope: the admin "Generate & Assign Tasks" page/endpoint only — both its existing advocate+designation mode and the new per-client mode.

Explicitly out of scope (decided during brainstorming): the Policy Group Manager's per-group "Generate Tasks" button, the Setup Wizard's silent auto-generation on client-setup completion, and the silent auto-regeneration that fires when a policy is removed from a group. None of these get a preview step in this phase.

Decisions

Question Decision
Preview scope Admin Generate & Assign page only (existing + new per-client mode)
Per-client assignee Always that client's own claimsAdvocateId — not an arbitrary admin pick
Selection mode Separate mode from advocate+designation (toggle), not a combined filter
Preview detail level Summary counts (total + per-client breakdown), not a full task-by-task list, for both modes
Preview mechanism Extend the existing endpoint with a dryRun flag rather than a separate preview endpoint or client-side estimate

Why dryRun on the existing endpoint, not a separate preview endpoint: a separate endpoint would duplicate the client/group/policy/template-matching logic (including the past-due-date filter added earlier this session) — real risk of the preview drifting out of sync with what actually gets created, which is exactly the class of bug this session spent most of its time fixing. A dryRun flag on the same code path guarantees the preview can never lie about what will happen.

Design

API — POST /api/tasks/generate-and-assign

Body accepts either targeting mode, plus an optional flag:

{ advocateId, designationId, dryRun? }   // existing mode
{ clientId, dryRun? }                     // new mode

Server validates exactly one mode is provided (400 if both or neither).

Client mode specifics:

  • Assignee is always the target client's own claimsAdvocateId.
  • If that client has no claims advocate set, return an error ("Client has no claims advocate assigned") rather than creating unassigned tasks.

Refactor: the route's three sections (renewal groups / ungrouped policies / client-level templates) already build a tasksToCreate array before writing (including the isRelevantDueDate past-due filter). Split each section into "compute what would be created" and "write it," so dryRun: true runs the exact same computation but skips the createMany/create calls and the audit log entry (nothing happened, so nothing to log). A real run (dryRun false/absent) behaves as today, plus records which mode triggered it (advocate-designation vs client) in the audit log's newValues.

Dry-run response shape:

{
  "dryRun": true,
  "clientsFound": 3,
  "totalEstimatedTasks": 42,
  "advocateName": "Jane Smith",
  "clients": [
    { "id": "cl_...", "name": "Acme Corp", "estimatedTasks": 14 }
  ]
}

UI — "Generate & Assign Tasks" card (src/app/(dashboard)/tasks/assign/page-client.tsx)

  • Add a mode toggle: By Advocate + Designation (today's UI, unchanged) vs By Client (new — a searchable picker using the clients prop already passed into this component). In client mode, if the selected client has no claimsAdvocateId, disable the action and show a helper message explaining why.
  • The generate button no longer calls the endpoint for real directly. It first calls with dryRun: true:
    • totalEstimatedTasks === 0 → just toast the existing "no new tasks" message, no dialog (nothing meaningful to confirm).
    • Otherwise → open an AlertDialog (this codebase's existing pattern for confirm-before-consequential-action) showing total counts + the per-client breakdown table, with Confirm (re-calls the endpoint without dryRun) and Cancel (closes, no side effects — nothing was written during the preview call).

Testing plan

Route-level tests for generate-and-assign:

  • dryRun: true and a real run produce identical counts for the same input (no drift between preview and reality).
  • clientId mode only ever touches that one client.
  • Missing-advocate error case for client mode.
  • Mutual-exclusivity validation (400 when both or neither targeting mode is provided).

Out of scope / explicitly deferred

  • Preview/confirm for the per-group manual button, the Setup Wizard's auto-generation, and the remove-from-group auto-regeneration — all still fire immediately with no confirmation, unchanged by this phase.
  • The separately-discovered gap where policies that stay inside a group never get POLICY-level tasks generated by any automatic path, and the generate-policy-tasks route's missing idempotency check — both noted during this session's review but not addressed by this design.