01 · SDK Offer optimization

Offer Optimization SDK

What is the least this customer would accept?

A weekend promo is ready at 15% off produce, and finance wants the discount smaller without losing redemptions. Ask each customer's twin what the cheapest version they would still take is — 8% for one, a lower minimum spend for another, nothing at all for a third — and send each of them that, instead of one blanket 15%.

Across a re-priceable campaign this removes 6–15% of the committed discount, holding acceptance fixed. The study behind it is written up in full on the research pages.

Quick start

from eggai import OfferOptimizer

optimizer = OfferOptimizer(api_key)
terms = optimizer.cheapest_accepted(
    customer   = "c_8813",
    profile    = "shops fortnightly, small baskets, "
                 "coupon-led, buys confectionery",
    offer      = "Fresh Picks Weekend — 15% off "
                 "produce, min spend 500",
    guidelines = """
        discount caps at 12%, min spend above 300.
        the produce section is fixed. the tagline may
        be reworded, but never promise free delivery.
    """,
)

terms.text       # "8% off produce, min spend 349"
terms.tagline    # "Weekend Picks"
terms.changed    # ["discount", "min_spend", "tagline"]
terms.objection  # "threshold above my usual basket"

How a run works

A run is two models taking turns. Offerwright drafts the terms and the words together, opening at the stingiest version your guidelines allow; the twin answers as this one customer — yes or no, how sure, and which condition it objected to. Each round the proposer moves only the lever the twin objected to. Neither model sees the other's reasoning.

The loop stops the moment the twin accepts, and returns the cheapest version that got there. If the guidelines run out first, the run returns no offer at all (accepted == False) rather than one that costs more than the customer is worth.

Objectives

ObjectiveThe search
saving (default)The offer already lands — find the cheapest version this customer still accepts.
winbackThe customer has lapsed — find any offer inside the guidelines they would take at all, cheapest first.

Batch runs

Pass rows instead of a single customer — one dict per customer, offers per row or one campaign for the file. Free-text offers are parsed server-side; structured terms ({depth, min_spend, category, validity, subject}) are accepted as-is.

run = optimizer.cheapest_accepted(
    rows = [
        {"id": "c_8813", "profile": "fortnightly, coupon-led",
         "offer": "15% off produce, min 500"},
        {"id": "c_2291", "profile": "weekly family shop",
         "offer": "15% off produce, min 500"},
    ],
    objective   = "saving",
    guidelines  = "discount caps at 12%",
    constraints = {"max_depth": 12, "min_spend_min": 300},
)

run.accepted          # the rows worth sending
run["c_8813"].text   # lookup by customer id
for terms in run:     # or iterate in input order
    ...
Guidelines vs constraints. Guidelines are free text the proposer honours — intent, tone, what may be reworded. Constraints (max_depth, min_depth, min_spend_min, min_spend_max) are numeric backstops enforced by the harness itself, whatever the models do. Use both: guidelines for intent, constraints for the numbers you cannot breach.

The result object

FieldMeaning
acceptedTrue if some version inside your guidelines got a yes. False → send nothing; every field below except objection is empty.
textThe cheapest accepted terms, as words — "8% off produce, min spend 349".
taglineThe offer message that goes with those terms.
termsThe same, structured: {depth, min_spend, category, validity, subject}.
changedWhich levers moved from the original: discount, min_spend, category, validity, tagline.
objectionThe twin's objection to the offer as written — why the original needed changing at all.
pThe twin's acceptance probability for the returned terms.
stepsThe full ladder: every proposal, its probability, the twin's words, accepted or not.
rawThe untouched server row, everything above included.

Verify it worked

Runs started from the EGGai app split a 5% holdout before scoring — customers who keep the original offer — so the saving is measured against a control, not asserted. The app's verification screen scores both groups when your redemption file comes back. SDK jobs return every row; keep your own control group if you need one.