← All work

Noon · 2025

Automated Fresh Pricing & Markdown Engine

Reducing waste while protecting sell-through and margin recovery

Pricing & Monetization | Platform & Operations

A day in a dark store

Illustrative

  1. 6 AM

    Inbound complete

    Fresh stock is on shelves. Morning demand peak hits.

  2. 6 PM

    Impression boost

    At-risk SKUs are promoted in search and browse. No price change yet.

  3. 8 PM

    Eligibility + dynamic price change

    Expiring stock identified. Below forecast, prices nudge up. In excess, markdowns fire. Reruns hourly until midnight.

  4. 12 AM

    Reset, disposal + inbound

    Markdown ends and prices revert. Expiring SKUs are disposed of and the new inbound cycle begins.

Principle · Recover with visibility before sacrificing margin with markdown.

Role
Product Manager
Timeline
~3 weeks
Worked with
Category, Supply Chain, Dark Store Ops, Pricing
Tools
Python, SQL, BI dashboards, price elasticity model

The problem

Every night, short shelf-life stock expired and was thrown away as a total loss. No trigger, no recovery, just manual work per store, per SKU.

What I did

Built an end-to-end engine (design, code, deployment and post-launch, across both markets) that boosts visibility first, then reprices hourly against sell-through curves inside margin floors.

The result

$13K/day in write-offs eliminated on perishables that drive 50% of platform GMV, with CPO down ~43% and disposal rate down ~50%.

What 12 AM looked like before

Every night, low keep-life SKUs reached expiry and were disposed of as a total loss. No secondary channel, no recovery. The loss was a daily certainty, and it grew with every forecast error that caused an over-order earlier in the day.

  • No automated trigger of any kind
  • Manual intervention per store, per SKU, every night
  • Expiring SKUs disposed of as a total loss
  • Disposal costs reaching double digits as a percentage of fresh GMV
  • Loss compounding with every over-order
  • No way to recover category margin

Four levers, every evening

Each module makes one decision. Together they run on the clock from 6 PM to midnight.

  1. 01

    Impression boost6 PM

    At-risk SKUs promoted in search and listings. No price change, no markdown signal.

  2. 02

    Eligibility + quantity8 PM

    Every SKU × store checked: what is expiring, what is underforecasted, and how much to act on.

  3. 03

    Price uplift on scarce stock8 PM

    Where inventory tracks below expected sales, prices nudge up to recover category margin.

  4. 04

    Dynamic markdown8 PM

    Excess and expiring stock marked down, with depth set by elasticity and deepened hourly until midnight.

6 PM: visibility before price

At-risk SKUs move up in search and listing pages at 6 PM. There's no price change and no customer-facing markdown, so organic demand gets a two-hour window to absorb the excess at full margin.

  • What it does

    Promotes flagged SKUs to top positions in search and listing rankings.

  • Why it matters

    Many perishables are thrown away only because they weren't visible enough. Boosting placement costs nothing and keeps full margin.

  • Why markdown waits

    Markdown only fires at 8 PM, for whatever organic demand couldn't absorb in the boost window.

At-risk SKUs move up from 6 PM, with no price change and no markdown signal.

8 PM: what to act on

When the boost window closes, every SKU × store pair is checked against its live inventory signal. Stock tracking below expected sales gets a small price uplift. Stock in excess gets a markdown. Both run at the same time but aren't symmetric: the uplift stays conservative because a price rise can kill conversion, and the markdown goes deeper because clearing expiring stock comes first.

The decision flow

How fresh produce is tracked without an expiry date

Fresh produce has no expiry printed on the pack, so the system tracks each inbound batch by date. At 8 PM, only units from batches that hit their keep life today are flagged. Newer batches stay in normal stock.

Example: 100 units in stock at 8 PM

50 units arrived this morning. Keep life intact, so they're excluded.

50 units carried over from yesterday. Keep life ends tonight.

Liquidation pool

Only the 50 carried-over units enter the markdown pool.

How much to clear

Liquidation qty = Expiring stock − Expected natural sales

  • Expiring stock: all units of a SKU × store whose last sellable day is today.
  • Expected natural sales: what the store is expected to sell at full price before close.
  • Liquidation qty: what's left, which the markdown has to move.

Three checks keep the demand heatmap honest

  • Layer 01

    Elasticity-adjusted history

    Post-8 PM sales at markdown prices are normalised back to full-price demand before they enter the heatmap. Otherwise the model under-liquidates.

  • Layer 02

    Instock filter + ptype fallback

    Hours below 80% in-stock are excluded. Very low-velocity SKUs fall back to product-type level for the store.

  • Layer 03

    Impression weighting

    75% historical add-to-cart distribution, 25% in-app impression share by hour. Hot SKUs convert without a deep markdown.

Intraday sales and the two intervention windows (illustrative)

8 PM: recovering margin

Where the eligibility engine finds underforecasted SKUs, a small price uplift goes on at those SKU × store pairs. It partly offsets the margin given up by markdowns on expiring stock, without the customer noticing.

Category PC1, 8 PM onwards: markdown drag, offset by uplift recovery (illustrative)
  • Rule 01

    Uplift ceiling

    Never exceeds a defined percentage above the reference price.

  • Rule 02

    Signal-dependent

    Automatically reversed if sell-through slows.

  • Rule 03

    Approval required

    Category manager notified on strategic SKUs.

Markdown depth, hour by hour

A price elasticity model sets the depth needed to move the target quantity. The bigger the excess relative to expected sell-through, the deeper the markdown. It's recalculated every hour until midnight.

Markdown depth is based on remaining excess. Not a blanket discount.

Guardrails

Automation only earns trust when people can override it cleanly. Four guardrails wrap the whole pipeline, so category managers stay in control.

  1. 01

    Margin floor

    Markdown never breaches a minimum margin without category manager approval.

  2. 02

    Instock buffer

    Liquidation qty = suggested qty minus one unit, which keeps the availability signal alive.

  3. 03

    PC1 monitoring

    If category margin is already compromised, an approval email is triggered automatically before proceeding.

  4. 04

    Disruption pause

    Festival spikes, campaigns or local ops issues pause the model for the affected stores automatically.

A separate expected-sale model

The risk

Reusing the ordering forecast for liquidation.

An over-order would also underestimate the liquidation quantity. Both errors compound in the same direction, and waste builds up silently.

What we built

An independent intraday expected-sale model.

Seeded from live morning sales, not the ordering forecast. With independent error sources, an over-order can no longer quietly cause under-liquidation.

Only a partial share of liquidation units feeds back into forward demand forecasts, so the system never starts forecasting for markdown sales. As accuracy improves, that share is designed to shrink automatically.

Results

Launched in early 2025 with fruits and vegetables, then expanded to bakery, dairy, milk, meat and seafood, ready-to-eat, deli and flowers. Around 70% of the disposal improvement is attributed to this system, alongside gains in forecasting accuracy.

Disposal down across categories; CPO on target in both markets. Exact figures withheld.

What I'd build next

  • Next 01

    Supplier shelf-life accountability

    SKUs that keep entering liquidation are often a supplier problem, not a demand problem. Flag suppliers whose inbound shelf life falls short of what they declare, and feed it into category and procurement reviews.

  • Next 02

    Two-way pricing for non-perishables

    The same inventory-signal pricing works for overstocked non-perishables. Judge eligibility on days of cover instead of keep life, and take dynamic pricing beyond fresh.

  • Next 03

    Forecasting feedback loop

    A SKU that hits liquidation too often in 30 days should trigger a recalibration of its ordering model, not just a flag. Fix the over-ordering at the source.

Built at Noon · 2025