Workforce Early-Warning System
An early-warning system for burnout and resignation. It reads the HR records a company already keeps, finds the patterns that come before somebody breaks down or leaves, and raises them while there is still time to help.
- Role
- AI Engineer
- Year
- 2026
- Status
- Pre-production
- Tests
- ~1,350, all offline
Most large employers run their HR on a system called Workday. It is the filing cabinet for everything about a job: who works here and who they report to, hours logged, holiday booked and taken, sick days, pay and pay reviews, performance ratings, training completed, contract end dates, visa expiries. Almost none of that is read forward. It is used to count what already happened — to run payroll, to close the quarter, and occasionally to build a case about somebody after the fact.
This system reads the same records the other way round. Take the founding example. Somebody takes a sick day, or a sick week, roughly every three months. Ordinary absence management counts the days, notices the total has crossed a line, and schedules a conversation about attendance. Read forward, that regularity is the interesting part: a body claiming the recovery the calendar never gave it. The gap between episodes is stable enough to take a median of, which means the next dip has an approximate date, which means somebody can offer a break before it lands rather than a warning after it.
The same method is applied to every part of working life the HR system can see — starting a job, growing in it, being paid for it, being recognised for it, and leaving it. Thirty-one patterns in all. Each one names a thing that tends to precede a bad outcome, computes the date the outcome is expected, and surfaces it early with the evidence attached.
The obvious objection is that a system watching individual employees this closely is one bad decision away from being a surveillance tool. That objection shaped the architecture more than anything else. The engine cannot act. It has no write path back into Workday, no drafting layer, no send button — it detects, scores, and displays, and every decision about what to do belongs to a person. Aggregates below five people are refused outright, the manager-facing copy is physically stripped of anything medical, and none of that is a policy document. It is the code.
Architecture
The engine is Python with no third-party runtime dependencies at all — the standard library and nothing else. That was a deliberate constraint rather than an aesthetic one: this is software that reads an entire company's HR records, and every dependency it does not have is a supply-chain question nobody has to answer before it is allowed near a real tenant.
Getting the data out of Workday takes three protocols, because no one of them can see everything. Modern REST endpoints cover most of the day-to-day records. A much older SOAP interface is the only route to manager chains, compensation, leavers, contingent contract dates and visa expiries. A query language called WQL fills the remaining gaps, mostly benefits and tenure cross-checks. The engine speaks all three behind a single interface, so the rest of the code never learns which protocol a fact arrived on — and a fourth implementation of that same interface is a fake tenant, which is why every test runs offline with no Workday, no network and no credentials.
What comes back is normalised into plain records and stored as a dated snapshot. Detectors then read the snapshot and emit signals. A signal is not a score on its own: it carries the evidence that produced it, a confidence, and where the maths allows one, a horizon — the computed date the predicted problem lands. Signals that describe a condition true right now are labelled differently from signals making a dated claim about the future, because only the second kind can be graded later.
The layer that makes it more than a set of thresholds is the personal baseline. A fixed rule that fires at 45 hours a week is wrong for almost everybody: it is a crisis for one person and an ordinary Tuesday for another. So every worker's metrics are resampled into a per-metric time series, and a robust baseline — median plus median absolute deviation, which a few extreme weeks cannot drag around — learns what is normal for that person specifically. A detector family fires on sustained deviation from somebody's own history rather than from a constant everybody shares. On top of that, changepoint detection turns "a threshold was crossed" into the more useful "this person's hours changed regime on the third of March".
Because the forecasts carry dates, they are falsifiable, and the engine is made to grade its own homework. Every cycle scores the predictions that came due — the sick dip either happened or it did not, the flight-risk person stayed past their anniversary or left — and prints a running record. That tally is the honest evidence, and it is also the tuning signal that catches a wrong detector before intuition does. Raw detector confidence is not shown as a probability until it has been mapped through an empirical, confidence-bounded calibration; below twenty observations an insight says it does not have the evidence yet instead of inventing a number.
No change to a detector or a model can ship on the strength of looking better. Every change replays against stored history and has to beat the incumbent on precision, Brier score and lead time before it is allowed in. The predictive models — discrete-time hazard models for attrition, sustained sick absence, probation non-completion and contract non-renewal — each carry a model card and a fairness audit, and each refuses to take over from the simpler rule-based logic until it has earned it on that same replay. When a model does run before it has earned the switch, its output is tagged in the interface as shadow: shown, but not deciding anything.
The privacy floors are structural. No aggregate over a team, manager, region, cost centre or location is computed below five people — below that it reports a count and nothing else, so a number can never be arithmetic away from naming a person. Location is off by default on top of that, because a small office is identifying on its own. The person a signal is about always sees the whole thing; the copy their manager sees goes through a redaction step that removes absence type, medical adjacency and the raw pattern parameters, so a manager is told somebody could use support without being handed a medical inference. Protected and parental leave never map to sickness anywhere in the signal path. Engagement-survey data has no per-worker field in the data model at all — not a rule about it, an absence of anywhere to put it.
Failures are loud in a specific way. If a data source degrades, the cycle does not crash and it does not quietly return a healthy-looking workforce: the gap is listed in the brief as a gap. Snapshots also record how much of the workforce was actually pulled, and anything reading history compares like with like — a run capped at 150 of 791 workers used to leave evidence indistinguishable from a genuine 150-person company, which is exactly the kind of silence that gets mistaken for good news.
Platforms used
Engine
- Python 3.11
- Standard library only
- No runtime dependencies
- Protocol seams + offline fake
Workday integration
- REST
- SOAP (Workday Web Services)
- WQL
- OAuth token refresh
- Model Context Protocol
Detection & modelling
- Median + MAD baselines
- CUSUM changepoints
- Discrete-time hazard models
- Wilson-bounded calibration
- Cohort z-scores
- Backtest ship gate
Console
- React 19
- TypeScript
- Tailwind CSS
- Recharts
- Playwright smoke suite
Assurance
- ~1,350 offline tests
- Model cards
- Fairness audits
- Schema-fidelity tests
- Ruff
Features
-
Thirty-one patterns across the whole of a job
Ordered the way a working life runs. Joining: a new starter with no goals, no training and no check-in booked; a probation milestone approaching with nobody prepared for it. Wellbeing: sick leave on a stable cycle; a large holiday balance nobody is spending, sharpened when requests were actually refused; hours climbing towards a computable crossing date. Growth: months with nothing appreciative on record; a promotion that happened and was never publicly acknowledged; declining ratings and stalled training together, surfaced before any formal capability process starts. Leaving: a contract ending in six weeks with no decision made; a first-year leaver whose sign-on repayment terms nobody has checked. Each one names the supportive move it points a human towards, and stops there.
-
Baselines that belong to a person
The unit of comparison is somebody's own history, not a company-wide threshold. Every metric the system tracks — hours, absences, recognition, position in a pay band — becomes a per-person time series with a robust baseline underneath it, so the same 46-hour week reads as a warning for one person and as unremarkable for another. Deviation is measured in how far from normal it is for them, and changepoint detection dates the moment the pattern shifted rather than the moment a line was crossed.
-
Forecasts with dates, graded in public
A prediction that cannot be wrong is not worth much. Signals that make a dated claim are scored the moment they come due, held or missed, and the running record sits in the interface as a scorecard rather than in a slide. It doubles as the tuning signal: a detector that keeps missing shows up as a row of misses long before anybody develops a hunch about it.
-
A gate that changes have to pass
Detector constants and models cannot be improved by assertion. A proposed change replays against stored history and must beat what it replaces on precision, calibration and how much warning it gives, or it does not ship. A separate loop sweeps the tunable constants against that same history and prints the settings that measurably win — and then applies none of them. Constants stay changeable only by a reviewed change, so the engine can suggest its own tuning without ever performing it.
-
Privacy enforced by the data model
No aggregate below five people, anywhere, for teams, managers, regions, cost centres or locations — and locations are off unless somebody turns them on, because a small site identifies people by itself. The employee sees their full insight; the manager copy is stripped of medical adjacency and raw pattern detail before it is rendered. Survey data has no per-person field to populate. A coverage test fails the build if a new detector is added without declaring which domain it belongs to and who is accountable for acting on it.
-
Surfacing, not deciding
The engine has no path that writes to Workday, no drafted messages and no send. That is the whole safety argument: not that it has been configured carefully, but that there is no trigger in it to pull. If a signal is ever to become an action — a booked conversation, a time-off request — that happens in a separate downstream system, and the one export boundary is an explicit, redaction-applied handoff that is switched off by default and carries its terms in the payload rather than in a policy document.
-
A monitoring console that opens on nobody
The dashboard lands on a company-wide view with no individual on it at all: how many people could use support, how that has moved, org-level health across wellbeing, delivery and citizenship, and what changed since the last run summarised by kind of signal rather than by name. Individuals appear in exactly two places, both of them deliberate — after drilling into a specific signal, or after searching for somebody. The health scores also have a qualitative counterweight: an engagement survey read, compared against the numbers at the same level, flags where the data looks fine and how people actually feel does not agree.
-
Loops that watch the engine itself
A backtester nobody runs is decoration. Each cycle checks its own models against their model cards — predicted-versus-actual calibration, and a fresh walk-forward score — and raises a retraining recommendation when either drifts. Nothing retrains itself; the recommendation is the output. Effort is spent where the uncertainty is, with full data pulls for anyone carrying an active signal or newly hired and thinner pulls elsewhere, and exactly what was skipped is recorded so a person who was deferred never reads as a person about whom there is no news.
Screens
These screens run against a synthetic tenant of 3,000 generated workers — every name, team and signal in them is fabricated test data, not a real person. Employer branding has been removed.