Generate a payroll register
Prerequisites
- A working Rust toolchain (see How to install the development toolchain)
- A checkout of the workspace containing the construction crates — the payroll crate is a member of it, and reads two reference files out of the construction crate's own directory at a path resolved when it compiles
- A construction data directory holding
work_packages.csv - Write permission on the output directory
No services need to be running. This is a batch task on the command line, and the command line is the whole interface — there is no console screen, no F-key slot, and nothing to click.
One prerequisite is not configurable and will surprise you: the output directory is a hard-coded absolute path with no environment-variable override. The run creates it and fails outright if it cannot. The input data directory is redirectable; the output is not. That asymmetry is the reverse of the sibling accounting tool's, and it is a real gap rather than a design position.
Purpose
Produce the Payroll Register (by Division) — a single working schedule of budgeted labour hours and crew size, one row per construction division, as HTML and PDF.
Read the next paragraph before running anything, because the report's name promises considerably more than the report delivers.
This command computes no pay. It does not calculate gross pay, net pay, or any deduction. It does not determine a pay frequency, a pay date, or a remittance schedule. It does not read a timecard — the hours it aggregates are budgeted hours from work-package estimates, not hours anyone worked. It has no concept of an employee: its rows are divisions, and the crew size beside each one is a planning assumption, not a headcount. The columns headed Pay Freq. and Gross Pay exist on the page and every cell in both of them is an em dash.
That is deliberate, and the document says so on its own face. The gross-to-net computation and the pay-frequency data model are both explicitly undesigned at this stage, and the renderer prints a dash rather than a fabricated number. What the register is genuinely useful for is what its two populated columns say: how many budgeted labour hours each division carries, and what crew size the plan assumes for it.
For the design this report is a first slice of, see tool-payroll — jurisdiction-aware payroll and statutory remittance. For the tool the hours come from, see tool-construction — construction cost, schedule, and quality ledger.
Procedure
1. Point the tool at the construction data directory
This is the same variable the construction reporting binary uses, and it behaves the same way: unset, the binary falls back to a hard-coded absolute path belonging to the deployment it was first built for, and the run fails on a file read naming a directory you have never heard of.
2. Confirm the four inputs
The run reads four CSV files from three different places. Two arrive with the checkout, one comes from the directory you just exported, and one lives in the payroll crate's own data directory.
| File | Where it comes from | What is read from it |
|---|---|---|
division_crosswalk.csv |
The construction crate's tracked reference data | Cost-code prefix and division name |
crew_assumptions.csv |
The construction crate's tracked reference data | Division name and crew size |
work_packages.csv |
TCO26_DATA_DIR |
Cost code and budgeted labour hours |
wage_payment_rules.csv |
The payroll crate's own tracked reference data | The jurisdiction row printed in the note |
division_crosswalk.csv:
uniformat_prefix,csi_division,division_name
crew_assumptions.csv:
csi_division,division_name,crew_size,hours_per_day
wage_payment_rules.csv:
jurisdiction_code,max_pay_period_days,max_days_to_pay_after_period_end,day_counting,remitting_authority,comp_authority,source_ref,effective_from
From work_packages.csv — a wider file the construction tool owns — this run reads exactly two fields: cost_code and labor_hours_budget. Everything else in the row is ignored.
Three details are worth knowing before a first run:
hours_per_dayis loaded and never used. The crew-assumptions loader parses it into memory; this report consumes onlycrew_size. It is not a column you need to get right for this task.- A cost code joins to a division by longest matching prefix. Each crosswalk row declares a prefix; a work package's cost code is matched against every prefix and the longest match wins. A code matching no prefix at all joins to nothing — see the third verification check below for why that matters more than it sounds.
- The jurisdiction is compiled in. There is no flag, no environment variable, and no column anywhere that selects which jurisdiction row is read. One jurisdiction code is a constant in the binary, and only that row is looked up.
3. Run the binary
From the workspace root:
That is the complete command. There are no flags, no subcommands, and no positional arguments — the binary does not parse a command line at all, so there is no --help to consult, no --jurisdiction to override, and no way to render only part of the report. Every configurable thing it has is the one environment variable from step 1.
4. Read the console line
A successful run prints exactly one line:
[payroll_register] 9 division(s), 4820 total budgeted hour(s) — written to <output directory>
The counts are invented for this guide; the shape is real. Both numbers are worth reading rather than skipping — they are the only summary the run produces, and the division count is the first thing that reveals a broken crosswalk.
Expected outcome
Two files in the compiled-in output directory, which the run creates if it is absent:
| File | What it is |
|---|---|
payroll_register.html |
The register as a web page |
payroll_register.pdf |
The same register as a print document |
The document has three parts: a masthead carrying the deployment's own project label, the report title, and the division count; a Basis of preparation note; and one table.
The table has five columns — Division, Crew Size, Budgeted Hours, Pay Freq., Gross Pay — and repeats its header across pages. Two of those five are populated. The register is typeset as a working document rather than as a filed statement, which is the correct classification for a schedule of budgeted figures.
The Basis of preparation note states in the document itself that crew size and budgeted hours are real and sourced from the construction tool's work-package data, that pay frequency and gross pay are not shown because neither has been designed, and that no timecard or payroll transaction has ever been recorded for the project. A second paragraph, present only when the jurisdiction row is found, states that jurisdiction's wage-payment ceiling in days, whether those days are counted as calendar or working days, which authority administers remittance, which administers workers'-compensation reporting, and the citation behind all of it.
That second paragraph is a statement of the rule, not an application of it. Nothing in this run computes a pay date, and nothing checks one against the ceiling.
Verification
Check the division count against the crosswalk. The number in the console line is how many distinct divisions received at least one matched work package. If it is lower than the number of divisions you expect to see work in, the crosswalk is not matching what you think it is.
Check the total against the table. The total budgeted hours in the console line is the sum of the Budgeted Hours column. It should also agree with the man-hours figure the sibling construction status report produces from the same work-package data — the join and aggregation here deliberately mirror that report's own logic so the two can never quietly disagree about the same underlying numbers. If they differ, one of the two has drifted, and that is a finding worth chasing.
Sum labor_hours_budget yourself and compare. This check is not optional, and it is the only way to catch the report's one silent failure. A work-package row whose cost code matches no crosswalk prefix, or whose labor_hours_budget is blank or does not parse as a number, is dropped from the aggregation with no warning, no counter, and no mention anywhere in the console line or the rendered document. The register will look complete. Add up the column in your source file: if your total is higher than the console line's, the difference is dropped rows, not an arithmetic error.
Read the em dashes as two different facts. A dash under Pay Freq. or Gross Pay means the platform does not compute that quantity at all. A dash under Crew Size means something narrower and more actionable: no row in crew_assumptions.csv has a division name matching that division. The renderer refuses to print 0 there, precisely so a missing assumption cannot be mistaken for a crew of nobody.
Confirm the jurisdiction paragraph is present. If the Basis of preparation note ends after its first paragraph, the jurisdiction lookup found nothing and the document has silently lost its entire regulatory disclosure. See the edge cases below.
What this task does not do
- It does not compute gross pay. No wage rate is read from anywhere. The column is structural.
- It does not compute net pay or any deduction. Gross-to-net computation — tax brackets, statutory deduction formulas — is explicitly out of scope for this build and is not partially implemented.
- It does not determine a pay frequency. No field carrying a per-crew or per-employee pay frequency has a home in any schema this run reads, which is why the column is a dash rather than a default.
- It does not compute or enforce a pay date. The jurisdiction's wage-payment ceiling is printed as text in a note. Nothing derives a pay date, and nothing checks one against that ceiling.
- It does not remit anything, or compute a remittance schedule. Remitting and workers'-compensation authorities are named in the note as facts about the jurisdiction. No schedule is calculated.
- It does not read timecards. Every hour in this report is a budgeted estimate attached to a work package. No hour anyone actually worked appears anywhere.
- It is not per-employee. There is no employee record, no roster, and no name in this pipeline at any point. Rows are divisions.
- It does not select a jurisdiction. One jurisdiction code is a compile-time constant; an operator in another jurisdiction currently needs a code change, not a configuration change.
- It writes no ledger entries. The run reads four files and writes two.
Edge cases
- Any missing input file aborts the run with
read <path>: <error>and a non-zero exit. Nothing is written. The message is a raw panic rather than a formatted error — it names the path, which is the part you need. - A short row in either reference file aborts the run the same abrupt way. Both reference loaders index fixed column positions, so a row with fewer fields than expected is an index failure, not a skipped line.
- A comma inside a division name will break the crosswalk. The work-package reader handles quoted fields; the two reference loaders split on plain commas and do not. A division name containing a comma silently shifts every field after it.
- An unrecognised
day_countingvalue in the jurisdiction table aborts the run naming the offending value. Only two spellings are accepted. - No row for the compiled-in jurisdiction is not an error. The run succeeds, the two files are written, and the Basis of preparation note simply omits its jurisdiction paragraph. This is the most dangerous failure in the tool, because the document looks finished and has quietly dropped its entire regulatory disclosure. Check for the paragraph rather than assuming it.
- Divisions are ordered alphabetically by name, not by division number. If you expect a numeric ordering, the report is not wrong — it is sorted on the other key.
- Output files are overwritten in place. No versioning, no timestamped directory, no prompt, and no environment variable to send them elsewhere. Copy anything you need to keep before re-running.
Rollback
Nothing to undo in the source data: the run reads four files and never writes to any of them. Its only writes are payroll_register.html and payroll_register.pdf. Delete them, or re-run to replace them.
Next steps
- Generate a construction cost estimate report — the report that produces the work-package data this register aggregates
- Generate a financial statement package — the sibling accounting task on the same shared renderer
- How to install the development toolchain — if
cargo runis not yet available on your machine
See also
- tool-payroll — jurisdiction-aware payroll and statutory remittance — the pay-cadence, remittance, and jurisdiction design this register is a first slice of, including everything it does not yet compute
- tool-construction — construction cost, schedule, and quality ledger — the ledger whose work-package data feeds this report one way
- tool-accounting — double-entry ledger and audit-ready financial statements — the destination the computed payroll dollars are designed to land in, once they exist
- The financial and construction tool family — a shared design across three products — where this tool sits among the financial and construction tools