Skip to content

EITI: accept a derived us_ein alongside registeredAs (fixes the shippable half of #26) - #46

Merged
StephenAbbott merged 1 commit into
StephenAbbott:mainfrom
Nitjsefnie-OSC:eiti-ein-matching
Jul 18, 2026
Merged

EITI: accept a derived us_ein alongside registeredAs (fixes the shippable half of #26)#46
StephenAbbott merged 1 commit into
StephenAbbott:mainfrom
Nitjsefnie-OSC:eiti-ein-matching

Conversation

@Nitjsefnie

Copy link
Copy Markdown
Contributor

Implements #26's "at minimum" tier plus the consumption half of Option A — deliberately not the whole thing; the boundary and why below.

What's in

  • fetch_by_registration(country, registration_number, us_ein=...): registeredAs path byte-identical and tried first; when it misses, the EIN is tried through the same country-scoped, punctuation-insensitive _match_identification (your _norm_forms claim held — the committed artifact stores dashed EINs, and both 42-1638663 and 421638663 match Alpha Natural Resources).
  • Lookup wiring: US: "us_ein" in _EITI_IDENTIFIER_KEY_BY_COUNTRY (so _bh_eiti corroborates on the EIN, per the issue's exact map entry), and the EITI dispatch fires on a derived us_ein even without registeredAs.
  • Tests (6 new + 1 widened) against the real committed artifact: the dashed-format test you asked for, the artifact match, and decoys proving the filter binds — wrong EIN → None, wrong country → None, and a registeredAs decoy absent from the US bucket so the positive match provably comes from the EIN path. Wiring tests each go red with the implementation reverted.

What's deliberately out, and why

EDGAR-side EIN derivation (ctx.derived["us_ein"] has no producer yet, so live US subjects don't match end-to-end from this PR alone — "an EIN arriving from any source joins cleanly" is what's true today, which is the issue's own "once derived" framing). Two structural reasons rather than scope-dodging: _dispatch runs before any EDGAR resolution exists in the loop (CIK lands mid-loop or in the post-loop fallback), so feeding EITI needs a post-loop re-dispatch; and sec_edgar.py currently extracts no EIN at all — that's new API surface (company facts, live-network-gated). Happy to take that as its own PR if you want to settle the re-dispatch shape; same for part 2, which your issue conditions on the upstream filter fix.

Two follow-up notes for whenever derivation lands: the dispatch condition isn't country-gated (a stray non-US us_ein would harmlessly dispatch-and-miss — the scope lives in the matcher), and the BODS mapper's _EITI_SCHEME_BY_COUNTRY has no US entry (plus us_ein isn't in reconcile's _STRONG_KEYS), so reconcile-level merge keys won't fire on EINs yet.

Suite: 2358 → 2364, 0 failed.


Generated by Claude Fable 5 (brief, review), Claude Opus 4.8 (implementation)

US EITI identifications are federal EINs in NN-NNNNNNN form, not the
state-registry numbers GLEIF publishes as registeredAs, so US subjects
never joined the committed EITI index. Wire an EIN path:

- eiti.py: fetch_by_registration accepts a derived `us_ein` tried
  alongside registeredAs. Matching stays country-scoped and (via the
  existing _norm_forms digit-stripping) punctuation-insensitive, so a
  dashed EIN joins the index cleanly.
- lookup.py: add US -> us_ein to _EITI_IDENTIFIER_KEY_BY_COUNTRY so a US
  match corroborates on the EIN, and let a derived us_ein trigger EITI
  dispatch / be passed through.

Tests cover dashed-EIN normalisation, country-scoped matching with
wrong-EIN and wrong-country decoys, the us_ein dispatch/hit wiring, and
the widened conservative-key map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@StephenAbbott
StephenAbbott merged commit 725cc3a into StephenAbbott:main Jul 18, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants