Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mavera.io/llms.txt

Use this file to discover all available pages before exploring further.

Scenario

You want product bundles backed by evidence. You analyze order history to find frequently co-purchased products, propose bundle concepts from top pairs, then validate with a Mavera focus group for synthetic audience feedback.

Architecture

Code

import os, requests
from collections import defaultdict
from itertools import combinations

STORE = os.environ["SHOPIFY_STORE"]
TOKEN = os.environ["SHOPIFY_ACCESS_TOKEN"]
MV = os.environ["MAVERA_API_KEY"]
SH = f"https://{STORE}.myshopify.com/admin/api/2024-10/graphql.json"
SH_H = {"X-Shopify-Access-Token": TOKEN, "Content-Type": "application/json"}
MB = "https://app.mavera.io/api/v1"
MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}

QUERY = """query ($cursor: String) {
  orders(first: 250, after: $cursor) {
    edges { node { lineItems(first: 20) { edges { node { title originalUnitPriceSet { shopMoney { amount } } } } } } }
    pageInfo { hasNextPage endCursor }
  }
}"""

orders, cursor = [], None
while True:
    resp = requests.post(SH, json={"query": QUERY, "variables": {"cursor": cursor} if cursor else {}}, headers=SH_H)
    resp.raise_for_status(); data = resp.json()["data"]["orders"]
    for e in data["edges"]:
        items = [li["node"] for li in e["node"]["lineItems"]["edges"]]
        if len(items) >= 2: orders.append(items)
    if not data["pageInfo"]["hasNextPage"]: break
    cursor = data["pageInfo"]["endCursor"]
print(f"Analyzed {len(orders)} multi-item orders")
counts, prices = defaultdict(int), defaultdict(list)
for items in orders:
    titles = sorted(set(i["title"] for i in items))
    pm = {i["title"]: float(i["originalUnitPriceSet"]["shopMoney"]["amount"]) for i in items}
    for a, b in combinations(titles, 2):
        counts[(a, b)] += 1
        prices[(a, b)].append(pm.get(a, 0) + pm.get(b, 0))

bundles = [{"products": list(p), "count": c, "bundle_price": round(sum(prices[p])/len(prices[p])*0.9, 2)}
           for p, c in sorted(counts.items(), key=lambda x: -x[1])[:10] if c >= 5]
for b in bundles[:5]:
    print(f"  {b['products'][0]} + {b['products'][1]}: {b['count']}x → ${b['bundle_price']}")
descs = [f"Bundle {i+1}: {b['products'][0]} + {b['products'][1]} at ${b['bundle_price']} (10% off)" for i, b in enumerate(bundles[:5])]
fg = requests.post(f"{MB}/focus-groups", json={
    "name": "Shopify Bundle Validation",
    "questions": [f"Would you buy '{bundles[0]['products'][0]}' + '{bundles[0]['products'][1]}' at ${bundles[0]['bundle_price']}?", "Which bundle is most appealing?", "What discount makes a bundle irresistible?", "Mix-and-match or curated sets?"],
    "context": "Testing bundle concepts from purchase data.\n\n" + "\n".join(descs),
}, headers=MH)
fg.raise_for_status(); result = fg.json()
print(f"\nFocus group: {result['id']}")
for r in result.get("responses", []):
    print(f"  Q: {r['question']}\n  A: {r['answer'][:200]}\n")

Example Output

{
  "id": "fg_7d3f1a9e",
  "responses": [
    {
      "question": "Would you buy 'Merino Base Layer' + 'Trail Runner Shorts' at $112.50?",
      "answer": "Absolutely — I already buy these together. Saving $12.50 is a no-brainer."
    },
    {
      "question": "What discount makes a bundle irresistible?",
      "answer": "15% is the sweet spot. At 10% I comparison-shop, at 15% I stop thinking."
    }
  ]
}

Error Handling

Co-occurrence needs orders with 2+ distinct products. If your store has mostly single-item orders, lower min_count or expand the date range.
An order with 20 unique products generates 190 pairs. Cap unique titles per order to the top 10 by quantity to keep memory manageable.