All benefits

Plan Facts Parsing Analysis · v1

Vision

Fixed 2-network parse with exam, lens, frame, and contact copays / allowances, frequencies, and four-tier composite rates.

Endpoint
POST /vision
Parse method
parseQuoteVision()
Source range
PlanfactService.php:2313–2443 (~131 lines — smallest of the parsers)
Source pull
plansight - Bit Bucket Pull 4.26

Bottom line

Smallest parser, but several small fragility items.

The parser hardcodes exactly two networks ("In-Network" and "Out-of-Network"), has a dual-key mapping that may drop information, and is missing out-of-network entries for two text fields (Conventional Contacts Discount Beyond Allowance, Frames Discount Beyond Allowance).

Per-tier / per-network handling

Hard-coded 2 networks named exactly "In-Network" and "Out-of-Network" at line 2321. No detection or sort logic. No employee-class concept.

What's mapped

Plan Facts fieldQuote fieldNotes
Plan NamenameDirect (line 2317)
Exams.Exam Copay (in-network)inNetwork1EyeExams + TypeparseCopay (line 2332)
Exams.Exam Allowance (out-of-network)outNetworkEyeExams + TypeparseCopay
Lenses.Single / Bifocal / Trifocal / Standard ProgressiveinNetwork1SingleVisionLenses, …BifocalLenses, etc."Standard Progressive" → BasicProvisionLenses (line 67)
Frames.Frames AllowanceinNetwork1FramesCovered, outNetworkFramesCoveredparseCopay; pay=Allowance flag
Lenses.Frames + Lenses MaterialsinNetwork1Materials⚠ Dual-key with SingleVisionLenses (line 64)
Exams.Exam FrequencyinNetwork1ExamFrequency, outNetworkExamFrequencyDirect text
Lenses.Lenses FrequencyinNetwork1LensesFrequency, outNetworkLensesFrequencyDirect text
Lenses.Premium Progressive Lenses Copay/AllowanceinNetwork1PremiumProgressiveLenses, outNetworkPremiumProgressiveLensesText only (line 93)
Contacts.Contacts FrequencyinNetwork1ContactsFrequency, outNetworkContactsFrequencyDirect text
Contacts.Conventional Contacts AllowanceinNetwork1ContactsElective, outNetworkContactsElectiveDirect text
Contacts.Conventional Contacts Discount Beyond AllowanceinNetwork1ContactsAboveAllowanceOut-of-network missing
Contacts.Medically Necessary Contacts AllowanceinNetwork1ContactsNecessary, outNetworkContactsNecessaryDirect text
Frames.Frames Discount Beyond AllowanceinNetwork1FramesAdditionalOut-of-network missing
Contacts.Contacts-In-Lieu of GlassesinNetwork1ContactsInLieuOfGlasses, outNetworkContactsInLieuOfGlassesYes / No (case-insensitive, line 2398)
Lasik coverage descriptioninNetwork1LasikDiscount, outNetworkLasikDiscountDirect text (line 2415)
Rates.[Coverage Tier]rateEmployeeOnly, rateEmployeeSpouse, rateEmployeeChildren, rateEmployeeChild, rateEmployeeFamilyNumeric (line 2423)

Plan-level / not mapped

Potentially mapped incorrectly

Single Vision Lenses dual-key mapping PlanfactService.php:64

The copayMap entry maps 'Lenses.Single Vision Lenses Copay' to both 'SingleVisionLenses' and 'Materials'. The parse loop iterates both keys and writes the same value to both Quote fields. If Plan Facts intends to distinguish lens cost from material upcharge, the dual-key mapping drops that distinction.

Cursor prompt
In app/Services/PlanfactService.php at line 64 (the copayMap definition for Vision), the entry is:
'Lenses.Single Vision Lenses Copay' => ['SingleVisionLenses', 'Materials']

The parse loop (around lines 2338–2370 in parseQuoteVision()) iterates over both keys and writes the SAME copay value to both inNetwork1SingleVisionLenses and inNetwork1Materials.

Please:
1. Show me the copayMap entry and the loop.
2. Confirm whether the dual-write is intentional (both fields are meant to hold the same value, perhaps for two different form labels). If yes, add a comment.
3. If not intentional, find the correct Plan Facts source for "Materials" specifically (likely a different key) and split the mapping.
4. Don't change yet — show the analysis.
Out-of-network entries missing for two text fields PlanfactService.php:textMap['vision']['Out-of-Network']

