# Project export: SportsMom

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Document, Plan, Compete. Tailored for elite SOLO athletes.
- Devpost: https://devpost.com/software/sportsmom-ai
- GitHub: https://github.com/JayaniSND/TeamUP
- Demo: https://asi1.ai/ai/agent1qg4n8h6w54c4uapkpa040gatncmdz2lqmswy2w9295unzlnjvm2yqevmg3y
- Video: https://www.youtube.com/embed/bPZfJjBKmKM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — Kyaw Thi Ha (24 commits), yuhwaL (18 commits), ren (16 commits), Jayani (13 commits)

## Devpost submission (written by the team)

### Overview

Elevator pitch SportsMom is the support team a self-managed athlete never had: a network of Fetch.ai agents that turns a voice note, a notebook photo, or a text dump into overtraining flags, performance trends, opponent scouting reports, sponsorship drafts, and fully-booked tournament trips all from a single ASI:One chat.

### Inspiration

Pro athletes have a coach, a physio, a data analyst, an agent, and a travel manager. Everyone else has a notebook and a dream. Take tennis. Only about the top 150 players in the world break even financially, and even among the top junior prospects, barely a third ever turn a profit over an entire career. The other 99%, the kid grinding ITF W15s, the club runner, the amateur fighter, are self-funded and on their own. And the cruel part is that they're not short on data. They track training loads, match notes, and how the knee felt this morning. They're drowning in it. They just do almost nothing with it, because turning scattered notes into decisions is a full-time job nobody is paying them to do. So we wanted to give a solo athlete that entire backroom team as a swarm of agents. Not one chatbot pretending to be five experts, but five real specialists that each own their domain, talk to each other, and reach the athlete wherever they already are: a chat window. You dump. They handle the rest.

### What it does

The athlete sends raw input: a voice note after practice, a photo of a handwritten notebook page, or typed text. From there, a team of seven uAgents takes over: Everything lands in Supabase and surfaces two ways at once: as ASI:One chat replies, and on a React "Aurora Glass" dashboard. The chat-only flow and the dashboard always show the same truth.

### How we built it

Fetch.ai uAgents + Agentverse all seven agents run as mailbox agents on Agentverse, registered through the Almanac and reachable by a stable handle. Every one implements the Agent Chat Protocol, which is what makes the whole system usable from ASI:One with no custom frontend at all. Agent-to-agent orchestration the Logistics agent collaborates with two independently operated Flights and Hotels agents on the open marketplace, purely through natural language, across organizational boundaries. Anthropic Claude Haiku 4.5 for high-volume classification, Sonnet 4.6 for the specialists' reasoning and synthesis, and Claude vision for reading handwritten notebook photos. Deepgram nova-3 pre-recorded and live-streaming voice transcription. Browserbase + Stagehand natural-language browser automation for ITF tournament search and opponent scouting, with no hardcoded selectors. Redis (RedisVL + LangCache) KNN vector retrieval and semantic answer caching behind the RAG chat endpoint. Supabase (Postgres) system of record for every table. Stripe a real test-mode Checkout gate, implementing the Fetch.ai Payment Protocol on the tournament-entry flow. FastAPI + React 18 / Vite / Tailwind v4 the data layer and the dashboard.

### Challenges we ran into

Making specialists collaborate without a god-agent. The Orchestrator had to fan a single log out to multiple specialists and correlate their replies into one coherent answer, instead of dumping five disconnected blobs on the athlete. Cross-org agent negotiation. Getting Logistics to plan a trip by talking to Flights and Hotels agents we don't own over natural language alone meant designing for ambiguity, timeouts, and partial answers rather than a clean API contract. A real payment gate, not a mock. Wiring a live Stripe test-mode Checkout into a multi-turn agent conversation (search → pick within a 10-minute window → pay → review) without ever auto-submitting an actual ITF entry. Graceful degradation everywhere. If Redis is down, retrieval falls back to recency. No Browserbase key? Scout/Logistics use whatever is already in Supabase. No Stripe key? The payment step is simply skipped. A demo should never go dark. Keeping two front-ends honest. The ASI:One chat flow and the React dashboard read the same data, so an insight generated in chat has to show up on the dashboard and vice versa with no drift.

### Accomplishments we're proud of

A genuine multi-agent system seven specialists, not one prompt wearing five hats that runs end-to-end from a single ASI:One chat with zero custom frontend required. True agent-to-agent commerce: planning a real tournament trip by negotiating with independent third-party agents and closing it with a live payment. A multimodal front door: voice, handwritten photo, or text all collapse into the same clean pipeline. The whole thing fails soft every external dependency degrades gracefully instead of crashing the demo.

### What we learned

Multi-agent systems live or die on the boundaries between agents, not the intelligence inside any one of them. The hard, interesting work was in routing, correlation, and negotiation — deciding who handles what, how partial answers get stitched together, and how agents that have never met cooperate over plain language. We also learned that the chat protocol is a genuine product surface: when an athlete can run the entire workflow from one conversation, the "app" almost disappears.

### What's next

