Skip to main content

Capability

Resource-Loaded, Shift and Outage Schedules

Sub-day durations, hourly and shift calendars, levelled dates and outage windows are where most schedule tooling quietly gives a wrong answer. This page says exactly what ours models, what it refuses to model, and how the analysis is done correctly anyway.

Read this part first. The critical path method engine behind this practice is day granular. It does not model work inside a day. A sub-day duration or a sub-day lag is rounded to whole days, and that rounding raises an alert which is fatal in forensic strict mode. Nothing on this page claims otherwise. What follows is why this class of schedule is hard, what the tooling covers, and the method that produces a defensible answer despite the limit.

A refinery turnaround, a nuclear outage, a plant shutdown and a two-shift civil pour have one thing in common. The unit of work is not a working day. It is a shift, a window, or a clock that runs through the weekend. Feed that schedule to a tool that assumes eight hours and Monday to Friday and the arithmetic goes wrong in a way nobody notices, because the answer still looks like a date.

The failure is rarely dramatic. It is a 10-hour shift treated as an 8-hour day, so every duration reads 25 percent short. It is a 24-hour continuous calendar divided by 8, so a lag inflates by 200 percent. It is a four-hour lag rounded up to a full day, fifty times over, and a finish date that drifts weeks from what the planning tool says. On a claim, that drift is the first thing an opposing expert finds, and once found it contaminates everything downstream of it.

Our position is the unglamorous one. Name the limit, gate it, and work inside it. The work behind this page has been construction scheduling and forensic delay analysis, and about twenty years of it has been in nuclear and energy.

Where this class of schedule actually breaks

Sub-day durations

P6 stores durations and lags in hours, not days. A turnaround schedule routinely carries two-hour isolations, four-hour lifts and six-hour tie-ins. Converted to days on an eight-hour calendar those become 0.25, 0.5 and 0.75. Any engine that works in whole days has to do something with the fraction, and every choice is lossy. Rounding half up inflates the positive lags. Rounding half to even loses the half day in the other direction. Truncating destroys short leads entirely. The honest handling is not to pick a favourite rounding rule; it is to detect the fraction, say so, and refuse to produce a court-grade number until the analyst has decided what the fraction means.

Hourly and shift calendars

A calendar is not just which days are working. It carries an hours-per-day figure, a work week, intra-day time slots with clock start and finish times, and a list of exceptions. Outage calendars use all of it: seven days a week, twelve-hour shifts, a continuous 24-hour clock on some paths, statutory holidays worked, and normally-off days switched on for the duration of the window. Two of those are traps. The first is the hours-per-day divisor, because using 8 when the calendar says 10 or 24 silently rescales every duration in the file. The second is the exception encoding, because a P6 exception that carries work hours is a modified working day, not a day off, and a parser that treats every exception as a holiday turns a continuous calendar into hundreds of phantom non-working days.

A real defect, and the fix

That second trap was a live bug in our own parser, found in June 2026 while diagnosing why the engine would not reproduce the planning tool's finish date. A 7x24 calendar decoded to 299 holidays and a 7-day calendar to 183, which pushed a curing chain out by roughly five months. The classifier now scans each exception's own segment for a work-time block, so a day carrying hours is recorded as a working day and only an empty exception counts as a day off. A final guard subtracts the working exceptions from the holiday set. The 7x24 calendar went from 299 phantom holidays to 24 real ones.

The reason to publish that is simple. A tool that has never had a calendar bug has probably never been pointed at a continuous calendar.

Levelled dates

Levelling is not logic. It moves work because a crew or a crane is not available, not because a predecessor said so. The engine does not perform levelling, by design, and it does not attempt to reproduce a levelled date. That is a deliberate boundary, not an omission: a resource-constrained date is the output of a heuristic with a priority rule, and reproducing somebody else's heuristic is not evidence of anything. What we do instead is read the levelled dates as recorded facts, compute the logic-driven answer separately, and treat any gap between them as a finding to explain. On a claim that gap is often the most useful number in the file, because it separates delay caused by logic and events from delay caused by resource availability, and those two things sit in very different places in a contract.

Outage and turnaround work

Outage schedules compress a year of decisions into a fixed window with a hard end date. Four features make them different from a building job. The window boundary is contractual and the clock does not stop for weekends. Durations are short, so rounding error is proportionally large. Work is often performed out of sequence when a system comes free early, and that out-of-sequence progress is real, not a modelling error. And the drivers are frequently permits, isolations, holds and system availability rather than trades. Two consequences follow for the analysis. First, the analysis window has to be the outage window, not a calendar month, or every result is smeared across the boundary. Second, a zero-resource waiting activity does not hold a date. If a system is unavailable, the gate milestone is what carries the constraint, and the way to prove which one drives is a knock-out test on the candidate driver rather than an appeal to the bar that looks longest.

