All benefits

Plan Facts Parsing Analysis · v1

Dental

Multi-network parse (1–3 networks) with annual maximum, deductible, OOP, rollover account, service-level coinsurance (preventive / basic / major / orthodontic), waiting periods, and frequencies.

Endpoint
POST /dental
Parse method
parseQuoteDental()
Source range
PlanfactService.php:2504–3059 (~556 lines)
Source pull
plansight - Bit Bucket Pull 4.26

Bottom line

Several small fragility items, plus a dual-storage rollover.

The parser is comprehensive. Items below are minor: a brittle deductible-period regex (matches single "c"), a dual-stored rollover field with comments suggesting tech debt, a frequency regex with limited coverage, and hardcoded "Excess Responsibility" defaults.

Per-tier / per-network handling

Multi-network: detects 1, 2, or 3 networks; inNetworkOptions = 1 or 2 (line 2511). No employee-class concept. Rates by coverage tier (Employee Only / +Spouse / +Children / +Child / Family).

What's mapped (high-level)

Plan Facts fieldQuote fieldNotes
Plan NamenameDirect (line 2508)
NetworksinNetwork1NetworkName, inNetwork2NetworkName, inNetworkOptionsLines 2511–2548
UCR (%)outNetworkClaimPaymentBasis + TypeIf numeric → percentage (line 2559)
Annual Maximum Amount.[Network]inNetwork{1,2}AnnualMaxIndividual, outNetworkAnnualMaxIndividual$0 → "Unlimited" (line 2592)
Deductible / Max (other copay services).[Network]inNetwork{1,2}DeductibleIndividual etc.via parseMoney
Deductible PeriodinNetwork{1,2}DeductibleAccumulation, outNetworkDeductibleAccumulation⚠ Brittle regex (line 2645)
Annual Maximum Rollover.Rollover AmountinNetwork{1,2}RolloverMaximum AND …RolloverVer2 + Type⚠ Dual-stored (line 2656)
Annual Maximum Rollover.Rollover DescriptioninNetwork{1,2}RolloverDescriptionUI removed 2025-05-30; data preserved
Annual Maximum Rollover.Rollover Account MaximuminNetwork{1,2}RolloverAccountLimit + Type(line 2690)
Annual Maximum Rollover.Threshold AmountinNetwork{1,2}RolloverThreshold + Type(line 2707)
Preventive / Basic / Major / Orthodontic Services.Coinsurance.[Network]inNetwork{1,2}Exams, …BenefitSchedPreventiveI, etc.via parseCopay (line 2810)
Service.Deductible AppliesinNetwork{1,2}[Service]Ded"yes" → AD; "no" → ND (line 2779)
Orthodontic Services.Child Eligibility Age / Adult EligibilityinNetwork{1,2}OrthodonticsAge, inNetwork{1,2}OrthodonticsAdult, outNetworkOrthodonticsAge(line 2841)
Waiting Period.Late Entrants (Ortho / Preventive / Basic / Major)inNetwork{1,2}{Wait fields} (legacy + new "Months"/"None" enum)Two formats stored; lines 2875–2986
Frequencies.CleaningsinNetwork{1,2}PreventiveFrequency⚠ Limited regex (line 2997)
Rates.[Coverage Tier]rateEmployeeOnly, rateEmployeeSpouse, rateEmployeeChildren, rateEmployeeChild, rateEmployeeFamilyLine 3039

Plan-level / not mapped

Potentially mapped incorrectly

Deductible period regex matches single character "c" PlanfactService.php:2645

The Deductible Period switch maps "c", "calendar year", "ccalendar year" (typo), and "plan year" / "benefit year". The single-character "c" match is too permissive — any string starting with "c" or having a stray "c" might trigger Calendar Year incorrectly.

Cursor prompt
In app/Services/PlanfactService.php at around line 2645, inside parseQuoteDental(), the Deductible Period switch handles unusual values like a single "c" or "ccalendar year". The single-character "c" match is too permissive.

Please:
1. Show me the switch.
2. Replace the single-character "c" case with explicit case-insensitive matching for "calendar year" only.
3. Add a default branch that logs a warning when an unexpected value is encountered, instead of silently mapping unknown strings.
4. Show me the proposed change.
Rollover Amount stored in two field formats simultaneously PlanfactService.php:2656–2676

