Requirement#
What one task needs: Count slots on Task, each for a worker that satisfies every SkillMatch in Matches. Pass a list of requirements to SkillLayer.Slots, which turns each one into Slots. Create one with the constructor and set the other properties in an object initializer.
Declaration#
namespace Plen.Assign;
public sealed class Requirement : IDeepCloneable<Requirement>, IEquatable<Requirement>
Constructors#
Requirement(Id, Id, List<SkillMatch>)#
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 |
Requirement id. |
task |
Id |
The task the slots belong to. |
matches |
List<SkillMatch> |
The skill matches every candidate must satisfy. The list is stored as passed, not copied. |
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
Id |
Id |
set by the constructor | Requirement id; unique within one Slots call. Slot ids are "<Id>#<n>", n = 1..Count. |
Task |
Id |
set by the constructor | A scheduled leaf task of the project. |
Matches |
List<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#
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#
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.
Methods#
DeepClone#
public Requirement DeepClone()
Returns a copy that shares no list or SkillMatch with this one.
Equals#
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#
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.
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 |
required | Skill id, compared with Skill.Id. |
Op |
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 applies. All three are inclusive.
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. It throws InputException with Kind = InputErrorKind.Validation 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:
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:
be#1: allowed [ann, bob], value 0.5
be#2: allowed [ann, bob], value 0.5
Related#
SkillLayer.Slots- the call that turns requirements into slotsPool- the workers the matches are checked againstSlot- what each requirement producesProject.SuggestAssignments- fills the slots- Skills and requirements - stating what each task needs