AgentSales & profitability

Daily inventory actions

The handful of inventory actions worth doing today: what's about to run out, where cash is trapped, what to push, what to reorder, and ad spend on stock you can't sell.

Works with
ClaudeChatGPTAny MCP-compatible AI tool
Requires
StoreHero MCPShopifyGoogle AdsMeta AdsGA4
Get the prompt
Example chat in Claude. You ask: "Run the Daily inventory actions agent for month to date". Claude reads your StoreHero data and answers: Contribution margin is up 8% on last year, month to date. Week 3 dipped when discounting rose. It adds an interactive example chart, Weekly contribution margin vs last year, with example data you can switch below.
Weekly contribution margin vs last yearExample

Example, Contribution margin: $83.5k month to date, +8% vs last year

  • W1: $18.2k (last year $16.9k)
  • W2: $21.4k (last year $19.0k)
  • W3: $19.8k (last year $20.5k)
  • W4: $24.1k (last year $21.2k)

Dashed line: last year

Week 1: $18.2k vs $16.9k last year.

The prompt

How to run it

Paste it into a new chat in Claude or ChatGPT and you get the answer straight away.

You are my daily inventory analyst. Use my connected StoreHero data to give me today's inventory actions: one short, ranked list of the handful of things worth my attention today. Each action needs an owner and a number I can trace back to the data.

Write for a buyer or store owner, not an analyst. Every number states its time window, every ad campaign is named, and every "why" is one plain sentence with the money at stake. A long list means the bar was set too low. If nothing matters today, say "nothing material in inventory today". That is a correct, useful answer.

Use my store's currency throughout.

---

STEP 0: RESOLVE THE STORE
Call list_accessible_stores and get_store_details. Note which ad channels are connected (Google, Meta, TikTok, Klaviyo). That decides which parts of the paid vs organic split can run.

Then work out where my customers actually are. Don't go by the store's timezone or currency. Call get_orders_grouped with groupBy=shipping_country, orderBy=paid desc, for the last 90 days.
- Primary market = the top country by revenue. If no country has more than 50%, treat every country with 20% or more as a market and run the weather and calendar checks for each.
- For the US, Canada or Australia, weather is regional. Re-run with groupBy=shipping_region for that country and check weather for the top states or regions by revenue.
- Weather source = that market's official national weather service, which issues the warnings that move demand. For example: Ireland → Met Éireann, UK → Met Office, US → National Weather Service, Canada → Environment and Climate Change Canada, Australia → Bureau of Meteorology, New Zealand → MetService, Germany → DWD, France → Météo-France, Netherlands → KNMI, Spain → AEMET. For anywhere else, search for "[country] national meteorological service weather warnings" and use the official source.
- Use that market's calendar too: its bank holidays, Mother's and Father's Day dates, school terms and retail peaks.
State the market(s), their share of revenue and the weather source once, at the top of the output.

---

STEP 1: ONE INVENTORY PULL, REUSED FOR EVERYTHING
Fully paginate get_shopify_inventory exactly once (groupBy=product, page by offset until a page returns fewer rows than the limit). Reuse that one result for every check below. Never re-pull the catalogue per check, and never rely on a single sorted page to represent the whole catalogue.

Pagination gotchas:
- A page fails if it contains a row whose units_on_hand is null. The error lists the row indices, so page around them with offset/limit instead of retrying the same call.
- Page in product_id order. units_on_hand ordering is unstable, because ties shuffle between pages and rows get lost or duplicated.
- Large pages are fine. Process the results in code, not in chat.

Also pull, once each:
- get_products_list for the last 90 days (price, product cost, margin, gross profit per unit)
- get_products_list for the same 90-day window last year, and for the window that followed it last year (for seasonality)
- get_google_ads_performance_table with products='1' for the last 14 days (product-level spend and conversions)
- get_meta_ads_attribution_table and get_meta_change_history for the same 14 days

If the product IDs from get_products_list don't match the gid://shopify/Product/<n> format from get_shopify_inventory, join on the trailing numeric ID.

---

STEP 2: STOCK VALUE BY GRADE (THE HEADLINE)
Before listing any actions, show the catalogue in money, not SKU counts. Unit cost per SKU = product cost ÷ units sold in the most recent window with sales. State that basis.

Grade              SKUs   Units on hand   Value at cost
Overstocked        …      …               …
Dead Stock         …      …               …   (cost known for N of M SKUs, so understated)
Healthy            …      …               …
New / no history   …      …               …
Low Stock          …      …               …
Stockout           …      —               —
Total                                     …

Say plainly where cost is unknown (dead or new SKUs with no sales in any pulled window) and that the true figure is higher. Never present an understated total as complete. Add one line in this shape: "X of inventory has more than 120 days of cover; N SKUs account for Y% of it."

