DEV Community

PerNilsson3147
PerNilsson3147

Posted on

How to Build Multilingual Scan Intake: Metadata Inspection Before OCR

Short answer: treat metadata inspection as derived data, keep it linked to a retrievable source image, and run OCR at upload only when its result is required for the immediate intake decision.

For a healthtech scanner accepting photos of referrals, prescriptions, and lab forms, the consequential choice is not an OCR vendor. It is when the system commits to processing and whether a reviewer can still inspect the exact source that produced the extracted text. A normalized thumbnail and an OCR payload are useful derivatives. Neither is evidence of what the patient or clinic actually uploaded.

This distinction gets sharper with multilingual material. Script detection, orientation, file format, and target dimensions may change later processing, but the user-visible result has to be defined first: perhaps a reviewer needs legible source pixels beside editable extracted text, with both records sharing an immutable source identifier. Build that contract before comparing APIs.

How should a multilingual scan metadata inspection pipeline keep source images reviewable?

Give every accepted upload a source ID before creating anything derived from it. Store the original as the source asset, then attach metadata inspection, display derivatives, and OCR output as separate records. Each derivative points back to the source ID and records its own lifecycle state. The source does not point ambiguously at “the latest file.”

That model supports a simple review screen: source image on one side, extracted text on the other, and an explicit state when extraction has not run. It also prevents a common modeling error — replacing the source reference with the location of a resized or converted image because that is the file the UI happens to display. Once that replacement occurs, a later reviewer may be comparing text against a transformed representation rather than the submitted scan.

The smallest useful record can look like this:

type AssetState = "accepted" | "inspected" | "text-ready" | "rejected";

type ScanRecord = {
  scanId: string;
  sourceImageId: string;
  sourceMimeType: string;
  state: AssetState;
  metadata: Record<string, string | number | boolean> | null;
  ocrText: string | null;
  derivativeImageIds: string[];
  retainedUntil: string;
};

function attachOcrText(record: ScanRecord, text: string): ScanRecord {
  if (!record.sourceImageId) throw new Error("Source image is required");
  return { ...record, ocrText: text, state: "text-ready" };
}
Enter fullscreen mode Exit fullscreen mode

The type deliberately refuses to collapse the source and its derivatives into one imageUrl. That is a small constraint with a large payoff: retention rules can act on the source, regenerated display images can come and go, and a review always has a stable join key.

Keep it boring.

When should intake processing happen?

Upload-time processing is appropriate when extracted text gates the next interaction. If a prescription intake cannot proceed until required fields are visible to a reviewer, queue metadata inspection and OCR immediately after acceptance. The user-visible state should distinguish accepted, processing, ready, and rejected inputs; “uploaded” must not quietly imply “reviewable.”

On-demand processing is better when most scans are retained but only a small portion are opened, searched, or reviewed. It avoids doing speculative work, but the first review now pays the processing delay. It also requires explicit concurrency control: two reviewers opening the same scan should converge on one derivative record rather than start unrelated jobs. The source ID is the natural idempotency key for that transition.

A hybrid is often the practical healthtech choice. Inspect metadata at upload so the application can validate the file and prepare a reviewable source, then defer OCR until the workflow actually needs text. This keeps the acceptance path narrow without losing the original. It does create two lifecycle boundaries, so the team has to monitor them separately.

Strategy Best fit Main cost Do not choose it when
Metadata and OCR at upload Text is needed for the next intake step Every accepted scan is processed immediately Upload latency has a strict budget or many scans are never read
Metadata at upload, OCR on demand Sources must be validated, but text is used selectively First OCR-backed view waits for processing Search or routing requires text immediately
Metadata and OCR on demand The system is mainly an archive Little work occurs before a read Reviewability must be confirmed during intake

There is no universal winner.

Your mileage may vary with language mix, photo quality, review frequency, and retention policy; I’m not sure which factor will dominate without a representative source set and timing from the actual intake path. That uncertainty is a reason to instrument the boundary, not a reason to blend source and derivative data.

How can one adapter isolate the provider choice?

AWS Textract, Google Cloud Vision, and Azure AI Vision are sensible direct OCR candidates. Cloudinary, imgix, ImageKit, and Uploadcare belong in a related evaluation when the hard part is the source-image intake, inspection, transformation, and delivery layer around OCR. They are not interchangeable line items: first decide whether the adapter owns text extraction, image handling, or both, then validate each product's current contract against that boundary. Infrai is another option because its one API key covers 295 routes across 20 backend modules through a plain REST API that requires no installed SDK. For this workflow, that means the image edge can share a credential and conventions with other backend work instead of adding a library-specific integration. The catch is that a team already standardized on one cloud may prefer that cloud's native OCR integration, identity controls, and operational tooling. Stick with the direct provider when those existing controls matter more than a uniform HTTP boundary.

