Pricing Review

Last updated June 4, 2026

The Pricing Review module turns each SKU's cost, demand, and (optional) competitor price into a defensible recommended price for next year, plus a forecast of units, revenue, and gross margin at that price. It is built for the annual or quarterly price reset most distributors, wholesalers, and retailers run once a year.

How to use it

  1. Upload an inventory file at /app/upload that includes at least sku, unit_cost, and a current_price column. (Headers like Selling Price, Retail Price, List Price, MSRP are auto-mapped.)
  2. Optionally include a competitor_price column (synonyms: Market Price, Benchmark Price) to get a high-confidence recommendation anchored to a known market signal.
  3. Run the diagnostic. Visit /app/pricing.
  4. Review the table. Sort by Priority. For each row you can accept the suggestion, override it inline, or click the three-dot menu to create a follow-up task for the responsible buyer.

SKUs missing current_price are tagged Investigate rather than guessed at — add the column to your upload and re-run when you have the data.

What you'll see — column definitions

Cost, Current, Suggested, Δ%

  • Cost: your landed unit cost from the upload.
  • Current: today's regular selling price.
  • Suggested: the price the engine recommends for the next period. Always at or above the cost floor; never more than ±15% from current; never more than 5% above a known competitor price.
  • Δ%: percent change between Current and Suggested.

Action

  • Raise: suggested price is above current. Either you're below your margin floor or the simulation found a higher-margin point within the legal band.
  • Trim: suggested price is below current. Usually triggered when you're > 10% over a known competitor; the lower price recovers volume faster than the per-unit margin loses ground.
  • Hold: current price already sits at the optimum. The simulator examined nearby alternatives and none beat it.
  • Investigate: the engine could not run. Most common cause: missing current_price on that row.

Proj. units / Proj. revenue / Margin lift

  • Proj. units (P50): median forecasted units sold over the next 12 months at the suggested price. Half of the simulation draws land above this number, half below.
  • Proj. revenue (P50): median forecasted annual revenue at the suggested price.
  • Margin lift (P50): median expected gross margin $ change versus holding the current price. The number the optimiser is actually trying to maximise.
  • Margin band (P5–P95): the honest worst (P5) and best (P95) case. 90% of the simulated outcomes fall inside this band. A tight band means the math is confident; a wide band means elasticity and demand uncertainty have meaningful sway.

Priority, Confidence, Elasticity

  • Priority — P1 (do now: actively selling below the margin floor with high inventory value), P2 (this week: large margin change at stake), P3 (this month), P4 (informational).
  • Confidence — High when both a competitor anchor and month-to-month sales variability are available; Medium with one; Low with neither.
  • Elasticity — the μ ± σ used for this SKU. See How the engine works below. Hover the cell for the source (category prior vs. workspace default).

How the engine works

The recommendation is the output of a seeded Monte Carlo simulation. For each SKU:

  1. A candidate-price grid is built from max(cost_floor, current − 15%) to current + 15%, capped at competitor × 1.05 when a competitor price exists. Around 9–11 prices including the current price and .99 endpoints (when psychological pricing is enabled).
  2. For each candidate, 400 simulated years are run. In each simulation:
    • Price elasticity ε is drawn from a normal distribution N(μ, σ²) using a per-category prior (see table below).
    • Baseline annual demand D₀ is drawn from a normal distribution centered on the SKU's observed annual sales, with σ from its monthly variance (20% CV fallback when no variability history exists).
    • Units sold are computed from the constant-elasticity demand curve Q(P) = D₀ · (P / current)^ε.
    • Revenue and margin are computed at the candidate price.
  3. The candidate with the highest median margin (P50, not mean) wins. Median beats mean here — it's robust to the long-tail draws that show up when demand variance is high. Ties go to the candidate closest to the current price.
  4. P5/P50/P95 of margin and revenue at the winning price are reported alongside the same percentiles at the current price, so the lift band is honest about model uncertainty.

Per-category elasticity priors

Elasticity is the percent change in units per percent change in price. More-elastic categories (commodity produce, beverages) respond more aggressively to price; less-elastic ones (specialty meat, branded paper) less so. Defaults reflect the rough consensus from retail / grocery literature; tune the workspace default in Settings.

  • Produce / Fresh / Beverages / Snacks — μ ≈ −1.4 to −1.5, σ 0.40
  • Bakery — μ −1.1, σ 0.35
  • Dairy / Frozen / Dry Goods / Pantry — μ −0.9 to −1.0, σ 0.30
  • Meat / Seafood — μ −0.8, σ 0.30
  • Supplies / Cleaning — μ −0.7, σ 0.30
  • Paper — μ −0.6, σ 0.25
  • Unknown category — workspace default (−1.2), σ 0.40

