How to Build a Labor Hour Database From Your Own Jobs
Why this matters
Published production rates describe an average shop doing average work in average conditions. Your shop is none of those things. Your crews, your service area, your customer mix, and your typical site conditions produce hour counts that can sit well off any published figure in either direction, and the only way to know which direction is to measure your own.
The payoff is not just tighter bids. It is being able to answer "how long does that take us" with a number and a spread instead of a shrug, which changes what you can commit to, what you can promise a customer, and whether a fixed price on a given job type is a reasonable risk or a coin flip. Shops that have this are visibly calmer when a big bid comes in.
What you are building
Not a list of averages. A small library of task codes, each carrying four things: how many times you have measured it, the median hours, the spread, and the conditions under which it was measured.
The spread is the part that gets left out and it is the part that decides whether you can take the job at a fixed price. A code with a median of 4.0 hours where the middle half of jobs land between 3.5 and 4.8 hours is safe to price flat. The same 4.0-hour median with a middle half spanning 2.5 to 9.0 hours is not a task, it is a category that needs splitting.
Step 1: Set the task-code grain
Two failure modes bracket the right answer. Codes too coarse ("service call") tell you nothing you did not already know. Codes too fine ("torque the connections") never get logged accurately because nobody will stop to switch codes six times an hour.
Two tests for the right grain: a code should represent work of at least about 0.5 hours, and it should appear on at least about 10 jobs a year. Below either, drop it or fold it into a neighbour.
Start with 8 to 15 codes covering the work that makes up most of your volume. Resist the urge to build 60 up front. Codes you add later, driven by a real question, get used. Codes you invent in a planning session do not.
Standing codes worth carrying regardless of trade
Alongside the task codes, carry a handful that catch time your task codes will otherwise absorb invisibly:
- Travel, separated from on-site time
- Diagnosis or investigation, separate from repair
- Site setup, protection, and reinstatement
- Waiting, whether on access, a part, another trade, or a customer decision
- Closeout, meaning paperwork, photos, and customer walkthrough
The waiting code is the highest-yield one on this list. Waiting time hidden inside task codes is what makes your task hours look inconsistent when the tasks are actually stable.
Step 2: Decide what makes two jobs the same job
Grouping is where the database is won or lost. Two jobs share a code only if the work is genuinely the same. Access type, property class, and equipment age are the three factors that most often make nominally-identical work take very different time, so record them as tags on every record even if you do not split codes by them at first.
You will not know which tags matter until you have data. The tag costs nothing to capture and cannot be recovered later, so capture more tags than you think you need and split codes only when the data shows a split.
Step 3: Capture at the point of work
Same-day entry, three fields: job, task code, and start and stop or duration. Anything more and compliance drops; anything less and the record is not usable.
Reconstructed time is systematically biased toward whatever the tech believes the task should take, which is the exact belief you built the database to test. A database built from Friday reconstructions will confirm your existing template no matter what the week actually looked like, and you will never know it is doing that.
Round to a consistent resolution, and make it a real one. Quarter hours are fine. Whole hours are not, because half your codes are under 2 hours and whole-hour rounding on a 1.4-hour task is a 40 percent error in the record.
Step 4: Clean the data on defect, never on outcome
You may exclude a record for a data defect. You may never exclude one for an outcome you did not like.
Legitimate exclusions: the code was applied to work it does not describe, the record spans two jobs, the crew composition did not match the code's assumption and you have a crew tag proving it, the duration is impossible on its face.
Illegitimate exclusion, and the one that quietly ruins databases: "that one does not count, it was a bad day." Bad days are part of the distribution. If you strip them out, your median describes an idealized job you occasionally achieve, and every bid built on it will be short.
Two controls keep this honest. Every exclusion carries a written reason. And you watch the exclusion rate: above about 1 in 10 records excluded, the problem is your code definitions, not your data.
Step 5: Compute four numbers per code
Count. How many usable records. Everything else is meaningless without it.
Median. The middle value. The typical instance.
Spread. The 25th and 75th percentile, which bracket the middle half of your jobs. With small samples, use nearest-rank: sort the values, and take the value at rank equal to the percentile times the count, rounded up. With fewer than 8 records, just report the full range instead and say so.
Mean, as a cross-check only. Never publish it as the number to bid, but do compute it. A mean sitting well above the median tells you the code has a long tail, which is a signal to look at what those long jobs share.
Step 6: Publish by sample size, and label the confidence
| Records | Status | How to use it |
|---|---|---|
| Under 5 | Not published | Keep collecting; estimate from judgment and note it |
| 5 to 9 | Provisional | Usable with a visible provisional flag and a wider contingency |
| 10 to 19 | Usable | Bid from it; keep watching the spread |
| 20 or more | Stable | Bid from it, and you can reasonably bid at a chosen percentile |
The label matters as much as the number. An unlabeled 6-record median gets used with the same confidence as a 40-record one, and the estimator has no way to know which they are holding.
Step 7: Keep it alive
Re-compute a code when 10 new records have accumulated since its last computation, or annually, whichever comes first. Use a rolling window: the last 24 months or the last 20 records, whichever is shorter. A code that still carries data from four years ago is describing crews, methods, and materials you may no longer have.
When a re-computation moves a code more than about 10 percent, do not just update the number. Ask what changed. A code drifting upward across a year with no method change is often the first visible sign of something else: crew turnover, a harder customer mix, or a supplier change adding handling time.
Worked example: one code from raw records to published
The code. A standard changeout task at standard access, tagged with crew composition and access type on every record. The existing template carries 3.5 hours.
Raw records over 14 months, sorted (hours):
3.0, 3.2, 3.5, 3.5, 3.6, 3.8, 3.8, 4.0, 4.0, 4.2, 4.5, 4.8, 5.5, 6.0, 7.5, 12.0
That is 16 records.
Cleaning. The 12.0-hour record carries a note that the tech also chased an unrelated fault on the same visit and logged the whole day to this code. That is a coding defect, not a bad day, so it is excluded with the reason written on the record. Fifteen records remain.
The 7.5-hour record stays. Standard crew, standard access, and it simply went long. Excluding it would be cleaning on outcome, and that record is carrying real information about how wide this code's tail is.
Exclusion rate: 1 of 16, about 6 percent, which is inside the 1-in-10 control.
The four numbers, from the 15 remaining records:
- Count: 15. That puts the code in the usable band.
- Median: with 15 values, the middle is the 8th, which is 4.0 hours.
- Spread by nearest rank: the 25th percentile is at rank 4, which is 3.5 hours; the 75th is at rank 12, which is 4.8 hours. So the middle half spans 3.5 to 4.8 hours, a width of 1.3 hours, about a third of the 4.0-hour median.
- Mean, as a cross-check: the 15 values sum to 64.9 hours, so the mean is 4.33 hours. That sits about 8 percent above the 4.0-hour median, a mild right tail driven by the 5.5, 6.0, and 7.5 records. Mild enough that the code does not need splitting yet, but the three long records are worth reading for a shared condition.
What gets published. Median 4.0 hours, middle half 3.5 to 4.8, full range 3.0 to 7.5, 15 records, status usable, measured over 14 months on standard crew and standard access.
What it does to the template. The template said 3.5 hours. The published median is 4.0, which is 0.5 hours above the 3.5-hour template, about 14 percent above it. With 15 records that clears both the sample gate and a 10 percent correction bar, so the template moves to 4.0 hours.
How to bid from it. For repeat work where you run enough volume that individual overruns average out, bid the 4.0-hour median. For a one-off customer, or a job where an overrun would be commercially painful, bid the 75th percentile at 4.8 hours and know exactly what you are buying: 4.8 hours covers about 3 in 4 historical instances rather than about half of them, at a cost of 0.8 hours of price on every bid. That is a stated trade, not a hunch, and it is the single most useful thing this whole exercise buys you.
The three long records. Before closing the code out, someone pulls the 5.5, 6.0, and 7.5-hour records to see whether they share a tag. If all three sit on one property class or one equipment-age band, the code should split into two, and both halves will bid far tighter than a single 4.0-hour code with a 3.0 to 7.5 range ever can. If the three causes are unrelated, the tail is genuine and the 75th-percentile option above is how you price for it.
What changes the answer
Very low volume. A shop doing a few hundred jobs a year will take a long time to reach 20 records on anything but its top codes. Publish provisional values with the flag visible rather than waiting. A flagged provisional number beats no number, as long as the flag actually travels with it into the estimate.
A crew you just changed. Historical hours assume the crews that produced them. After significant turnover, treat every code as provisional for a quarter and re-compute early. Do not delete the old data - the comparison between old and new is exactly how you size a training gap.
Trade or work types with heavy regulatory variability. Where inspection waits, permit timing, or utility coordination drive a meaningful share of elapsed time, keep those hours in the waiting code rather than in the task codes. Otherwise the task hours will appear to swing wildly between jurisdictions and you will chase a method problem that does not exist.
Piece-rate or flat-rate pay structures. Where techs are paid by the job rather than the hour, self-reported hours carry an obvious incentive to under-report. Either capture hours from a source the tech does not control, or accept that the database measures billed time rather than actual time and stop using it for capacity planning.
How to verify you got this right
- Every published code shows its record count next to its median. If a number circulates without its count, it will eventually be trusted more than it deserves.
- The exclusion log exists and every exclusion has a written reason. Read a random five: if any of them amount to "that job was bad," the cleaning discipline has slipped.
- Pull five recent bids and check whether the estimator used the database or their memory. Silent non-adoption is the most common way this effort dies, and it looks identical to success from the outside.
- Compare a code's median to the actuals of the last 5 jobs bid from it. If the actuals sit consistently above the published median, either the code's conditions no longer describe the work you are winning, or the estimator is applying it to jobs it does not fit.
References
- U.S. Small Business Administration (SBA), cost estimating and record-keeping for small contractors
- Standard construction estimating practice, production-rate development from historical records
- See related: Why Your Average Job Is Lying to You; The Job Costing SOP; How to Price From History Instead of Instinct