Work · Products · 09
Witness
A photograph of a returned gearbox part becomes an ISO failure record linked to the batch that made it.
- Type
- Products
- When
- 2026 · shipped
- Role
- Sole designer and developer. Schema, tenancy, anomaly model, classifier, console, drawings.
- Scale
- Multi-tenant on Postgres with row-level security · two part families · twelve routes
In plain language
A returned gearbox part arrives with a complaint and almost nothing else. Someone photographs it, writes one sentence in a spreadsheet, and puts the part in a bin. When the same damage appears on a later batch, nobody can prove it, because the first record said "worn" and the second said "pitting" and neither cited a standard. Witness makes the record the product. A photograph becomes a record with a damage mode, a clause number, a severity, a cause and a link to the batch that made the part. The model suggests. The inspector decides.
The question
The failure was never the hard part. The record was. How do you turn a photograph and a one-line complaint into evidence a warranty claim can stand on?
Rough sketch
Where things sit on the screen, and in what reading order. Drawn, not screenshotted, so it stays true when the styling moves on.
Review queue
Finding detail
Insights cube
The record is the product
A returned part arrives with a complaint and little else. A person photographs it. A person writes one sentence in a spreadsheet. The part goes in a bin.
Months later the same damage appears on a different batch. Nobody can prove it. The first record said “worn”. The second said “pitting”. Neither cited a standard, so neither is evidence.
The failure was never the difficult part. The record was.
Two standards, three axes
Witness reads two part families, and each has its own published catalogue. Rolling bearings are classified against ISO 15243, which has six primary damage classes running from rolling-contact fatigue to fracture and cracking. Gear teeth are classified against ISO 10825, which covers the flank, the root and the mesh line.
Each finding carries a clause number, so it traces back to the document rather than to an opinion.
The third axis is attribution. A damage mode says what the surface looks like. An attribution names the mechanism that caused it. The same pit can be fatigue at end of life or a contaminant dent that started it early. The surfaces look alike. The fleet action does not, so the mechanism is recorded separately rather than folded into the mode.
Where the software stops deciding
The pipeline runs in stages and each stage does one job. Intake scores provenance and catches a duplicate. Enrolment learns what a good part looks like from the tenant’s own photographs. Stage one asks only whether a part is abnormal. Stage two picks one code from the catalogue.
Then the confidence gate. Above 0.70 the record files itself. Below it the record waits for an inspector. That gate is the single place where the software decides whether a person is needed, and the threshold is on the page rather than in a config file.
After the gate, the person decides. The system stores the model’s suggestion beside the human determination and never overwrites it. Analytics and warranty reports read the human call. A disputed classification stays auditable, which is the only reason any of this is worth storing.
The loop closes on the inspector
Every confirmed record becomes a pair: what the model said, and what the inspector said. That pair is the label export, and it is the training set for the version of stage one that does not exist yet.
This is stated as a limit rather than hidden. Stage one today is a memory bank of normal patch features scored by nearest-neighbour distance. It has the shape of PatchCore without the backbone, because torch does not fit inside a serverless function. A trained diagnostic model needs labelled data, and labelled data is what the review queue produces.
There is no synthetic defect data anywhere in the system. A generated defect teaches the model the generator.
Stack
Next.js on the App Router with React and Tailwind, Supabase for Postgres, row-level security and authentication, and a stateless Python function for the anomaly score. Stage two runs keyless by default on a deterministic heuristic and routes to a vision model when a key is present. The confidence gate decides where a finding lands, not the source of the suggestion.
How it is put together
Left to right, the path a request or a record takes through the system.
01
Intake
- EXIF read
- Provenance scored
- Perceptual hash against the tenant's assets
- A re-sent photo is caught as a duplicate
Before anything is believed.
02
Enrol
- The tenant's own known-good photos
- A coreset is trained from them
- Threshold calibrated leave-one-image-out
Per part family, per tenant.
03
Detect
- PatchCore-style memory bank
- Nearest-neighbour distance
- Anomaly score and heat map
- No database credentials in the function
Abnormal against normal, nothing more.
04
Classify
- ISO 15243 for bearings
- ISO 10825 for gear teeth
- An off-list code is discarded
- Confidence gate at 0.70
A forced choice, never free text.
05
Decide
- High confidence files itself
- Low confidence queues for review
- Inspector confirms or corrects
- Model suggestion kept beside it, immutable
The human call is authoritative.
06
Attribute
- Mode, severity and cause as three axes
- Finding linked to batch and supplier
- Warranty report
- Label export as model and human pairs
What happens, step by step
In plain language, in the order it happens.
Read the photograph before trusting it
EXIF gives a provenance score. A perceptual hash is checked against the tenant's other assets, so the same part photographed twice and sent twice is recognised rather than counted twice.
Learn what good looks like
Enrolment is the tenant's own photographs of undamaged parts, per part family. There is no synthetic defect data anywhere in the system, because a generated defect teaches the model the generator.
Ask only whether the part is abnormal
Stage one is a memory bank of normal patch features scored by nearest-neighbour distance. It answers one question. It does not name the damage.
Force the answer onto a published standard
Stage two picks one code from the part family's ISO catalogue. It cannot invent a code, because an invented one is discarded rather than stored. The finding carries a clause number, so it traces back to the document.
Send the unsure ones to a person
Above the gate the record files itself. Below it the record waits in the review queue. The threshold is visible on the page rather than buried in a config file.
Record the mechanism, not only the surface
The same pit can come from fatigue at end of life or from a contaminant dent that started it early. The damage mode looks identical. The batch-level action does not, so attribution is a separate axis.
Close the loop
Every confirmed record becomes a model-and-human pair in the label export. The training data is the inspector's work, collected as a by-product of doing it.
Decisions, and what they cost
Every choice worth recording has a road not taken. Both are here.
Classify against ISO 15243 and ISO 10825
instead ofA house taxonomy of damage names
A house taxonomy is a private opinion. A warranty claim needs a clause number from a published standard, and two inspectors using the same catalogue disagree far less than two inspectors writing prose. The standard also makes the record portable to a customer who never saw this tool.
The inspector's decision overrides the model, permanently
instead ofA model that improves itself on its own predictions
The system stores the pair and keeps the suggestion immutable. Fleet analytics and warranty reports read the human call. A disputed classification is therefore auditable after the fact, which is the whole reason the record exists.
A visible confidence gate
instead ofAlways route to a person, or never
Routing everything to review wastes the inspector on the obvious cases. Routing nothing to review files a guess as a finding. One threshold, shown on the page, is the only honest version.
A memory bank with cheap patch statistics
instead ofA deep backbone
Torch does not fit the size limit of a serverless function. The shape of PatchCore survives without it: a memory bank of normal features, a nearest-neighbour score, and a threshold calibrated leave-one-image-out. This is stated as a limit in the model card rather than dressed up as a diagnostic model.
ISA-101 discipline in the console, warm paper on the public pages
instead ofOne palette everywhere
A high-performance HMI keeps the ground near-neutral and spends saturated colour on abnormal. A marketing page has the opposite job. Two regimes, one product, and the console never borrows the brochure's colours to mean something operational.
Severity as a hue, a glyph and a numeral together
instead ofSeverity as a colour
Colour alone fails under colour-vision deficiency, greyscale printing and forced-colours mode, and a warranty report is printed more often than it is read on screen. One component enforces the pairing, so the rule cannot drift page to page.
One geometry module behind both the drawings and the app
instead ofHand-drawn figures next to live components
A figure drawn once and edited later disagrees with the software it documents, and nothing fails when it does. The bearing and gear drawings are generated from the same module the interface renders from.
What came out of it
- Live, multi-tenant, with row-level security scoping every row to the signed-in workspace.
- Two ISO catalogues classified against, with the clause number carried on the finding.
- Three axes per record: damage mode, severity, and the cause mechanism behind it.
- Findings link to batches and suppliers, and export as a warranty document or as JSON.
- The label export ships every confirmed record as a model-and-human pair, so the review queue is the training set.
- The anomaly function holds no database credentials. Feature vectors in, a score out.
Still open
- Stage one is a memory bank, not a trained diagnostic model. That upgrade is blocked on labelled data, which is exactly what the review loop collects.
- EfficientAD is out of scope on patent grounds, and the model card says so rather than leaving the omission unexplained.
Built with
- TypeScript
- Next.js
- React
- Supabase
- Postgres RLS
- Python
- NumPy
- Computer vision
- Vercel