A tangled function is not ready for extraction. Hidden predicates change priced results after a cleanup. Pin which branches fire for a fixture set.
Then extract one predicate from that function. Stop after that single extract lands green.
Why return-value tests still miss money bugs
A single cents assertion hides extra control-flow paths. Two event lists can share one total. Coupon then holiday differs from holiday then coupon.
The order of if blocks is load-bearing behavior. Deleting an elif is a product change.
Dumping the file into a coding model multiplies that risk. The model simplifies predicates you never measured. Measure every predicate before any code extract.
The pin: a branch-outcome table
Each table row represents exactly one fixture. Each row stores inputs, booleans, events, and cents. Booleans are the predicates the function already computes.
Events are the side-effect tokens it already appends. Cents is the frozen money result for that row.
You treat that JSON file as the golden contract. Any later extract must reproduce every table cell. Extra events fail the test on purpose.
Missing events fail the same test as well. Reordered events fail the test as well.
This table is narrower than a full suite. It covers one function and one extract. That limited scope is the safety mechanism.
Worked example (labeled, not production)
The code below is an example harness. Copy it into a throwaway directory first.
Run it locally before you extract anything. Do not treat the numbers as performance claims. This harness is a labeled example, not executed here.
Step 1 — Isolate the tangled function
Create pricing.py with mixed policy and arithmetic. Name each predicate in a local boolean. Do not extract any helpers in this step.
# pricing.py — example only, not production
from __future__ import annotations
from dataclasses import dataclass
from datetime import date
from typing import Any
HOLIDAYS = {
"US": {(12, 25), (7, 4)},
"EU": {(12, 25), (5, 1)},
}
@dataclass(frozen=True)
class Order:
sku: str
qty: int
unit_cents: int
region: str
@dataclass(frozen=True)
class User:
tier: str
coupon: str | None
def apply_price(
order: Order,
user: User,
today: date,
flags: dict[str, Any],
) -> dict[str, Any]:
events: list[str] = []
cents = order.qty * order.unit_cents
bulk = order.qty >= 10
vip = user.tier == "vip"
member = user.tier in {"member", "vip"}
holiday = (today.month, today.day) in HOLIDAYS.get(order.region, set())
coupon_ok = user.coupon == "SAVE10" and member
surge = bool(flags.get("surge")) and order.region == "US"
if bulk:
cents = int(cents * 0.9)
events.append("bulk")
if vip:
cents = int(cents * 0.95)
events.append("vip")
elif member:
cents = int(cents * 0.98)
events.append("member")
if holiday:
cents = int(cents * 1.15)
events.append("holiday")
if coupon_ok:
cents -= 1000
events.append("coupon")
if surge:
cents = int(cents * 1.08)
events.append("surge")
if cents < 0:
cents = 0
events.append("floor")
return {
"cents": cents,
"events": events,
"predicates": {
"bulk": bulk,
"vip": vip,
"member": member,
"holiday": holiday,
"coupon_ok": coupon_ok,
"surge": surge,
},
}
The function returns predicates on purpose for measurement. That extra return key is the measurement seam.
Production code can drop that key later. Keep it while the table is the contract.
Step 2 — Build a fixture grid
Create fixtures.py with rows that flip one predicate. Include one stacked case that fires several rules.
Include a floor case that clamps negative cents. Skip random fuzz during this first table pass.
# fixtures.py — example only
from datetime import date
from pricing import Order, User
ROWS = [
{
"id": "guest_small_us",
"order": Order("sku", 1, 2000, "US"),
"user": User("guest", None),
"today": date(2026, 3, 2),
"flags": {},
},
{
"id": "member_bulk_us",
"order": Order("sku", 10, 2000, "US"),
"user": User("member", None),
"today": date(2026, 3, 2),
"flags": {},
},
{
"id": "vip_holiday_eu",
"order": Order("sku", 2, 5000, "EU"),
"user": User("vip", None),
"today": date(2026, 12, 25),
"flags": {},
},
{
"id": "member_coupon",
"order": Order("sku", 1, 4000, "US"),
"user": User("member", "SAVE10"),
"today": date(2026, 3, 2),
"flags": {},
},
{
"id": "guest_coupon_ignored",
"order": Order("sku", 1, 4000, "US"),
"user": User("guest", "SAVE10"),
"today": date(2026, 3, 2),
"flags": {},
},
{
"id": "us_surge_july4",
"order": Order("sku", 1, 1000, "US"),
"user": User("guest", None),
"today": date(2026, 7, 4),
"flags": {"surge": True},
},
{
"id": "coupon_floor",
"order": Order("sku", 1, 500, "EU"),
"user": User("member", "SAVE10"),
"today": date(2026, 1, 1),
"flags": {},
},
{
"id": "vip_bulk_holiday_surge",
"order": Order("sku", 12, 2500, "US"),
"user": User("vip", "SAVE10"),
"today": date(2026, 12, 25),
"flags": {"surge": True},
},
]
Eight rows is enough for this extract. Add a row when a predicate has no True case. Do not add rows for unplanned extracts.
Step 3 — Record the golden table
# record_branches.py — example only
import json
from pathlib import Path
from fixtures import ROWS
from pricing import apply_price
def row_record(row: dict) -> dict:
out = apply_price(row["order"], row["user"], row["today"], row["flags"])
return {
"id": row["id"],
"cents": out["cents"],
"events": out["events"],
"predicates": out["predicates"],
}
def main() -> None:
table = [row_record(r) for r in ROWS]
Path("branch_table.golden.json").write_text(
json.dumps(table, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
if __name__ == "__main__":
main()
Record the golden table once before the extract. Never regenerate it to make tests pass.
python record_branches.py
Commit branch_table.golden.json in the same change set. Review the booleans as if they were production data. A False holiday on 25 December is a fixture bug.
Step 4 — Lock the table in a test
# test_pricing_table.py — example only
import json
from pathlib import Path
from fixtures import ROWS
from pricing import apply_price
GOLDEN = json.loads(Path("branch_table.golden.json").read_text(encoding="utf-8"))
def test_branch_table_matches_golden() -> None:
got = []
for row in ROWS:
out = apply_price(row["order"], row["user"], row["today"], row["flags"])
got.append(
{
"id": row["id"],
"cents": out["cents"],
"events": out["events"],
"predicates": out["predicates"],
}
)
assert got == GOLDEN
def test_event_order_is_part_of_contract() -> None:
stacked = next(r for r in GOLDEN if r["id"] == "vip_bulk_holiday_surge")
assert stacked["events"] == [
"bulk",
"vip",
"holiday",
"coupon",
"surge",
]
python -m pytest test_pricing_table.py -q
The second test documents order as policy. Do not sort events in the function.
Sorting those events would hide rule sequence. Local pytest is the only merge gate here.
Illustrative predicate matrix
Do not copy money totals from prose. Generate cents with the recorder. The matrix below only shows expected booleans.
| id | bulk | vip | member | holiday | coupon_ok | surge |
|---|---|---|---|---|---|---|
| guest_small_us | false | false | false | false | false | false |
| member_bulk_us | true | false | true | false | false | false |
| vip_holiday_eu | false | true | true | true | false | false |
| member_coupon | false | false | true | false | true | false |
| guest_coupon_ignored | false | false | false | false | false | false |
| us_surge_july4 | false | false | false | true | false | true |
| coupon_floor | false | false | true | false | true | false |
| vip_bulk_holiday_surge | true | true | true | true | true | true |
Guest coupons stay inert on purpose. VIP still sets member true. That boolean split is part of the contract.
Step 5 — Extract one predicate only
The only allowed change is the holiday predicate. Introduce one helper named is_holiday for region and date.
Keep every other boolean inline in apply_price. Keep every if body on the same lines.
# allowed extract — example only
def is_holiday(region: str, today: date) -> bool:
marks = HOLIDAYS.get(region, set())
return (today.month, today.day) in marks
Replace only the holiday local with the helper.
holiday = is_holiday(order.region, today)
Re-run the same two tests after the extract. If cents drift, revert the extract immediately.
If events drift, revert that extract as well. If predicates drift, revert the extract without debate.
Do not extract member in the same diff. Do not merge vip and member in this diff.
Do not move multipliers into a dict. Those are later changes with new rows.
Step 6 — Use a model only after the table is green
A coding model can propose the holiday extract. It should not rewrite apply_price before the pin.
Paste the function, the golden table, and one instruction. The instruction must extract is_holiday and nothing else.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option.
That pair is useful as a disposable scratch space. You still run pytest on your machine.
The golden JSON file remains the only contract. Reject any patch that edits bulk, coupon, or surge.
Keep the model prompt short and mechanical. Name the new function in that prompt.
Name the files that must not change. Discard the session if the diff exceeds the holiday predicate.
Flags belong in the fixture, not in os.environ
Reading os.environ inside apply_price splits the table. The surge flag must be an argument.
Import-time env reads are a different pin. Do not mix those pins in one extract.
How to read a failing cell
A cents mismatch without an events mismatch is arithmetic. An events mismatch without a cents mismatch is a silent extra rule.
A predicates mismatch with both outputs stable is a renamed boolean. Treat that as a contract break anyway.
What the table will not catch
It will not catch races between flags and dates. It will not catch float remnants in other languages. It will not catch logger configuration changes at all.
It will not catch region codes absent from fixtures. It will not catch leap-day holidays you never listed.
Integer cents avoid one class of error. They still do not freeze tax rules.
They do not freeze currency rounding laws. Add those rules as later dedicated tables.
Who should not use this approach
Skip this if the function is a certified payments module. Skip this if reviewers cannot read JSON diffs.
Skip this if you need concurrent mutation of flags. Skip this if the next edit is a full rewrite.
Skip this if no owner can explain each event token. A model session is not a substitute owner.
If nobody can say why coupon requires member, stop. Ownership of each token comes before any extract.
Closing rule
Pin predicates, events, cents, and event order. Extract exactly one named predicate after that pin.
Re-run the table before you merge. That short sequence is the whole method. Keep the golden table in review, not the model transcript.
Top comments (0)