Project#slots#

Turns a pool of workers with skills and a list of task requirements into the Slots that suggestAssignments takes. Existing active assignments are bound to the slots, so repeated runs stay stable after a commit. Needs a solved Project with no unsolved edits.

Note

slots throws InputError / ProjectError like the other Project methods.

Signature#

slots(pool: Pool, requirements: Requirement[]): Slot[]

Synchronous. Reads the project's accepted tasks, resources and assignments. Never modifies the project.

Parameters#

Name Type Description
pool Pool Workers that may be assigned. Pool order sets the order of each slot's allowed
requirements Requirement[] What each task needs. An empty array returns []

Unknown fields in any of these objects throw InputError "parse". A null field takes its default.

Pool#

The workers available for assignment.

Field Type Default Description
workers Worker[] required Workers in preference order. Earlier workers win ties

Worker#

One resource and its skills.

Field Type Default Description
resource Id required A resource of the project. Each resource can be listed once
skills Skill[] [] The worker's skills. Each skill id can be listed once

Skill#

One skill a worker has, with its grade.

Field Type Default Description
id Id required Skill id, matched against SkillMatch.skill
value number 1 Grade: 0/1 for a yes/no skill, an integer grade otherwise. Must be finite

Requirement#

What one task needs: a set of skill matches and the number of people.

Field Type Default Description
id Id required Requirement id. Its slots get the ids "<id>#1" .. "<id>#<count>". Unique across requirements
task Id required A scheduled leaf task
matches SkillMatch[] required Every match must hold for a worker to be allowed. [] allows every worker
count number 1 Number of slots, an integer >= 1
units number 1 value of the assignment that fills each slot. Finite and > 0
assignments Id[] | null null Explicit binding: the assignments to bind to this requirement's slots, in order. Replaces automatic binding (see Behavior). [] leaves every slot empty

A requirement id must stay on one task. Its slot ids become assignment ids on that task after a commit. Reusing the id for another task then makes the next call throw at requirements[i].id.

SkillMatch#

One condition on a worker's skill: worker value <op> value, inclusive.

Field Type Default Description
skill Id required Skill id. A worker that doesn't list it has value 0
op "at_least" | "no_more" | "equal" "at_least" "at_least": >=; "no_more": <=; "equal": ==
value number 1 Value to compare with. Must be finite

With the defaults, { skill: "backend" } means "has the skill".

Behavior#

The task's existing active assignments are bound per task, across all requirements on that task, in three steps:

  1. Slot id. An assignment whose id is one of the requirement's slot ids binds to that slot, even when its resource isn't in allowed. That slot is then broken, which suggestAssignments can repair. This step runs for every requirement of the task before any explicit list.
  2. Explicit list. A requirement with assignments set uses that list instead of automatic binding. A listed slot-id assignment keeps its slot, the other entries take the remaining slots in order, and leftover slots stay empty.
  3. Matching. A deterministic maximum matching binds the remaining assignments to the empty slots of requirements without a list. An assignment can take a slot only when its resource is in the slot's allowed. Input order decides between equal matchings, and an earlier requirement can't take the only worker a later one fits.

Assignments left unbound stay on the task without a slot.

Returns#

A Slot[]: count slots per requirement, in requirement order, then instance order.

Field Value
id "<requirement id>#<n>", n = 1..count (for example be#1, be#2)
task The requirement's task
allowed Pool workers for which every match of the requirement holds, in pool order
value The requirement's units
assignment The id of the active assignment bound to the slot. Absent when the slot is empty

The array can be passed to suggestAssignments as is. An assignment created from a slot has the slot's id, so the next call binds it back to the same slot.

Errors#

The first failing check wins: the project state, then the pool, then each requirement in input order.

Error When
ProjectError "no-baseline" The project is unsolved or has unsolved edits (after apply)
InputError "validation" The pool or a requirement is invalid. path names the item (table below)
InputError "validation", path: null A field has the wrong type or value, such as a non-integer count or an unknown op
InputError "parse" An unknown field in the pool, a worker, a skill, a requirement or a match
Error("Project has been disposed") The project was disposed

InputError paths:

Path When
pool.workers[i].resource Unknown resource, or the same resource listed as two workers
pool.workers[i].skills[j].id The same skill listed twice on one worker
pool.workers[i].skills[j].value Not finite
requirements[i].id Duplicate requirement id, or one of its slot ids is an active assignment on another task
requirements[i].task Unknown or unscheduled task
requirements[i].count 0
requirements[i].units Not finite or <= 0
requirements[i].matches[j].value Not finite
requirements[i].assignments More entries than count, or an assignment with one of this requirement's slot ids left out of the list
requirements[i].assignments[k] Unknown or inactive assignment, an assignment on another task, or one bound twice (the message names the other requirement when it already holds it)

The call takes no baseline token, so it never throws "stale-baseline".

Example#

Build the slot for a task that needs a backend grade of at least 2.

import { Project } from "plen-js";

const project = new Project();
project.solve({
  tasks: [{ id: "B", start_date: "2026-08-10 08:00", duration: 2 }],
  resources: [{ id: "ann" }, { id: "bob" }],
});

const slots = project.slots(
  {
    workers: [
      { resource: "ann", skills: [{ id: "backend", value: 1 }] },
      { resource: "bob", skills: [{ id: "backend", value: 2 }] },
    ],
  },
  [{ id: "be", task: "B", matches: [{ skill: "backend", value: 2 }] }],
);
console.log(slots);
// [ { allowed: [ 'bob' ], id: 'be#1', task: 'B', value: 1 } ]