Skip to main content

dpm_engine/query/
advisory.rs

1//! Explained advice attached to `next` that never hides, removes or reorders candidates.
2
3use dpm_model::{ActorId, Key, Plan, WorkStatus};
4use serde::{Deserialize, Serialize};
5
6/// Advice about the caller's situation, shown beside the ranking.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(tag = "kind", rename_all = "snake_case")]
9pub enum Advisory {
10    /// The actor already holds claimed, started or blocked work.
11    HoldingClaims {
12        /// Actor the advice is for.
13        actor: ActorId,
14        /// Keys of the work it holds, in key order.
15        holding: Vec<Key>,
16        /// Why this is worth knowing before claiming more.
17        reason: String,
18    },
19}
20
21/// Advice for an actor about to choose work: whether it already holds claims.
22#[must_use]
23pub fn advisories(plan: &Plan, actor: &ActorId) -> Vec<Advisory> {
24    const HELD: [WorkStatus; 3] = [
25        WorkStatus::Claimed,
26        WorkStatus::InProgress,
27        WorkStatus::Blocked,
28    ];
29    let mut holding: Vec<Key> = plan
30        .work_items
31        .values()
32        .filter(|w| HELD.contains(&w.execution.status))
33        .filter(|w| w.execution.owner.as_ref() == Some(actor))
34        .map(|w| w.key.clone())
35        .collect();
36    if holding.is_empty() {
37        return Vec::new();
38    }
39    holding.sort_by(Key::natural_cmp);
40    let reason = format!(
41        "{actor} already holds {} claimed, started or blocked task(s); finishing, releasing or \
42         handing off held work before claiming more keeps work in progress and its aging low. \
43         Advice only: the candidates are neither filtered nor reordered",
44        holding.len()
45    );
46    vec![Advisory::HoldingClaims {
47        actor: actor.clone(),
48        holding,
49        reason,
50    }]
51}
52
53#[cfg(test)]
54mod tests;