Summary
Normalize / fuzzy-cluster the ~6,936 distinct raw affiliation strings now in the registry (post-#1196) down to a canonical-name index of ~1,000–2,000 actual institutions, then re-enable the Author Affiliation facet on the doc-list browse.
Rationale
PR #1196 backfilled authors[].affiliation from _source/SMPTE/HIGHWIRE/ NLM <aff> blocks across ~9,800 SMPTE journal-article docs. The raw strings are verbatim from the source XML — every spelling/punctuation variant survives:
"RCA Manufacturing Co., Camden, N. J."
"RCA Manufacturing Co., Camden, N.J."
"RCA Manufacturing Co., Camden, NJ"
"RCA Manufacturing Co."
→ 4 facet buckets for what's clearly one institution. Multiplied across the corpus this lands the affiliation facet at ~7k distinct buckets — unusable as a picker (even ~500 would be too many).
#1196 ships with the affiliation facet disabled ([build.search-index.js], [docList.js]) — search index + URL filter still work against the raw strings, but the picker UI is hidden until this issue is resolved.
Implementation thoughts
Strategy: display-time normalization, source verbatim
Keep authors[].affiliation as the raw source string per doc (provenance — never lose what the XML said). Add a canonical-name layer the facet builder uses to bucket.
Normalization steps (cheap to expensive)
- Whitespace + punctuation normalize (free — Levenshtein 0 collapses): strip trailing
., normalize internal whitespace, fold Co. ↔ Co ↔ Company, Inc. ↔ Inc ↔ Incorporated, Dept. ↔ Department, etc.
- Strip city/state/country trailing tokens for short-form canonicalization:
"RCA Manufacturing Co., Camden, N. J." → "RCA Manufacturing Co.". Keep the long form for display when the short form has multiple sources, but bucket on the short.
- Acronym expansion lookup (refMap-style):
"MIT" ↔ "Massachusetts Institute of Technology", "USC" ↔ "University of Southern California". Small curated map.
- Levenshtein-based fuzzy match within publisher-clustered subsets to catch typos / minor spelling drift. Threshold tuned per corpus size.
- Optional ROR/Wikidata cross-reference — too heavy for v1 but worth knowing the data exists.
Where the normalization lives
- New helper
src/main/lib/affiliationNormalize.js — pure function from raw → canonical key + display string.
build.search-index.js re-enables facets.affiliations bucket by emitting canonical → count (raw strings preserved per-doc on idx row for the URL filter and full-text search).
facets.affiliationLabels map emitted alongside the bucket so the picker shows the canonical name, not the raw form.
docList.js re-enables the picker entry — same facetLabel() pattern as ICS codes.
Picker UX
Even after normalization, ~1,500 buckets is still a lot for a vertical accordion. Worth pairing with a min-count threshold (only show institutions with ≥5 docs in the picker; long-tail still URL-filterable). Mirrors the existing min-bucket-threshold pattern on keywords if there is one — if not, this issue adds it.
Acceptance
Related
Summary
Normalize / fuzzy-cluster the ~6,936 distinct raw affiliation strings now in the registry (post-#1196) down to a canonical-name index of ~1,000–2,000 actual institutions, then re-enable the Author Affiliation facet on the doc-list browse.
Rationale
PR #1196 backfilled
authors[].affiliationfrom_source/SMPTE/HIGHWIRE/NLM<aff>blocks across ~9,800 SMPTE journal-article docs. The raw strings are verbatim from the source XML — every spelling/punctuation variant survives:"RCA Manufacturing Co., Camden, N. J.""RCA Manufacturing Co., Camden, N.J.""RCA Manufacturing Co., Camden, NJ""RCA Manufacturing Co."→ 4 facet buckets for what's clearly one institution. Multiplied across the corpus this lands the affiliation facet at ~7k distinct buckets — unusable as a picker (even ~500 would be too many).
#1196 ships with the affiliation facet disabled ([build.search-index.js], [docList.js]) — search index + URL filter still work against the raw strings, but the picker UI is hidden until this issue is resolved.
Implementation thoughts
Strategy: display-time normalization, source verbatim
Keep
authors[].affiliationas the raw source string per doc (provenance — never lose what the XML said). Add a canonical-name layer the facet builder uses to bucket.Normalization steps (cheap to expensive)
., normalize internal whitespace, foldCo.↔Co↔Company,Inc.↔Inc↔Incorporated,Dept.↔Department, etc."RCA Manufacturing Co., Camden, N. J."→"RCA Manufacturing Co.". Keep the long form for display when the short form has multiple sources, but bucket on the short."MIT"↔"Massachusetts Institute of Technology","USC"↔"University of Southern California". Small curated map.Where the normalization lives
src/main/lib/affiliationNormalize.js— pure function from raw → canonical key + display string.build.search-index.jsre-enablesfacets.affiliationsbucket by emittingcanonical → count(raw strings preserved per-doc on idx row for the URL filter and full-text search).facets.affiliationLabelsmap emitted alongside the bucket so the picker shows the canonical name, not the raw form.docList.jsre-enables the picker entry — samefacetLabel()pattern as ICS codes.Picker UX
Even after normalization, ~1,500 buckets is still a lot for a vertical accordion. Worth pairing with a min-count threshold (only show institutions with ≥5 docs in the picker; long-tail still URL-filterable). Mirrors the existing min-bucket-threshold pattern on keywords if there is one — if not, this issue adds it.
Acceptance
?f.affiliations=RCA+Manufacturing+Co.lands on every variant docRelated