> Source URL: /unit-3/project-paths/yeoram-k/yeoram-k-2026-04-21.guide
# Yeoram's Project Guide

**Project:** Workout Weight Calculator
**Category:** Web App (Flask)
**Last updated:** April 21

---

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

## Where You Are

Good progress on your project!

- `weights.py` holds all the logic: `EXERCISES`, `VALID_UNITS/GOALS/EXERCISES`, `ROUTINE_MAP`, `calculate_weight`, and `get_workout_data`.
- `main.py` walks the user through 4 steps (unit → exercise → goal → 1RM), validates each one with `prompt_for_choice` / `prompt_for_number`, and prints a full routine with a top set, back-off sets, and accessories.
- `flask` is already in your `pyproject.toml`, so `uv add flask` is done.

---

## Project Structure

Your project splits into two kinds of code:

- **Business logic — you already handwrote this.** Everything in `weights.py`.
- **View / web code — agent-assisted is fine.** `app.py` (reads the form, calls `get_workout_data`, renders a template) and the HTML files in `templates/`.

Example target layout:

```
workout-weight-calculator/
├── main.py                 ← keep the CLI working (optional — nice for demo)
├── weights.py              ← already done — don't touch
├── app.py                  ← NEW: Flask routes — read form + call weights.get_workout_data
├── templates/
│   ├── base.html           ← NEW: Tailwind CDN + mobile viewport + shared layout
│   └── index.html          ← NEW: the form + the result card
├── pyproject.toml
└── README.md
```

---

## Phase 1: Scaffold the Flask App

> **Agent-assisted is fine.**

### Objective

Get a Flask app running locally that shows a styled "Workout Weight Calculator" heading on `http://127.0.0.1:5000`.

### Instructions

