Name and address variants
Source:vignettes/name-and-address-variants.Rmd
name-and-address-variants.RmdHow recipient names and addresses actually vary in the 990-PF filings, which of those variations the pipeline already absorbs, and which ones decide whether a grant resolves to an EIN.
Everything here is computed from pf_variants: 5,410 pairs of recipient renderings filed at the same street and ZIP, across 4,291 addresses. In each pair the cascade resolved one rendering (YES) and returned MAYBE on the other. They come from the 50,000-case triangulation run over population cb3c78ff0f1a9546.
Read this first: these pairs survived normalization
The pairs are not a random sample of variation. They are the residue.
Before any pass runs, npmatch::np_normalize() has already uppercased, dropped a leading THE, rewritten & as AND, expanded FDN to FOUNDATION and contracted USPS street suffixes; pf_dedup() has then collapsed renderings sharing an identity key into a single recipient. So a pair that differs only in a way the pipeline handles never reaches the comparator as two things and never appears here.
What that buys: the table is a regression corpus. Every row is a live case that reached the comparator and split. Tighten the normalizer and specific rows should move; the rows recorded as already closed should stay closed.
What it costs: pf_variants tells you nothing about variation the normalizer already fixes. For that, compare the *_differs columns (the filed text) against the *_key_equal columns (what the normalizer made of it).
| pairs | share | |
|---|---|---|
| ZIP5 agrees inside the pair | 5,410 | 100.0% |
| filed street text differs | 88 | 1.6% |
| …but the normalized street key agrees | 9 | 10.2% |
| normalized name keys agree anyway | 46 | 0.9% |
| blocking intact (ZIP5 and state both usable) | 5,390 | 99.6% |
ZIP5 agrees in every pair because the group is defined by street-plus-ZIP5, which is also why this table cannot be used to study ZIP variation.
It is not ground truth
The triangulation verdicts are silver, confidence = "low", and unadjudicated. Writing the anchor’s EIN onto every other rendering at the address is the assumption under test, not a fact.
So expected_same_org is TRUE on 20 rows — where the two names are the same string under a documented cosmetic transformation at an identical address — and NA, never FALSE, on the other 5,390. Two examples of why a string test cannot decide it:
-
US OLYMPIC AND PARALYMPIC FOUNDATIONandUSOCshare no tokens and are the same family of organization. -
MARINE CORPS UNIVERSITY FOUNDATIONandMARINE CORP SCHOLARSHIP FUNDshare a distinctive token and are two different charities.
label_basis records which rows are waiting for a human.
Name variants
| class | pairs | share | median edit distance | median token Jaccard | name keys agree |
|---|---|---|---|---|---|
| different name | 4,080 | 75.4% | 23.0 | 0.00 | 1 |
| token added or dropped | 595 | 11.0% | 15.0 | 0.50 | 1 |
| truncation | 428 | 7.9% | 19.5 | 0.50 | 23 |
| partial token overlap | 274 | 5.1% | 12.0 | 0.50 | 4 |
| leading THE | 14 | 0.3% | 4.0 | 0.75 | 14 |
| typo | 8 | 0.1% | 1.0 | 0.60 | 0 |
| abbreviation | 4 | 0.1% | 7.0 | 0.60 | 3 |
| spacing or punctuation | 4 | 0.1% | 1.0 | 0.49 | 0 |
| corporate suffix | 2 | 0.0% | 4.0 | 0.50 | 0 |
| identical normalized | 1 | 0.0% | 0.0 | 1.00 | 0 |
The cosmetic classes everyone expects to dominate are almost absent, and that is the normalizer working: it has already closed them upstream. What is left is dominated by names that are genuinely different strings.
name keys agree is the column to watch. Where it is non-zero the normalizer did close the name difference and the two renderings still reached the comparator separately — covered in “A seam” below.
Exemplars
Six per class, from the curated subset.
different name
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| ALLIED ARTS COUNCIL OF ST JOSEPH | ARTS FUND | FALSE | 25 | 0.143 | 0.98 | 0.720 |
| DANIELLES ANIMAL SAFE HAVEN INC | ANIMAL RESCUE | FALSE | 22 | 0.167 | 0.98 | 0.720 |
| JEWISH COMMUNITY FEDERATION - EAST BAY | BERKLEY HIGH HOLIDAYS SERVICES | FALSE | 31 | 0.000 | 0.98 | 0.780 |
| ALLIANCE FRANCAISE DE MILWAUKEE | BIG BROTHERS BIG SISTERS | FALSE | 25 | 0.000 | 0.98 | 0.720 |
token added or dropped
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| BOY SCOUTS OF AMERICA | GNFC - Boy Scouts of America | FALSE | 5 | 0.800 | 0.98 | 0.760 |
| COMO FRIENDS | COMO ZOO AND CONSERVATORY SOCIETY - COMO FRIENDS | FALSE | 34 | 0.333 | 0.98 | 0.780 |
| COVER HOME REPAIR | COVER | FALSE | 12 | 0.333 | 0.98 | 0.720 |
| The Humane Society of Broward County | BROWARD HUMANE SOCIETY | FALSE | 25 | 0.500 | 0.98 | 0.751 |
truncation
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| CORNERSTONE CHRISTIAN SCHOOL AND EDUCATIONAL SERVICES | CORNERSTONE CHRISTIAN SCHOOL | FALSE | 25 | 0.500 | 0.98 | 0.720 |
| FRIENDS OF YOUTH SERVICES AND PALM BEACH COUNTY INC | FRIENDS OF YOUTH SERVICES | FALSE | 26 | 0.444 | 0.98 | 0.720 |
| FAITH CHURCH ST LOUISCOM INC | FAITH CHURCH | FALSE | 16 | 0.400 | 0.98 | 0.720 |
| COLLEGE BOUND ST LOUIS | College Bound | FALSE | 9 | 0.500 | 0.98 | 0.720 |
partial token overlap
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| MOON TOWNSHIP LIBRARY FRIENDS | MOON TOWNSHIP PUBLIC LIBRARY | FALSE | 13 | 0.600 | 0.98 | 0.980 |
| DELTA PHI EPSILON EDUCATIONAL FOUNDATION | DELTA UPSILON EDUCATIONAL FOUNDATION | FALSE | 5 | 0.500 | 0.98 | 0.720 |
| A BETTER WISCONSIN INSTITUTE | A BETTER WISCONSIN TOGETHER | FALSE | 8 | 0.600 | 0.98 | 0.980 |
| ALLIED ARTS COUNCIL OF ST JOSEPH | ALLIES ARTS OF ST JOSEPH | FALSE | 9 | 0.571 | 0.98 | 0.760 |
leading THE
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| The Pasadena Educational Foundation | Pasadena Educational Foundation | TRUE | 4 | 0.750 | 0.98 | 0.720 |
| THE JACKSONVILLE HUMANE SOCIETY | JACKSONVILLE HUMANE SOCIETY | TRUE | 4 | 0.750 | 0.98 | 0.739 |
| THE WESTERN PENNSYLVANIA CONSERVANCY | WESTERN PENNSYLVANIA CONSERVANCY | TRUE | 4 | 0.750 | 0.98 | 0.759 |
| THE ONESTAR FOUNDATION | Onestar Foundation | TRUE | 4 | 0.667 | 0.98 | 0.720 |
typo
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| MUSEUM OF THE AMERICAN REVOLUTION | MUESEUM OF THE AMERICAN REVOLUTION | FALSE | 1 | 0.667 | 0.98 | 0.760 |
| YOUNG WOMENS CHRISTIAN ASSO OF MISSOULA | YOUNG WOMENS CHRISTIAN ASSOC OF MISSOULA | FALSE | 1 | 0.714 | 0.98 | 0.780 |
| LONG BEACH COMMUNITY FOUNDATION | LONG BEACH COMMUNITY FOUNDATON | FALSE | 1 | 0.600 | 0.98 | 0.765 |
| YESHIVATH BETH YEHUDAH | Yeshiva Beth Yehudah | FALSE | 2 | 0.500 | 0.96 | 0.960 |
abbreviation
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| COMMUNITY FOUNDATION FOR LOUDOUN | COMMUNITY FDN FOR LOUDOUN | TRUE | 7 | 0.600 | 0.980 | 0.780 |
| MEDICAL FOUNDATION OF NASHVILLE | MEDICAL FDN OF NASHVILLE | TRUE | 7 | 0.600 | 0.960 | 0.780 |
| WHITE PLAINS HOSPITAL FDN | WHITE PLAINS HOSPITAL FOUNDATION | TRUE | 7 | 0.600 | 0.960 | 0.733 |
| LOS GATOS HIGH NEW MILLENNIUM FOUNDATION | LOS GATOS HIGH NEW MILLENNIUM FOUND | FALSE | 5 | 0.714 | 0.888 | 0.780 |
spacing or punctuation
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| BOYS & GIRLS CLUB OF WASHINGTON COUNTY | BoysGirls Club of Washington County | FALSE | 1 | 0.571 | 0.980 | 0.720 |
| US NAVAL ACADEMY | U S NAVAL ACADEMY | FALSE | 1 | 0.400 | 0.960 | 0.902 |
| US Marshalls Museum | U S Marshalls Museum | FALSE | 1 | 0.400 | 0.949 | 0.760 |
| HEALTH CARE FOUNDATION OF GREATER KANSAS CITY | HEALTHCARE FOUNDATION OF GREATER KANSAS CITY | FALSE | 1 | 0.625 | 0.900 | 0.780 |
corporate suffix
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| BRAC USA | BRAC | FALSE | 4 | 0.5 | 0.96 | 0.720 |
| CARE | CARE USA | FALSE | 4 | 0.5 | 0.82 | 0.720 |
identical normalized
| resolved (YES) | unresolved (MAYBE) | name key agrees | edit dist | jaccard | YES score | MAYBE score |
|---|---|---|---|---|---|---|
| BOYS & GIRLS CLUBS OF MANATEE COUNTY INC | BOYS GIRLS CLUBS OF MANATEE COUNTY INC | FALSE | 0 | 1 | 0.98 | 0.780 |
Address variants
| class | pairs | share | blocking still intact |
|---|---|---|---|
| identical | 4,278 | 79.1% | 100.0% |
| zip4 differs | 534 | 9.9% | 99.6% |
| city differs | 285 | 5.3% | 99.3% |
| unit differs | 229 | 4.2% | 98.3% |
| street spelling | 72 | 1.3% | 100.0% |
| state differs | 12 | 0.2% | 0.0% |
The address is scored, but inside a group it is saturated
Blocking and scoring are separate layers and the address behaves differently in each. pf_staged_passes() blocks on name_key or dba_key crossed with zip5 or state, and nothing else:
| pass | criteria |
|---|---|
| A1-name-zip5 | name + ZIP5 (exact) |
| A2-name-state | name + state (exact) |
| A3-name-dba | name == dba, same state |
| A4-dba-name | dba == name, same state |
| A5-dba-dba | dba == dba, same state |
| B1-token-state | shared token, same state |
| B2-token-concat | shared token + compound, same state |
| C1-name-any | name == name, ANY state |
| C2-dba-any | name == dba, ANY state |
So no pass blocks on street, unit or city. But scoring is a different layer, and there the address carries real weight. pfmatch runs method = "hier", whose score is
score = 0.6 * name_key_similarity + 0.4 * geo_score
and geo_score is the maximum over a tier ladder:
| geo term | weight | fires when |
|---|---|---|
| zip9 | 1.00 | ZIP+4 agrees |
| unit | 0.97 | the suite agrees, where it discriminates (a multi-tenant building, not a crowded mail drop) |
| street | 0.95 | street number agrees AND normalized street key similarity >= 0.9, not a PO box |
| zip5 | 0.90 | ZIP5 agrees |
| pobox | 0.90 | PO box number agrees |
| zip3 | 0.55 | ZIP3 agrees (zeroed on a state conflict) |
| city | 0.50 | city Jaro-Winkler, floored at jw_threshold (zeroed on a state conflict) |
| state | 0.20 | state agrees |
street_key — npmatch’s normalized street — is compared directly and is worth 0.95, behind only ZIP+4 and the suite. The address normalizer is very much in use.
The catch is the max(). Inside a triangulation group ZIP5 agrees by construction, so geo_score is already at least 0.90 before the street is considered, and a street hit can lift it only to 0.95. That is 0.05 of geo, 0.02 of the final score. City at 0.50 and state at 0.20 are dominated outright and cannot bind at all while ZIP5 agrees.
So the address is not inert — it is saturated, and only in this corpus. Outside it, wherever ZIP5 does not agree, street_hit is the best geo term available and street normalization is carrying the match. That is the opposite of what these pairs alone would suggest, and it is why pf_variants is the wrong instrument for judging the address comparator’s value.
Consequences that do hold here:
- A city spelling difference —
FT LAUDERDALEagainstFort Lauderdale— cannot change the outcome while ZIP5 agrees, because the 0.50 city term is dominated by the 0.90 ZIP5 term. There are 290 such pairs, and blocking stays intact in all of them. - A suite line could not separate two organizations in one building when these pairs were cut:
.np_hier_score()had no unit term, and thestreet_unit_fullweight profile that does carry one needs 50% unit coverage against an actual 3.84%. npmatch has since added theunitrung above — so the mechanism now exists, gated on the suite discriminating at that address rather than merely being present. The pairs in this table predate it, which is whypf_unit_evidence()exists here at all. Its own parser has since been retired in favour of npmatch’s; seedev/UNIT-UPSTREAM-SYNC.md. - The state costs twice when wrong: it blocks stages A2–A5 and all of stage B, and a state conflict zeroes the city and ZIP3 geo terms. It disagrees in 20 pairs, all adjacent-letter filing errors:
| resolved (YES) | state | unresolved (MAYBE) | state |
|---|---|---|---|
| AICE | MO | JDC | MD |
| UK PHILANTHROPY | NY | UNIVERSITY OF KENTUCKY | KY |
| FEATHER RIVER COLLEGE FOUNDATION | WA | FEATHER RIVER COLLEGE - ELLEN HOLLIFIELD SCHOLARSHIP | CA |
| ASSOCIATION OF BALTIMORE AREA GRANTMAKERS | MD | BALTIMORE COMMUNITY FOUNDATION INC | MA |
| FREE ARTS FOR ABUSED CHILDREN OF NEW YORK CITY INC | NY | MOTHERS2MOTHERS INTERNATIONAL INC | PA |
| NEW VENTURE FUND | DC | HOPE & HEAL FUND | CA |
Since ZIP5 is present and agrees in every one of these pairs, a ZIP5-to-state repair would fix them outright.
Where street normalization does pay
Not inside a group — by construction every pair already keyed to the same street. It pays in forming groups at all. The filed street text differs in 88 pairs here, and the normalizer reconciles 9 of them:
| filed (YES side) | filed (MAYBE side) | normalized key |
|---|---|---|
| 4550 WEST 77TH STREETSUITE 200 | 4550 West 77th Street Suite 200 | 4550 W 77TH ST |
| 1360 MISSION STSUITE 300 | 1360 MISSION ST SUITE 300 | 1360 MISSION ST |
| 506 2ND AVESUITE 1400 | 506 2ND AVE SUITE 1400 | 506 2ND AVE |
| 620 MOUNTAIN VILLAGE BLVD STE 2B | 620 MOUNTAIN VILLAGE BLVDSTE 2B | 620 MOUNTAIN VILLAGE BLVD |
| 6321 Porter RoadSuite 8 | 6321 PORTER ROAD SUITE 8 | 6321 PORTER RD |
| 8 S MICHIGAN AVENUE SUITE 500 | 8 S MICHIGAN AVENUESUITE 500 | 8 S MICHIGAN AVE |
A missing space before SUITE, Street against ST, ROAD against RD. Measured over the whole corpus rather than these pairs, normalizing the street collapses 528,862 of 2,326,459 distinct address keys — 22.7% — and brings 58,815 addresses above the two-rendering threshold that are currently split across spellings and so form no group at all.
The caution: a normalizer that drops the secondary-unit designator to achieve that will also merge STE 100 with STE 200, which are different tenants. Distinct units are rare at these addresses, so the trade is probably worth making, but it is a trade.
The suite line as an adjudication tie-breaker
The one address fact that bears directly on a triangulated label is the door. Two renderings at one street giving different suites are very likely different organizations, so the anchor’s EIN should not be written onto the candidate. When these pairs were cut the matcher could not use this at all; npmatch has since given the hierarchical scorer a unit rung (0.97) and annotates every candidate pair with geo_unit, so the mechanism now exists there too. A reviewer still needs the four-way reading below, which geo_unit does not give: it cannot separate “one side filed nothing” from “neither did”.
pf_unit_evidence() compares the two lines. The parse is npmatch’s — pf_street_unit() hands line 2 to np_normalize() and reads street_unit back, so pfmatch reads a suite the same way the matcher does. What is still pfmatch’s is the two rejections the field needs, because it is not a unit field: it holds street continuations, city names, PO boxes, and in one case a sequential counter written once per grantee. The verdicts tabulated below were recorded under the normalizer the corpus was built with; retiring the local parser moved 22 of the 5,410 pairs, every one traced in dev/UNIT-UPSTREAM-SYNC.md.
| evidence | pairs | share | how to read it |
|---|---|---|---|
| absent | 5266 | 97.3% | neither side filed a parseable unit |
| one_sided | 108 | 2.0% | one side did; silence is not disagreement |
| agree | 27 | 0.5% | same door — NOT evidence of one organization, see below |
| conflict | 9 | 0.2% | different doors — the label is probably wrong |
| anchor (YES) | unit | candidate (MAYBE) | unit |
|---|---|---|---|
| CITY GROWERS | 1105 | PRATT CENTER FOR COMMUNITY DEVELOP | 701 |
| Lankenau Medical Center Foundation | 340 | MAIN LINE HOSPICE | 100 |
| COURAGE WORLDWIDE | 433 | RRUFF - ROCKLIN RESIDENTS UNITE FO | 125 |
| AUTISM ALLIANCE OF MICHIGAN | 520 | WOMEN OF TOMORROW | 155 |
| BALTIMORE ARCHITECTURE FOUNDATION | STREET SUITE P-101 | JOHNS HOPKINS UNIVERSITYJOHNS HOPK | STREET SUITE 234 |
| LEUKEMIA & LYMPHOMA SOC | SUITE 115 | KORE HAITI INC | SUITE 100 |
| WEIMARANER RESCUE OF NORTH TEXAS | 184 | THOMAS JEFFERSON ALUMNI ASSOCIATIO | 150 |
| MCCOOK ECONOMIC DEVELOPMENT CORP | STE 301 | MCCOOK COMMUNITY FOUNDATION | RM 315 |
| CITY GROWERS | 1105 | DIEU DONN | 602 |
Two cautions, both measured rather than assumed.
agree is not support. Generic suites are shared by unrelated tenants — MIDDLE EAST CHILDRENS ALLIANCE and IRISH REPERTORY THEATER are both at Suite 100, GLOBAL FUND FOR WOMEN and THAKKER CHARITABLE TRUST both at Suite 400 — a fiscal sponsor’s suite is shared by everything it sponsors, and some values are not units at all (15219 is a Pittsburgh ZIP code on line 2). It is recorded for completeness and carries nothing.
conflict concentrates. Over the whole triangulation run it fires on 69 of 14,790 anchor/candidate pairs (0.47%), but one address produced 31 of them: a fellowship program writing 291 BROADWAY STE 1901, STE 1902, … one per individual grantee. Always read the raw columns, never the flag alone. In the adjudication queue the flag lands on 121 of 58,680 rows, 26 of which sit in the zero-flag band — cases that looked clean until the address was read.
What decides YES against MAYBE
| side | median score | exact name+ZIP5 | token-blocked |
|---|---|---|---|
| resolved (YES) | 0.96 | 56.0% | 19.8% |
| unresolved (MAYBE) | 0.78 | 9.1% | 53.2% |
A seam: identity is keyed on one thing, blocking on another
pf_dedup() keys identity on name_full — deliberately, so that an organization is not merged with its affiliate by a name_key that has already thrown the affiliate suffix away. The cascade then blocks stage A1 on name_key crossed with zip5. The two disagree, and 46 pairs land in the gap: same normalized name key, same street key, same ZIP5, two identities, two different tiers.
| resolved (YES) | unresolved (MAYBE) | shared name key | YES score | MAYBE score |
|---|---|---|---|---|
| COMMUNITY FOUNDATION FOR LOUDOUN | COMMUNITY FDN FOR LOUDOUN | COMMUNITY FOUNDATION FOR LOUDOUN | 0.98 | 0.780 |
| CHILDREN’S HOME SOCIETY | CHILDRENS HOME | CHILDRENS HOME | 0.98 | 0.680 |
| The Pasadena Educational Foundation | Pasadena Educational Foundation | PASADENA EDUCATIONAL | 0.98 | 0.720 |
| THE JACKSONVILLE HUMANE SOCIETY | JACKSONVILLE HUMANE SOCIETY | JACKSONVILLE HUMANE | 0.98 | 0.739 |
| THE WESTERN PENNSYLVANIA CONSERVANCY | WESTERN PENNSYLVANIA CONSERVANCY | WESTERN PENNSYLVANIA CONSERVANCY | 0.98 | 0.759 |
| THE ONESTAR FOUNDATION | Onestar Foundation | ONESTAR | 0.98 | 0.720 |
When the unresolved side has the better match
The most useful flag in the table. Triangulation writes the anchor’s EIN onto the candidate as correct_ein. On 726 pairs (13.4%) the candidate’s own match was better than the anchor’s — a higher score, or an exact name-plus-ZIP5 hit against a token-blocked one. On those the silver label runs against the system’s own evidence.
| anchor (YES) | score | pass | candidate (MAYBE) | score | pass |
|---|---|---|---|---|---|
| FRIENDS OF SPL | 0.900 | D1-acronym | Friends of the Scarsdale Library Inc | 0.98 | A1-name-zip5 |
| CHILDREN’S HOME SOCIETY | 0.900 | D1-acronym | BOYS AND GIRLS CLUB OF MIAMI-DADE INC | 0.90 | A2-name-state |
| REGENTS OF THE UNIVERSITY OF MINNES | 0.900 | D1-acronym | UNIVERSITY OF MN FOUNDATION | 0.90 | A2-name-state |
| BROWARD ALLIANCE INC (THE) | 0.913 | B1-token-state | GREATER FORT LAUDERDALE ALLIANCE | 0.98 | A1-name-zip5 |
| CENTRAL GWINNETT JROTC | 0.905 | B1-token-state | PARENT BOOSTER USA INC | 0.98 | A1-name-zip5 |
| PRESTON HOLLOW ELEMENTARY SCHOOL FUND | 0.958 | B1-token-state | PTA TEXAS CONGRESS | 0.98 | A1-name-zip5 |
| FRIENDS OF HAYWOOD PUBLIC LIB | 0.893 | B1-token-state | HAYWOOD COUNTY PUBLIC LIBRARY FOUNDATION INC | 0.98 | A1-name-zip5 |
| JENSEN BEACH COMMUNITY PLAYGROUND | 0.876 | B1-token-state | PTA FLORIDA CONGRESS | 0.98 | A1-name-zip5 |
BETHEL COLLEGE against BETHEL UNIVERSITY is the shape to recognise: a renamed organization, where the rendering being labelled wrong found the better reference row. Filter on b_outscores_a before trusting a triangulated label.
Using this as a regression corpus
The pairs carry both the raw filed strings and the keys np_normalize() made of them, so the keys can be recomputed and compared. tests/testthat/test-variants.R does exactly that, in two layers:
-
Invariants that must hold under any sane normalizer: a leading
THEnever decides a name key; streets differing only by suffix spelling or a missing space normalize together; streets recorded as genuinely different stay different; a city difference never breaks blocking. -
A pinned check that every recorded key still reproduces, scoped to the
np_normalize_signature()the fixture was built under (v1-6dcb298522cc5118). It skips when the normalizer moves, because an intentional improvement should move keys — and the skip message tells you to rebuild withdata-raw/make-pf-variants.R.
To test a change, re-run the fixture build against the new normalizer and diff the class table: cosmetic classes should shrink, name_key_equal should grow, and no pair should move from a different-street class into an agreeing one unless you meant it to.