Resource Load#
This guide shows how to measure how busy each resource is over a date range. That's the data behind a resource histogram. It covers the profile query, how capacity follows resource calendars, which tasks fill each bucket, and how to get the load for dates you haven't committed yet.
How a load profile works#
project.loadProfile(options) reads the solved baseline and cuts the date range into buckets, either hours or days. For every resource it reports each bucket's load and capacity. The call is synchronous and read-only. It never schedules and never changes the project.
The parts fit together like this:
- The options object says what to measure: the range
from..to, whichresources, the bucket size (basis) and whether to listtasks. Onlyfromandtoare required. Leave the rest out and you get every resource, the default basis and no task lists. An optionalbaselineIdpins the call to a specific baseline. - The result is a
LoadProfile: thebasisit used and one entry per resource inresources. Each entry has theresourceid, itscapacityModeand a list ofbuckets, each{ start, load, capacity, tasks? }.
A few rules decide what ends up in the buckets:
- Range. A bucket is included when its start lies in
[from, to). A day bucket starts at midnight, so afromof Monday 12:00 skips Monday and starts on Tuesday.tois exclusive. - Dense output. Every bucket in the range appears, including empty ones with load 0. That's why the range is capped at 100 000 buckets per resource, about 11 years at hour basis.
- Basis. By default the basis follows
config.duration_unit:"day"for days,"hour"for hours and minutes. Setbasisto override it. - Load units. Load is in assignment
valueunits, where 1 is 100 %. Nothing is converted to hours. An assignment that works in a bucket books itsvaluethere. Two full-time tasks on the same day give a load of 2. - Same load as leveling. The profile uses the same sweep as the leveler and the
overallocationsinScheduleResult: only real work segments, each assignment on its own calendar, cut to its slice. A bucket overallocates whenload > capacity. - Order. Resources come back in input order, and task ids in a bucket too.
Bucket start dates follow the project's date setting, like every other result date.
Set dates: "date" to get result dates as local Date objects instead of "YYYY-MM-DD HH:MM" strings; schedule() always returns strings.
The profile always describes the accepted baseline, so the project has to be solved and clean. Failures are thrown synchronously:
| Failure | Error |
|---|---|
Project not solved yet, or edited with apply since the last solve |
ProjectError, code: "no-baseline" |
baselineId doesn't name the current baseline |
ProjectError, code: "stale-baseline" |
to <= from, or more than 100 000 buckets |
InputError, path: "to" |
Unknown id in resources |
InputError, path: "resources" |
tasks isn't "none", "overallocated" or "all" |
InputError |
See Handling errors for the error classes and the LoadProfile reference for every field.
Querying a profile#
Solve the project, then pass the range to loadProfile.
// 2026-08-10 is a Monday.
const = ;
const = new;
;
// Sunday 9 up to Friday 14; `to` is exclusive.
const = ;
;
The project uses day units, so the basis is "day" and you get five buckets, Sunday to Thursday. The loads are 0, 2 (a and b), 1.5 (a plus c at half time), 1 (d) and 0. Capacity is 1 in every bucket: dev has no capacity of its own, so it takes config.default_resource_capacity.
Pass the query fields (from, to, ...) directly in the options object, next to baselineId.
Narrowing the query#
Set resources to measure only some resources, and basis to change the bucket size.
const = new;
;
// Monday 07:00 to 10:00, hour buckets, `dev` only.
const = ;
Only dev comes back, with three hour buckets: 07:00 is before work starts (0), and 08:00 and 09:00 carry both tasks (2 each). An id in resources that isn't in the project throws an InputError with path: "resources".
Pinning the profile to a baseline#
A UI often fetches the profile some time after the solve. To make sure the profile matches the dates on screen, pass the baselineId from the solve result. If the project was solved again in the meantime, the call throws a ProjectError with code: "stale-baseline" instead of returning load for other dates.
const = new;
const = ;
Take the token from the latest solve result. Tokens are opaque, and the same data solved twice can get a different one, so don't store them or compare them across runs.
Capacity modes#
Capacity is the resource's capacity (default config.default_resource_capacity, 1). The capacityMode of each entry in profile.resources tells you how it's spread over the buckets:
"calendar": the resource has its own calendar,config.calendar_modeis"resource"(the default) andconfig.use_calendarsis on. Buckets with working time on that calendar get the full capacity, all others get 0."flat": every bucket gets the full capacity, weekends and nights included. This applies when the resource has no calendar, whencalendar_modeis"task"or"task-inherited", or whenuse_calendarsisfalse.
In calendar mode an assignment always works on the resource's calendar, or on the overlap of the task's and the resource's calendars. So a bucket with capacity 0 never gets load, and a bucket overallocates in the profile exactly when leveling sees it as overallocated.
// `days` runs Sunday..Saturday.
const = ;
const = new;
;
// One week in day buckets, Sunday 9 to Saturday 15.
const = ;
// Tuesday 06:00 to 18:00 in hour buckets, for the part-time resource.
const = ;
;
What the week shows:
officehas no calendar, so it's"flat"with capacity 1 on all seven days, Sunday and Saturday included.shopis"calendar". Its capacity is 0 on Sunday and Monday and 1 from Tuesday to Saturday. Taskaruns Tuesday and Wednesday, so those two buckets have load 1.- At hour basis,
part_timehas capacity only in the four hour buckets between 08:00 and 12:00. The other eight hours are 0.
Set config: { calendar_mode: "task" } (or use_calendars: false) in the data and all three resources become "flat", with capacity 1 in every bucket. See Working time and calendars for calendar setup and SchedulingConfig for the config fields.
Task lists per bucket#
A histogram usually needs to say who is overloading a resource. Set tasks in the options to fill each bucket's tasks:
tasks option |
Bucket tasks |
|---|---|
"none" (default) |
absent in every bucket |
"overallocated" |
task ids in buckets where load > capacity, [] elsewhere |
"all" |
ids of every task booked in the bucket |
const = new;
;
// Monday 10 to Thursday 13.
const = ;
With "overallocated", Monday lists a and b, and Tuesday lists a and c. Wednesday and Thursday aren't overloaded, so they get empty arrays. Switch to "all" and Wednesday lists d too, because "all" reports every booked task regardless of capacity.
If you need only the overloaded periods, not the full histogram, skip the profile. Every ScheduleResult of a project with assignments carries overallocations: per-resource windows with peak, overload and the contending tasks (see Overallocation). See Leveling resources for how to remove the overload.
Load of hypothetical dates#
loadProfile only reads the baseline. To see the load an edit would cause, set the profile option of preview to a query. The result's profile is computed on the previewed dates, and the project is left untouched.
const = new;
;
// What if `b` moves from `dev` to `qa`?
const = ;
const = ;
const = ;
// The baseline still has both tasks on `dev`.
const = ;
;
In the preview, dev drops to 1 on Monday and keeps 1.5 on Tuesday, and qa gets 1 on Monday. The baseline profile still shows 2 and 1.5 for dev. Without the profile option, the preview result has no profile field.
The profile option takes the query fields only (from, to, resources, basis, tasks). A baselineId inside it is an unknown field and throws an InputError. To guard a preview against a stale baseline, set baselineId on the preview options instead. An invalid query fails the whole preview with an InputError.
To profile a multi-step edit or a leveling run, clone the project with clone(), edit or level the clone, and call loadProfile on it. The original stays as it was. See Previewing changes for both approaches.
clone() copies the whole project at once, so its cost grows with the project's size.