fix(labs): name a failed re-check as an error and close the last budget gaps

A re-check or reservation that failed itself stopped the scan as
"unavailable", and the review screen said AI had been turned off. It now
stops as stoppedBy error with its own message in every locale, and the
failure is reported as one: an error signal and the worker error
reporter. A scan that failed that way before any row answers 500
aiRuns.failed.

The overspend booked before a further read is now net of the cached
share, as the settlement books it. A reservation that names no known
owner stops the scan instead of taking the user plan's larger cap.

A page's text layer is cut at the last whole line; only a page whose
first line alone runs past the cap keeps part of it, cut at a space or
hard, rather than collapsing to the bare cut mark, and an endless line is
no longer read past the cap. The unused per-call text limit is gone. The
run result's description names every outcome a stopped scan can have.
This commit is contained in:
Marc-André Bombeck committed 2026-10-21 12:28:32 +02:00
1 parent e897549dff
commit 4c50f48b26
19 files changed
+279 -75

No files matched your search

+11 -4
View File
@@ -71322,12 +71322,14 @@ components:
stoppedBy:
description: "Present only when the scan stopped before its last read for a reason other than the provider: `budget`,
the day's AI budget ran out (every read after the first is reserved on its own); `unavailable`, AI was
turned off for the record while it ran (the wire is re-checked before every read). The pages it did not
reach are in `unreadPages`."
turned off for the record while it ran (the wire is re-checked before every read); `error`, that check or
the reservation failed itself, so the scan stopped rather than read on unchecked. The pages it did not reach
are in `unreadPages`."
type: string
enum:
- budget
- unavailable
- error
rowsCapped:
description: "Present only when the report held more readings than one scan carries (400): `rows` holds the first `kept`
of the `found` readings."
@@ -71350,13 +71352,18 @@ components:
- providerType
- rows
additionalProperties: false
description: The proposed rows for the human review screen, served by `GET /api/ai-runs/{id}` for a `LABS_OCR_EXTRACT`
description: "The proposed rows for the human review screen, served by `GET /api/ai-runs/{id}` for a `LABS_OCR_EXTRACT`
run. A PDF of more than one page is read a page at a time, each rendered page sent with its text layer as a hint
the image overrules, and the pages merged in order, a reading an earlier page already listed listed once. Never
written to the person's record; the commit route is the only write. A run in which no read produced a single
complete row fails with 422 `labs.ocr.outputLimit` when the reads stopped at the provider's output limit, else
`labs.ocr.extractFailed`; OCR'd text too long for the reads the deadline allows is refused up front with 422
`labs.ocr.tooLarge`.
`labs.ocr.tooLarge`. A scan stopped before any read produced a row answers with the stop: 429
`labs.ocr.budgetExceeded` when the day's budget refused a further read; the wire's own refusal (its status and
code, e.g. 403 `consent.ai.required`, `assistant.disabled.*`, `module.disabled`) when AI was turned off for the
record; 500 `aiRuns.failed` when the check before a read failed. A scan stopped after some rows succeeds with
them and names the stop in `stoppedBy` (`budget`, `unavailable`, `error`) and the pages it did not reach in
`unreadPages`."
OcrExtractedRow:
type: object
properties: