Project::roster#

Reads the accepted state of a Project without skills: scheduled leaf tasks, resources and active assignments, in model order. Use it to build your own Slot list for suggest_assignments.

Signature#

pub fn roster(&self) -> Result<Roster, Error>

Takes &self. Synchronous and CPU-only.

Returns#

A Roster.

Errors#

Error When
Error::Project(ProjectError::NoBaseline) The project is unsolved or has unsolved edits

Example#

Build one slot per scheduled task that has no assignment yet, open to every resource.

use plen_core::{
    AssignmentData, 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 task = |id: &str| TaskData {
        start_date: Some(monday),
        duration: Some(2.0),
        ..TaskData::new(id)
    };
    let data = ProjectData {
        tasks: vec![task("a"), task("b")],
        resources: ["dev1", "dev2"]
            .iter()
            .map(|id| ResourceData { id: (*id).into(), capacity: None, calendar_id: None })
            .collect(),
        assignments: vec![AssignmentData {
            id: Some("x".into()),
            task: "a".into(),
            resource: "dev1".into(),
            value: 1.0,
            offset: None,
            duration: None,
        }],
        ..Default::default()
    };

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

    let roster = project.roster()?;
    let slots: Vec<Slot> = roster
        .tasks
        .iter()
        .filter(|task| !roster.assignments.iter().any(|a| &a.task == *task))
        .map(|task| Slot::new(format!("slot-{task}"), task.clone(), roster.resources.clone()))
        .collect();

    let result = project.suggest_assignments(&slots, &SuggestOptions::default())?;
    println!("{} slot(s), {} step(s)", slots.len(), result.steps.len());
    Ok(())
}