{"id":"code-dev-py-extract-pure-fee-function","audience":"developers","category":"coding","track":"coding","technique":"constraints","title":"Extract a pure function so the fee logic can be tested","free":false,"task":"The late-fee rules are buried in run(), which reads the database and sends emails, so nobody can unit-test them. Extract the math into compute_late_fees(rentals, today) that returns a dict of rental id to fee and does no I/O. run() keeps all the I/O and calls compute_late_fees(rentals, date.today()). The fee rules must stay exactly the same. Return both functions in exactly 1 code block of at most 26 lines, with no new imports and under 20 words of text.","materialLabel":"The job (attached to your prompt automatically)","material":"# jobs/late_fees.py  (Python 3.12, Reelhouse video-rental kiosks)\nfrom datetime import date\n\nfrom kiosk.db import open_rentals, save_fee\nfrom kiosk.mail import send_fee_notice\n\n\ndef run():\n    rentals = open_rentals()\n    today = date.today()\n    for r in rentals:\n        days_late = (today - r.due).days - 2   # 2-day grace period\n        if days_late <= 0:\n            continue\n        fee = min(days_late * 1.50, 20.00)    # $1.50/day, capped at $20\n        save_fee(r.id, fee)\n        send_fee_notice(r.customer_email, r.title, fee)\n        print(f\"fee {fee:.2f} for rental {r.id}\")\n\n\n# Rental objects look like:\n#   Rental(id=88123, due=date(2026, 11, 1), title=\"Paper Moons\",\n#          customer_email=\"lee@reelhouse.test\")\n#\n# Bug report KSK-77: a customer was charged on day 2 after the due\n# date. (Turned out the kiosk clock was wrong, not this code.)\n#\n# Next sprint we want unit tests for the fee rules without a database.","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":26},{"type":"maxProseWords","max":20}],"mistakes":[{"title":"Letting the AI name it","why":"The AI picks names like calculate_fee(rental) that work on one rental, so your planned tests don't fit.","fix":"Say: \"compute_late_fees(rentals, today) returns {rental id: fee}.\"","checks":["r0"]},{"title":"I/O sneaking in","why":"The AI may call date.today() inside the new function, which makes it hard to test.","fix":"Add: \"No I/O and no date.today() inside it; today is passed in.\"","checks":["r0","r2"]},{"title":"Quietly changing the rules","why":"Rewrites sometimes drop the grace period or round differently.","fix":"Add: \"Keep the rules exactly: 2-day grace, $1.50/day, capped at $20.\"","checks":["r1"]},{"title":"Extra layers","why":"The AI adds a class, dataclasses or a config file, and explains the pattern.","fix":"Add: \"Just the two functions, no new imports, one code block, no text.\"","checks":["codeBlocks","maxCodeLines","maxProseWords"]}],"promptHints":[{"label":"Names the new function","patterns":["compute_late_fees"]},{"label":"Passes today in","patterns":["today","date\\.today\\(\\)"]},{"label":"Rules out I/O","patterns":["no i/?o","pure","no (database|db|email)"]},{"label":"Protects the rules","patterns":["same rules","unchanged","grace","1\\.50"]},{"label":"Limits the reply","patterns":["no new imports","only the (code|functions)","one code block"]}],"hint":"'Refactor this' gets you someone else's design. Name the function, what goes in and out, and what must stay where it is.","rubricLabels":["Defines compute_late_fees(rentals, today) that returns a dict mapping rental id to fee and contains no I/O (no open_rentals, save_fee, send_fee_notice, print or date.today() inside it)","The fee rules are unchanged: a 2-day grace period, $1.50 per day late after that, capped at $20.00, and no entry for rentals that aren't late","run() still calls open_rentals, save_fee, send_fee_notice and print, and gets the fees from compute_late_fees(rentals, date.today()), with no new imports"]}