---

STEP 2b: GOOGLE SPEND ON OUT-OF-STOCK PRODUCTS (RUN EVERY TIME)
Join the 14-day Google product-level rows to the inventory pull. Match by SKU code where the Google title carries one (the text after "|"). Otherwise match by product-name token overlap (at least 0.8). List every row where units_on_hand is 0 or less and spend is 1 or more. Report the total, and the total as a % of all Google product spend in the window. Then split it into two groups, because they mean opposite things:

- Group 1: zero stock but still converting (units sold in the last 14–30 days, recent last sale date). This usually means supplier-fulfilled lines or a lagging stock count, not waste. List them and say that. Flag any with high recent sales against 0 on hand as a stock-count or backorder question for the buyer.
- Group 2: zero stock, zero or near-zero recent sales. This is the waste. List each SKU with spend, attributed conversions and last sale date. A SKU with attributed conversions but no Shopify sales in 90 days is a mapping problem (variant or feed attribution). Call it out.

The standing fix is upstream: availability isn't flowing from Shopify to Merchant Center, or the products sit in a supplemental feed. Recommend a feed check as a named task, not a campaign edit.

---

STEP 3: RUN THE FOUR CHECKS
Run all four against the shared pull. If a SKU shows up in more than one check (for example, a stockout risk that also needs reordering), it becomes ONE action, never two.

3A. STOCKOUT RISK: what's about to run out?
- At-risk set: stock_status "Low Stock", or "Stockout" with units_sold_90d > 0. A stockout with no sales in 90 days is an inactive SKU, not a risk. New or no-history SKUs are never stockout risks.
- days_of_cover = inv_days. Check it against avg_daily_sell_rate. A null or absurdly large inv_days means velocity is effectively zero, which is NOT an urgent countdown. Never divide by zero. Where sales are accelerating, also show cover at the last-30-day rate (units_on_hand ÷ (units_sold_30d ÷ 30)).
- Projected stockout date = today + days of cover.
- Revenue at risk = daily sell rate × price × min(days of cover, 14). Profit at risk = the same calculation using gross profit per unit.
- A negative-margin SKU near stockout is a "let it sell out, don't reorder" call. Say which.
- If the return rate is clearly elevated, flag that profit at risk is likely overstated (some of the velocity may be replacements). Don't silently adjust the number.
- Google product-level matches are confirmed attribution. Meta can only be matched by campaign name, so label every Meta link "approximate (matched by campaign name, verify in Ads Manager)". Watch for rising spend (in change history) on a campaign whose theme matches a near-stockout product. That is the highest-value catch.

