How to audit a Primavera P6 schedule
Most P6 audit advice is a list of things to click. This page is organised around a different question, because it is the one that decides whether a finding survives being argued with: is the thing you are looking at recorded in the file, or is it something the application computed while you were looking at it?
The first is evidence: written by the submitter's own software at export time, the same tomorrow, and readable by anyone on the other side of the table. The second is a view, depending on the layout, the filter, the columns, and — for several of the most important numbers on the screen — on a project-level setting the schedule carries but does not display.
This page is the P6-specific companion to the schedule review checklist, which is software-agnostic and sorted by what decides each item. Read that first for what to check and what each item does not prove. This page is where the thing lives in P6 or in the exported file. It does not repeat the checklist's items.
Stored or computed: the two-column reading
| What you are looking at | Stored in the export | Computed on the fly |
|---|---|---|
| Activity ID, name, WBS | Yes — TASK.task_code, task_name, wbs_id |
— |
| Original and remaining duration | Yes, in hours (target_drtn_hr_cnt, remain_drtn_hr_cnt) |
The day figure on screen is hours divided by a calendar |
| Actual start, actual finish | Yes | — |
| Early and late dates | Yes, as P6 last calculated them | Recalculated the moment anyone presses F9 |
| Total float, free float | Yes, in hours (total_float_hr_cnt, free_float_hr_cnt) |
Same |
| Critical (the red bars) | No — a derived flag | Depends on a project setting, see below |
| Longest path membership | Yes — TASK.driving_path_flag |
Also derived when P6 reschedules |
| Relationships and lag | Yes — TASKPRED, lag in hours |
— |
| Calendars | Yes — CALENDAR.clndr_data, a packed string |
The working-day arithmetic on screen |
| Scheduling settings | Yes — SCHEDOPTIONS, one row per project |
— |
| Layout, filter, column set, bar colours | No | Entirely a display choice |
The practical consequence: a screenshot is never the finding. It shows a computed view under settings nobody recorded. The exported file plus the settings row is the finding, and it is reproducible.
1. Get the right file, and know what it is
Ask for the native export the submitter's own P6 produced — .xer or P6 XML —
not a PDF, not a printed Gantt, not a .mpp. What each of those carries is on
schedule file formats.
Three things about an XER, all covered at length on what an XER file is:
- Nothing declares the character encoding. The header states the P6 version that wrote the file and says nothing about the code page. A file from a non-Western install can decode into plausible-looking nonsense exactly where activity names should be, with no error raised.
- There is no record count and no checksum. A file truncated in transit is structurally indistinguishable from a short one.
- The header does state the writing version, which is worth recording: P6 versions disagree with each other about how some fields below are written.
2. Get the right project out of it — the trap nobody warns you about
A single XER can hold several projects, and a real schedule is frequently
exported alongside one or more near-empty stubs: a one-activity shell, or a
PROJECT row with no activities at all. Which is written first is not governed
by size, by name, or by anything a reader can predict.
A tool — or a person — that silently opens "the first project" can therefore produce a complete, confident, fully scored review of a one-activity stub while the several-thousand-activity schedule beside it in the same file is never read. Nothing about the output looks wrong.
So the first query on any XER is: how many PROJECT rows does it hold, and how
many TASK rows carry each proj_id? Count activities from the TASK table by
proj_id — not from anything the PROJECT row declares about itself. Then name
the project you reviewed, in writing, and say if the container held others.
This project's importer enumerates the projects it finds and refuses to choose one on your behalf, listing each with its activity count so the choice is obvious without opening the file.
3. The data date, and which field it came from
The data date lives on the PROJECT row as last_recalc_date. It is not
plan_start_date, which is the planned start of the project. Reading the wrong
one makes every progressed schedule reschedule from its original start and report
all the completed work as still to do — a failure that produces a complete,
internally consistent, entirely wrong schedule.
Then settle the convention, because the file does not state it. A data date of 1 February means either that work is reported through and including 1 February, or that 1 February is the first day of the next period. Schedulers, specifications and software disagree, mostly without noticing, and the difference is one day per window boundary — over eleven monthly updates, eleven days of argument from an assumption neither side wrote down. CONCEPTS.md §3 sets out both readings.
Also read plan_end_date on the same row: the Must Finish By a planner typed.
Where it is present it drives the backward pass, so it is the reason for float
figures that otherwise look inexplicable. Do not confuse it with scd_end_date,
P6's own computed finish; the two can sit months apart on one row.
4. Check which settings produced the dates, before you read any date
This is the step most reviews skip and the one that makes every later number
comparable. P6 keeps the scheduling settings in a SCHEDOPTIONS record, one row
per project, and they change computed dates by weeks: retained logic against
progress override, the definition of critical activities, the calendar a lag is
counted on. Each is a declared fact the file carries.
A schedule recalculated under different settings from the update it is compared against is not comparable to it, and nothing on the printed page says so. The P6 Schedule Options record is the reference for each setting, what changes if it is set the other way, and how to read which was used.
Two notes. The SCHEDOPTIONS row must be the one whose proj_id matches the
project you selected in step 2 — a multi-project export carries one row per
project. And the critical-activity definition is not in SCHEDOPTIONS at
all: P6 stores it on the PROJECT row as critical_path_type.
5. The logic sweep
The relationships are in TASKPRED, one row per link: successor task_id,
pred_task_id, pred_type, lag_hr_cnt.
- Relationship types are prefixed —
PR_FS,PR_SS,PR_FF,PR_SF. Slicing the prefix off rather than mapping the four values turns an unexpected value into a silent finish-to-start, moving the critical path with nothing logged. - Lag is in hours, and which calendar those hours are counted against is a scheduling setting, not a property of the link. See lags and leads.
- Links can cross projects. In a container, a
TASKPREDrow may name a predecessor in another project. Whether those are honoured is itself a setting.
The counts a reviewer wants out of this table — open ends, dangling logic, the finish-to-start share, the lag inventory — are items 1, 2, 9 and 10 of the checklist.
6. Constraints
Constraints are on the TASK row, as a type and a date. They are stored, so the
inventory is evidence. What it cannot tell you is whether a constraint is
artificial — that is intent, and no computation reaches it. The honest output is
the population a prohibition describes, listed, for a person to read.
Schedule constraints covers the types and what each
does to the backward pass.
Look first for the constraint that has quietly become the anchor of the whole schedule: a Must Finish By on the project, or a mandatory constraint mid-network, either of which makes total float a measurement against that date rather than against the schedule's own finish.
7. Calendars
CALENDAR.clndr_data is a single cell holding a packed, bracketed tree: the
working week, the shift times, and every holiday and shutdown. It is the column
with no downstream symptom — get it wrong and every date, float and delay
attribution moves together while the schedule still looks reasonable.
Inside clndr_data has the grammar; the audit-side points:
day_hr_cntis per calendar and is not always eight. Durations and floats are stored in hours; dividing by eight because most calendars are eight rescales the float of every activity on a ten-hour shift.- A project calendar inherits from a base.
base_clndr_idlinks it to a global calendar, and the child stores only its departures — so the shared winter shutdown may live entirely in the parent. Reading the child alone works days the project does not. - A file can declare far more calendars than the schedule uses, most of them
resource calendars. Count assignments from
TASK.clndr_id, not calendar rows.
More on the review question, as against the file question, on schedule calendars.
8. Progress and actual dates
Actual start and actual finish are stored, and comparing them against the data
date is the one purely arithmetic status check there is. The subtler issue:
P6 overwrites the early dates of a started activity with its recorded actual
start, so early_start_date on a progressed row is not a forecast. A
comparison that treats it as one reports a disagreement on every progressed
activity in the file.
Out-of-sequence progress belongs here too, and it is where the display sits furthest from the file: whether an out-of-sequence successor's remaining work is held by its unfinished predecessor depends entirely on the progress mode in the settings row, and the bars give no indication of which mode drew them.
9. Float
total_float_hr_cnt and free_float_hr_cnt are hours, against the activity's
own calendar. To convert to working days, divide by the day_hr_cnt of the
calendar named in that activity's TASK.clndr_id, and carry the sign around the
conversion — rounding the magnitude of a negative float is not the same as
rounding the signed value.
What float means on a given file is decided by two settings, not one: how total float is computed, and what the backward pass was anchored on. Both are in the options reference; the difference between the two float figures is on total float and free float.
10. The critical path, and why two reviewers can disagree
P6 reports one criticality definition at a time, chosen by a project-level
setting stored on the PROJECT row as critical_path_type: CT_DrivPath for
Longest Path, CT_TotFloat for the total-float definition. Whichever is set, the
red bars on screen show that one, and the other answer is discarded silently.
They are different questions. "Which activities have no spare time?" is a float question. "Which work is actually pushing the completion date?" is a longest-path question. On a schedule with a Must Finish By a working day past the computed finish, every activity on the driving chain can carry a day of float — so a reviewer filtering on total float at or below zero finds almost nothing, while a reviewer reading the longest path finds a chain of dozens. Both filtered correctly. Neither number corrects the other.
A third sense is worth knowing, because it is what driving_path_flag records:
the driving set — every activity that drives the finish — is wider than the
driving chain, because at a tie two predecessors both drive and a chain can
follow only one.
So when a comment and a response disagree about the length of the critical path, the first question is not who counted wrong. It is which definition each was reading, and what the file's own setting says. CONCEPTS.md §2 sets out the two schools and why this project computes and reports both. UFGS 01 32 01.00 10 §3.12(f) requires the Longest Path setting specifically, which is answerable from the exported file without re-running anything — see the UFGS page.
What P6 shows you that is a display choice, not a fact
- The activity ID you see is
task_code. The internal key istask_id, and P6 renumbers it between updates. Any join between two exports has to be on the code, and any join within one export has to be on the id. - Activity codes are free text somebody typed. A code type called
RESPONSIBILITYis a convention, not a fact about the schedule, and the same concept appears under a dozen names across organisations. Absence of a code type is not a finding; it is a name you guessed wrong. Some files carry code types with an empty name, which nothing keyed on a name can address at all. UFGS §3.12(a) requires codes at project level rather than global — a different question again: where the code type lives, not what it says. - Column values you cannot trace to a table — percent complete variants, rolled-up WBS dates, earned-value columns — are computed from settings and admin preferences the export may not carry.
- Bar colours, filters and the layout carry no information about the schedule. Two reviewers reading one file through different layouts are not reading the same schedule, and neither of them can tell.
What a clean pass through this page does not establish
Everything above is mechanical: that the file is readable, that you are reading the right project in it, and that you know which settings produced the numbers. It establishes nothing about whether the durations are achievable, whether the sequence is how the work will be built, whether the scope is complete, or whether anybody is owed time. Those are Part 3 of the checklist, and they are decided by people with names. Whether a submission is acceptable is the reviewing authority's determination.
Related reading
- P6 Schedule Options — the settings reference this page defers to.
- What an XER file is · Inside
clndr_data— the file-format detail. - The schedule review checklist — the specification-agnostic list.
docs/ANSWER-TABLE.md— the column-by-column mapping between this project's computed answer and P6's own stored fields, so a reviewer can produce a schedule of differences without running our code. It is held in the repository rather than published here, so it is named rather than linked.- Agency and federal requirements compared · DCMA 14-point · GAO-16-89G.
- Glossary — data date, total float, longest path, retained logic.
Source: web/pages/p6-schedule-audit.md. Source commit date: 2026-09-11.