More sports beyond tennis: running, combat sports, and other self-managed individual disciplines. A proactive coach: agents that reach out before you ask, when the data crosses a line. Wearable ingestion (Whoop, Garmin, Apple Health) feeding Recovery and Performance directly. A real send-path for Sponsorship outreach, behind explicit human approval. Athlete-to-athlete agents: sparring-partner matching and shared-travel coordination across the marketplace. Shared Chat Link: https://asi1.ai/shared-chat/d678e3ed-5c6f-4326-887d-6dbb704c8099 Try our SportsMom agent here: https://asi1.ai/ai/agent1qg4n8h6w54c4uapkpa040gatncmdz2lqmswy2w9295unzlnjvm2yqevmg3y @agent1qg4n8h6w54c4u Others: sportsmom-orchestrator: main ASI:One coordinator, intent router, and demo entrypoint sportsmom-librarian: classifies raw voice, photo, and text dumps into dashboard sections sportsmom-recovery: flags overtraining, fatigue patterns, and injury risk with wellness-safe language sportsmom-performance: tracks win/loss trends, skill progression, and recommends training focus sportsmom-sponsorship: matches athletes to brand fits and drafts approval-gated outreach emails sportsmom-scout: researches opponents and generates tactical game plans via live web scraping sportsmom-logistics: finds flights, hotels, and ITF tournaments with calendar sync and a payment gate Built with fetch.ai · agentverse · uagents · asi:one · agent-chat-protocol · anthropic · claude · deepgram · browserbase · stagehand · redis · redisvl · supabase · postgres · stripe · fastapi · python · react · typescript · vite · tailwindcss · ocr

## README (from the GitHub repository)

# SportsMom

**SportsMom: Multi-Agent Sports Analytics OS**

SportsMom is a network of specialist uAgents that acts as the support team a self-managed individual-sport athlete (tennis first) doesn't have. The athlete dumps a voice note, a photo of a handwritten notebook page, or typed text, and the agent network transcribes it, classifies it, files it, and turns it into overtraining flags, performance trends, opponent scouting reports, sponsorship drafts, and fully researched tournament trips, all reachable through a single ASI:One chat session, with a React dashboard on top for the days you'd rather look than talk.

- Raw input (voice / photo / text) is normalized to plain text.
- The Librarian classifies and files it into structured journal entries.
- The Orchestrator routes intent to the right specialist agent(s).
- Specialists (Recovery, Performance, Scout, Sponsorship, Logistics) reason over the athlete's data and return insights or drafts.
- Outcomes land in Supabase and surface through the dashboard, ASI:One replies, and (for Logistics) a real Stripe-gated tournament-entry flow.

## Agent roles and responsibilities

### 1) Orchestrator Agent

- The single ASI:One / Agentverse entry point for the whole system.
- Classifies every incoming message into one of three intents: `log`, `ask`, or `action`.
- `log` → Librarian → fan-out to specialists → correlates results into one reply.
- `ask` → routes to the `POST /chat` RAG endpoint and relays the grounded answer.
- `action` → routes straight to the named specialist (Recovery / Performance / Sponsorship / Logistics / Scout).
- Falls back to inline Claude calls when no worker addresses are configured, so a demo never goes dark.

### 2) Librarian Agent

- The front door for every raw dump.
- Uses Claude Haiku 4.5 to split a dump into entries across nine sections: training, performance, match_results, recovery, coaching, logistics, sponsorship, goals, media_notes.
- Writes each entry to Supabase via `POST /entries` and notifies whichever specialists care about what just got filed.
- Runs in two modes: standalone (replies directly over ASI:One) and orchestrator-driven (returns a `classify_result` envelope).

### 3) Recovery Agent

- Pulls the last 60 journal entries plus 14 days of recovery logs, training sessions, and metrics.
- Asks Claude Sonnet 4.6 for a risk level (none / low / medium / high), affected body parts, a recommended action, and a behavioral pattern summary (e.g. a burnout arc).
- Suggests which agent to open next, surfaced as text rather than auto-dispatched.
- A 72-hour deduplication guard prevents duplicate alerts from the same passive sweep.

### 4) Performance Agent

- Reads the last 10 matches plus recent training sessions and 20 recent metrics.
- Returns a trend (improving / stable / declining), the athlete's strongest and weakest area, and a recommended focus.
- Triggers automatically whenever a match result, training log, or performance entry is filed.
- Output lands in the same `agent_outputs` table the dashboard reads from.

### 5) Scout Agent

- Takes a match log or an opponent's name and sends Browserbase + Stagehand out to research them on the open web.
- Has Claude synthesize the research into patterns, strengths, weaknesses, and a tactical recommendation.
- Files the report as both a coaching journal entry and an `agent_outputs` card.
- Triggers automatically whenever a match result comes in.

### 6) Sponsorship Agent

- Reads the athlete's profile, recent matches, metrics, and media notes.
- Has Claude pick a brand fit and score it, then drafts a cold outreach email.
- Status always stays "drafted"; nothing sends itself. The frontend's draft → approve → send flow is the only path an email can actually leave through.

### 7) Logistics Agent

- Runs as a 3-stage interactive conversation rather than a single request/reply.
- **Search:** parallel fan-out to an external Flights agent and an external Hotels agent on the open Agentverse marketplace, plus a live ITF W15/W25 tournament scrape via Browserbase; resolved dates create a Google Calendar event.
- **Pick:** the athlete replies with a number to choose a tournament (10-minute window).
- **Payment gate (optional):** a Stripe TEST mode Checkout Session ($5.00 fee); on confirmation shows the ITF entry review screen, but never auto-submits the actual entry.

## Protocols and standards used

- **Fetch.ai Agent Chat Protocol:** implemented by all seven agents, which is what makes the whole workflow usable directly from an ASI:One conversation with no custom frontend required.
- **Fetch.ai Agentverse:** every agent runs as a mailbox agent, registered and reachable by a stable handle/address through the Almanac.
- **ASI:One:** the user-facing conversational entry point and bonus-track router.
- **Fetch.ai Payment Protocol:** implemented via the Logistics agent's Stripe Checkout step.
- **Agent-to-agent orchestration:** Logistics collaborates with two independently operated Flights/Hotels agents purely through natural language, across organizational boundaries.
- **Anthropic Claude:** Haiku 4.5 for high-volume classification, Sonnet 4.6 for synthesis/reasoning (Recovery, Performance, Scout, Sponsorship, Logistics), and Claude vision for notebook-photo OCR.
- **Deepgram nova-3:** pre-recorded REST transcription and live streaming mic transcription.
- **Browserbase + Stagehand:** natural-language browser automation for ITF tournament search and opponent research, no hardcoded selectors.
- **Redis (RedisVL + LangCache):** KNN vector retrieval and semantic answer caching behind the RAG chat endpoint.
- **Supabase (Postgres):** the database every table (entries, matches, recovery_logs, metrics, agent_outputs, sponsorship_opportunities, calendar_events) writes to.
- **Sentry (optional):** error monitoring and performance tracing, initialized only if `SENTRY_DSN` is set.

