When a ZIP code can't tell you the truth
Nobody memorizes their Medicare payment locality code. They know their ZIP. So the rate lookup tool and API both accept a ZIP code and resolve it to a locality behind the scenes, against CMS's own ZIP-to-Carrier-Locality crosswalk. That sounds like a lookup table problem — ZIP in, locality out — until you read the crosswalk closely enough to notice it isn't a function. Some 5-digit ZIPs really do span more than one payment locality, and CMS's file says so explicitly with a plus-four flag. A resolver has to decide what to say when the honest answer is "it depends on the last four digits, which you didn't give me."
Two easy answers, both wrong
The tempting options are both a kind of lie. Pick one of the localities a flagged ZIP maps to and return it silently, and you've handed back a number that's right for some addresses in that ZIP and wrong for others, with nothing in the response distinguishing the two cases. Reject flagged ZIPs outright, and you've broken lookups for real ZIPs that real Medicare claims get billed against every day, just because the resolver can't promise perfect precision.
ZipResolver does neither. A flagged ZIP resolves to its dominant
locality — the one CMS's crosswalk lists — but the result is explicitly
marked Ambiguous, not Exact, and carries a reason explaining why:
$status = $row->plus_four_flag ? ZipResolutionStatus::Ambiguous : ZipResolutionStatus::Exact;
if ($status === ZipResolutionStatus::Ambiguous) {
$parts[] = "ZIP {$zip5} spans more than one payment locality; provide ZIP+4 for an exact match. "
.'Showing the dominant locality.';
}
The caller gets an answer either way, but an Ambiguous response and an
Exact one are never presented as equally trustworthy — which is the
actual distinction that matters to someone using this rate to bill or
appeal a claim. Full ZIP+4 resolution needs CMS's ZIP9 override file,
which isn't ingested yet; until it is, a supplied +4 is recorded on the
response but can't change which locality comes back. Saying "we recorded
your ZIP+4 but can't act on it yet" is still more honest than silently
treating it as resolved.
Rates are historical, so ZIP resolution has to be too
The rate engine already supports "what would this code have paid in Q2 2024," because releases are immutable and versioned by quarter. A ZIP resolution feeding into that lookup has to be equally historical — a locality boundary or MAC assignment can move between crosswalk releases, so resolving today's ZIP-to-locality mapping against a two-year-old rate release would silently mix two different points in time.
public function crosswalkReleaseForPeriod(int $year, string $quarter): ?Release
{
return Release::query()
->where('schedule', ZipLocalityIngestService::SCHEDULE)
->where(function ($q) use ($year, $quarter): void {
$q->where('year', '<', $year)
->orWhere(function ($q) use ($year, $quarter): void {
$q->where('year', $year)->where('quarter', '<=', strtoupper($quarter));
});
})
->orderByDesc('year')
->orderByDesc('quarter')
->first();
}
This picks the newest crosswalk release that was current as of the
requested period — the same A < B < C < D quarter ordering the PFS
releases themselves use, which happens to sort correctly as plain strings
because CMS's own lettering is already chronological. A historical rate
lookup and the ZIP resolution feeding it now agree on which quarter's
world they're describing, instead of one silently using today's
boundaries against yesterday's rates.
Letting two inputs check each other instead of picking a winner
The API also accepts an explicit locality alongside a ZIP, for callers who already know their locality but want it cross-checked. When both are given and they agree, the ZIP acts as confirmation and the matched crosswalk row gets cited as evidence. When they disagree, the request fails loudly (422) rather than one input silently overriding the other — contradictory input is something to surface, not arbitrate quietly.
The interesting case is the third one: an ambiguous ZIP paired with an explicit locality that matches its dominant mapping. That's not treated as an error. An ambiguous ZIP's own error message says "provide more detail to disambiguate" — an explicit locality is exactly that, so a caller who supplies one is doing precisely what the ambiguity warning asked for, and the response says the explicit locality was used rather than implying the dominant one was merely guessed at.
Parsing input honestly too
Even turning a string into a candidate ZIP resists the urge to guess past what's actually there:
private static function parseZip(string $zip, ?string $plus4): ?array
{
$zipDigits = preg_replace('/\D/', '', $zip) ?? '';
$plus4Digits = $plus4 !== null ? (preg_replace('/\D/', '', $plus4) ?? '') : '';
if (strlen($zipDigits) === 9) {
$zip5 = substr($zipDigits, 0, 5);
if ($plus4Digits === '') {
$plus4Digits = substr($zipDigits, 5, 4);
}
} elseif (strlen($zipDigits) >= 1 && strlen($zipDigits) <= 5) {
$zip5 = str_pad($zipDigits, 5, '0', STR_PAD_LEFT);
} else {
return null;
}
return [$zip5, strlen($plus4Digits) === 4 ? $plus4Digits : null];
}
It accepts the reasonable variants — "90210", "90210-1234",
"902101234", a separate +4 argument, a ZIP that lost a leading zero
somewhere upstream — and left-pads what's clearly a truncated 5-digit
ZIP. Anything else returns null and becomes an explicit InvalidZip
result, not a best-effort guess dressed up as a resolved locality.
The pattern
Every decision here is the same instinct applied at a different layer:
resist false precision. Return Ambiguous instead of inventing a single
right answer for a ZIP that doesn't have one. Resolve against the
crosswalk that was actually in effect for a historical query instead of
today's. Treat two inputs that disagree as a signal, not a tiebreak. A
resolver that's occasionally allowed to say "I can't be exact here" is
more trustworthy than one that's always confident and sometimes wrong —
especially when the number it returns feeds into an actual Medicare
claim.