Reason#

What decided a task's date in the last planner run. A Reason arrives in Mutation::reason, which is filled only when the call ran with reasons: true in SolveOptions or PreviewOptions.

Declaration#

#[derive(Clone, Debug, PartialEq)]
pub enum Reason {
    Asap { link_id: Id, source_id: Id },
    Alap { link_id: Id, target_id: Id },
    SummaryTranslation { summary_id: Id },
    Constraint,
    Preferred,
}

Variants#

Variant Description
Asap { link_id, source_id } Forward scheduling. The incoming link link_id from the predecessor source_id sets the task's start.
Alap { link_id, target_id } Backward scheduling (schedule_from_end). The outgoing link link_id to the successor target_id sets the task's end.
SummaryTranslation { summary_id } The task moved together with its summary summary_id, which moves as a block.
Constraint A constraint set the date: it pulled the task away from the date its links or preferred date would give, or the task has a Mso / Mfo constraint.
Preferred No link or constraint moved the task. It sits at its own preferred date.

All ids are the ids from the input data (Id). When several links give the same date, Asap and Alap name the first matching one.

Serialization#

With the serde feature, Reason serializes as an object tagged by kind, with kebab-case variant names and camelCase field names:

{ "kind": "asap", "linkId": "l1", "sourceId": "design" }
{ "kind": "summary-translation", "summaryId": "phase-1" }
{ "kind": "preferred" }

See Serialize results to JSON.

Example#

use plen_core::{
    DateTime, LinkData, LinkType, Project, ProjectData, Reason, SolveOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let monday = DateTime::from_ymd_hm(2026, 1, 5, 8, 0).ok_or("invalid date")?;

    let mut design = TaskData::new("design");
    design.start_date = Some(monday);
    design.duration = Some(2.0);

    let mut build = TaskData::new("build");
    build.start_date = Some(monday);
    build.duration = Some(3.0);

    let data = ProjectData {
        tasks: vec![design, build],
        links: vec![LinkData {
            id: "l1".into(),
            source: "design".into(),
            target: "build".into(),
            link_type: LinkType::Fs,
            lag: 0.0,
            lag_unit: None,
        }],
        ..Default::default()
    };

    let options = SolveOptions {
        reasons: true,
        ..Default::default()
    };

    let mut project = Project::default();
    let result = project.solve(Some(data), &options)?;

    for update in &result.updates {
        match &update.reason {
            Some(Reason::Asap { link_id, source_id }) => {
                println!("{} follows {} via link {}", update.id, source_id, link_id)
            }
            Some(Reason::Alap { link_id, target_id }) => {
                println!("{} precedes {} via link {}", update.id, target_id, link_id)
            }
            Some(Reason::SummaryTranslation { summary_id }) => {
                println!("{} moved with summary {}", update.id, summary_id)
            }
            Some(Reason::Constraint) => println!("{} placed by its constraint", update.id),
            Some(Reason::Preferred) => println!("{} kept its preferred date", update.id),
            None => {}
        }
    }
    Ok(())
}