## High-level workflow

1. Athlete sends a voice note, notebook photo, or typed text.
2. Voice → `POST /convert/voice` (Deepgram nova-3); Photo → `POST /convert/photo` (Claude vision); Text needs no conversion.
3. `POST /ingest` runs the full pipeline: transcribe/OCR → classify (Librarian) → embed → store (Supabase).
4. Orchestrator classifies intent and routes to the Librarian, the RAG chat endpoint, or a named specialist.
5. Specialist agents read from Supabase, reason with Claude, and write results to `agent_outputs` (or `sponsorship_opportunities` / `calendar_events` for Sponsorship and Logistics).
6. Frontend dashboard and ASI:One replies both read the same underlying data, so the chat-only flow and the dashboard flow always stay in sync.
7. For Logistics, a confirmed tournament pick can optionally pass through a real Stripe test-mode payment gate before showing (never submitting) the ITF entry review.

## Project structure

```
TeamUP/
├── AGENTS.md                  # Frontend design system spec ("Aurora Glass")
├── Backend/
│   ├── agents/
│   │   ├── orchestrator.py    # Intent router and conductor
│   │   ├── librarian.py       # Classification and filing
│   │   ├── recovery.py        # Overtraining / wellness detection
│   │   ├── performance.py     # Trend analysis
│   │   ├── scout.py           # Opponent research
│   │   ├── sponsorship.py     # Brand matching + email drafting
│   │   └── logistics.py       # Travel, tournaments, calendar, payment
│   ├── common/                 # Shared chat/config/backend-client helpers
│   ├── integrations/           # Browserbase/Stagehand, Stripe, Google Calendar, Deepgram
│   ├── processing/             # voicetotext.py, phototext.py: input normalization
│   ├── readmes/                 # Agentverse Overview READMEs, one per agent
│   ├── authorize_gcal/          # Google Calendar OAuth flow
│   ├── calandar_client.py
│   ├── config.py
│   ├── database.py
│   ├── main.py                  # FastAPI backend (Supabase-backed)
│   ├── mock_backend.py          # Local mock backend for offline/seeded demos
│   ├── rag.py

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 121 recognized source files, 937 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Redis (technology) — claimed on Devpost, not found in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 138)

```
.DS_Store
.gitignore
AGENTS.md
Backend/.dockerignore
Backend/.env.example
Backend/agents/__init__.py
Backend/agents/authorize_gcal
Backend/agents/calandar_client.py
Backend/agents/coaching.py
Backend/agents/common/__init__.py
Backend/agents/common/backend_client.py
Backend/agents/common/booking_intent.py
Backend/agents/common/calendar_client.py
Backend/agents/common/chat.py
Backend/agents/common/claude.py
Backend/agents/common/config.py
Backend/agents/common/domain_router.py
Backend/agents/fitness.py
Backend/agents/integrations/__init__.py
Backend/agents/integrations/tournaments.py
Backend/agents/librarian.py
Backend/agents/logistics.py
Backend/agents/orchestrator.py
Backend/agents/performance.py
Backend/agents/processing/__init__.py
Backend/agents/processing/phototext.py
Backend/agents/processing/textinput.py
Backend/agents/processing/voicetotext.py
Backend/agents/README.md
Backend/agents/recovery.py
Backend/agents/scout.py
Backend/agents/sponsorship.py
Backend/asi_ping.py
Backend/database.py
Backend/Dockerfile
Backend/main.py
Backend/mock_backend.py
Backend/rag.py
Backend/readmes/coaching.md
Backend/readmes/fitness.md
Backend/readmes/librarian.md
Backend/readmes/logistics.md
Backend/readmes/orchestrator.md
Backend/readmes/performance.md
Backend/readmes/recovery.md
Backend/readmes/scout.md
Backend/readmes/sponsorship.md
Backend/requirements.txt
Backend/run_local.py
Backend/schema.sql
Backend/send_dump.py
Backend/services/__init__.py
Backend/services/agent_event_tracker.py
Backend/services/athlete_context.py
Backend/services/booking_service.py
Backend/services/orchestrator_service.py
Backend/services/user_identity.py
framework_draft.md
framework_v3.md
framework_v4.md
framework_v5.md
framework.md
Frontend/.env.example
Frontend/.gitkeep
Frontend/index.html
Frontend/package.json
Frontend/README.md
Frontend/requirements.txt
Frontend/src/App.tsx
Frontend/src/components/agents/AgentActivityStatus.tsx
Frontend/src/components/agents/AgentCanvasBoundary.tsx
Frontend/src/components/agents/AgentConnectionLine.tsx
Frontend/src/components/agents/AgentConnectionLine3D.tsx
Frontend/src/components/agents/AgentNetwork.tsx
Frontend/src/components/agents/AgentNetwork3D.tsx
Frontend/src/components/agents/AgentNode.tsx
Frontend/src/components/agents/FloatingAgentNode.tsx
Frontend/src/components/agents/LiveAgentPanel.tsx
Frontend/src/components/calendar/BookingConfirmationModal.tsx
Frontend/src/components/calendar/PaymentModal.tsx
Frontend/src/components/calendar/PaymentNoticeToast.tsx
Frontend/src/components/dashboard/AgentInsightCard.tsx
Frontend/src/components/dashboard/AgentInsightsStrip.tsx
Frontend/src/components/dashboard/AIChatPanel.tsx
Frontend/src/components/dashboard/ChatThread.tsx
Frontend/src/components/dashboard/DashboardHeader.tsx
Frontend/src/components/dashboard/Hero.tsx
Frontend/src/components/dashboard/IntakeStrip.tsx
Frontend/src/components/dashboard/Markdown.tsx
Frontend/src/components/dashboard/MatchResultsPanel.tsx
Frontend/src/components/dashboard/MetricCard.tsx
Frontend/src/components/dashboard/OverviewCards.tsx
Frontend/src/components/dashboard/PerformanceChart.tsx
Frontend/src/components/dashboard/RecoveryPanel.tsx
Frontend/src/components/dashboard/SchedulePanel.tsx
Frontend/src/components/dashboard/SectionCard.tsx
Frontend/src/components/dashboard/Sidebar.tsx
Frontend/src/components/dashboard/SponsorshipPanel.tsx
Frontend/src/components/dashboard/TrainingPanel.tsx
Frontend/src/components/dashboard/UpcomingCard.tsx
Frontend/src/components/dashboard/useChatSession.ts
Frontend/src/components/dashboard/WeeklyCalendar.tsx
Frontend/src/components/landing/LandingAgentConstellation3D.tsx
Frontend/src/components/ui/Badge.tsx
Frontend/src/components/ui/Button.tsx
Frontend/src/components/upload/FileDrop.tsx
Frontend/src/components/upload/VoiceRecorder.tsx
Frontend/src/context/CalendarEventsContext.tsx
Frontend/src/context/ChatSessionContext.tsx
Frontend/src/data/mockAthleteData.ts
Frontend/src/data/mockCalendarEvents.ts
Frontend/src/index.css
Frontend/src/lib/agents/agentActivityStore.ts
Frontend/src/lib/agents/agentFormationLayout.ts
Frontend/src/lib/agents/backendAgentRegistry.ts
Frontend/src/lib/agents/predictAgents.ts
Frontend/src/lib/api.ts
Frontend/src/lib/bookingIdentity.ts
Frontend/src/lib/icons.ts
Frontend/src/lib/paymentSessionPersistence.ts
[18 more files omitted for size]
```

### Dependencies

- Backend/requirements.txt: anthropic@>=0.40.0, browserbase@>=1.0.0, deepgram-sdk@>=3.7.0, fastapi@>=0.111.0, google-api-python-client@>=2.130.0, google-auth@>=2.30.0, google-auth-httplib2@>=0.2.0, google-auth-oauthlib@>=1.2.0, httpx@>=0.27.0, openai@>=1.40.0, playwright@>=1.40.0, python-dotenv@>=1.0.1, python-multipart@>=0.0.9, redisvl@>=0.20.1, sentence-transformers@>=5.6.0, sentry-sdk[fastapi]@>=2.0.0, sounddevice@>=0.5.0, stagehand@>=3.0.0, stripe@>=9.0.0, supabase@>=2.0.0, uagents@>=0.22.0, uvicorn[standard]@>=0.30.0
- Frontend/package.json: @react-three/drei@^9.122.0, @react-three/fiber@^8.18.0, @tailwindcss/vite@^4.0.0, @types/node@^22.20.0, @types/react@^18.3.18, @types/react-dom@^18.3.5, @types/three@^0.171.0, @vitejs/plugin-react@^4.3.4, clsx@^2.1.1, framer-motion@^11.15.0, gsap@^3.15.0, lucide-react@^0.469.0, react@^18.3.1, react-dom@^18.3.1, react-router-dom@^6.30.4, recharts@^2.15.0, tailwind-merge@^2.6.0, tailwindcss@^4.0.0, three@^0.171.0, typescript@^5.7.2, vite@^6.0.7
- Frontend/requirements.txt: annotated-types@==0.7.0, anyio@==4.14.0, certifi@==2026.6.17, cffi@==2.0.0, deepgram-sdk@==7.3.1, h11@==0.16.0, httpcore@==1.0.9, httpx@==0.28.1, idna@==3.18, pycparser@==3.0, pydantic@==2.13.4, pydantic_core@==2.46.4, python-dotenv@==1.2.2, sounddevice@==0.5.5, typing_extensions@==4.15.0, typing-inspection@==0.4.2, websockets@==16.0
- requirements.txt: annotated-types@==0.7.0, anyio@==4.14.0, certifi@==2026.6.17, cffi@==2.0.0, charset-normalizer@==3.4.7, deepgram-sdk@==7.3.1, h11@==0.16.0, httpcore@==1.0.9, httpx@==0.28.1, idna@==3.18, pycparser@==3.0, pydantic@==2.13.4, pydantic_core@==2.46.4, python-dotenv@==1.2.2, requests@==2.34.2, sounddevice@==0.5.5, stripe@==15.2.1, typing_extensions@==4.15.0, typing-inspection@==0.4.2, urllib3@==2.7.0, websockets@==16.0

### Recent commits (newest first)

- update live agent tracking
- 3D components update
- Merge branch 'main' of https://github.com/JayaniSND/TeamUP
- Upgraded 3D agent component
- README: remove ASCII banner
- Chat: instruct orchestrator to format replies as Markdown (renders cleanly in ASI:One)
- README: add SportsMom ASCII banner
- Merge branch 'main' of https://github.com/JayaniSND/TeamUP
- label
- Upgrade 3D live agent visualization
- live agent activity tracking
- Merge branch 'main' of https://github.com/JayaniSND/TeamUP
- Add 3D components for Agent Visualization
- Frontend: render chat answers as Markdown (was showing raw ## / ** / lists)
- Fix user_id resolution: alias 'demo-athlete' must resolve to the UUID, not itself
- Fix chat 'no records' bug: fall back to Supabase when RAG returns empty
- Add Backend/schema.sql — full Supabase schema matching main.py
- requirements: add sentry-sdk (main.py imports it at startup — was missing)
- Merge branch 'main' of https://github.com/JayaniSND/TeamUP
- Backend request data directly to the database

## Key source files (fetched from GitHub, selected and truncated for size)

### AGENTS.md

```markdown
# Athlete Analytics OS — Frontend Dashboard

