+

Connect every employer to their benefits administrator

Bidirectional integration with Employee Navigator. Push employer profiles, plan elections, and rates into EN. Pull census and enrollment data back for ACA quoting. Built like BenefitPoint — same OAuth pattern, same operation framework, same broker-controlled flow.

Browse the interactive Sync Map
Direction: Bidirectional Auth: OAuth 2.0 Status: Production · Working Docs: Employee Navigator API docs ↗ (partner login)
What it is

One handshake, then plans and people flow both ways

A broker activates the integration on a Plansight employer, links the employer to its EN counterpart once, and from then on plans push out and census pulls back with a click.

OAuth 2.0
Authorization Code + Client Credentials
3
Operations: CompanyIntegration, QuoteNotification, QuoteCensus
7
Routes for callbacks, status, push, pull
All
Plan types: medical, dental, vision, life, ancillary
🔗

Activated per employer

Toggle on under Edit Employer → Employee Navigator. A new "Employee Navigator" tab appears for that employer. Activation is per-employer, not per-brokerage — brokers control which employers sync.

🤝

One-time link, then automatic

First push prompts EN OAuth login. After the broker links the Plansight employer to an EN company (existing or new), all subsequent pushes / pulls happen without re-auth — system-level token handles ongoing operations.

📤

Push: plans + rates + benefits

From Plansight's Benefit History or post-election views, broker selects elected plans and pushes to EN. Plan design, rates (composite or age-banded), benefit summary all flow over. EN side accepts as new plans or renews existing ones by matching benefit type + dates.

📥

Pull: census for ACA quoting

Inside an RFP marked community-rated, broker can pull EN census directly at the quoting / census step — employee roster, demographics, current enrolled plan. Skips the spreadsheet upload entirely.

Why

Where this saves time

Each side of the integration eliminates a manual step in the renewal workflow.

👤

Broker

No more re-keying employer data into EN, no more uploading censuses for community-rated quotes, no more manually entering elected plan details on the EN side at renewal close.

🏢

Agency

One canonical record of an employer that lives in both systems. Reduces drift between Plansight's quote/election history and EN's enrollment record.

Renewal velocity

Push elected plans the moment elections close. Census comes back instantly when starting next year's RFP. Cuts hours off both ends of the renewal cycle.

Two integrations in one

The EN integration handles two distinct flows: employer + plan push (Plansight → EN) when elections close, and census pull (EN → Plansight) when starting an ACA / community-rated quote. They share auth and data-link plumbing but solve different broker problems and run at different points in the year.

Activation Flow

One-time setup per employer

Connecting a Plansight employer to an Employee Navigator company. Done once; all future pushes / pulls run on the persisted link.

1

Toggle the integration on

From the Plansight employer page, Edit Employer → Employee Navigator. Turn on, save. A new Employee Navigator tab appears for that employer.

2

Push the employer over

From the new tab, click push. Plansight redirects to EN's OAuth login. Broker authenticates with their EN credentials. Plansight calls POST /api/Quoting/companies/{groupId}/integration (the CompanyIntegration operation). EN receives the request and creates a Pending Integration on its dashboard.

3

Link on the EN side

Broker switches to EN, opens the pending integration. Either links to an existing EN company OR creates a new one (broker enters state + SIC code). EN persists the link.

4

Update status back in Plansight

Broker returns to Plansight's EN tab, refreshes status. Plansight calls GET /api/Quoting/companies/{groupId}/status. EN responds with Accepted. Connection is now live for this employer — both sides know which Plansight group maps to which EN company.

Why a manual link step?

EN companies and Plansight employers don't share a stable external ID. The broker is the only one who knows whether "ACME Industries" in Plansight is the same as "ACME Industries Inc." in EN, or whether to spin up a new EN company. The link step puts that decision in the broker's hands once, then the system tracks the pairing forever.

Push Flow · Plansight → EN

Send elected plans to Employee Navigator

Once the employer is linked, pushing elected plans is a one-click operation. Plan design, rates, and benefit summary all transfer.

1

From Benefit History or election view

After an RFP closes and elections are in, broker views the elected plans in Plansight. Selects one or more plans → Send to Employee Navigator.

2

Plansight calls QuoteNotification

For each selected plan, Plansight builds the benefit details payload from the Quote record and POSTs to EN's notification endpoint. Multiple plans push in a single request batch with error aggregation per plan.

3

EN shows plan notifications

On the EN dashboard, broker sees N new Plan Notifications (one per pushed plan). Each shows carrier, plan name, plan type, dates, and a preview of benefit summary + rates.

4

Broker accepts: new plan or renewal

Broker clicks into each notification. EN matches by benefit type + date range: if there's a plan with the same benefit type and adjacent dates, EN offers Renew; otherwise Add as new plan. Renewal carries forward eligibility, contributions, and rate history from the previous year.

5

Plan is live in EN

Plan design, benefit summary, and rates (composite or age-banded — preserved as-is) are all in EN's record. Broker can edit anything that needs to differ between Plansight's quote and EN's enrollment configuration (e.g., eligibility rules).

📋

What flows over

Plan name, carrier, dates, plan type · benefit summary (deductibles, copays, coinsurance, OOP max) · contribution structure · composite rates OR age-banded rates · funding type · network info.

🚫

What doesn't flow

Eligibility classes (broker sets these in EN at acceptance) · enrollment data (this flows the other direction) · employer-specific contribution overrides not captured in the Plansight quote.

Renewal vs new

EN matches incoming plans by benefit type + date adjacency. If matched, plan history (eligibility, contributions, rates from prior year) carries forward. If not, plan is added fresh — broker fills eligibility / contribution rules from scratch.

Pull Flow · EN → Plansight

Pull census for ACA / community-rated quoting

When starting an RFP marked community-rated, the census step can pull directly from EN — no spreadsheet upload, no manual entry of demographics.

1

Inside the RFP, mark community-rated

Standard RFP setup — broker sets up the RFP and marks it for community-rated quoting (small group / ACA market). The integration is only available for community-rated workflows where individual demographics drive premiums.

2

At the census step, pull from EN

At the quoting / census section, broker picks "Pull from Employee Navigator." Plansight calls EN's QuoteCensus operation, which returns the employer's current employee roster + dependents + currently enrolled plans + enrollment counts per plan.

3

Map EN plans to Plansight options

EN returns N plans (e.g., $250 deductible, $750, $1000) with enrollment counts per plan. Broker maps each EN plan to a Plansight option slot (option 1 / 2 / 3) — typically by deductible tier or some other ordering. This is just a mapping decision; no data is being transformed.

4

Census applied automatically

Plansight builds the census from EN data: each employee + their demographics (gender, birthDate, ZIP, tobacco status) + which option they're enrolled in. Quote rates calculate against the actual census without any spreadsheet upload.

Demographic mapping

Plansight maps several EN fields when building the census:

Wiring

Authentication & connection

OAuth 2.0 with two grant types — authorization code for broker-initiated actions, client credentials for system-level operations.

🔐

Authorization Code (broker-initiated)

