| Title: | Calculations for the WH-347 Certified Payroll Form |
|---|---|
| Description: | Computes one line and one week of the U.S. Department of Labor WH-347 certified payroll form, revised version (Rev. January 2025), used on Davis-Bacon and Related Acts contracts. Given a worker's week (base rate, fringe rates per plan, straight-time and overtime hours per day, cash in lieu of fringe, deductions), it returns columns 5 to 9 of the form, the per-plan fringe totals for the crew and a reconciliation check of column 6B. Port of the Go package 'wh347' published by TableTemplates; no dependency and no I/O. |
| Authors: | TableTemplates [aut, cre, cph] |
| Maintainer: | TableTemplates <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-10-09 09:30:42 UTC |
| Source: | https://github.com/tresor4k/wh347-r |
Computes one line of the U.S. Department of Labor WH-347 certified payroll form, revised version (Rev. January 2025), used on Davis-Bacon and Related Acts contracts: column 2 and columns 5 to 9, from one worker's week on one project.
wh347_compute_line(week)wh347_compute_line(week)
week |
a worker week built by |
Arithmetic is plain double precision with no intermediate rounding, in the
same order of operations as the Go package; use
wh347_round_cents for display only. The ST/OT split is entered
by the user, not computed from a daily or weekly threshold.
The line is flagged "review" when the net pay is negative, the week
exceeds 112 hours, the base rate is negative, or an entered column 7B is
lower than column 7A minus 0.005.
A named list:
worker_type |
column 2, copied from the input: |
st_hours |
column 5, straight-time hours for the week. |
ot_hours |
column 5, overtime hours for the week. |
total_hours |
column 5, total hours (ST + OT). |
st_rate |
column 6A, ST line: base rate, $/h. |
ot_rate |
column 6A, OT line: base rate x 1.5, $/h. |
gross |
wages ST x rate + OT x rate x 1.5 (excludes 6C). |
fringe_credit |
column 6B: total hours x total fringe $/h. |
cash_in_lieu |
column 6C: total hours x cash-in-lieu $/h. |
gross_project |
column 7A, gross earned on this project: wages + 6C. |
gross_all_work |
column 7B, gross earned for all work: entered value, else 7A. |
gross_all_work_entered |
|
total_deductions |
column 8, total deductions for all work. |
net_pay |
column 9: 7B - 8. The 6B fringe credit is never included. |
rate_plus_fringe |
base rate + total fringe, $/h (utility value, not a form column). |
sanity |
|
Not an official DOL tool, not legal advice; you remain responsible for your certified payrolls. The package does not check wage determinations, fill the Statement of Compliance (page 2), or produce the form itself. DOL instructions for the WH-347: https://www.dol.gov/agencies/whd/forms/wh347. The same calculations are available as a hosted tool at https://tabletemplates.com/certified-payroll-software/.
wh347_compute_run, wh347_worker_week
week <- wh347_worker_week( base_rate = 30, fringe = wh347_fringe_rates(health_welfare = 4, pension = 2), st_by_day = c(8, 8, 8, 8, 8, 0, 0), ot_by_day = c(4, 0, 0, 0, 0, 0, 0), deductions = wh347_deductions(other = 310)) l <- wh347_compute_line(week) l$total_hours # 44, column 5 l$ot_rate # 45, column 6A, OT line wh347_round_cents(l$fringe_credit) # 264, column 6B wh347_round_cents(l$gross_project) # 1380, column 7A wh347_round_cents(l$net_pay) # 1070, column 9 l$sanity # "ok"week <- wh347_worker_week( base_rate = 30, fringe = wh347_fringe_rates(health_welfare = 4, pension = 2), st_by_day = c(8, 8, 8, 8, 8, 0, 0), ot_by_day = c(4, 0, 0, 0, 0, 0, 0), deductions = wh347_deductions(other = 310)) l <- wh347_compute_line(week) l$total_hours # 44, column 5 l$ot_rate # 45, column 6A, OT line wh347_round_cents(l$fringe_credit) # 264, column 6B wh347_round_cents(l$gross_project) # 1380, column 7A wh347_round_cents(l$net_pay) # 1070, column 9 l$sanity # "ok"
Computes all lines of a run with wh347_compute_line, the crew
totals paid to each fringe plan and the reconciliation check of column 6B.
wh347_compute_run(weeks)wh347_compute_run(weeks)
weeks |
a list of worker weeks built by |
A named list:
lines |
one line per input worker, in input order (see
|
plan_totals |
crew totals paid to each plan, a list built by
|
plan_totals_sum |
sum of the 4 plan totals. |
fringe_column_sum |
sum of column 6B over all lines. |
reconciles |
|
week <- wh347_worker_week( base_rate = 30, fringe = wh347_fringe_rates(health_welfare = 4, pension = 2), st_by_day = c(8, 8, 8, 8, 8, 0, 0), ot_by_day = c(4, 0, 0, 0, 0, 0, 0), deductions = wh347_deductions(other = 310)) r <- wh347_compute_run(list(week)) wh347_round_cents(r$plan_totals$health_welfare) # 176 r$reconciles # TRUEweek <- wh347_worker_week( base_rate = 30, fringe = wh347_fringe_rates(health_welfare = 4, pension = 2), st_by_day = c(8, 8, 8, 8, 8, 0, 0), ot_by_day = c(4, 0, 0, 0, 0, 0, 0), deductions = wh347_deductions(other = 310)) r <- wh347_compute_run(list(week)) wh347_round_cents(r$plan_totals$health_welfare) # 176 r$reconciles # TRUE
wh347_deductions holds the itemized weekly deductions in $ for all
work (column 8). They are entered, never computed.
wh347_deductions_total returns the column 8 total: the sum of the 4
itemized deductions.
wh347_deductions(fica = 0, federal_tax = 0, state_tax = 0, other = 0) wh347_deductions_total(deductions)wh347_deductions(fica = 0, federal_tax = 0, state_tax = 0, other = 0) wh347_deductions_total(deductions)
fica |
FICA withholding. |
federal_tax |
federal income tax withholding. |
state_tax |
state income tax withholding. |
other |
other permissible deductions. |
deductions |
a list built by |
wh347_deductions: a named list with elements fica,
federal_tax, state_tax and other.
wh347_deductions_total: a number.
d <- wh347_deductions(fica = 61.2, federal_tax = 90, state_tax = 38.5, other = 10.3) wh347_deductions_total(d) # 200d <- wh347_deductions(fica = 61.2, federal_tax = 90, state_tax = 38.5, other = 10.3) wh347_deductions_total(d) # 200
wh347_fringe_rates holds the fringe benefit rates in $/hour, one per
plan. wh347_fringe_total returns the total hourly fringe credit: the
sum of the 4 plan rates.
wh347_fringe_rates(health_welfare = 0, pension = 0, vacation = 0, training = 0) wh347_fringe_total(fringe)wh347_fringe_rates(health_welfare = 0, pension = 0, vacation = 0, training = 0) wh347_fringe_total(fringe)
health_welfare |
health and welfare plan, $/h. |
pension |
pension plan, $/h. |
vacation |
vacation / holiday plan, $/h. |
training |
training plan, $/h. |
fringe |
a list built by |
wh347_fringe_rates: a named list with elements health_welfare,
pension, vacation and training.
wh347_fringe_total: a number.
f <- wh347_fringe_rates(health_welfare = 4, pension = 2) wh347_fringe_total(f) # 6f <- wh347_fringe_rates(health_welfare = 4, pension = 2) wh347_fringe_total(f) # 6
Rounds to cents, halves toward +Inf: the same result as the Go function
RoundCents and as JavaScript Math.round(n * 100) / 100. For
display and snapshots only, never inside the calculation.
wh347_round_cents(x)wh347_round_cents(x)
x |
a numeric vector. |
x * 100 is computed in double precision, floored, and increased by
one when the remainder is at least 0.5. This differs from round(x, 2),
which follows IEC 60559 and does not round halves toward +Inf.
A numeric vector of the same length as x.
wh347_round_cents(c(1069.999999999, 0.005, -1.005, -0.125, 2.675)) # 1070.00 0.01 -1.00 -0.12 2.68wh347_round_cents(c(1069.999999999, 0.005, -1.005, -0.125, 2.675)) # 1070.00 0.01 -1.00 -0.12 2.68
Builds the input of wh347_compute_line: a named list with the
base rate, the fringe rates per plan, the straight-time and overtime hours of
the 7 days of the payroll week, the deductions, the cash paid in lieu of
fringe benefits, the gross earned for all work (column 7B) and the worker
type (column 2).
wh347_worker_week(base_rate = 0, fringe = wh347_fringe_rates(), st_by_day = rep(0, 7), ot_by_day = rep(0, 7), deductions = wh347_deductions(), cash_in_lieu_rate = 0, gross_all_work = NULL, worker_type = "")wh347_worker_week(base_rate = 0, fringe = wh347_fringe_rates(), st_by_day = rep(0, 7), ot_by_day = rep(0, 7), deductions = wh347_deductions(), cash_in_lieu_rate = 0, gross_all_work = NULL, worker_type = "")
base_rate |
hourly base rate actually paid, $/h (column 6A, ST line). |
fringe |
hourly fringe credit per plan, built by
|
st_by_day |
straight-time hours per day, 7 days of the payroll week. |
ot_by_day |
overtime hours per day, 7 days of the payroll week. The ST/OT split is entered, not computed. |
deductions |
itemized deductions for all work (column 8), built by
|
cash_in_lieu_rate |
cash paid in lieu of fringe benefits, $/h (column 6C). Zero means none. |
gross_all_work |
gross earned for all work, all projects (column 7B).
|
worker_type |
column 2: |
A named list with the same elements as the arguments.
wh347_compute_line, wh347_fringe_rates,
wh347_deductions
week <- wh347_worker_week( base_rate = 20, fringe = wh347_fringe_rates(health_welfare = 1, pension = 1, vacation = 0.5, training = 0.25), st_by_day = c(8, 8, 8, 8, 8, 0, 0), gross_all_work = 1500, worker_type = "RA") wh347_compute_line(week)$net_pay # 1500week <- wh347_worker_week( base_rate = 20, fringe = wh347_fringe_rates(health_welfare = 1, pension = 1, vacation = 0.5, training = 0.25), st_by_day = c(8, 8, 8, 8, 8, 0, 0), gross_all_work = 1500, worker_type = "RA") wh347_compute_line(week)$net_pay # 1500