> Source URL: /unit-3/project-paths/lucas-w/lucas-w-2026-04-18.guide
# Lucas's Project Guide

**Project:** Roof Inspection Lead Generation Page
**Category:** Web Development (Flask)
**Last updated:** April 18

---

> 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

Strong Checkpoint 1. You have a working Flask app: form submits, required fields checked, saves to SQLite (`logic.py`), shows a confirmation page. The inline comments in your `app.py` show you're following the routes / form flow yourself — exactly what we want.

Honest gaps:

- Your form is missing two fields your spec lists: **insurance provider** and **reason for inspection**.
- No styling yet — your spec says "polished finished look".
- Validation error is just plain text, not a friendly form.
- Your `main.py` is still the placeholder stub.

---

## Project Structure

Your project already has a good split — let's name it:

- **Business logic — you handwrite this.** `logic.py` (what fields get saved, validation rules, the database schema). On demo day, the interesting question is "what data do you collect and how do you store it?" — the answer is here.
- **Library / view code — agent-assisted is fine.** `app.py` Flask routes, HTML templates, Bootstrap classes. These are the same for any Flask form app.

Current layout (with what changes this week):

```
final-project-whitlu0/
├── app.py                  ← Flask routes — agent-assisted OK
├── logic.py                ← business logic — handwrite (yours to own)
├── main.py                 ← placeholder (delete or repurpose)
├── pyproject.toml
├── templates/
│   ├── home.html           ← HTML — agent-assisted OK
│   └── confirmation.html   ← HTML — agent-assisted OK
└── data/
    └── leads.db            ← generated (should be gitignored)
```

Why the split? From [Lecture 1: The MVP](../../lectures/01-the-mvp/01-the-mvp.lecture.md) — your product decision is "what makes a good roofing lead?" That lives in `logic.py`. The Flask routes just plumb form → database.

**`logic.py` should not import `flask`.** It's pure data + DB. Keep it library-independent.

---

## Phase 1: Add the Missing Form Fields

> **Mixed phase.** The HTML additions are agent-friendly (standard form markup). But the *decision* of what fields to collect is yours — you already made it in your spec.

### Objective

Your spec lists 6 fields (name, email, phone, address, insurance provider, reason). Your form has 4. Add the missing two to the HTML and the route.

### Instructions

- [ ] In `templates/home.html`, add two inputs: `insurance` and `reason`
- [ ] In `app.py`, read both from `request.form`
- [ ] Update the required-fields check
- [ ] Pass them to `save_lead()`

### Hints

**New fields in `home.html`:**

```html
<label for="insurance">Home Insurance Provider</label><br>
<input id="insurance" name="insurance" type="text" required><br><br>

<label for="reason">Reason for Inspection</label><br>
<textarea id="reason" name="reason" rows="3" required></textarea><br><br>
```

**In `app.py`:**

```python
insurance = request.form.get("insurance", "").strip()
reason = request.form.get("reason", "").strip()

if not all([name, phone, email, address, insurance, reason]):
    return "Please fill out all fields."

save_lead(name, phone, email, address, insurance, reason)
```

> **Optional — get help from your agent:**
>
> Skip — this is adding two form fields you've done before.

---

## Phase 2: Update the Database Schema

> **Handwrite this yourself.** The schema is a product decision — what data about a lead matters? You're designing the shape of your database.

### Objective

Extend `save_lead` to accept and store the new fields. Update the `CREATE TABLE` SQL.

### Instructions

- [ ] Update `save_lead()` in `logic.py` to take two new arguments: `insurance`, `reason`
- [ ] Update the `CREATE TABLE` SQL to include both new columns
- [ ] Update the `INSERT` SQL to include them
- [ ] Delete `data/leads.db` (the existing DB has the old schema — easiest is to recreate)
- [ ] Add `data/leads.db` to `.gitignore` (your journal flagged this earlier)

### Hints

**Updated `save_lead`:**

```python
def save_lead(name, phone, email, address, insurance, reason):
    connection = sqlite3.connect(DB_NAME)
    cursor = connection.cursor()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS leads (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            phone TEXT NOT NULL,
            email TEXT NOT NULL,
            address TEXT NOT NULL,
            insurance TEXT NOT NULL,
            reason TEXT NOT NULL
        )
    """)

    cursor.execute("""
        INSERT INTO leads (name, phone, email, address, insurance, reason)
        VALUES (?, ?, ?, ?, ?, ?)
    """, (name, phone, email, address, insurance, reason))

    connection.commit()
    connection.close()
```

**Inspecting your DB from the terminal:**

```bash
uv run python -c "import sqlite3; c=sqlite3.connect('data/leads.db'); print(c.execute('SELECT * FROM leads').fetchall())"
```

**Why delete the DB file?** Adding columns to an existing SQLite table is fiddly (needs `ALTER TABLE`). Easier for MVP: blow it away and let the code recreate it with the new schema. You lose test submissions, which is fine.

