All benefits

Plan Facts Parsing Analysis · v1

Medical

Multi-network parse (1–3 networks) detecting plan type, HDHP status, deductibles, OOP maximums, specialist referral rules, ~25 service-level copays, drug tiers (up to 6), and four-tier composite rates.

Endpoint
POST /medical
Parse method
parseQuoteMedical()
Source range
PlanfactService.php:3984–4507 (~524 lines)
Source pull
plansight - Bit Bucket Pull 4.26

Bottom line

Largest parser in the file. Six small items worth a verify.

The multi-network detection, copay parsing, and drug-tier handling are sophisticated. The items below are mostly defensive: enum case sensitivity, a 6-tier hard cap on drug tiers, a network-sorting heuristic that's fragile at 4+ networks, and a non-obvious "[service]Other" form contract.

Per-tier / per-network handling

Multi-network architecture, not multi-class. The parser detects 1, 2, or 3 networks and stores fields under inNetwork / outNetwork prefixes:

No employee-class concept. Rates are coverage-tier (Employee Only, +Spouse, +Child, +Children, Family) — line 4487.

What's mapped (high-level)

Plan Facts fieldQuote fieldNotes
Plan NamenameDirect (line 3988)
Plan Type (HMO/PPO/EPO/POS/Indemnity/RBP)medicalPlanType⚠ Case-sensitive enum (line 3991)
HDHP (yes/no)hsaNormalized Yes/No (line 4002)
NetworksinNetworkNetwork+ multi-network sort logic (line 4014)
Overall Deductible.[Network].Individual / FamilyinNetworkDeductibleIndividual, …Family, outNetworkDeductibleIndividual, …FamilyNumeric, $ stripped (lines 4072–4157)
Overall Deductible.[Network].Deductible TypeinNetworkDeductibleType, outNetworkDeductibleTypeEmbedded / Aggregate (case-insensitive)
Overall Deductible.[Network].Deductible FrequencyinNetworkDeductibleOopAccumulation, outNetworkDeductibleOopAccumulation"calendar year" / "plan year"
Out-of-Pocket Maximum.[Network].Individual / FamilyinNetworkOOPIndividual, …Family, outNetworkOOPIndividual, …FamilyNumeric (lines 4196–4253)
Specialist Referral Rule.[Network].Specialist Referral RuleinNetworkSpecialistReferral, outNetworkSpecialistReferralYes / No (line 4256)
[Service from copayMap].[Network].Payment (~25 services)inNetwork[Service] + outNetwork[Service] + Type / Other suffixesVia parseCopay() — handles $, %, "Up to $", "Not Covered", ranges (lines 4293–4387)
[Service].[Network].Deductible AppliesinNetwork[Service]Ded, outNetwork[Service]Ded"No" → ND; "Yes"/"After Deductible" → AD (line 4379)
Drugs.Drug Tiers (list)RxTier1LabelRxTier6Label⚠ Capped at 6 tiers (line 4484)
Drugs.[TierName].[Network].PaymentinNetworkRxTier{1..6}, outNetworkRxTier{1..6}Via parseCopay() (line 4413)
Drugs.[TierName].[Network].Deductible AppliesinNetworkRxTier{1..6}Ded, outNetworkRxTier{1..6}Ded"nd" / "ad" (line 4458)
Rates.[Coverage Tier]rateEmployeeOnly, rateEmployeeSpouse, rateEmployeeChild, rateEmployeeChildren, rateEmployeeFamilyNumeric, $ + comma stripped (lines 4487–4504)

Plan-level / not mapped

Potentially mapped incorrectly

Plan Type enum is case-sensitive PlanfactService.php:3991–3999

Plan Type is matched case-sensitively. If Plan Facts returns "hmo" or "Hmo" instead of "HMO", the parse fails silently and medicalPlanType is left empty. Several other enums in this method are normalized with strtolower() — this one isn't.

Cursor prompt
In app/Services/PlanfactService.php at lines 3991–3999, inside parseQuoteMedical(), the Plan Type switch is case-sensitive. Other enum mappings in the same method use strtolower() to normalize before matching.

Please:
1. Show me the Plan Type switch.
2. Add strtolower() to the input value before the switch (matching the pattern used elsewhere in this parser).
3. Make sure the case statements use lowercase.
4. Show me the proposed change.
Network sorting heuristic fragile at 4+ networks PlanfactService.php:4026–4067

When a Plan Facts response has more than 2 networks, the parser uses a deductible-based sort heuristic that only ever extracts 3 specific networks (index 0, 1, last). Middle networks (4+) are silently dropped. Two networks with identical deductibles can also be mis-labeled as "in-network" vs "out-of-network".

Cursor prompt
In app/Services/PlanfactService.php at lines 4026–4067, inside parseQuoteMedical(), the multi-network handling uses a deductible-based sort heuristic when there are more than 2 networks.

The current logic:
- Builds an associative array from networks[0], networks[1], and networks[count-1] only.
- Sorts by deductible.

Issues:
- 4+ networks: middle networks are silently ignored.
- Networks with equal deductibles: ordering is undefined.

