Skip to content

evaluateDutyLog

evaluateDutyLog(log: DutyEntry[], at: string, rules: DutyRule[], options: { timeZone: string; dayStartTime?: string; disambiguation?: "compatible" | "earlier" | "later" | "reject" | undefined; }): DutyReport | null

Evaluate every hours-of-work rule over a duty log at one instant, and report each one whether or not it binds.

  • GMT implements no regulation: each rule is a shape, and every number in it is the caller’s. A regime is the array of rules the caller assembles and labels. GMT reports totals, what is left and what failed; it does not decide dispatch, fitness or eligibility.
  • The log is read as it stands at at: later entries are ignored and one straddling at is cut there. Time no entry covers is in no status, so it is neither work nor rest. That includes the time before the log’s first entry: give a log that reaches back as far as the longest rule looks, or a rest rule fails for want of logged rest. A planned roster is the same call with the plan appended and at set to its end.
  • This is the strict reading, for a check before dispatch. dutyViolations reads a record the other way: it does not judge a rest rule on a window that starts before the log’s first entry, since the log cannot say what happened then. Both read the log’s first entry as the start of a stretch without rest for maxGap, as the end of a rest would be.
  • Every outcome carries used, limit, remaining, window, dueBy and resetAt, so the second-tightest limit is visible before it binds. Failed rules are listed again under violations or, with severity: "advisory", under advisories only.
  • A duration in a Period is a sliding window ending at at; operatorDays are the days of dutyDayFor; calendar days, ISO weeks, months and years are local periods in timeZone, 23 or 25 hours long across a one-hour clock change. Each outcome names its periodBasis.
  • The local periods are the ones holding the last instant before at, so at a boundary the period just ended is judged: a roster ending at midnight is checked through its last day.
  • A window that runs past at (the rest of today, this week) has not failed a minimum while the minimum could still be met with rest from at to its end; a cap fails as soon as it is passed.
  • Endpoints are UTC instants ending in “Z”; durations are ISO 8601 with hours as the largest unit ("PT49H30M").
  • Returns null when the log fails isValidDutyLog, when at is not an ISO 8601 instant string, when a rule is malformed (an unknown kind, a status list that is empty or holds a non-string, a duration that is not positive exact time, a malformed period, a duplicate label, a count of periods past 10,000 alone or times adjacent, lookback or periods, a containing.count past 10,000), when options.timeZone is invalid, when a rule counts operatorDays without options.dayStartTime, when a window, or a requirement orFactor scales, falls outside Temporal’s range, when a count of free periods passes Number.MAX_SAFE_INTEGER, and when one run of rest in a freePeriod window holds more than 20,000 of its containing windows (about 54 years of a daily window).
ParameterTypeDescription
logDutyEntry[]the duty log, ordered and non-overlapping
atstringISO 8601 instant string to evaluate at
rulesDutyRule[]the rules, every number the caller’s

options

the reference zone and the operator day

OptionTypeDefaultDescription
timeZonestringRequiredThe reference time zone every local period is read in, an IANA name or UTC offset: a time zone identifier (+05:30, +0530, -08) or a stored offset (±HH:MM[:SS], what getTimeZoneOffset returns). A log kept in another clock is converted to instants first.
dayStartTime?stringNone. A rule that counts operator days makes the call invalid.The local wall time each operator day starts at, as an ISO PlainTime string ("04:00"). A rule that counts operatorDays needs it.
disambiguation?"compatible" | "earlier" | "later" | "reject" (Disambiguation)"compatible", Temporal’s default.How a day start, or a freePeriod window edge, that the clock repeats or skips becomes an instant, as resolveLocal resolves it. "compatible" reads a skipped time after the gap and a repeated one at its first pass, "earlier" and "later" take that side in both cases, and "reject" returns the sentinel when an operator day a rule counts starts or ends at such a time, or a local window with such an edge reaches a logged free period.

the report, or null on invalid input

Every rule of a regime evaluated at one instant.

import type { DutyReport } from "@northguild/gmt";
MemberTypeDescription
atstringThe instant evaluated, as a UTC instant.
outcomesRuleOutcome[] (RuleOutcome)One outcome per rule, in the order the rules were given.
violationsRuleOutcome[] (RuleOutcome)The outcomes of failed rules whose severity is "violation".
advisoriesRuleOutcome[] (RuleOutcome)The outcomes of failed rules whose severity is "advisory".

One rule evaluated at one instant, reported whether or not it binds. Durations are ISO 8601 with hours as the largest unit; counts are numbers.

import type { RuleOutcome } from "@northguild/gmt";
MemberTypeDescription
labelstringThe rule’s label.
kind"cumulativeCap" | "elapsedWindow" | "requiredInterruption" | "rollingCap" | "fixedPeriodCap" | "restRequirement" | "freePeriod" | "restBeforeDuty" | "quota" | "dueBy" | "pattern" | "occurrenceCap" | "usageLookback" (DutyRule)The rule’s kind.
severity"violation" | "advisory" (DutySeverity)The rule’s severity, "violation" when the rule gave none.
satisfiedbooleanWhether the rule holds at the instant evaluated.
usedstring | number | nullHow much the rule has used, or found: a duration or a count; null when nothing is measured.
limitstring | number | nullThe figure used is held to: a cap, a minimum or a count.
remainingstring | number | nullWhat is left before a cap binds, or still needed to meet a minimum; never negative.
windowInterval | null (Interval)The span the rule measured, as UTC instants. A window of local periods runs to the end of the current period, past the instant evaluated. Null when the rule has not started: an elapsedWindow with no work since its reset.
periodBasisPeriodBasis | null (PeriodBasis)How window was counted; null when the rule has no window of its own.
dueBystring | nullThe instant something is due by: a window’s close, a rest’s deadline; null when none.
resetAtstring | nullWhere the last reset ended, as a UTC instant; null when the rule has none or none occurred.

Which arithmetic a window used: exact elapsed time, operator days, or local calendar periods.

import type { PeriodBasis } from "@northguild/gmt";
"calendar" | "elapsed" | "operatorDay"
import { evaluateDutyLog } from "@northguild/gmt/duty/calculate";
evaluateDutyLog([{ status: "off", start: "2024-01-01T00:00:00Z", end: "2024-01-01T10:00:00Z" }, { status: "driving", start: "2024-01-01T10:00:00Z", end: "2024-01-01T21:00:00Z" }], "2024-01-01T21:00:00Z", [{ label: "11 driving", kind: "cumulativeCap", statuses: ["driving"], cap: "PT11H", since: { statuses: ["off"], consecutive: "PT10H" } }], { timeZone: "UTC" }) // { at: "2024-01-01T21:00:00Z", outcomes: [{ label: "11 driving", kind: "cumulativeCap", severity: "violation", satisfied: true, used: "PT11H", limit: "PT11H", remaining: "PT0S", window: { start: "2024-01-01T10:00:00Z", end: "2024-01-01T21:00:00Z" }, periodBasis: null, dueBy: null, resetAt: "2024-01-01T10:00:00Z" }], violations: [], advisories: [] }
log
rules
output 

duty/calculate/evaluateDutyLog.ts