> The athlete dashboard that builds itself from your notebooks and voice logs.

This file is the **source of truth** for the frontend design system and build plan. It was
researched and refined before any code was written (see _Design Research_ below), then used to
build the dashboard in `Frontend/`.

---

## Product context (from `framework_draft.md`)

A personal sports-analytics OS for **self-managed individual-sport athletes** (tennis first).
The athlete brain-dumps after practice or a match — voice, text, or a photo of their paper
notebook — and an agent network underneath transcribes it, files each piece into the right
section, surfaces progress graphs, answers questions about their own history, flags
overtraining, finds their next tournament, and drops it on their calendar.

The **journal is the face; the autonomous agent layer is the engine.** The dashboard's job is to
make that agent layer _legible_ — every panel reflects what an agent (Librarian, Recovery,
Logistics, Scout) produced from raw athlete input.

**Backend sections (single source of truth, mirror in the frontend types):**
`performance · injury_log · coaching · energy_nutrition · logistics · training`
**Metrics seen so far:** `training_volume_mi`, `recovery_score`.
Demo athlete = tennis player with a recurring **right-knee soreness / overtraining** pattern.

---

## Aurora Glass Dashboard Design System

This is the current frontend visual source of truth. Future frontend work must follow the
**Aurora Glass Dashboard** style in its approved white / green direction. Do not revert to the
old dark blue / purple futuristic direction, and do not flatten it into a generic plain SaaS UI.

