DEV Community

Dakota Huang
Dakota Huang

Posted on

An Overlap Contract Before Any Shift Helper Split

Current overlap results form the only safe refactor contract. Do not split helpers until that contract is green. This pass extracts one boundary helper and nothing else.

The sample checker mixes inclusive edges, overnight spans, and break credit. A casual edit can turn abutting shifts into false gaps. That outcome is a behavior change, not a cleanup.

Scope of this pass

The goal is a smaller function with identical booleans. The method is a frozen outcome table, then one extraction. No new scheduling policy enters the module here.

Use this path when current output is the intended baseline. Skip it when current output is a known defect you must ship now. Pinning a known defect freezes the wrong contract.

Why a drive-by edit fails

Edge contact, midnight wrap, and break credit share one return. Those three axes interact, so a local tweak has non-local results. A reviewer cannot see the interaction from a diff alone.

Consider two shifts that meet at minute 600. An exclusive end means they abut and do not overlap. Change one comparison operator, and the boolean flips.

The flipped boolean can look like a cleanup. Overnight spans add a second trap for stored day-zero values. The listing extends a span only when that span itself wraps.

A morning shift on day zero can miss a wrapped night shift. That miss is current behavior, even if a later policy rejects it. Keep the miss in the table until a named policy commit.

Proposed listing, not a production trace

The module below is a teaching example for a disposable repo. It is not a captured customer schedule or a live trace. Recompute every boolean before you trust the file.

Place the module at shifts.py in the disposable repo root. Place the test file at tests/test_shift_overlap.py right beside it. Keep both files free of network and clock imports.

The constant 1440 is minutes in this example day. It is not a claim about leap seconds or civil time zones. Timezone database updates stay outside this pass entirely.

from dataclasses import dataclass

@dataclass(frozen=True)
class Shift:
    start_min: int  # minutes from local midnight, 0..1439
    end_min: int    # exclusive end; may be numerically <= start
    break_min: int  # minutes credited against the raw window

def _span(shift: Shift) -> tuple[int, int]:
    start = shift.start_min
    end = shift.end_min
    if end <= start:
        end += 1440
    return start, end

def overlaps(left: Shift, right: Shift) -> bool:
    a0, a1 = _span(left)
    b0, b1 = _span(right)
    latest_start = a0 if a0 > b0 else b0
    earliest_end = a1 if a1 < b1 else b1
    raw = earliest_end - latest_start
    credit = left.break_min + right.break_min
    net = raw - credit
    return net > 0
Enter fullscreen mode Exit fullscreen mode

Read the comparisons before you rename any local symbol. The end minute is exclusive because abutting spans yield a zero raw window. Break credit is subtracted after the window, and it can erase a real overlap.

Both of those rules stay intact in this pass. Do not replace the comparison if with a max call. Do not swap exclusive ends for inclusive ends.

Numbered workflow

1. Name the axes before you add rows

Write the three axes on one line in the test module. They are edge contact, midnight wrap, and break credit. A row that does not hit an axis does not earn a slot yet.

2. Derive booleans from the listing, not from hope

Compute each expected value with the same operators as the function. If a result differs from the hoped policy, record the current result. Do not correct the table during this characterization pass.

3. Keep one assertion per case id

Parameterize the rows so a failure prints the case id. Avoid a loop that catches errors and continues. A swallowed miss lets the helper land anyway.

4. Run the suite and store the summary

Run the listed pytest command before any edit. Store the passed count and the six case ids. The after-run must match that stored summary.

5. Extract only the raw window math

Move latest-start and earliest-end math into one helper. Leave break credit and the final comparison in overlaps. Do not rename fields, change 1440, or reorder arguments.

6. Re-run, then stop the pass

Re-run the same command on the same tree. If any case id flips, revert the helper. Do not edit the expected boolean to match a new diff.

Hand check for one row

Use the one_minute_cross row as the hand check. Left span stays 540 to 601 because 601 exceeds 540. Right span stays 600 to 660 because 660 exceeds 600.

Latest start is 600 because 600 exceeds 540. Earliest end is 601 because 601 is below 660. Raw window is 601 minus 600, which equals 1.

Credit is 0 plus 0, so net stays 1. The boolean is true because net is greater than 0. Repeat this arithmetic for the other five rows before you commit the test file.

Outcome table derived from the listing

These six booleans follow from the operators above. Treat them as a worksheet, then recompute them locally. A mismatch means the worksheet is wrong, not that the function moved.

case_id left start, end, break right start, end, break raw credit overlaps
abut_exclusive 540, 600, 0 600, 660, 0 0 0 false
one_minute_cross 540, 601, 0 600, 660, 0 1 0 true
wrap_vs_morning 1380, 60, 0 0, 30, 0 -1350 0 false
wrap_vs_wrap 1320, 60, 0 1380, 90, 0 120 0 true
break_cancels_window 540, 600, 30 570, 630, 30 30 60 false
separated 540, 600, 0 660, 720, 0 -60 0 false

The wrap_vs_morning row is the likely premature policy edit. Under this listing the morning span stays on day zero. The wrapped span lives at 1380 to 1500, so the windows miss.

