# People & terms

Roster workers are the same workers used by task routing, with additional planning facts. Keep identity and qualifications on the worker; keep contract, pay, and site-planning facts in roster terms.

## Qualifications

Shift eligibility is checked against a worker’s date-valid tags and qualifications. Use hard requirements for licences, clearances, languages, or role qualifications that must never be waived by scoring.

Requirements can apply to:

- every person on a post or shift (`requiredTags`);
- the composition of the whole shift (`tagRequirements`);
- a maximum number in one category (`tagMaximums`).

Candidate explanations return failed requirements explicitly, so “not eligible” never has to be a black box.

## Working terms

```
tsawait fivexer.roster.setTerms('worker_anna', {
  weeklyMinutes: 2_400,
  maxDaysInPeriod: 5,
  rateCents: 2_250,
  overtimeConsent: true,
  homeSiteId: 'site_north',
  siteIds: ['site_north', 'site_south'],
});
```

Important semantics:

- `weeklyMinutes: null` means the contractual week is not stated.
- `maxDaysInPeriod: null` means no personal period cap is stated.
- `rateCents: null` inherits the workspace default rate when one exists.
- `overtimeConsent` is tri-state: `true`, `false`, or `null` for never recorded. When consent is required, `null` is treated like no consent.
- `homeSiteId` is a preference, not permission.
- `siteIds: []` means the person may work at every site. A non-empty list is a hard allow-list.

## Pay configuration

Pay settings price a plan; they do not invent legal rules.

```
tsawait fivexer.roster.setPay({
  defaultRateCents: 2_000,
  bands: [
    { id: 'night', label: 'Night', startTime: '22:00', endTime: '06:00', multiplier: 1.25 },
  ],
  sundayMultiplier: 1.5,
  holidayMultiplier: 2,
  holidays: ['2026-12-25'],
  overtimeMultiplier: 1.5,
  stacking: 'max',
});
```

`stacking: 'max'` uses the largest applicable premium for a minute; `add` combines premiums. Personal rates override the workspace default. If no rate resolves for a worker, their planned time remains visible but no invented cost is returned.

Use `roster.plannedHours({ from, to })` to read shift counts, ordinary time, premium-band minutes, overtime, and cost by worker and day. The response says whether it came from a published or draft plan.

## Time off

Only approved leave constrains the solver. Pending requests appear in publishing preflight because publishing over one may be a legitimate manager decision, but it should never happen invisibly.

```
tsconst request = await fivexer.timeOff.create({
  workerId: 'worker_anna',
  startDate: '2026-09-14',
  endDate: '2026-09-18',
  kind: 'vacation',
});

const impact = await fivexer.timeOff.impact(request.id);
await fivexer.timeOff.approve(request.id);
```

Check impact before deciding: it identifies affected rosters and conflicting shifts. For sickness or a no-show after publication, use the [cover workflow](/docs/rostering/time-off-cover/) instead of rewriting historical leave.
