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
6 AM
Inbound complete
Fresh stock is on shelves. Morning demand peak hits.
6 PM
Impression boost
At-risk SKUs are promoted in search and browse. No price change yet.
8 PM
Eligibility + dynamic price change
Expiring stock identified. Below forecast, prices nudge up. In excess, markdowns fire. Reruns hourly until midnight.
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
$13K/day
write-offs eliminated
~43%
CPO reduction across markets
~50%
reduction in disposal rate
8
categories, scaled from FNV to all of Fresh
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.
- 01
Impression boost6 PM
At-risk SKUs promoted in search and listings. No price change, no markdown signal.
- 02
Eligibility + quantity8 PM
Every SKU × store checked: what is expiring, what is underforecasted, and how much to act on.
- 03
Price uplift on scarce stock8 PM
Where inventory tracks below expected sales, prices nudge up to recover category margin.
- 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.
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.
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.
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.
- 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.
Guardrails
Automation only earns trust when people can override it cleanly. Four guardrails wrap the whole pipeline, so category managers stay in control.
- 01
Margin floor
Markdown never breaches a minimum margin without category manager approval.
- 02
Instock buffer
Liquidation qty = suggested qty minus one unit, which keeps the availability signal alive.
- 03
PC1 monitoring
If category margin is already compromised, an approval email is triggered automatically before proceeding.
- 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.
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