Used when a broker pushes an employer or pushes plans. Plansight redirects to EN's identity provider, broker authenticates, EN redirects back to /integrations/employeeNavigator/callback. Token cached with TTL = expiry minus 60 seconds.

⚙️

Client Credentials (system-initiated)

Used for census polling and status checks that don't require broker interaction. System token is cached separately and reused across requests until expiry.

Configuration

SettingValue / Source
OAuth scopesopenid · EnApi · QuotingCompanyIntegrationApi · QuotingPushNotificationApi
Identity URL (production)identity.employeenavigator.net
Identity URL (QA)identity.employeenavigator.net (QA tenant)
API base URLwww.employeenavigator.com
Credentials storageconfig/services.employeeNavigator
HTTP clientSymfony NativeHttpClient (registered as Laravel singleton)
Token cacheLaravel cache, tagged integrations, TTL = expiry − 60s
Session pairingKeyed session state pairs OAuth callback to the original action (groupPush / quotePush / censusPull)
Codebase

Where it lives in the Plansight repo

All source paths relative to the plansight repo root.

FilePurpose
app/Providers/EmployeeNavigatorClientProvider.phpService provider — registers Symfony NativeHttpClient as injectable singleton (lines 20–22)
app/integrations/EmployeeNavigator/Api.phpCore request dispatcher. Auth headers, body serialization, response parsing, error logging (lines 155–221)
app/integrations/EmployeeNavigator/TokenService.phpOAuth state machine. Authorization URL generation (70–102), user token exchange (206–252), client-credentials token fetch (149–198)
app/integrations/EmployeeNavigator/Controller.phpHTTP entry points — callback handler (35–124), groupPush() (133–205), quotePush() (214–336), pullCensus() (345–578)
app/integrations/EmployeeNavigator/Operations/CompanyIntegration.phpOperation class for employer integration (push employer)
app/integrations/EmployeeNavigator/Operations/QuoteNotification.phpOperation class for plan/quote pushes. Builds benefit details payload (line 790)
app/integrations/EmployeeNavigator/Operations/QuoteCensus.phpOperation class for census retrieval. Orchestrates employee/dependent extraction (line 145)
app/integrations/EmployeeNavigator/routes.php7 routes loaded into routes/web.php — callbacks, status queries, push/pull endpoints

Notable design choices

Operation pattern

All EN interactions implement an Operation interface — pluggable request, response, and post-processing handlers. Adding a new operation doesn't duplicate HTTP plumbing.

Session-state pairing

OAuth callback uses a keyed session entry to pair the auth return with the original user action. No interim DB record needed; flow stays seamless.

Tagged cache invalidation

Token cache uses Laravel's tagged cache with the integrations tag. One call invalidates EN tokens (and any other integrations on the same tag) wholesale.

Synchronous push, modal feedback

Quote and employer pushes are synchronous with immediate modal alerts — no background queue. Trade-off: simpler UX, but a slow EN response blocks the broker on screen.

Supported benefit types

What we sync today vs. what EN exposes

The push side covers six benefit types. The pull (census) side intentionally only processes Medical — every other product line that EN sends in the census payload is filtered out. The asymmetry is itself a gap and is captured below.

Benefit typePush to EN
(QuoteNotification)
Pull from EN
(QuoteCensus)
Notes
Medical✓ Synced✓ SyncedOnly line that's bi-directional today.
Dental✓ Synced— filtered outPlan + rates push, but enrollments are dropped on import.
Vision✓ Synced— filtered outSame — pushed, not pulled.
Short-Term Disability✓ Synced— filtered outComposite + age-banded + ASO flat-fee rate shapes supported on push.
Long-Term Disability✓ Synced— filtered outSame as STD.
Group Life (incl. AD&D)✓ Synced— filtered outFixed-amount and salary-multiple variants both supported. Contributions hardcoded as Employer / Percentage.
Voluntary Life— not mapped— not mappedEN exposes; Plansight has Voluntary Life elsewhere but no EN mapping.
Accident · Cancer · Critical Illness · Hospital Indemnity— not mapped— filtered outVoluntary lines absent from QuoteNotification.
HRA · HSA · FSA · COBRA · EAP— not mapped— filtered outAdmin/non-insurance products absent from EN integration.

Sources: QuoteNotification.php:85-108 (push type map), Controller.php:494-497 + line 424 (pull filter — hardcoded useTypes = ['Medical']). See the interactive Sync Map for field-level coverage.

Per-benefit field mapping

Field-by-field, per benefit line

For each benefit type we push to EN, here's exactly which Plansight column lands in which EN field path — and the fields EN's schema accepts that we don't currently send. Plain table, three columns: what we have, where it goes, and whether it's mapped.

Source coverage Every row in the four tables below carries a Source badge that tells you where the EN-side claim is grounded. Confidence drops left to right:
Health benefits (Medical · Dental · Vision) Group Life Short-Term Disability Long-Term Disability Voluntary & worksite (not mapped) Cross-cutting observations

Health benefits (Medical · Dental · Vision)

