Bowmark AIdocs

Medicare.gov

The US government's own Medicare site — Medicare Advantage, Part D and Medigap plan search with real drug-cost estimates, the Care Compare directory of…

The US government's own Medicare site — Medicare Advantage, Part D and Medigap plan search with real drug-cost estimates, the Care Compare directory of doctors, hospitals, nursing homes, home health, hospice and dialysis providers with CMS quality ratings, the A-to-Z coverage database, and what Medicare itself costs this year. Part D drug-plan search, Medigap plan search (every insurer selling each plan type, with its rating method and any household discount), the doctor-and-clinician directory (specialties, group practice, hospital affiliations by name, and whether they accept Medicare assignment), the nursing-home directory with CMS's full Five-Star record (component ratings, staffing hours, fines, payment denials and Special Focus status), the hospital directory with CMS's overall rating AND the five measure groups behind it (mortality, safety, readmission, patient experience, timely and effective care), and the Medicare cost reference (premiums, deductibles, coinsurance tiers and the Part B/Part D income brackets) are callable now; the other thirteen declared functions are still stubs.

Domain: medicare.gov

Also known as: Medicare, Medicare.gov, medicare.gov, CMS Medicare, Medicare Plan Finder, Medicare Care Compare

Call it directly

bowmark.providers.medicare.searchDrugPlans({ zip, year?, county?, limit? }): Promise<medicareDrugPlanSearch>
bowmark.providers.medicare.getCosts(): Promise<medicareCostsInfo>
bowmark.providers.medicare.findNursingHomes(query: medicareNursingHomeQuery): Promise<medicareNursingHomeSearch>
bowmark.providers.medicare.findDoctors(query: medicareClinicianQuery): Promise<medicareClinicianSearch>
bowmark.providers.medicare.searchMedigapPlans({ zip, planType, county }: medicareMedigapQuery): Promise<medicareMedigapSearch>
bowmark.providers.medicare.findHospitals(query: medicareHospitalQuery): Promise<medicareHospitalSearch>
bowmark.providers.medicare.getPlan({ planType, contractId, planId, segmentId, zip, county, year }: medicareGetPlanQuery): Promise<medicarePlanDetail>

Functions

FunctionWhat it does
searchDrugPlansThe standalone Part D prescription drug plans sold in a ZIP's Part D region, each with its monthly premium, annual deductible and CMS star rating.
getCostsWhat Medicare itself costs this year, read off Medicare.gov's own reference page — the Part A premium and hospital deductible with its per-day coinsurance tiers, the standard Part B premium…
findNursingHomesThe Medicare/Medicaid-certified nursing homes near a place, nearest first, each with CMS's Five-Star ratings AND the three components that compose them (health inspections, staffing,…
findDoctorsThe doctors and clinicians enrolled in Medicare near a place, nearest first, each with their NPI, primary and secondary specialties, credential, medical school and graduation year, the…
searchMedigapPlansThe Medigap (Medicare Supplement) plan types sold in a ZIP's state, each with the insurers selling it, their premium range, their RATING METHOD (attained-age — rises with age; issue-age or…
findHospitalsThe Medicare-registered hospitals near a place, nearest first, each with CMS's overall star rating AND the five measure groups behind it (mortality, safety, readmission, patient experience,…
getPlanThe full detail of one Part D / MA / MAPD / SNP plan, identified by its CMS contract-plan-segment triple (e.g. S5884-103-0 for Part D, Hxxxx-yyyy-0 for Part C). Returns the per-visit…

Types

interface medicareCounty {
  name: string;
  fips: string;
  state: string;
}

