Study platform for Cambridge International A Level Mathematics (syllabus 9709). Past papers get sliced into per-question images, Google Gemini tags each question with a syllabus topic, difficulty band, and a worked explanation, and a Vite + React frontend at uncooked.works serves them up for revision.
main.pyFastAPI job that walks a folder of question papers (9709_<session><year>_qp_<component><paper>.pdf), renders each page to PNG with PyMuPDF, uploads the images to a Supabase Storage bucket, and inserts a row per question into thealevel_math_questionstable.mark_sc.pysibling job for mark scheme PDFs (9709_<session><year>_ms_<component><paper>.pdf). Each mark-scheme page is matched back to its question number and pushed into that row'sanswer_imgarray.ai.pybatch worker that pulls rows without anai_explanation, groups them by syllabus component (Pure 1, Pure 3, Mechanics, Stats 1, Stats 2), and streams the images through a single Gemini 1.5 Flash chat session per component so the tutor persona stays consistent. Fills intopic,difficulty, andai_explanation, and deletes rows the model flags as "extra page" filler. Prints a running cost estimate as it goes.frontend/Vite + React + shadcn/ui app deployed at uncooked.works. Uses Supabase for data and Auth0 for authentication.papers/local drop folder for the source PDFs.
Backend jobs (Python 3.11+):
python -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn pymupdf requests python-dotenv google-generativeaiBackend .env:
SUPABASE_URL=...
SUPABASE_KEY=... # service role key
BUCKET_NAME=... # question-image bucket
MARK_SCHEME_BUCKET_NAME=... # mark-scheme bucket
GEMINI_API_KEY=...
Frontend:
cd frontend
npm install
npm run dev- Drop question papers into
papers/using the Cambridge naming scheme. - Start the FastAPI host:
uvicorn main:app --reload. - Hit
GET /upload-allto slice, upload, and index every PDF into Supabase. - Drop mark schemes into the answers folder and hit
/upload-mark-scheme(or runmark_sc.py) to attach answer images to the matching rows. - Run
python ai.pyto fill in AI explanations for any un-tagged rows.
Each question ends up as one row in alevel_math_questions with:
question_number, paper_code, paper_year, session, component,
question_img[], answer_img[], topic, difficulty, ai_explanation.