SuggestOptions#

Options passed to Project::suggest_assignments. SuggestOptions::default() targets empty slots only, fixes no assignment, adds no new-resource penalty, scores the scheduled stage, allows one rebalance pass, keeps stored leveling delays and skips the baseline check. Override single fields with struct update syntax.

Declaration#

#[derive(Clone, Debug)]
pub struct SuggestOptions {
    pub baseline_id: Option<String>,
    pub targets: Vec<AssignTarget>,
    pub fixed: Vec<Id>,
    pub new_resource_penalty: f64,
    pub level: Option<LevelingOptions>,
    pub passes: u32,
    pub clear_first: bool,
}

impl Default for SuggestOptions

Fields#

Field Type Default Description
baseline_id Option<String> None Expected baseline token. Some fails the call with ProjectError::StaleBaseline unless it names the current baseline. Take it from the latest ScheduleResult::baseline_id.
targets Vec<AssignTarget> vec![AssignTarget::Empty] Slot categories to act on, in any combination. An empty Vec changes no assignment and only reports SuggestResult::invalid. A category listed twice fails with Error::Input at path targets[i].
fixed Vec<Id> empty Assignment ids that are never changed. A slot bound to one of them is never visited; its resource still counts as in use, and the slot is still reported in invalid when broken. An unknown, inactive or repeated id fails with Error::Input at path fixed[i]. An id bound to no slot is accepted.
new_resource_penalty f64 0.0 Cost added per counted resource in use that the reference state doesn't use. 0.0 scores pure balance; a large value favors the fewest people. Included in SuggestProposal::cost and StepCost. A negative or non-finite value fails with Error::Input.
level Option<LevelingOptions> None Some levels every candidate with these options and scores the leveled stage; it also fills SuggestProposal::leveled. None scores the scheduled stage. Options that are invalid for the accepted state fail with Error::Input.
passes u32 1 Maximum number of rebalance passes over AssignTarget::Valid slots. Has no effect unless targets contains Valid. 0 fails with Error::Input.
clear_first bool false true builds and measures the proposal on a copy of the accepted state with leveling_delay and pauses cleared on in-scope tasks. See clear_first.

clear_first#

The scope is level.scope.tasks when level sets it, otherwise every task, also when level is None.

  • SuggestResult::changes gets one task update {id, leveling_delay: 0, pauses: []} per cleared task, ahead of the assignment edits. The clears are returned whenever something was cleared, also with targets: vec![] or no slots.
  • SuggestResult::baseline and SuggestResult::proposed describe the cleared state.
  • It's independent of LevelingOptions::clear_first, so it also applies to scheduled-stage scoring.
  • It adds no errors: validation, fixed and slot states are checked against the accepted state.
  • When nothing in scope has a delay or pause, the output is identical to clear_first: false.

For how targets, passes and the penalty drive the search, see Suggesting assignments.

Example#

use plen_core::{
    AssignTarget, DateTime, Project, ProjectData, ResourceData, Slot, SolveOptions,
    SuggestOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let monday = DateTime::from_ymd_hm(2026, 8, 10, 8, 0).ok_or("invalid date")?;
    let mut tasks = Vec::new();
    for id in ["A", "B"] {
        let mut task = TaskData::new(id);
        task.start_date = Some(monday);
        task.duration = Some(2.0);
        tasks.push(task);
    }
    let resource = |id: &str| ResourceData { id: id.into(), capacity: None, calendar_id: None };

    let mut project = Project::default();
    let solved = project.solve(
        Some(ProjectData {
            tasks,
            resources: vec![resource("dev1"), resource("dev2")],
            ..Default::default()
        }),
        &SolveOptions::default(),
    )?;

    let slots = [
        Slot::new("A#1", "A", vec!["dev1".into(), "dev2".into()]),
        Slot::new("B#1", "B", vec!["dev1".into(), "dev2".into()]),
    ];
    let options = SuggestOptions {
        baseline_id: solved.baseline_id.clone(),
        targets: vec![AssignTarget::Empty, AssignTarget::Broken, AssignTarget::Valid],
        new_resource_penalty: 0.5,
        passes: 2,
        ..SuggestOptions::default()
    };
    let result = project.suggest_assignments(&slots, &options)?;
    println!("unfilled {:?}, cost {}", result.unfilled, result.proposed.cost);

    project.apply(&result.changes)?;
    Ok(())
}