interface medicareDrugPlan {
  /** CMS's contract-plan-segment triple, e.g. "S5884-103-0". Stable within a plan
   * year; CMS renumbers across years, so read it with `year`. */
  id: string;
  name: string;
  organization: string;
  contractId: string;
  planId: string;
  segmentId: string;
  /** Dollars per month. A 0 is a real price — several carriers sell a
   * zero-premium basic Part D plan — not a missing value. */
  monthlyPremium: number;
  /** Annual drug deductible in dollars; 0 means none. */
  annualDeductible: number;
  /** CMS's overall star rating (1-5, half steps), or null when CMS publishes
   * none. A null is NOT a bad rating — `starRatingNote` says why it is absent
   * (usually "too new to be rated"). */
  starRating: number | null;
  starRatingNote: string | null;
  lowPerforming: boolean;
  highPerforming: boolean;
  carrierUrl: string | null;
}

interface medicareDrugPlanSearch {
  zip: string;
  /** The county actually searched. */
  county: medicareCounty;
  /** Every county the ZIP touches. More than one is common and does not change a
   * Part D answer — plans are sold by region, and a region never splits a state. */
  countiesConsidered: medicareCounty[];
  year: number;
  /** How many plans Medicare.gov reports before `limit` is applied. */
  total: number;
  plans: medicareDrugPlan[];
  carriers: { name: string; count: number }[];
}

interface medicareCostRow {
  label: string;
  detail: string;
}

interface medicareIrmaaBracket {
  individualIncome: string;
  jointIncome: string;
  /** Literal "Not applicable" for a bracket a separate filer cannot reach — never
   * coerced to null, which would make it indistinguishable from a missing value. */
  marriedSeparateIncome: string;
  /** Part B publishes a flat total premium ("$284.10"); Part D publishes a
   * SURCHARGE added to the caller's own plan premium ("$14.50 + your plan
   * premium"). Kept verbatim on both. */
  youPayEachMonth: string;
}

interface medicarePartCosts {
  rows: medicareCostRow[];
}

interface medicarePartCostsWithIrmaa extends medicarePartCosts {
  /** The six income-bracket rows CMS publishes for this part. Part B and Part D
   * only — Part A, Medicare Advantage and Medigap carry no income adjustment. */
  irmaa: medicareIrmaaBracket[];
}

interface medicareCostsInfo {
  /** The plan year read off the page's own "What you pay in <year>" header. */
  year: number;
  partA: medicarePartCosts;
  partB: medicarePartCostsWithIrmaa;
  medicareAdvantage: medicarePartCosts;
  partD: medicarePartCostsWithIrmaa;
  medigap: medicarePartCosts;
  sourceUrl: string;
}

interface medicareNursingHomeRatings {
  /** 1-5, or null when CMS publishes none. NEVER render a null as a middling
   * rating: a null here skews hard toward the WORST facilities, not the unknown
   * ones — every one of the country's 88 Special Focus Facilities has one. */
  overall: number | null;
  /** CMS's raw footnote code for a null rating (e.g. "18" for a Special Focus
   * Facility, "1" for too-few-cases / newly certified). Deliberately not mapped
   * to text: the PDC "Footnote Crosswalk" dataset is the HOSPITAL code set and
   * its wording for the same numbers is about hospitals. */
  overallFootnote: string | null;
  /** The three components CMS computes `overall` from. They frequently
   * disagree, and `staffing` is the one tied to resident outcomes — a
   * five-star overall on a one-star staffing is a real, common combination, so
   * the rollup alone is a worse answer than the website's. */
  healthInspection: number | null;
  healthInspectionFootnote: string | null;
  staffing: number | null;
  staffingFootnote: string | null;
  qualityMeasures: number | null;
  qualityMeasuresFootnote: string | null;
  longStayQualityMeasures: number | null;
  shortStayQualityMeasures: number | null;
}

interface medicareNursingHomeStaffing {
  totalNurseHoursPerResidentDay: number | null;
  /** Registered-nurse hours per resident per day. The number a family
   * comparing two facilities is usually actually comparing. */
  rnHoursPerResidentDay: number | null;
  lpnHoursPerResidentDay: number | null;
  nurseAideHoursPerResidentDay: number | null;
  /** Weekend coverage, which routinely differs from the weekday figure. */
  rnHoursPerResidentDayWeekend: number | null;
  totalNurseHoursPerResidentDayWeekend: number | null;
  /** Percent as CMS publishes it — 44.8 means 44.8%. */
  totalNursingStaffTurnoverPct: number | null;
  rnTurnoverPct: number | null;
  administratorsDepartedLastYear: number | null;
}

