Do this BEFORE assigning anyone else:
pnpm create vite@latest . -- --template react-tspnpm install- Create
vercel.json:{ "rewrites": [{ "source": "/((?!api/).*)", "destination": "/index.html" }] } - Create
config.json:{ "games": ["maimai", "chunithm"], "currency_per_play": 40 } - Create 3 API stubs with mock data:
api/query.ts— returns hardcodeddaily_playrowsapi/chat.ts— returns a canned chat responseapi/refresh.ts— returns{ "status": "ok" }
- Copy
play_counter/from Chunimai-tracker →scraper/play_counter/ - Copy
main.py,pyproject.toml,uv.lock→scraper/ - Copy
init.sql→scraper/init.sql(adduser_scorestable) - Copy
.github/workflows/schedule.yml→.github/workflows/scrape-daily.yml- Update
working-directory: scraper
- Update
- Deploy to Vercel, verify stubs work at
<your>.vercel.app - Add
CLAUDE.mdto repo root
After this: tell teammates "pull main, read CLAUDE.md, start on your section."
Read first: CLAUDE.md (UI layout section, config.json section) Depends on: Big's scaffold (API stubs return mock data)
-
App.tsx — Single-page layout
- Header bar with refresh button + gear icon + chat toggle
- Main content area (heatmap + rating chart stacked vertically)
- Collapsible right sidebar for chat
- Settings modal (triggered by gear icon)
-
src/lib/api.ts — Fetch wrappers
queryDB(sql, params?)→POST /api/querysendChat(messages)→POST /api/chat(handle streaming)triggerRefresh()→POST /api/refreshgetConfig()→fetch('/config.json')
-
src/components/Heatmap.tsx
pnpm add cal-heatmap(or similar React heatmap lib)- Fetch data:
SELECT * FROM daily_play ORDER BY play_date - Show maimai (orange) and/or chunithm (green) based on
config.json - Year selector, tooltip on hover showing play count + rating
- Both games in one row per date (NOT separate rows)
-
src/components/RatingChart.tsx
pnpm add recharts- Line chart:
maimai_ratingand/orchunithm_ratingover time - Conditionally show lines based on
config.jsongames array
-
src/components/ChatPanel.tsx
- Right sidebar, toggleable via header button
- Text input + message history display
- Call
sendChat(), display streamed response - Keep it simple — no
deep-chat-reactneeded, raw fetch + ReadableStream
-
src/components/SettingsModal.tsx
- Modal overlay
- Theme toggle (dark/light)
- Any display preferences (stored in localStorage)
- Game selection is NOT here (it's in config.json)
- Build against the mock API stubs first. When real routes land, everything just works.
- Use pnpm, not npm or bun.
- No react-router-dom. No client-side routing. Single page.
- Test with
pnpm devlocally andnpx vercel devfor API routes.
Read first: CLAUDE.md (rating system section, song suggestion algorithm section) Depends on: Nothing — pure functions, testable standalone
-
src/lib/maimai-rating.ts — Port from Python
rating.pyRANK_FACTORS: array of{ minScore, multiplier, rankName }SSS+ = 1005000, 22.4 SSS = 1000000, 21.6 SS+ = 995000, 21.1 SS = 990000, 20.8 S+ = 980000, 20.3calculateSongRating(constant, score):Math.floor(constant * multiplier * Math.min(score/10000, 100.5) / 100)calcRating(playerData, version): compute total DX rating from top 50 (35 old + 15 new)getRankInfo(score): returns{ rank, achievementPct }getNextRank(score): returns next rank threshold above current score- New version songs:
releasedVersionisCiRCLEorPRiSM+ - Write unit tests with known values to validate against the formula
-
src/lib/maimai-suggest.ts — Port from Python
suggest_songs.py- Two modes:
best_effortandtarget best_effort: return top N improvements + new songs sorted by rating gaintarget: greedy algorithm picking songs until remaining_rating ≤ 0- Improvements: replace song's own rating in top-50 (no push-out)
- New songs: enter top-50, push out lowest
- Reads
maimai-songs.jsonfor chart constants - maimai only (CHUNITHM song suggestion is out of scope)
- The full Python code is in the repo's CLAUDE.md project context — ask Big if needed
- Two modes:
-
api/chat.ts — Replace Big's stub with real implementation
- Read
OPENAI_API_KEYandOPENAI_BASE_URLfromprocess.env - System prompt includes:
daily_playschema DDLuser_scoresschema DDL- Rating formula with multiplier table
- Instructions for when to use each tool
- Two tool definitions:
query_database: LLM generates SQL → execute via Neon → return results- Read-only guard: reject non-SELECT statements
pnpm add @neondatabase/serverless
maimai_suggest_songs: call maimai-suggest.ts with parameters from LLM
- Stream response back using ReadableStream
- 60-second Vercel Hobby timeout — keep streaming to stay alive
- Read
maimai-rating.tsandmaimai-suggest.tsare pure functions. Test them withvitestwithout Vercel or Neon.- For
api/chat.ts, test locally withnpx vercel dev. - The system prompt is critical for demo quality — invest time in good examples.
- Read the suggest_songs.py code Big will provide. Don't reinvent the algorithm.
Read first: CLAUDE.md (GitHub Actions workflows section, user deployment flow) Depends on: Big's scaffold (for workflow file locations)
-
scraper/import_user_data.py
- Parse JSON output from chuumai-tools Docker containers
- Upsert into
user_scorestable (JSONB) - Keep only latest 5 snapshots per game (DELETE older to stay under Neon's 0.5 GB)
- Use asyncpg (same as existing
db.py)
-
.github/workflows/scrape-user-data.yml
- Trigger:
workflow_dispatch - Read
config.jsonto determine which games to scrape - Run Docker containers:
- maimai:
ghcr.io/leomotors/maimai-scraper:v1withVERSION=CiRCLE - chunithm:
ghcr.io/leomotors/chunithm-scraper:v6withVERSION=XVRSX
- maimai:
- Mount
outputs/to capture JSON - Run
import_user_data.pyto write to Neon - NEVER commit output to git (privacy)
- Secrets:
DATABASE_URL,SEGA_USERNAME,SEGA_PASSWORD
- Trigger:
-
.github/workflows/refresh-songs.yml
- Trigger: weekly cron +
workflow_dispatch - Fetch
maimai.wonderhoy.me/api/musicData - Write to
public/maimai-songs.json - Commit and push (this is public data, OK to commit)
- Trigger: weekly cron +
-
api/refresh.ts — Replace Big's stub
- Read
GITHUB_PATandGITHUB_REPOfromprocess.env POST https://api.github.com/repos/{owner}/{repo}/actions/workflows/scrape-user-data.yml/dispatches- Return
{ run_url }
- Read
-
Update scraper/init.sql — Add
user_scorestableCREATE TABLE IF NOT EXISTS user_scores ( id SERIAL PRIMARY KEY, game TEXT NOT NULL, scraped_at TIMESTAMPTZ DEFAULT NOW(), data JSONB NOT NULL );
-
Presentation deck (12-min + 3-min Q&A)
- Problem: expertise gap in rhythm games, casual players waste credits
- Solution: ChuMaiNichi — free, fork-and-deploy dashboard with AI agent
- Tech: Generative AI (tool-use), Playwright automation, Neon, Vercel
- Demo: live walkthrough (heatmap → chat → song suggestion → rating improvement)
- Feasibility: 0 THB cost, 3 free-tier accounts, ~15 min setup
-
Demo script — Exact steps to show in 3 minutes
- Show heatmap with real data
- Ask AI: "how many times did I play this week?"
- Ask AI: "suggest songs to reach rating 15000"
- Show the suggestion output with target scores
- Use
uvfor Python, not pip. In GitHub Actions useastral-sh/setup-uv@v5. - Docker images use env vars:
USERNAME,PASSWORD,VERSION,TZ=Asia/Bangkok. - The scrape-daily.yml already exists from Big's scaffold — don't recreate it.
- Coordinate with Big on the presentation — Big knows the architecture best.
Work lands directly on main via PR (no long-lived feature branches). Suggested order to minimise conflicts:
- Pipeline first (init.sql + workflows, no frontend conflicts)
- AI agent second (api/chat.ts + lib/, no frontend conflicts)
- Frontend last (consumes the APIs above)
- Big: end-to-end test on Vercel, fix integration bugs
- Friend onboarding documentation (README with screenshots)
- Docker self-hosting support
- Settings modal game selection (it's in config.json)
- CHUNITHM song suggestion
- Polish, animations, mobile responsiveness
- User study protocol (HCI group handles this separately)