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


# Requirement

What one task needs: `Count` slots on `Task`, each for a worker that satisfies every [`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/requirement.md#skillmatch>) in `Matches`. Pass a list of requirements to [`SkillLayer.Slots`](<https://docs.dhtmlx.com/planning-engine/net/api/skill-layer-slots.md>), which turns each one into [`Slot`](<https://docs.dhtmlx.com/planning-engine/net/api/core/suggest/slot.md>)s. Create one with the constructor and set the other properties in an object initializer.

## Declaration

```csharp
namespace Plen.Assign;

public sealed class Requirement : IDeepCloneable<Requirement>, IEquatable<Requirement>
```

## Constructors

### Requirement(Id, Id, List<SkillMatch>)

```csharp
public Requirement(Id id, Id task, List<SkillMatch> matches)
```

Creates a requirement for one slot at `Units = 1.0` with automatic binding (`Count = 1`, `Assignments = null`). It's the only constructor, so `new Requirement { … }` without arguments doesn't compile.

| Name | Type | Description |
|---|---|---|
| `id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | Requirement id. |
| `task` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | The task the slots belong to. |
| `matches` | `List<`[`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/requirement.md#skillmatch>)`>` | The skill matches every candidate must satisfy. The list is stored as passed, not copied. |

## Properties

| Property | Type | Default | Description |
|---|---|---|---|
| `Id` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | set by the constructor | Requirement id; unique within one `Slots` call. Slot ids are `"<Id>#<n>"`, n = 1..`Count`. |
| `Task` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | set by the constructor | A scheduled leaf task of the project. |
| `Matches` | `List<`[`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/requirement.md#skillmatch>)`>` | set by the constructor | Every match must hold for a worker to be a candidate. An empty list allows every pool worker. |
| `Count` | `uint` | `1` | Number of slots. At least `1`. |
| `Units` | `double` | `1.0` | Rate of a new assignment (1 = 100 %); becomes each slot's `Value`. Finite and `> 0`. |
| `Assignments` | `List<Id>?` | `null` | Explicit binding of existing assignments. `null` = automatic binding; `[]` = every slot empty. |

### Id

```csharp
public required Id Id { get; set; }
```

The requirement's slot ids become the ids of the assignments that fill them on `Task`. After those assignments are committed, the id stays tied to that task: reusing it for a requirement on another task throws.

### Assignments

```csharp
public List<Id>? Assignments { get; set; }
```

A non-null list replaces automatic binding for this requirement. An assignment whose id is one of this requirement's slot ids keeps its slot. The other listed assignments take the remaining slots in list order, and slots left over stay empty. The list can't hold more entries than `Count`. Each entry must be an active assignment on `Task`, and a slot-id assignment of this requirement must be listed. For how binding works across all requirements of a task, see [Binding existing assignments](<https://docs.dhtmlx.com/planning-engine/net/resources-and-assignment/skills-and-requirements.md#binding-existing-assignments>).

## Methods

### DeepClone

```csharp
public Requirement DeepClone()
```

Returns a copy that shares no list or [`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/requirement.md#skillmatch>) with this one.

### Equals

```csharp
public bool Equals(Requirement? other)
public override bool Equals(object? obj)
```

Value equality over all six properties: lists element by element, `double` by `==`. A `null` `Assignments` equals only another `null`, not `[]`. `GetHashCode` is consistent with it.

### ToString

```csharp
public override string ToString()
```

Returns a readable rendering of all properties, for logging.

## SkillMatch

One condition on a worker's skill: `worker value <Op> Value`. A skill the worker doesn't list counts as `0`, so `SkillMatch.NoMore("qa", 0)` holds for every worker without the `qa` skill.

```csharp
namespace Plen.Assign;

public sealed class SkillMatch : IDeepCloneable<SkillMatch>, IEquatable<SkillMatch>
{
    public required Id Skill { get; set; }
    public SkillOp Op { get; set; }
    public double Value { get; set; }

    public static SkillMatch AtLeast(Id skill, double value);
    public static SkillMatch NoMore(Id skill, double value);
    public static SkillMatch Equal(Id skill, double value);
}
```

| Member | Type | Default | Description |
|---|---|---|---|
| `Skill` | [`Id`](<https://docs.dhtmlx.com/planning-engine/net/api/core/data/id.md>) | required | Skill id, compared with [`Skill.Id`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/skill.md>). |
| `Op` | [`SkillOp`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/requirement.md#skillop>) | `SkillOp.AtLeast` | The comparison. |
| `Value` | `double` | `1.0` | The value to compare with. Must be finite. |
| `AtLeast(skill, value)` | `SkillMatch` | | A match with `Op = SkillOp.AtLeast`. |
| `NoMore(skill, value)` | `SkillMatch` | | A match with `Op = SkillOp.NoMore`. |
| `Equal(skill, value)` | `SkillMatch` | | A match with `Op = SkillOp.Equal`. |

The class has a parameterless constructor, so `new SkillMatch { Skill = "backend" }` gives `worker value >= 1`. `DeepClone`, `Equals` (all three properties, `double` by `==`), `GetHashCode` and `ToString` work as on `Requirement`.

## SkillOp

The comparison a [`SkillMatch`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/requirement.md#skillmatch>) applies. All three are inclusive.

```csharp
namespace Plen.Assign;

public enum SkillOp { AtLeast, NoMore, Equal }
```

| Value | Description |
|---|---|
| `AtLeast` | Worker value `>=` match value. The default. |
| `NoMore` | Worker value `<=` match value. |
| `Equal` | Worker value `==` match value. |

## Errors

Requirements are validated by [`SkillLayer.Slots`](<https://docs.dhtmlx.com/planning-engine/net/api/skill-layer-slots.md>). It throws [`InputException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md>) with `Kind = `[`InputErrorKind.Validation`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md#inputerrorkind>) and one of these paths for requirement `i`:

| Path | When |
|---|---|
| `requirements[i].id` | The id repeats an earlier requirement's id, or one of its 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` is `NaN`, infinite or `<= 0`. |
| `requirements[i].matches[j].value` | A match `Value` is `NaN` or infinite. |
| `requirements[i].assignments` | The list has more entries than `Count`, or leaves out an assignment that has one of this requirement's slot ids. |
| `requirements[i].assignments[k]` | The entry is unknown or inactive, is on another task, or is bound twice (also when another requirement already bound it). |

## Example

Two half-time slots for workers with backend grade 1 or higher:

```csharp
using Plen;
using Plen.Assign;

var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");

var project = new Project();
project.Solve(new ProjectData
{
    Tasks = [new TaskData("api") { StartDate = monday, Duration = 2 }],
    Resources = [new ResourceData { Id = "ann" }, new ResourceData { Id = "bob" }, new ResourceData { Id = "cat" }],
}, new SolveOptions());

var pool = new Pool
{
    Workers =
    [
        new Worker { Resource = "ann", Skills = [new Skill { Id = "backend", Value = 2 }] },
        new Worker { Resource = "bob", Skills = [new Skill { Id = "backend" }] },
        new Worker { Resource = "cat" },
    ],
};

var requirement = new Requirement("be", "api", [SkillMatch.AtLeast("backend", 1)])
{
    Count = 2,
    Units = 0.5,
};

foreach (var slot in SkillLayer.Slots(project, pool, [requirement]))
{
    Console.WriteLine($"{slot.Id}: allowed [{string.Join(", ", slot.Allowed)}], value {slot.Value}");
}
```

Output:

```text
be#1: allowed [ann, bob], value 0.5
be#2: allowed [ann, bob], value 0.5
```

## Related

- [`SkillLayer.Slots`](<https://docs.dhtmlx.com/planning-engine/net/api/skill-layer-slots.md>) - the call that turns requirements into slots
- [`Pool`](<https://docs.dhtmlx.com/planning-engine/net/api/assign/pool.md>) - the workers the matches are checked against
- [`Slot`](<https://docs.dhtmlx.com/planning-engine/net/api/core/suggest/slot.md>) - what each requirement produces
- [`Project.SuggestAssignments`](<https://docs.dhtmlx.com/planning-engine/net/api/project-suggest-assignments.md>) - fills the slots
- [Skills and requirements](<https://docs.dhtmlx.com/planning-engine/net/resources-and-assignment/skills-and-requirements.md>) - stating what each task needs