The textMap for Vision Out-of-Network (around lines 101–108) doesn't include entries for "Conventional Contacts Discount Beyond Allowance" or "Frames Discount Beyond Allowance". If Plan Facts returns out-of-network values for those, they're dropped.

Cursor prompt
In app/Services/PlanfactService.php inside the textMap['vision'] definition (around lines 96–108), the In-Network section has entries for:
- "Contacts.Conventional Contacts Discount Beyond Allowance" → inNetwork1ContactsAboveAllowance
- "Frames.Frames Discount Beyond Allowance" → inNetwork1FramesAdditional

But the Out-of-Network section is missing the corresponding entries.

Please:
1. Show me the In-Network and Out-of-Network sections of textMap['vision'].
2. Confirm whether those two fields should have out-of-network mappings (e.g., outNetworkContactsAboveAllowance, outNetworkFramesAdditional). If the Quote model has those fields, add the entries.
3. If the Quote model doesn't have those fields, document why out-of-network is intentionally not captured.
4. Don't change yet — show the analysis.
Yes/No parsing breaks on Y/N or True/False PlanfactService.php:2398–2411

The Contacts-In-Lieu / Lasik fields use strtolower() matching for "yes" / "no". If Plan Facts returns "Y", "N", "true", "false", the value silently drops.

Cursor prompt
In app/Services/PlanfactService.php at lines 2398–2411, inside parseQuoteVision(), the Contacts-In-Lieu and Lasik switches match strtolower("yes") / strtolower("no").

If Plan Facts ever returns "Y", "N", "true", "false", or "included" / "not included", they fail silently.

Please:
1. Show me the switch.
2. Broaden the matcher to handle the common variants: "yes"/"y"/"true"/"included" → Yes; "no"/"n"/"false"/"not included" → No.
3. Add a default branch that logs unexpected values rather than silently dropping.
4. Show me the proposed change.
Hardcoded network names PlanfactService.php:2321

Vision always assumes exactly two networks named "In-Network" and "Out-of-Network". If a carrier ever uses different names ("Provider Network" / "Non-Participating", etc.), the copayMap keys won't match and data is silently dropped.

Cursor prompt
In app/Services/PlanfactService.php at line 2321, inside parseQuoteVision(), the parser hardcodes two network names:
$inNetwork = "In-Network";
$outNetwork = "Out-of-Network";

If a carrier uses different network names (e.g., "Provider Network", "Non-Participating"), every subsequent lookup silently fails and the Quote ends up empty.

Please:
1. Show me the assignment.
2. Replace it with detection: read the actual network names from the response (e.g., $data['Vision']['Networks'] or wherever Plan Facts lists them) and use those.
3. If the carrier really does have non-standard names, the parse should still work — or fall back to the hardcoded names with a log warning.
4. Show me the proposed change.
Plan Name has no fallback PlanfactService.php:2317

If Plan Facts omits "Plan Name", the Quote's name field is empty. Could fall back to the carrier name to avoid blank labels in the UI.

Cursor prompt
In app/Services/PlanfactService.php at line 2317, inside parseQuoteVision(), the Quote name field is set from Plan Facts' "Plan Name" with no fallback.

If Plan Facts omits "Plan Name" (or it's empty), the Quote ends up nameless.

Please:
1. Show me the current assignment.
2. Add a fallback: if "Plan Name" is missing or empty, use the carrier name + "Vision Plan" or similar.
3. Show me the proposed change.

Manual-entry-only fields

Recommendations

  1. Resolve dual-key Single Vision Lenses → Materials Hour

    Either document the intentional dual-write or split into two distinct sources.

  2. Add out-of-network entries to textMap 15 minutes

    Match the in-network coverage so Plan Facts data isn't dropped.

  3. Broaden Yes/No matchers 15 minutes

    Handle common variants; log unexpected values.

  4. Detect network names instead of hardcoding Hour

    Or document the assumption and add a fallback / warning.

Code references

FileLinesPurpose
app/Services/PlanfactService.php162–172POST /vision endpoint config
app/Services/PlanfactService.php451Dispatch to parseQuoteVision
app/Services/PlanfactService.php2313–2443Full parser
app/Services/PlanfactService.php2321Hardcoded network names
app/Services/PlanfactService.php2332–2374copayMap iteration / parseCopay
app/Services/PlanfactService.php2398–2411Yes/No matchers
app/Services/PlanfactService.php61–78copayMap['vision'] + textMap['vision']