**Visual style direction:** clean, premium, calm glassmorphism-inspired dashboard with Apple-like
softness and a compact SaaS command-center layout. The UI should feel like one polished product
window floating over a white, soft off-white, or very pale green background.

**Color palette:** white, off-white, and very pale green backgrounds; mint, sage, emerald, and
teal-green accents; dark charcoal or deep green-gray primary text; muted gray-green secondary
text; soft green-tinted borders; and semantic status colors tuned for a light glass interface:
green positive, soft amber warning, muted rose negative only where needed, teal neutral, and
sage/emerald AI.

**Typography rules:** use Inter/system sans. Keep labels small, uppercase, and widely tracked.
Use medium/semi-bold weights rather than heavy bold. Metric values use `tabular-nums`, large type,
and high-contrast dark text or emerald/teal accent colors.

**Layout rules:** the app shell is a centered rounded glass container with an internal left
sidebar divider, compact top toolbar, bento dashboard grid, and optional right AI rail. Desktop
should feel information-rich without becoming crowded; tablet and mobile stack cards cleanly.

**Glassmorphism rules:** major surfaces use translucent white or green-ti
[truncated — 10467 more characters]
```

### framework.md

```markdown
# BASELINE — Build Framework

**A personal sports analytics OS for individual athletes — built from their own notebooks, voice logs, training history, game results, recovery patterns, and sponsorship potential.**

This is not "a journal with AI." The stronger framing:

> **An AI sports analytics dashboard that builds itself from an athlete's personal logs.**

The notebook photo, voice dump, and text input are the *input methods*. The
dashboard is the *product*. The autonomous agents are the *engineering story*
(and the Fetch.ai prize).

Pitch: *"It turns messy athlete logs into a live sports analytics dashboard."*
Shortest: *"The athlete dashboard that builds itself from your notebooks and voice logs."*

> Supersedes `framework_draft.md`. Track: **UC Berkeley AI Hackathon 2026 — Fetch.ai**.

---

## 0. Stack & track alignment

| Layer | Tool | Prize / reason |
|---|---|---|
| Frontend dashboard | Next.js + Tailwind + shadcn/ui | — |
| API backend + RAG | FastAPI (same runtime as uAgents) | — |
| Agents | **Fetch.ai uAgents** on **Agentverse**, **Agent Chat Protocol** | **Fetch.ai (co-host)** |
| Agentic entry point | **ASI:One** (`api.asi1.ai/v1`, model `asi1`) | **Fetch.ai** |
| Reasoning inside agents | **Claude** (`claude-haiku-4-5` classify, `claude-sonnet-4-6` synthesis) | **Anthropic** |
| Voice → text | **Deepgram** | **Deepgram** |
| Photo → text | **Claude vision** (no separate OCR) | **Anthropic** |
| Vector store + cache | **Redis** (RediSearch) | **Redis** |
| Structured data | Postgres (Supabase) | — |
| Tournament/brand search | **Browserbase** | **Browserbase** |
| Calendar | Google Calendar API | — |

**Fetch.ai track requirements (must hold at submission):**

- Agents built on **uAgents** and registered on **Agentverse** (`mailbox=True`).
- Implement the **Agent Chat Protocol** (`uagents_core.contrib.protocols.chat`):
  handle `StartSessionContent` / `TextContent` / `EndSessionContent`, send
  `ChatAcknowledgement`, and `agent.include(chat_proto, publish_manifest=True)`.
- At least one agent **discoverable and directly usable through ASI:One**.
- The **primary workflow completes without a custom frontend** (works in a chat
  session). The dashboard is the polished surface on top, not a dependency for
  the agent demo.
