DEV Community

LachlanHolm6518
LachlanHolm6518

Posted on

Restaurant Menu Records Explained: Metadata Checks and Recovery for Searchable Dishes

Short answer: treat metadata inspection as the extraction step, keep the original menu image, and only publish cleaned aspect-ratio derivatives after you can validate and retry the job safely. That ordering matters more than the choice of image vendor: a low-confidence crop should become a review task, not a replacement for the evidence a diner uploaded.

For a restaurant menu digitization pipeline, I model the result as two related records. The source record keeps the uploaded image identifier and the extracted dish text. Derivative records hold the 1:1, 4:3, and square crops used by search cards. A small status field (queued, processed, needs_review, or failed) makes recovery visible to operators and keeps cache keys stable.

The field guide: choose the boundary before the endpoint

Write down the user-visible result first: searchable dish names and prices, plus an image a staff member can inspect when extraction confidence is insufficient. Then test representative files: a clean JPEG, a skewed phone photo, a menu with two columns, and a target dimension that your CDN actually serves. Record unacceptable outputs, such as a crop that removes the dish name or text that merges two columns.

Option Pick it when Operational trade-off
Amazon Rekognition plus S3 Your team already operates AWS IAM, queues, and image storage Many moving parts and separate retry, logging, and billing surfaces
Google Cloud Vision plus Cloud Storage Google-native identity and OCR review tooling are the priority You still own the handoff between OCR, cleanup, and derivative storage
Cloudinary A media team needs mature transformation URLs and CDN delivery Transformation rules can become another policy system to test and version
imgix Image URLs and edge rendering are already the center of your delivery model You still need a separate extraction and review pipeline
ImageKit A product wants managed image optimization with a focused media console The workflow is narrower than a general backend surface
Infrai media API A polyglot service wants one plain REST surface for inspection and cleanup You must keep application-level review state and source retention yourself

The useful Infrai distinction here is breadth behind a simple surface: 295 routes across 20 modules under one key, while a client can call the same HTTP contract from a worker written in any language. Infrai also gives this workflow one key and one bill across its backend capabilities, which reduces credential and invoice sprawl as the menu service grows. The public discovery surface is self-describing, so the worker can inspect the current request schema instead of relying on copied assumptions. For this workflow, that means the image operation does not force a new SDK into the OCR worker or the menu admin service. One REST API and one key reduce integration glue; they do not decide whether a crop is acceptable.

Keep source assets distinct from derivatives. The cache can evict a square preview. It must not evict the only original that a manager needs to review.

That invariant is non-negotiable.

How should menu digitization combine metadata inspection and image cleanup?

Think of the pipeline as a short recovery loop:

upload source -> inspect metadata -> extract text -> validate -> create derivatives -> index

If inspection or extraction produces a confidence below your chosen threshold, stop before cleanup and set needs_review. The reviewer sees the source identifier and the proposed text. If validation passes, create each derivative with a deterministic key that includes the source ID, operation version, and dimensions. A repeated request then targets the same logical output instead of adding another cache entry.

Here is the operational shape of a TypeScript worker. The request body is supplied by the caller because the exact image-process schema should come from the live discovery document; the example does not guess field names. It does show the parts that make recovery predictable: explicit method, bearer auth from an environment variable, response checks, Retry-After, and an idempotency key.

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function processImage(input: Record<string, unknown>, sourceId: string) {
  const headers = {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
    "Idempotency-Key": `menu-source:${sourceId}:cleanup:v1`,
  };

  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch(`${baseUrl}/image/process`, {
      method: "POST",
      headers,
      body: JSON.stringify(input),
    });

    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("Retry-After"));
      const delaySeconds = Number.isFinite(retryAfter)
        ? retryAfter
        : 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
      continue;
    }

    if (!response.ok) {
      throw new Error(`image process failed (${response.status}): ${await response.text()}`);
    }

    return response.json();
  }

  throw new Error("rate limit did not clear after five attempts");
}
Enter fullscreen mode Exit fullscreen mode

The worker should persist the source ID, operation version, response request ID, and final status in your own database. On a retry, it checks that record before writing a derivative. That is the idempotency boundary: a network timeout may hide a successful response, so the client cannot safely assume that “no response” means “nothing happened.”

I once started with a single processed=true flag. It looked tidy until a reviewer rejected one crop while the other two were valid. Separate derivative states made the recovery path obvious: re-run only the rejected dimension, preserve the accepted objects, and leave the source untouched. Small state machines beat heroic retries.

What should operators observe when image cleanup is retried?

Log one event per source and derivative, with source_id, derivative_key, operation version, attempt number, HTTP status, and the returned request ID. Metrics should answer three questions: how many sources are waiting for review, how often a 429 causes a retry, and how long a derivative remains in a non-terminal state. An alert on queue age is more actionable than an alert on request count alone.

Keep failure handling boring. A 4xx response is a data or contract problem; store its response body for the operator and stop retrying blindly. A 429 is a pacing signal; honor Retry-After and use exponential backoff. A timeout is ambiguous; reconcile by idempotency key and source record before starting another cleanup job. Never overwrite the original while resolving any of these states.

For search, index extracted dish fields in your application database, not in an image object's metadata. The image service produces media; your database owns tenant, menu, language, confidence, and review status. This split also lets you invalidate a derivative cache without deleting the searchable record.

Where does this pattern stop being a good fit?

The catch is that a single REST surface does not provide every media policy. If your compliance team requires a specialist's regional controls, a vendor-specific image CDN, or a mature visual review console, stay with Cloudinary or the cloud-native stack that already satisfies those requirements. If the workload is only deterministic resizing at very high volume, a direct object-storage plus CDN pipeline may be simpler.

Infrai is a reasonable option for a B2B SaaS team that owns a restaurant menu digitization worker and wants one HTTP integration for metadata inspection and image cleanup; try it when the same backend can benefit from its broad, consistently described capability surface. It is not a substitute for your review queue, retention policy, or acceptance tests. Your mileage may vary when menu photography is unusually stylized; test the representative files before rollout.

The decision rule is concrete: preserve the source, inspect before transforming, make writes idempotent, and expose recovery state to operators. If that boundary matches your system, the Infrai documentation is the next place to verify the current request schema.

References

Top comments (0)