Research artifact from SNHP's experiments — not a product. → snhp.dev

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.

examples:

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

  • id short slug · label display name · price your list price · cost what one unit costs you to serve. Item choice compiles to a choice dimension.
  • stock (optional) finite units today — gates availability. Add expected_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 an addon dimension — 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 a price/cost and it stops being free — the profiler will say so.

slots — optional, max 6

  • { "id", "label", "minutes", "capacity"? } — deferred fulfillment (pickup later). minutes 0 = right now (one immediate slot is required; the page adds it and tells you if missing). capacity caps how many units fit in a later slot. Compiles to a fulfillment dimension: 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_qty 1–6 (default 3) — the quantity dimension's cap.
  • min_price_frac 0–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.