interface medicareNursingHomeEnforcement {
  /** "SFF" (Special Focus Facility — CMS's designation for persistent serious
   * problems), "SFF Candidate", or null for neither. Show it WITH the stars. */
  specialFocusStatus: string | null;
  /** Substantiated abuse cited on a recent inspection. */
  abuseIcon: boolean;
  fines: number | null;
  finesTotalDollars: number | null;
  /** Denials of Medicare/Medicaid payment for new admissions. */
  paymentDenials: number | null;
  totalPenalties: number | null;
  /** True when the last standard health inspection is over two years old — i.e.
   * the inspection ratings are stale, whatever they say. */
  healthInspectionOlderThanTwoYears: boolean;
}

interface medicareNursingHome {
  /** CMS Certification Number — the id every other CMS nursing-home dataset
   * (deficiencies, penalties, ownership, MDS quality measures) joins on. */
  ccn: string;
  name: string;
  legalBusinessName: string | null;
  address: string;
  city: string;
  state: string;
  zip: string;
  phone: string | null;
  county: string | null;
  latitude: number | null;
  longitude: number | null;
  /** Straight-line miles from the search origin, one decimal. */
  distanceMiles: number;
  ownershipType: string | null;
  certifiedBeds: number | null;
  averageResidentsPerDay: number | null;
  /** "Medicare and Medicaid" / "Medicare" / "Medicaid" — which programmes
   * certify it, and therefore who it can admit. */
  providerType: string | null;
  residesInHospital: boolean;
  continuingCareRetirementCommunity: boolean;
  changedOwnershipInLast12Months: boolean;
  chainName: string | null;
  firstCertified: string | null;
  ratings: medicareNursingHomeRatings;
  staffing: medicareNursingHomeStaffing;
  enforcement: medicareNursingHomeEnforcement;
}

interface medicareNursingHomeQuery {
  /** 5-digit US ZIP, placed via the Census Bureau's ZCTA centroid. Either this
   * or a `latitude`/`longitude` pair is required. */
  zip?: string;
  latitude?: number;
  longitude?: number;
  /** Straight-line miles, default 25, max 100. */
  radiusMiles?: number;
  /** Max facilities returned, default 20. `total` reports the unlimited count. */
  limit?: number;
}

interface medicareNursingHomeSearch {
  origin: {
    zip: string | null;
    latitude: number;
    longitude: number;
    /** "zcta-centroid" (the Census Bureau's centre for the ZIP) or "caller". A
     * centroid carries real slack in a large rural ZIP. */
    source: "zcta-centroid" | "caller";
  };
  radiusMiles: number;
  /** Facilities inside the radius before `limit` is applied. */
  total: number;
  /** CMS's own processing date for these rows — the vintage of the Five-Star
   * data, NOT today. CMS refreshes this dataset monthly. */
  dataAsOf: string | null;
  /** Nearest first. */
  homes: medicareNursingHome[];
}

interface medicareHospitalMeasureGroup {
  /** How many measures CMS defines for this group nationally. */
  measuresInGroup: number | null;
  /** How many of them THIS hospital actually reported — can be fewer than
   * `measuresInGroup`; the gap is itself informative. */
  measuresReported: number | null;
  /** Null for `patientExperience`/`timelyAndEffectiveCare` — CMS publishes no
   * national-average comparison for those two groups, only a rate. */
  better: number | null;
  noDifferent: number | null;
  worse: number | null;
  /** CMS's own footnote code(s), verbatim — occasionally more than one,
   * comma-separated. */
  footnote: string | null;
}

