# Planlined case integration kit

Use this package with Copilot, ChatGPT, Claude, a custom agent, a CRM, or any script that can produce JSON. It prepares data for the existing API without requiring an AI vendor connection.

The package contains:

- `ASSISTANT-INSTRUCTIONS.md`: portable instructions to attach to your assistant or integration.
- `catalog.json`: generated benefit IDs, categories, per-category fields, aliases and tiers.
- `case.schema.json`: standalone JSON Schema for normalized candidates.
- `case-kit.cjs`, `case-core.cjs`, `plan-facts.cjs`, `pricing.cjs` and `member-contracts.cjs`: dependency-free local Node.js 18+ preflight and source-import commit preparation. The web paste screen and CLI share `case-core.cjs`.
- `runtime/MemberIdentity.cjs` and `runtime/TierUtils.cjs`: generated copies of the existing app's roster identity and manual enrollment projection helpers.
- `examples/comparison.json`: synthetic current/proposed data across medical, dental, vision, life, STD and LTD.
- `examples/vendor-extraction.json`: noncanonical vendor labels that preflight can repair.
- `examples/workflow-parity.json`: starts from the existing Swagger plan example and covers all four designations, composite rates, priced rosters, volume plus AD&D, bracket populations/volume, and total-only pricing.

The catalog also includes ancillary/worksite and other app categories. Use its field lists to build complete payloads; the examples intentionally contain only selected benefits. The schema permits extension fields. Neither the kit nor server validates every business rule or renderer behavior.

## Prepare extracted data

Download/copy the entire folder, including plan-facts.cjs, pricing.cjs, member-contracts.cjs and runtime/. Provide the instructions, catalog and schema to your extractor. Save its case JSON as extracted.json.

```bash
node case-kit.cjs preflight extracted.json \
  --out ready.json --report diagnostics.json
```

No network request, parsing call, upload or case write occurs. The report preserves the original input, normalized candidate and each diagnostic. Diagnostics can contain source data; keep them private. Use task-owned output/report paths: failed preparation removes a previous ready output at that path to prevent accidental stale submission.

| Mismatch                                                                                 | Behavior                                                               |
| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Recognized benefit/category/tier alias                                                   | Map deterministically; record the repair                               |
| US currency/numeric text                                                                 | Convert without changing the amount                                    |
| Explicit annual total premium                                                            | Divide by 12 and record the conversion                                 |
| Unknown benefit                                                                          | Preserve it; warn that standard rows may not display it                |
| Nonstandard tier explicitly declared                                                     | Preserve the exact tier ID and population                              |
| Competing aliases, unknown category, undeclared tier, missing/invalid rate or enrollment | Retain original and candidate; withhold a ready payload until resolved |
| Employee deduction, unclear premium period, unknown volume unit                          | Require review; do not infer total premium                             |

Exit codes: `0` ready, `2` review required, `1` usage/IO/JSON failure. Add `--strict` to withhold readiness for warnings too. JSON Schema validation alone cannot detect alias collisions, duplicated plan IDs, source ownership, or incorrect extraction; use preflight and review the evidence.

## Create a case with structured data

Run only after reviewing a successful preflight. Set PLANLINED_API_KEY through your secure runtime, and generate REQUEST_ID once per new operation; retain it for retries of the exact same request. No credential is included in this package.

```bash
curl --fail-with-body --silent --show-error \
  --request POST 'https://app.trybart.com/api/v1/cases' \
  --header "X-API-Key: $PLANLINED_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "X-Idempotency-Key: $REQUEST_ID" \
  --dump-header case-response.headers \
  --output case-response.json \
  --data-binary @ready.json
```

The versioned route returns HTTP 201 with `case.id`, `case.rowVersion`, an ETag and a Location header. Credentials determine the organization boundary. Include `cases:write` for creation and `cases:read` for verification. The case can then be opened in Planlined for review, contributions, reports and exports. This does not upload files or start an export.

## Native case and pricing fields

Output uses the existing saved-case shape. `plan.type` selects current, renewal, proposed, or alternate; `planType` can retain a PPO/HMO/product label. Alternates carry the app's isAlternate/excludeFromTotals flags and an explicit alternateSlot (QCC's default is slot 1), with slot visibility enabled.

