diff --git a/frontend/src/api/async-job.ts b/frontend/src/api/async-job.ts new file mode 100644 index 0000000..cd432a1 --- /dev/null +++ b/frontend/src/api/async-job.ts @@ -0,0 +1,44 @@ +export type AsyncJobStatus = "ONGOING" | "SUCCEEDED" | "FAILED"; + +export interface AsyncJob { + id: string; + status: AsyncJobStatus; + payload?: TPayload; +} + +const DEFAULT_POLL_INTERVAL_MS = 10 * 1_000; +const DEFAULT_TIMEOUT_MS = 5 * 60 * 1_000; + +export async function pollUntilSucceeded( + fetchJob: () => Promise>, + options: { intervalMs?: number; timeoutMs?: number } = {}, +): Promise { + const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS; + const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS; + const deadline = Date.now() + timeoutMs; + + while (true) { + await sleep(intervalMs); + + if (Date.now() >= deadline) { + throw new Error(`Did not get results within ${timeoutMs}ms. Aborting polling.`); + } + + const job = await fetchJob(); + + if (job.status === "SUCCEEDED") { + if (job.payload === undefined) { + throw new Error("Async job succeeded but returned no payload."); + } + return job.payload; + } + + if (job.status === "FAILED") { + throw new Error(`Async job ${job.id} failed.`); + } + } +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/frontend/src/api/normalize.ts b/frontend/src/api/normalize.ts index a3862b8..7c3e99a 100644 --- a/frontend/src/api/normalize.ts +++ b/frontend/src/api/normalize.ts @@ -1,3 +1,4 @@ +import { type AsyncJob, pollUntilSucceeded } from "./async-job.js"; import { nablaFetch } from "../transport/client.js"; import type { ClinicalNote } from "./note.js"; @@ -18,11 +19,18 @@ export interface NormalizedData { export async function generateNormalizedData( note: ClinicalNote, ): Promise { - const response = await nablaFetch("/v1/core/user/generate-normalized-data", { - method: "POST", - body: JSON.stringify({ - note, - }), - }); - return response.json() as Promise; + const submitResponse = await nablaFetch( + "/v1/core/user/generate-normalized-data-async", + { + method: "POST", + body: JSON.stringify({ note }), + }, + ); + const { id } = (await submitResponse.json()) as AsyncJob; + + return pollUntilSucceeded(() => + nablaFetch(`/v1/core/user/generate-normalized-data-async/${id}`).then( + (response) => response.json() as Promise>, + ), + ); } diff --git a/frontend/src/pages/full-encounter-demo/work-on-note.render.ts b/frontend/src/pages/full-encounter-demo/work-on-note.render.ts index cf6450e..4e27cc6 100644 --- a/frontend/src/pages/full-encounter-demo/work-on-note.render.ts +++ b/frontend/src/pages/full-encounter-demo/work-on-note.render.ts @@ -66,7 +66,7 @@ export function markup(): string {

Extract ICD-10 / LOINC codes in FHIR format from the note.