Concepts 🚀 Project 🧮 Labs
🚀 Learn with Project / Stage 12 — Wrap up — folder structure, deploy, what's next
🚀 Stage 12 of 13
← Previous — Stage 11
Add a /model-info page — show metrics + feature importance
←

Wrap up — folder structure, deploy, what's next

Recap the 12 stages, see the final folder tree, deploy to Render in 5 minutes, and choose what to learn next.

🧠 The intuition You now have a complete project: Python trains the model, ML makes predictions, FastAPI serves them, Jinja renders the pages, HTML/CSS makes them look like a product. 12 stages, 4 routes, 8 files, 1 deployed website. The architecture is FORCED — every layer exists because the previous one couldn't do the job. That's the test of a good architecture: remove any layer and the project breaks.
✏️ Tiny task — write this yourself Deploy to Render. Push to GitHub, create a Render web service, set build command 'pip install -r requirements.txt' and start command 'uvicorn app.main:app --host 0.0.0.0 --port $PORT'. Done.
✅ Check — what you should see Visit your Render URL — the form should load, submit should give a prediction, /model-info should show metrics. That's a deployed ML product, built from scratch.

🧮 Code files for this stage

📄 final-folder-structure.txt text
student_predictor/
  app/
    main.py              # FastAPI app + 4 routes
    ml/
      train.py           # trains model.pkl (Stage 4)
      evaluate.py        # computes metrics (Stage 5)
      error_analysis.py  # finds worst predictions (Stage 6)
      compare_models.py  # Linear vs RF (Stage 7)
      model.pkl          # the saved winner
    templates/
      base.html          # shared shell (Stage 9)
      home.html          # the form (Stage 9)
      result.html        # the prediction (Stage 9)
      model.html         # /model-info page (Stage 11)
    static/
      style.css          # one stylesheet (Stage 10)
  data/
    students.csv         # the dataset (Stage 1)
  requirements.txt
  run.sh
  render.yaml           # Render deploy config

14 files total. ~600 lines of code.
📄 requirements.txt text
fastapi==0.115.0
uvicorn[standard]==0.30.0
jinja2==3.1.4
pandas==2.2.2
scikit-learn==1.5.1
joblib==1.4.2
python-multipart==0.0.9
📄 render.yaml yaml
services:
  - type: web
    name: student-performance-predictor
    runtime: python
    plan: free
    buildCommand: pip install -r requirements.txt
    startCommand: uvicorn app.main:app --host 0.0.0.0 --port $PORT
    healthCheckPath: /docs
    autoDeploy: true
📄 run.sh bash
#!/bin/bash
# One-time setup
pip install -r requirements.txt
python app/ml/train.py        # produces app/ml/model.pkl

# Every time you want to run
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

# Open http://localhost:8000 in your browser

How it works

The folder structure splits 'training time' (app/ml/) from 'serving time' (app/main.py + app/templates/ + app/static/). train.py runs offline, writes model.pkl, exits. main.py loads model.pkl once and serves predictions forever. That split is what makes deployment trivial — Render only needs to install requirements.txt and run uvicorn; the model is already trained and bundled. WHAT TO LEARN NEXT: (1) Add user accounts (Stage 3 of OpenBenchML's build course covers this — SQLite + JWT). (2) Add a database to save predictions per user. (3) Add a /notebook so users can experiment with the model. (4) Add a leaderboard for who can train the best model. (5) Add WebSocket live updates when a new model is uploaded. That's literally the path from 'student predictor' to 'OpenBenchML'. You just walked it backwards.

🚧 Routes — what's wired up at this stage FINAL ROUTES (recap): GET / -> form page (home.html) POST /predict -> JSON API (for scripts) POST /predict-form -> form submission (renders result.html) GET /model-info -> model metrics + feature importance 4 routes. That's the entire web app. Every other URL (static files, /docs) is auto-served by FastAPI.
🧮 Is a response model necessary here? NO — use response_class=HTMLResponse

FINAL ANSWER on response models: POST /predict -> YES, response_model=PredictResponse POST /predict-form -> NO, returns HTML (response_class=HTMLResponse) GET / -> NO, returns HTML GET /model-info -> NO, returns HTML Rule of thumb: if a route returns JSON, declare a response_model. If it returns HTML, use response_class=HTMLResponse. If it returns a file, use FileResponse. Never mix — the client expects ONE content type per route.

⚠️ Common mistakes to avoid
  • Committing model.pkl to git — it's a binary, can be 100MB+. Use git-lfs or train on deploy.
  • Using `--reload` in production — it watches files and restarts on every change, kills throughput.
  • Not setting host='0.0.0.0' in production — Render won't be able to reach your app on 127.0.0.1.
👀 What's next You're done. The next project is YOURS — pick a dataset, run the same 12 stages, ship it. Or jump into OpenBenchML's Build Course to learn how to add auth, database, WebSocket, and the notebook kernel to this same app.