Characterization keeps that miss visible in the table. The break_cancels_window row shows a second preserved result. Thirty minutes of raw overlap disappear because both breaks are summed.

The sum is larger than the window, so the boolean is false. A fairer credit rule belongs in a later pass. Leave that credit rule untouched in this pass.

Test file that encodes the table

The test file below is a proposed artifact only. It has not been executed for this draft. Point it at the module only after you recompute the table.

import pytest
from shifts import Shift, overlaps

CASES = [
    ("abut_exclusive", Shift(540, 600, 0), Shift(600, 660, 0), False),
    ("one_minute_cross", Shift(540, 601, 0), Shift(600, 660, 0), True),
    ("wrap_vs_morning", Shift(1380, 60, 0), Shift(0, 30, 0), False),
    ("wrap_vs_wrap", Shift(1320, 60, 0), Shift(1380, 90, 0), True),
    ("break_cancels_window", Shift(540, 600, 30), Shift(570, 630, 30), False),
    ("separated", Shift(540, 600, 0), Shift(660, 720, 0), False),
]

@pytest.mark.parametrize(
    "case_id,left,right,expected",
    CASES,
    ids=[row[0] for row in CASES],
)
def test_overlap_contract(case_id, left, right, expected):
    assert overlaps(left, right) is expected, case_id
Enter fullscreen mode Exit fullscreen mode

Commands to run before and after

Use one command so the summary stays comparable. Do not mix flags between the two runs. Version wording differs across runners, so compare case ids and the passed count.

Install pytest in that disposable repo before the first run. Pin that dependency locally, not from a version stated here. Confirm your interpreter accepts the listed type hints before running.

python -m pytest tests/test_shift_overlap.py -q --tb=line
Enter fullscreen mode Exit fullscreen mode

Run that command on the unchanged listing first. Confirm six case ids and zero failed assertions. Only then apply the extraction in the helper section.

The only edit allowed in this pass

The helper below copies the window math without changing operators. The overlaps function still applies break credit and the same check. Public names and argument order stay as they were.

def raw_overlap_minutes(left: Shift, right: Shift) -> int:
    a0, a1 = _span(left)
    b0, b1 = _span(right)
    latest_start = a0 if a0 > b0 else b0
    earliest_end = a1 if a1 < b1 else b1
    return earliest_end - latest_start

def overlaps(left: Shift, right: Shift) -> bool:
    raw = raw_overlap_minutes(left, right)
    credit = left.break_min + right.break_min
    net = raw - credit
    return net > 0
Enter fullscreen mode Exit fullscreen mode

Review the diff for operator drift before you read style notes. Keep the original comparison form even when a max call looks equal. Ties match either way, but sameness is the point of this pass.

Commit boundary

The characterization commit should contain the tests only. The extraction commit should contain the helper move only. Two commits make a flipped row obvious during review.

Decision table for the next commit

signal you observe action in this pass action deferred
six ids green, diff is extraction only keep the commit none
any case id flips revert the helper inspect the operator that moved
wrap_vs_morning should become true do not edit the pin new test name, new policy commit
break credit should ignore minutes outside the window do not edit the pin new test name, new credit rule
a new axis appears, such as timezone offsets stop and add a row first no helper split until that row is green

Timezone offsets are absent from the listing on purpose. Adding them inside the extraction would mix a feature with a move. That mix defeats the before-and-after summary comparison.

Where an optional runner fits

The workflow above is complete on a local checkout. A hosted runner is optional and is not required. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode's free model access can draft extra candidate rows from the three axes. Accept a row only after you recompute it from the listing. Reject any draft that changes an expected boolean without a new case id.

The free server option can run the same pytest command away from your laptop. This article does not state hardware, quotas, duration, or permanence. Confirm the current terms in the product documentation before you depend on that runner.

Do not upload production rosters, employee names, or secrets with the example. The teaching module uses bare minute integers only. Keep real schedules out of both the prompt and the remote workspace.

Who should not use this approach

Skip the pin when the boolean is a known defect. A characterization suite will argue for that bug. Ship the policy fix under a new test name instead.

Skip it when overlaps reads a clock, a network, or a database. Those inputs will not stay frozen across runs. Add a seam or a stub before you trust a table.

Skip it when the extract cannot land in its own commit. Mixed commits hide a flipped row inside formatting noise. Split the commits, or do not call the edit a refactor.

Limits of the six-row gate

Six rows cover the named axes, not every minute pair. They will not catch a change that preserves these windows and breaks another pair. Add a row when a review finds a new axis.

The preserved break rule can erase overlap that a payroll policy would keep. This pass documents that result and does not repair it. Repair is a behavior change and needs its own table.

No timing numbers or hosted-runner benchmarks appear in this article. The example was not executed for this draft. Your local recompute is the gate, not this page.

If a free MonkeyCode server is already on your account, rerun pytest there. Compare case ids with the laptop summary before you trust it. Ignore any draft row you did not recompute yourself.

Top comments (1)

Collapse
 
suppdevbot profile image
DEV SUPPORTS •
You need to verify your account.
Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to