A FastAPI + Supabase backend that ingests student documents, calls Gemini to classify/summarize/extract assignments, and serves a knowledge-graph-backed tutoring chat to a React frontend.
- FastAPI: HTTP layer; app + router mounts in
backend/main.py. - Supabase (PostgREST): primary datastore; accessed via
httpxREST throughdb/connection.py. - Gemini (
google-genai): current LLM provider; wrapped inservices/gemini_service.py(being deprecated). - Pydantic AI: target agent framework; not yet in
requirements.txt, agents will live underbackend/agents/. - React frontend: lives in
frontend/(out of scope for backend sessions). - pytest: backend test runner, fixtures in
tests/conftest.py.
- backend/main.py:84 — FastAPI app, CORS, and every router mount (mounts at :139).
- backend/routes/documents.py:181 —
_process_documentsingle-call classify/summarize/extract (refactor target #1). - backend/routes/documents.py:577 —
upload_documentPOST/api/documents/uploadpipeline. - backend/routes/learn.py:239 —
build_system_promptfor the streaming tutor (SSE). - backend/routes/quiz.py:1 — quiz session create/answer/score endpoints.
- backend/routes/notes.py:31 —
/api/notesnotetaker CRUD, concept link/unlink, and agent actions (summarize/extract-concepts/chat/send-to-tutor/generate-quiz). - backend/routes/auth.py:1 — Google OAuth + HMAC session token issuance.
- backend/services/gemini_service.py:64 —
call_geminiplain-text call (LLM seam being deprecated). - backend/services/gemini_service.py:135 —
call_gemini_jsonJSON-mode helper used by document/quiz prompts. - backend/services/notes_service.py:45 — notes CRUD with column encryption (
create_note/update_note/save_summary/link_concept). - backend/services/graph_service.py:375 —
apply_graph_update(becomes a Pydantic AI tool). - backend/services/extraction_service.py:1 — OCR engine router (Docling / GOT-OCR / Tesseract).
- backend/services/auth_guard.py:1 —
require_self/require_adminFastAPI dependencies. - backend/agents/note_summary.py, note_concepts.py, note_chat.py — Pydantic AI agents backing the
/api/notesagent actions (model slots inagents/_providers.py). - backend/db/connection.py:102 —
table()factory; the only sanctioned Supabase entry point.
Backend (run from backend/, with .env populated from .env.example):
python main.py # uvicorn on PORT (see config.py), reload=True
python -m pytest tests/ -q # backend test suite
Docker (full stack from repo root):
docker-compose up
Lint (run from backend/):
ruff check . # lint, gated in CI against the ruff.toml baseline (#193)
ruff format . # formatter — available, not yet CI-gated (see ruff.toml)
- All Supabase access goes through
db/connection.py::table(). Do not instantiatehttpxclients or importsupabasedirectly elsewhere. - All current LLM calls route through
services/gemini_service.py(call_gemini,call_gemini_json,call_gemini_multiturn). New LLM-driven code should be written as Pydantic AI agents inbackend/agents/rather than extendinggemini_service.py. - Knowledge-graph mutations go through
services/graph_service.py::apply_graph_update— routes never writegraph_nodes/graph_edgesdirectly. - Backend tests live in
backend/tests/and run viapytest; shared fixtures (mock Supabase, mock Gemini) are intests/conftest.py. - Routers are mounted in
main.pywith/api/<name>prefixes; new routes follow that pattern.
- For architectural decisions, see
docs/decisions/(read the latest 3). - For things that didn't work, see
docs/attempts/. - For the current architecture overview, see
docs/architecture.md. - For agent-building patterns, run
/sync-contextat session start.
- Column-level encryption is on for sensitive columns (
users.name/first_name/last_name/bio/location, Google OAuth tokens,messages.content,room_messages.text,sessions.summary_json,documents.summary+concept_notes,notes.title/body/last_summary, gradebook + calendar assignment notes/points). Helpers live inbackend/services/encryption.py; useencrypt_if_presentat write boundaries anddecrypt_if_present/decrypt_numericat read boundaries (including before injecting into AI prompts).ENCRYPTION_KEYmust be set (32 bytes as 64 hex chars; generate viapython -c "import secrets; print(secrets.token_hex(32))").