# Cursor Prompt — Add Employee Navigator Push for the 9 Voluntary / Worksite Plan Endpoints

**Goal:** Wire Plansight's `QuoteNotification` push to the nine EN plan-notification endpoints we don't currently use, so quoted voluntary/worksite plans land in EN automatically instead of being re-keyed by the broker. EN's v3 spec already supports all nine — this is purely a Plansight-side mapping job.

**Repo:** `Plansight APP` (Laravel) · package: `integrations/EmployeeNavigator/`

**Reference data:** the EN v3 swaggers in `Plansight Dev/Apps/integrations.plansight.com/Employee Navigator Swagger/`. Per-plan field listings are pre-extracted at `source/employee-navigator/swagger-extracts/<SchemaName>.json` — one file per plan, JSON list of every leaf field with type and description.

---

## Background

Today, `QuoteNotification.php:85-108` only handles `medical · dental · vision · groupLife · stDisability · ltDisability`. The `default:` branch throws `UserFacingException('Unsupported plan type')`. EN exposes 16 plan-notification endpoints; we use 6.

Coverage workbook with full surface area: `source/employee-navigator/EN_API_Coverage_2026-05-10.xlsx` (one sheet per plan + summary).

---

## The 9 endpoints to add

In rough broker-pain order (top of list = highest demand):

| # | Plansight `insuranceType` | EN endpoint | EN schema | Available fields |
|---|---|---|---|---|
| 1 | `voluntaryLife` | `/plans/voluntarylife/notification` | `VoluntaryLifePlan` | 228 |
| 2 | `voluntaryAdAndD` | `/plans/voluntaryadandd/notification` | `VoluntaryADAndDPlan` | 212 |
| 3 | `criticalIllness` | `/plans/criticalillness/notification` | `CriticalIllnessPlan` | 269 |
| 4 | `accident` | `/plans/accident/notification` | `AccidentPlan` | 80 |
| 5 | `hospitalIndemnity` | `/plans/hospitalindemnity/notification` | `HospitalIndemnityPlan` | 82 |
| 6 | `adAndD` | `/plans/adandd/notification` | `GroupADAndDPlan` | 127 |
| 7 | `voluntaryStDisability` | `/plans/voluntaryshorttermdisability/notification` | `VoluntaryShortTermDisabilityPlan` | 108 |
| 8 | `voluntaryLtDisability` | `/plans/voluntarylongtermdisability/notification` | `VoluntaryLongTermDisabilityPlan` | 105 |
| 9 | `cancer` | `/plans/cancer/notification` | `CancerPlan` | 80 |

Confirm the actual `insuranceType` strings against `app/Models/QuoteBenefitHistory` enum — column names above are the expected casing pattern but engineering may already use slightly different identifiers.

---

## Phasing

Don't ship all nine in one PR. Two phases:

