Round overlap resolution midpoint to nearest 5 minutes
This commit is contained in:
parent
de46399010
commit
b2b45fd4e1
2 changed files with 48 additions and 1 deletions
|
|
@ -273,6 +273,11 @@ def _minutes_to_time(minutes: int) -> str:
|
||||||
return f"{minutes // 60:02d}:{minutes % 60:02d}"
|
return f"{minutes // 60:02d}:{minutes % 60:02d}"
|
||||||
|
|
||||||
|
|
||||||
|
def _round_to_5(minutes: int) -> int:
|
||||||
|
"""Round minutes to the nearest 5-minute boundary."""
|
||||||
|
return round(minutes / 5) * 5
|
||||||
|
|
||||||
|
|
||||||
def _make_closed_row(template: dict, start_m: int, end_m: int) -> dict | None:
|
def _make_closed_row(template: dict, start_m: int, end_m: int) -> dict | None:
|
||||||
"""
|
"""
|
||||||
Return a copy of *template* with updated start, end, and duration_hours.
|
Return a copy of *template* with updated start, end, and duration_hours.
|
||||||
|
|
@ -330,7 +335,7 @@ def resolve_overlaps(rows: list[dict]) -> list[dict]:
|
||||||
if overlap_end_m <= b_start_m:
|
if overlap_end_m <= b_start_m:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
midpoint_m = (b_start_m + overlap_end_m) // 2
|
midpoint_m = _round_to_5((b_start_m + overlap_end_m) // 2)
|
||||||
replacements: list[dict] = []
|
replacements: list[dict] = []
|
||||||
|
|
||||||
if b_end_m <= a_end_m:
|
if b_end_m <= a_end_m:
|
||||||
|
|
|
||||||
|
|
@ -678,3 +678,45 @@ class TestResolveOverlaps:
|
||||||
assert result[i]["end"] <= result[i + 1]["start"], (
|
assert result[i]["end"] <= result[i + 1]["start"], (
|
||||||
f"Overlap between entry {i} ({result[i]}) and {i + 1} ({result[i + 1]})"
|
f"Overlap between entry {i} ({result[i]}) and {i + 1} ({result[i + 1]})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- midpoint rounding to 5 minutes ---
|
||||||
|
|
||||||
|
def test_partial_overlap_midpoint_rounds_down(self):
|
||||||
|
"""Midpoint not on a 5-min boundary is rounded down to the nearest 5 min.
|
||||||
|
|
||||||
|
09:00-10:00 vs 09:33-10:33
|
||||||
|
overlap region: 09:33-10:00 raw midpoint: 09:46 rounded: 09:45
|
||||||
|
"""
|
||||||
|
rows = [self._row("09:00", "10:00", "a"), self._row("09:33", "10:33", "b")]
|
||||||
|
result = self._sorted(resolve_overlaps(rows))
|
||||||
|
assert len(result) == 2
|
||||||
|
assert (result[0]["start"], result[0]["end"]) == ("09:00", "09:45")
|
||||||
|
assert (result[1]["start"], result[1]["end"]) == ("09:45", "10:33")
|
||||||
|
|
||||||
|
def test_partial_overlap_midpoint_rounds_up(self):
|
||||||
|
"""Midpoint not on a 5-min boundary is rounded up to the nearest 5 min.
|
||||||
|
|
||||||
|
09:00-10:00 vs 09:37-10:37
|
||||||
|
overlap region: 09:37-10:00 raw midpoint: 09:48 rounded: 09:50
|
||||||
|
"""
|
||||||
|
rows = [self._row("09:00", "10:00", "a"), self._row("09:37", "10:37", "b")]
|
||||||
|
result = self._sorted(resolve_overlaps(rows))
|
||||||
|
assert len(result) == 2
|
||||||
|
assert (result[0]["start"], result[0]["end"]) == ("09:00", "09:50")
|
||||||
|
assert (result[1]["start"], result[1]["end"]) == ("09:50", "10:37")
|
||||||
|
|
||||||
|
def test_containment_midpoint_rounded(self):
|
||||||
|
"""Containment case: boundary is rounded to the nearest 5 min.
|
||||||
|
|
||||||
|
09:00-10:00 contains 09:14-09:44
|
||||||
|
overlap region: 09:14-09:44 raw midpoint: 09:29 rounded: 09:30
|
||||||
|
"""
|
||||||
|
rows = [self._row("09:00", "10:00", "a"), self._row("09:14", "09:44", "b")]
|
||||||
|
result = self._sorted(resolve_overlaps(rows))
|
||||||
|
assert len(result) == 3
|
||||||
|
assert (result[0]["start"], result[0]["end"]) == ("09:00", "09:30")
|
||||||
|
assert result[0]["project"] == "a"
|
||||||
|
assert (result[1]["start"], result[1]["end"]) == ("09:30", "09:44")
|
||||||
|
assert result[1]["project"] == "b"
|
||||||
|
assert (result[2]["start"], result[2]["end"]) == ("09:44", "10:00")
|
||||||
|
assert result[2]["project"] == "a"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue