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.pyis 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.pyFlask 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 — 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
Hints
New fields in home.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:
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
Hints
Updated save_lead:
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:
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:
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
Hints
Bootstrap CDN (in <head>):
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
Bootstrapped field pattern:
<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:
<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:
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
Hints
In app.py:
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):
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
Optional — get help from your agent:
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
Hints
Print button:
<button type="button" onclick="window.print()" class="btn btn-outline-secondary no-print">
Print Confirmation
</button>
Hide when printing:
<style>
@media print {
.no-print { display: none; }
}
</style>
Optional — get help from your agent:
Skip — this is a single attribute + one CSS rule.