interface medicareHospital {
  /** CMS Certification Number — the same id `findNursingHomes` and
   * `findDoctors`'s hospital affiliations key on. */
  ccn: string;
  name: string;
  address: string;
  city: string;
  state: string;
  zip: string;
  phone: string | null;
  county: string | null;
  /** Straight-line miles to the CENTROID OF THIS HOSPITAL'S OWN ZIP, not its
   * street address — this dataset carries no coordinates at all, the same gap
   * `findDoctors`'s clinician file has. Everyone sharing a ZIP shares a
   * distance, and a large rural ZIP carries real slack. */
  distanceMiles: number;
  /** e.g. "Acute Care Hospitals", "Critical Access Hospitals", "Psychiatric",
   * "Childrens", "Rural Emergency Hospital". Never "Long-term" — that type
   * belongs to `findRehabAndLongTermCareFacilities`. */
  hospitalType: string;
  ownershipType: string | null;
  emergencyServices: boolean;
  birthingFriendly: boolean;
  /** 1-5, or null when CMS publishes none — about 40% of hospitals nationally
   * carry no overall rating, mostly because they don't participate in the
   * reporting programs this rating requires, not because they scored poorly.
   * A null is NOT a bad rating; read it with `overallRatingFootnote`. */
  overallRating: number | null;
  /** Can be present even alongside a real star rating — always surface it. */
  overallRatingFootnote: string | null;
  /** The measure GROUPS behind the star, not just the rollup. */
  measureGroups: {
    mortality: medicareHospitalMeasureGroup;
    safety: medicareHospitalMeasureGroup;
    readmission: medicareHospitalMeasureGroup;
    patientExperience: medicareHospitalMeasureGroup;
    timelyAndEffectiveCare: medicareHospitalMeasureGroup;
  };
}

interface medicareHospitalQuery {
  /** 5-digit US ZIP, placed via the Census Bureau's ZCTA centroid. Either this
   * or a `latitude`/`longitude` pair is required. */
  zip?: string;
  latitude?: number;
  longitude?: number;
  /** Straight-line miles, default 25, max 100. */
  radiusMiles?: number;
  /** Max hospitals returned, default 20. `matchesInSearchedZips` reports the
   * unlimited-by-`limit` count for the ZIPs actually searched. */
  limit?: number;
}

interface medicareHospitalSearch {
  origin: {
    zip: string | null;
    latitude: number;
    longitude: number;
    source: "zcta-centroid" | "caller";
  };
  radiusMiles: number;
  /** ZIP Code Tabulation Areas the Census Bureau places inside the radius. */
  zipsInRadius: number;
  /** How many of them this call actually queried, nearest-batch-first. */
  zipsSearched: number;
  /** FALSE when the walk stopped before every ranked ZIP was queried —
   * normally because `limit` was already satisfied. Read it before describing
   * the result as "every hospital in the radius". */
  radiusFullyScanned: boolean;
  /** Hospitals found in the ZIPs actually searched, before `limit` — NOT a
   * radius-wide total when `radiusFullyScanned` is false. */
  matchesInSearchedZips: number;
  /** CMS's own publication date for this extract. Not "today" — CMS refreshes
   * quarterly. */
  dataAsOf: string | null;
  /** Nearest first. */
  hospitals: medicareHospital[];
}

/** Whether a clinician takes Medicare's approved amount as payment in full.
 * NOT a boolean, and that is load-bearing: CMS's `ind_assgn` is only ever "Y"
 * or "M" — never "N" — so "does not take Medicare" is not a state this data can
 * express, and mapping "M" onto false would invent it. */
type medicareAssignmentAcceptance =
  /** Participating: takes the Medicare-approved amount as full payment. */
  | "always"
  /** Non-participating: may accept case by case, and when they do not they can
   * bill up to Medicare's limiting charge ABOVE the approved amount, which the
   * patient pays. Never render this as "no". */
  | "mayAccept";

