Bull vs Bear

An AI credit committee. A user picks a company; a bull and a bear analyst debate its debt out loud from SEC filings and news; a moderator fact-checks every claim and hands back a committee brief.

Most AI tells you what to think; ours shows you what to check.

Built for Hack Knight (Finance, Gemini and ElevenLabs tracks).

Repo layout

bull-vs-bear/
├── frontend/              # Michael: Next.js (home, debate room, brief)
│   ├── app/               #   pages: /, /debate/[id], /brief/[id]
│   └── lib/               #   types.ts (shared shapes), useDebate.ts (WebSocket + audio queue), mock/
├── backend/               # Fahim & Michael: FastAPI
│   ├── app/
│   │   ├── main.py        #   Michael & Fahim: endpoints + debate loop
│   │   ├── schemas.py     #   Michael & Fahim & Alanna: the 3 shared shapes (pydantic)
│   │   ├── edgar.py       #   Alanna: SEC EDGAR: company lookup, structured (XBRL) data, latest 10-K
│   │   ├── filing.py          # Alanna: 10-K HTML → text with page markers; reads untagged numbers
│   │   ├── research.py        #Alanna:  Gemini reads debt instruments + grounded news; code checks every quote
│   │   ├── metrics.py     #   Alanna: debt-to-EBITDA, interest coverage (EBIT),
│   │   │                        floating %, next big maturity, total immediate liquidity, capex / operating cash flow
│   │   ├── facts.py       #   Alanna & Michael:  Builds the fact sheet: metrics + citable sources; caching; outage notices
│   │   ├── verify.py      #   Alanna: verify.py            Fact-checker: NLI model (DeBERTa) labels each claim verified / contested / unsupported
│   │   ├── pacing.py            Keeps generation one line ahead of audio playback
│   │   ├── agents.py      #   Fahim: Gemini bull / bear / moderator / brief
│   │   ├── voice.py       #   Fahim: ElevenLabs (voices, clip generation, audio storage)
│   │   └── store.py       #   Fahim & Michael: Supabase (in-memory fallback)
│   └── tests/
├── shared/                # Michael & Fahim & Alanna
│   ├── schemas/           # JSON Schemas, generated from backend/app/schemas.py
│   └── examples/          # Fake data everyone builds against
├── .env.example           # Key names only, never real keys
└── backend/railway.json   # Backend deploy (Railway)

Everything runs end to end out of the box on stub data. Each TODO(<name>) marks where real work goes.

Run it locally

Backend (Python 3.12+)

cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp ../.env.example .env        # fill in the keys you have; blanks are fine to start
uvicorn app.main:app --reload  # http://localhost:8000/docs
pytest                         # checks shared shapes + metrics

Frontend (Node 20+)

cd frontend
npm install
cp .env.local.example .env.local   # NEXT_PUBLIC_USE_MOCK=true runs without a backend
npm run dev                        # http://localhost:3000
npm test                           # unit tests (Vitest)
npm run typecheck

Set NEXT_PUBLIC_USE_MOCK=false to talk to the real backend.

API

Method Path Returns
GET /health {ok: true}
GET /companies pre-cached demo companies
POST /debates {ticker} {debate_id}
GET /debates/{id} full debate for replay
GET /debates/{id}/brief committee brief
WS /ws/debates/{id} streams fact_sheet, positions, turn_start + line per turn, brief; accepts {type: "interrupt", question}

Shared data shapes

See shared/README.md for the shapes, how ids connect them, the WebSocket protocol and how to change them.

Ground rules

  • The AI never does math. Gemini reads and argues; Python calculates.
  • The AI never decides. No buy, sell or verdict anywhere. The brief ends with "This summarizes the debate. It is not investment advice."
  • Every claim cites a source. No citation means it's labeled Unsupported.
  • API keys stay on the backend. The browser never talks to Gemini or ElevenLabs.
  • Test with text before voice. Keep VOICE_ENABLED=false until the debate reads well.
  • Deploy in the first two hours. Frontend → Vercel (root frontend/), backend → Railway (root /backend, config /backend/railway.json).
  • Cache every demo run. If the network fails on stage, replay a saved debate.
  • Cut, don't slip. Drop in this order: voice interrupts, WebSockets (switch to generate-then-play), contradiction pass, topic tagging.

Who owns what

Person Role
Alanna Data and finance
Fahim AI, voice and backend
Michael Frontend, backend, Gemini fact sheet and demo

Alanna

  • Owns: edgar.py, metrics.py, verify.py
  • Shares: facts.py with Michael

Fahim

  • Owns: agents.py, voice.py
  • ElevenLabs: picks the 3 voices, generates a clip per line, saves clips, streams audio, tracks character credits
  • Shares: the FastAPI app (main.py, store.py) with Michael

Michael

  • Owns: frontend/ (app/, lib/), including audio playback in useDebate.ts
  • Gemini fact sheet: runs the Gemini step in facts.py that turns Alanna's data into the debate fact sheet
  • Shares: facts.py with Alanna; the FastAPI app (main.py, store.py) with Fahim

Everyone

  • Shares: schemas.py and shared/

Shared files: post in the group chat before editing a file you share, so two people don't change it at once. Changes to schemas.py or shared/ affect everyone, since every part of the app builds against those shapes.

Git workflow

  • main always deploys. Work on a branch named <name>/<what> (alanna/metrics, fahim/agents, michael/debate-room) and open a PR.
  • Keep PRs small and merge often. Stay in the files you own; for shared files, coordinate first.
  • Never commit .env.

Built With

Share this project:

Updates

Submission history