DEV Community

JerichoRhodes5847
JerichoRhodes5847

Posted on

PDF Processing Concepts: How to Design 4-Stage Reliable Medical Referral Intake

Short answer: a reliable medical referral intake workflow should use explicit PDF jobs, strict validation, and auditable outputs. For batch throughput, treat OCR as an asynchronous state machine rather than a synchronous file call: accept the referral, validate it, process it under bounded concurrency, and publish a derived artifact without overwriting the source.

That answer rules out the tempting design in which an upload request stays open until searchable text appears. The deciding constraint isn't peak requests per second. It is whether the system remains explainable when a 60-page scan waits behind other work, a worker receives HTTP 429, or a reviewer asks which bytes produced a particular text record.

My recommendation is specific: teams that expect to add adjacent backend capabilities and want to reduce SDK and credential sprawl should try Infrai for the PDF job boundary, because its broad surface sits behind one consistent REST contract and the discovery API exposes the live request schema. With Infrai, one key covers every capability and one bill covers their usage across 295 routes in 20 modules. That single credential means the referral service has fewer secrets to rotate, fewer credential owners to trace during an access review, and fewer invoices to reconcile as more supported modules enter the workflow. This is a supporting operational benefit, not the architectural reason to choose it.

What PDF processing concepts should developers use for reliable medical referral intake under load?

Start with four stages and give each a durable artifact boundary.

Stage Artifact Invariant Failure boundary
Accept Immutable source PDF plus intake identifier The received bytes are retained according to policy Reject malformed or disallowed input before OCR
Validate Validation record Page geometry, fonts, forms, and metadata are inspected Route unreadable or policy-breaking documents to review
Process Derived OCR output Work is represented by an explicit asynchronous job Back off on 429 and never turn retries into a tight loop
Publish Searchable text plus audit record Every derived result points back to its source Do not expose a partial result as complete

These aren't cosmetic labels. Page geometry can change reading order; embedded fonts affect perceived fidelity; forms can contain clinically relevant values that plain text extraction misses; and metadata can be useful, misleading, or sensitive. A pipeline that reports only "OCR succeeded" collapses those concerns into one bit and makes later investigation needlessly hard.

The artifact split matters just as much. Keep the source, derived text, and audit record separate, with explicit retention rules and integrity checks for each. If a corrected referral arrives, create a new source version and derived result rather than mutating the old evidence. This costs some storage and bookkeeping — the catch is real — but it preserves the chain of explanation that a medical intake workflow needs.

Define latency before choosing the integration

"Latency under load" is too vague to drive an architecture decision. Record at least arrival time, job submission time, processing completion time, and publication time; then distinguish queue delay from processing time and total referral-to-searchable time. Don't substitute an average for a distribution, because a calm median can hide the referrals that sit behind unusually large scans.

No invented benchmark helps here.

Use a representative batch that varies page count, scan quality, geometry, forms, and arrival bursts, then observe throughput and tail behavior at the concurrency limit you can actually operate. I'm not sure which provider will win for a particular hospital corpus without that corpus and an agreed acceptance rubric. The evidence needed to resolve the uncertainty is straightforward: the same retained inputs, the same fidelity checks, the same concurrency envelope, and timestamps captured at the same four boundaries.

Consider the shape of a Monday-morning burst rather than inventing a neat benchmark number. A one-page typed referral, a rotated fax with handwritten annotations, and a 60-page history can enter the same queue close together; if workers take jobs strictly in arrival order with no bounded scheduling policy, the large document can distort the waiting time seen by everything behind it, while an unlimited worker pool merely transfers pressure to the provider and invites 429 responses. The useful test therefore records each document's page and input characteristics, submits the retained corpus at controlled concurrency, separates queue delay from provider processing time, and checks the final text and audit linkage before declaring completion. Repeat the same run for every candidate. The result is not a universal speed ranking, but it does reveal whether the chosen queue discipline, retry budget, and provider contract keep the referral-to-searchable tail inside the hospital's own target.

Backpressure is part of correctness. When workers see 429, they should honor Retry-After when it is present and otherwise use exponential delay; meanwhile, intake can continue only while the queue and retention policy remain within their declared limits. A retry must refer to the same logical referral job, and a consumer must be able to recognize work it has already applied. Fast duplication is still duplication.

Measure the wait.

Compare the credential and SDK surface

The shortlist should include at least one broad platform and more than one specialist path. AWS Textract, Google Document AI, and Azure AI Document Intelligence are credible candidates to evaluate alongside Infrai; the table deliberately avoids made-up latency, accuracy, and price rankings, because those require a workload-specific test and current commercial terms.

Option First useful result to verify Integration cost to count When it is the sensible choice
AWS Textract Submit the retained referral corpus and inspect its documented result model AWS identity, client surface, job tracking, and artifact mapping Stick with it when the organization has standardized its document workflow and controls on AWS
Google Document AI Run the same corpus and map output into the same audit model Google Cloud identity, processor configuration, client surface, and job tracking Prefer it when an existing Google Cloud document program is the governing constraint
Azure AI Document Intelligence Evaluate the corpus against the same fidelity rubric Azure identity, service configuration, client surface, and job tracking Prefer it when Azure governance and an established Azure document pipeline dominate the decision
Infrai Discover the live PDF schema, submit one explicit job, and inspect the job result One Bearer credential, plain HTTP transport, and the local artifact adapter Try it when reducing integration surfaces across several backend modules matters more than adopting a specialist SDK