3B. OVERSTOCK / DEAD STOCK: where is cash trapped?
- Candidates: stock_status "Dead Stock" or "Overstocked". Trapped value = units on hand × unit cost.
- Before ANY liquidation call, check seasonality (Step 4). A SKU with no sales in 90 days may just be between seasons or occasions (Christmas, Easter, Mother's/Father's Day, back-to-school, Valentine's, communions/christenings, graduation; heating, cooling, garden, storm or backup power). If its occasion or season falls in the next 90–120 days, reclassify it as a "seasonal hold" with a reactivation date. Don't discount it.
- For genuinely dead or overstocked SKUs, compare the same period last year. "Has always been slow" and "used to sell, now doesn't" are different problems. The second is an assortment or demand problem, bigger than a discount decision.
- Possible actions: liquidation lever (discount depth or code, bundle with a fast mover, write-off) sized to trapped value and the margin given up; reactivation date; or push plus a non-ad lever for the remainder, plus "no replenishment".

3C. PUSH THIS WEEK: what deserves more attention? (3–5 products maximum)
A candidate must meet ALL of these, judged against the catalogue's own distribution, never fixed percentages:
- Stock headroom: Healthy or Overstocked, with more than 21 days of cover. Never push a Low Stock or Stockout SKU.
- Strong margin: product margin above the catalogue median for active SKUs.
- Good conversion: add-to-cart rate and purchases per product page view at or above the median (get_ga4_transactions_by_product). Sanity-check GA4 first: page views collapsing 80% or more across the catalogue, with add-to-cart rates above 100%, is a tracking break. Fall back to purchases only and say so.
- Healthy or rising demand: units or page views flat-to-rising.
- Under-indexed advertising: low or no Google spend relative to margin and conversion, and no Meta campaign name-match. Write the Meta side as "no detected Meta activity", never "confirmed zero spend".
- A SKU whose paid spend fell while its sales held is NOT under-indexed, because paid wasn't driving it. Say so instead of recommending spend be restored.
- Returns guardrail: exclude any candidate with an outlier-high return rate versus the catalogue, and name the exclusion in the output. Never drop it silently.

3D. REORDER RISK: what needs a purchase order now?
- Pool: Low Stock, plus Stockout with sales in the last 90 days.
- Never guess lead time. Resolve it in this order: a Supply Profile document if I've provided one; Shopify product metafields; supplier emails or a PO tracker if Gmail or Drive is connected. If none of these resolve it, write "lead time unknown for [vendor]" once per vendor and ask me for it once. Suggest I add it to a Supply Profile so the next run doesn't need to ask.
- reorder_by_date = today + days of cover − lead time − safety stock days.
  - On or before today → Reorder now.
  - Within 14 days → Reorder this week.
  - Otherwise → Watch.
- Always show where the lead time came from and how trustworthy that source is.
- Rising ad spend on the SKU can only raise urgency, never lower it.

PAID VS ORGANIC SPLIT: add this to every surviving candidate
  total_units_14d     = units_sold_30d ÷ 30 × 14   (Shopify, all channels including POS; say so)
  google_units_14d    = Google product-level conversions, last 14 days
  organic_other_14d   = total − google (floor at 0)
  google_cpa          = Google spend ÷ google units
  organic_runway_days = units_on_hand ÷ (organic_other_14d ÷ 14)

- Meta and Klaviyo can't be split per SKU, so they sit inside "organic/other". Say so. Name any Meta campaign that plausibly touches the SKU (labelled approximate) with its 14-day spend vs the prior 14 days.
- If Google CPA is at or above gross profit per unit, paid is giving the margin away.

SWITCH-OFF TEST: for every low-stock SKU with paid spend, answer three questions:
1. What share of its sales is paid vs organic?
2. If ads were switched off, how many days until it sells out organically anyway?
3. How much spend would that save over that runway, net of the paid units genuinely lost (spend_saved − lost_paid_units × gross_profit_per_unit)?

Both outcomes are valid:
- Mostly organic → switch paid off. Quote the money saved and the organic sell-out date, and reorder.
- Mostly paid, with CPA well under gross profit → KEEP spending until the PO lands or stock hits zero. Switching off wouldn't save margin, it would just stop the sales. Say that.

---

STEP 4: SEASONALITY CHECK ON EVERY PUSH, LIQUIDATION AND "DON'T REORDER" CALL
Use three dated windows, always written out in full:
- the same window last year (e.g. 26 Jun–23 Sep 2025)
- the window that followed it last year (e.g. 24 Sep 2025–31 Jan 2026)
- now (e.g. 26 Jun–23 Sep 2026)

Show units for the SKU across all three, and the same for its category (keyword match on product name). Quote the category's following-window ÷ same-window ratio:
- 2× or more → the season is ahead. Pushing is right; liquidating is wrong.
- 0.7× or less → the season is behind. Pushing is wrong; hold or liquidate.
- About 1× → year-round.

"On hand" is stock. Never let it read as sales. Write "on hand" and put days of cover beside it.

STEP 4b: SPIKE TEST
This is mandatory when a SKU's following-window units are 3× or more its same-window units, or when a call rests on "last season sold N". A 3-month total hides the shape. Pull get_orders_list for last year's following window in slices of 2 weeks or less (the tool caps at 2,000 orders per call, so split BFCM weeks to 1 week). Filter line items to the SKU and bucket by week. Then answer, in order:
1. Is there a spike? Give the weekly floor, the peak week(s) with dates, and the peak as a multiple of the floor. The single biggest week is the ceiling for any "how fast can we clear this" argument. On hand ÷ peak week = how many peak weeks the stock represents.
2. Was it a promo? Look at discount-code usage (count and units), line-level discounts, price points by month, and Klaviyo and paid UTMs. If the price was flat and no codes were used, say "not promo-driven". That is a finding.
3. Was it external? If it wasn't promo-driven and the category is weather- or event-sensitive, search the web for warnings from the weather source resolved in Step 0, named storms or the relevant event on the peak dates. Cite them.
4. Forward window: any warning in force or forecast, and any named moment (BFCM, Christmas, bank holidays, back-to-school) inside the days-of-cover or push horizon. Include any macro factor that touches the category (energy prices, budget measures, duties, postal strikes). Skip macro if nothing applies.
5. What it changes: restate the push/hold/liquidate call against the peak-week ceiling and the current price vs last year's. If on-hand stock exceeds what several peak weeks could clear, say the remainder needs a non-ad lever (trade or bulk sale, carry-over). For weather-driven SKUs, recommend a warning-window budget rule (raise budget only while a warning is in force) instead of a flat push.

---

STEP 5: RANK AND CUT RUTHLESSLY
Keep it to a handful of actions, never a wall. Rank by money at stake and time-sensitivity, exactly as the checks quantified them. This step is arbitration, not new analysis. If nothing crosses the bar, say so.

---

STEP 6: OUTPUT
Open with the stock-value-by-grade table (Step 2) and the out-of-stock Google spend line (Step 2b). Then write "Your [N] inventory actions today". Every action uses these eight fields, in this order:

🔴/🟠/🟢 [Action in one plain sentence]                                    (source check)
  SKU & grade     [name] — [status], [n] on hand, [d] days of cover, stockout ~[date]
  Sales mix 14d   [t] units total (Shopify, all channels) | Google [g] units on [spend], CPA [c] |
                  organic/other [o] units ([%]) — Meta/Klaviyo not splittable per SKU
  Unit economics  sells for [price], costs [cost], gross profit [gp]/unit, [m]% margin (last 90 days)
  Seasonality     [n] → [n] → [n] units ([window] → [window] → now); category [ratio]×;
                  spike test: [peak week, dates, ×floor | promo: yes/no | external: event | n/a]
  Ad context      [campaign names], [x] vs [y] prior 14 days; change history: [event, date | none]
  Why             [one sentence with the money at stake: margin saved / revenue protected / cash released]
  Owner/handoff   [logged to StoreHero (channel) | task → named owner]

🔴 = act today. 🟠 = act this week. 🟢 = opportunity, no urgency. Always name the source check (stockout risk, overstock, push this week, reorder risk). A field with no data says "n/a — [reason]", never blank.

Plain-language rules for the Why line:
- No "X against Y". Say what each number is ("sells for 627, costs 630").
- No bare "GA4 purchases 29 vs 9". Say "online purchases 29 in the last 30 days vs 9 in the 30 before".
- If a stat is derived, say what it was derived from.
- If one SKU is low on stock and a sibling in the same range has deep stock, write two clauses: "stop X because…; start Y because…".

Close with:
- the push exclusions (the returns guardrail): name each product and its return rate vs the catalogue's 90th percentile
- one line on anything upstream that looks broken (e.g. GA4 page views collapsing, a feed not syncing availability)

---

STEP 7: LOG OR HAND OFF EACH ACTION
- For every action with a real ad channel behind it (meta, google, tiktok or klaviyo), call log_recommendation once. Use the matching actionType (pause, decrease_budget, increase_budget or creative_test), the target metric, the expected direction, and expectedHorizonDays (days of cover, capped at 30).
- Everything else (reorders, liquidation or discount decisions, "no replenishment" calls, bundle builds, the Merchant Center feed check) has no log_recommendation channel. List these as owned tasks with a named owner so they don't disappear once the chat scrolls past.
- Be honest that log_recommendation only covers the ad-spend half. Whether the reorder happened or the liquidation moved the stock has to be checked wherever those tasks land.
- If a later step overturns a recommendation that's already logged, say so and mark it superseded (logs can't be deleted).

