Debarred together by the African Development Bank in 2021 for fraud in tenders on a Kenyan education project. On a tender table, nothing connects the names. The affiliates were found by tracing common officers.
AegisVeritas
Two bidders. One owner.
A system that finds bidders in Kenyan public procurement that look independent but are linked to each other or to a debarred party, before the contract is signed. It reads open award, ownership and contact records, ranks supplier pairs by how likely they are to share an owner, and shows the evidence behind every flag. A person always decides.

What the committee can't see
A debarred company rarely bids under its own name. It bids through a clean-looking company that shares a director, owner, phone number or address with it.
A name check passes that company, because there is no name to match. Two "competing" bidders can also belong to the same person, so the committee compares them, picks the cheaper one and signs, and the competition was never real. Kenya's tender documents already ask bidders to declare common control, but an evaluation committee sees one tender at a time, and the links are spread across tens of thousands of firms.
of contracts went to firms that did not say who owns them: 14,819 of 25,994. When ownership is declared, it sits in records no committee can search across the whole market in the time it has.
What I built
A full system, not a notebook: a data pipeline over Kenya's public procurement records, a screening service, an evidence engine that retrains and gates itself, and an investigator-facing web app. It runs on a laptop-class machine with Docker Compose.
Bidder screening
Checks a supplier against sanctions and debarment lists, then follows owners and directors two steps out to find exposure a name check misses.
Review queue and evidence cards
A short list of supplier pairs for an investigator, each with its evidence, how strong that kind of evidence has proven, and what to check next.
Pre-award tender check
Takes the bidders on one tender and reports any links between them in seconds, the way Kenya's e-GP system would call it at bid opening.
Ownership path tracing
Draws the path from a supplier to the listed party on an investigation canvas.
Tamper-evident audit trail
Every screening, decision and tender check is appended to a SHA-256 hash chain. Changing any past entry breaks the chain, and the app can verify it.
Role-based access
Only investigator, auditor and admin roles see named pairs. Each institution sees only its own data, enforced by PostgreSQL row-level security.
My first approach failed
The proposal asked "is this one firm bad?" After the data was cleaned, only 18 confirmed Kenyan cases remained, and 11 of them had fewer than three recorded links.
No graph model beat a baseline that only looked at company size. The labels decide what is possible, so after a supervision meeting I changed the question to "are these two firms under one control?" For that question, the ownership details Kenyan awards must disclose give 12,441 labelled links. Kenya has no open ownership register to test hidden links against, so I also validated the method in Estonia, where both procurement data and beneficial ownership are public.
Three kinds of evidence, one probability
Candidate pairs are firms that win from the same buyer within 30 days, or that share a disclosed record. Each pair gets evidence from three channels, each a log-likelihood ratio, combined Fellegi-Sunter style so every part of the score stays explainable. A contact shared by many firms counts for less, and the behaviour model is capped so it can never outweigh an ID match.
How much each clue multiplies the odds that two firms share an owner
Starting weights in the evidence engine, log scale. They are replaced by weights learned from investigators' verdicts.
Show as table
| Evidence | Odds multiplier | Log-likelihood ratio |
|---|---|---|
| Same owner, matched by ID number | ×1,000 | 6.9 |
| Same director, matched by ID number | ×200 | 5.3 |
| Behaves like one company (behaviour model, cap) | up to ×55 | ±4.0 |
| Same phone or email | ×50 | 3.9 |
| Same address or P.O. Box | ×10 | 2.3 |
| Owner with the same name only | ×5 | 1.6 |
LightGBM on 20 award-pattern features, trained on Kenyan and Estonian pairs, with isotonic calibration. Calibration error 0.010. Thirteen models were compared; none beat it.
Silent on pairs where a firm has fewer than 3 awards, and on pairs unlike its training data. Refusing to judge is a result, not a failure.
A retrained model goes live only if it beats both the baseline and the current live model on a fixed holdout. The rule is in the code.
Results
Tests marked pre-registered had their protocol and pass mark committed to GitHub before the run, so the rules could not move after the results were seen. The baseline throughout is Adamic-Adar, the standard link-prediction score.
Estonia: firms the model never saw
AUC, with true owners hidden from the model and answers checked against Estonia's open register. 0.5 is a coin flip.
Show as table
| Method | AUC |
|---|---|
| AegisVeritas (LightGBM) | 0.85 |
| Adamic-Adar | 0.57 |
Estonia: the whole system
Share of all register-linked pairs found in the top 1,000, before and after adding register candidates to behaviour.
Show as table
| Top 1,000 | Earlier | Full system |
|---|---|---|
| Recall | 4% | 32% |
| Precision | 6% | 51% |
Kenya: held-back supplier pairs
Average precision on 7,884 pairs the model never trained on, only 178 truly linked. Reproduced on a full retrain.
Show as table
| Method | Average precision |
|---|---|
| AegisVeritas, Estonia + Kenya pooled | 0.146 |
| Kenya only | 0.144 |
| Adamic-Adar | 0.067 |
| Estonia only (failed) | 0.065 |
| Random (base rate) | 0.023 |
What a reviewer gets
Each square is one supplier pair. Filled squares are real links.
AegisVeritas top 100
25 linked
100 random pairs
about 2 linked
A register the model never saw
KeRRA, the rural roads authority, publishes contractor registers that list each firm's P.O. Box. The model flags pairs from ownership records only; the registers were never part of its data. This check was run after the detector was built.
209.9 times more often than same-buyer controls, and at least 63.8 times at the cautious end of the 95% interval (Fisher's exact test, p = 1.0e-42). Against random pairs from the same region: 118.6 times, at least 23.2 times (p = 2.7e-27). A shared box shows a relationship, not wrongdoing.
The Kenyan labels come from disclosed ownership, so the Kenyan result shows that award behaviour can recover links that were disclosed. Estonia's official register is what supports the claim for hidden links.
Reported in full
Failed and withdrawn results sit in the results ledger next to the ones that passed. They are the reason the passing results are worth believing.
A model trained on Estonia alone failed in Kenya
On the Kenyan holdout it scored 0.065 and 0.058 (raw and normalised), no better than the 0.067 baseline.
An outside test on road-contractor registers missed its pass mark
The pre-registered KeRRA test found no signal at its primary endpoint, twice: with the pair model and with the evidence engine.
My own safety gate caught a weaker model running live
The engine-2 model used importance weighting that was never tested, and a step-shaped calibrator.
A result I withdrew
An Estonia-to-Kenya AUC of 0.87 turned out to have read the synthetic sample market, not real data.
Every number rerun after submission
The submitted figures came from a run on an older database that still held duplicate company records. After a clean rebuild, concealed pairs went from 3,976 to 3,916 and the P.O. Box ratio from 242 to 209.9 times. The submission is tagged in git, and every run is kept.
Built to be trusted
Because it names firms, the system has to be correct, explainable and hard to misuse. The tests, timings and design choices below come from the technical report.
| Workload | Result |
|---|---|
| API calls on real data | median 1 to 7 ms |
| Screening a company | median 36 ms |
| Tender check, 2 bidders | median 70 ms |
| Full retrain and scoring, 1.44 million pairs | 3 min 45 s |
| Review queue on a slow phone | 2.6 s |
| Algorithm | Why |
|---|---|
| Breadth-first search | Shortest path to a listed party, skipping buyers |
| Union-find | Firm groups, so no group crosses the holdout |
| Sliding time window | Candidate pairs: same buyer within 30 days |
| Fellegi-Sunter fusion | Combine evidence, keep each part explainable |
| SHA-256 hash chain | Tamper-evident audit log |
Seven data connectors implement one abstract connector; two risk models share one interface, so the graph model can be swapped for rules. The screening orchestrator depends on interfaces, not on Neo4j or PyTorch, so a baseline can replace the model with no other change.
A person always decides, as Kenya's Data Protection Act requires. The API serves links by kind and date, never people's names. Firm names can be masked as stable codes for recordings. The system is not on the open internet, because its findings name firms that have not been found to do anything wrong. Invited evaluators reach it through Cloudflare Access, behind a one-time code and an app account.
The product
The review queue, evidence card and e-GP screens run on real records with firm names masked as stable codes. The screening, investigation and audit screens use an invented demo company, and the people named in them are invented too.





The system is invite-only. Evaluators and recruiters can ask for a guided session.
Request accessWalkthrough
A recorded tour of the system: screening, the review queue, an evidence card and the pre-award tender check.
A reason to look, not an accusation
Hidden links in Kenya are not yet proven: there is no public register to check against. 75 of the top 100 Kenyan pairs are not confirmed links; some may be hidden links and some will be innocent. Pairs where a firm has fewer than 3 awards (770,624 candidates) are not scored. Suppliers are matched by normalised name, so two different firms with the same name would be treated as one, and entity resolution precision has not yet been measured on a hand-checked sample. A 9,000-firm stress test ran out of memory, so scoring moves to batches next.
Invite-only access has been live since 28 October 2026. Expert evaluation is under way: practitioners complete guided tasks in the app and a questionnaire that includes the System Usability Scale, under ALU ethics approval SEPT26-BEL-070. Next is a pilot with an organisation that works with procurement authorities.
When the public pays for competition, it should get competition.