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


# Requirement

The staffing need of one task: `count` slots, each filled by a worker who satisfies every [`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/skill-match.md>) in `matches`. Requirements are the third argument of [`slots`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots.md>). Create one with [`Requirement::new`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/requirement.md#new>) and change `count`, `units` or `assignments` on the returned value, or write a struct literal.

Requirements aren't stored in the project. Pass them again on every `slots` call.

## Declaration

```rust
#[derive(Clone, Debug, PartialEq)]
pub struct Requirement {
    pub id: Id,
    pub task: Id,
    pub matches: Vec<SkillMatch>,
    pub count: u32,
    pub units: f64,
    pub assignments: Option<Vec<Id>>,
}
```

`Requirement` has no `Default` and doesn't implement `Serialize` / `Deserialize`.

## Constructors

### new

```rust
pub fn new(id: impl Into<Id>, task: impl Into<Id>, matches: Vec<SkillMatch>) -> Requirement
```

Creates a requirement for one slot at units `1.0`, with automatic binding.

**Parameters**

| Name | Type | Description |
|---|---|---|
| `id` | `impl Into<`[`Id`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/id.md>)`>` | Requirement id. Slot ids are built from it. |
| `task` | `impl Into<Id>` | The task that needs the work. |
| `matches` | `Vec<`[`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/skill-match.md>)`>` | Skill conditions. All of them must hold. |

**Returns** a `Requirement` with `count: 1`, `units: 1.0` and `assignments: None`.

`new` doesn't validate anything. [`slots`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots.md>) checks the fields.

## Fields

| Name | Type | Default via `new` | Description |
|---|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/id.md>) | - | Unique among the requirements of one `slots` call. The slot ids are `"<id>#<n>"`, `n` = 1..=`count`. |
| `task` | `Id` | - | A scheduled leaf task of the project. |
| `matches` | `Vec<`[`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/skill-match.md>)`>` | - | Conditions a worker must meet, all of them. An empty list accepts every pool worker. |
| `count` | `u32` | `1` | Number of slots. At least `1`. |
| `units` | `f64` | `1.0` | The `value` of each slot: the units of a new assignment that fills it. Finite and `> 0`. |
| `assignments` | `Option<Vec<Id>>` | `None` | Which existing active assignments of `task` fill the slots. See below. |

### id stays on its task

[`slots`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots.md#returns>) binds an assignment back to its slot by the slot id. Keep a requirement id on the same task across runs: if one of its slot ids is an active assignment on another task, `slots` fails at `requirements[i].id`.

### assignments

| Value | Effect |
|---|---|
| `None` | Automatic binding: assignments with this requirement's slot ids, then matching of the task's other assignments by [`allowed`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/slot.md>). |
| `Some(list)` | Explicit binding. Only the listed assignments fill slots, at most `count` of them. Any assignment with one of this requirement's slot ids must be in the list. Slots left over stay empty. |
| `Some(vec![])` | Every slot stays empty. Fails if an assignment with one of the slot ids exists. |

The binding order across all requirements of a task is on [`slots`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots.md#binding>).

## Errors

[`slots`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots.md#errors>) reports an invalid requirement as [`MatchError::Invalid`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/match-error.md>) at one of these paths.

| Path | When it occurs |
|---|---|
| `requirements[i].id` | Another requirement in the call has the same id, or one of the slot ids is an active assignment on another task. |
| `requirements[i].task` | The task is unknown or unscheduled. |
| `requirements[i].count` | `count` is `0`. |
| `requirements[i].units` | `units` isn't finite, or is `<= 0`. |
| `requirements[i].assignments` | The list has more entries than `count`, or leaves out an assignment with one of this requirement's slot ids. |
| `requirements[i].assignments[k]` | The assignment is unknown or inactive, is on another task, or is bound twice. When another requirement already holds it, the message names that requirement, for example `already bound to requirement "fe"`. |

Ids compare by their [`Id`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/data/id.md>) key.

## Example

Two backend slots at half units on task `api`. `bob` already works on `api`, so his assignment fills the first slot.

```rust
use plen_assign::{slots, Pool, Requirement, Skill, SkillMatch, Worker};
use plen_core::{AssignmentData, DateTime, Project, ProjectData, ResourceData, SolveOptions, TaskData};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut task = TaskData::new("api");
    task.start_date = DateTime::from_ymd_hm(2026, 3, 2, 8, 0);
    task.duration = Some(4.0);
    let data = ProjectData {
        tasks: vec![task],
        resources: ["ann", "bob"]
            .map(|id| ResourceData { id: id.into(), capacity: None, calendar_id: None })
            .to_vec(),
        assignments: vec![AssignmentData {
            id: Some("a1".into()),
            task: "api".into(),
            resource: "bob".into(),
            value: 1.0,
            offset: None,
            duration: None,
        }],
        ..Default::default()
    };
    let mut project = Project::default();
    project.solve(Some(data), &SolveOptions::default())?;

    let backend = |resource: &str| Worker {
        resource: resource.into(),
        skills: vec![Skill { id: "backend".into(), value: 1.0 }],
    };
    let pool = Pool { workers: vec![backend("ann"), backend("bob")] };

    let mut requirement = Requirement::new("be", "api", vec![SkillMatch::at_least("backend", 1.0)]);
    requirement.count = 2;
    requirement.units = 0.5;

    for slot in slots(&project, &pool, &[requirement])? {
        let bound = slot.assignment.as_ref().map(|id| id.to_string());
        println!("{}: value {}, assignment {bound:?}", slot.id, slot.value);
    }
    Ok(())
}
```

Output:

```text
be#1: value 0.5, assignment Some("a1")
be#2: value 0.5, assignment None
```

## Related

- [`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/skill-match.md>) - one condition in `matches`
- [`Pool`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/pool.md>) - the workers a requirement's matches are checked against
- [`slots`](<https://docs.dhtmlx.com/planning-engine/rust/api/assign/slots.md>) - turns requirements into slots
- [`Slot`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/suggest/slot.md>) - what each of the `count` instances becomes
- [Skills and Requirements](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/skills-and-requirements.md>) - shows requirements, matches and binding of existing assignments