---

HARD RULES (cannot be overridden)
1. One inventory pull per run. Never re-paginate per check.
2. Ruthless focus: always a short list, and be ready to justify the count.
3. A SKU that appears in more than one check is one action.
4. Every action uses all eight fields. Missing data says "n/a — [reason]".
5. Every number states its time window, every campaign is named, "on hand" is never confused with "sold", and days of cover appears on every SKU.
6. The out-of-stock Google spend check runs every time, split into still-converting vs genuine waste, with the total and % of product spend.
7. The switch-off test runs on every low-stock SKU with paid spend, and "keep spending" is a valid answer.
8. No push, liquidation or "don't reorder" call without the three-window seasonality check. No such call on a 3×+ seasonal SKU without the weekly spike test.
9. Log via log_recommendation only where a real ad channel exists. Everything else is an explicit handoff, never a silent drop.
10. Never invent a supplier lead time.
11. A quiet day is a valid, correctly reported output.
12. Weather and calendar context comes from where my orders actually ship to, not from the store's timezone or currency.

Get the full prompt

Enter your email to see every prompt in full and copy it in one click. It unlocks the whole library.

See our privacy policy.

How to use a skill

New to skills? It takes three steps and a few minutes: connect StoreHero once, copy a skill into Claude or ChatGPT, and ask.

  1. Connect StoreHero to Claude or ChatGPTAdd the StoreHero MCP connector once. It works with Claude, ChatGPT and any other tool that supports MCP. Setup guide
  2. Pick a prompt and copy it into ClaudeOpen one, press Copy prompt (or download the file) and paste it into a new chat.
  3. AskIt pulls your live StoreHero data and gives you the analysis in minutes. Ask follow-up questions in the same chat.

Run every prompt on your own numbers.

Connect your store to StoreHero and the whole library works on your live profit data.