A local-first MVP demo for TAs to ingest office-hour transcripts, detect misconception clusters, and explore a live concept graph.
apps/api: FastAPI backend with transcript parsing, extraction, scoring, root-cause tracing, recommendations, replay snapshots, and SSE events.apps/web: Next.js, React, TypeScript, Tailwind, React Flow, Framer Motion.infra: Docker Compose for Neo4j.
The backend runs without Gemini or Neo4j by default. If GEMINI_API_KEY is not set, it uses a deterministic extractor so the demo remains reliable. If GRAPH_REPOSITORY=neo4j is not configured, the app uses an in-memory graph repository.
# API
npm run dev:api
# Web
npm install
npm run dev:webOpen http://localhost:3000.
The app opens through Google OAuth before Classroom onboarding. In Google Cloud Console, create an OAuth web client with this local redirect URI:
http://localhost:8000/auth/google/callbackCopy .env.example to .env and set:
GOOGLE_OAUTH_CLIENT_ID=...
GOOGLE_OAUTH_CLIENT_SECRET=...
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:8000/auth/google/callback
AUTH_SESSION_SECRET=use-a-long-random-string
AUTH_COOKIE_SECURE=false
FRONTEND_URL=http://localhost:3000The login flow requests Google profile/email plus Classroom course and roster read-only scopes. Tokens stay server-side in the FastAPI session store; the React app only receives session and Classroom data.
npm run test:api
npm --workspace apps/web run typecheck
npm --workspace apps/web run lint
npm --workspace apps/web run buildUpload one or more files from samples/ through the Ingestion panel:
recursion-office-hours.txtlinked-list-office-hours.txttree-traversal-office-hours.txtdp-office-hours.txt
The graph will mark concepts unstable, surface misconception clusters, trace root causes, show score breakdowns, and generate TA interventions. Applying an intervention creates a stabilization snapshot for replay.
Reset the local demo while the API is running:
npm run demo:resetdocker compose -f infra/docker-compose.yml up -dTo use Neo4j from the local API without containers, copy .env.example to .env and set:
GRAPH_REPOSITORY=neo4j
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=classroom-memorySet GEMINI_API_KEY in .env to use Gemini extraction. If Gemini fails or the key is missing, the deterministic fallback extractor is used and the ingestion response records extraction_mode.
GET /concepts/{id}includesscore_breakdown.GET /ingestions/{id}includesextraction_mode,raw_extractions, andextraction_error.POST /demo/resetclears local demo state unlessAPP_ENV=production.GET /classroom/courses/{course_id}/students/previewpreviews a Google Classroom roster without mutating local state.POST /classroom/courses/{course_id}/students/syncupserts the selected Classroom roster after TA confirmation.
Roster import uses the signed-in TA's server-side Google OAuth session and the Google Classroom courses.students.list API. The required Google scope is:
https://www.googleapis.com/auth/classroom.rosters.readonlyEmail visibility can require:
https://www.googleapis.com/auth/classroom.profile.emailsRoster sync never imports assignments, submissions, or student work.