Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Corp Dev Companion

CI License: MIT

Turn a public company's filings, earnings calls and peer set into concrete M&A options — then force-rank them with pairwise voting instead of arguing about a scoring rubric.

See a finished session → — a complete run against Hamilton Beach Brands: 22 briefing cards, four benchmarked peers, 76 options across three voting rounds and 300 votes. No key, no signup, no network calls.

A room of people cannot reliably rate fifty acquisition ideas on a 1–10 scale. The same room can always answer "which of these two matters more". This app asks that question a few hundred times, fits a Bradley–Terry model to the answers, and returns a ranked list with confidence intervals, a written briefing, and a deck outline.

How it works

  1. Read the companyanalyze-company pulls financial statements, four quarters of earnings-call transcripts, analyst coverage and M&A history from Financial Modeling Prep, then computes the deterministic KPI cards (revenue, margin, leverage, acquisition firepower).
  2. Pick a peer set — SIC-based peer discovery, then a benchmark table across margin, leverage, goodwill intensity and acquisition spend.
  3. Force the choices — three rounds of pairwise voting: strategic posture, then market segments and product categories, then named acquisition targets. New options are injected every few votes based on what is winning.
  4. Get the ranking — Bradley–Terry MLE scores on an Elo-equivalent scale (median 1500) with Fisher-information confidence intervals, plus a strategic narrative.

Sessions run solo, or live with a room full of people via a share code.

Running it locally

npm install
cp .env.example .env      # then fill in the keys below
npx netlify dev           # serves the app and the functions together on :8888

Use netlify dev, not npm run dev — the app calls Netlify Functions for everything. On WSL, Vite's startup can exceed the Netlify CLI's detection window: start Vite first (npx vite &), wait for port 5173, then run npx netlify dev.

Environment

Variable Required What it is
ANTHROPIC_API_KEY yes Claude API key. CLAUDE_API_KEY also works.
FMP_API_KEY yes Financial Modeling Prep — all company data.
VITE_SUPABASE_URL no Enables live multi-user sessions and the AI response cache.
VITE_SUPABASE_ANON_KEY no Public key for the browser client.
SUPABASE_SERVICE_KEY no Server-only; lets functions write the FMP cache.
BRIGHTDATA_API_TOKEN no Enables Crunchbase-backed private-company sourcing.
APIFY_TOKEN no Enables Apify actor sourcing.
CLAY_API_KEY no Enables Clay enrichment of sourced candidates.
VITE_SITE_ORIGIN no Canonical origin for canonical links, Open Graph and sitemap.xml. Set this if you host it publicly, or the sitemap advertises someone else's domain.
VITE_GTM_ID no Google Tag Manager container. Unset means no analytics loads at all — the right default for a self-hosted copy.
RESEND_API_KEY no Contact form delivery. Without it the form returns a clear "not configured" error rather than dropping messages.
CONTACT_TO_EMAIL no Where contact submissions go.

Without the optional keys the app still runs end to end: sessions are solo, target sourcing falls back to the public-company screener, and the marketing pages render with analytics disabled.

The Supabase schema lives in supabase/schema.sql — run it in the SQL editor of a fresh project. Read the SECURITY MODEL comment block in that file before changing any policy.

Commands

npm run build       # vitest + tsc + vite build + prerender the marketing routes
npm test            # unit tests
npm run test:e2e    # Playwright (mocks all functions)
npm run lint        # eslint
npm run check:tokens # fails on a raw hex literal or default-Tailwind palette class

Self-hosting

The app is a React SPA plus a handful of Netlify Functions. There is no database to migrate, no queue and no container — Supabase is optional and only needed for live multi-user sessions.

npm install
cp .env.example .env       # ANTHROPIC_API_KEY and FMP_API_KEY are the only required ones
npm run build
npx netlify deploy --prod  # or point any static host at dist/ and run the functions elsewhere

Two things worth knowing before you put it on the internet:

  • Every session spends real money on the Anthropic and FMP APIs. The functions are origin-checked and rate-limited per instance, but that is a speed bump, not a budget. If your deployment is not meant to be public, put Netlify password protection in front of it.
  • npm run build prerenders the marketing pages to real HTML at /, /method, /contact, /privacy and /terms, and writes sitemap.xml from the same route list. The SPA shell is preserved as app.html, which netlify.toml serves as the fallback for every app route. If you swap in a different host, that fallback rule has to come with you.

Layout

src/pages/              one page per phase
src/components/         voting/, dashboard/, session/, ui/
src/lib/                bradleyTerry, pairingEngine, storage, supabase sync, api client
src/context/            useReducer game state + localStorage persistence
netlify/functions/      all backend endpoints (.mts)
netlify/functions/lib/  guard, claude, promptData, sourcing, schemas
supabase/schema.sql     tables, RLS policies, RPCs

Design system

The visual system is ported from the SG&A Benchmarking diagnostic: navy structure, a single hot magenta accent used only where the reader should act, warm paper ground with white panels, hairline rules, 4px corners, Archivo throughout with tabular figures on every number.

All of it lives in src/index.css as semantic tokens — surface-card, heading, edge, accent, series-1..8. Components reference the token names, never a color value, so the whole app re-skins from that one file. npm run check:tokens fails the build on a raw hex literal or a default-Tailwind palette class (bg-amber-500 and friends), which is how the previous palette drift went unnoticed.

Utilities worth knowing: kicker (uppercase eyebrow), num (tabular figures), page (full-width shell), panel.

Notes for anyone extending it

  • All model access goes through netlify/functions/lib/claude.ts. Model choice, retries, the prompt-cache layout and JSON output live there, so changing models is a one-line edit.
  • Generated quotes are verified. promptData.ts checks every quoted span against the source transcript and regenerates once if a quote cannot be found. Attributing an invented sentence to a named executive is the worst failure this tool could have.
  • Sourced companies carry provenance. Every candidate records where it came from (fmp_screener, crunchbase, apify, static_pool, model_generated) and the UI shows it. Model-generated names are suggestions, not verified companies.
  • The worked example is generated, not hand-written. scripts/build-demo-session.mjs runs the real pipeline against a deployed URL and fits Bradley–Terry between rounds, so each round's options come from the previous round's actual winners. Rebuild it after any prompt change that would alter the output.

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md for the working agreements, and SECURITY.md if you have found something that should not be reported in public.

What this is not

It does not value anything, check whether a target is for sale, or replace diligence. The output is a prioritized agenda for the next conversation. Portions are generated by a language model: quotes are verified against their source transcript and the headline metrics are computed in code, but interpretation is inference and any company name labelled Unverified came from the model rather than a data source.

License

MIT — see LICENSE.

About

Turn a public company's filings, earnings calls and peer set into concrete M&A options, then force-rank them with pairwise voting and Bradley-Terry scoring.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages