Spaces:
Sleeping
Sleeping
File size: 4,034 Bytes
2574e86 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | use serde::{Deserialize, Serialize};
use solverforge::prelude::*;
/// One technician's route, including the visit order SolverForge is allowed to change.
///
/// A `TechnicianRoute` is the planning entity in this app. Its descriptive
/// fields are fixed input data for the technician, while `visits` is the list
/// planning variable that local search reorders and moves between routes.
#[planning_entity]
#[derive(Serialize, Deserialize)]
pub struct TechnicianRoute {
#[planning_id]
pub id: String,
pub technician_id: String,
pub technician_name: String,
pub color: String,
pub start_location_idx: usize,
pub end_location_idx: usize,
pub shift_start_minute: i32,
pub shift_end_minute: i32,
pub max_route_minutes: i32,
pub skill_mask: i64,
pub inventory_mask: i64,
pub territory: String,
// SolverForge mutates this vector. Each value is an index into
// `FieldServicePlan.service_visits`, not a copied `ServiceVisit`.
// @solverforge:begin entity-variables
#[planning_list_variable(element_collection = "service_visits")]
pub visits: Vec<usize>,
// @solverforge:end entity-variables
}
/// Constructor payload for `TechnicianRoute`.
///
/// Grouping the technician attributes keeps call sites readable and makes the
/// immutable technician data visually separate from the mutable route list.
#[derive(Debug, Clone)]
pub struct TechnicianRouteInit {
pub id: String,
pub technician_id: String,
pub technician_name: String,
pub color: String,
pub start_location_idx: usize,
pub end_location_idx: usize,
pub shift_start_minute: i32,
pub shift_end_minute: i32,
pub max_route_minutes: i32,
pub skill_mask: i64,
pub inventory_mask: i64,
pub territory: String,
}
impl TechnicianRoute {
/// Builds an empty route for one technician.
///
/// The list variable starts empty so construction heuristics can choose the
/// first assignment instead of inheriting a hand-written visit order.
pub fn new(init: TechnicianRouteInit) -> Self {
Self {
id: init.id,
technician_id: init.technician_id,
technician_name: init.technician_name,
color: init.color,
start_location_idx: init.start_location_idx,
end_location_idx: init.end_location_idx,
shift_start_minute: init.shift_start_minute,
shift_end_minute: init.shift_end_minute,
max_route_minutes: init.max_route_minutes,
skill_mask: init.skill_mask,
inventory_mask: init.inventory_mask,
territory: init.territory,
// @solverforge:begin entity-variable-init
visits: Vec::new(),
// @solverforge:end entity-variable-init
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_technician_route_construction() {
let entity = TechnicianRoute::new(TechnicianRouteInit {
id: "test-id".to_string(),
technician_id: "test".to_string(),
technician_name: "test".to_string(),
color: "test".to_string(),
start_location_idx: Default::default(),
end_location_idx: Default::default(),
shift_start_minute: Default::default(),
shift_end_minute: Default::default(),
max_route_minutes: Default::default(),
skill_mask: Default::default(),
inventory_mask: Default::default(),
territory: "test".to_string(),
});
assert_eq!(entity.id, "test-id");
let _ = &entity.technician_id;
let _ = &entity.technician_name;
let _ = &entity.color;
let _ = &entity.start_location_idx;
let _ = &entity.end_location_idx;
let _ = &entity.shift_start_minute;
let _ = &entity.shift_end_minute;
let _ = &entity.max_route_minutes;
let _ = &entity.skill_mask;
let _ = &entity.inventory_mask;
let _ = &entity.territory;
}
}
|