Caliber is a fullstack teaching platform prototype for creating coursework from PDFs, organizing courses, and delivering student-facing assignments with saved progress.
- Authentication via Keycloak/OIDC (portal session or Bearer token)
- Role-aware product experience:
- Students:
Courses+ profile access - Instructors/Admins: full platform access + student-view preview
- Students:
- Course management with autogenerated join codes
- Student self-enrollment using course codes
- Assignment authoring + student assignment delivery
- Student assignment progress persistence (autosave/resume)
- Instructor student-view preview that does not save progress
- Question bank workflow with PDF extraction and manual editing
- Sign in and complete onboarding (default student role)
- Join a course by code (
<CourseNamePrefix>_<RANDOM6>) - Open current assignments in student view
- Work through questions with autosaved progress
- Return later and resume where they left off
- Create and manage courses
- Share course code with students for self-enrollment
- Create assignments and attach questions
- Use Student View to preview assignment UX without mutating student progress data
- Manage users and roles
- Review pending instructor requests at top of Users page
- Approve or dismiss instructor access requests
- Keycloak/OIDC JWT auth across frontend/backend
- Onboarding collects profile data
- Instructor requests are captured as
pending - Admin approval toggles users into instructor access
- Teachers can create/update/delete courses
- Students can join via course code
- Course code format:
course_namestripped of spaces/symbols- first 16 chars max
- uppercase
_<6 random letters/numbers>suffix
- Instructors create assignments per course
- Students see assignments in a student-facing experience
- Assignment progress model tracks per-student state
- Dedicated
assignment_progressrows per(assignment, student) - Stores:
- selected/free-response answers
- current question index
- submission status/time
- Sync behavior:
- Created when assignment is created (for current enrolled students)
- Created when a new student is added/joins a course
- Deleted when assignment is deleted
- PDF upload starts background parsing
- Milestone 2 parser files are vendored into
backend/app/m2/and executed from Milestone 1 backend - Upload processing runs the copied M2 layout pipeline first (layout detection + OCR extraction)
- Fallback parser performs question segmentation from PDF text, with OCR fallback for scanned PDFs
- Question bank supports verification/editing and assignment inclusion
- Frontend: React + Vite
- Backend: FastAPI + SQLModel + Alembic
- Auth: Keycloak/OIDC
- DB: PostgreSQL (or SQLite for local testing)
- Storage: Supabase buckets for uploaded PDFs/images
- Background work: FastAPI background tasks for PDF parsing
docs/FEATURES_AND_API.mdfor current feature scope, routes, and endpoint mapdocs/SETUP_OPERATIONS_AND_TESTING.mdfor setup, migrations, storage policy setup, and smoke testingdocs/CODING_RUNNER.mdfor coding-question execution modes, Docker Compose setup, and runner env vars
Use Python 3.10 or 3.11 for the Milestone 2 parser dependencies (torch==2.1.2, effdet).
cd backend
python -m venv .venv
source .venv/bin/activate # On Windows: .venv/Scripts/activate
pip install -r requirements.txt
cp .env.example .envInstall system dependencies required by the copied Milestone 2 parser:
- macOS:
brew install poppler tesseract - Ubuntu/Debian:
sudo apt-get install -y poppler-utils tesseract-ocr
Notes:
- The first PDF upload may take longer because model files are downloaded and cached.
- The
torch/effdetstack is required to run the copiedbackend/app/m2/layout_ingest.py.
Then install Ollama, which runs a local LLM to clean PDF extraction output:
brew install ollamaYou can then either run ollama serve in a new terminal, or run brew services start ollama to run it in the background.
Then in backend/.env set the following:
LLM_CLEANUP_ENABLED=true
LLM_CLEANUP_BASE_URL=http://127.0.0.1:11434
LLM_CLEANUP_MODEL=llama3.1:8b
# Optional: stronger formatter model if your machine can run it.
# LLM_CLEANUP_MODEL=qwen2.5:14b
# Optional fallback chain:
# LLM_CLEANUP_MODEL_FALLBACKS=qwen2.5:14b,qwen2.5:7b
# Optional: force LLM formatting on every question (slower).
# LLM_CLEANUP_FORCE=true
# Optional: debug logs for formatter behavior.
# LLM_CLEANUP_DEBUG=truePrompt style instructions are read from backend/app/prompts/llm_cleanup_style.md.
You can override that by setting LLM_CLEANUP_STYLE_GUIDE_PATH=/absolute/path/to/your-style-guide.md.
Edit your backend/.env file and replace placeholders:
DATABASE_URLwith your local/dev Postgres URLOIDC_ISSUERand/orOIDC_JWKS_URLfor your Keycloak realmOIDC_AUDIENCEif your tokens enforce audience checksSUPABASE_URLandSUPABASE_SERVICE_ROLE_KEYso/api/upload-pdfcan store files inquestion-pdfs- Optional:
M2_TESSERACT_TIMEOUT_SECto cap per-page OCR time (default45) - Optional:
M2_RENDER_DPIto control PDF rasterization cost (default170) - Optional: configure
LLM_CLEANUP_*andROSTER_*values only if you plan to use those integrations locally - Optional: leave
CODING_RUNNER_URLblank for localhost dev, or set it tohttp://coding-runner:8010when using the Docker runner service on a server
Run database migrations (first time setup):
alembic upgrade headIf you hit install/runtime errors, make sure your backend venv uses Python 3.10 or 3.11.
See docs/SETUP_OPERATIONS_AND_TESTING.md for Alembic workflow details and troubleshooting.
Start the backend server:
uvicorn app.main:app --reload --port 8000The backend will be available at http://localhost:8000
For coding questions:
- Local dev: leave
CODING_RUNNER_URLblank and the backend will use the built-in local executor. - Server / Docker: use the root
docker-compose.ymlso the backend can reach the dedicatedcoding-runnerservice athttp://coding-runner:8010.
cd frontend
npm install
cp .env.example .envEdit your frontend .env file and replace the placeholders:
- Replace
VITE_SUPABASE_URL/VITE_SUPABASE_ANON_KEYfor signed URL + image helpers - Set
VITE_API_BASEto your backend URL (default:http://localhost:8000) - Use
VITE_BASE_PATH=/for local dev - Set
VITE_OIDC_ISSUER+VITE_OIDC_CLIENT_IDfor direct local Keycloak login (recommended fornpm run dev) - Set
VITE_PORTAL_BASE_URLonly if you want legacy portal/login+ cookie flow
npm run devThe frontend will be available at http://localhost:5173
This bypasses SSO and is useful for frontend/backend iteration.
- Start backend and frontend normally.
- Open
http://localhost:5173/?test-mode=true.
Use direct Keycloak PKCE login from the frontend (works with npm run dev + uvicorn).
frontend/.env:
VITE_API_BASE=http://localhost:8000
VITE_BASE_PATH=/
VITE_OIDC_ISSUER=https://app.caliber.cs.ucsb.edu/auth/realms/platform
VITE_OIDC_CLIENT_ID=portal
VITE_OIDC_SCOPES=openid profile emailbackend/.env:
OIDC_ISSUER=https://app.caliber.cs.ucsb.edu/auth/realms/platform
OIDC_JWKS_URL=https://app.caliber.cs.ucsb.edu/auth/realms/platform/protocol/openid-connect/certs
OIDC_AUDIENCE=portal
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-keyIf your Keycloak client does not already allow local redirects, add:
- Valid redirect URI:
http://localhost:5173/* - Web origin:
http://localhost:5173 - Post logout redirect URI:
http://localhost:5173/?logged_out=1
Yes, your teammates can compile and host the frontend image locally while testing.
cd caliber-milestone-one
docker build -t caliber-frontend:dev .
docker run --rm -p 8003:8003 caliber-frontend:devThen open http://localhost:8003 (or through your reverse proxy path).
POST /api/upload-pdfand question endpoints for bank managementGET/POST/PUT/DELETE /api/courses+POST /api/courses/joinGET/POST/PUT/DELETE /api/assignmentsGET/PUT /api/assignments/{id}/progressfor student stateGET/PUT /api/users/{id}for admin role managementGET/POST/PUT /api/user*for current-user profile/onboarding/preferences
- Run migrations whenever pulling schema/model changes.
- OCR fallback requires a local
tesseractbinary inPATH. - Milestone 2 parser code is copied into
backend/app/m2/and called byPOST /api/upload-pdf. - Local LLM markdown cleanup is optional and uses Ollama (no API key).