[Canonical page](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/suggest-proposal/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# SuggestProposal

The score of the final working state of a [`Project::suggest_assignments`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#suggest_assignments>) call: its cost, the number of resources in use, and its metrics against the reference state. You read it from [`SuggestResult::proposed`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/suggest-result.md>). It isn't built by hand.

Compare it with [`SuggestResult::baseline`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/assign-total.md>), which holds the same cost and resource count for the reference state.

## Declaration

```rust
#[derive(Clone, Debug, PartialEq)]
pub struct SuggestProposal {
    pub cost: f64,
    pub resources: u32,
    pub scheduled: Metrics,
    pub leveled: Option<LeveledMetrics>,
}
```

With the `serde` feature, `SuggestProposal` implements `Serialize` only, with camelCase field names. `leveled` is left out when it's `None`.

## Fields

| Field | Type | Description |
|---|---|---|
| `cost` | `f64` | Cost of the final working state on the scoring stage, new-resource penalty included. Lower is better. |
| `resources` | `u32` | Resources of the counted pool that have at least one active assignment in the final working state. Counted the same way as [`AssignTotal::resources`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/assign-total.md>). |
| `scheduled` | [`Metrics`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/metrics.md>) | Metrics of the scheduled stage, with deltas against the reference state. |
| `leveled` | `Option<`[`LeveledMetrics`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/leveled-metrics.md>)`>` | Metrics of the leveled stage plus the leveling summary. `Some` only when [`SuggestOptions::level`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/suggest-options.md>) is set, and `None` when the scheduled stage is cyclic. |

`scheduled` and `leveled` come from the last accepted step; no extra run is made for them. When no step was taken, they describe the reference state itself: `scheduled` deltas are zero, while `leveled` deltas can be non-zero.

For how the state cost and the new-resource penalty are computed, and when a commit can differ from `proposed`, see [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/suggesting-assignments.md>).

## Example

`dev1` already works on task `A`. Slot `B#1` is filled with leveling on, and the proposal is printed next to the reference state.

```rust
use plen_core::{
    AssignmentData, DateTime, LevelingOptions, 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 mut tasks = Vec::new();
    for id in ["A", "B"] {
        let mut task = TaskData::new(id);
        task.start_date = Some(monday);
        task.duration = Some(2.0);
        tasks.push(task);
    }
    let resource = |id: &str| ResourceData { id: id.into(), capacity: None, calendar_id: None };

    let mut project = Project::default();
    project.solve(
        Some(ProjectData {
            tasks,
            resources: vec![resource("dev1"), resource("dev2")],
            assignments: vec![AssignmentData {
                id: None,
                task: "A".into(),
                resource: "dev1".into(),
                value: 1.0,
                offset: None,
                duration: None,
            }],
            ..Default::default()
        }),
        &SolveOptions::default(),
    )?;

    let slots = [Slot::new("B#1", "B", vec!["dev1".into(), "dev2".into()])];
    let options = SuggestOptions {
        new_resource_penalty: 0.5,
        level: Some(LevelingOptions::default()),
        ..SuggestOptions::default()
    };
    let result = project.suggest_assignments(&slots, &options)?;

    let proposed = &result.proposed;
    println!("cost {} -> {}", result.baseline.cost, proposed.cost);
    println!("people {} -> {}", result.baseline.resources, proposed.resources);
    println!("scheduled overload {}", proposed.scheduled.overload.total.value);
    if let Some(leveled) = &proposed.leveled {
        let finish_delta = leveled.metrics.finish.project.as_ref().map(|s| s.delta);
        println!("leveled finish delta {:?}, windows {}", finish_delta, leveled.windows);
    }
    Ok(())
}
```

## Related

- [`SuggestResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/suggest-result.md>) - carries this value in its `proposed` field
- [`AssignTotal`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/assign-total.md>) - the reference-state score to compare against
- [`SuggestOptions`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/suggest-options.md>) - `level` and `new_resource_penalty` shape this score
- [Suggesting assignments](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/suggesting-assignments.md#reading-the-proposal>) - shows reading the proposal's cost and metrics
