Duward's Project Guide

Project: GPT Workout Planner
Category: AI / Mobile Web App (Flask + Tailwind)
Last updated: April 24

Note: This guide reflects the latest state of your project repo (final-project-angldu6). It may not match the most up-to-date version if you've worked since.

Where You Are

Your Flask app works: / + /generate, Tailwind templates, .env for the key, and a solid Checkpoint 2 journal entry.

Two pieces of business logic you already wrote in planner.py are not wired into Flask — they only existed in the old commented-out main.py flow:

  1. select_group_and_workouts(goal, options_data, location_key) — picks a focus group using GOAL_GROUP_WEIGHTS + build_group_weights (e.g. "lose weight" biases toward cardio). app.py still calls pick_focus_group(recent) + workouts_for_group(...), which ignores the goal text for picking.
  2. load_last_workout() / save_workout(goal, plan_text)workouts/history.json supports history, but app.py never reads or writes it.

Checkpoint 3 is about activating that code, polishing the repo for portfolio + Demo Day, and optionally deploying.

Phase 1: Wire up smart goal-based focus picking

Handwrite the edits. This is your planner logic — you should understand every line.

Objective

Use select_group_and_workouts in /generate so focus-group choice reflects keywords in the user's goal (while still avoiding repeats from recent_focus_groups).

Instructions

group, workouts = select_group_and_workouts(goal, options, location_key)

Hints

select_group_and_workouts already:

  • loads recent groups from workouts/history.json
  • filters out groups in recent_focus_groups (or resets if all are used)
  • applies build_group_weights from GOAL_GROUP_WEIGHTS
  • picks with random.choices(..., weights=...)
  • saves the updated recent_focus_groups via save_previous_groups

So do not duplicate load_previous_groups / save_previous_groups in app.py for the focus group — that would double-save or fight the planner.

Optional — get help from your agent:

Read my app.py and planner.py. After Phase 1, does /generate still
update recent_focus_groups exactly once per request? Trace the call
path into select_group_and_workouts. Don't change my code — explain.

Phase 2: Show "Your last workout" on the home page

Mixed. Small app.py changes + template.

Objective

After each successful generation, persist the plan to history. On /, show the most recent workout in a collapsible block so users see context before submitting again.

Instructions

Hints

/ route shape:

@app.route("/")
def home():
    last = load_last_workout()
    return render_template("home.html", last_workout=last)

Fragment for home.html (inside {% block content %}, before {% if error %}):

{% if last_workout %}
<details class="mb-6 rounded-xl border border-slate-200 bg-white p-4 text-sm">
  <summary class="cursor-pointer font-semibold text-slate-800">
    Your last workout
  </summary>
  <pre class="mt-3 whitespace-pre-wrap font-sans text-slate-700 text-xs leading-relaxed">{{ last_workout }}</pre>
</details>
{% endif %}

If last_workout is always empty, check workouts/history.jsonsave_workout must run after a successful OpenAI response.

Optional — get help from your agent:

My home.html shows last_workout but the <pre> is one long line. Fix
only the CSS/classes so newlines from load_last_workout() render.
Don't change planner.py.

Phase 3: Loading state on submit

Agent-assisted OK. View-only + tiny inline JS.

Objective

While OpenAI responds, the user should see feedback — disabled button + "Generating…" so they don't double-submit.

Instructions

    • finds the submit button
    • sets disabled = true
    • replaces button label with "Generating…" and optionally adds a small Tailwind spinner (inline svg or animate-spin border)

Hints

Minimal pattern — add to the <form> tag:

<form
  method="post"
  action="/generate"
  class="space-y-6"
  onsubmit="this.querySelector('button[type=submit]').disabled=true; this.querySelector('button[type=submit]').textContent='Generating…';"
>

Polish version: keep the button text in a <span id="gen-label"> and toggle a sibling spinner hidden class.

Optional — get help from your agent:

Add a Tailwind spinner next to my submit button in home.html. On
form submit, hide the original label, show spinner, disable button.
Keep method/action unchanged.

Phase 4: Portfolio polish (README, spec, main.py, .env.example, screenshot)

Handwrite the prose in spec/README/journal; file moves are straightforward.

Objective

Make the GitHub repo demo-ready: no dead main.py, documented env vars, accurate spec, visual proof in README.

Instructions

OPENAI_API_KEY=
uv sync
uv run flask --app app run --debug

Mention: open http://127.0.0.1:5000, need OPENAI_API_KEY in .env (not committed). Link to project.spec.md / project.journal.md.

Hints

Commit message idea:

checkpoint 3: wire select_group_and_workouts + history UI, polish README

Optional — get help from your agent:

Draft README.md for my GPT Workout Planner: uv sync, uv run flask,
.env for OPENAI_API_KEY, link docs/screenshot.png. I'll paste my
real screenshot path after.

Phase 5: Checkpoint 3 journal + Demo Day plan

Handwrite.

Objective

Align project.journal.md with what you built and lock a 2–5 minute demo outline.

Instructions

Hints

Demo flow that shows off your work:

  1. Reset or note workouts/history.json if you want a clean "first visit"
  2. Submit "build muscle" + gym — show plan + focus on screen
  3. Return home — last workout appears in <details>
  4. Submit "lose weight" — show different vibe / focus bias over a couple runs

Phase 6 (Bonus): Deploy to Render

Only after Phases 1–5 work locally. Skip if time is short.

Objective

Public URL for Demo Day; same pattern as other class guides.

Instructions

Hints

render.yaml sketch:

services:
  - type: web
    name: gpt-workout-planner
    env: python
    plan: free
    buildCommand: uv sync --frozen && uv cache prune --ci
    startCommand: uv run gunicorn app:app
    autoDeploy: true
    envVars:
      - key: OPENAI_API_KEY
        sync: false

Optional — get help from your agent:

My Render deploy builds but crashes on start. Here's my render.yaml
and startCommand. What's wrong?

Checkpoint 3 Readiness

By Friday, May 1st at 3:30pm (Demo Day):