seubert-claims/tasks/mvp-ondeck.md

404 lines
16 KiB
Markdown
Raw Permalink Normal View History

# OnDeck MVP — Policy Renewal Workflow System
> Minimum Viable Product scope for the OnDeck policy renewal management platform.
> This document defines the feature set required for initial production deployment.
---
## Table of Contents
1. [User Authentication](#1-user-authentication)
2. [Logging](#2-logging)
3. [AMS360 Data Sync](#3-ams360-data-sync)
4. [Task Management & Renewal Workflow](#4-task-management--renewal-workflow)
5. [Renewal Grouping & Policy Exclusion](#5-renewal-grouping--policy-exclusion)
---
## 1. User Authentication
### 1.1 Authentication Providers
| Provider | Purpose | Status |
|----------|---------|--------|
| Local credentials | Development and testing (3 built-in accounts: admin, manager, ae) | Existing |
| Microsoft Entra ID (Azure AD) | Production SSO | Existing |
### 1.2 Account Linking
When a user signs in via Microsoft Entra ID and the email matches an existing local account, the accounts are linked automatically by populating the `entraOid` field on the existing user record.
- First lookup: find user by email where `entraOid` is null (pre-created local account)
- If found: update the record with the Entra Object ID to link the accounts
- Fallback: standard upsert by `entraOid` for net-new users
> **Reference:** `src/lib/auth.ts:110-144`
### 1.3 Role-Based Access Control
Four roles with granular permissions:
| Role | Key Permissions |
|------|----------------|
| **Admin** | Full access — users, clients, tasks, designations, templates, sync, reports, audit |
| **Manager** | Read users; read/write clients, tasks, templates; assign tasks; view reports |
| **Account Executive** | Read/write clients and tasks; read designations |
| **Claims** | Read clients; read/write tasks |
Permission categories: User Management, Client Management, Task Management, Designation Management, Template Management, Sync Operations, Reports, Audit.
Role resolution combines two sources:
- **Entra Group mappings** — `EntraGroupRoleMapping` table maps Azure AD groups to application roles
- **Direct assignments** — `UserRole` join table for explicit per-user role grants
> **Reference:** `src/lib/auth/roles.ts`, `src/lib/auth/permissions.ts`
### 1.4 Session Strategy
- JWT-based sessions (`strategy: 'jwt'`)
- Session callback enriches the token with: `id`, `entraOid`, `roles[]`, merged `permissions` object (union of all assigned role permissions)
---
## 2. Logging
### 2.1 Authentication Logging
| Event | Details |
|-------|---------|
| Sign-in success | Provider, user ID, timestamp |
| Sign-in failure | Provider, email attempted, failure reason |
| Account linking | User ID, linked Entra OID |
### 2.2 Action Logging (Audit)
All user-initiated mutations on entities are recorded in the `AuditLog` model:
| Field | Description |
|-------|-------------|
| `userId` | Actor who performed the action |
| `action` | Operation type (create, update, delete) |
| `entityType` | Target entity (Client, Policy, Task, etc.) |
| `entityId` | Target record ID |
| `oldValues` | JSON snapshot of previous state |
| `newValues` | JSON snapshot of new state |
| `ipAddress` | Request origin |
| `userAgent` | Client identifier |
| `createdAt` | Timestamp |
> **Reference:** `AuditLog` model in `prisma/schema.prisma`
### 2.3 Error Logging
- API route errors with request context
- Sync failures with stream identification and row-level detail
- Unhandled exceptions with stack traces
### 2.4 Info Logging
- Sync progress: rows fetched, processed, inserted, updated per stream
- Scheduled job execution: start, completion, next scheduled run
- System events: application startup, configuration changes
### 2.5 Alerts & Notifications
Alerts are delivered through the existing `Notification` model:
| Alert Type | Trigger |
|------------|---------|
| Overdue tasks | Task `dueDate` has passed with status not in COMPLETED/NA/CANCELLED |
| Sync failures | Any sync stream fails after retry exhaustion |
| System health | Scheduler down, database connectivity issues |
User notification preferences are stored in `NotificationPreference` with per-type toggles for: in-app, email, and Teams delivery channels.
> **Reference:** `Notification` and `NotificationPreference` models in `prisma/schema.prisma`
---
## 3. AMS360 Data Sync
### 3.1 Overview
Sync data from the AFW SQL Server database (AMS360 source of truth) into the local PostgreSQL database. The sync engine connects to AFW via a managed connection pool (max 10 connections, 60s request timeout).
> **Reference:** `src/lib/sync/afw-connection.ts`
### 3.2 Department Scope
| Item | Current State | MVP Target |
|------|---------------|------------|
| Departments synced | 5 departments (Dept1, Dept8, Dept9, Dept10, Dept12) | **All 13 departments** |
**MVP change:** Remove the hardcoded `ShortName IN (...)` filter in the policy sync query so all AFW departments are included.
> **Reference:** `src/lib/sync/afw-queries.ts:221` — current 5-department WHERE clause
### 3.3 Sync Streams
Three parallel streams execute concurrently via `Promise.all()`:
| Stream | Source (AFW) | Target (Local) | Key Mapping |
|--------|-------------|-----------------|-------------|
| **Employees → Users** | `AFW_Employee` (Status = 'A') | `User` | EmpCode, Name, Email, DefaultGLDeptCode → department |
| **Customers → Clients** | `AFW_Customer` (Active = 'Y') | `Client` | CustId → amsCustomerId, contact fields, producer code |
| **Policies → Policies** | `AFW_BasicPolInfo` + 20 joined lookup tables | `Policy` | Complex CTE with company, department, personnel lookups; up to 2 additional reps and 2 additional execs per policy |
> **Reference:** `src/lib/sync/sync-engine.ts`, `src/lib/sync/afw-queries.ts`, `src/lib/sync/mappers.ts`
### 3.4 Sync Modes
| Mode | Behavior |
|------|----------|
| **Incremental** (default) | Syncs records changed since last successful sync, using `ChangedDate` filtering where supported |
| **Full** | Re-syncs all records regardless of last sync timestamp |
Signature: `runSync(triggeredBy?: string, isIncremental: boolean = true)`
### 3.5 Scheduling & Triggers
| Mechanism | Details |
|-----------|---------|
| **Scheduled** | Configurable cron expression stored in `SyncConfig` table (default: `0 2 * * *` — daily at 2 AM) |
| **Manual** | `POST /api/sync` with optional date range selection; requires `sync.trigger` permission |
Configuration keys in `SyncConfig`:
- `sync_enabled` — master toggle
- `sync_schedule_cron` — cron expression
- `policy_start_date` / `policy_end_date` — date range for policy sync window
> **Reference:** `src/lib/sync/scheduler.ts`
### 3.6 Retry Logic
Each sync stream independently retries on failure:
- **Max retries:** 3
- **Backoff:** Exponential — 2s, 4s, 8s
### 3.7 Sync Logging
Every sync execution is recorded in the `SyncLog` model:
| Field | Description |
|-------|-------------|
| `syncType` | Which stream or overall sync |
| `startedAt` / `completedAt` | Timing |
| `status` | Success or failure |
| `rowsProcessed` | Total rows fetched from AFW |
| `rowsInserted` | New records created |
| `rowsUpdated` | Existing records updated |
| `errorMessage` | Failure details (if any) |
| `triggeredBy` | User ID or "scheduler" |
### 3.8 API Endpoints
| Method | Path | Purpose | Permission |
|--------|------|---------|------------|
| `POST` | `/api/sync` | Trigger manual sync | `sync.trigger` |
| `GET` | `/api/sync` | List recent sync logs | `sync.trigger` |
| `GET` | `/api/sync/status` | Scheduler status | `sync.trigger` |
---
## 4. Task Management & Renewal Workflow
### 4.1 Task Templates
Templates define repeatable task patterns for renewal workflows:
| Field | Description |
|-------|-------------|
| `name` | Template display name |
| `description` | Detailed instructions |
| `department` | PERSONAL_LINES, COMMERCIAL_LINES, CLAIMS, BENEFITS, OTHER |
| `timing` | PRE_RENEWAL or POST_RENEWAL |
| `daysOffset` | Signed integer — number of days before (negative) or after (positive) policy expiration |
| `defaultPriority` | LOW, MEDIUM, HIGH, or URGENT |
| `designationId` | Optional — restrict template to clients with a specific designation |
| `displayOrder` | Presentation ordering |
| `isActive` | Soft-disable toggle |
Designation-based rules: When a template has a `designationId`, it only generates tasks for clients whose primary or secondary designation matches.
> **Reference:** `TaskTemplate` model in `prisma/schema.prisma`
### 4.2 Task Auto-Generation
Tasks are generated from templates based on policy expiration dates:
1. Identify policies approaching expiration within the configured window
2. Match applicable templates by department and (optionally) client designation
3. Calculate due date: `policy.expirationDate + template.daysOffset`
4. Create task records linked to the client, policy, and source template
### 4.3 Task Statuses
| Status | Description |
|--------|-------------|
| `NOT_STARTED` | Default state on creation |
| `IN_PROGRESS` | Work underway |
| `COMPLETED` | Finished — captures `completedAt` timestamp and `completedBy` user |
| `BLOCKED` | Cannot proceed — requires intervention |
| `NA` | Not applicable — **requires `naReason`** documentation |
| `CANCELLED` | Voided — **requires `cancelledReason`** documentation |
### 4.4 Priority Levels
| Priority | Usage |
|----------|-------|
| `LOW` | Routine, no urgency |
| `MEDIUM` | Standard renewal tasks |
| `HIGH` | Approaching deadline or important client |
| `URGENT` | Immediate attention required |
### 4.5 Timing & Day Offsets
Tasks are classified as either:
- **PRE_RENEWAL** — due before the policy expiration date (negative `daysOffset`)
- **POST_RENEWAL** — due after the policy expiration date (positive `daysOffset`)
The `daysOffset` value is configurable per template, allowing precise scheduling of each step in the renewal workflow.
### 4.6 Task Assignment
- Tasks support multi-user assignment via the `TaskAssignment` join table
- Workload visibility: `GET /api/dashboard/workload` provides manager-facing metrics on task distribution across team members
- Dashboard displays per-user statistics: overdue, due today, upcoming
### 4.7 N/A Marking
When a task is marked as `NA`, the `naReason` field is required. This ensures accountability and auditability for skipped tasks. Similarly, `CANCELLED` status requires `cancelledReason`.
### 4.8 API Endpoints
| Method | Path | Purpose | Permission |
|--------|------|---------|------------|
| `GET` | `/api/tasks` | List tasks with filtering (status, clientId, assignedToMe, pagination) | `tasks.read` |
| `POST` | `/api/tasks` | Create task | `tasks.write` |
Query parameters: `status`, `clientId`, `assignedToMe`, `page`, `limit` (default 50).
---
## 5. Renewal Grouping & Policy Exclusion
> **Status: NEW — not yet implemented.** This section defines net-new functionality for the MVP.
### 5.1 Problem Statement
A single client may have multiple policies with the same expiration date. Currently, there is no mechanism to control how renewal tasks are generated across those policies — whether they share a single task set or need separate ones. Additionally, some policies (being non-renewed, rewritten, or cancelled) should be excluded from task generation entirely.
### 5.2 Default Behavior: Client-Level Task Sets
When no explicit grouping exists, the system generates **one set of renewal tasks per client** based on the client's policies and their expiration dates. This preserves backward compatibility with the existing workflow.
### 5.3 Renewal Groups
A new `RenewalGroup` entity enables manual control over how policies are grouped for task generation:
**RenewalGroup model (new):**
| Field | Type | Description |
|-------|------|-------------|
| `id` | UUID | Primary key |
| `name` | String | User-provided label (e.g., "Main BOP + Auto bundle") |
| `clientId` | FK → Client | Owning client |
| `createdBy` | FK → User | Who created the group |
| `createdAt` | DateTime | Timestamp |
| `updatedAt` | DateTime | Timestamp |
**RenewalGroupPolicy join table (new):**
| Field | Type | Description |
|-------|------|-------------|
| `renewalGroupId` | FK → RenewalGroup | Group membership |
| `policyId` | FK → Policy | Included policy |
### 5.4 Manual Grouping
Users can create explicit renewal groups by selecting which policies should share a single task set:
- Select a client
- Choose one or more policies from that client
- Name the group
- Task generation uses the group rather than the full client policy set
**Use case:** A client has 5 policies but 3 of them are bundled and should be renewed together. Create one group with those 3 policies — they get one shared task set. The remaining 2 policies can be in separate groups or fall through to default behavior.
### 5.5 Splitting
When a client's policies need separate renewal workflows, users can split them into distinct renewal groups:
- Each group gets its own independent set of renewal tasks
- Groups can contain one or many policies
- Ungrouped policies fall back to default client-level behavior
### 5.6 Policy Exclusion
Individual policies can be marked as excluded from task generation:
**Policy model addition (new field):**
| Field | Type | Description |
|-------|------|-------------|
| `isExcludedFromRenewal` | Boolean (default: false) | Excludes policy from task generation |
| `exclusionReason` | String (nullable) | Required when excluded — documents why |
**Use cases for exclusion:**
- Policy is being non-renewed
- Policy is being rewritten under a new policy number
- Policy is being moved to a different carrier outside the normal renewal flow
- Client has requested cancellation
Excluded policies are skipped during task auto-generation but remain visible in the client's policy list with a visual indicator.
### 5.7 Task Generation Logic (Updated)
With renewal grouping and exclusion in place, task auto-generation follows this precedence:
1. **Exclude** any policy where `isExcludedFromRenewal = true`
2. **Grouped policies**: For each `RenewalGroup`, generate one task set using the group's policies
3. **Ungrouped policies**: For remaining policies not in any group, generate one task set per client (default behavior)
---
## Appendix
### A. Technology Stack
| Layer | Technology |
|-------|-----------|
| Frontend | React, TypeScript, Next.js 16 (App Router), Tailwind CSS, shadcn/ui |
| Backend | Next.js API Routes, NextAuth.js 4.x (JWT) |
| Database | PostgreSQL via Prisma ORM 7.x |
| Source DB | SQL Server via mssql (AFW/AMS360) |
| Scheduling | node-cron |
| Deployment | Docker |
### B. Data Model Index
| Model | Purpose | Status |
|-------|---------|--------|
| User | Application users synced from AFW employees | Existing |
| Client | Customers synced from AFW | Existing |
| Policy | Insurance policies synced from AFW | Existing |
| Task | Renewal workflow tasks | Existing |
| TaskTemplate | Reusable task definitions | Existing |
| TaskAssignment | Multi-user task assignment | Existing |
| Designation | Client shape/classification | Existing |
| AuditLog | Action audit trail | Existing |
| Notification | User-facing alerts | Existing |
| NotificationPreference | Per-user delivery prefs | Existing |
| SyncLog | Sync execution history | Existing |
| SyncConfig | Sync configuration key-value store | Existing |
| EntraGroupRoleMapping | Azure AD group → role mapping | Existing |
| UserRole | Direct user → role assignment | Existing |
| **RenewalGroup** | **Policy grouping for shared task sets** | **New** |
| **RenewalGroupPolicy** | **Group ↔ Policy join table** | **New** |
### C. Related Documents
- [PRD — OnDeck Policy Renewal](./prd-ondeck-policy-renewal.md)
- [Tasks — PRD OnDeck Policy Renewal](./tasks-prd-ondeck-policy-renewal.md)