The register: what the tooling models, and what it does not

This is the whole list, not a selection. Where the answer is no, the reason is stated. Where an alert is named it is the literal alert context the engine emits, so an opposing expert can filter for it in the result object.

SCHEDULE-FEATURE REGISTER  ·  20 rows  ·  scroll within the frame; the header stays fixed

# Schedule feature Modelled What the tooling actually does, and the consequence
01 Whole-day activity durations on a named calendar Yes Durations arrive in hours and are divided by that calendar's day_hr_cnt from the CALENDAR table. Whole-day results pass through with no loss.
02 Hours per day other than 8 (10-hour shift, 12-hour shift, 24-hour continuous) Yes The divisor is read per calendar, so one engine day equals one calendar day of that length. Before this was wired, a hardcoded 8 inflated 10-hour shift lags by 25 percent and 24-hour continuous-operations lags by 200 percent.
03 Calendar present but day_hr_cnt missing Partial Falls back to 8 hours and raises lag-hours-per-day-fallback, once per unknown calendar rather than once per relationship. That alert is fatal in forensic strict mode, so an opinion cannot be built on the fallback without a written override.
04 Fractional (sub-day) activity duration No Rounded to whole days and reported as SUB_DAY_LAG_ROUNDED. Fatal in forensic strict mode. Sub-day precision is not available from this engine and the page does not pretend it is.
05 Fractional (sub-day) lag or lead No Same rounding and the same fatal alert. The loss is asymmetric: positive sub-day lags inflate by up to a full day, while leads between minus 0.5 and zero collapse to zero. Fifty successive four-hour lags can move a finish date by weeks.
06 Intra-day shift boundaries (clock start and finish times, multiple slots in one day) No Time slots are read only to classify a day as working or non-working, and to recover hours per day. The timeline inside the day is not modelled, so no answer is produced at shift-boundary resolution.
07 Which days of the week are working Yes Decoded from the DaysOfWeek block by balanced-paren walking, with fallbacks for older export encodings. A calendar string that decodes to zero working days is flagged as an incomplete parse rather than silently replaced by Monday to Friday.
08 Calendar exceptions that are genuine days off Yes Decoded to real dates from the P6 serial (epoch 1899-12-30) or from a literal date string, with a sanity range applied so a corrupt serial cannot invent a holiday outside 1970 to 2099.
09 Calendar exceptions that carry work hours (a normally-off day switched on for the outage) Yes Classified as modified working days, not holidays, by scanning each exception's own segment for a work-time block. Separator tolerant, because the export puts line markers between the serial and its hours. A final guard removes any working exception from the holiday set.
10 Continuous 7-day and 24-hour calendars (cure clocks, outage clocks, hold periods) Yes Supported, and the correct way to model any clock that runs through weekends. A cure or hold modelled on a five-day work calendar pauses over the weekend and overstates the elapsed period, which is a modelling error rather than a tool error.
11 Multiple calendars in one file, assigned per activity Yes Supported on the calendar-aware path. Float is reported in working days on the activity's own calendar, which is what the planning tool's float columns mean; the raw calendar-day figures remain available but are not comparable to those columns.
12 Monte Carlo inner loop No The lightweight simulation engine uses ordinal seven-day arithmetic and is not calendar aware. It refuses forensic strict mode outright and raises an alert telling the caller to switch to the calendar-aware path. It must never be the basis of an opinion on a shift-calendar schedule.
13 Resource levelling and resource-constrained scheduling No Out of scope by design. The engine computes logic-driven dates only. It does not run a levelling heuristic and does not attempt to reproduce one.
14 Levelled dates already in the file Partial Read as recorded facts and reported. The logic-driven answer is computed separately and the gap between the two is surfaced as a finding, not averaged away. That gap is what separates logic and event delay from resource-availability delay.
15 Resource-loaded quantities, cost and time-phased usage Yes Handled by the resource layer rather than the date math: per-resource usage at day, week or month granularity, peak and average crew, periods above the availability ceiling, and trade-stacking windows, with an optional baseline overlay.
16 Percent-complete consistency (activity, physical and duration percent against remaining duration) No The engine does not consume those fields, and the disclosure says so. The consistency check, where the remaining-over-original ratio implies one percentage and the physical field reports another, is performed one layer up in the claim workbench.
17 Retained logic and progress override scheduling modes Yes Both implemented. Retained logic is the default and is the mode validated against native planning-tool output. An unrecognised mode raises an alert and falls back to retained logic rather than guessing.
18 Actual start on an in-progress activity Yes Treated as an immutable historical fact that wins over both the data-date floor and predecessor-driven logic. The predecessor anomaly is still flagged separately so the out-of-sequence signal is not lost.
19 Shift definition tables in the export (SHIFT, SHIFTPER) No Preserved on round trip so a regenerated file is not degraded, but no analytical layer consumes them. Shift structure reaches the analysis through the calendar, not through these tables.
20 Out-of-sequence progress during an outage window Yes Detected and alerted, including a premature start against unstarted predecessors, with the driving predecessor surfaced for traceability. Fatal in forensic strict mode, which is correct: on a turnaround it is usually a real event that needs explaining, not noise to suppress.

