CHAPTER 14 VISUAL LAB - Preserve quantities while matching records
Book boards: 14a and 14b

PRACTICE ROUTINE
Cover the board answer. Predict aloud, try the example, compare with the
stated result, change one input, and try again. Explain a discrepancy
before changing another thing. All marketing examples are fictional.

Use boards 14a and 14b. Open walkthrough.py in Thonny and press F5. These
daily figures are a separate teaching example, not additional starter rows.

EXPECTED OUTPUT
Daily records: 3
Source cost EUR: 420
Paid search: 300 EUR; 12 enquiries
Email: 120 EUR; 8 enquiries
Duplicate description rejected.
Unknown campaign rejected.

FOLLOW THE CODE
build_lookup refuses duplicate campaign IDs. combine finds one channel for
each daily result and rejects unknown IDs. The summary adds quantities by
channel after matching. The assertions compare joined record count and cost
with the source expectation. Deliberate bad examples are separate values;
the good source lists are preserved.

CHANGE, PREDICT, CHECK
In a practice copy, add an Email result for C02 costing 80 with four enquiries.
Update the independently checked expected row count to 4 and total cost to
500. Predict the summary before running.
Answer: Paid search remains 300/12; Email becomes 200/12. There are four
daily records and overall cost is EUR 500. The original assertions should
fail until you deliberately update them to the new, independently derived
expectations. Do not change expectations merely to silence an unexplained error.

Explain why two C01 descriptions would multiply matching pairs in a careless
all-pairs join. The guard rejects that ambiguity before it affects reporting.