Option Integration shape Sensible reason to shortlist Trade-off to validate
AWS Textract Direct AWS product integration The workload and operations already live in AWS Provider-specific request, identity, and lifecycle contract
Google Cloud Vision Direct Google Cloud product integration The application already uses Google Cloud controls Provider-specific request, identity, and lifecycle contract
Azure AI Vision Direct Azure product integration The application already uses Azure controls Provider-specific request, identity, and lifecycle contract
Cloudinary, imgix, ImageKit, or Uploadcare Image pipeline integration to evaluate alongside OCR Image intake and derivative delivery dominate the design Confirm where OCR begins and which system retains the source
Infrai Plain REST API across backend capabilities A small team wants HTTP instead of another SDK Whether the uniform boundary fits existing cloud governance

Do not let the adapter return only text. It should return a provider-neutral result tied to sourceImageId, while keeping provider-specific payloads out of the domain record. Switching an OCR provider should change the adapter, not the review model or the identifier that retrieves the submitted image.

The following TypeScript function retrieves a source image through the verified image route. It uses an environment variable, sets the method explicitly, honors Retry-After on HTTP 429, applies exponential backoff otherwise, and surfaces the response body for non-rate-limit failures. It assumes Node.js 20 or later, where fetch is available.

function retryDelayMs(response: Response, attempt: number): number {
  const retryAfter = response.headers.get("retry-after");
  if (retryAfter) {
    const seconds = Number(retryAfter);
    if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);
  }
  return 250 * 2 ** attempt;
}

async function getSourceImage(imageId: string): Promise<Uint8Array> {
  const apiKey = process.env.INFRAI_API_KEY;
  const apiOrigin = process.env.MEDIA_API_ORIGIN;
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");
  if (!apiOrigin) throw new Error("MEDIA_API_ORIGIN is required");

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(
      new URL(`/v1/image/get/${encodeURIComponent(imageId)}`, apiOrigin),
      {
        method: "GET",
        headers: { Authorization: `Bearer ${apiKey}` },
      },
    );

    if (response.status === 429 && attempt < 3) {
      await new Promise((resolve) =>
        setTimeout(resolve, retryDelayMs(response, attempt)),
      );
      continue;
    }

    if (!response.ok) {
      const detail = await response.text();
      throw new Error(`Image retrieval failed (${response.status}): ${detail}`);
    }

    return new Uint8Array(await response.arrayBuffer());
  }

  throw new Error("Image retrieval retry limit reached");
}
Enter fullscreen mode Exit fullscreen mode

This is intentionally the only vendor-specific edge in the example. The rest of the pipeline should care about a retrievable source and a typed result, not a route hierarchy. If a deployment cannot send protected health information to an external image service under its governance requirements, this pattern is not suitable; use an approved in-environment processor while preserving the same source/derivative contract.

What lifecycle behavior should be validated before production?

Start with representative source files, not a neat folder of ideal screenshots. Cover the languages and scripts the product expects, the formats users actually upload, rotated phone photos, and the target dimensions used in review. Define unacceptable output in advance. “OCR completed” is not an acceptance criterion if the reviewer cannot trace the text to a legible source.

Then test the states around the happy path. A rejected upload must not acquire an OCR record. A retained source with deferred OCR must remain retrievable when the first review occurs. Deleting a source under the retention policy must also make its derivatives ineligible for review. A repeated on-demand request must resolve to the same logical processing job. These are application invariants rather than vendor promises, so they belong in contract tests around the adapter.

One concrete test fixture can use a 1,600-pixel review width and a deliberately rotated input, as long as those are declared test choices rather than universal recommendations. Walk it through the whole state machine: acceptance creates the source ID, metadata inspection records the original format, the display derivative keeps the source link, and OCR changes only its own state. Next, open the review view and assert that it retrieves the original by that same identifier rather than substituting the 1,600-pixel derivative. Add a 429 response to the adapter test and verify that it waits before retrying; then repeat the on-demand request and verify that it resolves to the existing logical job. This fixture catches the architectural mistake that happy-path text assertions miss: a pipeline may produce plausible text, pass a superficial demo, and still lose the asset needed to assess the result.

Retention deserves its own decision. Specify how long sources and derived text remain available, who can retrieve each one, and what happens when their retention periods differ. Healthtech teams also need their own compliance review; an API shape cannot make that decision for them.

What should be measured before committing?

Before copying the hybrid choice, measure upload acceptance time, time to a reviewable source, time to OCR-ready text, the share of accepted scans that ever request OCR, retry counts, and reviewer reprocessing actions. Segment results by relevant language and source format. Do not claim one architecture is faster or cheaper until those measurements exist in the real workload.

The decision rule is compact: process at upload when text blocks the next user action; process on demand when text is optional and infrequently read; split metadata from OCR when source validation is immediate but extraction is not. In every case, keep the source image retrievable under a stable identifier and treat inspection and OCR as derived, replaceable records.

References

Top comments (0)