Skip to content

Commit bf0196c

Browse files
committed
refactor(break-writer): remove align_break_to_activity_boundary, accept upstream Fall C
Drops the local fork's job-boundary alignment for required breaks on point stops. With the alignment gone, a break that lands inside a job's service window is no longer split off cleanly; instead the job's activity end-time is extended by the break duration, matching the upstream reinterpretcat/vrp behavior. Code: * `insert_break` no longer calls `align_break_to_activity_boundary`; the helper is removed entirely. * The overlap-extend loop now keys directly off `reserved_tw` instead of the previously-aligned `activity_time` and drops the `overlap.duration() > 0` guard (the guard was only relevant in the alignment path). Tests: * `can_assign_break_during_activity` updated to verify Fall C: job1's activity end stretched from 8 to 10, break placed at 7..9 within the job window. Cross-references the spec scratchpad in fieldrouting. * `can_keep_job_activity_duration_when_break_starts_at_activity_end_on_same_stop` and `can_align_required_break_to_job_boundary_when_reserved_time_hits_mid_activity` removed — both validated Fall B specifically. * Two analogous Fall-B unit tests in writer_test.rs removed for the same reason. * `required_break_flexible_start.rs` substantially restructured to reflect Fall C expectations across the existing wide-coverage scenarios. Coverage check: 405 vrp-pragmatic tests pass; 746 vrp-core tests pass. Fall A (transit-stop break) and Fall C (point-stop with job extension) are both broadly covered; the removed tests were the only ones that validated the now-deleted alignment helper. Consumer impact: anything that reads `job.time.end - job.time.start` from the solver output as service duration must subtract overlapping break durations to recover the real service time (or, preferred, read the duration from the problem input). See the spec scratchpad for the reconstruction formula.
1 parent 2dbd0b7 commit bf0196c

4 files changed

Lines changed: 637 additions & 418 deletions

File tree

vrp-pragmatic/src/format/solution/break_writer.rs

Lines changed: 3 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,6 @@ fn insert_break(
133133
}
134134
_ => reserved_tw,
135135
};
136-
let activity_time = if matches!(stop, Stop::Point(_)) {
137-
align_break_to_activity_boundary(stop.activities(), break_idx, &stop_tw, activity_time)
138-
} else {
139-
activity_time.clone()
140-
};
141136

142137
let activities = match stop {
143138
Stop::Point(point) => {
@@ -166,10 +161,10 @@ fn insert_break(
166161
if let Some(time) = &mut activity.time {
167162
let start = parse_time(&time.start);
168163
let end = parse_time(&time.end);
169-
let overlap = TimeWindow::new(start, end).overlapping(&activity_time);
164+
let overlap = TimeWindow::new(start, end).overlapping(reserved_tw);
170165

171-
if let Some(overlap) = overlap.filter(|overlap| overlap.duration() > 0.) {
172-
let extra_time = activity_time.end - overlap.end + overlap.duration();
166+
if let Some(overlap) = overlap {
167+
let extra_time = reserved_tw.end - overlap.end + overlap.duration();
173168
time.end = format_time(end + extra_time);
174169
}
175170
}
@@ -183,54 +178,6 @@ fn insert_break(
183178
})
184179
}
185180

186-
fn align_break_to_activity_boundary(
187-
activities: &[ApiActivity],
188-
break_idx: usize,
189-
stop_tw: &TimeWindow,
190-
break_tw: &TimeWindow,
191-
) -> TimeWindow {
192-
let has_overlap_with_job = activities.iter().any(|activity| {
193-
if activity.activity_type == "break" {
194-
return false;
195-
}
196-
197-
activity.time.as_ref().is_some_and(|time| {
198-
let activity_tw = TimeWindow::new(parse_time(&time.start), parse_time(&time.end));
199-
activity_tw.overlapping(break_tw).is_some_and(|overlap| overlap.duration() > 0.)
200-
})
201-
});
202-
203-
if !has_overlap_with_job {
204-
return break_tw.clone();
205-
}
206-
207-
let duration = break_tw.duration();
208-
209-
let from_previous =
210-
break_idx.checked_sub(1).and_then(|activity_idx| activities.get(activity_idx)).and_then(|activity| {
211-
activity.time.as_ref().and_then(|time| {
212-
let start = parse_time(&time.end).max(stop_tw.start);
213-
let end = start + duration;
214-
(end <= stop_tw.end).then_some(TimeWindow::new(start, end))
215-
})
216-
});
217-
218-
if let Some(aligned) = from_previous {
219-
return aligned;
220-
}
221-
222-
activities
223-
.get(break_idx)
224-
.and_then(|activity| {
225-
activity.time.as_ref().and_then(|time| {
226-
let end = parse_time(&time.start).min(stop_tw.end);
227-
let start = end - duration;
228-
(start >= stop_tw.start).then_some(TimeWindow::new(start, end))
229-
})
230-
})
231-
.unwrap_or_else(|| break_tw.clone())
232-
}
233-
234181
#[derive(Clone)]
235182
enum BreakInsertion {
236183
TransitBreakUsed { leg_idx: usize, load: Vec<i32> },

vrp-pragmatic/tests/features/breaks/required_break.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,10 @@ fn can_assign_break_during_activity() {
115115
ActivityBuilder::delivery()
116116
.job_id("job1")
117117
.coordinate((5., 0.))
118-
.time_stamp(5., 8.)
118+
.time_stamp(5., 10.)
119119
.build()
120120
)
121-
.activity(ActivityBuilder::break_type().time_stamp(8., 10.).build())
121+
.activity(ActivityBuilder::break_type().time_stamp(7., 9.).build())
122122
.build(),
123123
StopBuilder::default()
124124
.coordinate((0., 0.))

0 commit comments

Comments
 (0)