All three health-shaped lines share the same EN schema and the same Plansight push code path. The mappings below apply identically to Medical, Dental, and Vision — the only differences are which Plansight quote rows are populated for each line, not how the fields map.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Configuration
QuoteBenefitHistory.inForcePolicyNumberplan.configuration.policyNumberMappedCodeQuoteNotification.php:115QuoteBenefitHistory.inForcePolicyNumberplan.configuration.policyNumberMappedQuoteNotification.php:115
Carrier.name / employeeNavigatorNameplan.configuration.carrierNameMappedCodeLines 116, 135 — uses employeeNavigatorName when populatedCarrier.name / employeeNavigatorNameplan.configuration.carrierNameMappedLines 116, 135 — uses employeeNavigatorName when populated
QuoteBenefitHistory.nameplan.configuration.planNameMappedSpecLine 117QuoteBenefitHistory.nameplan.configuration.planNameMappedLine 117
QuoteBenefitHistory.inForceStartDateplan.configuration.startDateMappedCodeLine 118 — UTC ISO 8601QuoteBenefitHistory.inForceStartDateplan.configuration.startDateMappedLine 118 — UTC ISO 8601
QuoteBenefitHistory.inForceEndDateplan.configuration.endDate + nextRenewalDateMappedCodeLines 119–120 — same value to bothQuoteBenefitHistory.inForceEndDateplan.configuration.endDate + nextRenewalDateMappedLines 119–120 — same value to both
QuoteBenefitHistory.fundingTypeplan.configuration.isSelfFundedMappedCodeLine 122 — true when == 'Self Funded'QuoteBenefitHistory.fundingTypeplan.configuration.isSelfFundedMappedLine 122 — true when == 'Self Funded'
Rfp.serviceState or Quote.stateplan.configuration.situsStateConditionalSpecLines 142–161 — uses RFP value if originalRfpId set, else falls backRfp.serviceState or Quote.stateplan.configuration.situsStateConditionalLines 142–161 — uses RFP value if originalRfpId set, else falls back
Plan design (services)
Plansight quote design fields (per Plantypes config)plan.planCommunications.benefitSummary.services[].typeMappedSpecLines 183–242 — driven by field.integrations.employeeNavigator configPlansight quote design fields (per Plantypes config)plan.planCommunications.benefitSummary.services[].typeMappedLines 183–242 — driven by field.integrations.employeeNavigator config
Plansight design value (in-network)plan.planCommunications.benefitSummary.services[].inNetworkValueMappedCodeLines 213–224Plansight design value (in-network)plan.planCommunications.benefitSummary.services[].inNetworkValueMappedLines 213–224
Plansight design value (preferred network)plan.planCommunications.benefitSummary.services[].preferredNetworkValueConditionalCodeOnly sent when inNetworkOptions == 2Plansight design value (preferred network)plan.planCommunications.benefitSummary.services[].preferredNetworkValueConditionalOnly sent when inNetworkOptions == 2
Plansight design value (out-of-network)plan.planCommunications.benefitSummary.services[].outOfNetworkValueMappedCodeLines 227–230Plansight design value (out-of-network)plan.planCommunications.benefitSummary.services[].outOfNetworkValueMappedLines 227–230
plan.benefits[].employee.flatBenefit.amountNot mappedSpecEN supports flat-benefit copays / deductibles. Plansight doesn't sendplan.benefits[].employee.flatBenefit.amountNot mappedEN supports flat-benefit copays / deductibles. Plansight doesn't send
plan.benefits[].dependents.incrementsBenefitNot mappedSpecEN supports dependent-specific cost-share structuresplan.benefits[].dependents.incrementsBenefitNot mappedEN supports dependent-specific cost-share structures
Rates — composite (non-ACA)
QuoteBenefitHistory.rateEmployeeOnlyplan.rates.compositeRates.tierRates[Employee].rateConditionalSpecLine 625 — sent when aca != 1QuoteBenefitHistory.rateEmployeeOnlyplan.rates.compositeRates.tierRates[Employee].rateConditionalLine 625 — sent when aca != 1
rateEmployeeSpouse...tierRates[EmployeePlusSpouse].rateConditionalSpecLine 630rateEmployeeSpouse...tierRates[EmployeePlusSpouse].rateConditionalLine 630
rateEmployeeChild...tierRates[EmployeePlusChild].rateConditionalSpecLine 635rateEmployeeChild...tierRates[EmployeePlusChild].rateConditionalLine 635
rateEmployeeChildren...tierRates[EmployeePlusChildren].rateConditionalSpecLine 640rateEmployeeChildren...tierRates[EmployeePlusChildren].rateConditionalLine 640
rateEmployeeFamily...tierRates[EmployeePlusFamily].rateConditionalSpecLine 645rateEmployeeFamily...tierRates[EmployeePlusFamily].rateConditionalLine 645
...tierRates[EmployeePlusDomesticPartner].rateNot mappedSpecEN supports a domestic-partner tier; Plansight schema has no equivalent column...tierRates[EmployeePlusDomesticPartner].rateNot mappedEN supports a domestic-partner tier; Plansight schema has no equivalent column
Rates — ACA age-banded + tobacco
QuoteACARate.rates[Y##]plan.rates.acaAgeBasedRates.rates[].rateConditionalCodeLines 611–616 — sent when aca == 1QuoteACARate.rates[Y##]plan.rates.acaAgeBasedRates.rates[].rateConditionalLines 611–616 — sent when aca == 1
Age extracted from key...rates[].fromAge / toAgeConditionalCodeSame age in both fields per ACA conventionAge extracted from key...rates[].fromAge / toAgeConditionalSame age in both fields per ACA convention
QuoteACARate.rates[Y##]...tobaccoBasedRates[].rateNonsmokerConditionalCodeLines 596, 600 — when tobacco rates exist on the rating areaQuoteACARate.rates[Y##]...tobaccoBasedRates[].rateNonsmokerConditionalLines 596, 600 — when tobacco rates exist on the rating area
QuoteACARate.tobaccoRates[Y##]...tobaccoBasedRates[].rateSmokerConditionalCodeLines 593, 601QuoteACARate.tobaccoRates[Y##]...tobaccoBasedRates[].rateSmokerConditionalLines 593, 601
Contributions
plan.contributions.contributionGroupings[].monthlyContributions[]Not mappedCodeEN accepts premium-sharing data on health plans; Plansight doesn't populate (only Group Life does — see below)plan.contributions.contributionGroupings[].monthlyContributions[]Not mappedEN accepts premium-sharing data on health plans; Plansight doesn't populate (only Group Life does — see below)

Field-level mappings — by plan type

Pulled from config/plantypeFiles/{medical,dental,vision}.php. These are the design fields the broker fills in on Plansight that get pushed into plan.planCommunications.benefitSummary.services[] on the EN side — each row shows the Plansight key/label and the EN service-type identifier it lands as.

Plansight Medical fields   29 fields   configured in config/plantypeFiles/medical.php
— / —
Plansight fieldEN service type
Plan Design - Medical Deductible
Per Individual
DeductibleIndividual
DeductibleIndividual
Per Family
DeductibleFamily
DeductibleFamily
Deductible Type
DeductibleType
DeductibleType
Deductible | OOP Accumulation
DeductibleOopAccumulation
CalendarOrPlanYearDeductible
Plan Design - Out-of-pocket Maximum
Per Individual
OOPIndividual
OutOfPocketMaximumIndividual
Per Family
OOPFamily
OutOfPocketMaximumFamily
Plan Design - Office Visit
Primary Physician
PrimaryCareDoctor
PrimaryCarePhysician
SpecialistSpecialist
Specialist Referral
SpecialistReferral
SpecialistReferralRequired
TelehealthVirtualTelemedicineVisit
Plan Design - Tests
Preventive Services
PreventativeServices
PreventiveServices
Diagnostic Test (X-Ray, Blood Work)
Diagnostic
DiagnosticTest
Imaging (CT/PET Scans, MRIs)
Imaging
Imaging
Plan Design - Prescription Drugs
Per Individual
RxIndividualDeductible
DeductiblePrescription
Tier 1
RxTier1
GenericDrugsTier1
Tier 3
RxTier3
PreferredBrandDrugsTier2
Tier 4
RxTier4
NonPreferredBrandDrugsTier3
Tier 5
RxTier5
SpecialtyDrugsTier4
Mail Order
RxMailOrder
MailOrderRx
Plan Design - Outpatient Surgery
Facility Fee
OutpatientFacility
HospitalizationOutpatient
Free Standing Facility
OutpatientFreeStandingFacility
HospitalizationOutpatient
Physician / Surgeon Fee
OutpatientPhysician
SurgeryOutpatient
Plan Design - Immediate Attention
Emergency Room Copay
EmergencyRoomCopay
EmergencyRoom
Urgent Care
UrgentCare
UrgentCare
Plan Design - Inpatient Services
Facility Fee
HospitalFacility
HospitalizationInpatient + Coinsurance
Physician / Surgeon Fee
HospitalPhysician
SurgeryInpatient
Plan Design - Substance Abuse
Outpatient Services
OutpatientMental
MentalHealthOutpatient
Inpatient Services
InpatientMental
MentalHealthInpatient
Plan Design - Maternity / Pregnancy
Office Visits
MaternityOffice
MaternityVisits
Plansight Dental fields   21 fields   configured in config/plantypeFiles/dental.php
— / —
Plansight fieldEN service type
Schedule Of Benefits
Individual Deductible
DeductibleIndividual
DeductibleIndividual
Family Deductible
DeductibleFamily
DeductibleFamily
Deductible Accumulation
DeductibleAccumulation
BenefitPeriod
Annual Maximum
AnnualMaxIndividual
AnnualMaximumBenefit
Preventive (I)
BenefitSchedPreventiveI
???
Preventive (I)
BenefitSchedPreventiveI
OralExaminations
Basic (II)
BenefitSchedBasicII
Fillings
Dental Services
ExamsOralExaminations
CleaningsCleaningsAdult + CleaningsChildren
Basic Filling
BasicFilling
Fillings
PeriodonticsPeriodontal
CrownsCrowns
DenturesCompleteDenture
ImplantsImplantCoverage
Dental Waiting Periods
Preventive (I)
WaitingPeriodPreventiveI
WaitingPeriodPreventativeServices
Basic (II)
WaitingPeriodBasicII
WaitingPeriodBasicServices
Major (III)
WaitingPeriodMajorIII
WaitingPeriodMajorServices
Orthodontics
Orthodontics (IV)
Orthodontics
OrthodontiaServices
Lifetime Ortho. Max.
LifetimeOrthoMax
OrthodontiaLifetimeMaximum
Orthodontics Age
OrthodonticsAge
OrthodontiaEligibility
Ortho (IV) Wait Period
OrthodonticsIVWaitPeriod
WaitingPeriodOrthodontiaServices
Plansight Vision fields   14 fields   configured in config/plantypeFiles/vision.php
— / —
Plansight fieldEN service type
Benefit Frequency
Eye Exam
ExamFrequency
EyeExams
Frames
FramesFrequency
Frames
Lenses
LensesFrequency
Lenses
Contacts (in lieu of glasses)
ContactsFrequency
ContactLenses
Plan Provisions
Eye Exam
EyeExams
ExamCopay
Single Vision Lenses
SingleVisionLenses
SingleVisionLenses
Bifocal Lenses
BifocalLenses
BifocalLenses
Trifocal Lenses
TrifocalLenses
TrifocalLenses
Basic Progressive Lenses
BasicProvisionLenses
ProgressiveLenses
Premium Progressive Lenses
PremiumProgressiveLenses
PremiumProgressiveLensOptions
Contacts Allowance - Elective
ContactsElective
ContactsAllowance
Contacts Visually Necessary
ContactsNecessary
ContactsAllowanceMedicallyNecessary
Frames
Coverage Allowance
FramesCovered
FrameAllowance
Corrective Vision Services
Lasik Vision Correction
LasikDiscount
LaserEyeCorrection

Group Life

Group Life uses a different shape — a benefit-amount calculation (flat or salary-multiple) plus an explicit contributions block. Plan Type itself can't be set via the API in v3.0 (it's a manual selection in EN); Plansight sends a descriptive label only.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Configuration
(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, isSelfFunded, situsState}MappedSpecIdentical to Health benefits — see above(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, isSelfFunded, situsState}MappedIdentical to Health benefits — see above
QuoteBenefitHistory.groupLifeCoverageplan.configuration.carrierPlanTypeDescriptionMappedCodeLines 514–525 — sent as descriptive text; EN's API can't set Plan Type itself in v3.0 (manual in EN UI)QuoteBenefitHistory.groupLifeCoverageplan.configuration.carrierPlanTypeDescriptionMappedLines 514–525 — sent as descriptive text; EN's API can't set Plan Type itself in v3.0 (manual in EN UI)
Benefits (employee)
QuoteBenefitHistory.deathBenefit-X (when groupLifeCalculation == 'fixed')plan.benefits[].employee.flatBenefit.amountConditionalSpecLines 383–415QuoteBenefitHistory.deathBenefit-X (when groupLifeCalculation == 'fixed')plan.benefits[].employee.flatBenefit.amountConditionalLines 383–415
deathBenefit-X (when groupLifeCalculation == 'salary')plan.benefits[].employee.multipleOfEarningsBenefit.multipleOfEarningsConditionalSpecLines 406–414deathBenefit-X (when groupLifeCalculation == 'salary')plan.benefits[].employee.multipleOfEarningsBenefit.multipleOfEarningsConditionalLines 406–414
maximumBenefit...multipleOfEarningsBenefit.maximumBenefitConditionalCodeLines 410–411 — salary-multiple mode onlymaximumBenefit...multipleOfEarningsBenefit.maximumBenefitConditionalLines 410–411 — salary-multiple mode only
(combined Life + ADD when basic+ADD)deathBenefit (combined into employee benefit)MappedCodeLines 283–285 — Life and ADD values combined into a single deathBenefit(combined Life + ADD when basic+ADD)deathBenefit (combined into employee benefit)MappedLines 283–285 — Life and ADD values combined into a single deathBenefit
Benefits (dependents)
plan.benefits[].dependents.approvedBenefitAmountNot mappedSpecEN supports per-dependent benefit amountplan.benefits[].dependents.approvedBenefitAmountNot mappedEN supports per-dependent benefit amount
plan.benefits[].dependents.incrementsBenefitNot mappedSpecEN supports spouse/child benefit incrementsplan.benefits[].dependents.incrementsBenefitNot mappedEN supports spouse/child benefit increments
Contributions
hardcoded "Percentage"...monthlyContributions[].contributionTypeMappedCodeLine 448hardcoded "Percentage"...monthlyContributions[].contributionTypeMappedLine 448
hardcoded "Employer"...monthlyContributions[].contributionBasisMappedCodeLine 449hardcoded "Employer"...monthlyContributions[].contributionBasisMappedLine 449
groupLifePremiumPaidBy...monthlyContributions[memberType=Dependents].contributionMappedCodeLines 453–463 — set to 0 or 100 (percentage)groupLifePremiumPaidBy...monthlyContributions[memberType=Dependents].contributionMappedLines 453–463 — set to 0 or 100 (percentage)
contributionVoluntary...monthlyContributions[memberType=Employee].contributionMappedSpecLines 465–479 — set to 0 or 100 (percentage)contributionVoluntary...monthlyContributions[memberType=Employee].contributionMappedLines 465–479 — set to 0 or 100 (percentage)
...contributionGroupings[].memberContributionsNot mappedCodeEN supports per-member contribution rules; Plansight only sends type-level...contributionGroupings[].memberContributionsNot mappedEN supports per-member contribution rules; Plansight only sends type-level
Rates
plan.rates.compositeRates.tierRates[]Not mappedCodeEN accepts composite life rates; Plansight doesn't send (optional in EN)plan.rates.compositeRates.tierRates[]Not mappedEN accepts composite life rates; Plansight doesn't send (optional in EN)

Field-level mappings — Group Life

Pulled from config/plantypeFiles/groupLife.php — Plansight design fields with integrations.employeeNavigator.attributePath set, and the EN payload paths they land in.

Plansight Group Life fields   8 fields   configured in config/plantypeFiles/groupLife.php
— / —
Plansight fieldEN field path
Other
groupLifeCalculation-Xfixed: benefits.employee.flatBenefit · salary: benefits.employee.multipleOfEarningsBenefit
Death Benefit
deathBenefit-X
fixed: benefits.employee.flatBenefit.amount · salary: benefits.employee.multipleOfEarningsBenefit.multipleOfEarnings
Maximum Benefit
maximumBenefit-X
benefits.employee.multipleOfEarningsBenefit.maximumBenefit
Dependent Benefits
Spouse Benefit Amount
spouseBenefit
benefits.dependents.incrementsBenefit.spouseIncrement
Child(ren) Benefit Amount
childBenefit
benefits.dependents.incrementsBenefit.childIncrement
Rates
Life Rate /per $1,000
Life
rates.compositeRate.rate
Spouse Rate /per Unit
SPOUSE
benefits.dependents.incrementsBenefit.monthlyPremiumOption.combinedPremium.dependentMonthlyPremium
Child Rate /per Unit
CHILD
benefits.dependents.incrementsBenefit.monthlyPremiumOption.combinedPremium.dependentMonthlyPremium

Short-Term Disability

STD supports both Insured and ASO funding via the benefitType field, which overrides the global isSelfFunded. Rate structure is selectable: Composite, Age-Banded, or ASO Flat-Fee. Plansight sends the right rate shape based on rateStructure.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Configuration
(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, situsState}MappedSpecIdentical to Health benefits(same Configuration block as Health)plan.configuration.{policyNumber, carrierName, planName, startDate, endDate, situsState}MappedIdentical to Health benefits
QuoteBenefitHistory.<benefitType> (Insured / ASO)plan.configuration.isSelfFundedMappedSpecLines 305–327 — overrides the global funding flag for STDQuoteBenefitHistory.<benefitType> (Insured / ASO)plan.configuration.isSelfFundedMappedLines 305–327 — overrides the global funding flag for STD
Benefit design
plan.benefits[].employee.approvedBenefitAmountNot mappedSpecEN supports active benefit cap amountsplan.benefits[].employee.approvedBenefitAmountNot mappedEN supports active benefit cap amounts
plan.benefits[].employee.benefitCalculationMethodNot mappedSpecEN schema allows; Plansight has the data but doesn't send itplan.benefits[].employee.benefitCalculationMethodNot mappedEN schema allows; Plansight has the data but doesn't send it
plan.benefits[].employee.eliminationPeriodNot mappedSpecEN supports waiting periods before benefit kicks inplan.benefits[].employee.eliminationPeriodNot mappedEN supports waiting periods before benefit kicks in
plan.benefits[].employee.integrationWithOtherIncomeNot mappedSpecEN supports coordination with other income sourcesplan.benefits[].employee.integrationWithOtherIncomeNot mappedEN supports coordination with other income sources
Rates
Y19_Under (when rateStructure == 'Composite')plan.rates.compositeRate.rateConditionalCodeLines 344–349Y19_Under (when rateStructure == 'Composite')plan.rates.compositeRate.rateConditionalLines 344–349
Y20_24Y70_Above (when rateStructure == 'Age Banded')plan.rates.ageBasedRates.rates[].fromAge / toAge / rateConditionalCodeLines 350–372 — age boundaries extracted from key namesY20_24Y70_Above (when rateStructure == 'Age Banded')plan.rates.ageBasedRates.rates[].fromAge / toAge / rateConditionalLines 350–372 — age boundaries extracted from key names
Y19_Under (when rateStructure == 'ASO')plan.rates.asoFlatRate.flatMonthlyFeeConditionalCodeLines 334–340 — STD-only path; LTD has no ASO supportY19_Under (when rateStructure == 'ASO')plan.rates.asoFlatRate.flatMonthlyFeeConditionalLines 334–340 — STD-only path; LTD has no ASO support
plan.rates.ageBasedRates.rates[].rateAdjustmentFactorNot mappedCodeEN supports rate modifiersplan.rates.ageBasedRates.rates[].rateAdjustmentFactorNot mappedEN supports rate modifiers

Field-level mappings — STD

Pulled from config/plantypeFiles/stDisability.php.

Plansight STD fields   8 fields   configured in config/plantypeFiles/stDisability.php
— / —
Plansight fieldEN field path
Schedule of Benefits
Injury Elimination Period
injuryEliminationPeriod
benefits.details.accidentWaitingPeriodDays
Sickness Elimination Period
sicknessEliminationPeriod
benefits.details.sicknessWaitingPeriodDays
Minimum Weekly Benefit
minimumWeeklyBenefit
benefits.percentEarningsBenefit.minimumBenefit
Maximum Benefit Duration
maximumBenefitDuration
benefits.details.benefitDurationWeeks
Other
Benefit Percentage
benefitPercentage-X
benefits.percentEarningsBenefit.benefitPercentEarnings
Maximum Weekly Benefit
maxWeeklyBenefit-X
benefits.percentEarningsBenefit.maximumBenefit
Rates
Rate Type
benefitType
aso: configuration.isSelfFunded
Rate Structure
rateStructure
composite: rates.compositeRate.rate · ageBanded: rates.ageBasedRates.rates · asoFlatMonthlyFee: rates.asoFlatRate.flatMonthlyFee

Long-Term Disability

LTD is structurally identical to STD with two exceptions: (1) no ASO mode — Plansight always sends LTD as Insured (line 315 forces this); (2) the Maximum Benefit Duration field is uniquely relevant to LTD and isn't mapped on either side.

Mapped Conditional Not mapped
— / —
Plansight fieldEN field pathStatusSourceNotes
Differences from STD
(no ASO equivalent)plan.rates.asoFlatRate.flatMonthlyFeeNot supportedSpecLine 315 — $asoSelected = true only when insuranceType != 'ltDisability'. LTD always pushed as Insured(no ASO equivalent)plan.rates.asoFlatRate.flatMonthlyFeeNot supportedLine 315 — $asoSelected = true only when insuranceType != 'ltDisability'. LTD always pushed as Insured
plan.benefits[].employee.maximumBenefitDurationNot mappedSpecEN supports max benefit period (months / years) — LTD-specific conceptplan.benefits[].employee.maximumBenefitDurationNot mappedEN supports max benefit period (months / years) — LTD-specific concept
All Configuration, Benefit Design, and non-ASO Rate fields are identical to STD above.

Field-level mappings — LTD

Pulled from config/plantypeFiles/ltDisability.php.

Plansight LTD fields   7 fields   configured in config/plantypeFiles/ltDisability.php
— / —
Plansight fieldEN field path
Schedule of Benefits
Elimination Period
eliminationPeriod
benefits.details.waitingPeriodDays
Benefit Duration
benefitDuration
benefits.details.benefitDuration
Other
Benefit Schedule
benefitSchedule-X
benefits.percentEarningsBenefit.benefitPercentEarnings
Maximum Monthly Benefit
maximumMonthlyBenefit-X
benefits.percentEarningsBenefit.maximumBenefit
Minimum Monthly Benefit
minimumMonthlyBenefit-X
benefits.percentEarningsBenefit.minimumBenefit
Rates
Rate Type
benefitType
aso: configuration.isSelfFunded
Rate Structure
rateStructure
composite: rates.compositeRate.rate · ageBanded: rates.ageBasedRates.rates

Voluntary & worksite lines (not currently mapped)

EN's v3 notification spec exposes nine plan endpoints Plansight doesn't push to today — accident, cancer, hospital indemnity, AD&D, critical illness, and the four voluntary lines. Brokers re-key these by hand after quoting. The table below summarizes the surface area available; the per-field detail (paths, types, required flags, descriptions) lives in the generated reference fragment and the audit workbook.

EN endpoint Schema (links to per-plan JSON) Fields Required
POST /plans/criticalillness/notificationCriticalIllnessPlan2692
POST /plans/voluntarylife/notificationVoluntaryLifePlan2282
POST /plans/voluntaryadandd/notificationVoluntaryADAndDPlan2122
POST /plans/adandd/notificationGroupADAndDPlan1272
POST /plans/voluntaryshorttermdisability/notificationVoluntaryShortTermDisabilityPlan1082
POST /plans/voluntarylongtermdisability/notificationVoluntaryLongTermDisabilityPlan1052
POST /plans/hospitalindemnity/notificationHospitalIndemnityPlan822
POST /plans/accident/notificationAccidentPlan802
POST /plans/cancer/notificationCancerPlan802
Total surface area not used today:1,29118

Each plan body has a similar shape: configuration + planDependencies + eligibilityOptions + options + rates + contributions. The plan-specific field counts above include all nested leaf properties across that whole tree. Required fields are minimal — every endpoint requires only planIdentifier + configuration.startDate. EN doesn't require us to populate every available field; we choose which ones map to Plansight quote columns.

Full per-field reference (every field with type, required flag, description, and enum values) lives in source/employee-navigator/swagger-extracts/voluntary_fragment.html · full coverage workbook at EN_API_Coverage_2026-05-10.xlsx (one sheet per plan + summary). Both regenerate from the swaggers via scripts/extract-en-fields.py.

Cross-cutting observations

Patterns across multiple benefit lines

Gap analysis

What EN exposes that we don't read — and what's worth fixing

Eight gaps came out of comparing the EN OpenAPI spec (Quote Census v1, 2024-08-21) against integrations/EmployeeNavigator/ in Plansight APP commit 9d1afd54d5df. Each is a fixable change rather than a structural rewrite.

1

Dependent tobacco status uses the parent's value, not the dependent's own

Likely bug

Problem. EN exposes a per-dependent tobaccoUseInformation.useTobacco field. Controller.php:486 assigns the parent employee's tobacco value to every dependent instead of reading each dependent's own value.

Fix. Read employees[].dependents[].demographics.tobaccoUseInformation.useTobacco per dependent. Spouse-tobacco rates differ from employee-tobacco rates for many carriers, so this matters.

Cursor prompt

In integrations/EmployeeNavigator/Controller.php around line 486, replace the dependent tobacco assignment so it reads $dependent['demographics']['tobaccoUseInformation']['useTobacco'] for each dependent rather than copying from the parent employee. Confirm by adding a fixture where parent and spouse have different tobacco values and verifying the census record reflects the dependent's own value.

2

Census import filters out everything except Medical

Asymmetry

Problem. Controller.php:424 hardcodes $useTypes = ['Medical']. Even though we successfully push Dental, Vision, STD, LTD, and Group Life plans to EN, when we pull census back, we silently drop every enrollment for those same lines. Brokers see employees from EN but not their dental/vision/life/disability elections.

Fix. Expand $useTypes to match the push side (Medical, Dental, Vision, STD, LTD, Group Life). The benefit-type loop and plan-storage logic in lines 494-549 should already handle additional types — verify by tracing each enrollment branch.

Cursor prompt

In Controller.php:424, change the hardcoded useTypes array from ['Medical'] to the full set we push: ['Medical', 'Dental', 'Vision', 'ShortTermDisability', 'LongTermDisability', 'Life']. Confirm that the downstream code at lines 494-549 stores enrollments per benefit type without conflating them, and that the existing tests in tests/Feature/EmployeeNavigator/Operations/ still pass.

3

Org structure (class, division, department) is dropped on the floor

Capability gap

Problem. EN exposes employees[].companyStructureMapping with eligibilityClass, division, department, businessUnit, and officeLocation. None of these are read by Plansight. For a multi-class employer (different contributions / waiting periods / plan eligibility per class), this means we can't honor the broker's class-based logic.

Fix (medium lift). Capture eligibilityClass on the census record at minimum. Treat it as a typed enum — surface in the RFP UI and let the broker map EN classes to Plansight rate classes. Division/department can be string fields for display.

4

Hire date, job title, work state, and salary are exposed but not stored

Capability gap

Problem. EN sends employmentInformation.benefitHireDate, jobTitle, benefitWorkState, and employmentCompensation.annualBenefitSalary. None are read in the census import.

What we'd gain. Hire date drives waiting-period logic and ACA eligibility — without it, brokers manually enter dates we already have. Salary enables salary-based plan modeling (multiple-of-earnings life, salary-banded disability). Work state matters for situs-state rating on multi-state employers.

5

Coverage tier from EN is ignored

Note

Problem. EN sends benefitTier per enrollment (EmployeePlusFamily, EmployeePlusSpouse, etc.). Plansight ignores it and re-derives tier from dependent count. For most cases the answer matches, but EN's value is the source of truth for what the employee actually elected — re-deriving can disagree (e.g., if an employee has dependents but waived spouse coverage).

Fix. Store EN's benefitTier as the authoritative tier on the census enrollment. Use Plansight-derived tier as a sanity check, log when they differ.

6

Effective and termination dates are dropped

Note

Problem. EN sends employeeBenefit.effectiveDate + terminationDate (and dependent equivalents). Plansight stores neither. For renewal pulls or mid-year changes, we can't tell from the census whether someone is actively enrolled or aged off.

Fix. Capture both dates on the census enrollment. Treat enrollments where terminationDate < today as inactive.

7

Nine plan endpoints exposed by EN that Plansight doesn't push to

Capability gap

Problem. EN's v3 notification spec exposes 16 plan-notification endpoints. Plansight's QuoteNotification.php (line 87–108) handles only 6 — Medical, Dental, Vision, Life, STD, LTD. The remaining 9 endpoints are valid push targets that Plansight ignores entirely; for these benefit lines the broker has to re-key everything in EN by hand after quoting in Plansight.

What's available. Counts come from scripts/extract-en-fields.py walking the v3 swagger; "fields" is the recursive count of leaf-level properties across the request body schema (the surface area we'd be choosing what to map from):

EN endpointSchemaFields available
/plans/criticalillness/notificationCriticalIllnessPlan269
/plans/voluntarylife/notificationVoluntaryLifePlan228
/plans/voluntaryadandd/notificationVoluntaryADAndDPlan212
/plans/adandd/notificationGroupADAndDPlan127
/plans/voluntaryshorttermdisability/notificationVoluntaryShortTermDisabilityPlan108
/plans/voluntarylongtermdisability/notificationVoluntaryLongTermDisabilityPlan105
/plans/hospitalindemnity/notificationHospitalIndemnityPlan82
/plans/accident/notificationAccidentPlan80
/plans/cancer/notificationCancerPlan80
Total surface area not currently used:1,291 fields

Phasing. Adding all nine at once is unnecessary. The ones brokers feel first are typically Voluntary Life, AD&D, Critical Illness, and Accident — those would unlock the most quoting → enrollment automation. Hospital Indemnity and Cancer are lower-volume; Voluntary STD/LTD overlap conceptually with the existing STD/LTD push.

Cursor prompt

Add benefit-type branches in integrations/EmployeeNavigator/Operations/QuoteNotification.php:85-108 (the per-line case statement) and a matching entry in the $enUrlPathMap at line 762 for each new line. Start with Voluntary Life (insuranceType='voluntaryLife'voluntarylife) and AD&D (insuranceType='adAndD'adandd); each needs its own pushDataMap entry mapping Plansight quote columns to the EN schema's options/rates/contributions paths. Schema field detail per endpoint: source/employee-navigator/swagger-extracts/{Schema}.json. Full long-form spec with phased rollout: cursor-prompts/en-add-voluntary-lines.md.

8

OpenAPI spec drift — resolved with the v3 spec drop

Resolved

What changed. Three current OpenAPI 3.0.1 specs were dropped into Employee Navigator Swagger/ on 2026-05-10:

  • QuotingCompanyBenefitsNotificationApi_v3 (4).json — the push side. 16 plan-notification endpoints, 275 schema definitions. This is the document the previous audit was missing — the v1 census spec only described one endpoint.
  • QuotingCompanyIntegrationRequestApi_v3_1 (2).json — the company-integration handshake (3 endpoints). Confirms current v3.1 is what Plansight calls.
  • QuoteCensusApi v1.json — refreshed census spec (1 endpoint). Same shape as the August 2024 file; no breaking changes detected.

Audit script scripts/extract-en-fields.py walks all three specs and emits a coverage workbook (source/employee-navigator/EN_API_Coverage_<date>.xlsx) plus per-plan JSON dumps in source/employee-navigator/swagger-extracts/. Re-run after each refresh.

Still TBD. The example census response includes benefitEligibility.isEligible, period, and decision fields that still aren't in the formal OpenAPI schema. Plansight reads decision == 'Elected' — if EN tightens the schema, we'd silently break. Worth one email to EN partner support to either add these to the spec or confirm they'll stay forever.

9

Potential capability — pull plan data from EN to seed "current plans"

Exploration

The idea (not built). Today, when a broker starts a renewal RFP in Plansight, they have to either upload current-plan PDFs (which Plan Facts then parses) or hand-key the existing plan design. If EN already holds the same employer's current plan data, we may be able to pull plans + plan-design + current rates from EN and pre-populate the "current plans" side of the renewal automatically — bypassing the PDF dance for employers that are already on EN.

What's documented today. Updated 2026-05-10: we now have all three current EN specs on file (Benefits Notification v3, Integration Request v3.1, Quote Census v1). All three are push-only or one-shot pull — none of them expose GET plans or GET rates by company. So this gap is still open: pulling existing plan design + current rates from EN requires a partner API surface we still haven't seen. The shape we'd need would be roughly the inverse of the v3 notification request (read instead of write).

If feasible, what we'd gain. A "Connect EN → import current plans" button on renewal kickoff that eliminates 80% of the data-entry work for any employer already on EN. Better-than-Plan-Facts accuracy for those employers, since EN's plan data is the source of truth for what's actually being administered.

Next step

Ask EN partner support for the partner API spec covering plan/group/benefit administration (not just Quote Census). Confirm endpoints exist for: list plans by company, get plan-design detail, get current rates by tier. If yes, scope a new integrations/EmployeeNavigator/Operations/ImportCurrentPlans.php that mirrors the QuoteCensus token + auth pattern.

10

Push the new Type I–IV dental benefit roll-ups under Schedule of Benefits

Capability gap

What changed. Plansight's dental plantype now has category-level summary fields under Schedule of Benefits: BenefitSchedPreventiveI (Type I), BenefitSchedBasicII (Type II), Type III (Major), and Type IV (Orthodontia). These are new — before, we only had the individual procedure-level fields (Exams, Cleanings, Basic Filling, Crowns, Dentures, Implants, Periodontics, Orthodontics, etc.), which already flow into EN's service-type fields (OralExaminations, CleaningsAdult/CleaningsChildren, Fillings, Crowns, etc.). Brokers were already filling those individual benefits in — they just rolled up implicitly under each category.

The gap. The new Type I–IV summary fields in config/plantypeFiles/dental.php aren't yet wired into the EN push the way the individual procedures are. Today their integrations.employeeNavigator config either points back at one of the same procedure-level EN attributes (e.g. BenefitSchedPreventiveI → OralExaminations) or is missing entirely (Type III, Type IV). That means the category-level value the broker enters at the top of the dental Schedule of Benefits doesn't actually land in EN as a Schedule-of-Benefits roll-up — it overwrites a single procedure on EN's side, or gets dropped.

What to do. Update the dental EN integration so each Type I–IV field pushes to the correct EN Schedule of Benefits roll-up rather than overwriting individual procedure values. Confirm the corresponding EN attribute names with the broader partner API spec (Edie's pending request) before wiring — we want the roll-ups to coexist cleanly with the individual procedure fields, not collide with them.

Cursor prompt

In config/plantypeFiles/dental.php, audit the integrations.employeeNavigator.attribute values on BenefitSchedPreventiveI, BenefitSchedBasicII, the missing Type III field (Major), and Type IV (Orthodontia) under Schedule Of Benefits. They should map to EN's category-level Schedule-of-Benefits attributes, not the individual procedure attributes already used by Exams, Cleanings, BasicFilling, Crowns, etc. (which live under the per-procedure Dental Services section). Verify EN attribute names with the partner API spec before wiring.

11

Archive every census pull as a snapshot in the group's documents (S3)

Quick win

The opportunity. Every time a broker pulls an EN census today, the raw response is parsed into people rows and then garbage-collected. There's no persistent record of "what EN said the census was on date X" — if an enrollment dispute comes up later, the broker has to re-pull and hope nothing changed in the interim.

The fix. After each successful census pull, drop two snapshot files into a per-group EN Census Snapshots folder under the group's documents (already on S3 via App\Helpers\S3GroupFile): the raw JSON response and a flat CSV of all employees + dependents. Filename pattern: EN_Census_<YYYY-MM-DD>_<HHMM>.{json,csv}. Side-effect of the existing pull, wrapped in try/catch so a snapshot failure can never break the broker's census pull.

Why it's a quick win. ~50 lines, one file, one commit. No schema changes. No UX to design. No migration. Reuses existing S3GroupFile + GroupFile infrastructure. Compliance-grade audit trail with effectively zero risk surface.

Cursor prompt

After the EN census pull succeeds in integrations/EmployeeNavigator/Controller.php (~line 440), persist two snapshot files (EN_Census_<YYYY-MM-DD>_<HHMM>.json + .csv) into a per-group "EN Census Snapshots" folder via App\Helpers\S3GroupFile. Auto-create the folder on first pull. Wrap the snapshot call in try/catch so a snapshot failure never breaks the census pull itself. Mirror the existing GroupFileCopyJob.php:108 pattern — Storage::disk('s3_groupFiles')->put(path, body, [ContentType]) + manual GroupFile::create(). Full long-form spec, CSV column list, tests, and rollback: cursor-prompts/en-archive-census-snapshot.md.

12

Pull LTD / STD volume drivers (salary, class, hire date) from EN census

Capability gap

The opportunity. Plansight's small-group medical census pull from EN was the original integration win — we get accurate roster + family structure for community-rated quoting. EN's quote-census response also includes employmentCompensation.annualBenefitSalary, employmentInformation.benefitHireDate / jobTitle / benefitWorkState, and companyStructureMapping.eligibilityClass / division / department / businessUnit / officeLocation — exactly the inputs LTD/STD volume calculations need. Plansight's ingest at Controller.php:466 drops every one of those fields. Brokers are uploading a separate volume CSV for LTD/STD when EN already has the data.

What we'd gain. One-click pull-and-go for LTD/STD volume on any group already on EN. No CSV upload step. For renewals, we also get the prior-year approvedBenefitAmount per employee, which is the volume baseline the renewal calc wants.

What it touches. Two halves: (1) the EN ingest in Controller.php grows new fields per employee row; (2) the existing LTD/STD volume builder in Plansight (engineering needs to confirm the exact path — likely a model under app/Models/ + a controller under app/Http/Controllers/Rest/App/) consumes those new fields the same way it consumes CSV-uploaded ones. The two paths converge into one volume model.

Why it pairs well with #11. Same code path. Both are additions to the existing successful census-pull flow. A single sprint can ship both — #11 alone, #12 alone, or paired.

Cursor prompt

Extend the EN census transform in integrations/EmployeeNavigator/Controller.php (~line 457-466) to populate annualSalary, salaryEffectiveDate, benefitHireDate, jobTitle, workState, eligibilityClass, division, department, businessUnit, officeLocation, and (for renewals) approvedDisabilityBenefit[] on each employee row. Then route those fields into Plansight's existing LTD/STD volume builder so brokers don't need to upload a separate volume CSV when EN has the data. Locate the existing volume uploader's model + controller before writing — the new EN-sourced rows should converge into the same model. Tolerate missing salary (don't block the pull). Full long-form spec, the four "confirm before writing code" questions, and tests: cursor-prompts/en-pull-ltd-std-volumes.md.

The bigger pattern

Most of the gaps are EN sends data, Plansight ignores it. That's the definition of a one-way valve — we built the integration to push our quote data out and pull the bare minimum to identify employees on the way back in. The pieces of EN data that would most help our quoting (class, salary, work state, hire date, coverage tier) are exactly the ones we drop.

Recommendation: tackle gaps #1 and #2 first — they're small, well-scoped, and unblock real broker pain (tobacco accuracy + showing all enrollments after a pull). Treat #3-#7 as a separate quarter since they require RFP / quote schema changes. #8 is now resolved (v3 spec is on file). Gap #9 (current-plan import) is the big strategic prize — turns EN from a one-way push into a renewal accelerator. Gap #10 (dental Type I–IV roll-ups) is a small, well-scoped fix that pairs naturally with Edie's pending API spec. Gap #11 (snapshot archive) is the easiest ship of the lot — one commit, one file, ~50 lines, immediate audit-trail value. Gap #12 (LTD/STD volumes) is the highest-direct-revenue lift — pulls a quoting-step that currently requires a CSV upload into the same one-click pull brokers already love for medical census. #11 and #12 share a code path; ship them together if engineering capacity allows.

Things to verify

Items worth a developer's review

Surface-level findings from a static read of the integration code. None block production; some are worth a quick verify next time the EN code is touched.

🔍

Census field name: demographics.firstInitialLastName

Used at Controller.php:454 for both employee and dependent. The name suggests "first initial + last name" but is mapped to a full name field downstream. Worth a quick verify against EN API docs.

Verify
📮

ZIP truncation cites Vericred

Controller.php:454–455 truncates ZIP to 5 chars with a comment about "Vericred processing." Now that we're on IDEON, worth confirming this is still needed and updating the comment.

Stale comment
🚬

Tobacco copies from employee to dependent

If a dependent's tobacco status is missing, Controller.php:465–486 copies the employee's value. Reasonable default, but may not reflect actual dependent tobacco use — worth flagging in the UI when this fallback fires.

Verify
⚠️

Census callback may skip full flow

Line 374 has 'onlyRedirect' => true which skips the actual QuoteCensus operation execution on callback (line 68). Suggests census pull's auth callback may not complete the census fetch in the same hop. Worth tracing once.

Verify
🔁

No retry on census / status checks

Unlike the IDEON integration which has exponential backoff, census and status queries have no retry logic. A transient EN failure shows the user an error rather than retrying. Low priority unless EN reliability becomes an issue.

Note
📋

Limited error context surfaced

Quote push errors aggregate but only surface title and errors from EN's ProblemDetails (Controller.php:292–301). Some EN-specific error contexts (e.g., field-level validation) may be obscured.

Note
Resources

Help center articles & training videos

External documentation that explains the broker-side workflow. Use these alongside this audit page when onboarding new team members or troubleshooting customer questions.

Help center articles

Enabling and using the Employee Navigator integration

Article 17379 · End-to-end broker setup walkthrough

Help →

Plansight ↔ Employee Navigator plan data sync guide

Article 17402 · What flows over and how it maps

Help →

Connect employers (across BenefitPoint and Employee Navigator)

Article 18773 · Cross-integration linking workflow

Help →

Master training videos

Activate Employee Navigator Integration

18 seconds · Wistia · turn the integration on for an employer

Video →

Connect Employer to Employee Navigator

90 seconds · Wistia · the linking handshake (push, link, status check)

Video →

Push Renewal Plans to Employee Navigator

2 minutes · Wistia · post-elections plan push, EN's renewal-vs-new logic

Video →

Push Plans from Plansight to Navigator

2 minutes · Wistia · longer-form push walkthrough (medical, dental, vision)

Video →

Employee Navigator Census Import

2 minutes · Wistia · pulling census directly into a community-rated RFP

Video →