feat(spac): AI SPAC classifier, sponsor promote, de-SPAC linkage (#149, #150, #151)#196
Conversation
#150, #151) Implements three SPAC roadmap items: #149 AI content classifier for miscoded SPACs: an AI classifier behind the existing S1Classification `classifier_source = "ai"` seam catches SPACs filed under a miscoded/absent SIC. Gated on a cheap blank-check keyword heuristic so the AI call stays rare; a confident SPAC verdict flips is_spac, overwrites the classification row, and mints the known-SPAC row. Env: SEC_S1_CLASSIFIER_MODEL / SEC_S1_CLASSIFIER_CONFIDENCE_FLOOR. Added to the eval harness. #150 Sponsor promote economics: extracts founder (Class B) shares + percentage, private-placement warrant count/price/public coverage, and trust per-share/total from the prospectus into a new spac_promote_terms table, via the shared offering-sections runner (S-1 and priced-424). Added to the eval harness. #151 De-SPAC linkage: on a completed combination the rollup derives surviving_name/current_name from the deal target, and recordDeSpacLinkage populates surviving_name/post_merger_sic/post_merger_tickers from the SPAC CIK's post-close entity metadata on the item-2.01 8-K. New `sec spac backfill-despac` refreshes completed SPACs after submissions catch up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqMBp35YzRXtkJMu4Xa4zm
There was a problem hiding this comment.
Pull request overview
Implements three SPAC roadmap items across the S-1 and 8-K pipelines: (1) an AI content classifier to catch SIC-miscoded SPACs, (2) extraction + storage of sponsor-promote economics from SPAC prospectuses, and (3) de-SPAC linkage enrichment from post-close entity metadata (plus a CLI backfill).
Changes:
- Add an AI-gated SPAC classifier (keyword heuristic → model classification → upgrade
is_spac/ mint SPAC row) and evaluation fixtures. - Add sponsor promote extraction + persistence (
spac_promote_terms) and wire it into SPAC offering section processing. - Add de-SPAC linkage derivation/enrichment and wiring on 8-K Item 2.01 close, plus a CLI refresh command.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/storage/spac/spacRollup.ts | Derives surviving/current identity fields from completed deals and post-merger patches. |
| src/storage/spac/spacRollup.test.ts | Tests surviving/current name behavior for completed vs pending deals and explicit overrides. |
| src/storage/spac/SpacReportWriter.ts | Adds recordDeSpacLinkage to enrich post-merger identity from entity metadata. |
| src/storage/spac/SpacReportWriter.deSpac.test.ts | Tests de-SPAC linkage enrichment and no-op conditions. |
| src/storage/offering/SpacPromoteTermsSchema.ts | New storage schema/token for sponsor promote terms table. |
| src/storage/offering/SpacPromoteTermsRepo.ts | Repo wrapper for saving/querying sponsor promote terms. |
| src/storage/offering/SpacPromoteTermsRepo.test.ts | Repo round-trip and query ordering tests. |
| src/sec/forms/registration-statements/s1/sponsorPromoteSchema.ts | DataPort schema for sponsor promote model output. |
| src/sec/forms/registration-statements/s1/spacContentHeuristic.ts | Cheap keyword heuristic to gate classifier calls. |
| src/sec/forms/registration-statements/s1/spacClassifierSchema.ts | DataPort schema/types for SPAC content classification. |
| src/sec/forms/registration-statements/s1/spacClassifierModel.ts | Model resolution + confidence floor config for classifier. |
| src/sec/forms/registration-statements/s1/spacClassifier.test.ts | Tests heuristic + classifier extractor behavior. |
| src/sec/forms/registration-statements/s1/sectionExtractors.ts | Adds extractSponsorPromote and extractSpacClassification. |
| src/sec/forms/registration-statements/s1/offeringSections.ts | Wires sponsor-promote extraction + persistence into offering runner (SPAC-only). |
| src/sec/forms/registration-statements/s1/extractSponsorPromote.test.ts | Tests sponsor promote extractor parsing/null behavior. |
| src/sec/forms/registration-statements/Form_S_1.storage.ts | Integrates AI classifier upgrade path into processFormS1. |
| src/sec/forms/registration-statements/Form_S_1.storage.offering.test.ts | Integration tests for sponsor-promote persistence and SPAC/non-SPAC gating. |
| src/sec/forms/registration-statements/Form_S_1.storage.classifier.test.ts | Integration tests for classifier upgrade/non-upgrade/no-call scenarios. |
| src/sec/forms/miscellaneous-filings/spac8kMilestones.test.ts | Tests de-SPAC linkage wiring on 8-K close (Item 2.01). |
| src/sec/forms/miscellaneous-filings/Form_8_K.storage.ts | Calls recordDeSpacLinkage after completion milestone write. |
| src/eval/fixtures.ts | Adds eval harness extractors + fixtures for sponsor-promote and spac-classification. |
| src/config/TestingDI.ts | Registers in-memory storage for spac_promote_terms in tests. |
| src/config/setupAllDatabases.ts | Ensures spac_promote_terms storage is set up. |
| src/config/resetAllDatabases.ts | Ensures spac_promote_terms storage is reset. |
| src/config/registerModels.ts | Includes SEC_S1_CLASSIFIER_MODEL in registered model IDs. |
| src/config/DefaultDI.ts | Registers persistent storage for spac_promote_terms. |
| src/commands/spac.ts | Adds sec spac backfill-despac CLI to refresh post-merger identity on completed SPACs. |
| CLAUDE.md | Documents classifier gating/config, sponsor promote table, and de-SPAC linkage/backfill command. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Renamed surviving entity: the current entity name once it diverges from | ||
| // the blank-check shell name. Overrides the deal-target-derived fallback. | ||
| if (entity?.name != null && entity.name !== existing.spac_name) { | ||
| patch.surviving_name = entity.name; | ||
| } |
There was a problem hiding this comment.
Good catch — all three comments (this, the post_merger_sic, and the post_merger_tickers ones) were the same real gap: the code overwrote on every run whenever entity data diverged, contradicting the "cannot regress a populated linkage" contract. Fixed in 3c6b481 to make these genuine write-once close-time snapshots:
surviving_name: upgrades the deal-target fallback (or fills an empty slot) exactly once; never overwrites an already entity-sourced value on a later replay/rebrand.post_merger_sic: only set when the slot is still null.post_merger_tickers: only set when still null, deduped + sorted for determinism, and only when the symbol set diverges fromspac_tickers(no more SPAC-era-symbol leak).
Added tests for the later-rebrand no-mutation case and the SPAC-era-ticker non-leak case.
Generated by Claude Code
| // Operating-company SIC once it diverges from the SPAC-era ~6770. | ||
| if (entity?.sic != null && entity.sic !== existing.spac_sic) { | ||
| patch.post_merger_sic = entity.sic; | ||
| } |
| // New listing symbol(s) — JSON string[] mirroring the spac_tickers shape. | ||
| const symbols = tickers.map((t) => t.ticker).filter((s): s is string => !!s); | ||
| if (symbols.length > 0) patch.post_merger_tickers = JSON.stringify(symbols); |
Address review: recordDeSpacLinkage overwrote surviving_name / post_merger_sic / post_merger_tickers on every run whenever entity data diverged from the SPAC-era value, contradicting the "cannot regress a populated linkage" contract, and wrote post_merger_tickers without checking divergence from spac_tickers or sorting (non-deterministic order). - surviving_name: upgrade the deal-target fallback (or fill an empty slot) once; never overwrite an already entity-sourced snapshot on a later replay/rebrand. - post_merger_sic: write-once (only when the slot is still null). - post_merger_tickers: write-once, deduped + sorted for determinism, and only when the symbol set diverges from the SPAC-era tickers. Adds tests for the later-rebrand no-mutation case and the SPAC-era-ticker non-leak case. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqMBp35YzRXtkJMu4Xa4zm
Implements three SPAC roadmap items, based on
harness.#149 — AI content classifier for miscoded SPACs
extractSpacClassificationruns behind the existingS1Classification.classifier_source = "ai"seam to catch SPACs filed under a miscoded/absent SIC. It runs inprocessFormS1only when the deterministic SIC path did not flag the filing and a cheap keyword heuristic (looksLikeBlankCheck, ≥2 blank-check signals) trips, so the AI call stays rare. A confidentspacverdict (it distinguishes a true SPAC fromshell/operating) flipsis_spac, overwrites the classification row as"ai", and mints the known-SPAC row so lifecycle extractors attach. "Not a SPAC" auto-resolves itsMODEL_EMPTYdead-letter (like the LOI detector); a model-unavailable blank-check filing dead-lettersspac-classificationfor retry.SEC_S1_CLASSIFIER_MODEL/SEC_S1_CLASSIFIER_CONFIDENCE_FLOOR; registered insecModelIds(); added toEVAL_EXTRACTORS.#150 — Sponsor promote economics
extractSponsorPromote+ a newspac_promote_termstable capture founder (Class B) shares & percentage, private-placement warrant count/price/public coverage, and trust per-share/total. Keyed(extractor_id, accession_number)likespac_unit_terms, so S-1-registered and 424-final promote compare across extractor ids. Wired into the sharedrunOfferingSections(SPAC-only) so both S-1 and priced-424 pipelines populate it; added to the eval harness and DI.#151 — De-SPAC linkage
On a completed combination,
buildSpacRowderivessurviving_name/current_namefrom the deal target, andSpacReportWriter.recordDeSpacLinkage(invoked on the item-2.01 8-K) fillssurviving_name/post_merger_sic/post_merger_tickersfrom the SPAC CIK's post-closeentity/entity_tickersmetadata — each only when it diverges from the SPAC-era value, so replays stay order-safe.current_cikstays null in the common CIK-survives case. Newsec spac backfill-despacrefreshes completed SPACs after submissions catch up.Testing
npx tsc --noEmitclean.processFormS1integration, sponsor-promote extractor + storage + offering-wiring, de-SPAC rollup + writer + 8-K integration.src/storage,src/config,src/task/forms,src/sec/forms/registration-statements,src/sec/forms/miscellaneous-filings,src/eval.No new extractor id was added — the classifier reuses
S-1, promote reusesS-1/424, de-SPAC linkage rides the8-Kpath. Genuinely separate newco/S-4 CIK linkage remains deferred.🤖 Generated with Claude Code
https://claude.ai/code/session_01KqMBp35YzRXtkJMu4Xa4zm
Generated by Claude Code