File size: 2,702 Bytes
f6213fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
//! Delivery problem facts.
//!
//! A delivery is input data, not something SolverForge mutates directly. The
//! solver places delivery ids into each vehicle's list variable.

use serde::{Deserialize, Serialize};
use solverforge::prelude::*;
use solverforge_maps::{Coord, RoutingError};

use super::CoordValue;

/// A delivery stop that can be assigned into a vehicle route.
#[problem_fact]
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Delivery {
    #[planning_id]
    pub id: usize,
    pub label: String,
    pub kind: DeliveryKind,
    /// Latitude in decimal degrees, wrapped so derived equality stays stable.
    pub lat: CoordValue,
    /// Longitude in decimal degrees, wrapped so derived equality stays stable.
    pub lng: CoordValue,
    /// Load consumed from the assigned vehicle capacity.
    pub demand: i32,
    /// Earliest allowed service start, expressed as seconds after midnight.
    pub min_start_time: i64,
    /// Latest allowed service end, expressed as seconds after midnight.
    pub max_end_time: i64,
    /// Time spent at the stop after arrival.
    pub service_duration: i64,
}

/// Coarse stop type used to shape demo-data demand and UI icons.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeliveryKind {
    Residential,
    Business,
    Restaurant,
    #[default]
    Other,
}

impl Delivery {
    /// Creates one problem fact from transport-friendly primitive values.
    pub fn new(
        id: usize,
        label: impl Into<String>,
        kind: DeliveryKind,
        coord: (f64, f64),
        demand: i32,
        time_window: (i64, i64),
        service_duration: i64,
    ) -> Self {
        Self {
            id,
            label: label.into(),
            kind,
            lat: coord.0.into(),
            lng: coord.1.into(),
            demand,
            min_start_time: time_window.0,
            max_end_time: time_window.1,
            service_duration,
        }
    }

    /// Converts the serialized coordinates into the map library's checked type.
    pub fn coord(&self) -> Result<Coord, RoutingError> {
        Ok(Coord::try_new(self.lat.get(), self.lng.get())?)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_delivery_construction() {
        let fact = Delivery::new(
            3,
            "Test stop",
            DeliveryKind::Business,
            (43.77, 11.25),
            4,
            (9 * 3600, 17 * 3600),
            20 * 60,
        );
        assert_eq!(fact.id, 3);
        assert_eq!(fact.label, "Test stop");
        assert_eq!(fact.kind, DeliveryKind::Business);
    }
}