Ask who I am, what is in this portfolio, how projects connect, or what evidence backs any claim. I pull from the site docs.
My Personal Portfolio Website
The site you're reading this on, treated as a system. Next.js front end, Supabase and BigQuery as a data spine, a Gemini chatbot grounded on the site's own content, and a project_metrics table that makes the whole thing observable.
Ask the Site
The same chatbot that powers the rest of the portfolio is available here. It is grounded on the site's own content (RAG), so you can ask about the stack, the data sources, or the project you're currently reading about.
Ask about projects, evidence, and how the work on this site is framed. Answers use portfolio documentation.
Key Performance Indicators
What You're Looking At
This page is part of a Next.js 15 application deployed as a static export with selective ISR. It serves as the public surface for everything else I build: Sports Edge feeds the live betting cards, LLM Advisor feeds the trading snapshots, and a Gemini-backed chatbot answers questions grounded on the site's own content. The rest of this page is the case study — the site is the artifact, and this is the postmortem.
Edge Delivery
Static export served from a CDN. ISR revalidates the data-backed pages on a schedule so a push doesn't require a full rebuild.
Next.js Route Layer
App Router pages, server components for data, client components for the chatbot and interactive demos.
Data Spine
Supabase for serving, BigQuery for warehouse. Same GCP project, different access patterns.
Live System Metrics
Refreshes every 5 min · source: project_metrics tableThe four KPIs above the hero (and the long-form view below) come from a Supabase table that gets upserted on a schedule. This is the only project in the portfolio whose metrics are about the portfolio itself.
RAG Index
Content from project case studies, READMEs, and methodology docs is chunked, embedded, and stored for retrieval-augmented generation. The chatbot only answers from indexed content, which is why it can stay honest about what it does and doesn't know.
Build & Deploy
Static export for the public surface, ISR for the data-backed routes, and a single environment-driven deploy. No runtime servers for the marketing pages; only the API routes and the chatbot hit serverless functions.
Live Data Sources
Sports Edge publishes a JSON snapshot of model spreads and edges on a daily cron. LLM Advisor publishes a backtest summary. The portfolio reads both at build time and bakes them into the static pages, with ISR refresh on stale data.
Chatbot Stack
Gemini via Vertex AI for generation. The prompt includes a system message that scopes the assistant to "answer only from retrieved content or refuse." Retrieval confidence threshold decides whether to answer or escalate to "I don't know."
Changelog
Every meaningful change to this site is recorded here, in reverse chronological order. This file is committed to the repo, so the changelog is also the audit trail.
Under the Hood
A tour of the subsystems that make this site work. Each subsection is short on purpose — enough to understand the choice, not a tutorial.
1. Static-first rendering
The site is a Next.js 15 app with output: 'export' for the public surface. Marketing pages, project cards, and case studies are pre-rendered at build time and served as static assets from the CDN. The few pages that need fresh data (Sports Edge, project metrics) use ISR with a revalidation window so a code change doesn't force a full rebuild of the data layer.
2. Supabase as the serving layer
Supabase is the read path. The project_metrics table stores the KPIs you see on every case study. A scheduled job upserts rows frompublic/data/project_metrics_seed.json. The site reads via a typed useProjectMetrics(id) hook that polls every five minutes. The service_role key is only used in scripts, never in the browser.
3. BigQuery as the source of truth
Heavy aggregations live in BigQuery. The portfolio project itself doesn't run those jobs — the upstream repos (Sports Edge, LLM Advisor) do — but it consumes the results through Supabase. The contract is simple: warehouse computes, serving layer serves, the portfolio renders. This keeps the static site cheap to host while still letting the data side stay heavy.
4. The chatbot / RAG pipeline
Build-time scripts (build:rag-manifest,build:rag-embeddings) walk the repo's docs and case studies, chunk them, embed them with Vertex AI, and write the index topublic/data/rag_embeddings.json. At request time, the user's question is embedded, the top-k chunks are retrieved, and Gemini generates an answer scoped to that context. A confidence threshold determines whether the assistant answers or admits it doesn't know.
5. Live data wiring
The Sports Edge card and the LLM Advisor backtest card both read JSON snapshots published by their respective repos. A GitHub Action in each upstream repo writes the latest snapshot to its public/ folder on a schedule. The portfolio pulls them at build time. The result: a visitor hitting the homepage sees today's edges, not last week's.
6. The "site-as-product" loop
The whole reason this page exists: to make the site legible as a system, not a vibe. Project metrics, a changelog, a system diagram, and a chatbot that can be queried about any of it. The portfolio doesn't just show projects — it shows the infrastructure that makes the portfolio work, and treats that infrastructure the same way it treats everything else. That's the only thing that makes this not a gimmick.
Pipeline Logic
Three small scripts do most of the work at build time. They run in order: manifest, warehouse schema, embeddings. The site stays deployable as long as each script produces a valid artifact.
"build:rag-manifest": "node scripts/build-rag-manifest.mjs", "build:warehouse-schema": "node scripts/build-warehouse-schema.mjs", "build:rag-embeddings": "node scripts/build-rag-embeddings.mjs", "refresh-project-metrics":"node scripts/refresh-project-metrics.mjs"
Try It / Break It
Three concrete things you can do right now:
- Open the chatbot above and ask "What stack is this site built on?" — it should walk you through it from indexed content.
- Ask the chatbot something it doesn't know (e.g. "What's the weather in Tokyo?") — it should refuse rather than confabulate.
- Read the source on GitHub — every section on this page links to a real file in the repo.