When a SKU has no month-to-month sales variability (Unknown cov_class or null sales_stddev_monthly), σ is widened by 50% so the confidence band honestly reflects what the model can't see.

Hard rules (always applied on top of the math)

  • Cost floor: suggested price ≥ unit_cost / (1 − target_margin). With the default 35% margin target, this is unit_cost / 0.65. Tune in Settings.
  • Year-over-year band: suggested price is always within ±15% of current when a competitor anchor is known, and within ±7.5% when no competitor is given — see the "no competitor pricing" section below for why we narrow it. Bigger moves belong in the Markdowns or Promotions modules, not in the regular-price tool.
  • Market cap: never more than 5% above a known competitor price. The pure constant-elasticity math will happily say "raise into the cap" when you're already overpriced — that's mathematically valid and commercially terrible. This cap blocks it.
  • Psychological pricing: when enabled, candidates are snapped to the nearest .99 endpoint. Turn off in Settings if your category prices to round dollars.

What if I don't have competitor pricing?

The engine still runs. Most TROPIX customers don't have clean competitor data, especially in B2B / distribution where the market price is genuinely opaque. The recommendation will be more conservative, and you should understand why.

What changes when competitor_price is missing:

  • The YoY band tightens from ±15% to ±7.5%. With no market anchor, constant-elasticity math always wants to push toward the upper cap for inelastic categories (ε in the −0.7 to −1.0 range). Letting it recommend "raise to +15%" on every row based on a generic prior is bad advice. Halving the band turns that into "raise to +7.5%" — a modest move that doesn't bet a year of customer goodwill on a prior we never measured for your customers.
  • The Trim action label can no longer fire from the "priced above market" rule (we don't know the market). It can still appear if the optimiser finds a lower-margin point preferable, which happens for highly elastic categories.
  • Confidence caps at Medium (High requires both a competitor anchor and month-to-month variability).
  • Everything else — cost floor, elasticity prior, demand uncertainty, MC simulation, percentile reporting — is identical.

How to make the recommendations stronger: even a rough competitor signal helps. Sources:

  • Public shelf prices from the largest 2–3 distributors / retailers in your category (a once-a-year spot check is enough — you're not running a price-war engine).
  • A trade benchmark report (NACDS, FMI, NRF, your category association).
  • Distributor-to-distributor pricing from a syndicated source (Numerator, NielsenIQ, IRI) if you have one.
  • Internal POS-equivalent: if you sell across multiple channels, the price you charge a competing wholesaler.

Add a competitor_price column to even a subset of your SKUs and the engine will produce High-confidence recommendations for those rows while keeping conservative recommendations on the rest.

Determinism

Every recommendation is fully deterministic: the per-SKU random-number seed is a hash of the SKU, unit cost, current price, location, and category. The same upload always produces the same numbers. Customers losing trust in recommendations that wobble between page loads kills these tools faster than imperfect numbers do.

FAQ

Why is my margin band so wide?

Three usual causes: (1) unknown category — we fall back to the workspace default elasticity with a wide σ, (2) no monthly sales history — demand variance is guessed at 20% CV instead of measured, (3) no competitor price — the market anchor is missing. Adding any one of these tightens the band.

Why didn't it suggest a bigger price increase?

The recommendation respects three guardrails: cost floor, ±15% year-over-year band, and the competitor + 5% cap. The optimiser inside the band picked the candidate that maximised median margin — exceeding the band would have to be a manual decision.

Why did my recommendation change?

Recommendations are deterministic per upload. If you re-ran the diagnostic with a new file, anything that changed in the inputs — cost, current price, sales history, category — can shift the seed and the elasticity prior, which moves the suggested price.

What happens if I override the suggestion?

Nothing automatic — the engine doesn't enforce its recommendations. The recommendation is advisory; pricing decisions remain a buyer judgement. Create a task from the row to record your decision and assign follow-up.

What's in the roadmap?

  • Fitted per-SKU elasticity: once a workspace has two or more runs spanning an actual price change, the engine will estimate that SKU's own elasticity from the realised unit response and override the category prior.
  • Cross-elasticity: when SKU A's price rises, some demand shifts to SKU B. Important for private label vs. branded pairs.
  • Per-category margin and elasticity overrides in the Settings UI (currently editable only via DB).

Limitations to know about

  • Elasticity priors are based on category averages, not your customers' observed behaviour. Treat the confidence band as a model uncertainty, not a sales forecast.
  • The engine assumes a stable category mix, channel, and competitive set over the forecast year. Major launches, channel shifts, or price wars are outside its scope.
  • Promotional pricing and clearance markdowns are handled separately in the Promotions and Markdowns modules — the Pricing Review is for regular shelf prices only.

Questions or surprises in your data? Send a note from /app/feedback — we read every one.