DEV Community

Cover image for Preserve conflicting offer terms before comparing promotions
Tim Zinin
Tim Zinin

Posted on

Preserve conflicting offer terms before comparing promotions

I maintain this Actor. This is an AI-assisted technical walkthrough checked against the installed source contract.

An offer can arrive as structured fields and as text, and the two may disagree. If a record says 20 percent off in one field and 30 percent off in the captured copy, choosing one value silently destroys evidence. A useful normalization step should preserve that disagreement for review.

US Brand Offer Evidence Normalizer accepts bounded buyer-supplied evidence rows, either inline or through the supported Dataset input. Inline input supports one to 100 rows. It parses supported explicit offer terms, checks the consistency of submitted entity-attribution fields, and returns eligible normalization rows with current-run delivery accounting.

Inspect the supplied demonstration

The example below describes a synthetic retailer at demo.example. It contains captured text saying 20 percent off orders of at least 50 dollars with code SPRING20, along with structured fields describing those terms. The HTTPS URLs and attribution object are submitted evidence fields. The Actor does not visit the retailer or independently verify the promotion.

Notice the supplied end date in the structured terms. The short text does not establish every field in that object. A reviewer should retain the capture context and understand which terms came from structured extraction versus the visible excerpt. The example is a contract demonstration, not a currently available shopping offer.

The attribution status submitted as verified_by_source is not an independent verification by this Actor. The normalizer tests the supported field relationships and can label submitted fields consistent. That distinction matters when a downstream table drops the original proof object and leaves only a short status string. Synthetic evidence can still be eligible and charged when it satisfies those internal checks; a demonstration input does not make a cloud run free.

Keep partial language partial

Phrases such as up to, from, and as low as can describe a range or a conditional price. A normalizer should not convert them into an unconditional discount. This implementation retains partial or unparsed states when it cannot establish an unambiguous supported value from the supplied material.

When text and structured terms disagree, inspect the retained conflict rather than picking the more attractive number. Conflict, partial, unparsed, missing-proof, and mismatched-proof rows are not eligible paid normalization rows. A batch can therefore contain input work without producing the same number of delivered paid rows.

Use a small authorized export for a first integration. Keep labels disabled as in this example; optional external labeling is a separate capability with its own credentials and costs. It should not be introduced merely to make deterministic term parsing appear more complete.

Review the terminal receipt

Check whether the run reached its declared result limit, stopped at its budget, or encountered a delivery anomaly. A result-limit stop means the chosen eligible-row ceiling was reached. A budget stop has a different cause and should remain distinct in your monitoring.

The useful downstream comparison contains normalized terms, their original evidence, attribution-consistency status, and any unresolved conflicts. It does not claim that an offer is live, legally valid, redeemable, or exhaustive. Verify those questions against the actual merchant material before using the record in a customer-facing promotion comparison.

Input and execution boundary

Save this workflow-specific contract example as input.json. This is a synthetic contract example for the workflow described above; it does not assert a real observed business event.

{
  "schemaVersion": "1.0",
  "requestId": "offer-normalize-demo-001",
  "rows": [
    {
      "entity": {
        "entityId": "brand:demo-outfitters",
        "brandName": "Demo Outfitters",
        "legalName": null,
        "domain": "demo.example",
        "ticker": null,
        "aliases": [],
        "country": "US",
        "sourceEntityIds": [
          "demo:outfitters"
        ]
      },
      "evidence": {
        "evidenceType": "landing_page",
        "sourceName": "synthetic_demo",
        "sourceRecordId": "offer-001",
        "sourceUrl": "https://demo.example/offers/spring",
        "observedAt": "2026-08-04T08:00:00Z",
        "rawTitle": "Spring offer",
        "rawTermsText": "20% off orders $50+ with code SPRING20.",
        "terms": {
          "offerKind": "percent_off",
          "percentOff": 20,
          "minPurchaseAmount": {
            "value": 50,
            "currency": "USD"
          },
          "couponCode": "SPRING20",
          "endAt": "2026-09-30T23:59:59Z"
        },
        "entityAttribution": {
          "status": "verified_by_source",
          "proofType": "first_party_domain",
          "proofValue": "demo.example",
          "sourceEntityId": "demo:outfitters",
          "proofUrl": "https://demo.example/",
          "proofDomain": "demo.example",
          "merchantId": null,
          "adAccountId": null,
          "officialSourceId": null
        },
        "additionalUrls": []
      }
    }
  ],
  "options": {
    "labels": "off",
    "maxItems": 10
  }
}
Enter fullscreen mode Exit fullscreen mode

A deliberate cloud execution can use the following asynchronous request. It creates a new run and may incur charges; this walkthrough did not execute it.

curl --request POST \
  'https://api.apify.com/v2/acts/zinin~us-brand-offer-evidence-normalizer/runs?build=0.4.1&timeout=180&maxTotalChargeUsd=0.10' \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-binary @input.json
Enter fullscreen mode Exit fullscreen mode

Save the returned run ID. Check that existing run before retrying after a client timeout, then inspect its terminal status, Dataset, and KVS OUTPUT. A timeout in the caller does not cancel a server run. The charge ceiling bounds the request; it does not guarantee delivery or remove the start event. The build selector pins the observed build number, while the run's actual build ID should be retained for provenance.

Price and reproducibility

On 19 October 2026, the observed FREE-plan rates are $0.005 for the automatic start event and $0.01 per eligible normalized evidence row. The platform can count start events according to allocated memory. Other subscription tiers differ. Verify the current Actor page before purchasing a run; optional external services, where enabled, are separate.

This article uses the installed 0.4.1 source contract and saved public input shape, checked on the stated date. The arithmetic examples are explanatory, not fresh cloud acceptance, customer activity, or evidence of revenue. Preserve your input, actual build ID, observation boundary, and terminal receipt when adapting the workflow.

Top comments (0)