SNHP · general offer-graph engine SIMULATED · ADVISORY
Run it on your own menu.
Paste your menu as JSON. The real engine — the same general offer-graph core our simulations validate — compiles it, tells you which choices are FREE (your customer's call, never priced) and which are LEVERS (cost moves with your shop's state, so there's a deal to find), and quotes any cart live. Discount-only: never above your list.
What we can and can't know from a pasted menu: FREE is proven from your own numbers — zero cost spread means there is never a cost basis to price that choice, whatever your demand looks like. LEVER means your inputs show real cost asymmetry, so a deal exists to be found. The big one — a later pickup that clears your rush — pays off in proportion to how many of your customers are flexible, and we don't guess that: we offer them the choice, and the flexible ones take it (they self-select), with an attestation that the later slot they picked is the one they actually use. The quotes below use a stated example shopper; your real dollars are what your customers reveal by taking (or not taking) the offer — measured on your own order flow, not modelled by us.
Your menu
Two example menus to start from — neither is boba; the engine doesn't care what you sell. Edit anything and hit Run.
Validated strictly — typos get a real error, not a silent guess.
Menu format — exactly what this page accepts
The page compiles this JSON into the engine's declarative buildGraph spec (core/js/api.mjs) — the compiled spec is shown verbatim in “Take it with you” below. Top-level keys:
items — required
idshort slug ·labeldisplay name ·priceyour list price ·costwhat one unit costs you to serve. Item choice compiles to achoicedimension.stock(optional) finite units today — gates availability. Addexpected_demand(buyers you expect) to arm the engine's scarcity shadow: every discounted unit that displaces an expected full-price buyer is costed at your list, so scarce items hold at list.perishable: true+salvage(its value to you at close, default 0) — at end of day the engine's salvage_on_expiry component drops its cost to salvage, which can open room under clearance conditions.
addons — optional, max 12
- Extras with
price+cost(same fields as items minus stock). Compiles to anaddondimension — the engine enumerates every subset, which is why 12 is a hard engine cap.
preferences — optional, max 6
{ "id", "label", "options": [{ "id", "label" }] }— zero-cost choices (temperature, cup, slicing…). These profile FREE: the shopper's call, never priced. Give an option aprice/costand it stops being free — the profiler will say so.
slots — optional, max 6
{ "id", "label", "minutes", "capacity"? }— deferred fulfillment (pickup later).minutes0 = right now (one immediate slot is required; the page adds it and tells you if missing).capacitycaps how many units fit in a later slot. Compiles to afulfillmentdimension: when you're busy, a shopper who can wait removes your risk of losing the sale — the engine turns that into a deal.
knobs
max_qty1–6 (default 3) — the quantity dimension's cap.min_price_frac0–1 (default 0.6) — your policy floor: the engine never quotes below this fraction of list.name,currency— display only.
Limits: ≤ 40 items, ≤ 8 options per preference, ids unique across the whole menu, ≤ 300,000 enumerable configurations. Not exposed here (but in the engine): batch_economies (a per-order setup cost that makes quantity a real per-unit lever) and dependency edges (requires/excludes) — see core/js/api.mjs.
Same format, hosted. This is exactly the JSON the deployed engine's HTTP API takes too — POST it as {"spec": … } to api.snhp.dev/v1/offer/{profile,quote} and the server compiles it with the same rules (smaller hosting caps: ≤ 12 options per dimension, ≤ 20,000 configurations). One format, page and API — the ready curl is under “Take it with you” below.
What the engine sees —
Your menu compiled to a typed offer graph, and the profiler's verdict on every dimension. This is the signature move: the engine probes its cost model and only negotiates where cost actually moves — everything else stays your customer's free choice.
dimensions
cost model (composable, state-dependent)
compile notes
Quote playground
Build a cart, set the shop moment, and read the engine's quote. Every number is a live quote() output on your compiled menu — nothing scripted.
Your list price
$0.00
Engine quote
$0.00
Why (engine receipt)
If no deal (the walk-away the engine anchored on)
Notes
Take it with you
The engine runs anywhere. Four copy-paste paths — the same friendly menu, whichever you pick: your menu as data, the engine pricing it in node, a prompt that hands your menu to an AI agent, and a hosted curl to the deployed engine.
a · your menu (the exact JSON you ran)
b · price it yourself — node + core/js (no dependencies)
c · hand it to an AI agent — a ready prompt
d · call the deployed engine — hosted HTTP (nothing installed)
The same friendly menu you pasted, POSTed to the deployed general engine at api.snhp.dev — it accepts this exact format (no separate spec to learn). /profile returns the FREE/LEVER verdicts from section 2; /quote runs the same search as the node snippet above — the engine's best deal for the simulated shopper — so the hosted reply is what “price it yourself” prints, with nothing installed.
profile — FREE vs LEVER per dimension
quote — the Nash split, discount-only (never above list)
What this page is — and isn't. Everything here is simulated and advisory. The quotes are real outputs of the same general engine our simulations validate, computed live on the menu you pasted — but the shopper and the shop moment are disclosed stand-ins, and your real numbers depend on your real demand. No forecasts are made on this page, and no quote can ever exceed your list price.
Curious how a shopper sees this? Walk up to the boba tablet →