From 7fc3e0bb7e41d89742dbd630d4041b9dc7aa657a Mon Sep 17 00:00:00 2001 From: Robin Nicollet Date: Wed, 15 Jul 2026 13:35:11 +0200 Subject: [PATCH 1/2] Replace sync normalized-data with async submit + poll flow (API-432). Showcase the recommended integration pattern for generate-normalized-data-async with a shared pollUntilSucceeded helper. Co-authored-by: Cursor --- frontend/src/api/async-job.ts | 33 +++++++++++++++++++ frontend/src/api/normalize.ts | 22 +++++++++---- .../work-on-note.render.ts | 2 +- frontend/src/shared/documentationLinks.ts | 3 +- 4 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 frontend/src/api/async-job.ts diff --git a/frontend/src/api/async-job.ts b/frontend/src/api/async-job.ts new file mode 100644 index 0000000..ecd2e42 --- /dev/null +++ b/frontend/src/api/async-job.ts @@ -0,0 +1,33 @@ +export type AsyncJobStatus = "ONGOING" | "SUCCEEDED" | "FAILED"; + +export interface AsyncJob { + id: string; + status: AsyncJobStatus; + payload?: TPayload; +} + +const DEFAULT_POLL_INTERVAL_MS = 5_000; + +export async function pollUntilSucceeded( + fetchJob: () => Promise>, + options: { intervalMs?: number } = {}, +): Promise { + const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS; + + while (true) { + 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.`); + } + + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + } +} 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.