Extract nothing until the refund clock is pinned by tests. A messy gate hides date rules beside status checks. Move one comparison only after every frozen row still passes.
This pass records current behavior before any split. The table is the contract for this diff. A policy change belongs in a later, separate review.
Why this gate fails open
Refund logic often lands in one crowded function. Status, credits, and dates share a single return. A Sunday quirk can hide beside a missing date.
A broad extract can move two rules at once. Reviewers then cannot see which rule changed. A frozen table keeps that review question small.
What stays untouched
The smallest safe change is one date helper. Status checks remain inside the original function body. Prior-refund checks remain there for this pass.
Do not remove the Sunday extension during extraction. Do not add timezone conversion in the same diff. Do not rename fields while the table is still new.
Label the example honestly
The module below is a proposed teaching example. It has not been run as a production trace. It does not describe a real merchant policy.
Dates use the Gregorian calendar for October 2026. October 1, 2026 falls on a Thursday exactly. That calendar fact only anchors the synthetic rows.
Observed rules to freeze
Paid and shipped orders reach the date check. Every other status returns false before dates run. A prior refund above zero cents returns false.
A missing placed date returns false immediately here. The open window lasts fourteen calendar days here. A delta of fourteen days still returns true.
If the fourteenth day is Sunday, one extra day is allowed. Clock time is ignored by the current comparison. Those quirks are frozen, not recommended as policy.
Python counts Monday as weekday zero in this module. Sunday is weekday six in that same count. The helper must keep that numbering intact too.
Step 1. Capture seven decision rows
Keep the first table small enough to read aloud. Seven rows cover the branches you might break. Add rows only when a new branch is observed.
| Row | Status | Placed | Now | Prior cents | Expected |
|---|---|---|---|---|---|
| A | paid | 2026-10-01 | 2026-10-15 | 0 | true |
| B | paid | 2026-10-01 | 2026-10-16 | 0 | false |
| C | cancelled | 2026-10-01 | 2026-10-02 | 0 | false |
| D | paid | 2026-10-01 | 2026-10-10 | 500 | false |
| E | paid | 2026-10-04 | 2026-10-19 | 0 | true |
| F | paid | 2026-10-04 | 2026-10-20 | 0 | false |
| G | shipped | 2026-10-01 | 2026-10-15 | 0 | true |
Row A locks the inclusive fourteenth day boundary. Row B locks the first closed day after that. Row E locks the Sunday extension through Monday.
Row F locks the day after that extension. Row C locks a non-payable status before dates. Row D locks an existing partial refund amount.
Row G locks shipped as an allowed status. Placement on October 4, 2026 is a Sunday. Fourteen days later is Sunday, October 18, 2026.
October 19 is the extra day in row E. October 20 is closed again in row F. Those three dates are the whole extension story.
Step 2. Put the messy function under test
Start from the crowded function, not a cleaned design. The listing is proposed code for a local exercise. Run it yourself before you trust the split.
from datetime import datetime, timedelta
def can_refund(order, now):
status = order.get("status")
if status not in ("paid", "shipped"):
return False
if order.get("prior_refund_cents", 0) > 0:
return False
placed = order.get("placed_at")
if placed is None:
return False
start = placed.date()
current = now.date()
delta = (current - start).days
deadline = 14
if (start + timedelta(days=14)).weekday() == 6:
deadline = 15
return delta <= deadline
Notice the date math sits after two early returns. That order is part of the observed behavior. A helper must not run when placed_at is missing.
Step 3. Encode the table as assertions
Each row becomes one assertion with a clear name. The test file should fail if any expected bit flips. Keep fixtures local and free of customer data.
from datetime import datetime
from refunds import can_refund
def at(value):
return datetime.fromisoformat(value)
CASES = [
("A", {"status": "paid", "placed_at": at("2026-10-01"), "prior_refund_cents": 0}, "2026-10-15", True),
("B", {"status": "paid", "placed_at": at("2026-10-01"), "prior_refund_cents": 0}, "2026-10-16", False),
("C", {"status": "cancelled", "placed_at": at("2026-10-01"), "prior_refund_cents": 0}, "2026-10-02", False),
("D", {"status": "paid", "placed_at": at("2026-10-01"), "prior_refund_cents": 500}, "2026-10-10", False),
("E", {"status": "paid", "placed_at": at("2026-10-04"), "prior_refund_cents": 0}, "2026-10-19", True),
("F", {"status": "paid", "placed_at": at("2026-10-04"), "prior_refund_cents": 0}, "2026-10-20", False),
("G", {"status": "shipped", "placed_at": at("2026-10-01"), "prior_refund_cents": 0}, "2026-10-15", True),
]
def test_refund_rows_stay_put():
for name, order, now, expected in CASES:
assert can_refund(order, at(now)) is expected, name
Add a missing-date row if that branch exists. The expected result is false, with no exception. That protects the helper from a new crash.
def test_missing_placed_date_stays_false():
order = {"status": "paid", "placed_at": None, "prior_refund_cents": 0}
assert can_refund(order, at("2026-10-10")) is False
Step 4. Run the table before moving code
Run the characterization file before you edit anything. A red row means the table is wrong, or the code drifted. Fix the table or the call, then stop.
python -m pytest tests/test_refund_window.py -q
Record the command in the pull request body. Reviewers should be able to repeat it locally. Do not replace that command with a chat transcript.
Step 5. Extract only the comparison
Move the delta and deadline into one function. Call it only after the early returns succeed. Leave every status string where it already sits.
from datetime import timedelta
def window_open(placed, now):
start = placed.date()
current = now.date()
delta = (current - start).days
deadline = 14
if (start + timedelta(days=14)).weekday() == 6:
deadline = 15
return delta <= deadline
def can_refund(order, now):
status = order.get("status")
if status not in ("paid", "shipped"):
return False
if order.get("prior_refund_cents", 0) > 0:
return False
placed = order.get("placed_at")
if placed is None:
return False
return window_open(placed, now)
The diff should show one new function and one call. It should not show new statuses or new day counts. If the diff does more, split the patch again.
Step 6. Re-run the same rows
Run the identical test file after the move. Every row name should still match its expected bit. A new failure means the extract changed behavior.
python -m pytest tests/test_refund_window.py -q
git diff --stat
git diff -- refunds.py
Compare the diff with a stat check before you push. One function move should stay within a small file. A wide stat line is a signal to revert.
How to read the extract
Open the patch and count behavioral lines first. A moved block is not a new rule by itself. A changed number is a new rule, so revert it.
Check that window_open receives placed and now only. Check that can_refund still owns status and credits. Check that missing dates never enter the helper.
Step 7. Optional notes from a free model
Disclosure: This article was prepared as part of MonkeyCode's product outreach. You can paste this small diff into a free model session. The free server option can host that review session.
Treat both as availability, not as a capacity promise. Send the frozen table and the diff, not live orders. Ask whether the patch moves anything besides the comparison.
Reject any suggestion that edits an expected bit. Then run the same pytest command again yourself. The model reply is a comment, not a result.
Merge only when the table is still green. Keep the review session free of customer identifiers. A green local run still outranks the written note.
A stop-or-go table
| Signal | Action |
|---|---|
| A row fails before the edit | Correct the table, do not extract |
| The diff adds a status or a day count | Revert and extract less |
| Tests pass and the diff is one call | Continue review |
| A suggestion changes an expected bit | Discard the suggestion |
| Fixtures need customer identifiers | Stop and use synthetic rows |
Read this table before you ask for a review. It keeps a model session from widening the task. It also keeps you from cleaning up mid-move.
Limitations you should state
Characterization tests preserve known defects on purpose here. The Sunday extension remains after this extract pass. Removing it requires a new expected bit and a new review.
The example assumes naive datetimes in one calendar. It does not convert zones or honor leap seconds. Those concerns need their own failing test first.
Free model access and a free server are availability claims only. This article states no model name, limit, or duration. Do not infer permanence from a tutorial workflow.
The listings are proposed and were not executed here. Run them in your repository before you cite a result. A local failure means the fixture, not a published score.
Who should not use this pass
Skip this pass if the Sunday rule must die now. You would be changing policy, not locking it. Write the new expected bits first, in another diff.
Skip it when the function charges a payment provider. Side effects make a row replay unsafe here. Isolate a pure decision before you freeze outputs.
Skip it if the only fixtures are production payloads. A free review channel is the wrong place for that data. Build synthetic rows that carry no account identifiers.
Skip it when you cannot run the test command locally. A remote comment cannot replace a red or green bar. Wait until the table can fail on your machine.
Close
Pin the refund clock with rows, then move one comparison. Leave status and credit checks in their current place. Treat a wider diff as a failed extract.
If a free server is already in your workflow, use it next. Ask it to compare the diff with rows A through G. Keep those rows as the only merge gate.
Top comments (0)