feat(breaks): trace vehicle breaks between problem and solution by id - #19
Merged
Merged
Conversation
Vehicle breaks were impossible to identify in the output. Optional breaks
surfaced as an activity with `jobId: "break"`, traceable only through the
per-place `tag`; required breaks are flattened into reserved time spans and
lost every link back to the problem, so a tour with two of them gave no way
to tell which was which. Break violations named only the vehicle and shift.
Both break variants now accept an optional `id` which is propagated back to
the solution as `breakId` on the break activity and on the break violation.
- vrp-core: `ReservedTimeSpan`/`ReservedTimeWindow` carry an optional id, the
only way a required break can keep its identity, since it never becomes a job
- optional breaks store the id in a new `BreakId` dimension; the synthetic
`{vehicle}_break_{shift}_{idx}` job id is left alone because initial solution
reading keys off that naming
- `breakId` is a new field rather than an overload of `jobId`, so consumers
filtering on `jobId == "break"` and the round trip reader keep working
- the activity matcher and checker prefer the id over time window intersection
when resolving a break, and the checker asserts the emitted id matches
- new E1309 validation: break ids must be unique within a shift
Adding a public field to `ReservedTimeSpan` breaks external struct literals,
hence the version bump to 1.25.7.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Formatting drift left over from earlier changes, unrelated to any behaviour. `cargo fmt --all -- --check` is now clean, so a future edit to one of these files no longer drags unrelated reformatting into its diff. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hutchinsp01
approved these changes
Sep 9, 2026
hutchinsp01
left a comment
Collaborator
There was a problem hiding this comment.
Cool, lots of plumbing but can't see anything out of place
Comment on lines
+264
to
+274
| fn check_e1309_vehicle_break_ids_are_unique(ctx: &ValidationContext) -> Result<(), FormatError> { | ||
| let duplicates = ctx | ||
| .vehicles() | ||
| .flat_map(|vehicle| vehicle.shifts.iter()) | ||
| .filter_map(|shift| { | ||
| get_duplicates(shift.breaks.iter().flat_map(|breaks| breaks.iter()).filter_map(|br| br.id())) | ||
| }) | ||
| .flatten() | ||
| .collect::<HashSet<_>>(); | ||
|
|
||
| if duplicates.is_empty() { |
Collaborator
There was a problem hiding this comment.
Do we ever send up the same taskSession to two seperate shifts for multiple techs?
Or are you going to handle that as 2 seperate break id's on the WF side?
Author
There was a problem hiding this comment.
I reckon we probably do but I think I'll burn that bridge when we get to it. I expect we will just send it as two separate breaks with a guid or something.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Vehicle breaks cannot be identified in the solution output.
jobId: "break"for every break activity. The only trace available is the per-placetag, which is optional, sits on the place rather than the break, and is matched by location and time.Change
Both
VehicleBreakvariants accept an optionalid, propagated back to the solution asbreakIdon the break activity and on the break violation.vrp-core:ReservedTimeSpan/ReservedTimeWindowcarry an optional id — the only way a required break can keep its identity, since it never becomes a job.BreakIddimension. The synthetic{vehicle}_break_{shift}_{idx}job id is left alone, because initial-solution reading keys off that naming.breakIdis a new optional field rather than an overload ofjobId, so consumers filtering onjobId == "break"and the round-trip reader keep working.E1309validation: break ids must be unique within a shift.Existing problem JSON is unaffected —
idis optional and skipped on serialization, and the untagged enum still disambiguates onplaces/duration.Compatibility
Adding a public field to
ReservedTimeSpanbreaks external struct literals, hence the version bump to 1.25.7. All crates shareworkspace.package.version, so the whole workspace moves together.Tests
New
tests/features/breaks/break_id_test.rscovers optional breaks, required breaks, two breaks distinguished by id, violation propagation, and the id-omitted case; parameterizedE1309cases added to the validation tests. Full suite passes.The second commit is a standalone
cargo fmt --allover formatting drift that predates this work, so it does not muddy the feature diff.cargo fmt --all -- --checkis now clean.🤖 Generated with Claude Code