interface medicareClinicianAffiliation {
  /** One of CMS's seven: "Hospital", "Home health agency", "Hospice", "Dialysis
   * facility", "Nursing home", "Inpatient rehabilitation facility",
   * "Long-term care hospital". */
  facilityType: string;
  /** The facility's CMS Certification Number — the join key into every other CMS
   * facility dataset, including this provider's own `findNursingHomes`. */
  ccn: string;
  /** Resolved for "Hospital" rows; null for the other six types rather than
   * guessed from the wrong dataset. `ccn` still joins when this is null. */
  name: string | null;
}

interface medicareClinicianGroupPractice {
  /** Null for a solo practitioner billing under no organisation. */
  name: string | null;
  pacId: string | null;
  /** Clinicians billing under the group nationally. */
  memberCount: number | null;
  /** The GROUP's posture, which can differ from the individual's. */
  acceptsAssignment: medicareAssignmentAcceptance | null;
}

interface medicareClinician {
  /** National Provider Identifier — the id to key on. */
  npi: string;
  pacId: string | null;
  firstName: string;
  middleName: string | null;
  lastName: string;
  suffix: string | null;
  /** e.g. "MD", "DO", "NP". Null means "not published", never "no credential". */
  credential: string | null;
  gender: string | null;
  medicalSchool: string | null;
  graduationYear: number | null;
  /** CMS's caps-written specialty, e.g. "CARDIOVASCULAR DISEASE (CARDIOLOGY)".
   * Also the value the `specialty` filter matches against. */
  primarySpecialty: string | null;
  secondarySpecialties: string[];
  /** Billed Medicare for telehealth in this extract. A false is not a promise
   * they refuse it. */
  offersTelehealth: boolean;
  groupPractice: medicareClinicianGroupPractice;
  address: string;
  city: string;
  state: string;
  /** 5-digit, cut from CMS's ZIP+4. */
  zip: string;
  /** The full ZIP+4 as CMS publishes it — what the dataset is keyed on. */
  zipPlus4: string;
  phone: string | null;
  /** Straight-line miles to the CENTROID OF THIS CLINICIAN'S ZIP, not to their
   * street address: the CMS clinician file publishes no coordinates. Everyone
   * sharing a ZIP shares a distance, and a large rural ZIP carries real slack. */
  distanceMiles: number;
  /** Read `medicareAssignmentAcceptance` before rendering this. */
  acceptsAssignment: medicareAssignmentAcceptance | null;
  /** Empty when CMS publishes no affiliation — common, not an error. */
  affiliations: medicareClinicianAffiliation[];
}

interface medicareClinicianQuery {
  /** 5-digit US ZIP, placed via the Census Bureau's ZCTA centroid. Either this
   * or a `latitude`/`longitude` pair is required. */
  zip?: string;
  latitude?: number;
  longitude?: number;
  /** Straight-line miles, default 25, max 100. */
  radiusMiles?: number;
  /** Matched case-insensitively as a SUBSTRING against the primary specialty and
   * against the packed secondary list, so `"cardio"` finds
   * `"CARDIOVASCULAR DISEASE (CARDIOLOGY)"`. */
  specialty?: string;
  /** Matched as a PREFIX, which is what a caller typing a surname means. */
  lastName?: string;
  firstName?: string;
  /** True restricts to clinicians who ALWAYS take the Medicare-approved amount
   * as payment in full. Read `medicareAssignmentAcceptance` — there is no "no"
   * in this data, so a false here is not the opposite of a true. */
  acceptsAssignmentOnly?: boolean;
  /** Max distinct clinicians returned, default 20, max 100. */
  limit?: number;
}

