Skip to content

Commit d3d0e42

Browse files
authored
feat: add priority-based locomotive dispatcher with fill-level rules
Introduce a central LocomotiveDispatcher that assigns locomotives to tasks based on configurable, dynamic priorities. Priorities react to current track fill levels, replacing the implicit FIFO allocation. Key additions: - TaskPriorityConfig domain model with base priority, rules, and hold_until - LocomotiveDispatcher service with priority queue and eligibility gating - task_priorities configuration in scenario.json - hold_until condition to suppress wasteful small-batch trips - All four coordinators wired to use dispatcher when configured - Fallback to direct FIFO allocation when task_priorities is not set Also adds track_type_fill_factors to scenario.json for per-type max fill levels (separate from per-track fillfactor overrides in tracks.json). Includes example scenario (ten_trains_two_days_priority_dispatch) and updated tutorial documentation.
1 parent b14c608 commit d3d0e42

26 files changed

Lines changed: 1356 additions & 5 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"metadata": {
3+
"description": "locomotive configurations for DAC retrofit operations",
4+
"version": "1.0.0",
5+
"created": "2024-01-15"
6+
},
7+
"locomotives": [
8+
{
9+
"id": "LOCO_01",
10+
"name": "Shunting loco",
11+
"home track": "track_19"
12+
}
13+
]
14+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"wagon_retrofit_time": 60.0,
3+
"train_to_hump_delay": 0.0,
4+
"wagon_hump_interval": 0.0,
5+
"screw_coupling_time": 2.0,
6+
"screw_decoupling_time": 3.0,
7+
"dac_coupling_time": 0.5,
8+
"dac_decoupling_time": 0.5
9+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"routes": [
3+
{"id": "track_19_collection1", "duration": 60.0, "path": ["track_19", "Mainline", "collection1"]},
4+
{"id": "track_19_collection2", "duration": 60.0, "path": ["track_19", "Mainline", "collection2"]},
5+
{"id": "track_19_collection3", "duration": 60.0, "path": ["track_19", "Mainline", "collection2"]},
6+
{"id": "track_19_retrofit", "duration": 5.0, "path": ["track_19", "retrofit"]},
7+
{"id": "track_19_WS_01", "duration": 5.0, "path": ["track_19", "WS_01"]},
8+
{"id": "track_19_WS_02", "duration": 5.0, "path": ["track_19", "WS_02"]},
9+
{"id": "track_19_retrofitted", "duration": 5.0, "path": ["track_19", "retrofitted"]},
10+
{"id": "collection1_track_19", "duration": 60.0, "path": ["collection1", "Mainline", "track_19"]},
11+
{"id": "collection2_track_19", "duration": 60.0, "path": ["collection2", "Mainline", "track_19"]},
12+
{"id": "retrofit_track_19", "duration": 5.0, "path": ["retrofit", "Mainline", "track_19"]},
13+
{"id": "WS_01_track_19", "duration": 5.0, "path": ["WS_01", "track_19"]},
14+
{"id": "WS_02_track_19", "duration": 5.0, "path": ["WS_02", "track_19"]},
15+
{"id": "retrofitted_track_19", "duration": 5.0, "path": ["retrofitted", "track_19"]},
16+
{"id": "collection1_retrofit", "duration": 60.0, "path": ["collection1", "Mainline", "retrofit"]},
17+
{"id": "collection2_retrofit", "duration": 60.0, "path": ["collection2", "Mainline", "retrofit"]},
18+
{"id": "retrofit_WS_01", "duration": 5.0, "path": ["retrofit", "WS_01"]},
19+
{"id": "retrofit_WS_02", "duration": 5.0, "path": ["retrofit", "WS_02"]},
20+
{"id": "WS_01_retrofitted", "duration": 5.0, "path": ["WS_01", "retrofitted"]},
21+
{"id": "WS_02_retrofitted", "duration": 5.0, "path": ["WS_02", "retrofitted"]},
22+
{"id": "retrofitted_parking1", "duration": 5.0, "path": ["retrofitted", "parking1"]},
23+
{"id": "retrofitted_parking2", "duration": 5.0, "path": ["retrofitted", "parking2"]},
24+
{"id": "retrofitted_parking3", "duration": 5.0, "path": ["retrofitted", "parking3"]},
25+
{"id": "retrofitted_parking5", "duration": 5.0, "path": ["retrofitted", "parking5"]},
26+
{"id": "retrofitted_parking6", "duration": 5.0, "path": ["retrofitted", "Mainline", "parking6"]},
27+
{"id": "retrofitted_parking7", "duration": 5.0, "path": ["retrofitted", "Mainline", "parking7"]},
28+
{"id": "retrofitted_parking8", "duration": 5.0, "path": ["retrofitted", "Mainline", "parking8"]},
29+
{"id": "retrofitted_parking9", "duration": 5.0, "path": ["retrofitted", "Mainline", "parking9"]},
30+
{"id": "retrofitted_parking10", "duration": 60.0, "path": ["retrofitted", "Mainline", "parking10"]},
31+
{"id": "retrofitted_parking11", "duration": 60.0, "path": ["retrofitted", "Mainline", "parking11"]},
32+
{"id": "retrofitted_parking12", "duration": 60.0, "path": ["retrofitted", "Mainline", "parking12"]},
33+
{"id": "retrofitted_parking13", "duration": 60.0, "path": ["retrofitted", "Mainline", "parking13"]},
34+
{"id": "retrofitted_parking14", "duration": 60.0, "path": ["retrofitted", "Mainline", "parking14"]},
35+
{"id": "retrofitted_parking15", "duration": 60.0, "path": ["retrofitted", "Mainline", "parking15"]},
36+
{"id": "retrofitted_parking16", "duration": 60.0, "path": ["retrofitted", "Mainline", "parking16"]},
37+
{"id": "parking1_track_19", "duration": 5.0, "path": ["parking1", "track_19"]},
38+
{"id": "parking2_track_19", "duration": 5.0, "path": ["parking2", "track_19"]},
39+
{"id": "parking3_track_19", "duration": 5.0, "path": ["parking3", "track_19"]},
40+
{"id": "parking5_track_19", "duration": 5.0, "path": ["parking5", "track_19"]},
41+
{"id": "parking6_track_19", "duration": 5.0, "path": ["parking6", "Mainline", "track_19"]},
42+
{"id": "parking7_track_19", "duration": 5.0, "path": ["parking7", "Mainline", "track_19"]},
43+
{"id": "parking8_track_19", "duration": 5.0, "path": ["parking8", "Mainline", "track_19"]},
44+
{"id": "parking9_track_19", "duration": 5.0, "path": ["parking9", "Mainline", "track_19"]},
45+
{"id": "parking10_track_19", "duration": 60.0, "path": ["parking10", "Mainline", "track_19"]},
46+
{"id": "parking11_track_19", "duration": 60.0, "path": ["parking11", "Mainline", "track_19"]},
47+
{"id": "parking12_track_19", "duration": 60.0, "path": ["parking12", "Mainline", "track_19"]},
48+
{"id": "parking13_track_19", "duration": 60.0, "path": ["parking13", "Mainline", "track_19"]},
49+
{"id": "parking14_track_19", "duration": 60.0, "path": ["parking14", "Mainline", "track_19"]},
50+
{"id": "parking15_track_19", "duration": 60.0, "path": ["parking15", "Mainline", "track_19"]},
51+
{"id": "parking16_track_19", "duration": 60.0, "path": ["parking16", "Mainline", "track_19"]}
52+
]
53+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"id": "priority_dispatch_scenario",
3+
"description": "DAC retrofit simulation with priority-based locomotive dispatch. Same infrastructure as baseline but with fill-level-dependent task prioritization and hold conditions.",
4+
"version": "1.0.0",
5+
"start_date": "2025-12-01T00:00:00+00:00",
6+
"end_date": "2025-12-11T00:00:00+00:00",
7+
"workflow_mode": "retrofit_workflow",
8+
"collection_track_strategy": "first_available",
9+
"retrofit_selection_strategy": "least_occupied",
10+
"retrofitted_selection_strategy": "least_occupied",
11+
"workshop_selection_strategy": "least_occupied",
12+
"parking_selection_strategy": "least_occupied",
13+
"task_priorities": {
14+
"collection_to_retrofit": {
15+
"base_priority": 2,
16+
"hold_until": {"condition": "target_fill_below", "threshold": 0.85},
17+
"rules": [
18+
{"condition": "source_fill_above", "threshold": 0.5, "priority": 1},
19+
{"condition": "source_fill_above", "threshold": 0.8, "priority": 0}
20+
]
21+
},
22+
"retrofit_to_workshop": {
23+
"base_priority": 3,
24+
"rules": [
25+
{"condition": "target_idle", "threshold": 0.0, "priority": 0},
26+
{"condition": "source_fill_below", "threshold": 0.1, "priority": 5}
27+
]
28+
},
29+
"workshop_to_retrofitted": {
30+
"base_priority": 1,
31+
"rules": []
32+
},
33+
"retrofitted_to_parking": {
34+
"base_priority": 3,
35+
"hold_until": {"condition": "source_fill_above", "threshold": 0.15},
36+
"rules": [
37+
{"condition": "source_fill_above", "threshold": 0.6, "priority": 1},
38+
{"condition": "source_fill_above", "threshold": 0.85, "priority": 0}
39+
]
40+
}
41+
},
42+
"references": {
43+
"locomotives": "locomotive.json",
44+
"process_times": "process_times.json",
45+
"routes": "routes.json",
46+
"trains": "train_schedule.csv",
47+
"topology": "topology.json",
48+
"tracks": "tracks.json",
49+
"workshops": "workshops.json"
50+
}
51+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"nodes": [1, 2],
3+
"edges": {
4+
"parking1": {"nodes": [1, 2], "length": 150.0},
5+
"parking2": {"nodes": [1, 2], "length": 270.0},
6+
"parking3": {"nodes": [1, 2], "length": 257.0},
7+
"parking5": {"nodes": [1, 2], "length": 216.0},
8+
"parking6": {"nodes": [1, 2], "length": 266.0},
9+
"parking7": {"nodes": [1, 2], "length": 212.0},
10+
"parking8": {"nodes": [1, 2], "length": 203.0},
11+
"parking9": {"nodes": [1, 2], "length": 400.0},
12+
"parking10": {"nodes": [1, 2], "length": 600.0},
13+
"parking11": {"nodes": [1, 2], "length": 400.0},
14+
"parking12": {"nodes": [1, 2], "length": 400.0},
15+
"parking13": {"nodes": [1, 2], "length": 400.0},
16+
"parking14": {"nodes": [1, 2], "length": 400.0},
17+
"parking15": {"nodes": [1, 2], "length": 400.0},
18+
"parking16": {"nodes": [1, 2], "length": 100.0},
19+
"track_19": {"nodes": [1, 2], "length": 169.0},
20+
"collection1": {"nodes": [1, 2], "length": 500.0},
21+
"collection2": {"nodes": [1, 2], "length": 500.0},
22+
"retrofit": {"nodes": [1, 2], "length": 426.0},
23+
"retrofitted": {"nodes": [1, 2], "length": 352.0},
24+
"WS1": {"nodes": [1, 2], "length": 260.0},
25+
"WS2": {"nodes": [1, 2], "length": 260.0},
26+
"Mainline": {"nodes": [1, 2], "length": 8000.0}
27+
}
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"tracks": [
3+
{"id": "parking1", "name": "Parking track", "edges": ["parking1"], "type": "parking"},
4+
{"id": "parking2", "name": "Parking track", "edges": ["parking2"], "type": "parking"},
5+
{"id": "parking3", "name": "Parking track", "edges": ["parking3"], "type": "parking"},
6+
{"id": "parking5", "name": "Parking track", "edges": ["parking5"], "type": "parking"},
7+
{"id": "parking6", "name": "Parking track", "edges": ["parking6"], "type": "parking"},
8+
{"id": "parking7", "name": "Parking track", "edges": ["parking7"], "type": "parking"},
9+
{"id": "parking8", "name": "Parking track", "edges": ["parking8"], "type": "parking"},
10+
{"id": "parking9", "name": "Parking track", "edges": ["parking9"], "type": "parking"},
11+
{"id": "parking10", "name": "Parking track", "edges": ["parking10"], "type": "parking"},
12+
{"id": "parking11", "name": "Parking track", "edges": ["parking11"], "type": "parking"},
13+
{"id": "parking12", "name": "Parking track", "edges": ["parking12"], "type": "parking"},
14+
{"id": "parking13", "name": "Parking track", "edges": ["parking13"], "type": "parking"},
15+
{"id": "parking14", "name": "Parking track", "edges": ["parking14"], "type": "parking"},
16+
{"id": "parking15", "name": "Parking track", "edges": ["parking15"], "type": "parking"},
17+
{"id": "parking16", "name": "Parking track", "edges": ["parking16"], "type": "parking"},
18+
{"id": "track_19", "name": "Locomotive parking", "edges": ["track_19"], "type": "rescource_parking"},
19+
{"id": "collection1", "name": "Collection track", "edges": ["collection1"], "type": "collection"},
20+
{"id": "collection2", "name": "Collection track", "edges": ["collection2"], "type": "collection"},
21+
{"id": "retrofit", "edges": ["retrofit"], "type": "retrofit"},
22+
{"id": "retrofitted", "edges": ["retrofitted"], "type": "retrofitted"},
23+
{"id": "WS1", "edges": ["WS1"], "type": "workshop"},
24+
{"id": "WS2", "edges": ["WS2"], "type": "workshop"},
25+
{"id": "Mainline", "name": "Mainline connecting yards", "edges": ["Mainline"], "type": "mainline"}
26+
]
27+
}

0 commit comments

Comments
 (0)