draft-imran-systems-and-arguments-45Internet-Draft
← draft-imran-systems-and-arguments-45
Off the Bit Implementation 3.10 Category: System State: production Share: https://mosthofaimran.com/l/3-10
Attendance as a ledger, and the line it eventually bills

Attendance as a ledger, and the line it eventually bills

One append-only record of who was where, projected into payroll truth, a dispatch estimate and a liveness indicator, and carried through to the invoice line it justifies.

Stack

append-only ledger · projections · mobile capture · geofencing · payroll export · revenue attribution

Text

How to read this note. This is the reference design for turning field attendance into payroll and into billable lines: the constraints it operates under, the standard decisions, and the failure modes this shape has. It is a solution path for a system like the one built rather than a disclosure of that system's internals. Schema, table and field names, identifiers, client versions, thresholds, customer configuration and the internal document numbering are deliberately absent.

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

the phone writes events one ledger append only, never edited THREE PROJECTIONS, THREE QUESTIONS, NOT ONE ENUM attendance: payroll truth activity: dispatch estimate liveness: computed on read no sweep to fail locked day, payroll THE SAME VISIT, ON ITS WAY TO A BILL visit completed by one or several whose line is it? first match wins explicit, then who reported it done, then assigned credit splits, and sums to one a two-person job is worth one job Unmatched lines stay visible as unclassified. A line that cannot be attributed is a reporting gap somebody can close, and a line silently dropped is a number that lies. The console does not ship before the thing that produces its data, or it is a demo with a refresh button.
Figure 1. One writer, one ledger, three readers who disagree about what they are asking. The bottom half is the same day arriving at an invoice.

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.

Known failure modes

5.1, fixed. Overnight shifts split across a date boundary. Timestamps are stored in UTC and a working day is a local calendar day, so a shift beginning at nine in the evening and ending at five the next morning becomes two days of partial hours, or one day of zero. Payroll totals are wrong in a way that nobody notices until somebody is paid wrong. A shift inherits the day it opened on, which is the rule a human already uses when they say what day they worked.
5.2, fixed. A geofence that blocks rather than flags. Location drifts indoors, in basements and in steel-framed buildings, so a hard boundary check refuses a check-in from somebody standing at the site. The worker is then stranded at the start of their day with no way to proceed, which converts a data quality nicety into an operational outage. The check-in is accepted and marked for review, and integrity becomes a field on the record rather than a gate in front of it.
5.3, fixed. A scheduled job marking people offline. Liveness derived by a periodic sweep means the sweep is now load-bearing: if it stalls, everyone appears present forever, and if it runs against a stale clock, everyone appears absent at once. Both are system-wide and neither is visible in the data being swept. Liveness is computed at read time from the last heartbeat, which cannot fail separately from the thing asking the question.
5.4, fixed. Revenue double counted on crew work. When two people complete one job and each is credited with the line, the totals say twice the money was earned. Bonus schemes and utilisation reports built on that number reward sending more people, which is precisely backwards. Credit is fractional and sums to one, so a job is worth what it was worth however many people attended.
5.5, accepted. Unclosed shifts have to stop somewhere and every choice is wrong for somebody. Accruing to the present moment turns one forgotten check-out into forty hours of overtime. Discarding the shift loses a day somebody worked. It stops accruing at the rostered end, which is correct for the common case of a forgotten tap and wrong for genuine unrostered overtime, and it is visible as an exception so a person can restore what the rule removed.
5.6, open. Two reports can define the same word differently and both be internally consistent. If a utilisation view and an attendance summary each decide what counts as late, and one uses a grace period the other does not, they disagree about identical underlying records and both look correct to whoever built them. The answer is that a definition is a shared object rather than a constant in two files, and enforcing that across reports written months apart is a discipline nobody has automated here.
5.7, open. Attribution defaults quietly become measurement. The chain that decides who a line of work belongs to falls back through several rules, and the last of them was whoever created the record. In an operation where one office administrator enters most work, that rule attributes nearly all field revenue to a person who has never been to a site. It is disabled, which fixes the visible symptom and not the underlying one: a fallback that is wrong often enough to disable is a fallback that was answering a question nobody had asked.
ImranImplementation 3.10production