End of register. 20 of 20 rows shown.

The alert, verbatim

When a fraction appears, this is the text the engine produces. It is quoted exactly, including the rounding-direction disclosure, because the wording is the disclosure.

context: SUB_DAY_LAG_ROUNDED   severity: ALERT

SUB_DAY_LAG_ROUNDED: lag/duration value 0.5 is fractional; engine
rounds to 1 day(s). V8 Math.round rounds half toward +Infinity;
sub-day lags inflate, sub-day leads truncate to zero — sub-day
precision is forensically unavailable in this engine. P6 typically
stores lags in hours; re-run with full-day lags or accept the
documented drift.
What that means in practice

In forensic strict mode this alert is fatal. The run throws a labelled exception rather than returning a date. The analyst has three lawful choices: rebase the schedule so durations and lags are whole multiples of the calendar's working day, record a written override with a reason that survives cross-examination, or state the result as a bounded range with the drift disclosed. There is no fourth option where the number is quietly produced anyway.

How the analysis is done correctly anyway

The limit is real and it is narrower than it first sounds. The engine's day is not eight hours by definition; it is whatever the calendar says. So a twelve-hour shift calendar carrying twelve hours per day makes one engine day exactly one shift, and a continuous calendar carrying twenty-four makes one engine day exactly one round-the-clock period. Sub-day precision is only lost when a duration or lag is not a whole multiple of the calendar's working day. That reframes the problem from "the tool cannot do shift work" to "the unit of the analysis has to be chosen deliberately, and every value has to be a whole multiple of it".

The sequence below is how that gets enforced. It is ordinary discipline rather than anything clever, and the order matters.

1. Decode and diff every calendar before anything else

The moment a file arrives, every calendar is decoded to a working week, an hours-per-day figure, a real-date holiday list and a real-date modified-working-day list. Those are then compared in two directions: against the statutory list for the project window, and against sibling contracts working the same site in the same period. Both directions matter. A missing statutory holiday and a phantom exception are equally serious, and two crews on one site observing different holidays is a finding that must surface at intake.

This is first because fixing a calendar later means rescheduling and reissuing every derived document. Catching it at intake costs an hour. Catching it on the third review round costs a revision of the whole package.

2. Pin the hours-per-day divisor, and prove it was pinned

Every calendar used by an activity or a relationship in scope has to carry an explicit hours-per-day value. Where it is absent, the fallback fires an alert that is fatal in strict mode, so the gap cannot be carried silently into a result. Lag conversion uses the successor's calendar, which is the behaviour of the planning tool, not the predecessor's.

3. Choose the unit of the analysis, then make every value a whole multiple of it

On a two-shift job the unit is the shift. On a continuous outage it is the 24-hour period. Once chosen, the durations and lags in scope are expressed as whole multiples of that unit, and the fractional-value alert becomes the audit that the rebasing was complete. If any value refuses to become a whole multiple, that value is a finding in its own right, usually a placeholder someone entered in hours without thinking about what it meant.

4. Run the gate in strict mode and treat a throw as information

Forensic strict mode makes silent-fallback paths fatal instead of advisory. Calendar fallbacks, dropped relationships, duplicate activity codes, unrecognised constraint tokens, out-of-sequence progress, actuals recorded after the data date, hammock anomalies and the sub-day rounding alert all stop the run. Overrides are permitted, but each one requires a written rationale, an empty or non-string rationale is rejected, and the applied overrides are recorded inside the result object alongside the original alert. The underlying alerts are never suppressed.

A run that throws has told you something about the schedule. On outage files it usually throws the first time, and what it throws on is the list of things to fix or explain.

5. Set the window to the outage, not the calendar month

Windows are cut at the schedule data dates that bracket the outage window, so slip inside the window is measured against the record that existed when the work happened. A monthly window that straddles the boundary mixes pre-outage planning drift with in-window execution and produces a number that belongs to neither.

