mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fixes ocr for newer open ai models
This commit is contained in:
1 parent
320788a984
commit
6ab3b02935
5 files changed
+49
-11
No files matched your search
@@ -136,6 +136,16 @@ describe("POST /api/labs/ocr/extract — text mode budget", () => {
|
||||
"2026-06-26",
|
||||
);
|
||||
});
|
||||
|
||||
it("identifies provider failures instead of blaming image quality", async () => {
|
||||
vi.mocked(runOcrExtraction).mockRejectedValue(new Error("provider down"));
|
||||
|
||||
const res = await POST(textReq());
|
||||
const body = (await res.json()) as { error: string };
|
||||
|
||||
expect(res.status).toBe(502);
|
||||
expect(body.error).toContain("configured AI provider");
|
||||
});
|
||||
});
|
||||
|
||||
describe("POST /api/labs/ocr/extract — vision PDF rasterization", () => {
|
||||
|
||||
@@ -63,6 +63,25 @@ const EXTRACT_WINDOW_MS = 60 * 60 * 1000;
|
||||
/** OCR'd text is bounded in the schema; cap the JSON body proportionally. */
|
||||
const TEXT_BODY_MAX_BYTES = 512 * 1024;
|
||||
|
||||
function providerFailureMeta(error: unknown, mode?: "text") {
|
||||
const err = error as {
|
||||
httpStatus?: unknown;
|
||||
model?: unknown;
|
||||
bodyExcerpt?: unknown;
|
||||
};
|
||||
return {
|
||||
reason: "provider_error",
|
||||
...(mode ? { mode } : {}),
|
||||
...(typeof err.httpStatus === "number"
|
||||
? { upstreamStatus: err.httpStatus }
|
||||
: {}),
|
||||
...(typeof err.model === "string" ? { model: err.model } : {}),
|
||||
...(typeof err.bodyExcerpt === "string"
|
||||
? { upstreamError: err.bodyExcerpt }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
export const POST = apiHandler(async (request: Request) => {
|
||||
const { user } = await requireAuth();
|
||||
|
||||
@@ -191,11 +210,13 @@ async function handleTextExtract(
|
||||
}
|
||||
annotate({
|
||||
action: { name: "labs.ocr.extractFailed" },
|
||||
meta: { reason: "provider_error", mode: "text" },
|
||||
});
|
||||
return apiError("Couldn't read the report. Try a clearer photo.", 502, {
|
||||
errorCode: "labs.ocr.extractFailed",
|
||||
meta: providerFailureMeta(err, "text"),
|
||||
});
|
||||
return apiError(
|
||||
"The configured AI provider could not process this report. Check the provider configuration and retry.",
|
||||
502,
|
||||
{ errorCode: "labs.ocr.extractFailed" },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,11 +418,13 @@ async function handleVisionExtract(
|
||||
}
|
||||
annotate({
|
||||
action: { name: "labs.ocr.extractFailed" },
|
||||
meta: { reason: "provider_error" },
|
||||
});
|
||||
return apiError("Couldn't read the report. Try a clearer photo.", 502, {
|
||||
errorCode: "labs.ocr.extractFailed",
|
||||
meta: providerFailureMeta(err),
|
||||
});
|
||||
return apiError(
|
||||
"The configured AI provider could not process this report. Check the provider configuration and retry.",
|
||||
502,
|
||||
{ errorCode: "labs.ocr.extractFailed" },
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// A guard threw after the reservation (e.g. consent races) — refund fully.
|
||||
|
||||
@@ -37,6 +37,9 @@ describe("supportsVisionForConfig", () => {
|
||||
] as const) {
|
||||
expect(supportsVisionForConfig(providerType, "gpt-4o")).toBe(true);
|
||||
expect(supportsVisionForConfig(providerType, "gpt-4o-mini")).toBe(true);
|
||||
expect(supportsVisionForConfig(providerType, "gpt-5.6-terra")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(supportsVisionForConfig(providerType, "gpt-4.1")).toBe(true);
|
||||
expect(supportsVisionForConfig(providerType, "gpt-4-turbo")).toBe(true);
|
||||
// Full o-series reasoning models read images.
|
||||
|
||||
@@ -189,13 +189,14 @@ export const AI_BUDGETS = {
|
||||
* v1.18.9 — Lab-OCR structured extraction. ONE vision call transcribes a
|
||||
* photo / PDF of a paper lab report into a JSON array of analyte rows
|
||||
* (analyte + value/valueText + unit + reference range + date + per-field
|
||||
* confidence). A full panel is a dozen-plus rows of compact JSON; 4000
|
||||
* tokens covers a dense report plus the envelope. Temperature 0 — the task
|
||||
* confidence). A full panel is a dozen-plus rows of compact JSON; 8000
|
||||
* tokens leaves room for GPT-5 reasoning plus the dense report envelope.
|
||||
* Temperature 0 — the task
|
||||
* is faithful transcription, not generation, so the lowest setting minimises
|
||||
* invented values. Vision input tokens are expensive, so the route also
|
||||
* gates with a tight 6/hour rate bucket + reserveBudget + upload-size cap.
|
||||
*/
|
||||
ocrExtract: { temperature: 0, maxTokens: 4000 },
|
||||
ocrExtract: { temperature: 0, maxTokens: 8000 },
|
||||
|
||||
/**
|
||||
* v1.20.1 — Lab-OCR text-mode structuring. The browser OCR's the image
|
||||
|
||||
@@ -63,6 +63,7 @@ function isVisionReasoningSlug(m: string): boolean {
|
||||
function openaiSupportsVision(model: string): boolean {
|
||||
const m = model.toLowerCase();
|
||||
return (
|
||||
m.startsWith("gpt-5") ||
|
||||
m.startsWith("gpt-4o") ||
|
||||
m.startsWith("gpt-4.1") ||
|
||||
m.startsWith("gpt-4-turbo") ||
|
||||
|
||||
Reference in new issue
Block a user