| title | Retail Markdown | ||||
|---|---|---|---|---|---|
| description | Set discount levels across weeks to maximize revenue while clearing inventory. | ||||
| featured | false | ||||
| experience_level | intermediate | ||||
| industry | Retail & Consumer | ||||
| reasoning_types |
|
||||
| tags |
|
Retailers often face the challenge of clearing seasonal inventory before it loses value. Markdown optimization determines the best discount schedule across a planning horizon to maximize total revenue -- including both sales revenue and the salvage value of any remaining stock. Discounts stimulate demand but reduce per-unit revenue, so the trade-off must be carefully balanced.
This template finds the discount schedule that maximizes revenue across a multi-week horizon, respecting a price ladder (discounts only deepen over time) and finite inventory, and crediting the salvage value of whatever is left at the end. It captures the full trade-off between aggressive discounting to drive volume and preserving margin on high-value items.
The reasoning approach uses prescriptive optimization: a mixed-integer program that picks one discount level per product-week and tracks the resulting sales and cumulative inventory.
- Retail pricing and merchandising analysts optimizing markdown schedules
- Operations researchers working with mixed-integer programming
- Data scientists exploring multi-period optimization with binary decisions
- Assumed knowledge: comfortable reading Python; the pricing and optimization terms are explained as they come up
- A revenue-maximizing markdown schedule (one discount level per product per week), produced by prescriptive reasoning (mixed-integer program)
- A price ladder that prevents discounts from reversing week to week
- A demand model combining base demand, discount lift, and weekly seasonal multipliers
- Per-week sales and cumulative-inventory tracking, bounded so cumulative sales never exceed initial stock
- A total-revenue figure that credits end-of-horizon salvage value on unsold units
retail_markdown.py-- Main script defining the MIP model with discount selection, sales tracking, and revenue optimization- Runbook:
runbook.md— a paste-testable walkthrough that reproduces the template step by step with the RAI skills; as important a reference as the script itself. data/products.csv-- Products with initial price, cost, inventory, base demand, and salvage ratedata/discounts.csv-- Discount levels with percentage and demand lift factordata/weeks.csv-- Planning weeks with seasonal demand multiplierspyproject.toml-- Python package configuration with dependencies
- A Snowflake account that has the RAI Native App installed.
- A Snowflake user with permissions to access the RAI Native App.
- Python >= 3.10
-
Download ZIP:
curl -O https://docs.relational.ai/templates/zips/v1/retail_markdown.zip unzip retail_markdown.zip cd retail_markdown[!TIP] You can also download the template ZIP using the "Download ZIP" button at the top of this page.
-
Create venv:
python -m venv .venv source .venv/bin/activate python -m pip install --upgrade pip -
Install:
python -m pip install . -
Configure:
rai init
-
Run:
python retail_markdown.py
-
Expected output:
Status: OPTIMAL Total revenue (sales + salvage): $23374.65Discounts start shallow (20%) and deepen to 30% later in the season; no product needs the 50% tier. See
runbook.mdfor the full discount, sales, and cumulative-sales schedule.
retail_markdown/
├── README.md # this file
├── pyproject.toml # dependencies
├── retail_markdown.py # main script (MIP model, solve, result tables)
├── runbook.md # analyst-facing walkthrough
└── data/
├── products.csv # products with price, cost, inventory, base demand, salvage rate
├── discounts.csv # discount levels with percentage and demand lift
└── weeks.csv # planning weeks with seasonal demand multipliers
Start here: run python retail_markdown.py for the full run end to end, or follow runbook.md to reproduce it step by step with the RAI skills.
The bundled data is small and illustrative — a short seasonal clearance across a handful of products, sized so the model solves instantly while showing the full workflow.
products.csv— one row per product, withinitial_price,cost,initial_inventory,base_demand(units per week at full price), andsalvage_rate(fraction of price recovered on leftovers).discounts.csv— one row per discount tier, withdiscount_pct(percent off) anddemand_lift(demand multiplier at that discount). Includes alevel0 / 0% tier so "no markdown" is always an option.weeks.csv— one row per planning week, withdemand_multiplier(seasonal factor applied to base demand).
- Key entities:
Product— an item to mark down, with its price, cost, stock, demand, and salvage economics;Discount— a discount tier defining how much price is cut and how much demand rises;Week— a period in the planning horizon, with its seasonal demand factor. - Primary identifiers: string
nameonProduct; integerlevelonDiscount; integernumonWeek. - Important invariants: exactly one discount level is active per product-week; discounts can only deepen over successive weeks (price ladder); cumulative sales never exceed
initial_inventory;discount_pct,demand_lift, anddemand_multiplierare non-negative; the selection variable is binary and sales variables are non-negative.
For the full concept and property definitions, see retail_markdown.py; runbook.md builds them step by step with the RAI skills.
The pipeline loads products, discount tiers, and planning weeks, then builds a single mixed-integer program that chooses a discount for each product-week, tracks the resulting sales and inventory, and credits salvage value on whatever is left over.
CSV inputs → load Product / Discount / Week → decision variables (discount choice, sales, cumulative sales)
→ one-discount + price-ladder + inventory constraints → maximize sales revenue + salvage → solve → schedule
- Load the data. Products carry price, cost, starting inventory, base demand, and a salvage rate; discounts carry a percent-off and a demand-lift multiplier (including a 0% tier so "no markdown" is always available); weeks carry a seasonal demand multiplier.
- Set up the decisions. Three variable families capture the plan: a binary choice of which discount is active for each product-week, continuous units sold per product-week-discount, and cumulative units sold through each week. A
num_weekscount marks the final week for the salvage term. - Constrain the schedule. Exactly one discount level is active per product-week; discounts can only deepen from one week to the next (the price ladder); and cumulative sales can never exceed starting inventory.
- Maximize revenue. The objective adds sales revenue — discounted price times units sold — to the salvage value of unsold units at the end of the horizon, and the solver returns the revenue-maximizing discount schedule.
See retail_markdown.py for the implementation and runbook.md for the skill-driven reproduction.
Focus on the first changes most users will make.
- Replace the CSVs in
data/with your own, keeping the column names listed in Sample data above. - Keep a
level0 / 0% row indiscounts.csvso "no markdown" remains a feasible choice. - For Snowflake-backed runs, swap the
read_csv(...)calls formodel.data(snowflake_table).
- Discount levels — modify
discounts.csvto add finer or coarser tiers with different demand lifts. - Planning horizon — add or remove rows in
weeks.csv; the model scales with longer horizons. - Solver time limit —
time_limit_sec(default60) on theproblem.solve(...)call.
- Minimum margin constraint — add a constraint ensuring the discounted price always exceeds the product cost.
- Category-level constraints — group products by category and limit the total discount budget per category.
- Demand elasticity — replace the fixed demand lift with a price-elasticity function for more realistic demand modeling.
- Replace the CSV bundle with ingestion from your merchandising or point-of-sale tables.
- Mixed-integer programs grow harder with more products, weeks, and discount tiers; give the solver more time via
time_limit_sec, or accept a near-optimal solution by checking the MIP gap.
Problem is infeasible
Check that initial inventory is sufficient for at least one week of base demand. Also verify that the discount levels include a 0% option (no discount) so the model has a feasible starting point.
Solver is slow or times out
Mixed-integer programs can be computationally expensive. Reduce the number of products, weeks, or discount levels. You can also increase time_limit_sec or accept a near-optimal solution by checking the MIP gap.
rai init fails or connection errors
Ensure your Snowflake credentials are configured correctly and that the RAI Native App is installed on your account. Run rai init again and verify the connection settings.
ModuleNotFoundError for relationalai
Make sure you activated the virtual environment and ran python -m pip install . from the template directory. The pyproject.toml declares the required dependencies.
- PyRel v1 query language —
model.where(...),.per(...), aggregations, andmodel.select(...).
- Prescriptive reasoner — the
ProblemAPI, decision variables, constraints, and objectives.
- Multi-period optimization patterns — modeling week-over-week state (cumulative sales, price ladders) with indexed decision variables.
- File issues at the RelationalAI templates repository.