Note#

A record the engine left out of the plan, or a change it made to the input data. Notes arrive in ScheduleResult::notes. They're information, not errors: the call still succeeds.

Declaration#

#[derive(Clone, Debug, PartialEq)]
pub enum Note {
    DroppedLink { link: Id, reason: String },
    DroppedTask { task: Id, reason: String },
    DroppedAssignment { assignment: Id, reason: String },
    Repaired { path: String, message: String },
}

Variants#

Variant When it occurs
DroppedTask { task, reason } The task has unscheduled: true in TaskData. reason is "unscheduled".
DroppedLink { link, reason } The link can't take part in the plan: its source or target task is unknown or unscheduled, it connects a summary with its own descendant while descendant_links is false in SchedulingConfig, or one endpoint is a manually scheduled summary (auto_scheduling: false) while summaries are expanded to their leaves (move_projects: false or gap_behavior: Compress).
DroppedAssignment { assignment, reason } The assignment's task is unscheduled.
Repaired { path, message } The project uses Validation::Repair and the engine changed the input. path names the changed field, such as tasks[2].constraint_type or links[0].id. message describes the change.

task, link and assignment are the ids from the input data (Id). reason and message are human-readable text, for example source task "qa" is unscheduled. Match on the variant, not on the text.

Serialization#

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

{ "kind": "dropped-link", "link": "l1", "reason": "target task \"qa\" is unscheduled" }
{ "kind": "dropped-task", "task": "qa", "reason": "unscheduled" }
{ "kind": "repaired", "path": "tasks[1].constraint_type", "message": "dated constraint without date cleared" }

See Serialize results to JSON.

Example#

use plen_core::{
    DateTime, LinkData, LinkType, Note, Project, 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 build = TaskData::new("build");
    build.start_date = Some(monday);
    build.duration = Some(3.0);

    let mut qa = TaskData::new("qa");
    qa.duration = Some(2.0);
    qa.unscheduled = true;

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

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

    for note in &result.notes {
        match note {
            Note::DroppedTask { task, reason } => println!("task {task} skipped: {reason}"),
            Note::DroppedLink { link, reason } => println!("link {link} skipped: {reason}"),
            Note::DroppedAssignment { assignment, reason } => {
                println!("assignment {assignment} skipped: {reason}")
            }
            Note::Repaired { path, message } => println!("{path}: {message}"),
        }
    }
    Ok(())
}

For Repaired notes from messy input, see Accept messy input.