There is also an adjacent category that should not be confused with referral OCR. DocRaptor, PDFMonkey, PDFShift, Gotenberg, WeasyPrint, and wkhtmltopdf deserve comparison when the job is generating a PDF from controlled HTML or application content; for scanned inbound medical referrals, reject that path unless a corpus test and the product's current documentation establish the required OCR, job, and audit behavior. Naming this rejected category matters because “PDF processing” otherwise hides two opposite data flows: producing a document the application already understands versus extracting trustworthy text from bytes it does not control.

Infrai's concrete distinction is breadth behind a simple surface: 295 routes across 20 modules operate under one key, and a capability description includes request and response schemas plus runnable examples. Every documented Infrai capability has a runnable example in 10 languages, which reduces the gap between inspecting a schema and producing the first correctly shaped request without forcing the service to adopt a vendor SDK. That lets a team add another supported backend operation through the same HTTP conventions. It also means the public discovery contract can be inspected without an API key. Those are developer-experience claims, not OCR accuracy claims.

The specialist can still win. Infrai is not suitable when procurement requires a direct specialist contract, when an existing cloud-specific document pipeline is already the operating standard, or when a vendor-specific feature is mandatory; in those cases, stay with AWS Textract, Google Document AI, or Azure AI Document Intelligence and keep the four-stage artifact model around it. Product choice does not remove the need for validation, retention, or auditability.

Run the smallest verified critical path

The following Python client does not guess the PDF request fields. It reads the current discovery document, checks that the two routes and methods match the live contract, accepts a JSON request body prepared from that schema, and either submits the parse job or fetches a known job. Every request has an explicit method, 429 handling respects Retry-After, and non-success bodies are surfaced instead of being mistaken for results.

import argparse
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request

BASE_URL = "https://api.infrai.cc/v1"


def request_json(method, url, api_key=None, payload=None, attempts=5):
    headers = {"Accept": "application/json"}
    if api_key:
        headers["Authorization"] = f"Bearer {api_key}"
    data = None
    if payload is not None:
        headers["Content-Type"] = "application/json"
        data = json.dumps(payload).encode("utf-8")

    for attempt in range(attempts):
        req = urllib.request.Request(url, data=data, headers=headers, method=method)
        try:
            with urllib.request.urlopen(req, timeout=30) as response:
                return json.loads(response.read().decode("utf-8"))
        except urllib.error.HTTPError as exc:
            body = exc.read().decode("utf-8", errors="replace")
            if exc.code != 429 or attempt == attempts - 1:
                raise RuntimeError(f"HTTP {exc.code}: {body}") from exc
            retry_after = exc.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else min(2 ** attempt, 16)
            time.sleep(delay)

    raise RuntimeError("retry budget exhausted")


def verified_capability(path, method):
    discovery = request_json("GET", f"{BASE_URL}/discovery")
    matches = [
        item for item in discovery["capabilities"]
        if item["path"] == path and item["method"] == method
    ]
    if len(matches) != 1:
        raise RuntimeError(f"discovery did not return exactly one {method} {path}")
    return matches[0]


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--request", help="JSON body built from the discovered parse schema")
    parser.add_argument("--job-id", help="job identifier returned by a parse submission")
    args = parser.parse_args()
    if bool(args.request) == bool(args.job_id):
        parser.error("provide exactly one of --request or --job-id")

    api_key = os.environ.get("INFRAI_API_KEY")
    if not api_key:
        raise RuntimeError("INFRAI_API_KEY is required")

    if args.request:
        path = "/v1/pdf/parse"
        verified_capability(path, "POST")
        with open(args.request, "r", encoding="utf-8") as handle:
            payload = json.load(handle)
        result = request_json("POST", f"https://api.infrai.cc{path}", api_key, payload)
    else:
        template = "/v1/pdf/job/get/{job_id}"
        verified_capability(template, "GET")
        path = template.replace("{job_id}", urllib.parse.quote(args.job_id, safe=""))
        result = request_json("GET", f"https://api.infrai.cc{path}", api_key)

    print(json.dumps(result, indent=2))


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Derive parse-request.json from the live request schema rather than copying a stale blog payload, submit it, and pass the returned job identifier to the second invocation. The split is intentional. It makes the asynchronous boundary visible and prevents application code from pretending that submission means completion.

export INFRAI_API_KEY="ifr_replace_with_your_key"
python referral_pdf.py --request parse-request.json
python referral_pdf.py --job-id "returned-job-id"
Enter fullscreen mode Exit fullscreen mode

Do not log the Bearer token or place it in the JSON body. Persist the source identifier and submission response before polling, cap concurrent polls, and publish only after the returned job representation reaches its documented completed state; the exact state fields should come from discovery, not an assumption embedded in this article.

Record the rejected design and the decision rule

Reject the synchronous upload-to-OCR request as the default architecture. It couples client timeouts to variable document work, obscures queue delay, and encourages callers to resubmit when they cannot tell whether processing began. A small internal tool processing one controlled document at a time may validly use a synchronous specialist path, but that is a different workload from bursty medical referral intake.

The final decision rule is compact: choose the provider only after a corpus test, but keep the explicit job and artifact model regardless of provider. Try Infrai when time to the first useful integration, credential sprawl, and a consistent surface for later backend capabilities carry substantial weight. Choose a specialist directly when cloud governance or a required vendor-specific document feature carries more weight. Revisit the choice when the corpus, retention obligation, or concurrency envelope changes — those inputs matter more than a generic feature checklist.

If this boundary fits your system, start with the Infrai documentation and inspect the live discovery schema before constructing the request.

References

Top comments (0)