- [ ] Create `app.py` at the project root with one `/` route that renders `index.html`
- [ ] Create a `templates/` folder
- [ ] Create `templates/base.html` with the Tailwind Play CDN, a mobile viewport meta, and a `{% block content %}`
- [ ] Create `templates/index.html` that extends `base.html` and shows a heading
- [ ] Run `uv run flask --app app run --debug` and open the page in your browser
- [ ] On your phone, open Chrome DevTools → device toolbar (or visit your computer's local IP on your phone on the same Wi-Fi) to confirm it looks OK on a small screen

### Hints

**`app.py`:**

```python
from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    return render_template("index.html")
```

**`templates/base.html`:**

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Workout Weight Calculator</title>
    <script src="https://cdn.tailwindcss.com"></script>
  </head>
  <body class="bg-slate-50 text-slate-900">
    <main class="max-w-md mx-auto px-4 py-6">
      {% block content %}{% endblock %}
    </main>
  </body>
</html>
```

**`templates/index.html`:**

```html
{% extends "base.html" %} {% block content %}
<h1 class="text-2xl font-bold mb-4">Workout Weight Calculator</h1>
<p class="text-slate-600">Plan your next session.</p>
{% endblock %}
```

> **Optional — get help from your agent:**
>
> ```text
> I just scaffolded a Flask app with a base.html using the Tailwind
> Play CDN. Walk me through how {% extends %} and {% block content %}
> connect base.html and index.html. Don't change my code — just explain.
> ```

---

## Phase 2: Build the Form

> **Mixed phase.** The _decisions_ about which fields exist (unit, exercise, goal, 1RM) are yours — they mirror the 4 steps in your CLI. The Tailwind _styling_ is agent-assisted.

### Objective

Put your 4 CLI questions on one mobile-friendly page as a single `<form>`. Submitting it reloads `/` with the answers in the URL.

### Instructions

- [ ] In `templates/index.html`, add a `<form method="get" action="/">` with four inputs:
  - [ ] **Unit**: radio buttons — `value="1"` (Pounds) and `value="2"` (Kilograms)
  - [ ] **Exercise**: radio buttons or a `<select>` — values `"1"`–`"4"` matching `weights.EXERCISES`
  - [ ] **Goal**: radio buttons — values `"1"` Strength, `"2"` Hypertrophy, `"3"` Endurance
  - [ ] **1RM**: `<input type="number" name="one_rm" step="any" min="0" required>`
- [ ] Big phone-friendly button: `Calculate`
- [ ] Keep the `name` attributes exactly as `unit`, `exercise`, `goal`, `one_rm` — `app.py` will read these in Phase 3

### Hints

**Reuse the numeric string keys ("1", "2", ...) that `weights.py` already uses — don't change `weights.py`, just send the same strings from the form.**

```html
<fieldset class="mb-5">
  <legend class="font-semibold mb-2">Goal</legend>
  <div class="grid grid-cols-3 gap-2">
    {% for value, label in [("1", "Strength"), ("2", "Hypertrophy"), ("3",
    "Endurance")] %}
    <label class="block">
      <input
        type="radio"
        name="goal"
        value="{{ value }}"
        class="peer sr-only"
        required
      />
      <span
        class="block text-center py-3 rounded-xl border border-slate-300
                   peer-checked:bg-indigo-600 peer-checked:text-white peer-checked:border-indigo-600"
      >
        {{ label }}
      </span>
    </label>
    {% endfor %}
  </div>
</fieldset>
```

**1RM input + submit button:**

```html
<label class="block mb-2 font-semibold" for="one_rm">Your 1RM</label>
<input
  id="one_rm"
  name="one_rm"
  type="number"
  step="any"
  min="0"
  required
  class="block w-full px-4 py-3 rounded-xl border border-slate-300 text-lg mb-5"
  placeholder="e.g. 225"
/>

<button
  type="submit"
  class="w-full py-4 rounded-xl bg-indigo-600 text-white font-semibold text-lg
               hover:bg-indigo-700 active:bg-indigo-800"
>
  Calculate
</button>
```

> **Optional — get help from your agent:**
>
> ```text
> Here is my index.html form. Keep the name attributes and values
> exactly as they are. Help me polish the mobile styling — better
> spacing, bigger tap targets, a soft card background on the form.
> Don't change any form field names.
> ```

---

## Phase 3: Show the Workout Card

> **Handwrite the route yourself.**

### Objective

When the form submits, read the 4 values from the URL, call `weights.get_workout_data(...)`, and render the same routine your CLI prints — as styled cards on the page.

### Instructions

- [ ] In `app.py`, read `unit`, `exercise`, `goal`, `one_rm` from `request.args`
- [ ] Validate: only call `weights.get_workout_data(...)` if all four are present and `one_rm` parses as a positive float (use `try/except`)
- [ ] Pass `data` (the dict `get_workout_data` returns) to the template. Pass `None` if the user hasn't submitted yet
- [ ] In `index.html`, render the result only when `data` is set (`{% if data %}`)
- [ ] Show the main lifts and accessories in cards — same info your CLI prints, just styled

### Hints

**`app.py`:**

```python
from flask import Flask, render_template, request
import weights

app = Flask(__name__)

@app.route("/")
def index():
    unit = request.args.get("unit")
    exercise = request.args.get("exercise")
    goal = request.args.get("goal")
    one_rm_raw = request.args.get("one_rm")

    data = None
    error = None
    if unit and exercise and goal and one_rm_raw:
        try:
            one_rm = float(one_rm_raw)
            if one_rm <= 0:
                raise ValueError
            data = weights.get_workout_data(unit, goal, exercise, one_rm)
        except ValueError:
            error = "Please enter a positive number for your 1RM."

    return render_template("index.html", data=data, error=error)
```

**`index.html`:**

```html
{% if error %}
<p class="mt-5 p-3 rounded-xl bg-red-100 text-red-800">{{ error }}</p>
{% endif %} {% if data %}
<section class="mt-6 p-5 rounded-2xl bg-white shadow">
  <h2 class="text-xl font-bold">{{ data.exercise }}</h2>
  <p class="text-slate-500 mb-4">Goal: {{ data.goal }}</p>

  <h3 class="font-semibold mb-2">Main lift</h3>
  <ul class="mb-4 space-y-1">
    {% for lift in data.main_lifts %}
    <li class="flex justify-between">
      <span>{{ lift.type }}</span>
      <span class="font-mono">
        {{ lift.weight }}{{ data.unit }} × {{ lift.reps }} × {{ lift.sets }}
        sets
      </span>
    </li>
    {% endfor %}
  </ul>

  <h3 class="font-semibold mb-2">Accessories</h3>
  <ul class="space-y-1">
    {% for acc in data.accessories %}
    <li class="flex justify-between">
      <span>{{ acc.name }}</span>
      <span class="font-mono">
        {{ acc.weight }}{{ data.unit }} × {{ acc.reps }} × {{ acc.sets }} sets
      </span>
    </li>
    {% endfor %}
  </ul>
</section>
{% endif %}
```

> **Optional — get help from your agent:**
>
> ```text
> My /dashboard-style result card is rendering. Keep my route logic
> and the {% for %} loops exactly as they are. Help me polish the
> Tailwind styling on the result card — nicer typography hierarchy,
> a subtle divider between main lift and accessories, colored pill
> for the goal name. Don't change app.py.
> ```

---

## Checkpoint 2 Readiness

By Thursday April 23 at 3pm, you should have:

- [ ] `app.py` exists with one `/` route that reads the form and calls `weights.get_workout_data`
- [ ] `weights.py` is unchanged — no math moved into `app.py` or the template
- [ ] `templates/base.html` has the Tailwind Play CDN and mobile viewport meta
- [ ] Form has unit, exercise, goal, and 1RM inputs; the `name` attributes match what `app.py` reads
- [ ] Submitting the form shows a styled card with main lift + accessories below the form
- [ ] Invalid 1RM (blank, `abc`, negative) shows a friendly message instead of crashing
- [ ] Page looks good on a phone-sized viewport (test with DevTools device toolbar)
- [ ] `README.md` updated with `uv run flask --app app run --debug` and a one-line description
- [ ] Checkpoint 2 entry in `project.journal.md` mentions the web pivot + what was yours vs agent-assisted
- [ ] Committed and pushed

## Helpful Resources

- [Checkpoint 2 Instructions](../../projects/final-project-checkpoint-2.project.md)
- [Flask Setup Guide](../../resources/flask-setup.guide.md)
- [Tailwind Play CDN docs](https://tailwindcss.com/docs/installation/play-cdn) — copy-paste setup, no build step
- [Tailwind utility cheatsheet](https://nerdcave.com/tailwind-cheat-sheet) — handy when you don't remember a class name


---

## Backlinks

The following sources link to this document:

- [April 21 -- Pivot to Flask web app with Tailwind](/unit-3/project-paths/yeoram-k/yeoram-k.path.llm.md)
