#!/usr/bin/env python3
"""
Week 24: Area & Perimeter
Phase 4: Geometry & Fractions
Standards: 2.MD.A.1 (measure lengths), intro area/perimeter with unit squares
New Standard:
- Reduced drill (40% fewer problems)
- Estimation before calculating (estimate area/perimeter before counting)
- Multiple strategies encouraged (count all, count rows, add sides)
- Better word problems (reasoning over keywords)
- Real challenges (open-ended thinking)
- Number talks (compare WITHOUT counting every square)
- Friday self-check with reflection
"""
import os
import glob
from weasyprint import HTML
from math_helpers import (
CSS, TEACHER_CSS,
page, page_multi, hdr, sec, b, bw, bl,
tg_page_multi, tg_sec, tg_note, tg_section_title,
grid_svg, ruler_inches, ruler_cm, measure_line,
)
from activity_builders import (
build_area_perimeter,
build_ruler_measurement,
build_challenge_box,
)
BASE = os.path.expanduser("~/Home_School/2nd_Grade/Math")
WEEK = os.path.join(BASE, "Week_24")
BLUE = "#4a90d9"
GREEN = "#5cb85c"
def rect_grid(rows, cols, cell=16, color=BLUE):
"""Rectangle drawn as a labeled unit-square grid."""
w, h = cols * cell + 2, rows * cell + 2
svg = f'<svg viewBox="0 0 {w} {h}" width="{w}" height="{h}">'
for r in range(rows):
for c in range(cols):
svg += (f'<rect x="{c*cell+1}" y="{r*cell+1}" width="{cell}" height="{cell}" '
f'fill="{color}" opacity="0.35" stroke="#333" stroke-width="1"/>')
svg += '</svg>'
return svg
def l_shape_grid(cell=16, color=GREEN):
"""L-shaped figure: 3x2 column + 1x2 foot = area 8, perimeter 12 (units)."""
cells = [(0,0),(0,1),(1,0),(1,1),(2,0),(2,1),(2,2),(2,3)]
w, h = 4 * cell + 2, 3 * cell + 2
svg = f'<svg viewBox="0 0 {w} {h}" width="{w}" height="{h}">'
for r, c in cells:
svg += (f'<rect x="{c*cell+1}" y="{r*cell+1}" width="{cell}" height="{cell}" '
f'fill="{color}" opacity="0.35" stroke="#333" stroke-width="1"/>')
svg += '</svg>'
return svg
# ═══════════════════════════════════════════════════
# MONDAY — Perimeter: The Distance Around
# ═══════════════════════════════════════════════════
def monday():
# 1: Warm-up — count by units around simple shapes
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Warm-up</b> — Skip count out loud: count by 2s to 20, then by 5s to 50. '\
'Then answer: how many sides does a rectangle have? ' + b("3em") + '</p></div>'
# 2: Learn — what is perimeter? Guided example
s2 = '<p class="note"><b>Perimeter</b> is the distance all the way AROUND a shape. '\
'Imagine an ant walking around the edge — how far does it walk?</p>'
s2 += '<div class="mgrid c2">'
s2 += f'<div class="abox" style="text-align:center;">{rect_grid(2, 3)}'\
'<p style="font-size:11px;">Walk around: 3 + 2 + 3 + 2 = <b>10 units</b></p></div>'
s2 += f'<div class="abox" style="text-align:center;">{rect_grid(2, 2)}'\
'<p style="font-size:11px;">Your turn: 2 + 2 + 2 + 2 = ' + b("3em") + ' units</p></div>'
s2 += '</div>'
# 3: Find the perimeter (reduced from 6 to 4)
s3 = '<p class="note">Count the units around each shape. Put a dot where the ant starts so you don\'t count twice!</p>'
s3 += build_area_perimeter({
"shapes": [
{"rows": 2, "cols": 4, "blank_area": False, "blank_perimeter": True},
{"rows": 3, "cols": 3, "blank_area": False, "blank_perimeter": True},
{"rows": 1, "cols": 5, "blank_area": False, "blank_perimeter": True},
{"rows": 3, "cols": 2, "blank_area": False, "blank_perimeter": True},
],
"note": "Perimeter only today — count the units around the outside edge."
})
# 4: Number Talk — Explain WITHOUT counting every unit
s4 = '<div class="abox" style="background:#e3f2fd;">'\
'<p><b>Number Talk</b> — Explain WITHOUT counting every single unit.</p>'\
'<div class="mgrid c2">'
s4 += '<div class="abox"><p>A rectangle is 4 units long and 2 units wide. Sam counts every unit around: 1, 2, 3... Nia says "4 + 2 + 4 + 2, done!" Whose way is faster? Why does it work?</p><p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s4 += '<div class="abox"><p>Two rectangles: one is 5 long and 1 wide, one is 3 long and 3 wide. Which has the bigger perimeter — or are they the same?</p><p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s4 += '</div></div>'
# 5: Challenge — Show two different ways
s5 = '<div class="abox" style="background:#f3e5f5;">'\
'<p><b>Challenge</b> — Show two different ways to find the perimeter of a 3 × 2 rectangle.</p>'\
'<p style="font-size:11px; color:#666;">Try: (1) counting units around, (2) adding the four sides.</p>'\
'<div class="mgrid c2">'
for i in [1, 2]:
s5 += f'<div class="abox"><p><b>Strategy {i}</b></p>'\
'<p style="font-size:11px;">' + bw() + '</p>'\
'<p>Perimeter: ' + b("3em") + ' units</p></div>'
s5 += '</div></div>'
return page(24, "Monday", "Perimeter: The Distance Around",
f'''{sec(1, "Warm-up: Skip Counting", s1)}
{sec(2, "Learn: The Ant Walk", s2)}
{sec(3, "Find the Perimeter", s3)}
{sec(4, "Number Talk: A Faster Way", s4)}
{sec(5, "Challenge: Two Ways to Find Perimeter", s5)}''')
# ═══════════════════════════════════════════════════
# TUESDAY — Area: Counting Unit Squares
# ═══════════════════════════════════════════════════
def tuesday():
# 1: Warm-up — quick perimeter review
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Warm-up</b> — Perimeter check! A square has sides of 3 units. '\
'Perimeter = 3 + 3 + 3 + 3 = ' + b("3em") + ' units</p>'\
'<p>A rectangle has sides 5, 2, 5, 2. Perimeter = ' + b("3em") + ' units</p></div>'
# 2: Learn — what is area?
s2 = '<p class="note"><b>Area</b> is the space a shape COVERS. We measure it by counting unit squares — '\
'like counting tiles on a floor.</p>'
s2 += '<div class="mgrid c2">'
s2 += f'<div class="abox" style="text-align:center;">{rect_grid(2, 3)}'\
'<p style="font-size:11px;">Count the squares: <b>6 square units</b></p></div>'
s2 += f'<div class="abox" style="text-align:center;">{rect_grid(3, 3)}'\
'<p style="font-size:11px;">Your turn: ' + b("3em") + ' square units</p></div>'
s2 += '</div>'
# 3: Estimate First! — before counting
s3 = '<div class="abox" style="background:#fff3e0;">'\
'<p><b>Estimate First!</b> Look quickly — do NOT count yet. Write your estimate. '\
'THEN count the squares and write the actual area.</p>'\
'<div class="mgrid c2">'
# reduced from 6 to 4
for rows, cols in [(2, 5), (4, 3), (3, 4), (2, 2)]:
s3 += f'<div class="abox" style="text-align:center;">{rect_grid(rows, cols)}'\
f'<p style="font-size:11px;">Estimate: {b("3em")} squares</p>'\
f'<p style="font-size:11px;">Actual: {b("3em")} squares</p></div>'
s3 += '</div></div>'
# 4: Strategy — count all vs. count rows
s4 = '<div class="abox" style="background:#f3e5f5;">'\
'<p><b>Two Ways to Count Area</b> — Find the area of this 3 × 4 rectangle two different ways.</p>'\
f'<div style="text-align:center;">{rect_grid(3, 4)}</div>'\
'<div class="mgrid c2">'\
'<div class="abox"><p><b>Way 1:</b> Count every square one by one.</p><p>Area: ' + b("3em") + '</p></div>'\
'<div class="abox"><p><b>Way 2:</b> Count one row, then skip count: 4, 8, ...</p><p>Area: ' + b("3em") + '</p></div>'\
'</div>'\
'<p style="font-size:11px;">Which way was faster for you? ' + bw() + '</p></div>'
# 5: Word problem
s5 = '<div class="wp">'\
'<p class="note">Read carefully. Draw the shape on the grid thinking space if it helps.</p>'\
'<div class="mgrid c2">'
problems = [
"Dad is tiling the bathroom floor. It is 4 tiles long and 3 tiles wide. How many tiles does he need to cover the whole floor?",
"A garden bed is covered with 2 rows of 6 grass squares. A second bed has 3 rows of 4 squares. Which bed covers more space?",
]
for text in problems:
s5 += f'<div class="abox"><p style="font-size:0.95em;">{text}</p>'\
'<p style="font-size:11px;">Draw or write your thinking: ' + bw() + '</p>'\
'<p>Answer: ' + b("6em") + '</p></div>'
s5 += '</div></div>'
return page(24, "Tuesday", "Area: Counting Unit Squares",
f'''{sec(1, "Warm-up: Perimeter Review", s1)}
{sec(2, "Learn: Space a Shape Covers", s2)}
{sec(3, "Estimate First! Then Count", s3)}
{sec(4, "Two Ways to Count Area", s4)}
{sec(5, "Word Problems", s5)}''')
# ═══════════════════════════════════════════════════
# WEDNESDAY — Area AND Perimeter Together
# ═══════════════════════════════════════════════════
def wednesday():
# 1: Warm-up — which word?
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Warm-up</b> — Write <b>area</b> or <b>perimeter</b>.</p>'\
'<div class="mgrid c2">'
for q in ["A fence around a yard measures the {b}",
"Tiles covering a floor measure the {b}",
"The ant walking around the edge finds the {b}",
"Counting the squares inside finds the {b}"]:
s1 += f'<div class="abox"><p>{q.replace("{b}", b("6em"))}</p></div>'
s1 += '</div></div>'
# 2: Both together (reduced from 6 to 4)
s2 = build_area_perimeter({
"shapes": [
{"rows": 2, "cols": 3},
{"rows": 3, "cols": 4},
{"rows": 2, "cols": 5},
{"rows": 3, "cols": 3},
],
"note": "Find BOTH: count squares inside for area, count units around for perimeter."
})
# 3: An L-shape — not everything is a rectangle!
s3 = '<p class="note">This shape is NOT a rectangle. Can you still find its area and perimeter? Yes — count carefully!</p>'
s3 += f'<div class="abox" style="text-align:center;">{l_shape_grid()}'\
'<p>Area: ' + b("3em") + ' square units Perimeter: ' + b("3em") + ' units</p>'\
'<p style="font-size:11px;">Hint for perimeter: put a dot where you start and trace the outside edge.</p></div>'
# 4: Find the error
s4 = '<div class="abox">'\
'<p class="note">Jayden found the perimeter of a 4 × 2 rectangle. He wrote: '\
'"Perimeter = 4 + 2 = 6 units." Is he right?</p>'\
'<p>✓ or ✗? ' + b("3em") + '</p>'\
'<p style="font-size:11px;">Explain the error: ' + bw() + '</p>'\
'<p style="font-size:11px;">Correct perimeter: ' + b("3em") + ' units</p></div>'
# 5: Challenge — Create Your Own
s5 = '<div class="abox" style="background:#f3e5f5;">'\
'<p><b>Create Your Own</b> — Draw a shape on the grid below with an area of exactly '\
'<b>10 square units</b>. It does NOT have to be a rectangle!</p>'\
f'<div style="text-align:center;">{grid_svg(5, 8, cell=18, gap=1, color="#ddd")}</div>'\
'<p style="font-size:11px;">Now find its perimeter: ' + b("3em") + ' units</p>'\
'<p style="font-size:11px; color:#666;">Bonus: could a friend draw a DIFFERENT shape with area 10 but a different perimeter? yes / no</p></div>'
return page(24, "Wednesday", "Area and Perimeter Together",
f'''{sec(1, "Warm-up: Area or Perimeter?", s1)}
{sec(2, "Find Both: Area and Perimeter", s2)}
{sec(3, "A Tricky L-Shape", s3)}
{sec(4, "Find the Error", s4)}
{sec(5, "Challenge: Create Your Own Shape", s5)}''')
# ═══════════════════════════════════════════════════
# THURSDAY — Measuring with Rulers & Real World
# ═══════════════════════════════════════════════════
def thursday():
# 1: Warm-up — mental math with sides
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Warm-up</b> — Perimeter in your head!</p>'\
'<div class="mgrid c3">'
for sides in ["Square, sides 5", "Square, sides 10", "Rectangle 6 and 2"]:
s1 += f'<div class="abox"><p>{sides}</p><p>P = {b("3em")}</p></div>'
s1 += '</div></div>'
# 2: Measure with a ruler (2.MD.A.1) — reduced from 6 to 4
s2 = build_ruler_measurement({
"lines": [
{"length_px": 96, "label": "A."},
{"length_px": 192, "label": "B."},
{"length_px": 144, "label": "C."},
{"length_px": 240, "label": "D."},
],
"unit": "inches",
"note": "Use your real ruler to measure each line to the nearest inch. Line up the 0 mark with the start!"
})
# 3: Estimate First! — non-standard units
s3 = '<div class="abox" style="background:#fff3e0;">'\
'<p><b>Estimate First!</b> Measure with paper clips (or your thumb)! '\
'FIRST estimate, THEN measure for real.</p>'\
'<table class="tbl">'\
'<tr><th>Object</th><th>My estimate</th><th>Actual measure</th></tr>'
for obj in ["This worksheet (long side)", "Your pencil", "A book cover"]:
s3 += f'<tr><td>{obj}</td><td>{b("4em")} clips</td><td>{b("4em")} clips</td></tr>'
s3 += '</table>'\
'<p style="font-size:11px;">Were your estimates close? What helped you estimate better each time? ' + bw() + '</p></div>'
# 4: Word problems — reasoning (reduced from 4 to 3)
s4 = '<div class="wp">'\
'<p class="note">Read carefully. Think about what is happening. Is it an AREA question or a PERIMETER question?</p>'\
'<div class="mgrid c2">'
problems = [
("Grandpa is building a fence around his rectangle garden. It is 6 meters long and 4 meters wide. How many meters of fence does he need?", "meters"),
("Zoe is covering her rectangle desk with sticky notes. The desk fits 5 rows of 4 notes. How many sticky notes cover the desk?", "notes"),
("A picture frame goes around a photo that is 7 units long and 5 units wide. How many units of frame are needed?", "units"),
]
for text, unit in problems:
s4 += f'<div class="abox"><p style="font-size:0.95em;">{text}</p>'\
'<p style="font-size:11px;">Area or perimeter? ' + b("6em") + '</p>'\
'<p style="font-size:11px;">My thinking: ' + bw() + '</p>'\
'<p>' + b("5em") + f' {unit}</p></div>'
s4 += '</div></div>'
# 5: Number Talk — Explain WITHOUT calculating
s5 = '<div class="abox" style="background:#e3f2fd;">'\
'<p><b>Number Talk</b> — Explain WITHOUT calculating.</p>'\
'<div class="mgrid c2">'
s5 += '<div class="abox"><p>Two gardens have the SAME perimeter of fence. Could one of them still have MORE area inside? </p><p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s5 += '<div class="abox"><p>Liam measures his pencil with paper clips and gets 4. Emma measures the SAME pencil with tiny beads and gets 12. Whose pencil measurement is right?</p><p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s5 += '</div></div>'
return page(24, "Thursday", "Measuring & Real-World Area/Perimeter",
f'''{sec(1, "Warm-up: Perimeter in Your Head", s1)}
{sec(2, "Measure with a Ruler (Inches)", s2)}
{sec(3, "Estimate First! Paper Clip Measuring", s3)}
{sec(4, "Word Problems: Area or Perimeter?", s4)}
{sec(5, "Number Talk: Big Ideas", s5)}''')
# ═══════════════════════════════════════════════════
# FRIDAY — Review & Self-Check
# ═══════════════════════════════════════════════════
def friday():
# 1: Warm-up — vocabulary flash
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Warm-up</b> — Finish each sentence.</p>'\
'<p>The distance AROUND a shape is its ' + b("6em") + '.</p>'\
'<p>The space a shape COVERS is its ' + b("6em") + '.</p>'\
'<p>We measure area in ' + b("6em") + ' units.</p></div>'
# 2: Estimate First! — mixed
s2 = '<div class="abox" style="background:#fff3e0;">'\
'<p><b>Estimate First!</b> Glance at each rectangle. Estimate the area, then count.</p>'\
'<div class="mgrid c2">'
for rows, cols in [(3, 5), (2, 4)]:
s2 += f'<div class="abox" style="text-align:center;">{rect_grid(rows, cols)}'\
f'<p style="font-size:11px;">Estimate: {b("3em")} Actual: {b("3em")}</p></div>'
s2 += '</div></div>'
# 3: Mixed review — area & perimeter (reduced from 6 to 3)
s3 = build_area_perimeter({
"shapes": [
{"rows": 2, "cols": 6},
{"rows": 4, "cols": 4},
{"rows": 3, "cols": 5},
],
"note": "Review: find both area and perimeter. Use your favorite strategy!"
})
# 4: Word problem review
s4 = '<div class="wp">'\
'<p class="note">Read carefully. Show your work.</p>'\
'<div class="mgrid c2">'
problems = [
("A rug is 3 units wide and 5 units long. How many square units of floor does it cover?", "square units"),
("Nora walks all the way around a square sandbox with 4-unit sides. How far does she walk?", "units"),
]
for text, unit in problems:
s4 += f'<div class="abox"><p style="font-size:0.95em;">{text}</p>'\
'<p style="font-size:11px;">Draw or write your thinking: ' + bw() + '</p>'\
'<p>' + b("5em") + f' {unit}</p></div>'
s4 += '</div></div>'
# 5: Self-check with emoji reflection
s5 = '<div class="selfcheck">'\
'<p><b>Self-Check: How do you feel about area and perimeter?</b></p>'\
'<div style="display:flex; gap:20px; flex-wrap:wrap;">'\
'<div style="background:#fff3e0; padding:10px; border-radius:5px; flex:1; min-width:150px;">'\
'<p><b>Perimeter (distance around)</b></p>'\
'<p>😊 I got it! | 😐 Getting there | 😟 Need help</p>'\
'</div>'\
'<div style="background:#e3f2fd; padding:10px; border-radius:5px; flex:1; min-width:150px;">'\
'<p><b>Area (counting squares)</b></p>'\
'<p>😊 I got it! | 😐 Getting there | 😟 Need help</p>'\
'</div>'\
'<div style="background:#f3e5f5; padding:10px; border-radius:5px; flex:1; min-width:150px;">'\
'<p><b>Measuring with a Ruler</b></p>'\
'<p>😊 I got it! | 😐 Getting there | 😟 Need help</p>'\
'</div>'\
'</div>'\
'<p style="margin-top:10px;"><b>One thing I learned this week:</b> ' + bl() + '</p>'\
'<p><b>One thing I want to practice more:</b> ' + bl() + '</p>'\
'<p><b>My favorite strategy for finding area:</b> ' + bl() + '</p>'\
'</div>'
return page(24, "Friday", "Week 24 Review & Self-Check",
f'''{sec(1, "Warm-up: Finish the Sentence", s1)}
{sec(2, "Estimate First!", s2)}
{sec(3, "Area & Perimeter Review", s3)}
{sec(4, "Word Problems", s4)}
{sec(5, "Self-Check & Reflection", s5)}''')
# ═══════════════════════════════════════════════════
# TEACHER GUIDE
# ═══════════════════════════════════════════════════
def teacher_guide():
return tg_page_multi(
"Week 24", "Area & Perimeter — Teacher Guide",
tg_section_title("Monday: Perimeter") +
tg_sec(1, "Warm-up", ["Rectangle has 4 sides"]) +
tg_sec(2, "Ant Walk", ["2×2 square: 8 units"]) +
tg_sec(3, "Find Perimeter", ["2×4: 12 | 3×3: 12 | 1×5: 12 | 3×2: 10"]) +
tg_sec(4, "Number Talk", ["Nia's adding-sides way is faster; opposite sides are equal",
"Same! 5+1+5+1=12 and 3+3+3+3=12"]) +
tg_sec(5, "Challenge", ["Both strategies give 10 units for 3×2"]) +
tg_note("Page break"),
tg_section_title("Tuesday: Area") +
tg_sec(1, "Warm-up", ["12 units, 14 units"]) +
tg_sec(2, "Learn", ["3×3: 9 square units"]) +
tg_sec(3, "Estimate First", ["2×5=10, 4×3=12, 3×4=12, 2×2=4 — praise close estimates"]) +
tg_sec(4, "Two Ways", ["Both give 12; skip counting rows (4, 8, 12) is usually faster"]) +
tg_sec(5, "Word Problems", ["12 tiles", "Same! 2×6=12 and 3×4=12 — great discussion moment"]) +
tg_note("Page break"),
tg_section_title("Wednesday: Both Together") +
tg_sec(1, "Warm-up", ["perimeter, area, perimeter, area"]) +
tg_sec(2, "Find Both", ["2×3: A=6 P=10 | 3×4: A=12 P=14 | 2×5: A=10 P=14 | 3×3: A=9 P=12"]) +
tg_sec(3, "L-Shape", ["Area = 8 square units, Perimeter = 14 units (trace carefully!)"]) +
tg_sec(4, "Find the Error", ["✗ — Jayden only added 2 sides. Correct: 4+2+4+2 = 12 units"]) +
tg_sec(5, "Create Own", ["Any shape covering exactly 10 squares; perimeters vary (e.g., 2×5 → 14; L-shapes differ). Bonus: yes"]) +
tg_note("Page break"),
tg_section_title("Thursday: Measuring & Real World") +
tg_sec(1, "Warm-up", ["20, 40, 16"]) +
tg_sec(2, "Ruler", ["A: 1 in, B: 2 in, C: 1.5 in (accept 1 or 2), D: 2.5 in (accept 2 or 3) — nearest inch"]) +
tg_sec(3, "Paper Clips", ["Answers vary; discuss how the first measurement helps calibrate later estimates"]) +
tg_sec(4, "Word Problems", ["Perimeter: 6+4+6+4 = 20 meters", "Area: 5×4 = 20 notes", "Perimeter: 7+5+7+5 = 24 units"]) +
tg_sec(5, "Number Talk", ["Yes! Same fence, different area (e.g., 5×1 vs 3×3)",
"Both are right — different units give different numbers for the same length"]) +
tg_note("Page break"),
tg_section_title("Friday: Review & Self-Check") +
tg_sec(1, "Warm-up", ["perimeter, area, square"]) +
tg_sec(2, "Estimate First", ["3×5=15, 2×4=8"]) +
tg_sec(3, "Review", ["2×6: A=12 P=16 | 4×4: A=16 P=16 | 3×5: A=15 P=16"]) +
tg_sec(4, "Word Problems", ["15 square units", "16 units"]) +
tg_sec(5, "Self-Check", ["Accept student self-assessment and reflection"])
)
# ═══════════════════════════════════════════════════
# GENERATE
# ═══════════════════════════════════════════════════
if __name__ == "__main__":
os.makedirs(WEEK, exist_ok=True)
for f in glob.glob(os.path.join(WEEK, "*.pdf")):
os.remove(f)
for f in glob.glob(os.path.join(WEEK, "*.html")):
os.remove(f)
days = [
(monday, "Monday"),
(tuesday, "Tuesday"),
(wednesday, "Wednesday"),
(thursday, "Thursday"),
(friday, "Friday"),
]
for lesson_func, day in days:
html = lesson_func()
fname = f"Week_24_{day}"
path = os.path.join(WEEK, f"{fname}.html")
with open(path, "w") as f:
f.write(html)
pdf_path = os.path.join(WEEK, f"{fname}.pdf")
HTML(filename=path).write_pdf(pdf_path)
print(f"Generated: {pdf_path}")
html = teacher_guide()
path = os.path.join(WEEK, "Week_24_Teacher_Guide.html")
with open(path, "w") as f:
f.write(html)
pdf_path = os.path.join(WEEK, "Week_24_Teacher_Guide.pdf")
HTML(filename=path).write_pdf(pdf_path)
print(f"Generated: {pdf_path}")
print("\nWeek 24 (Area & Perimeter — new standard) complete!")