Popcorn Hack 1: The Silent Failure

Step 1, before fixing: With amount_requested = 0, the original code printed nothing. There was no error and no warning, so this is a bug, not an error. Python ran it successfully, but the “not a large amount” outcome had no answer, so it silently fell off the end.

Step 2, the fix: one condition has 2 outcomes, so it needs 2 answers. Adding an else gives the missing outcome its answer.

Code Runner Challenge

3.6 Popcorn 1 - Run it first. Then make both outcomes print.

View IPYNB Source
# CODE_RUNNER: 3.6 Popcorn 1 - Run it first. Then make both outcomes print.

amount_requested = 0

# 1 condition -> 2 outcomes -> 2 answers
if amount_requested > 100:
    print("This message is asking for a large amount of money.")
else:
    # Without this else, an amount of 0 printed nothing at all, which looked the same as "this message is fine."
    print("This message is not asking for a large amount of money.")
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Actual output:

Code Runner Challenge

3.6 Popcorn 2 - Run it first. Then fix the order.

View IPYNB Source
This message is not asking for a large amount of money.
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

Popcorn Hack 2: The Chain in the Wrong Order

Step 1, before fixing: With risk_score = 9, the worst possible score, the broken chain told the person:

Code Runner Challenge

3.6 Homework - score a message, then advise on it

View IPYNB Source
Be a little careful, but this is probably fine.
Lines: 1 Characters: 0
Output
Click "Run" in code control panel to see output ...

That’s confidently wrong advice for an almost-certain scam.

Step 2, unreachable branches: The elif risk_score >= 6 branch and the elif risk_score >= 9 branch can never run for any score. Any score of 6 or 9 is also >= 3, so the first if catches it, and elif is only asked when everything above it was False. Counting answers doesn’t catch this, since there are still 4 answers for 4 outcomes. You have to check that each branch is reachable.

Step 3, the fix: order the thresholds highest first.

# CODE_RUNNER: 3.6 Popcorn 2 - Run it first. Then fix the order.

risk_score = 9

# 3 thresholds -> 4 outcomes -> 4 answers
# Highest threshold first: elif is only asked when everything above it was False,
# so each branch only catches the scores the branches above it let through.
if risk_score >= 9:
    print("Do not reply. This is almost certainly a scam.")
elif risk_score >= 6:
    print("This looks risky. Check with someone you trust before replying.")
elif risk_score >= 3:
    print("Be a little careful, but this is probably fine.")
else:
    print("Nothing suspicious in this message.")

Actual output:

Do not reply. This is almost certainly a scam.

Homework Hack: Build the Trainer’s Scoring Step

The function scores a message with three independent if statements, then reports on the score with an if / elif / else chain. It runs on the starter message, both extreme tests, and the inputs that reach the middle branches.

# CODE_RUNNER: 3.6 Homework - score a message, then advise on it

def check_message(message, amount_requested, known_contact):
    risk_score = 0

    # Scoring step: a separate if for each signal, because the signals are independent.
    # A message can trip any combination of them, so every if is checked, not just the first True one.
    # None of these needs an else: when a signal isn't present, the right action is to add nothing,
    # and risk_score already holds the correct value. The answer for "no" is "leave the score alone."
    # .lower() makes the search catch "Gift Card" and "GIFT CARD" too.
    if "gift card" in message.lower():
        risk_score = risk_score + 4
    if amount_requested > 100:
        risk_score = risk_score + 3
    if not known_contact:
        risk_score = risk_score + 2

    print("Message:", message)
    print("Risk score:", risk_score)

    # Reporting step: this is the decision the user sees, so every score must get an answer.
    # 3 thresholds -> 4 outcomes -> 4 answers
    # Highest threshold first so no branch is unreachable, and the final else catches every
    # remaining score, so nothing from 0 upward falls off the end.
    if risk_score >= 9:
        print("Advice: Do not reply. This is almost certainly a scam.")
    elif risk_score >= 6:
        print("Advice: This looks risky. Check with someone you trust before replying.")
    elif risk_score >= 3:
        print("Advice: Be a little careful, but this is probably fine.")
    else:
        print("Advice: Nothing suspicious in this message.")
    print()

# The starter message: gift cards (+4), $500 (+3), unknown sender (+2) = 9
check_message("URGENT: send gift cards now to unlock your account", 500, False)

# Extreme test 1: a completely clean message from a known contact = 0
check_message("Hi Mom, are we still on for Sunday?", 0, True)

# Extreme test 2: a message that trips every signal, with different capitalization = 9
check_message("Your GIFT CARD reward is waiting, send $250 to claim it", 250, False)

# Reachability: one input set for each of the two middle branches
check_message("Please buy a gift card for the office party", 150, True)   # 4 + 3 = 7 -> first elif
check_message("Can you lend me $200 until Friday?", 200, True)              # 3 -> second elif

Actual output:

Message: URGENT: send gift cards now to unlock your account
Risk score: 9
Advice: Do not reply. This is almost certainly a scam.

Message: Hi Mom, are we still on for Sunday?
Risk score: 0
Advice: Nothing suspicious in this message.

Message: Your GIFT CARD reward is waiting, send $250 to claim it
Risk score: 9
Advice: Do not reply. This is almost certainly a scam.

Message: Please buy a gift card for the office party
Risk score: 7
Advice: This looks risky. Check with someone you trust before replying.

Message: Can you lend me $200 until Friday?
Risk score: 3
Advice: Be a little careful, but this is probably fine.

Reachability Proof

Signals add 4 (gift card), 3 (over $100), and 2 (unknown contact), so the possible scores are 0, 2, 3, 4, 5, 6, 7, and 9. One input set reaches each of the 4 answers:

Branch Scores it catches Inputs that reach it Score
if risk_score >= 9 9 “URGENT: send gift cards now to unlock your account”, 500, unknown contact 4 + 3 + 2 = 9
elif risk_score >= 6 6, 7, 8 “Please buy a gift card for the office party”, 150, known contact 4 + 3 = 7
elif risk_score >= 3 3, 4, 5 “Can you lend me $200 until Friday?”, 200, known contact 3
else 0, 1, 2 “Hi Mom, are we still on for Sunday?”, 0, known contact 0

Every branch has an input that reaches it, so there is no dead code. The output above shows all four answers actually printing.

Extreme Tests

  • Clean message (“Hi Mom…”, $0, known contact) scored 0 and printed “Nothing suspicious in this message.”
  • All signals (“Your GIFT CARD reward…”, $250, unknown contact) scored 9 and printed “Do not reply.” It also shows that .lower() catches “GIFT CARD” in capitals.

Both extremes printed a sensible answer, so no input falls off the end.

Stretch: Why if With No else Is Fine Here

In the scoring step, the “no” outcome correctly does nothing because the score just stays the same, but in Example A the “no” outcome was the user’s answer, so leaving it out made the trainer go silent.