Conflict#

A scheduling conflict found while placing tasks. You don't build it yourself. Read it from ScheduleResult::conflicts, which lists every conflict of the run. A conflict is data in the result, not an error: the run still succeeds and returns dates.

Declaration#

#[derive(Clone, Debug, PartialEq)]
pub enum Conflict {
    ConstraintViolation {
        task: Id,
        constraint: ConstraintType,
        violation: Violation,
        required: DateTime,
        actual: DateTime,
    },
    CalendarNonWorking {
        task: Id,
        proposed: DateTime,
        snapped: DateTime,
    },
}

With the serde feature, it also implements Serialize and Deserialize; see Serialization.

Variants#

Variant When it occurs
ConstraintViolation A task's constraint placed it at a date its driving dependency link doesn't allow. The constraint wins, and the task keeps the constraint's date. Reported only when SchedulingConfig::apply_constraints is true.
CalendarNonWorking A summary moved as one block and a descendant task landed on non-working time of its calendar. Reported only when SchedulingConfig::strict_calendar is true.

ConstraintViolation#

Field Type Description
task Id The task or summary whose constraint won.
constraint ConstraintType The constraint that placed the task: the task's own, or a dated constraint inherited from a summary.
violation Violation Which bound the link contradicts. EarliestStart and LatestStart compare start dates, EarliestEnd and LatestEnd compare end dates.
required DateTime The date the constraint gave the task. It's the date the task got in the result.
actual DateTime The date the driving link would have given the task.

CalendarNonWorking#

Field Type Description
task Id The descendant task that landed on non-working time.
proposed DateTime The task's new start (its new end in a backward-scheduled project), on non-working time.
snapped DateTime The date the task keeps. Currently it's always equal to proposed: the task isn't moved to working time.

Serialization#

With the serde feature, a Conflict serializes as an object tagged by kind. The variant name is written in kebab-case, field names in camelCase:

{
  "kind": "constraint-violation",
  "task": "build",
  "constraint": "snlt",
  "violation": "latest-start",
  "required": "2026-01-06 08:00",
  "actual": "2026-01-07 08:00"
}
{ "kind": "calendar-non-working", "task": "b", "proposed": "2026-01-10 08:00", "snapped": "2026-01-10 08:00" }

Example#

build must start no later than Tuesday, but its predecessor design ends on Wednesday. The constraint wins, and the result reports the conflict.

use plen_core::{
    schedule, Conflict, ConstraintType, DateTime, LinkData, LinkType, ProjectData,
    SchedulingConfig, 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 tuesday = DateTime::from_ymd_hm(2026, 1, 6, 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(1.0);
    build.constraint_type = Some(ConstraintType::Snlt);
    build.constraint_date = Some(tuesday);

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

    let result = schedule(data, &SolveOptions::default())?;

    for conflict in &result.conflicts {
        match conflict {
            Conflict::ConstraintViolation { task, constraint, violation, required, actual } => {
                println!(
                    "{task}: {} {violation:?}, constraint gives {required}, link wants {actual}",
                    constraint.as_str()
                );
                // build: snlt LatestStart, constraint gives 2026-01-06 08:00, link wants 2026-01-07 08:00
            }
            Conflict::CalendarNonWorking { task, proposed, .. } => {
                println!("{task}: on non-working time at {proposed}");
            }
        }
    }
    Ok(())
}