- Each agent's Agentverse **Overview** README includes its name, address, and the
  **Innovation Lab** badge.
- Bonus: multi-agent orchestration, real-time data, payment protocol.

---

## 1. Target user

**Individual, self-managed athletes** across sports — tennis, swimming, running,
badminton, track, golf, fighters, and others. They manage alone what a team's
staff normally would: training notes, match results, recovery/soreness, travel
and logistics, performance trends, sponsorship, goals, schedule, coaching.

---

## 2. Core product flow

```txt
Notebook photo / voice dump / text input
        ↓ preprocessing (Claude vision / Deepgram)
Parser / Librarian Agent  (classify → sections)
        ↓
Specialized Agents 
[truncated — 14258 more characters]
```

### requirements.txt

```
annotated-types==0.7.0
anyio==4.14.0
certifi==2026.6.17
cffi==2.0.0
charset-normalizer==3.4.7
deepgram-sdk==7.3.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.18
pycparser==3.0
pydantic==2.13.4
pydantic_core==2.46.4
python-dotenv==1.2.2
requests==2.34.2
sounddevice==0.5.5
stripe==15.2.1
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.7.0
websockets==16.0

```

### Frontend/requirements.txt

```
annotated-types==0.7.0
anyio==4.14.0
certifi==2026.6.17
cffi==2.0.0
deepgram-sdk==7.3.1
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.18
pycparser==3.0
pydantic==2.13.4
pydantic_core==2.46.4
python-dotenv==1.2.2
sounddevice==0.5.5
typing-inspection==0.4.2
typing_extensions==4.15.0
websockets==16.0

```

### Backend/Dockerfile

```
FROM python:3.12-slim

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    curl \
  && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```

### Frontend/package.json

```
{
  "name": "sportsmom",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@react-three/drei": "^9.122.0",
    "@react-three/fiber": "^8.18.0",
    "@types/three": "^0.171.0",
    "clsx": "^2.1.1",
    "framer-motion": "^11.15.0",
    "gsap": "^3.15.0",
    "lucide-react": "^0.469.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router-dom": "^6.30.4",
    "recharts": "^2.15.0",
    "tailwind-merge": "^2.6.0",
    "three": "^0.171.0"
  },
  "devDependencies": {
    "@tailwindcss/vite": "^4.0.0",
    "@types/node": "^22.20.0",
    "@types/react": "^18.3.18",
    "@types/react-dom": "^18.3.5",
    "@vitejs/plugin-react": "^4.3.4",
    "tailwindcss": "^4.0.0",
    "typescript": "^5.7.2",
    "vite": "^6.0.7"
  }
}

```

### Backend/requirements.txt

```
# Agent layer (Fetch.ai uAgents + Claude reasoning)
uagents>=0.22.0
anthropic>=0.40.0
httpx>=0.27.0
python-dotenv>=1.0.1
deepgram-sdk>=3.7.0
sounddevice>=0.5.0

# ASI:One bonus router (OpenAI-compatible client)
openai>=1.40.0

# Logistics/Scout — tournament + opponent scraping (optional; agents fall back
# to cached results if absent). Run `playwright install chromium` once if used.
browserbase>=1.0.0
playwright>=1.40.0
# stagehand drives the Browserbase cloud browser for the interactive Logistics
# agent (provides `from stagehand import Stagehand`). The package is `stagehand`,
# NOT `stagehand-python` (which doesn't exist on PyPI).
stagehand>=3.0.0

# Logistics -> Google Calendar (optional; events still persist to the backend)
google-api-python-client>=2.130.0
google-auth>=2.30.0
google-auth-oauthlib>=1.2.0
google-auth-httplib2>=0.2.0

# Data backend (Supabase) + local mock backend
supabase>=2.0.0
fastapi>=0.111.0
uvicorn[standard]>=0.30.0
# RAG retrieval + semantic cache (rag.py)
redisvl>=0.20.1
sentence-transformers>=5.6.0

# Travel booking checkout (Stripe test mode). Optional — without STRIPE_SECRET_KEY
# the Book & Pay flow degrades to a "not configured" message.
stripe>=9.0.0

# Multipart form parsing for FastAPI file uploads (/convert/photo, /convert/voice)
python-multipart>=0.0.9

# Error monitoring / observability (main.py imports sentry_sdk at startup, so
# this is required for the real backend to boot — runs without a SENTRY_DSN).
sentry-sdk[fastapi]>=2.0.0

```

### Frontend/src/main.tsx

```typescript
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import App from "./App";
import CalendarPage from "./pages/CalendarPage";
import UploadPage from "./pages/UploadPage";
import AssistantPage from "./pages/AssistantPage";
import LandingPage from "./pages/LandingPage";
import CheckoutReturnPage from "./pages/CheckoutReturnPage";
import { BookingConfirmationModal } from "./components/calendar/BookingConfirmationModal";
import { PaymentNoticeToast } from "./components/calendar/PaymentNoticeToast";
import { PaymentModal } from "./components/calendar/PaymentModal";
import { CalendarEventsProvider } from "./context/CalendarEventsContext";
import { ChatSessionProvider } from "./context/ChatSessionContext";
import "./index.css";

// One generic greeting for the single shared chat thread (dashboard rail + the
// full-page assistant render the SAME conversation).
const CHAT_GREETING =
  "Hi — I'm your athlete assistant, wired into your training, recovery, match, schedule and sponsorship data. Ask me anything, or have me plan travel and booking for an upcoming event.";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <BrowserRouter>
      <CalendarEventsProvider>
        <ChatSessionProvider greeting={CHAT_GREETING}>
          <Routes>
            <Route path="/" element={<LandingPage />} />
            <Route path="/landing" element={<LandingPage />} />
            <Route path="/dashboard" element={<App />} />
            <Route path="/calendar" element={<CalendarPage />} />
            <Route path="/upload" element={<UploadPage />} />
            <Route path="/upload/voice" element={<UploadPage />} />
            <Route path="/assistant" element={<AssistantPage />} />
            <Route path="/checkout/complete" element={<CheckoutReturnPage status="success" />} />
            <Route path="/checkout/cancelled" element={<CheckoutReturnPage status="cancelled" />} />
          </Routes>
          <PaymentModal />
          <BookingConfirmationModal />
          <PaymentNoticeToast />
        </ChatSessionProvider>
      </CalendarEventsProvider>
    </BrowserRouter>
  </StrictMode>
);

```