Please:
1. Show me the current logic.
2. Recommend an improvement: either explicitly limit to 3 networks at parse time with a log warning when more are encountered, OR find a more reliable signal than deductible (e.g., network name pattern matching).
3. Don't change anything yet — show the proposed approach.
Non-obvious "[service]Other" form contract PlanfactService.php:4313–4348

When a 3+ network plan has two in-network copays for the same service, the parser combines them with a "/" separator into a field named [service]Other and sets [service]Type = "Other". The base [service] field is left unset. Form code has to know to read [service]Other when [service]Type === "Other" — and the contract isn't documented in code comments.

Cursor prompt
In app/Services/PlanfactService.php at lines 4313–4348, inside parseQuoteMedical(), there's a non-obvious contract for multi-network copay combining:
- When type is "Other", the actual value is in [service]Other (not [service]).
- Form / display code must check Type first to know which field to read.

Please:
1. Show me the current logic.
2. Add an inline comment block (4–6 lines) explaining the contract and pointing at the frontend file that consumes it.
3. Don't change behavior — just document.
Drug tier hard cap at 6 PlanfactService.php:4484

Drug tier parsing hard-stops at 6 tiers (if ($tierNum > 6) break;). If a carrier returns a 7th tier (specialty injectables, biosimilars, etc.), it's silently dropped. Worth confirming whether 6 is enough in practice.

Cursor prompt
In app/Services/PlanfactService.php at line 4484, inside parseQuoteMedical(), drug tier parsing hard-stops at 6 tiers. Tiers beyond the 6th are silently dropped.

Please:
1. Show me the line.
2. Check the Quote model and form template — is RxTier defined for tiers 1–6 only, or higher?
3. If the limit is real (form only supports 6), add a log warning when more tiers are encountered, so we know if this becomes an issue.
4. If higher tiers are supported on the form but not in the parser, raise the parser's limit.
$0 deductible may be silently dropped PlanfactService.php:4084, 4107, etc.

Several extraction blocks use if ($ded) to check for presence — which is falsy when the value is "0". A $0 deductible (valid for some plans) might be skipped, leaving the Quote field empty rather than recording $0.

Cursor prompt
In app/Services/PlanfactService.php inside parseQuoteMedical(), several deductible extractions use truthy checks like:
if ($ded) { ... }

PHP treats "0" as falsy, so a $0 deductible value would be skipped. $0 deductibles are valid (e.g., HSA-paired or first-dollar plans).

Please:
1. Find every instance of this pattern in parseQuoteMedical() and the helper functions it calls (parseMoney, parseCopay).
2. Replace truthy checks with explicit "is not null and is not empty string" checks where the field semantically allows "0" as a valid value.
3. Show me the affected lines and the proposed change.
Hardcoded network names in Vision-style downstream parsers PlanfactService.php:4014

Medical's network detection is robust (it reads the carrier's network names), but several downstream blocks build keys like 'Overall Deductible.' . $networkName . '...'. If the carrier uses unusual punctuation or whitespace in network names, those concatenations could fail. Mostly defensive — worth a one-time audit of test responses.

Cursor prompt
In app/Services/PlanfactService.php inside parseQuoteMedical() (lines 4072 onward), many lookups concatenate the network name into a Plan Facts path key:
$value = $data['Overall Deductible'][$networkName]['Overall Individual Deductible'] ?? null;

If a carrier's network name contains unusual whitespace or characters that don't match what's in the response, the lookup silently fails.

Please:
1. Find all such concatenations in this parser.
2. Wrap them in a helper that, on lookup failure, logs the network name being searched and the available keys at that level. This makes future failures debuggable instead of silent.
3. Show me the proposed helper and an example of how to apply it.

Manual-entry-only fields

Recommendations

  1. Normalize Plan Type case sensitivity 15 minutes

    Apply strtolower() consistent with other enums in this parser.

  2. Document the [service]Other contract 15 minutes

    Inline comment block. No behavior change.

  3. Add log warnings for 4+ networks and 7+ drug tiers Half day

    Minor — these become silent data drops today.

  4. Audit truthy-vs-explicit checks for $0 deductibles Half day

    Several spots in this parser drop $0 because of if ($ded). Replace with explicit null-check.

Code references

FileLinesPurpose
app/Services/PlanfactService.php132–146POST /medical endpoint config
app/Services/PlanfactService.php447Dispatch to parseQuoteMedical
app/Services/PlanfactService.php3984–4507Full parser
app/Services/PlanfactService.php3991–3999Plan Type enum (case sensitivity)
app/Services/PlanfactService.php4014–4067Network sorting heuristic
app/Services/PlanfactService.php4293–4387Service-level copay parsing
app/Services/PlanfactService.php4391–4485Drug tier parsing (6-tier cap at line 4484)
app/Services/PlanfactService.php4555–4687parseCopay() helper (regex patterns for $, %, ranges)
app/Services/PlanfactService.php4509–4553parseMoney() helper
app/Services/PlanfactService.php25–79copayMap definition