#!/usr/bin/env python3
"""
Week 29: Multiplication Facts 0-5
Phase 5: Multiplication Introduction
Standards: 2.OA.A.1 — operations; times tables 0-5, patterns, skip counting
New Standard:
- Reduced drill (4-6 problems per section)
- Estimation before calculating
- Multiple strategies (skip counting, repeated addition, patterns)
- Better word problems (reasoning, real-world contexts)
- Open-ended challenges (Create Your Own)
- Number talks (explain WITHOUT calculating)
- Friday self-check with emoji 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,
array_svg, eq_group_svg, grid_svg,
star_svg, medal_svg, trophy_svg, check_svg,
times_table_html, skip_count_html, mini_chart_html,
)
from activity_builders import (
build_equal_groups,
build_skip_counting,
build_times_table,
build_challenge_box,
build_two_step_problems,
)
BASE = os.path.expanduser("~/Home_School/2nd_Grade/Math")
WEEK = os.path.join(BASE, "Week_29")
# ═══════════════════════════════════════════════════
# MONDAY — The Easy Ones: ×0, ×1, ×2
# ═══════════════════════════════════════════════════
def monday():
# 1: Fluency warmup — doubles (connect to ×2)
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Fluency Warm-up</b> — Doubles! Solve in your head.</p>'\
'<div class="mgrid c3">'
for expr in ["3 + 3", "5 + 5", "4 + 4", "7 + 7"]:
s1 += f'<div class="abox"><p>{expr} = {b("3em")}</p></div>'
s1 += '</div></div>'
# 2: The ×0, ×1, ×2 rules (guided intro)
s2 = '<p class="note"><b>Three easy rules:</b><br>'\
'• Any number × 0 = <b>0</b> (zero groups = nothing!)<br>'\
'• Any number × 1 = <b>itself</b> (one group)<br>'\
'• Any number × 2 = <b>the double</b> (two groups)</p>'\
'<div class="mgrid c3">'
facts = [("6 × 0", ""), ("4 × 1", ""), ("3 × 2", ""), ("9 × 0", ""), ("7 × 1", ""), ("6 × 2", "")]
for f, _ in facts:
s2 += f'<div class="abox"><p>{f} = {b("3em")}</p></div>'
s2 += '</div>'
# 3: 2s times table with blanks (reduced drill — 5 blanks, not all 11)
s3 = build_times_table({
"factor": 2,
"blanks": [3, 5, 7, 8, 10],
"note": "Fill in the missing 2s facts. Hint: double it!"
})
# 4: Number Talk — the zero mystery
s4 = '<div class="abox" style="background:#e3f2fd;">'\
'<p><b>Number Talk</b> — Explain WITHOUT calculating.</p>'\
'<div class="mgrid c2">'
s4 += '<div class="abox"><p>Maya says 100 × 0 is a really big number because 100 is big.</p>'\
'<p>Is she right? Why or why not?</p>'\
'<p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s4 += '<div class="abox"><p>Which is bigger: 8 × 1 or 8 × 0?</p>'\
'<p>How do you know without solving?</p>'\
'<p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s4 += '</div></div>'
# 5: Challenge
s5 = build_challenge_box({
"problems": [
{"text": "I am a number. When you multiply me by 1, I stay the same. When you multiply me by 2, I become 14. What number am I? " + b("3em")},
{"text": "Write your OWN riddle like this for a family member to solve: " + bl()},
]
})
return page(29, "Monday", "The Easy Ones: ×0, ×1, ×2",
f'''{sec(1, "Warm-up: Doubles", s1)}
{sec(2, "Three Easy Rules", s2)}
{sec(3, "The 2s Times Table", s3)}
{sec(4, "Number Talk: The Zero Mystery", s4)}
{sec(5, "Challenge: Number Riddles", s5)}''')
# ═══════════════════════════════════════════════════
# TUESDAY — The 5s and 10s
# ═══════════════════════════════════════════════════
def tuesday():
# 1: Fluency warmup — skip count by 5s and 10s
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Fluency Warm-up</b> — Skip count. Fill in the missing numbers.</p>'
s1 += build_skip_counting({
"sequences": [
{"start": 5, "step": 5, "count": 10, "blank_indices": [3, 6, 8]},
{"start": 10, "step": 10, "count": 10, "blank_indices": [2, 5, 8]},
],
"note": "Count by 5s (like nickels), then by 10s (like dimes)."
})
s1 += '</div>'
# 2: 5s times table with blanks (reduced drill)
s2 = build_times_table({
"factor": 5,
"blanks": [2, 4, 6, 8, 9],
"note": "Fill in the missing 5s facts. Hint: skip count by 5!"
})
# 3: Estimate First! — 5s patterns
s3 = '<div class="abox" style="background:#fff3e0;">'\
'<p><b>Estimate First!</b> Circle MORE or LESS than 25 first, then solve by skip counting.</p>'\
'<div class="mgrid c2">'
for g, n in [(5, 3), (5, 7), (5, 9), (5, 4)]:
s3 += f'<div class="abox"><p><b>{n} × 5</b></p>'\
'<p>more than 25 / less than 25</p>'\
f'<p>Actual: {b("3em")}</p></div>'
s3 += '</div></div>'
# 4: Pattern detective — what do you notice about 5s?
s4 = '<div class="abox">'\
'<p class="note"><b>Pattern Detective!</b> Look at the 5s answers: 5, 10, 15, 20, 25, 30...</p>'\
'<p>What digit do 5s answers ALWAYS end in? ' + b("3em") + ' or ' + b("3em") + '</p>'\
'<p>Could 5 × 6 = 32? Circle: YES / NO</p>'\
'<p style="font-size:11px;">How do you know? ' + bw() + '</p>'\
'<p>What do 10s answers always end in? ' + b("3em") + '</p>'\
'</div>'
# 5: Two strategies for 5s
s5 = '<div class="abox" style="background:#f3e5f5;">'\
'<p><b>Show Two Different Ways</b> — Solve <b>6 × 5</b> two ways.</p>'\
'<p>Way 1 (skip count by 5, six times): 5, 10, ___, ___, ___, ___</p>'\
'<p>Way 2 (repeated addition): ' + bw("14em") + '</p>'\
'<p>6 × 5 = ' + b("3em") + '</p>'\
'<p style="font-size:11px; color:#666;">Which way was faster? ' + bw() + '</p>'\
'</div>'
return page(29, "Tuesday", "The 5s and 10s",
f'''{sec(1, "Warm-up: Skip Count by 5s and 10s", s1)}
{sec(2, "The 5s Times Table", s2)}
{sec(3, "Estimate First!", s3)}
{sec(4, "Pattern Detective", s4)}
{sec(5, "Show Two Different Ways", s5)}''')
# ═══════════════════════════════════════════════════
# WEDNESDAY — The 3s and 4s
# ═══════════════════════════════════════════════════
def wednesday():
# 1: Fluency warmup — skip count by 3s and 4s
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Fluency Warm-up</b> — Skip count. Fill in the missing numbers.</p>'
s1 += build_skip_counting({
"sequences": [
{"start": 3, "step": 3, "count": 10, "blank_indices": [4, 6, 9]},
{"start": 4, "step": 4, "count": 10, "blank_indices": [3, 5, 8]},
],
"note": "Count by 3s, then by 4s."
})
s1 += '</div>'
# 2: 3s times table with blanks (reduced drill)
s2 = build_times_table({
"factor": 3,
"blanks": [4, 6, 7, 9],
"note": "Fill in the missing 3s facts. Skip count by 3 to check!"
})
# 3: 4s with a strategy — double the double!
s3 = '<p class="note"><b>Strategy: 4s are double-doubles!</b> To find 4 × 3: '\
'double 3 (= 6), then double again (= 12). Try it:</p>'\
'<div class="mgrid c2">'
for n in [5, 6, 4, 7]:
s3 += f'<div class="abox"><p><b>4 × {n}</b></p>'\
f'<p>Double {n}: {b("5em")}</p>'\
f'<p>Double again: {b("5em")}</p>'\
f'<p>4 × {n} = {b("3em")}</p></div>'
s3 += '</div>'
# 4: Word problems with 3s and 4s (reduced to 2)
s4 = '<div class="wp">'\
'<p class="note">Write the multiplication sentence and solve.</p>'\
'<div class="mgrid c2">'
problems = [
("A tricycle has 3 wheels. How many wheels do 6 tricycles have?", "wheels"),
("Each table in art class has 4 paintbrushes. There are 5 tables. How many paintbrushes are there?", "paintbrushes"),
]
for text, unit in problems:
s4 += f'<div class="abox"><p style="font-size:0.95em;">{text}</p>'\
'<p>Multiplication sentence: ' + bw("9em") + '</p>'\
'<p>' + b("4em") + f' {unit}</p></div>'
s4 += '</div></div>'
# 5: Create your own — mini chart
s5 = '<div class="abox" style="background:#f3e5f5;">'\
'<p><b>Create Your Own</b> — Fill in this mini multiplication chart. '\
'Color your 3 favorite facts!</p>'
s5 += mini_chart_html(facts=set(), max_factor=5)
s5 += '<p style="font-size:11px; color:#666;">What patterns do you see in the chart? ' + bw() + '</p>'\
'</div>'
return page(29, "Wednesday", "The 3s and 4s",
f'''{sec(1, "Warm-up: Skip Count by 3s and 4s", s1)}
{sec(2, "The 3s Times Table", s2)}
{sec(3, "Strategy: 4s are Double-Doubles", s3)}
{sec(4, "Word Problems", s4)}
{sec(5, "Challenge: Build the Chart", s5)}''')
# ═══════════════════════════════════════════════════
# THURSDAY — Mixed Facts 0-5 & Word Problems
# ═══════════════════════════════════════════════════
def thursday():
# 1: Fluency warmup — mixed facts (reduced to 6)
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Fluency Warm-up</b> — Mixed facts! Solve fast.</p>'\
'<div class="mgrid c3">'
for f in ["2 × 4", "5 × 5", "3 × 3", "4 × 2", "1 × 9", "5 × 0"]:
s1 += f'<div class="abox"><p>{f} = {b("3em")}</p></div>'
s1 += '</div></div>'
# 2: Better word problems (reduced to 3)
s2 = '<div class="wp">'\
'<p class="note">Underline the important numbers. Write the sentence and solve.</p>'\
'<div class="mgrid c2">'
problems = [
("A pack of gum has 5 pieces. Jaden buys 4 packs. How many pieces of gum does he have?", "pieces"),
("There are 3 shelves in the bookcase. Each shelf holds 5 books. How many books fit in the bookcase?", "books"),
("A starfish has 5 arms. How many arms do 3 starfish have?", "arms"),
]
for text, unit in problems:
s2 += f'<div class="abox"><p style="font-size:0.95em;">{text}</p>'\
'<p>Multiplication sentence: ' + bw("9em") + '</p>'\
'<p>' + b("4em") + f' {unit}</p></div>'
s2 += '</div></div>'
# 3: Two-step problem
s3 = '<div class="abox">'\
'<p class="note">Solve step by step.</p>'\
+ build_two_step_problems({
"problems": [
{
"text": "Nora has 4 bags with 5 marbles in each bag. Her friend gives her 7 more marbles. How many marbles does Nora have now?",
"blanks": 2
}
],
"note": "Step 1: multiply. Step 2: add."
}) + '</div>'
# 4: Number Talk — clever strategies
s4 = '<div class="abox" style="background:#e3f2fd;">'\
'<p><b>Number Talk</b> — Explain WITHOUT calculating everything.</p>'\
'<div class="mgrid c2">'
s4 += '<div class="abox"><p>You know 5 × 4 = 20.</p>'\
'<p>How can that help you find 5 × 5 FAST?</p>'\
'<p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s4 += '<div class="abox"><p>Is 3 × 4 the same as 4 × 3?</p>'\
'<p>Explain with a drawing or words.</p>'\
'<p style="font-size:11px;">My thinking: ' + bw() + '</p></div>'
s4 += '</div></div>'
# 5: Create your own
s5 = '<div class="abox" style="background:#f3e5f5;">'\
'<p><b>Create Your Own Word Problem</b></p>'\
'<p>Write a word problem that uses one of THESE facts: 3 × 5, 4 × 4, or 2 × 6. Then solve it.</p>'\
'<p style="min-height:60px; border-bottom:1px solid #333;"></p>'\
'<p style="min-height:30px; border-bottom:1px solid #333;"></p>'\
'<p>Answer: ' + bl() + '</p>'\
'</div>'
return page(29, "Thursday", "Mixed Facts 0-5 & Word Problems",
f'''{sec(1, "Warm-up: Mixed Facts", s1)}
{sec(2, "Word Problems", s2)}
{sec(3, "Two-Step Problem", s3)}
{sec(4, "Number Talk: Clever Strategies", s4)}
{sec(5, "Challenge: Create Your Own", s5)}''')
# ═══════════════════════════════════════════════════
# FRIDAY — Review & Self-Check
# ═══════════════════════════════════════════════════
def friday():
# 1: Fluency warmup — mixed 0-5 facts
s1 = '<div class="abox" style="background:#e8f5e9;">'\
'<p><b>2-min Fluency Warm-up</b> — Show what you know! Solve fast.</p>'\
'<div class="mgrid c3">'
for f in ["3 × 5", "2 × 7", "4 × 4", "5 × 6", "0 × 8", "1 × 5"]:
s1 += f'<div class="abox"><p>{f} = {b("3em")}</p></div>'
s1 += '</div></div>'
# 2: Estimate First!
s2 = '<div class="abox" style="background:#fff3e0;">'\
'<p><b>Estimate First!</b> Circle MORE or LESS than 20, then solve.</p>'\
'<div class="mgrid c2">'
for g, n in [(3, 8), (5, 3), (4, 6), (2, 9)]:
s2 += f'<div class="abox"><p><b>{g} × {n}</b></p>'\
'<p>more than 20 / less than 20</p>'\
f'<p>Actual: {b("3em")}</p></div>'
s2 += '</div></div>'
# 3: Skip counting review — all the tables
s3 = '<div class="abox">'\
'<p class="note">Fill in the missing numbers for each count.</p>'
s3 += build_skip_counting({
"sequences": [
{"start": 2, "step": 2, "count": 10, "blank_indices": [4, 7, 9]},
{"start": 3, "step": 3, "count": 10, "blank_indices": [3, 6, 8]},
{"start": 4, "step": 4, "count": 10, "blank_indices": [2, 5, 9]},
{"start": 5, "step": 5, "count": 10, "blank_indices": [4, 6, 9]},
],
"note": "Count by 2s, 3s, 4s, and 5s."
})
s3 += '</div>'
# 4: Word problem review (reduced to 2)
s4 = '<div class="wp">'\
'<p class="note">Write the multiplication sentence and solve.</p>'\
'<div class="mgrid c2">'
problems = [
("A ladybug has 6 legs. How many legs do 5 ladybugs have?", "legs"),
("Mr. Lee puts 4 pencils in each of 4 cups. How many pencils is that?", "pencils"),
]
for text, unit in problems:
s4 += f'<div class="abox"><p style="font-size:0.95em;">{text}</p>'\
'<p>Multiplication sentence: ' + bw("9em") + '</p>'\
'<p>' + b("4em") + 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 your times tables?</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>×0, ×1, ×2 Facts</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>×3 and ×4 Facts</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>×5 Facts & Patterns</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>The times table I know BEST:</b> ' + b("4em") + '</p>'\
'<p><b>The times table I want to practice more:</b> ' + b("4em") + '</p>'\
'</div>'
return page(29, "Friday", "Week 29 Review & Self-Check",
f'''{sec(1, "Warm-up: Mixed Facts", s1)}
{sec(2, "Estimate First!", s2)}
{sec(3, "Skip Counting Review", s3)}
{sec(4, "Word Problems", s4)}
{sec(5, "Self-Check & Reflection", s5)}''')
# ═══════════════════════════════════════════════════
# TEACHER GUIDE — Combined for all days
# ═══════════════════════════════════════════════════
def teacher_guide():
return tg_page_multi(
"Week 29", "Multiplication Facts 0-5 — Teacher Guide",
# Monday
tg_section_title("Monday: ×0, ×1, ×2") +
tg_sec(1, "Warm-up Doubles", ["6, 10, 8, 14"]) +
tg_sec(2, "Easy Rules", ["0, 4, 6, 0, 7, 12"]) +
tg_sec(3, "2s Table blanks", ["2×3=6, 2×5=10, 2×7=14, 2×8=16, 2×10=20"]) +
tg_sec(4, "Number Talk", ["Maya is wrong — anything × 0 = 0", "8×1=8 is bigger than 8×0=0"]) +
tg_sec(5, "Riddle", ["The number is 7"]) +
tg_note("Page break"),
# Tuesday
tg_section_title("Tuesday: 5s and 10s") +
tg_sec(1, "Warm-up", ["5s blanks: 20, 35, 45 | 10s blanks: 30, 60, 90"]) +
tg_sec(2, "5s Table blanks", ["5×2=10, 5×4=20, 5×6=30, 5×8=40, 5×9=45"]) +
tg_sec(3, "Estimate First", ["3×5=15 less; 7×5=35 more; 9×5=45 more; 4×5=20 less"]) +
tg_sec(4, "Pattern Detective", ["5s end in 0 or 5; NO — 32 doesn't end in 0 or 5; 10s end in 0"]) +
tg_sec(5, "Two Ways", ["15, 20, 25, 30; 5+5+5+5+5+5=30; answer 30"]) +
tg_note("Page break"),
# Wednesday
tg_section_title("Wednesday: 3s and 4s") +
tg_sec(1, "Warm-up", ["3s blanks: 15, 21, 30 | 4s blanks: 16, 24, 36"]) +
tg_sec(2, "3s Table blanks", ["3×4=12, 3×6=18, 3×7=21, 3×9=27"]) +
tg_sec(3, "Double-Doubles", ["4×5: 10→20; 4×6: 12→24; 4×4: 8→16; 4×7: 14→28"]) +
tg_sec(4, "Word Problems", ["6×3=18 wheels; 5×4=20 paintbrushes"]) +
tg_sec(5, "Chart", ["Full 0-5 chart; patterns: rows repeat skip counts, symmetric across diagonal"]) +
tg_note("Page break"),
# Thursday
tg_section_title("Thursday: Mixed Facts & Word Problems") +
tg_sec(1, "Warm-up", ["8, 25, 9, 8, 9, 0"]) +
tg_sec(2, "Word Problems", ["4×5=20 pieces; 3×5=15 books; 3×5=15 arms"]) +
tg_sec(3, "Two-Step", ["Step 1: 4×5=20. Step 2: 20+7=27 marbles"]) +
tg_sec(4, "Number Talk", ["5×5 is one more 5: 20+5=25", "Yes — commutative; 3 rows of 4 = 4 rows of 3"]) +
tg_sec(5, "Create Own", ["3×5=15, 4×4=16, or 2×6=12"]) +
tg_note("Page break"),
# Friday
tg_section_title("Friday: Review & Self-Check") +
tg_sec(1, "Warm-up", ["15, 14, 16, 30, 0, 5"]) +
tg_sec(2, "Estimate First", ["3×8=24 more; 5×3=15 less; 4×6=24 more; 2×9=18 less"]) +
tg_sec(3, "Skip Counting", ["2s: 10, 16, 20 | 3s: 12, 21, 27 | 4s: 12, 24, 40 | 5s: 25, 35, 50"]) +
tg_sec(4, "Word Problems", ["5×6=30 legs; 4×4=16 pencils"]) +
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", "The Easy Ones: ×0, ×1, ×2"),
(tuesday, "Tuesday", "The 5s and 10s"),
(wednesday, "Wednesday", "The 3s and 4s"),
(thursday, "Thursday", "Mixed Facts 0-5 & Word Problems"),
(friday, "Friday", "Week 29 Review & Self-Check"),
]
for lesson_func, day, title in days:
html = lesson_func()
fname = f"Week_29_{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_29_Teacher_Guide.html")
with open(path, "w") as f:
f.write(html)
pdf_path = os.path.join(WEEK, "Week_29_Teacher_Guide.pdf")
HTML(filename=path).write_pdf(pdf_path)
print(f"Generated: {pdf_path}")
print("\nWeek 29 (Multiplication Facts 0-5) complete!")