use std::cmp::Ordering;
use argonauts_core::events::task::TaskSpec;
use argonauts_core::limits::TrackRecord;
use argonauts_core::names::ModelRef;
use argonauts_core::session_runner::RunnerKind;
use argonauts_core::settings::fallback::FallbackEntry;
use crate::engine::host::Host;
use crate::fallback::{debt, rules};
pub fn candidates(host: &Host, task: &TaskSpec, stopped: &ModelRef) -> Vec<FallbackEntry> {
let services = host.services();
let task_type = debt::task_type(&task.kind);
let mut ranked: Vec<(Option<f64>, Option<f64>, ModelRef)> = Vec::new();
for worker in services.workers.available() {
let cli = services
.workers
.get(&worker)
.is_ok_and(|agent| agent.kind() == RunnerKind::Cli);
if worker == *stopped
|| !cli
|| !rules::takes_work(host, &worker)
|| ranked.iter().any(|(_, _, seen)| *seen == worker)
{
continue;
}
let record = pass_rate(services.track_records.get(&worker, task_type));
let plan_usage = services
.plan_usage
.get(&worker.agent_id())
.and_then(|reading| reading.used)
.map(|used| (1.0 - used).clamp(0.0, 1.0));
ranked.push((record, plan_usage, worker));
}
ranked.sort_by(|(a_record, a_plan_usage, _), (b_record, b_plan_usage, _)| {
descending(*a_record, *b_record).then_with(|| descending(*a_plan_usage, *b_plan_usage))
});
ranked
.into_iter()
.map(|(_, _, worker)| FallbackEntry::Model(worker))
.collect()
}
pub fn apply(host: &Host, task: &TaskSpec, stopped: &ModelRef) -> bool {
rules::applied_for(host, task, stopped)
.candidates
.is_empty()
}
fn pass_rate(record: Option<TrackRecord>) -> Option<f64> {
record
.filter(|record| record.of > 0)
.map(|record| f64::from(record.passed) / f64::from(record.of))
}
fn descending(a: Option<f64>, b: Option<f64>) -> Ordering {
match (a, b) {
(Some(a), Some(b)) => b.total_cmp(&a),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => Ordering::Equal,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn known_values_come_first_and_highest_first() {
let mut values = vec![None, Some(0.2), Some(0.9), None, Some(0.5)];
values.sort_by(|a, b| descending(*a, *b));
assert_eq!(values, [Some(0.9), Some(0.5), Some(0.2), None, None]);
}
#[test]
fn a_record_with_no_finished_task_says_nothing() {
assert_eq!(pass_rate(Some(TrackRecord { passed: 0, of: 0 })), None);
assert_eq!(
pass_rate(Some(TrackRecord { passed: 3, of: 4 })),
Some(0.75)
);
}
}