interface medicareClinicianSearch {
  origin: {
    zip: string | null;
    latitude: number;
    longitude: number;
    source: "zcta-centroid" | "caller";
  };
  radiusMiles: number;
  /** ZIP Code Tabulation Areas the Census Bureau places inside the radius. */
  zipsInRadius: number;
  /** How many of them this call actually queried, nearest first. */
  zipsSearched: number;
  /** FALSE when the search stopped early — normally because `limit` was already
   * satisfied, which in a city happens on the first ZIP of several hundred. Read
   * it before describing the result as everyone in the radius. */
  radiusFullyScanned: boolean;
  /** Practice-location RECORDS matching in the ZIPs actually searched — NOT
   * distinct clinicians and NOT a radius-wide total. CMS's grain is (clinician,
   * address). */
  matchesInSearchedZips: number;
  /** CMS's publication date for the National Downloadable File. A periodic
   * extract, not a live read. */
  dataAsOf: string | null;
  /** Distinct by NPI, nearest first; a clinician with several addresses in range
   * appears once, at the nearest. */
  clinicians: medicareClinician[];
}

interface medicareMedigapDiscountRange {
  min: number;
  max: number;
}

/** How the premium changes with the buyer's age. Attained-age premiums RISE with
 * age; issue-age and community-rated do not (community additionally moves with
 * inflation for everyone at once, regardless of age). "unknown" is a real,
 * intended value — see `ratingMethodRaw` on the policy it appears on — never a
 * parse failure. */
type medicareMedigapRatingMethod = "attainedAge" | "issueAge" | "communityRated" | "unknown";

interface medicareMedigapPolicy {
  /** The insurer, exactly as CMS lists it. A parenthetical suffix like
   * "(Standard I)" is the SAME company selling this plan type under more than one
   * underwriting tier, each priced separately — do not dedupe by a company name
   * with the parenthetical stripped. */
  company: string;
  ratingMethod: medicareMedigapRatingMethod;
  /** CMS's raw rate-type string, kept ONLY when `ratingMethod` is "unknown" — a
   * value CMS started publishing after this mapping was written. Null whenever
   * `ratingMethod` is one of the three known values. */
  ratingMethodRaw: string | null;
  monthlyRateMin: number;
  monthlyRateMax: number;
  address: string;
  phoneNumber: string;
  website: string | null;
  /** A married/related-household discount, when this insurer offers one. Priced
   * SEPARATELY from `householdDiscountRoommate` — one existing does not imply
   * the other does. */
  householdDiscountStandard: medicareMedigapDiscountRange | null;
  /** An unrelated-adults-sharing-a-residence discount, priced separately from
   * `householdDiscountStandard` and frequently absent when that one is present. */
  householdDiscountRoommate: medicareMedigapDiscountRange | null;
}

interface medicareMedigapPlanType {
  /** CMS's discriminator minus its "MEDIGAP_PLAN_TYPE_" prefix — "A", "HIGH_F", or
   * a Minnesota/Wisconsin waiver type ("MN_BASIC", "WI_HIGH_DEDUCTIBLE", …). Pass
   * this back as `searchMedigapPlans`'s `planType` argument to re-fetch just
   * this one type. */
  planType: string;
  /** The range Medicare.gov reports ACROSS every insurer selling this plan type
   * here, before picking one. Null means the `planType` filter named a type the
   * overview did not list as offered here — `policies` is then reliably empty
   * too, and null is the honest value rather than a fabricated 0. */
  monthlyRateMin: number | null;
  monthlyRateMax: number | null;
  householdDiscountStandard: medicareMedigapDiscountRange | null;
  householdDiscountRoommate: medicareMedigapDiscountRange | null;
  /** Every insurer selling this plan type here. */
  policies: medicareMedigapPolicy[];
}

interface medicareMedigapQuery {
  /** 5-digit US ZIP. */
  zip: string;
  /** Omit to fetch every plan type the state offers. A result's own `planType`
   * (`'G'`, `'HIGH_F'`, or a waiver state's `'MN_BASIC'`) fetches just that one. */
  planType?: string;
  /** Only needed for the rare ZIP that crosses a STATE line — Medigap is priced
   * by state, so the wrong side returns a different market, not an error. */
  county?: string;
}