| Pricing mode                   | Existing app fields                                                                                                                          |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Composite                      | enrollmentAndRates[tierId].enrolled and .rate for all declared tiers                                                                         |
| Age-rated / member-rated input | members[].cost, age, coverageTier, stable identity, premiumUnit and contract/subscriber identity                                             |
| Age-banded                     | ageRateBands[].fromAge, toAge, rate, numberOfCovered and volumeAmount, plus ageBandConfig aggregate population/volume and rateDivisor        |
| Volume                         | volumeConfig.numberOfCovered, volumeAmount, ratePerUnit, ratePerUnitADnD and rateDivisor, with existing source volumeRates metadata retained |
| Total-only                     | totalMonthlyPremium, ageRatedTotalOnly/ageRatedTotalOnlyAmount and explicit tier enrollment; optional ageRatedTierTotals must agree          |

Known ancillary divisor defaults come from the app's VolumePremiumCalculator; explicit unit sizes take precedence. Bracket populations are not spread using a guessed average rate. Contract-roster counts can be projected from explicit elected tiers; component member counts are not silently treated as subscriber enrollment. For component-member inputs supply premiumUnit, contract/subscriber identity, and explicit contract enrollment by tier.

Non-current plans should specify `mapsToPlanId` for their current-plan baseline, or use the existing planLinks/linkGroupId shapes. Missing links produce `comparison_link_required` warnings; conflicting or invalid explicit links block readiness. The helper never guesses a baseline. A composite plan linked to an age-rated roster follows roster enrollment by default in the app; set the existing `linkEnrollmentFromMap: false` when independent tier enrollment is intended. Total-only input becomes the native `rateStructure: "age-rated"` plus `ageRatedTotalOnly` and aggregate premium. The existing manual-entry TierUtils helper projects its explicit tier enrollment into canonical enrollmentAndRates; blended rates in that projection are derived from the group premium and are not quoted tier prices.

The report owner reads caseData.planYear. Preflight preserves a supplied year or derives it from an explicit US/ISO policy start date; otherwise the year requires review. Benefits retain the canonical individual/family split-value shape as well as scalars and value/provenance objects.

## Retain original files and review corrected data

Source intake is multipart, not embedded/base64 JSON. For externally extracted data, submit the preflight output in the `case` field with the original file bytes. This option supports new cases in `mode=review` only. It retains originals and produces a review candidate without calling Planlined's parser. Every plan must have an explicit zero-based `sourceFileIndex` matching the upload order. Source references are assigned by the server; foreign document references are rejected. Data is marked `externalDataUnverified`; submission does not record parser acceptance or source verification.

```bash
curl --fail-with-body --silent --show-error \
  --request POST 'https://app.trybart.com/api/v1/case-imports' \
  --header "X-API-Key: $PLANLINED_API_KEY" \
  --header "X-Idempotency-Key: $IMPORT_REQUEST_ID" \
  --form-string 'mode=review' \
  --form 'case=<ready.json;type=application/json' \
  --form 'files=@current.pdf;type=application/pdf' \
  --form 'files=@proposal.pdf;type=application/pdf' \
  --output import-submission.json
```

Use `case=<ready.json`, with `<` so curl sends a text field containing the JSON. `case=@ready.json` sends a file part and is not this contract. Employer and policy period come from the supplied case unless separately provided. To request Planlined's normal parser instead, omit `case` and provide `employerName` and `policyPeriod`. Parser-only imports retain their existing processing, review decisions and failure gates.

The importer accepts up to 10 files, each up to 25 MiB, in its supported PDF/image/spreadsheet/CSV/DOCX MIME types. It retains every original before preparing a candidate. It returns HTTP 202 and links for polling; supplied-data candidates are completed and require a separate commit. Use the returned self/Location URL, honor Retry-After with bounded backoff, and stop on terminal failure/cancellation/dead-letter states. Retrieve the completed, ready review result into import-result.json:

```bash
curl --fail-with-body --silent --show-error \
  --header "X-API-Key: $PLANLINED_API_KEY" \
  "https://app.trybart.com/api/v1/case-imports/$IMPORT_ID" \
  --output import-result.json

node case-kit.cjs review-import extracted.json import-result.json \
  --out commit.json --report diagnostics.json
```

The live response envelope is `{ "ok": true, "caseImport": { "candidateCase": ..., "commitState": "ready", ... } }`. Save the whole response; the helper accepts that envelope or an unwrapped import record. Each extracted plan must include `sourceFileIndex`: 0 for the first upload, 1 for the second, etc. File names are not identities. The helper binds that index to the durable document ID/storage reference returned by the server, retains every original at case level, and supplies the QCC import marker needed for saved-case restoration. Readiness means references were consistently assembled locally; only the server and a subsequent app review can confirm access and evidence correctness.

Then review and submit the existing commit envelope:

```bash
curl --fail-with-body --silent --show-error \
  --request POST "https://app.trybart.com/api/v1/case-imports/$IMPORT_ID/commit" \
  --header "X-API-Key: $PLANLINED_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "X-Idempotency-Key: $COMMIT_REQUEST_ID" \
  --data-binary @commit.json \
  --output case-response.json
```

