Analytics#

Critical path and slack computed over a schedule's dates. It's the analytics field of ScheduleResult. That field is Some only when the call's AnalyticsOptions set critical_path, slack, or both. With both false, it's None.

Declaration#

#[derive(Clone, Debug, PartialEq)]
pub struct Analytics {
    pub status: AnalyticsStatus,
    pub critical_path: Option<Critical>,
    pub slack: Option<Vec<TaskSlack>>,
}

With the serde feature, Analytics implements Serialize and Deserialize. Fields use camelCase (criticalPath), and None fields are left out. See Serialize results to JSON.

Fields#

Field Type Description
status AnalyticsStatus Whether the analytics could be computed.
critical_path Option<Critical> Critical tasks and links. Some when AnalyticsOptions::critical_path is true and status is Available.
slack Option<Vec<TaskSlack>> Total and free slack per task. Some when AnalyticsOptions::slack is true and status is Available.

With status set to AnalyticsStatus::Invalid, both critical_path and slack are None, whatever the options asked for. Check status before reading the other fields.

Example#

Requests the critical path and slack for three tasks and prints both.

use plen_core::{
    schedule, AnalyticsOptions, AnalyticsStatus, DateTime, LinkData, LinkType, ProjectData,
    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 mut docs = TaskData::new("docs");
    docs.start_date = Some(monday);
    docs.duration = Some(1.0);

    let data = ProjectData {
        tasks: vec![design, build, docs],
        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 {
        analytics: AnalyticsOptions {
            critical_path: true,
            slack: true,
        },
        ..Default::default()
    };

    let result = schedule(data, &options)?;
    let analytics = result.analytics.ok_or("analytics not requested")?;

    if analytics.status == AnalyticsStatus::Invalid {
        println!("the dependency graph has a cycle");
        return Ok(());
    }
    if let Some(critical) = &analytics.critical_path {
        println!("critical tasks: {:?}", critical.tasks);
    }
    for s in analytics.slack.iter().flatten() {
        println!("{}: total {}, free {}", s.task, s.total, s.free);
    }
    Ok(())
}