fix(company): state the diacritic gap honestly, and restore the family-key floor - #280
Merged
Merged
Conversation
Two corrections to the company identity story, one in the docs and one
in behavior.
**generateCompanyHash is a derived slug, not the company identity key.**
It folds diacritics; nothing persists it. No table stores
`company_hash_id`, and its one in-repo consumer is the eval scorer's
company match key. The key the company tier actually matches on is
`company_observations.normalized_name`, written by `normalizeCompanyName`
— which does not fold. So `Søren Skou Holdings LLC` and
`Soren Skou Holdings LLC` still mint two canonical companies and two
identity links, and CLAUDE.md said the opposite ("both identity tiers
use it").
The fold is deliberately NOT extended to the persisted key. There is no
rebuild path for a company re-key: `normalized_name` is written only by
the extraction path, and `sec resolve` re-resolves FROM that column
rather than recomputing it, so folding would take effect only by
re-extracting every company-observing form and re-paying the AI cost of
all of them. The prerequisite is a re-normalizing
`ResolveObservationsTask`. Until then the remedy is an explicit
`sec canonical company alias`, and a test pins the gap so the fold cannot
land later as a silent one-line re-key.
**companyFamilyName keeps a series marker that would leave a bare
vehicle word.** Removing the vehicle-word vocabulary also removed the
floor it provided: `Fund III`, `Fund II, L.P.` and `Fund I` all collapsed
to `fund`, as did `Partners III LLC` / `Partners IV LLC` to `partners`
and `Ventures 2021` / `Ventures 2022` to `ventures`. Unrelated sponsor
vehicles named only by a generic word plus a series marker shared one
family — the over-merge the design notes call the worse error, since it
silently attributes one house's deals to another.
The vocabulary comes back as `GENERIC_VEHICLE_WORDS` with the OPPOSITE
polarity: never a strip list, only a floor. `isDroppableTail` splits into
`isLegalTail` (always drops) and `isSeriesTail` (drops unless dropping it
would leave a single generic vehicle word as the whole house name), so
`Churchill Sponsor XIII LLC` → `churchill-sponsor` and
`WAVE Equity Fund II, L.P.` → `wave-equity-fund` are unchanged while
`Fund III` → `fund-iii` ≠ `fund-ii`.
The test that was meant to pin this asserted `"fund"` under a comment
saying the name "is kept whole" — the contract was unpinned in the test
and the doc at once. The "token-exact" test re-anchors on a legal form
(`DirectorCo` ends in `co`, `Cambridge Quantum` in `ua`); its old
`Fundamental` / `Fund` rationale went vacuous when `fund` stopped being
droppable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKvKnyVeQQQCm6FhtaLyMa
…j5s-company-identity-keys
`companyFamilyName` only dropped a trailing series marker, so a sponsor that serializes a vehicle in the MIDDLE of the name got one family per vehicle — the opposite of what the family tier is for. Three real names in the committed golden labels do exactly that: Southern Cross Acquisition I Sponsor Corp. -> ...-acquisition-i-sponsor Southern Cross Acquisition II Sponsor Corp. -> ...-acquisition-ii-sponsor Osprey Acquisition III, Sponsor LLC -> osprey-acquisition-iii-sponsor CGC III Sponsor DirectorCo LLC -> cgc-iii-sponsor-directorco The first two are the same sponsor and did not meet. Across the 129 company-shaped names in the golden labels, 4 keys carried a surviving interior marker; after this change, 0. `Samara Acquisition Sponsor VI` consolidated before only because its numeral happened to land last. Mid-name the rule has to be stricter than at the tail, because position stops being evidence there: - Well-formed roman numerals only. The tail test accepts any run of `ivxlcdm`, which is safe at the end of a name and not in the middle: `civil`, `dim`, `mild`, `vivid` and `lid` all pass it, and deleting one of those from inside a name deletes a real word. - No bare numbers. An interior number belongs to the name — `Route 66 Ventures` would become `route-ventures` — while a TRAILING year is a vintage (`Curnes Fund 2001`) and keeps its existing treatment. - Never the first or last token. A leading numeral is the house's own name (`V Capital`), and the last position already answered to the tail rule including its generic-vehicle floor, so `Fund III` cannot be stripped by the back door. Those two bounds also make the result safe by construction: an interior index has a token on each side, so the name can be neither emptied nor reduced to a single word. The interior test fails against the previous code; the three guard tests (real words, bare numbers, first/last position) pass before and after, which is what scopes the change. Full suite green, tsc clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two corrections to the company identity story — one in the docs, one in behavior.
1.
generateCompanyHashis a derived slug, not the company identity keyIt folds diacritics. Nothing persists it.
grep company_hash_idoutside tests hits onlyCompanyNormalization.tsandsrc/eval/scoreExtraction.ts. The key the company tier actually matches on iscompany_observations.normalized_name, written bynormalizeCompanyName— the function the fold commit deliberately left unchanged, and the columnCompanyResolver's name fallback andcanonical_companyare keyed on.Two spellings of one company still mint two
canonical_companyrows and two identity links, while CLAUDE.md said "both identity tiers use it".Decision: do not fold the persisted key. There is no rebuild path for a company re-key —
normalized_nameis written only by the extraction path, andsec resolvere-resolves from that column rather than recomputing it, so a fold would take effect only by re-extracting every company-observing form and re-paying the AI cost of all of them. The prerequisite for closing the gap is teachingResolveObservationsTaskto re-normalize as it re-partitions.So instead: the fold stays where it is (the eval scorer's company key uses it),
generateCompanyHashgains a JSDoc saying plainly that it is a derived slug, not persisted, not the company identity key; CLAUDE.md is corrected to say the company tier does not fold, withsec canonical company aliasas today's remedy and the re-normalizing resolver named as the prerequisite; and two tests pin the gap, so a future "fix" cannot land as a silent one-line re-key with no migration.2.
companyFamilyName— the deleted vehicle-word list took a floor with itRemoving
VEHICLE_WORDSalso removed the guard they provided. Verified by running the shipped function onmain:Fund IIIfundfund-iiiFund II, L.P.fundfund-iiPartners III LLC/Partners IV LLCpartnerspartners-iii/partners-ivVentures 2021/Ventures 2022venturesventures-2021/ventures-2022Unrelated sponsor vehicles named only by a generic word plus a series marker shared one family — the over-merge the design notes call the worse error, since it silently attributes one house's deals to another and leaves no trace, while an under-merge is visible as two families and costs one alias.
The vocabulary comes back as
GENERIC_VEHICLE_WORDSwith the opposite polarity: never a strip list, only a floor. Nothing in it is ever dropped —Chardan Capital Marketsstill keeps every token. It answers exactly one question: would the name that survives stripping still name a house?isDroppableTailsplits intoisLegalTail(always drops) andisSeriesTail(drops unless dropping it would leave a single generic vehicle word as the whole house name). Unchanged behavior where a real house name survives:Churchill Sponsor XIII LLC→churchill-sponsor(=Churchill Sponsor XIV LLC)WAVE Equity Fund II, L.P.→wave-equity-fundCurnes Fund 2001→curnes-fundBluerock Acquisition Holdings II, LLC→bluerock-acquisition-holdingsTest and doc corrections
CompanyFamilyName.test.ts:110asserted"fund"under a comment saying the name "is kept whole" — the contract was unpinned in the test and the doc at once. Replaced with the real contract, plus a companion test that the series marker still drops when a house name survives it.Fundamental/Fundrationale went vacuous whenfundstopped being droppable. Re-anchored on a legal form, using real names from the committed golden labels:DirectorCoends incoandCambridge Quantuminua, and a substring rule would gut both.companyFamilyNameparagraph now describes the shipped rule, and carries a warning that changing the function re-keys the family tier.Verified
npx vitest run src/storage/company src/resolver src/sec/forms/registration-statements/Form_S_1.storage.underwriters.test.ts src/sec/forms/registration-statements/s1/spacSponsor.e2e.test.ts— 92 pass, 0 fail (16 files).npx tsc --noEmitclean.The three new/changed assertions were confirmed red against the current code before the fix (
Fund IIIreturned"fund", expected"fund-iii").One pre-existing failure on
main, untouched by this PR and present before it:componentRegistry.test.tsexpects 20 registered components and finds 21.resolveexists yet, so in practice a deployment holding such rows re-extracts the affected S-1/424 filings.🤖 Generated with Claude Code
Generated by Claude Code