Attendance as a ledger, and the line it eventually bills
Stack
append-only ledger · projections · mobile capture · geofencing · payroll export · revenue attribution
Text
This note publishes no measurements, and section 4 names the ones that would matter.
1. The constraint
Three consumers want to know where somebody was, and they do not want the same answer.
Payroll wants a defensible record. It is the input to money paid to a person, it is retained for years, it will be produced in a dispute, and it must never change after the fact without the change itself being recorded.
Dispatch wants a working estimate, right now. It is deciding who to send, and an answer that is approximately right immediately beats an answer that is exactly right in an hour.
The screen wants to know whether a person is currently connected, which is neither of those. It is a property of the last few minutes and it is worthless tomorrow.
One field cannot hold all three. The instinct is a single status enum with values like present, en route, on break and offline, and it fails on contact: a person can be rostered and absent, or absent and connected, or working and unreachable. Every combination is real and an enum admits one axis.
The second constraint arrives at the other end of the pipeline. A billed line has to be attributable to a person, because that is what makes commission, utilisation and bonus schemes possible, and the attribution must be correct when several people did the work and honest when nobody knows.
2. The decisions, and where each is enforced
2.1. One append-only ledger, not a table per event type. Check-in, break, shift end and location are one kind of fact: something happened to somebody at a time. Split across separate tables they are a join every time anybody asks a real question, and the retention rule, the audit rule and the correction rule have to be implemented four times and will diverge. One ledger means the record is enforced at write: nothing is edited, corrections are new entries, and the audit trail is the storage rather than a feature added to it.
2.2. Three axes as separate fields. Attendance, activity and liveness answer different questions over different time horizons and are allowed to disagree. Collapsing them into one status is the decision that looks tidy on day one and produces a report nobody can explain in month six.
2.3. Liveness is computed at read time. Derived from the last heartbeat when somebody asks, rather than written by a job that marks people offline. A periodic sweep becomes a single point of failure for a fact that is not worth one, and its failure mode is silent and total. Nothing computed on read can be stale in a way the reader cannot see.
2.4. UTC in storage, local days in logic. Every timestamp is stored in one zone and every business rule runs on the calendar day a human would name. A shift belongs to the day it opened on, which is what somebody means when they say what day they worked, and which stops an overnight shift becoming two days of partial hours.
2.5. Attribution is an ordered rule with fractional credit. Explicit assignment first, then whoever reported the work finished, then whoever it was assigned to. First match wins, the rule that fires is recorded so the answer can be explained, and when several people attended the credit divides and sums to one. A line nothing can attribute is surfaced as unclassified rather than dropped, because a visible gap is a task and a silent one is a wrong total.
2.6. Integrity is a flag, never a gate. Location that looks wrong marks the record for review and lets the person start their day. Anything that can refuse a check-in will eventually refuse a correct one, and the cost of that is a worker standing outside a building unable to work.
3. Why this is a product decision rather than plumbing
The reason to build the ledger properly is that three departments are going to argue about it, and the argument will be settled by whichever number is easiest to produce.
Payroll disputes are the sharp end. Somebody says they worked a day the system does not show, and the answer has to be a record with a history rather than a current value, because a current value invites the question of what it used to be. An append-only ledger answers that by construction, which is worth more than any feature built on top of it.
The attribution half decides whether the operation can pay people for outcomes. Commission, utilisation and bonus schemes all need to know whose work produced a number, and every one of them is corrosive if the attribution is wrong: a scheme that rewards crew size, or that credits an administrator for field work, changes behaviour immediately and in the wrong direction. A measurement that determines pay is not a report. It is an incentive, and it is worth being slower and more explicit about than anything else in the pipeline.
There is a sequencing rule underneath all of it that generalises past this system. The console does not ship before the thing that produces its data. A live operations view built ahead of the mobile client that writes the events is a demonstration with a refresh button, it will be evaluated as the product, and it will be judged as broken by the first person who compares it with the yard.
4. Figures
This note reports none. The four that would say whether it works are the proportion of shifts closed by a person rather than by the rostered-end rule, the rate of integrity-flagged check-ins and how many survive review, the share of billed lines that reach an attribution rule other than the fallback, and the count of payroll disputes resolved by pointing at the ledger.
The third is the one I would want first. If most lines are attributed by the last rule in the chain, the ordered rule set is decoration and the attribution is a guess with a procedure in front of it.
5. What I would do differently
Define the vocabulary once, in one place, before the second report is written. Failure 5.6 is open and it is the one that erodes trust rather than uptime: two views that disagree about what “late” means are both correct and the operation stops believing either. A shared definition object that every report must consult is cheap at the start and a migration later.
Require the roster before enabling attendance, in the product rather than in the documentation. Exceptions are differences from an expectation, so a deployment without scheduling produces false lateness from the first hour, and the feature is rejected before anybody sees it work. This is stated as a prerequisite and would be better as a gate.
Never let an attribution fallback stay on by default. Failure 5.7 is disabled rather than fixed. The deeper lesson is that the last rule in a chain answers a question nobody asked, and a rule that has to be switched off in production is evidence that the chain should have ended one step earlier and returned nothing.