New-case import review is supported by this helper. Updating an existing case uses the API's targetCaseId/expectedVersion and proposedChanges workflow; do not use this helper to rewrite an existing case.

## QCC, reports and exports

Before commit, API review occurs against the candidate in the calling integration. There is no pending-import QCC inbox in this package. After commit, load the saved case and open the app's QCC review action. Linked plans can restore with their source files, and the person can compare, edit and make required acceptance decisions in QCC. Merely retaining originals is not a record of review or acceptance.

The helper links one primary original per plan, retains all originals on the case, and does not create QCC candidates for unassigned files. Multi-document merged plans and field-by-field evidence relationships require explicit app review. Local tests cover QCC restoration, the real case writer/reader using local storage/repository substitutes, state hydration, and the app report calculators. They are not proof of a live authenticated source download or production save/reopen.

After saving, app dashboards/reports, contribution calculations and exports continue through their existing owners. The legacy create-with-report renderer is narrower than the app renderer and does not guarantee all canonical benefit rows. The Proposal Book integration PDF/XLSX routes require rendered HTML/report data, not a case ID alone.

## Maintenance

In the repository:

```bash
node scripts/generate-case-integration-kit.cjs
node scripts/generate-case-integration-kit.cjs --check
npx jest --runInBand --runTestsByPath __tests__/server/vendorNeutralCaseKit.test.js
```

Catalog data is derived from BenefitFieldMap and StateDefinition; app divisor defaults and roster identity are reused. Contextual aliases are an explicit boundary policy in the generator. The case-plan write validator now accepts alternate designations and PPO/HMO/product labels when an explicit scenario is supplied; case persistence and parsing ownership are unchanged. These files become available publicly only after an approved application deployment.

## Quoted member premiums and household enrollment

`examples/member-premiums.json` is a complete synthetic case containing nine priced people in four households, one contract per elected tier. Its monthly premiums total $3,550. It can be pasted into the app or prepared for the versioned Cases API.

- `members[].id` uniquely identifies a priced row. `contractId` groups an actual employee/dependent household; different households must use different IDs.
- `premiumUnit: "member"` means the amount prices that person only. Give each person their actual quoted monthly `cost`, `age`, `relationship`, and the household's elected `coverageTier`. The household must contain exactly one employee. Ages alone do not specify a carrier rate schedule.
- `premiumUnit: "contract"` means the amount already includes the whole household. Do not add separately priced dependents or a second whole-contract premium under the same contract ID.
- Everyone under a contract uses the same elected tier (`EE`, `ES`, `EC`, `FM`), even when their relationships differ. A spouse in Employee + Spouse coverage uses `ES` along with the employee.
- Component rosters require `enrollment: {employee, spouse, child, family}`. These values count distinct contracts, not people, and must reconcile to the complete roster. Whole-contract rows can derive their counts from their elected tiers.

Paste preparation and versioned case/plan writes reject inconsistent household tiers, overlapping premiums, invalid identities, missing employee membership and mismatched contract enrollment. API create/replace validation returns HTTP 400; merged case/plan patches return HTTP 422. Field errors include `memberCode` for household diagnostics. Unversioned historical saves retain their existing compatibility behavior.

The review dialog permits source-confirmed identity, relationship, tier and enrollment corrections. Missing component enrollment offers explicit confirmation of the complete roster's household counts. Ambiguous overlapping amounts require a corrected response from the agent; they are never silently dropped. The resulting roster belongs to the plan's Rates & Enrollment data. It does not create a separate Census Processor dataset or enrollment invitation session.

## Plan Library collections

The separate Plan Library **Paste from agent** page accepts `kind: "planlined.plan-collection"`, `version: 1`, or the documented case `planData` shape. It extracts selected plans without creating a case. `plan-core.cjs` exports the portable `preflightPlanCollection(input)` profile; `plan-collection.schema.json` and `examples/plan-collections.json` document canonical fields and all supported pricing modes. The app supplies a self-contained copyable prompt and downloadable field guide using the same contract and catalog.

Use `benefitType` for the category and a unique staging `id` for each plan. The new collection forbids `type`; case scenario context is excluded from library records. Benefits-only plans need no case, year, scenario, enrollment or premiums. The page excludes pricing and member/household data by default and requires a separate per-plan opt-in. External ownership, storage and verification claims are removed; source facts remain unverified. Legacy arrays and exported library files continue to use the existing Import plan action.

This profile does not change case preflight, CLI strict behavior or API case-readiness requirements. Copy the entire folder when using either portable profile; no repository-relative imports, storage or network access are required.
