-
-
Home Page (Dark Mode 1/2)
-
Home Page (Dark Mode 2/2)
-
Home Page (Light Mode 1/2)
-
Home Page (Light Mode 2/2)
-
History/Past Debates (Dark Mode)
-
Model Selection Selection Overlay (Light Mode)
-
History/Past Debates (Light Mode)
-
Past Debate Entry (Dark Mode)
-
Gemini Token Usage (Light Mode)
-
Debate Citation Overlay on Debate Screen (Light Mode)
-
Debate Briefing, Expanded Full Transcript (Dark Mode)
-
Debate Screen (Dark Mode)
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=falseuntil 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.pywith 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 inuseDebate.ts - Gemini fact sheet: runs the Gemini step in
facts.pythat turns Alanna's data into the debate fact sheet - Shares:
facts.pywith Alanna; the FastAPI app (main.py,store.py) with Fahim
Everyone
- Shares:
schemas.pyandshared/
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
mainalways 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
- css
- elevenlabs
- fastapi
- gemini
- huggingface
- javascript
- json
- node.js
- python
- railway
- supabase
- typescript
Log in or sign up for Devpost to join the conversation.