interface medicareMedigapSearch {
  zip: string;
  /** Resolved from the ZIP, never taken from the caller — Medigap is priced by
   * state and a wrong one silently returns an empty result rather than an error. */
  state: string;
  county: medicareCounty;
  countiesConsidered: medicareCounty[];
  /** Empty is a real, honest answer for a state/ZIP CMS reports no Medigap market
   * data for — never a sign this call failed. */
  planTypes: medicareMedigapPlanType[];
}

interface medicarePlanDetail {
  /** CMS's contract-plan-segment triple joined by '-', e.g. "S5884-103-0". */
  id: string;
  name: string;
  nameSpanish: string | null;
  organization: string;
  contractId: string;
  planId: string;
  segmentId: string;
  year: number;
  /** CMS's own category string: "Medicare Advantage" / "Medicare Advantage with
   * Prescription Drug Coverage" / "Medicare Prescription Drug Plan" /
   * "Special Needs Plan". */
  category: string;
  planType: "PLAN_TYPE_PDP" | "PLAN_TYPE_MA" | "PLAN_TYPE_MAPD" | "PLAN_TYPE_SNP";
  carrierUrl: string | null;
  contractYear: string;
  partcPremium: number;
  partdPremium: number;
  partbPremiumReduction: number;
  /** Annual drug deductible in dollars; 0 means none. PDP only. */
  drugPlanDeductible: number | null;
  /** Annual in-network OOP maximum, verbatim. Empty when not published. */
  maximumOopc: string;
  /** Per-visit cost strings, verbatim. Empty when CMS publishes no figure —
   * "$0" (a real benefit) and "" (not published) are deliberately distinct. */
  primaryDoctorVisitCost: string;
  specialistDoctorVisitCost: string;
  emergencyCareCost: string;
  /** MA/MAPD only — null on PDP, which has no primary/specialist visits. */
  primaryDoctorCostSharing: string | null;
  specialistDoctorCostSharing: string | null;
  /** 1-5 in half steps, or null when CMS publishes none (with starRatingNote
   * naming the reason — e.g. "too new to be rated"). */
  starRating: number | null;
  starRatingNote: string | null;
  lowPerforming: boolean;
  highPerforming: boolean;
  /** All false on a PDP — supplemental benefits live on the Part C side. */
  supplementalBenefits: {
    silverSneakers: boolean;
    transportation: boolean;
    telehealth: boolean;
    otcDrugs: boolean;
    homeSafetyDevices: boolean;
    inHomeSupport: boolean;
    supportForCaregivers: boolean;
    healthEducation: boolean;
    counselingServices: boolean;
    emergencyResponseDevice: boolean;
    worldwideEmergency: boolean;
  };
  providerCoverage: {
    /** False on plans with no network (e.g. PFFS); providers is then reliably empty. */
    hasProviderCoverageData: boolean;
    providerCount: number;
  };
  /** Extra Help / Low-Income Subsidy dollar amounts at each LIS band. */
  lis: {
    level100: number;
    level75: number;
    level50: number;
    level25: number;
  };
  snpType: string;
  dsnpIntegrationLevel: string;
  /** When CMS marks the plan as terminated for the search year. */
  terminatedWithoutCrosswalk: string | null;
  /** Empty when the plan is published in full; the SPA hides fields CMS marks
   * for redaction and the same fields arrive here. */
  redactions: string[];
}

interface medicareGetPlanQuery {
  /** "pdp" | "ma" | "mapd" | "snp" — the caller's MUST match the row they
   * have in hand. The same (contractId, planId, segmentId) triple does NOT
   * exist across plan types by design. */
  planType: "pdp" | "ma" | "mapd" | "snp";
  /** CMS's contract id (e.g. "S5884" for Part D, "H1234" for Part C). */
  contractId: string;
  planId: string;
  segmentId: string;
  /** Defaults to the current calendar year — CMS renumbers every January. */
  year?: number;
  /** 5-digit US ZIP. Required because the API is keyed on (fips, zip). */
  zip: string;
  /** Needed only for the rare ZIP that crosses a state line. */
  county?: string;
}