use serde::{Deserialize, Serialize};
use converge_pack::{ExecutionIdentity, FactPayload};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SchedulingAgent {
pub id: usize,
pub name: String,
pub capabilities: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SchedulingTask {
pub id: usize,
pub name: String,
pub required_capability: String,
pub duration_min: i64,
pub release_min: i64,
pub deadline_min: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SchedulingRequest {
pub id: String,
pub agents: Vec<SchedulingAgent>,
pub tasks: Vec<SchedulingTask>,
pub horizon_min: i64,
#[serde(default = "default_time_limit")]
pub time_limit_seconds: f64,
}
impl FactPayload for SchedulingRequest {
const FAMILY: &'static str = "ferrox.scheduling.request";
const VERSION: u16 = 1;
}
fn default_time_limit() -> f64 {
30.0
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TaskAssignment {
pub task_id: usize,
pub task_name: String,
pub agent_id: usize,
pub agent_name: String,
pub start_min: i64,
pub end_min: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SchedulingPlan {
pub request_id: String,
pub assignments: Vec<TaskAssignment>,
pub tasks_total: usize,
pub tasks_scheduled: usize,
pub makespan_min: i64,
pub solver: String,
pub execution_identity: ExecutionIdentity,
pub status: String,
pub wall_time_seconds: f64,
}
impl FactPayload for SchedulingPlan {
const FAMILY: &'static str = "ferrox.scheduling.plan";
const VERSION: u16 = 1;
}
impl SchedulingPlan {
#[allow(clippy::cast_precision_loss)]
pub fn throughput_ratio(&self) -> f64 {
if self.tasks_total == 0 {
return 0.0;
}
self.tasks_scheduled as f64 / self.tasks_total as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::solver_identity::non_native_solver_identity;
fn empty_plan(tasks_total: usize, tasks_scheduled: usize) -> SchedulingPlan {
SchedulingPlan {
request_id: "r".into(),
assignments: vec![],
tasks_total,
tasks_scheduled,
makespan_min: 0,
solver: "x".into(),
execution_identity: non_native_solver_identity("x", "test"),
status: "feasible".into(),
wall_time_seconds: 0.0,
}
}
#[test]
fn throughput_ratio_zero_when_no_tasks() {
let p = empty_plan(0, 0);
assert!((p.throughput_ratio() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn throughput_ratio_partial() {
let p = empty_plan(10, 7);
assert!((p.throughput_ratio() - 0.7).abs() < 1e-9);
}
#[test]
fn request_serde_round_trip_with_default_time_limit() {
let json = r#"{"id":"r","agents":[],"tasks":[],"horizon_min":120}"#;
let r: SchedulingRequest = serde_json::from_str(json).unwrap();
assert!((r.time_limit_seconds - 30.0).abs() < f64::EPSILON);
}
}