diff --git a/ondeck/src/lib/shape-import/run-import.ts b/ondeck/src/lib/shape-import/run-import.ts index 3b8be13..43bb30f 100644 --- a/ondeck/src/lib/shape-import/run-import.ts +++ b/ondeck/src/lib/shape-import/run-import.ts @@ -631,12 +631,23 @@ async function processTaskRow(clientId: string, row: ParsedRow, template: DbTemp } } +function extractDatePrefix(text: string): { date: Date; remainder: string } | null { + // Matches: 02/05/2026- | 2/5/2026: | 02/05/2026-- | 2/5/2026 (with optional trailing punctuation/space) + const m = text.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})\s*[-:]+\s*/) + if (!m) return null + const d = new Date(parseInt(m[3]), parseInt(m[1]) - 1, parseInt(m[2])) + if (isNaN(d.getTime())) return null + return { date: d, remainder: text.slice(m[0].length).trim() } +} + async function createAdHocTask(clientId: string, text: string, effectiveDate: Date, advocateUserId: string, stats: ImportStats, dryRun: boolean): Promise { - const title = text.slice(0, 500) + const parsed = extractDatePrefix(text) + const taskDate = parsed?.date ?? effectiveDate + const title = (parsed?.remainder ?? text).slice(0, 500) const existing = await prisma.task.findFirst({ where: { clientId, title, isAdHoc: true } }) if (existing) return if (!dryRun) { - const task = await prisma.task.create({ data: { title, department: 'CLAIMS', timing: 'PRE_RENEWAL', daysOffset: 0, dueDate: effectiveDate, status: 'COMPLETED', priority: 'MEDIUM', clientId, isAdHoc: true, completedAt: effectiveDate, completedBy: advocateUserId, notes: text, createdBy: advocateUserId } }) + const task = await prisma.task.create({ data: { title, department: 'CLAIMS', timing: 'PRE_RENEWAL', daysOffset: 0, dueDate: taskDate, status: 'COMPLETED', priority: 'MEDIUM', clientId, isAdHoc: true, completedAt: taskDate, completedBy: advocateUserId, notes: text, createdBy: advocateUserId } }) await ensureTaskAssignment(task.id, advocateUserId, stats, dryRun) } else { stats.tasksAssigned++ } stats.adHocCreated++ diff --git a/tasks/task-generation-review.md b/tasks/task-generation-review.md new file mode 100644 index 0000000..5a101a1 --- /dev/null +++ b/tasks/task-generation-review.md @@ -0,0 +1,161 @@ +# Task Generation & Parent-Child Relationships — Design Review + +**Date:** 2026-04-08 +**App:** OnDeck — Insurance Broker Claims Management +**Status:** Under Review + +--- + +## Current State Summary + +Tasks always carry `clientId` (required) and optionally `policyId` or `policyGroupId`. Generation is triggered per-PolicyGroup or per-standalone-policy, matched to clients by designation. The core gap is that **templates have no concept of what level they belong to** — so the same template is generated for every policy or group, creating the redundancy and noise problems described below. + +--- + +## Scenario Analysis + +### 1. New Policy + New Client +**Status: Straightforward — no issues.** + +Auto-generate cron picks it up after 20 days. Client gets their designation, policies are created, PolicyGroup optionally assigned, tasks generate from matching templates. Clean path, no design changes needed. + +--- + +### 2. New Policy + Existing Client + +#### Is there more than one claims review? + +Yes, currently — and this is the primary bug. If a client has 3 ungrouped policies, they receive 3 claims reviews. A claims review is a *client-level* concern (one per renewal cycle), not a per-policy concern. + +**Fix:** Introduce a `taskLevel` field on `TaskTemplate`: + +| Level | Meaning | Example Tasks | +|---|---|---| +| `CLIENT` | Generate once per client per renewal cycle | Claims Review | +| `GROUP` | Generate once per PolicyGroup | Renewal prep, aggregate tasks | +| `POLICY` | Generate once per policy | Loss Run | + +Without this distinction, smart task generation is not achievable. + +#### Should a renewal group automatically be created? + +**Recommendation: Suggest, don't auto-create.** + +When a new policy is added to an existing client, check if any existing policies share a renewal date within ±30 days. If so, surface a prompt: + +> *"This policy has a similar renewal date to [Group X] — add it to that group?"* + +Auto-creation can produce wrong groups silently. A suggestion keeps the user in control and avoids unintended data structures. + +--- + +## Task Assignment Level + +| Task Type | Should Live At | Rationale | +|---|---|---| +| Claims Review | Client (or Group if grouped) | One per renewal cycle, not per policy | +| Loss Run | Policy | Specific to a single policy's claim history | +| Renewal Prep | Group (or Client if ungrouped) | Group anchors the renewal date | +| Ad-hoc | User's choice | Manually assigned at creation | + +The schema already supports all three levels via `clientId`, `policyId`, and `policyGroupId`. The missing piece is the template knowing which level to generate at. + +--- + +## Data Already Exists: Scenarios + +### Client — Single Policy +Works correctly today. No changes needed. + +### Client — Multiple Policies +**Currently broken without `taskLevel`.** Each ungrouped policy spawns a full task set, including redundant client-level tasks (e.g. multiple claims reviews for the same client). Generation logic needs to respect task level before this scenario works correctly. + +### Renewal Group — Automatic +Not yet implemented. Requires the group suggestion flow described above. Once a group is created: +- Tasks should regenerate anchored to the group's `renewalDate` +- Duplicate policy-level tasks that are now covered by the group should be cleaned up + +### Group Suggested from Existing Data +Should exist as a UI affordance when: +- A new policy is added with a renewal date close to an existing policy +- Multiple ungrouped policies are discovered with similar dates during review + +--- + +## Smart Task Generation vs. Per-Policy Generation + +### The Fix (Two Parts) + +**1. Add `taskLevel` to `TaskTemplate`** (`CLIENT | GROUP | POLICY`) + +**2. Update generation logic:** + +- `CLIENT` tasks — create once per client; skip if one already exists for this renewal cycle +- `GROUP` tasks — create once per PolicyGroup +- `POLICY` tasks — create once per policy (loss run per policy is correct and intended) + +This handles the single claims review cleanly: even if a client has 5 policies across 2 groups, they receive exactly one claims review because the template is marked `CLIENT`. + +### Task Inheritance + +Policy- and group-level tasks already carry `clientId`, so they naturally appear in client-level task views. The gap is the UI — the policy/group context isn't clearly surfaced on task cards within the client view. + +**Recommendation:** Add a context badge to task cards: +- `Policy: GL-12345` +- `Group: 2025 Renewal` + +This makes it clear what a task belongs to without requiring navigation away from the client view. + +--- + +## Parent-Child Relationships + +### Current State +Parent-child is organizational only (`parentClientId` on Client). Tasks never cascade to subsidiaries, which is correct. However, the UI currently ignores the hierarchy entirely. + +### Recommended UI Behavior + +**Client detail page — default view** +Show only the direct client's tasks. A parent client manager should not wade through subsidiary tasks by default. This keeps the view focused and fast. + +**"Include subsidiaries" toggle** +Add an expandable section or filter toggle that pulls in subsidiary tasks. Power users managing a parent + multiple subsidiaries need this, but it should be opt-in. + +**Parent client summary card** +Surface aggregate metrics on the parent client header: +> `4 subsidiaries | 12 tasks (3 overdue)` + +Clicking opens a filtered rollup view. Gives visibility without cluttering the default view. + +**Task creation at parent level** +When creating a task on a parent client, prompt: +> *"Is this task for [Parent Co.] specifically, or should it apply to all subsidiaries?"* + +Cascading to subsidiaries is likely rare but should be a conscious choice when it happens. + +--- + +## Open Questions + +1. **What defines a "renewal cycle" for deduplication of CLIENT-level tasks?** Is it calendar year, policy year, or keyed to the group/policy renewal date? +2. **If a client has two groups with different renewal dates, do they get two claims reviews?** (Probably yes — one per renewal event — but needs confirmation.) +3. **For the group suggestion flow: what is the right date proximity threshold?** ±30 days? Should it also consider policy type/line of business? +4. **Should parent-level tasks (created on the parent entity) be visible on subsidiary pages?** Or does information only flow upward (subsidiaries visible at parent)? +5. **When a PolicyGroup is created from previously ungrouped policies, what happens to existing tasks?** Archive them? Regenerate? Show a conflict resolution UI? + +--- + +## Implementation Priority + +| Priority | Item | Schema Change? | +|---|---|---| +| 1 | Add `taskLevel` enum to `TaskTemplate` | Yes — small | +| 2 | Update generation logic to respect task level | No | +| 3 | Single claims review deduplication | No | +| 4 | Per-policy loss run generation | No | +| 5 | PolicyGroup suggestion when adding policies | No | +| 6 | Task card context badges (policy/group label) | No | +| 7 | Parent-child UI rollup with toggle | No | +| 8 | Parent client aggregate metrics | No | + +The schema change in item 1 is minimal — one new enum and field on `TaskTemplate`. Everything downstream is logic and UI work.