6. Measure every delta in working days on the governing calendar

No slip, variance or extension is ever reported in raw calendar days. Every delta is computed in working days on the calendar that governs the activity, which is also what the planning tool's float columns mean. On a seven-day outage calendar working days and calendar days converge, which is exactly why the calendar has to be decoded before the delta is computed rather than after.

7. Test the driver rather than trusting the longest bar

Outage schedules are full of zero-resource waiting activities: system unavailable, awaiting clearance, in service. Those do not hold a date and should not be presented as drivers. The constraint belongs on the gate milestone, and the claim that a given activity drives completion is tested by knocking it out and re-running, not asserted from a bar chart.

8. Separate the levelled answer from the logic answer

The logic-driven dates are computed without any resource constraint. The levelled dates in the file are reported as recorded. Where they diverge, the divergence is quantified and attributed, because resource-availability delay and logic or event delay are different questions with different contractual homes. Averaging them, or presenting a levelled date as though logic produced it, is how a report loses its footing under questioning.

Resource-loaded files get verified before they are analysed

A resource-loaded export has a specific failure mode that has nothing to do with dates. Remaining durations reset to zero the first time the file is scheduled. It happens because the planning tool recomputes remaining duration from remaining units, so if a generated file ships with a blank or zero remaining quantity, a units-driven duration type, or missing remaining fields, every remaining duration collapses on the first schedule run and the file is worthless after import. It is the kind of defect that lives in nobody's checklist, which is why it kept recurring until it was scripted.

So every loaded file, whether it was generated here or received from someone else, is run through verify_loaded_xer.py before any analysis. The script exits zero or the file does not move. It checks five things.

  1. Duration type. Every activity must be fixed duration. Any units-driven type lets the planning tool recompute duration from remaining units, which is the root cause of the reset.
  2. Remaining equals original on not-started activities. A mismatch is a failure unless the activity code is explicitly listed as an accepted compression, in which case it is recorded as a warning with an "allowed" marker rather than hidden.
  3. Remaining early dates are populated on every not-started activity. A missing remaining start or remaining finish is a failure.
  4. Assignment-level remaining fields. On every assignment against a not-started activity, remaining quantity must equal original quantity within 0.005 units, and remaining cost must equal original cost within half a cent. A zero or blank remaining quantity is flagged in the output as the classic reset trigger. Remaining units per hour must be above zero wherever remaining quantity is above zero, and every actual quantity and cost field must be zero.
  5. Orphan and total checks. An assignment pointing at an activity that is not in the file is a failure. Where an expected loaded total is supplied, the sum of assignment costs is compared to it within half a cent, so a penny-level drift in the loading cannot pass unnoticed.
Why this belongs on a capability page

Because it is the difference between a resource-loaded deliverable that survives being opened by somebody else and one that quietly destroys itself. The check runs on receipt as well as on delivery, which means a loaded file arriving from another party is screened before its dates are relied on.

What we will not claim

Four things, stated plainly, because a capability page that only lists strengths is not useful to anyone deciding whether to hire.

  1. The engine does not model sub-day work. It is day granular. Fractions round, and the rounding is disclosed and fatal in strict mode. If a matter genuinely turns on hour-level sequencing inside a shift, that analysis is done against the native planning tool's own output and the engine is used for the day-level questions only.
  2. The engine does not level resources. It will not reproduce a levelled date, and it does not try to. It reports the logic answer and the gap.
  3. Strict mode does not make an override correct. It enforces that the override was written down with a reason. Whether the reason holds up is the analyst's burden, not the tool's.
  4. Passing the gate is not the same as being right about the project. The observed error rate on the published validation suite describes the computation, not how well the schedule represents what happened on site. That second question is answered with records, and it is where the real work is.

Have a shift-based or outage file you do not trust

Send the export. The first pass is calendar decode and diff, the loaded-file verification, and a strict-mode run, which between them tell you whether the dates in the file mean what they appear to mean.

Method references and engine disclosure

  • AACE International Recommended Practice 29R-03, Forensic Schedule Analysis (2003, rev. 2011)
  • Society of Construction Law, Delay and Disruption Protocol, 2nd edition (2017)

The engine's own reliability disclosure, its validation surface, its disclosed limitations and its forensic strict-mode contract are published with the open-source engine and summarised on the validation page. The limits described on this page are taken from that disclosure rather than written around it. Every alert context named here is emitted by the engine and can be filtered directly out of a result object by any reviewing party.

Scope note. This page describes analytical method and tooling behaviour. It is general information for construction professionals and their advisers, not legal advice, and it does not create a professional relationship. Outcomes on any specific matter depend on the contract and the records.