### Frontend/src/App.tsx

```typescript
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { Bot, Hexagon } from "lucide-react";
import { Sidebar } from "@/components/dashboard/Sidebar";
import { DashboardHeader } from "@/components/dashboard/DashboardHeader";
import { OverviewCards } from "@/components/dashboard/OverviewCards";
import { PerformanceChart } from "@/components/dashboard/PerformanceChart";
import { RecoveryPanel } from "@/components/dashboard/RecoveryPanel";
import { WeeklyCalendar } from "@/components/dashboard/WeeklyCalendar";
import { AgentInsightsStrip } from "@/components/dashboard/AgentInsightsStrip";
import { AIChatPanel } from "@/components/dashboard/AIChatPanel";
import { useCalendarEvents } from "@/context/CalendarEventsContext";
import { dateKey, parseDateKey } from "@/data/mockCalendarEvents";
import { cn } from "@/lib/utils";
import { athleteData } from "@/data/mockAthleteData";
import type { OverviewMetric, SharedCalendarEvent } from "@/types/athlete";

const MOBILE_NAV = [
  ["overview", "Overview"],
  ["calendar", "Calendar"],
  ["upload", "Upload"],
  ["assistant", "AI"],
] as const;

const matchDateFormatter = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" });

function startOfDay(date: Date) {
  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}

function getDaysUntil(date: Date, today: Date) {
  return Math.max(0, Math.ceil((startOfDay(date).getTime() - startOfDay(today).getTime()) / 86_400_000));
}

function getNextMatchMetric(metrics: OverviewMetric[], events: SharedCalendarEvent[], today: Date): OverviewMetric[] {
  const todayKey = dateKey(today);
  const nextMatch = events
    .filter((event) => event.type === "match" && event.startDate >= todayKey)
    .sort((a, b) => `${a.startDate} ${a.startTime}`.localeCompare(`${b.startDate} ${b.startTime}`))[0];

  if (!nextMatch) {
    return metrics.map((metric) =>
      metric.id === "event"
        ? {
            ...metric,
            value: "TBD",
            detail: "No match on calendar",
            status: "neutral",
          }
        : metric
    );
  }

  const matchDate = parseDateKey(nextMatch.startDate);
  const daysUntil = getDaysUntil(matchDate, today);
  const value = daysUntil === 0 ? "Today" : daysUntil === 1 ? "Tomorrow" : `${daysUntil} days`;
  const matchMeta = [matchDateFormatter.format(matchDate), nextMatch.location].filter(Boolean).join(" · ");

  return metrics.map((metric) =>
    metric.id === "event"
      ? {
          ...metric,
          value,
          detail: `${nextMatch.title}${matchMeta ? ` · ${matchMeta}` : ""}`,
          status: nextMatch.status === "Confirmed" || nextMatch.paymentStatus === "paid" ? "positive" : "neutral",
        }
      : metric
  );
}

export default function App() {
  const location = useLocation();
  const navigate = useNavigate();
  const [active, setActive] = useState("overview");
  const [chatOpen, setChatOpen] = useState(() => typeof window !== "undefined" && window.innerWidth >= 1440);
  const [seed, setSeed] = useState<{ id: number; text: string; replyId?: string } | null>(null);
  const { events: calendarEvents } = useCalendarEvents();
  const seqRef = useRef(0);

  const data = athleteData;
  const today = useMemo(() => new Date(), []);
  const overviewMetrics = useMemo(
    () => getNextMatchMetric(data.overview, calendarEvents, today),
    [calendarEvents, data.overview, today]
  );

  const askAI = useCallback((text: string, replyId?: string) => {
    seqRef.current += 1;
    setSeed({ id: seqRef.current, text, replyId });
    setChatOpen(true);
  }, []);

  const closeChat = useCallback(() => setChatOpen(false), []);
  const openChat = useCallback(() => setChatOpen(true), []);
  const openCalendar = useCallback(() => navigate("/calendar"), [navigate]);
  const openVoiceUpload = useCallback(() => navigate("/upload/voice"), [navigate]);
  const openLanding = useCallback(() => navigate("/"), [navigate]);

  const h = useMemo(
    () => ({
      overview: () => askAI("What should I focus on next week?", "p1"),
      explainTrend: () => askAI("Explain my recent performance trend.", "p6"),
      recovery: () => askAI("Check my recovery risk.", "p3"),
      agent: (prompt: string, replyId?: string) => askAI(prompt, replyId),
    }),
    [askAI]
  );

  const recoveryInsight = useMemo(() => data.insights.find((i) => i.agent === "Recovery"), [data.insights]);

  const onSelect = useCallback(
    (id: string) => {
      if (id === "calendar") {
        navigate("/calendar");
        return;
      }
      if (id === "upload") {
        navigate("/upload");
        return;
      }
      if (id === "assistant") {
        navigate("/assistant");
        return;
      }
      setActive(id);
      if (id === "ai") {
        setChatOpen(true);
        return;
      }
      document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
    },
    [navigate]
  );

  // Arriving from another page (e.g. the Upload page) via a sidebar section:
  // honor the requested target once the dashboard has mounted.
  useEffect(() => {
    const target = (location.state as { scrollTo?: string } | null)?.scrollTo;
    if (target) requestAnimationFrame(() => onSelect(target));
    // run once on mount; location.state is read at arrival time
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div className="relative min-h-screen overflow-x-hidden p-3 text-text sm:p-4 lg:h-screen lg:overflow-hidden lg:p-4">
      <div className="app-aurora" />

      <div className="aurora-shell relative z-10 mx-auto flex min-h-[calc(100vh-1.5rem)] max-w-[1540px] overflow-hidden rounded-[2rem] sm:min-h-[calc(100vh-2rem)] lg:h-[calc(100vh-2rem)] lg:min-h-0">
        <Sidebar active={active} onSelect={onSelect} profile={data.profile} />

        <div className="min-w-0 flex-1">
          <div className="sticky top-0 z-30 flex items-center g
[truncated — 3815 more characters]
```