> **Optional — get help from your agent:**
>
> ```text
> Show me the exact diff to logic.py to add insurance and reason
> columns to the schema and to save_lead's signature. Also the one
> line to add to .gitignore for leads.db.
> ```

---

## Phase 3: Bootstrap Styling

> **Agent-assisted is fine here.** Bootstrap classes are pure library code. Nothing about roofing leads is in the class names.

### Objective

One `<link>` tag + a handful of class names takes you from "raw HTML" to "clean mobile-friendly form".

### Instructions

- [ ] Add the Bootstrap CDN link in `<head>` of both `home.html` and `confirmation.html`
- [ ] Wrap the form in a `<div class="container">` + `<div class="card">`
- [ ] Change each `<input>` to `class="form-control"`, labels to `class="form-label"`
- [ ] Submit button to `class="btn btn-primary"`

### Hints

**Bootstrap CDN (in `<head>`):**

```html
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
```

**Bootstrapped field pattern:**

```html
<div class="mb-3">
  <label for="name" class="form-label">Full Name</label>
  <input id="name" name="name" type="text" class="form-control" required>
</div>
```

**Full form skeleton:**

```html
<div class="container py-5">
  <div class="card shadow">
    <div class="card-body">
      <h1 class="card-title mb-4">{{ title }}</h1>
      <p class="text-muted">Please fill out this form to request a roof inspection.</p>
      <form method="POST">
        <!-- ... mb-3 field groups ... -->
        <button type="submit" class="btn btn-primary btn-lg w-100">Submit</button>
      </form>
    </div>
  </div>
</div>
```

Read what the classes do (`mb-3` = margin-bottom 3, `shadow` = card shadow). If anything confuses you, ask about it.

> **Optional — get help from your agent:**
>
> ```text
> Take my home.html form and convert every field to Bootstrap 5
> classes. Wrap the form in a centered card. Don't change the names
> or ids of any inputs — app.py depends on them.
> ```

---

## Phase 4: Better Validation UX

> **Mixed phase.** The *rule* (what counts as invalid) is yours. The *rendering* of an error message is view work.

### Objective

Right now missing fields return a plain text page. Send the user back to the form with the error shown above the inputs.

### Instructions

- [ ] In `app.py`, instead of returning plain text, render `home.html` with an `error` variable
- [ ] In `home.html`, show the error when it's present
- [ ] (Optional) Preserve what the user already typed so they don't re-type everything

### Hints

**In `app.py`:**

```python
if not all([name, phone, email, address, insurance, reason]):
    return render_template(
        "home.html",
        title="Roof Inspection Form",
        error="Please fill out every field.",
    )
```

**In `home.html` (above the form):**

```html
{% if error %}
  <div class="alert alert-danger">{{ error }}</div>
{% endif %}
```

> **Optional — get help from your agent:**
>
> ```text
> Walk me through preserving the user's typed values on a validation
> error so they don't have to re-type everything. Show me the changes
> to app.py and home.html. I'll do it myself after I understand.
> ```

---

## Phase 5: Print-Friendly Confirmation

> **Agent-assisted is fine here.** A print button is one HTML attribute.

### Objective

Your spec mentions "print a confirmation screen for the user". Simplest version: a button that triggers the browser's print dialog.

### Instructions

- [ ] In `confirmation.html`, add a "Print Confirmation" button with `onclick="window.print()"`
- [ ] Optional: hide the button itself when the page is printed

### Hints

**Print button:**

```html
<button type="button" onclick="window.print()" class="btn btn-outline-secondary no-print">
  Print Confirmation
</button>
```

**Hide when printing:**

```html
<style>
  @media print {
    .no-print { display: none; }
  }
</style>
```

> **Optional — get help from your agent:**
>
> Skip — this is a single attribute + one CSS rule.

---

## Checkpoint 2 Readiness

By Thursday April 23 at 3pm:

- [ ] All 6 fields (name, email, phone, address, insurance, reason) in form + DB
- [ ] `logic.py` does **not** import `flask`
- [ ] Bootstrap styling on both templates
- [ ] Validation errors shown on the form, not a blank page
- [ ] Print button on the confirmation page
- [ ] `data/leads.db` in `.gitignore`
- [ ] Checkpoint 2 entry in `project.journal.md`
- [ ] Committed and pushed

## Helpful Resources

- [Checkpoint 2 Instructions](../../projects/final-project-checkpoint-2.project.md)
- [Lecture 1: The MVP](../../lectures/01-the-mvp/01-the-mvp.lecture.md)
- [Flask Setup Guide](../../resources/flask-setup.guide.md)
- [Bootstrap 5 Forms](https://getbootstrap.com/docs/5.3/forms/overview/)


---

## Backlinks

The following sources link to this document:

- [April 18 -- Checkpoint 2 (Working MVP)](/unit-3/project-paths/lucas-w/lucas-w.path.llm.md)
