The method
How a clip is judged, and what a partner is told
A clip is accepted, not accepted, or still waiting to be reviewed, and the Capture Partner who recorded it reads which of the three it is. The reasons a clip can be turned down are a closed list of nine, held in one table in the engine. This page explains what that list is for. It does not keep a second copy of it, and the second section says why.
The verdicts
A clip carries one of three verdicts and there is no fourth: accepted, rejected, pending. The app prints a rejected clip as NOT ACCEPTED, and says of a pending one that it is waiting to be reviewed. Waiting is a real state rather than a gap. A partner's clip list is built by joining what they sent to what has been judged, so a clip nobody has looked at yet still appears, marked as waiting, instead of dropping out of their record.
Two paths write a verdict today with nobody involved. The first is a single-shot upload route, kept for small files and for the test harness and explicitly not for field capture: it runs one decode pass over the file — mean sharpness, mean brightness, and whether the decode reached the last frame — and records an accepted verdict when all three clear their thresholds. The second is segmentation, and it is the one a Capture Partner's Recording actually meets: as it cuts a Recording into Clips it records an accepted verdict against every Clip it cuts. A third writer, the old standalone QA stage, is kept reachable so that an older job row does not crash the queue, and it sits in neither live sequence.
That second path decides what the word accepted means on most rows, so it is worth being exact. The verdict segmentation writes carries no rejection reason, and it is written at the moment the Clip is minted, before anything else runs on it. It records that the cutter judged that stretch of footage usable. It does not record that a person watched it, and it does not record that anyone found it good enough to hand to a Commissioning lab.
A span the cutter judged unusable never becomes a Clip, so it never gets a verdict row at all. Its reason goes into the segmentation summary saved beside the Recording, in a separate vocabulary of five values, of which exactly one name — low light — also appears among the nine. The nine govern clips. The five govern spans. They are not the same list, and a count taken from one does not describe the other.
A verdict can be replaced. Recording a second verdict against the same clip id overwrites the first, deliberately, because a re-review supersedes an earlier judgement and QA is an operational measurement. The consent ledger is the record that keeps every entry it was ever given; this one keeps the current answer rather than the sequence of answers that produced it, and that limit is real.
The nine rejection reasons
Nine reasons exist and the list is closed. A reason outside it cannot be recorded, which is what makes a breakdown countable: a rejection rate on its own tells us nothing we can act on, and the same rate describes completely different problems depending on which of the nine dominates.
They divide three ways, three each. Three can be settled without a person. Two of those are arithmetic — a mean Laplacian variance under 80 reads as blur, a mean grey level under 30 on the 0–255 scale reads as too dark — and the third asks only whether the decode reached the last frame. Three need a person, because no threshold decides whether both hands stayed in shot, whether the briefed task is the one that was performed, or whether the action was complete. The last three name a failure on our side of the line rather than a judgement about the recording.
The nine live in exactly one place: a table in the engine, where each reason carries the label a Capture Partner reads and the single line telling them what to do differently. This page used to stop there and say so — a hand-typed copy would be a second source for a fact the engine owns, and on the day somebody adds a tenth reason or rewords a line of guidance it would carry on telling a partner something the app no longer says. The condition it set was a build step that reads that table and a check that fails when the two disagree. Both exist now, so the list is printed below: the same rows, in the same words, generated from the engine on every build. Add a reason there and it appears here; change one and a push is refused until they agree again. The one field the engine holds that is not shown is our own internal note on what each reason means for us to fix — it is not what a partner reads, and it is excluded by the generator rather than by anyone remembering to leave it out.
| Reason | Decided by | The gate | What the partner is told |
|---|---|---|---|
| Motion blur or soft focus | A measurement | laplacian_variance_blur < MIN_LAPLACIAN_VAR_BLUR (80.0) | Move your head more smoothly and pause briefly at the start of each action. |
| Scene too dark | A measurement | avg_lux_level < MIN_LUX_LEVEL (30.0) | Record near a window or switch on more lights before you start. |
| Dropped or corrupted frames | A measurement | dropped_frame_percent > MAX_FRAME_DROP_PERCENT (5.0) | Close other apps while recording and make sure the phone is not overheating. |
| Hands or task out of frame | A person | no threshold — it is a judgement | Keep both hands and the object visible for the whole action. |
| Not the task that was briefed | A person | no threshold — it is a judgement | Re-read the brief before recording; record the specific task described. |
| Action starts or ends mid-way | A person | no threshold — it is a judgement | Start recording before you reach for the object and stop after you have set it down. |
| No valid consent receipt | The pipeline | no threshold — it is a judgement | Consent is recorded in the app before you record. Contact us if you see this. |
| Duplicate of an existing clip | The pipeline | no threshold — it is a judgement | Each submission should be a separate recording. |
| File unreadable | The pipeline | no threshold — it is a judgement | Re-upload the clip. If it fails again the recording itself is damaged. |
Only three of the nine are ever written by code, and they are the three that decode pass can derive. Nothing in the engine assigns the other six anywhere; they arrive only when a person records one by hand. One of them cannot arrive at all today — a duplicate upload is caught at ingestion by content addressing, which derives the same identifier from the same bytes and reports a duplicate instead of writing a second row, so nothing is ever recorded against the partner for it.
Each of the nine carries a third field beside those two. It is written for us rather than for the partner, it names something at our end, and it stays internal. The engine builds the partner-facing version out of the label and the guidance alone, by construction rather than by remembering to remove the third field at each point of use. This page does not print it either.
What a Capture Partner sees
The signed-in app lists the clips a Capture Partner sent, newest first, each with its clip id, the day it went and the brief it was recorded against. Two limits are worth naming: the read is capped at five hundred rows, and a clip posted through the single-shot route never appears at all, because that route mints its own identifier and registers nothing in the provenance table this list is built from. A rejection renders as two lines, the label and then the guidance, so it reads as instruction rather than as a verdict to absorb.
Two internal fields stop at that boundary. The reviewer's free-text note is written for a colleague, and a note about a person is not feedback to them. The taxonomy's third field is ours to carry. The engine removes both in the controller rather than in the store, so our own internal book reads the same rows and still sees the full record. Two independent suites assert the removal, and the browser one feeds a response carrying both fields on purpose — proving the screen would not render them even if the engine sent them.
The acceptance figure on that screen arrives as nothing at all until at least one of that partner's clips has been judged, by the automatic pass or by a person, and the branch that handles nothing runs before any arithmetic. A partner with five clips still waiting has no rate. Printing nought per cent would tell them they had failed five times. Clips still waiting count neither way: they cannot raise the figure and they cannot lower it. Where a rate does appear it is labelled derived, not measured.
Each clip in that list carries a control that re-runs the automatic pass over that one clip and hands back its sharpness, brightness and decode figures. It answers the question a turned-down partner actually has — how far off was this — which a category on its own does not. A partner can reach only their own clips, and asking for somebody else's returns a not-found rather than a refusal, because a refusal would confirm the clip exists.
The human hold
A Recording the cutter reduced to zero usable spans is not settled. The job moves into a state that is deliberately not terminal, carries no completion time, and stays counted as in flight. Nothing accrues against it, because an entitlement is derived only from accepted clips and there are none to derive from.
The reason is measurement rather than sentiment. A fault in the cutter and a genuinely unusable Recording look identical from outside; the second is rare, and the first is exactly what an unvalidated cutter does when it breaks. Review is how we find out which one we are looking at. The cutter also refuses rather than reporting zero when it cannot run at all — the failure propagates and the stage retries — so a crash never presents itself as an hour of somebody's work being worth nothing.
Redaction runs before this point. Anyone who reviews the footage therefore reads the redacted copy, and if that copy is missing the stage raises an error and says to re-run redaction rather than reaching for the original.
What is recorded and what is worked are two different things today. The held state lives in the pipeline store and no screen anywhere in the web suite lists it. The queue somebody would have to clear is not built. Stated plainly, because it is the part that costs: that queue sits on the path to paying people.
Who decides a clip that is questioned
One route lets a person write a verdict, and it requires an admin or reviewer role. It is how the three reasons no machine can judge get recorded, and how an automatic verdict is corrected. Recording a second verdict against the same clip id replaces the first.
A Capture Partner raises a clip from the app itself. The help panel carries a category reading “A clip was not accepted and I think that is wrong”, with a field for the clip id — which exists because that message is unanswerable without knowing which clip. Identity comes from the sign-in token rather than from anything typed into the form, so the message attaches to a real account and a name or address in the body is ignored.
No control joins that message to a re-review. Nothing reopens a verdict from a message, and no route a partner can reach changes one. The path from an objection to a different outcome runs through a person reading the message and recording a new verdict by hand. We would rather write that down than give the route a name that implies machinery we have not built.
One thing here is owed rather than built. We have chosen automated QA as the thing that measures how much of a Recording is usable, and that choice carries a condition we have not met: a derived measure that sets what a person is paid needs a documented override, with a record of who overrode it and why. That override is not built. Neither is the model behind the measure — today's automatic pass is arithmetic over frames in OpenCV and nothing else.
What the acceptance rate needs before we publish one
We publish no QA acceptance rate, and two separate things would each be enough on their own to stop us. The first is our own provenance rule: a figure may be called measured only when it comes from a committed artifact, and QA verdicts are written to a runtime database that is not in version control. No number computed from that file can be published here, whatever it happens to say on any one machine. The second is that there is nothing yet to measure. An acceptance rate is a statement about a cohort recording against a commissioned brief, and no cohort has.
The code is built so that a zero cannot escape by accident. With no verdicts to count, the report returns no rate at all and labels itself none rather than measured; the partner's own screen carries the same branch, and it runs before any arithmetic. That distinction is doing real work here, because a zero would be a claim about work nobody ever judged.
There is a second reason to be careful with any rate this store ever produces, and it is a property of the code rather than of today's contents. A verdict written automatically counts toward the rate in exactly the same way a reviewed one does, and every verdict segmentation writes is an acceptance. So a rate computed over automatic verdicts alone would report how much footage the cutter kept, and would report it under the name of an acceptance rate. Any figure we eventually publish has to say which verdicts are underneath it.
The machinery around the number exists and the number does not. When a rate is computed it carries a 95% Wilson score interval, and a flag stays false until that interval narrows to five percentage points either side or better — five, because the band we are trying to locate ourselves in is twenty points wide, and anything looser cannot tell its two ends apart. Wilson rather than the normal approximation, because the normal method reports a zero-width interval when ten clips out of ten pass. Certainty, from ten data points, is the most dangerous output the function could produce. Wilson on the same ten returns 72.25% to 100%, give or take 13.88 points — still nearly three times too wide to price against.
Until 12 August 2026 this codebase answered the question with a hardcoded 94.8%, and it stood in three tracked places: the value the admin service returned, the example in the public API schema, and the figure rendered on our console. It was indistinguishable on screen from something we had measured. A computation that returns nothing when there is nothing to compute replaced it, and the number survives now only in comments recording what it used to be.
The three automatic gates measure less than their names suggest, and each limit sits where the figure does. Sharpness and brightness are means over an evenly spaced sample of frames rather than over every frame of a long file: the step is the frame count divided by fifty, so a long recording contributes about fifty samples and one under a hundred frames contributes all of them. The brightness gate reads a mean grey level on the 0–255 scale; the field it is stored in is named for lux, and no illuminance is measured anywhere in this pipeline. The dropped-frame check is the weakest of the three. It registers that a decode stopped before the last frame rather than counting scattered drops through a file, it counts such a stop once, and its threshold of five per cent can therefore be crossed only by a recording shorter than twenty frames. On anything longer, a decode that stops early passes. That is a defect in the counter rather than a property of the footage.
Producing the rate needs a Capture Partner cohort recording against a commissioned brief. Until that exists the pilot price stays unposted, because an acceptance rate nobody has measured prices nothing.
none Acceptance rate. No cohort has recorded against a commissioned brief, so there is no denominator. This is a real zero, not a missing number.
none Review window. A verdict records when it was made. Nothing computes how long a clip waited for one, so we cannot tell you a turnaround.