INTERACTIVE DEMO — SYNTHETIC DATA
PeopleOS Payroll Engine
A month of clock punches, priced to the rupiah by the production payroll brain. Every hop is inspectable, because being wrong here is not a bad recommendation — it is somebody's pay.
Every record on this page is fabricated. No production system, customer, employee or credential is involved.
The input
August 2026, one fictional roster. Pick a person, then pick a day. The punches below are EMP-D007's month; selecting anyone else re-runs those same punches through their schedule and pay profile, which is exactly how one payroll run differs person to person.
The day engine
Clock events in, eight payable minute buckets out — plus the flags that decide the allowances. The timing verdict and the money are separate decisions, and neither overrides the other.
2026-08-28 · WORKDAY · NONE
- 08:00
- 22:00
- 1.00 h
- 13.00 h
- 08:00
- 0.00 h
- 08:00–17:00
- 30 min · strict start
| BUCKET | MINUTES | HOURS | PRICED AT |
|---|---|---|---|
| Regular | 420 | 7.00 | × 1 hourly rate |
| Overtime, first hour | 60 | 1.00 | × 1.5 combined base |
| Overtime, after first | 300 | 5.00 | × 2 combined base |
| Sunday / red-day regular | 0 | 0.00 | × 2 hourly rate |
| Sunday overtime, first hour | 0 | 0.00 | × 3 combined base |
| Sunday overtime, after first | 0 | 0.00 | × 4 combined base |
| Paid annual leave | 0 | 0.00 | credited, not worked |
| Paid sick leave | 0 | 0.00 | credited, not worked |
- no
- YES
- YES
- no
- YES
- no
- no
- none
- no
The month, and the 78-hour line
Overtime is never requested and never approved here; it is computed from what happened. The month's overtime is then walked chronologically against the company's confirmed monthly threshold, and everything past the line re-prices without a single day being reordered.
CROSSED ON 2026-08-28 · 4.25 H RE-PRICED
- within the threshold, priced where it was earned
- weekday excess → Bonus O/T, × 2 on the combined base
- Sunday excess → Sunday Bonus O/T, kept at × 4
The 78-hour monthly threshold is the company's confirmed rule, not a statutory one. Overtime is walked in date order and, within a day, first hour before after-first — so a crossing splits a single day's bucket rather than moving the day. A Sunday hour never gets cheaper for having been worked late in the month.
| LINE | QUANTITY | FORMULA | AMOUNT |
|---|---|---|---|
| Regular Hours | 155.75 h | 155.75 h x Rp25.000 | Rp3.893.750 |
| Overtime First Hour | 17.00 h | 17.00 h x Combined Base x 1.5 | Rp721.286 |
| Overtime After First Hour | 55.00 h | 55.00 h x Combined Base x 2 | Rp3.111.429 |
| Bonus Overtime After Threshold | 0.25 h | 0.25 h x Combined Base x 2 (above the monthly 78h threshold) | Rp14.143 |
| Sunday Regular Hours | 28.00 h | 28.00 h x Rp25.000 x 2 | Rp1.400.000 |
| Sunday Overtime First Hour | 2.00 h | 2.00 h x Combined Base x 3 | Rp169.714 |
| Sunday Overtime After First Hour | 4.00 h | 4.00 h x Combined Base x 4 | Rp452.571 |
| Sunday Bonus Overtime | 4.00 h | 4.00 h x Combined Base x 4 (Sunday excess keeps the Sunday rate) | Rp452.571 |
| Regular Meal | 19 day(s) | 19 qualifying day(s) x Rp23.000 | Rp437.000 |
| Regular Meal (Sunday/red day) | 4 day(s) | 4 Sunday/red day(s) x Rp23.000 (same regular-meal amount) | Rp92.000 |
| Attendance Bonus | 19 day(s) | 19 qualifying day(s) x Rp15.000 | Rp285.000 |
| Overtime Meal | 14 day(s) | 14 qualifying day(s) x Rp30.000 | Rp420.000 |
| GROSS EARNINGS | Rp11.449.464 |
Exact decimal, not floating point
Every overtime rate is built on a base that does not divide evenly. That is where money quietly goes missing.
- 28285.714285714285714…
- 28285.714285714286234…
The base is a seventh, so it never terminates. A double diverges from it at decimal 12 — 5.2e-13 rupiah — and that error then travels through every overtime multiplier in the rule set, across every overtime minute in the month, before anything is rounded. The amount is trivial. The problem is that nobody can tell you where it came from, and payroll is the one place where “close enough” is a conversation with a person about their rent.
So the engine keeps every intermediate exact and rounds exactly once, half-up to Rp1, at the payroll line — HALF_UP_RP1 in the rule set, never per minute and never per day. Each formula in the table above is that single rounding, and the amount beside it is its result.
Statutory deductions
Contributions first, then tax on a gross the contributions themselves changed. Both engines print their own arithmetic; neither of them is reimplemented above.
| CONTRIBUTION | EMPLOYEE | EMPLOYER |
|---|---|---|
| BPJS Kesehatan | Rp49.500 | Rp198.000 |
| JHT | Rp99.000 | Rp183.150 |
| JP | Rp49.500 | Rp99.000 |
| JKK | — | Rp44.055 |
| JKM | — | Rp14.850 |
| TOTAL | Rp198.000 | Rp539.055 |
JKK is priced by risk class — this employee is MEDIUM. Only the employee column leaves the payslip as cash; the employer column is paid alongside it, and three of its lines become taxable income below.
- K/2
- 2
- yes
- Rp11.706.369
- Rp11.600.001 – Rp12.600.000
- 3.00%
- 3.00%
- Rp351.191
Taxable gross is not the same figure as gross pay: JKK, JKM and BPJS Perusahaan are employer money that counts as the employee's income, while JHT Perusahaan and JP Perusahaan are excluded. That is the confirmed rule the composition function encodes, and it is why the number the tax is charged on is larger than the number in the earnings table.
PPh 21 itself is Indonesian public law. The effective-rate table shipped here is TARIF-PAJAK-2026-v1, and it carries its own note: “Company-configured PPh21 effective-rate table. Requires formal statutory verification before production payroll reliance. Float artifacts in the source normalized to 4-decimal rates.” The contribution rates, caps and floors in bpjs-v1-demo are the same — modelled on the legacy workbook and tracked as unverified. Every one of these was logged as pending confirmation with Accounting rather than quietly assumed correct, which is why the engine says so in its own output instead of leaving it to a comment.
Take-home
The end of the chain, and the only number on this page an employee would recognise.
- Gross earnings
- Rp11.449.464
- BPJS, employee side
- −Rp198.000
- PPh 21
- −Rp351.191
Rp10.900.273
This last subtraction is the only arithmetic on the page the engines did not do. In production it belongs to the orchestrator, along with the payslip, the approval chain and the bank file — none of which are here.
Rp4.375.000
THR uses basic salary only. Every allowance, bonus and overtime rupiah above is excluded from it, which is why a big overtime month does not move this figure at all.
The month grid belongs to this employee. Strict Start is on for his schedule, so an early tap is recorded where it happened and paid from 08:00 — and his August overtime crosses the 78-hour line, which is the one rule a visitor should watch fire.
- Rp25.000
- Rp23.000
- Rp15.000
- none
- K/2
- on file
- medium
- 0
- 2022-03-14
- 08:00–17:00
The day the month crosses 78 overtime hours — and it crosses in the middle of it. Overtime is walked chronologically, first hour before after-first, so this day's after-first bucket splits: most of it stays where it was earned and the last few minutes move to Bonus O/T. The day's own classification is never rewritten; the move is a payroll-level view of it.
- Indonesian PPh 21 and its monthly effective-rate method. Naming it discloses nothing.
- Operational, not statutory, and the employer is not named anywhere on this page or in the dataset:
- Saturday 5-hour shift credited as 7 regular hours
- Overtime meal threshold basis
- Sunday/red-day multipliers (2x / 3x / 4x)
- Monthly 78-hour overtime threshold
- Daily attendance bonus qualification
- The statutory rates, caps and floors were carried as pending confirmation with Accounting for as long as they were unconfirmed, and the rule sets say so in their own version strings and output. An unverified number that admits it is worth more than a confident one that does not.