**Phase 1 — high-volume voluntary lines (#1–#5):** Voluntary Life, Voluntary AD&D, Critical Illness, Accident, Hospital Indemnity. These are what brokers most often quote alongside Medical/Dental/Vision and re-key by hand today. Smallest broker-pain delta to merge.

**Phase 2 — supplementary / lower volume (#6–#9):** Group AD&D, Voluntary STD/LTD, Cancer. These overlap conceptually with existing pushes (Group AD&D ≈ Life, Voluntary STD/LTD ≈ STD/LTD), so the implementation can crib heavily from the existing case branches.

Each plan ships as its own commit / PR for review and rollback isolation.

---

## Code changes per plan (template)

For each new line, three things change:

### 1. `Operations/QuoteNotification.php` (line 85-108)

Add a `case` branch in the switch:

```php
case 'voluntaryLife':
    $planType = 'VoluntaryLife';
    break;
```

### 2. `Operations/QuoteNotification.php` (line 762, `enUrlPathMap`)

Add an entry mapping the Plansight `insuranceType` to the EN endpoint segment when they don't match exactly:

```php
$enUrlPathMap = [
    'stDisability' => 'shorttermdisability',
    'ltDisability' => 'longtermdisability',
    'groupLife' => 'life',
    'voluntaryLife' => 'voluntarylife',
    'voluntaryAdAndD' => 'voluntaryadandd',
    'criticalIllness' => 'criticalillness',
    'hospitalIndemnity' => 'hospitalindemnity',
    'adAndD' => 'adandd',
    'voluntaryStDisability' => 'voluntaryshorttermdisability',
    'voluntaryLtDisability' => 'voluntarylongtermdisability',
    // accident, cancer match the EN segment exactly — no mapping needed
];
```

### 3. `Operations/QuoteNotification.php` (the body builder, around line 110-700)

This is the meat. The existing health/life/disability branches construct the EN request body field-by-field from `$this->quote` columns. Each new line needs an analogous mapping block.

Pattern (from existing code):
- `companyIdentifier` and `plan.planIdentifier` — same as today, comes from `$this->group` and `$this->quote`.
- `plan.configuration` — same shape across all lines (`policyNumber`, `carrierName`, `planName`, `startDate`, `endDate`, `nextRenewalDate`, `isSelfFunded`, `situsState`). Reuse the existing helper.
- `plan.options` — varies per line. Schema-specific. Pull field names from `source/employee-navigator/swagger-extracts/<SchemaName>.json`.
- `plan.dependentOptions` — for lines that have spouse/child coverage (Voluntary Life, Voluntary AD&D, Critical Illness with dependents, Hospital Indemnity, Accident, Cancer).
- `plan.rates` — varies per line. Voluntary lines often use age-banded or composite rate shapes. Schema reference: `<SchemaName>.json` paths starting with `rates.*`.
- `plan.contributions` — usually voluntary plans are 100% employee-paid, so `contributions.contributionGroupings[].employeeContributionPercent = 0`. Confirm with the broker.
- `plan.eligibilityOptions` — typically passed through from existing quote eligibility config.

For each plan, look up the per-field paths in the corresponding JSON file in `source/employee-navigator/swagger-extracts/` to know what EN expects.

---

## Tests to add

For each new plan type:

1. **Unit:** Construct a `QuoteNotification` for a quote with the new `insuranceType`. Assert the request URL matches the expected `/plans/<segment>/notification`. Assert the request body has `companyIdentifier`, `plan.planIdentifier`, `plan.configuration.startDate` populated (the only required fields).
2. **Unit (per-line):** A representative happy-path quote → expected body, asserting each mapped Plansight column appears at the right EN path.
3. **Integration (manual, sandbox):** Push a real quote of each type to EN's sandbox; confirm the plan appears in the EN UI with the expected fields populated.

---

## Required vs nice-to-have fields

Across all 9 endpoints, only **2 fields are formally required**: `planIdentifier` and `configuration.startDate`. EN tolerates partial bodies — if you don't have a value for an option, omit it. The "field count" in the table above is the *available* surface area, not a list of fields you must populate.

Suggested minimum for a useful Phase 1 push: identifier + configuration block + the rates block (so EN gets enough to display the plan). Options/contributions/eligibility can come in follow-up commits per plan.

---

## Non-goals

- **Plan Type field** — EN's v3 still doesn't accept Plan Type as data on most lines; the broker still picks it in EN UI. See the existing `QuoteNotification.php:501-511` comment about this. Don't try to push it.
- **Pulling enrollments back** — covered separately by `QuoteCensus` and will need its own ticket if we want voluntary-line enrollments back in Plansight.
- **Schema validation client-side** — EN does its own validation server-side. Don't reimplement.

---

## Rollback

Each new `case` is additive. Reverting a single plan = remove its `case` branch + its `enUrlPathMap` entry + its body-builder block. Quotes of that `insuranceType` will throw `Unsupported plan type` again (which is the pre-change behavior).

---

## Cursor prompt — copy/paste

```
Implement the Phase 1 plan-type pushes described in
Plansight Dev/Apps/integrations.plansight.com/cursor-prompts/en-add-voluntary-lines.md.

Goal: extend integrations/EmployeeNavigator/Operations/QuoteNotification.php to push five new
plan types to Employee Navigator: voluntaryLife, voluntaryAdAndD, criticalIllness, accident,
hospitalIndemnity. EN's v3 spec supports them; we currently throw "Unsupported plan type" for
anything outside medical/dental/vision/groupLife/stDisability/ltDisability.

For each of the five lines:
1. Add a `case '<insuranceType>'` branch in the planType switch (currently lines 85-108).
2. Add an entry to $enUrlPathMap (line 762) mapping the insuranceType to the EN endpoint
   segment if they don't match exactly.
3. Build the request body, mirroring the structure of the existing groupLife / stDisability
   branches. Map Plansight quote columns to the EN schema's configuration / options /
   dependentOptions / rates / contributions / eligibilityOptions paths.

Schema reference for each plan type's available fields:
  Plansight Dev/Apps/integrations.plansight.com/source/employee-navigator/swagger-extracts/<Schema>.json
  - VoluntaryLifePlan.json (228 fields)
  - VoluntaryADAndDPlan.json (212 fields)
  - CriticalIllnessPlan.json (269 fields)
  - AccidentPlan.json (80 fields)
  - HospitalIndemnityPlan.json (82 fields)

Required fields per plan: planIdentifier + configuration.startDate. Everything else is
optional — populate what we have a Plansight column for, omit the rest.

Add unit tests for each new branch:
  - Correct URL is constructed
  - companyIdentifier, plan.planIdentifier, plan.configuration.startDate are present
  - One representative happy-path mapping per line

Confirm the actual insuranceType enum strings against app/Models/QuoteBenefitHistory before
writing the case branches — column names in the spec doc are pattern guesses.

Do NOT touch:
- The existing 6 branches (medical/dental/vision/groupLife/stDisability/ltDisability)
- Plan Type push (still not supported by EN v3 — see QuoteNotification.php:501-511)
- The QuoteCensus pull side

Phase 2 (Group AD&D, Voluntary STD, Voluntary LTD, Cancer) is a follow-up — do not include
in this PR.

When done, summarize: which insuranceType strings you actually used (after checking the
QuoteBenefitHistory enum), how many lines per file changed, any quote columns that don't
have an obvious EN mapping yet (those become follow-up "field gap" tickets).
```

---

## After this ships

- Re-run `scripts/extract-en-fields.py` from the integrations site repo. The "Mapped in Plansight" count in the audit workbook will go from 6 to 11.
- Update `employee-navigator.html` gap #7: move the Phase 1 lines from the "available but not used" table to a new "recently shipped" callout. Re-deploy via `vercel --prod`.
- Send a release note to the brokers: voluntary line X now flows to EN automatically.