### Backend/main.py

```python
"""Real FastAPI backend — reads/writes Supabase instead of in-memory dicts.

Drop-in replacement for mock_backend.py. Point BACKEND_URL at this service
and the agents + frontend work without any other changes.

Run:
    uvicorn main:app --reload --port 8000     (from Backend/)

Tables must exist in Supabase before running. SQL to create them:

    create table if not exists entries (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      raw_input_id text,
      section text not null,
      text text not null,
      metadata jsonb default '{}',
      embedded boolean default false,
      created_at timestamptz default now()
    );
    create table if not exists raw_inputs (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      input_type text,
      raw_text text,
      created_at timestamptz default now()
    );
    create table if not exists training_sessions (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      date date,
      sport text,
      session_type text,
      duration_minutes int,
      intensity int,
      focus_area text,
      notes text,
      created_at timestamptz default now()
    );
    create table if not exists match_results (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      date date,
      opponent text,
      event_name text,
      result text,
      score text,
      key_stats jsonb default '{}',
      notes text,
      created_at timestamptz default now()
    );
    create table if not exists recovery_logs (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      date date,
      soreness_level int,
      fatigue_level int,
      sleep_hours float,
      injury_area text,
      pain_level int,
      risk_level text,
      notes text,
      created_at timestamptz default now()
    );
    create table if not exists metrics (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      metric_name text,
      metric_value float,
      unit text,
      date date,
      created_at timestamptz default now()
    );
    create table if not exists calendar_events (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      title text,
      event_type text,
      start_time timestamptz,
      end_time timestamptz,
      location text,
      source text,
      metadata jsonb default '{}',
      created_at timestamptz default now()
    );
    create table if not exists sponsorship_opportunities (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      brand_name text,
      category text,
      contact_email text,
      fit_score float,
      reason text,
      status text default 'suggested',
      draft_email text,
      created_at timestamptz default now()
    );
    create table if not exists agent_outputs (
      id uuid primary key default gen_random_uuid(),
      user_id text not null,
      agent_name text,
      section text,
      summary text,
      severity text default 'info',
      recommended_action text,
      related_entry_ids jsonb default '[]',
      created_at timestamptz default now()
    );
"""

from __future__ import annotations

import asyncio
import json
import logging
import mimetypes
import os
from datetime import datetime, timedelta, timezone

import anthropic
import sentry_sdk
from dotenv import load_dotenv
from fastapi import FastAPI, File, HTTPException, Request, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel

load_dotenv()

import rag  # noqa: E402 — after load_dotenv so REDIS_URL is available

log = logging.getLogger("main")

# ── Sentry (optional — only init if DSN is present) ────────────────
# Integrations are best-effort: a missing optional dep (e.g. SQLAlchemy, which
# this project doesn't use — it talks to Supabase over REST) must NOT crash the
# whole API on import. Each integration is loaded defensively and skipped if its
# backing package isn't installed.
_sentry_dsn = os.environ.get("SENTRY_DSN", "").strip()
if _sentry_dsn:
    _sentry_integrations = []
    for _import_integration in (
        lambda: __import__("sentry_sdk.integrations.fastapi", fromlist=["FastApiIntegration"]).FastApiIntegration(),
        lambda: __import__("sentry_sdk.integrations.sqlalchemy", fromlist=["SqlalchemyIntegration"]).SqlalchemyIntegration(),
    ):
        try:
            _sentry_integrations.append(_import_integration())
        except Exception as _e:  # noqa: BLE001 — optional integration, skip if unavailable
            log.warning("sentry integration skipped: %s", _e)
    sentry_sdk.init(
        dsn=_sentry_dsn,
        integrations=_sentry_integrations,
        traces_sample_rate=1.0,
        environment="hackathon",
        release="baseline@1.0.0",
    )

# ── Supabase client ────────────────────────────────────────────────
from database import supabase  # noqa: E402  (after load_dotenv)

# ── Service layer (Supabase data access + HTTP Orchestrator brain) ──
from services import athlete_context, booking_service, orchestrator_service  # noqa: E402
from services import agent_event_tracker  # noqa: E402
from services.user_identity import resolve_user_id  # noqa: E402

app = FastAPI(title="BASELINE real backend (Supabase)")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

_SECTIONS = [
    "training", "performance", "match_results", "recovery",
    "coaching", "logistics", "sponsorship", "goals", "media_notes",
]

DEMO_USER_ID = os.environ.get("DEFAULT_USER_ID", "demo-athlete")


# ── Claude helpers (same as mock_backend) ──────────────────────────

def _extract_image_text(image_data: str, media_type: str) -> str:
    resp = anthropic.Anthropic().messages.create(
        model=os.environ.get("S
[truncated — 29476 more characters]
```

[108 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]