Both inNetwork{1,2}RolloverMaximum (legacy) and inNetwork{1,2}RolloverVer2 + Type (new) are written for every parse. Comments suggest the old format was kept "in case we want to revert." Worth deciding which is canonical and removing the other.

Cursor prompt
In app/Services/PlanfactService.php at lines 2656–2676, inside parseQuoteDental(), the Rollover Amount is written to two field formats simultaneously:
- inNetwork{1,2}RolloverMaximum (legacy)
- inNetwork{1,2}RolloverVer2 + Type (new)

Comments in the source suggest the dual-write was a reversible migration safety net.

Please:
1. Show me the current dual-write.
2. Search the codebase (form templates, presentation rendering, exports, any Excel job) for which format is actually consumed.
3. If only Ver2 is consumed, recommend removing the legacy write here. If both are still used somewhere, document why with a comment.
4. Don't change anything yet — show the audit results first.
Cleanings frequency regex misses common phrasings PlanfactService.php:2997–3034

The frequency regex matches "N per [period]" but only handles 6 specific period strings. Phrasings like "every 12 months", "twice annually", or "2 per benefit year" may silently fail.

Cursor prompt
In app/Services/PlanfactService.php at lines 2997–3034, inside parseQuoteDental(), the Cleanings frequency regex matches "N per [period]" but only covers 6 specific period strings (calendar year, plan year, 6 Months, etc.).

Common phrasings like "every 12 months", "twice annually", "2 per benefit year" may silently fail to match.

Please:
1. Show me the current regex / switch.
2. Recommend a more flexible matcher that handles "every N months", "N per [period]", "twice/three times per [period]" — or a fallback path that captures the raw string and flags it for review.
3. Show me the proposed change.
Excess Responsibility hardcoded PlanfactService.php:2555–2557

Excess Responsibility is set to "None" for in-network and "Yes" for out-of-network unconditionally — not parsed from Plan Facts. This is correct in practice (balance billing is standard for dental), but the hardcoding is non-obvious. Worth a comment.

Cursor prompt
In app/Services/PlanfactService.php at lines 2555–2557, inside parseQuoteDental(), Excess Responsibility is set unconditionally:
- in-network: "None"
- out-of-network: "Yes"

Not read from Plan Facts.

Please:
1. Show me the assignment.
2. Add a 2-line code comment explaining why these are hardcoded (because dental balance-billing rules are standard and don't need per-plan extraction).
3. No behavior change.
Waiting Period zero / empty handling PlanfactService.php:2875–2900

Missing waiting period is skipped entirely, but an explicit "0" or "" sets Type to "None". Could mask a real data quality issue (e.g., the response is incomplete).

Cursor prompt
In app/Services/PlanfactService.php at lines 2875–2900, inside parseQuoteDental(), waiting period handling distinguishes:
- field absent → skip entirely (no value set on Quote)
- field present with empty / zero value → Type = "None"

The first case might mask incomplete Plan Facts responses.

Please:
1. Show me the current logic.
2. Recommend either:
   (a) treat absent same as zero (always set Type = "None" if no value), or
   (b) log a warning when the field is absent so we know if Plan Facts ever sends incomplete data.
3. Don't change yet — recommend.

Manual-entry-only fields

Recommendations

  1. Tighten Deductible Period regex 15 minutes

    Replace single-char "c" match with explicit case-insensitive "calendar year".

  2. Resolve the dual-stored Rollover fields Hour

    Audit which format is consumed; remove the legacy one if no longer used.

  3. Document the hardcoded Excess Responsibility 5 minutes

    One-line comment.

  4. Improve frequency regex coverage Hour

    Or add a fallback that preserves the raw string and flags it for review.

Code references

FileLinesPurpose
app/Services/PlanfactService.php149–159POST /dental endpoint config
app/Services/PlanfactService.php449Dispatch to parseQuoteDental
app/Services/PlanfactService.php2504–3059Full parser
app/Services/PlanfactService.php2645Deductible Period regex
app/Services/PlanfactService.php2656–2676Dual-stored Rollover
app/Services/PlanfactService.php2997–3034Cleanings frequency regex
app/Services/PlanfactService.php2555–2557Hardcoded Excess Responsibility
app/Services/PlanfactService.php55–60copayMap['dental']