Project FORESIGHT — step-by-step analysis walkthrough

NorthBay Living · Zidio Development Data Science internship · Eswar Mahalingam · every table below is computed from the pipeline outputs · ← back to the planning dashboard · API health
Raw rows ingested
42,131
4 tables
Analysis-ready rows
5,200
50 SKUs × 104 weeks
Backtest SKU-weeks
2,400
6 folds × 8 wk × 50
WAPE model / naive
8.75% / 11.16%
21.5% better
Holdout Dec-25
9.26%
80% band covers 79.5%
Sales at risk
₹47.9 L
8 reorder SKUs
Locked capital
₹1.48 Cr
8 markdown SKUs

1. Business problem & success metrics

NorthBay Living (D2C home & lifestyle, ~50 active SKUs in the extract, one warehouse) plans stock on spreadsheets. Best-sellers stock out; slow movers pile up. The brief asks for a weekly SKU-level forecast that beats a naive baseline, a stockout early-warning, an overstock flag, rupee impact, and a dashboard + scoring service the ops team can use unaided.

Success metricTarget (brief)Achieved
Forecast accuracyWAPE beats seasonal-naive on a rolling-origin backtest8.75% vs 11.16% (21.5% better); wins 49/50 SKUs
Risk precisionFlags right often enough to act onprobability-based; 80% interval calibrated at 79.5% on holdout; triage 8 / 8 / 34
Quantified impactRupee sales-at-risk and locked capital₹47.9 L at risk · ₹1.48 Cr locked
UsabilityOps can answer “what do I reorder?” unaidedReorder & markdown lists, filters, what-if, CSV export, API
ReproducibilityPipeline re-runs from raw with one commandpython src/run_all.py (~1 min), fixed seed, 5 tests

2. Data inventory & profiling

1
Ingest — four CSV extracts read with pandas; columns renamed to the brief’s data dictionary (snake_case). A fifth file, Stock_data.csv (equity prices, ticker AABA 2006–2017), has no join key and is excluded.
2
Profile — dtypes, nulls, uniques, ranges and duplicates per column (tables below). Findings feed the cleaning rules in step 3.

sales_daily.csv (fact) — 36,550 rows × 6 columns · duplicates: 0

columndtypenon-nullnullsuniqueminmaxmeanexample
Datestr36,55007312024-01-012025-12-312024-01-01
SKUstr36,550050SKU001SKU050SKU001
Units_Soldint6436,55005505614.05
Revenuefloat6436,55001,3270.0546977.084707.4318320.25
Pricefloat6436,550050663.4611642.455892.663664.05
Promotionint6436,55002010.10

sku_master.csv (dimension) — 50 rows × 8 columns · duplicates: 0

columndtypenon-nullnullsuniqueminmaxmeanexample
SKUstr50050SKU001SKU050SKU001
Product_Namestr50050Product 001Product 050Product 001
Categorystr5005FurnitureStorageFurniture
Subcategorystr50010CabinetTableChair
Launch_Datestr500482022-03-122024-12-092022-04-09
Cost_Pricefloat6450050307.067748.23770.731758.45
Selling_Pricefloat6450050663.4611642.455892.663664.05
Gross_Margin_Per_Unitfloat6450050-5484.3210224.932121.931905.6

calendar.csv (dimension) — 731 rows × 11 columns · duplicates: 0

columndtypenon-nullnullsuniqueminmaxmeanexample
datestr73107312024-01-012025-12-312024-01-01
yearint6473102202420252024.52024
monthint647310121126.521
quarterstr73104Q1Q4Q1
weekint6473105215226.41
day_of_weekstr73107FridayWednesdayMonday
is_weekendint6473102010.280
seasonstr73105AutumnWinterWinter
holidaystr87234ChristmasRepublic Daynan
is_holidayint6473102010.010
promotion_eventstr756561Seasonal PromotionSeasonal Promotionnan

inventory_snapshots.csv (periodic fact) — 4,800 rows × 8 columns · duplicates: 0

columndtypenon-nullnullsuniqueminmaxmeanexample
Snapshot_Datestr4,8000242024-01-012025-12-012024-01-01
SKUstr4,8000200SKU001SKU200SKU001
Current_Stockint644,800095422585304.1416
On_Orderint644,800082203060213.0923
Lead_Time_Daysint644,8000123148.4411
Safety_Stockint644,8000341574683.157
Reorder_Pointint644,8000715101661211.7518
Inventory_Valuefloat644,80004,6302444.220279867.851304166.4758420.96

Sample rows

sales_daily — first 6 rows
DateSKUUnits_SoldRevenuePricePromotion
2024-01-01SKU001518,320.253,664.050
2024-01-01SKU0021557,085.353,805.690
2024-01-01SKU003540,391.158,078.230
2024-01-01SKU004534,307.856,861.570
2024-01-01SKU00512113,9199,493.220
2024-01-01SKU006756,742.288,106.040
inventory_snapshots — first 6 rows
Snapshot_DateSKUCurrent_StockOn_OrderLead_Time_DaysSafety_StockReorder_PointInventory_Value
2024-01-01SKU00116231171858,420.96
2024-01-01SKU00229124510163,983
2024-01-01SKU003342314620209,060
2024-01-01SKU004344751242,257.92
2024-01-01SKU0052355611170,946
2024-01-01SKU006136551086,143.20
sku_master — first 6 rows
SKUProduct_NameCategorySubcategoryLaunch_DateCost_PriceSelling_PriceGross_Margin_Per_Unit
SKU001Product 001FurnitureChair2022-04-091,758.453,664.051,905.60
SKU002Product 002Home DecorTable2024-05-013,867.093,805.69-61.40
SKU003Product 003KitchenCushion2023-12-22589.488,078.237,488.75
SKU004Product 004LightingCookware2023-04-281,445.746,861.575,415.83
SKU005Product 005StorageLamp2023-04-225,543.639,493.223,949.59
SKU006Product 006FurnitureShelf2024-08-026,021.918,106.042,084.13
calendar — first holiday / promo rows
dateyearmonthquarterweekday_of_weekis_weekendseasonholidayis_holidaypromotion_event
2024-01-262,0241Q14Friday0WinterRepublic Day1nan
2024-03-022,0243Q19Saturday1Springnan0Seasonal Promotion
2024-03-032,0243Q19Sunday1Springnan0Seasonal Promotion
2024-03-092,0243Q110Saturday1Springnan0Seasonal Promotion
2024-03-102,0243Q110Sunday1Springnan0Seasonal Promotion
2024-03-162,0243Q111Saturday1Springnan0Seasonal Promotion

Descriptive statistics — sales_daily

columncountmeanstdmin25%50%75%max
Units_Sold36,550.0014.008.470.007.0013.0020.0056.00
Revenue36,550.0084,707.4376,534.630.0027,643.7056,125.90127,954546,977
Price36,550.005,892.663,138.78663.463,484.095,649.808,516.6011,642.45
Promotion36,550.000.100.300.000.000.000.001.00

Descriptive statistics — inventory_snapshots (all 200 SKUs)

columncountmeanstdmin25%50%75%max
Current_Stock4,800.00304.14280.892.00109.00229.00418.002,585.00
On_Order4,800.00213.09244.460.0049.00135.00292.003,060.00
Lead_Time_Days4,800.008.443.493.005.008.0011.2514.00
Safety_Stock4,800.0083.1570.265.0035.0067.00112.00746.00
Reorder_Point4,800.00211.75179.9110.0087.00169.00286.001,661.00
Inventory_Value4,800.001,304,1661,741,5512,444.20252,186683,7391,720,02320,279,868

3. Cleaning & data-quality log

Every rule lives in src/pipeline.py and writes to data/processed/data_quality_log.json — nothing is edited by hand, and the log is what the dashboard’s Data-quality tab renders.

TableIssue foundnHandlingRationale
Stock_data.csvUnrelated file: equity price history (AABA ticker 2006–2017)—Excluded from the pipelineNot part of the NorthBay star schema; no join key to any table.
sales_dailyDuplicate (date, sku) rows0None found — no actionOne row per SKU-day is the declared grain.
sales_dailyNegative units_sold (returns?)0None found — no actionReturns are not demand.
sales_dailyMissing numeric values0None found — no actionA missing sales row means nothing sold that day.
sales_dailyrevenue != units_sold * unit_price (>₹1 gap)0None found — internally consistentRevenue is the booked figure; price is a reference.
sales_dailyRows retained after cleaning36,55036550 → 36550
sku_masterlaunch_date AFTER first recorded sale21launch_date overridden with first_sale_date (original kept as launch_date_master)Sales history is the stronger evidence; a product cannot sell before launch.
sku_masterlist_price below unit_cost (negative gross margin)16Kept; flagged margin_flag='negative' and surfaced to FinanceCannot be corrected from data — likely a master-data error or loss-leader; must be confirmed by client.
sku_mastercategory/subcategory pairs that look inconsistent (e.g. Kitchen→Cushion)40Kept the master 'category' for reporting; added 'subcategory_group' as a sanity viewCategory is the client's planning hierarchy; we do not silently re-classify their catalogue.
inventory_snapshotsSKUs present in inventory but with NO sales history (SKU051–SKU200)150Excluded from forecasting/risk; listed separately as 'unsellable-or-unmapped stock' for the clientNo demand signal → cannot forecast; likely discontinued or master-data gap. Raised as a finding.
inventory_snapshotsDuplicate (date, sku) snapshots0None found — no action
inventory_snapshotsSnapshot cadence24Monthly snapshots (1st of month); latest = 2025-12-01Risk scoring uses the latest snapshot as the opening stock position at the forecast origin.
calendarBlank holiday / promotion_event cells—Treated as 'None' (not missing)Blank means no event that day.
sales_weeklyPartial weeks at series edges (fewer than 7 days)50DroppedA 3-day 'week' would look like a demand crash and poison the model.
sales_weeklySKU-weeks with no sales rows at all0Filled with 0 unitsNo row = no sale.
Biggest finding: 150 inventory SKUs (SKU051–SKU200) never sold a unit in two years but hold ₹17.49 Cr of stock at the latest snapshot. Excluded from forecasting (no demand signal); listed in inventory_unmapped_skus.csv for reconciliation.

4. Weekly analysis-ready dataset

3
Aggregate to weeks — Sunday-ending weeks; partial edge weeks dropped (a 3-day “week” looks like a demand crash). Complete SKU × week grid so silent zero-weeks are explicit. Calendar attributes (week-of-year, month, season, promo days, holiday days) and master attributes (category, launch date, price, cost) joined.
4
Set the forecast origin = 2025-11-30, the Sunday before the latest inventory snapshot (2025-12-01), so the stock position and the forecast start on the same day. The 4 December weeks become a true holdout (flag is_holdout).
PropertyValue
Grainone row per SKU per week
Rows5,200 = 50 SKUs × 104 weeks
Span2024-01-07 → 2025-12-28
Training weeks100 (≤ origin)
Holdout weeks4 (Dec-2025)
Total units510,082
Total revenue₹308.56 Cr
Columnssku_id, week_end, units, revenue, promo_days, holiday_days, unit_price, week_of_year, month, year, season, category, subcategory, launch_date, unit_cost, list_price, weeks_since_launch, is_holdout
sales_weekly — last 10 weeks of SKU012 (note the is_holdout flag)
sku_idweek_endunitsrevenuepromo_daysholiday_daysunit_priceweek_of_yearmonthyearseasoncategorysubcategoryunit_costlist_priceweeks_since_launchis_holdout
SKU0122025-10-26 00:00:001411,237,891018,779.3743102,025AutumnHome DecorTable1,256.898,779.3794False
SKU0122025-11-02 00:00:001511,325,685208,779.3744112,025AutumnHome DecorTable1,256.898,779.3795False
SKU0122025-11-09 00:00:001551,360,802208,779.3745112,025AutumnHome DecorTable1,256.898,779.3796False
SKU0122025-11-16 00:00:001631,431,037208,779.3746112,025AutumnHome DecorTable1,256.898,779.3797False
SKU0122025-11-23 00:00:001551,360,802208,779.3747112,025AutumnHome DecorTable1,256.898,779.3798False
SKU0122025-11-30 00:00:001611,413,479208,779.3748112,025AutumnHome DecorTable1,256.898,779.3799False
SKU0122025-12-07 00:00:001671,466,155008,779.3749122,025WinterHome DecorTable1,256.898,779.37100True
SKU0122025-12-14 00:00:001551,360,802008,779.3750122,025WinterHome DecorTable1,256.898,779.37101True
SKU0122025-12-21 00:00:001711,501,272008,779.3751122,025WinterHome DecorTable1,256.898,779.37102True
SKU0122025-12-28 00:00:001501,316,906018,779.3752122,025WinterHome DecorTable1,256.898,779.37103True

5. EDA — seasonality & demand drivers

Monthly seasonality — average total units per week (both years, and by year)
MonthAvg units / weekAvg units / week 2024Avg units / week 2025Seasonality indexRead
Jan4,749.384,739.504,759.250.97average
Feb5,126.255,142.005,110.501.05average
Mar5,922.005,911.805,932.201.21peak
Apr5,637.255,663.005,611.501.15peak
May5,534.125,520.755,547.501.13peak
Jun5,644.905,670.005,619.801.15peak
Jul4,778.384,786.254,770.500.98average
Aug4,407.894,406.504,409.000.90average
Sep4,361.894,382.604,336.000.89trough
Oct3,889.503,892.753,886.250.80trough
Nov4,313.674,342.504,290.600.88trough
Dec4,327.004,306.604,352.500.88trough
Reading: Mar is the peak and Oct the trough in both years — a 52% swing that repeats almost identically. That stability is what makes a 52-week seasonal signal usable.
Weekday effect
WeekdayAvg units / SKU-dayTotal unitsSKU-daysIndex vs weekday avg
Monday13.0368,4305,2501.00
Tuesday13.0268,3425,2501.00
Wednesday13.0468,4515,2501.00
Thursday13.1168,1935,2001.00
Friday13.1968,5675,2001.01
Saturday16.2884,6525,2001.24
Sunday16.3885,1755,2001.25
Promotion effect
Day typeSKU-daysAvg unitsAvg revenue (₹)
No promotion32,80013.4881,550.08
Promotion day3,75018.61112,324
Holiday & season effect
Day typeSKU-daysAvg units
Normal day36,150.0014.02
National holiday400.0012.16
Spring16.67
Summer16.01
Winter13.56
Monsoon12.83
Autumn11.77

Weekend lift +24.9%, promotion lift +38.1%, holiday effect -13.3%. Promotions are planned, so promo_days_target is a legitimate forward-looking feature.

6. EDA — categories, SKUs and dead stock

Category mix
CategorySKUsUnits (2 yrs)Revenue (₹)Avg price (₹)Units share %Revenue share %
Furniture1082,026601,392,7006,594.7516.0819.49
Home Decor10132,100885,273,9836,478.7225.9028.69
Kitchen10100,784561,503,2625,178.7319.7618.20
Lighting10102,131457,384,5754,897.3120.0214.82
Storage1093,041580,007,5356,313.8118.2418.80
All 50 SKUs ranked by two-year revenue (CoV = weekly coefficient of variation; Cum % = cumulative revenue share)
RankSKUCategorySubcatUnitsRevenue (₹)Avg units/wkCoVPriceCostMarginLaunchRev %Cum %
1SKU026FurnitureShelf16,480180,283,619158.50.1610,939.541,799.29positive2023-12-105.845.8
2SKU035StorageLamp15,522168,727,089149.20.1610,870.191,137.61positive2024-01-015.4711.3
3SKU012Home DecorTable18,996166,772,913182.70.148,779.371,256.89positive2024-01-015.4016.7
4SKU042Home DecorTable16,559152,323,426159.20.159,198.834,583.86positive2024-01-014.9421.7
5SKU027Home DecorCabinet17,650150,317,990169.70.158,516.60307.06positive2023-03-194.8726.5
6SKU045StorageLamp18,553142,427,299178.40.177,676.783,634.56positive2022-03-164.6231.1
7SKU043KitchenCushion16,201141,690,220155.80.158,745.772,605.61positive2024-01-014.5935.7
8SKU029LightingOrganizer11,495133,829,963110.50.1511,642.455,402.59positive2023-08-234.3440.1
9SKU031FurnitureChair13,671119,155,479131.50.158,715.936,300.65positive2023-05-113.8643.9
10SKU008KitchenRug11,908117,206,157114.50.169,842.642,775.15positive2024-01-013.8047.7
11SKU005StorageLamp12,146115,304,650116.80.159,493.225,543.63positive2023-04-223.7451.5
12SKU018KitchenRug18,392102,542,941176.80.145,575.415,677.05negative2022-05-213.3254.8
13SKU049LightingOrganizer18,32695,723,479176.20.145,223.372,063.03positive2024-01-013.1057.9
14SKU007Home DecorCabinet18,06192,365,579173.70.145,114.097,748.20negative2022-04-052.9960.9
15SKU017Home DecorCabinet10,33787,630,05799.40.178,477.326,703.26positive2023-07-192.8463.7
16SKU047Home DecorCabinet10,81785,867,726104.00.177,938.223,416.10positive2023-05-062.7866.5
17SKU041FurnitureChair8,12884,016,94178.20.1810,336.734,540.24positive2022-07-012.7269.2
18SKU013KitchenCushion10,26259,086,33898.70.175,757.783,936.71positive2024-01-011.9171.1
19SKU019LightingOrganizer11,76356,695,896113.10.164,819.852,670.84positive2024-01-011.8473.0
20SKU037Home DecorCabinet18,09649,717,312174.00.152,747.423,901.00negative2024-01-011.6174.6
21SKU046FurnitureShelf6,74448,391,84264.80.177,175.541,913.03positive2024-01-011.5776.2
22SKU021FurnitureChair6,21947,956,45059.80.177,711.286,486.92positive2023-07-051.5577.7
23SKU033KitchenCushion13,18246,901,556126.80.173,558.004,657.74negative2022-04-121.5279.2
24SKU020StorageSofa10,09539,345,66697.10.153,897.546,700.01negative2023-05-111.2880.5
25SKU006FurnitureShelf4,76638,633,38745.80.208,106.046,021.91positive2024-01-011.2581.8
26SKU044LightingCookware7,12638,567,90768.50.185,412.28488.84positive2023-01-251.2583.0
27SKU001FurnitureChair10,46538,344,283100.60.153,664.051,758.45positive2022-04-091.2484.3
28SKU032Home DecorTable6,79538,137,54965.30.205,612.593,806.60positive2022-09-071.2485.5
29SKU014LightingCookware15,73335,725,710151.30.162,270.752,008.55positive2024-01-011.1686.7
30SKU022Home DecorTable7,40434,036,41071.20.184,597.033,252.96positive2023-02-121.1087.8
31SKU003KitchenCushion4,19733,904,33140.40.228,078.23589.48positive2023-12-221.1088.9
32SKU030StorageSofa3,47932,819,18133.50.209,433.515,715.03positive2023-05-031.0689.9
33SKU002Home DecorTable7,38528,105,02171.00.183,805.693,867.09negative2024-01-010.9190.8
34SKU040StorageSofa11,36627,853,065109.30.162,450.565,169.07negative2023-11-260.9091.7
35SKU004LightingCookware3,37423,150,93732.40.216,861.571,445.74positive2023-04-280.7592.5
36SKU025StorageLamp1,96922,757,72218.90.2511,558.011,333.08positive2024-01-010.7493.2
37SKU034LightingCookware14,68922,299,958141.20.161,518.141,333.43positive2023-09-030.7293.9
38SKU038KitchenRug5,61022,154,45153.90.193,949.104,630.58negative2024-01-010.7294.7
39SKU016FurnitureShelf10,10621,897,88397.20.172,166.823,637.93negative2024-01-010.7195.4
40SKU036FurnitureShelf3,49919,898,81333.60.235,687.005,430.12positive2022-03-120.6496.0
41SKU015StorageLamp2,98718,555,60228.70.226,212.125,441.06positive2024-01-010.6096.6
42SKU009LightingOrganizer7,80918,248,77475.10.192,336.893,121.06negative2022-08-100.5997.2
43SKU024LightingCookware9,52816,853,79391.60.171,768.875,539.34negative2024-01-010.5597.8
44SKU039LightingOrganizer2,28816,288,15822.00.257,118.956,178.74positive2022-11-010.5398.3
45SKU023KitchenCushion10,79115,288,041103.80.171,416.742,484.54negative2022-07-200.5098.8
46SKU028KitchenRug4,08714,239,47639.30.213,484.096,348.66negative2024-01-010.4699.2
47SKU048KitchenRug6,1548,489,75159.20.181,379.556,863.87negative2024-01-010.2899.5
48SKU010StorageSofa12,4148,236,192119.40.17663.463,889.06negative2022-04-140.2799.8
49SKU050StorageSofa4,5103,981,06743.40.22882.72701.85positive2024-01-010.1399.9
50SKU011FurnitureChair1,9482,814,00318.70.261,444.561,718.40negative2023-08-030.09100.0
Insights: top-10 SKUs = 47.7% of revenue, bottom-10 = 4.0%; dead-stock candidates SKU028, SKU048, SKU010, SKU050, SKU011. 16 SKUs are priced below cost (16.7% of revenue) — master-data error or loss-leaders; Finance must confirm before discounting.

7. Feature engineering (leakage-safe)

5
Direct multi-horizon design — for every origin week t and horizon h = 1…8, one training row: features from history ≤ t plus known calendar attributes of week t+h; target = units in week t+h. Origins step weekly from week 13 onward, giving 2,400 scored rows in the backtest and a larger training set for the final model.
FeatureDefinitionSource / leakage status
hHorizon (weeks ahead, 1–8)design
lag1…lag4, lag8, lag13Units in the k-th week before the originhistory ≤ origin
mean4 / mean8 / mean13Rolling mean of the last 4/8/13 weekshistory ≤ origin
std4Rolling std of last 4 weeks (volatility)history ≤ origin
trend4_13mean4 − mean13 (short vs medium level)history ≤ origin
lag52_targetUnits in the same week last year (target week − 52)history ≤ origin
lag52_mean3Mean of last year's target week ±1history ≤ origin
level_ratio_52mean4 ÷ same 4 weeks last yearhistory ≤ origin
promo_days_targetPlanned promotion days in the target weekcalendar (known in advance)
holiday_days_targetHoliday days in the target weekcalendar
week_of_year, month, seasonCalendar position of the target weekcalendar
weeks_since_launchProduct age at the target weekmaster data
unit_priceSelling pricemaster data
sku_id, categoryIdentity (LightGBM categorical)master data
Leakage guard: tests/test_foresight.py::test_no_future_leakage_in_training_rows asserts that lag1 equals the last observed week ≤ origin and every target week is strictly after the origin.

8. Metric & baseline

WAPE = Σ|actual − forecast| ÷ Σ actual   (primary — robust when some SKUs sell few units)
Bias = Σ(forecast − actual) ÷ Σ actual   (secondary — systematic over/under-forecast)
MAPE = mean(|actual − forecast| ÷ actual) over actual > 0   (reported, not optimised)
Seasonal-naive: forecast(t+h) = actual(t+h−52)   (falls back to mean of last 4 weeks if last year is missing)

Horizon fixed at 8 weeks. The baseline is the bar: if the model cannot beat it on the backtest, the baseline ships.

9. Model

LightGBM parameterValueWhy
objectivetweedie (variance power 1.2)non-negative, right-skewed count-like demand
n_estimators600
learning_rate0.03
num_leaves31
min_child_samples20
subsample / colsample_bytree0.8 / 0.8 (freq 1)regularisation
reg_lambda1.0
random_state42fixed seed → reproducible

One model for all 50 SKUs (identity as a categorical feature) so shared seasonality and promotion effects are learned jointly; Tweedie objective suits non-negative, right-skewed demand; predictions clipped at 0.

10. Rolling-origin backtest

6
Six origins in 2025, four weeks apart (2025-05-18, 2025-06-15, 2025-07-13, 2025-08-10, 2025-09-07, 2025-10-05). At each, the model is retrained from scratch on data ≤ origin and scored on the next 8 weeks. No random splits.
By fold
FoldOriginWAPE modelWAPE naive
12025-05-187.98%10.04%
22025-06-158.48%10.97%
32025-07-139.38%11.69%
42025-08-109.21%12.13%
52025-09-078.99%11.37%
62025-10-058.70%11.03%
By horizon
Weeks aheadWAPE modelWAPE naive
18.02%10.66%
28.62%10.60%
38.77%10.95%
49.18%11.67%
58.71%11.09%
68.61%11.08%
78.63%11.23%
89.57%12.07%
By category
CategoryWAPE modelWAPE naive
Furniture9.67%12.12%
Home Decor8.32%10.37%
Kitchen8.75%11.50%
Lighting8.39%10.79%
Storage8.95%11.46%
Model WAPE by fold × horizon
FoldOriginh1h2h3h4h5h6h7h8
12025-05-186.1%6.9%7.7%8.9%8.8%8.6%6.9%10.1%
22025-06-157.8%8.8%7.1%10.2%7.6%8.9%9.4%8.1%
32025-07-137.4%9.9%10.3%8.2%9.6%8.2%10.4%11.3%
42025-08-109.8%7.9%10.3%10.1%9.6%8.5%7.6%9.9%
52025-09-079.2%8.5%8.7%9.4%8.2%10.0%9.9%8.2%
62025-10-058.5%10.4%8.9%8.3%8.5%7.5%7.8%9.9%
Per-SKU backtest (sorted by model WAPE) — the model wins on every SKU except one
SKUCategoryWAPE modelWAPE naiveImprovement %
SKU049Lighting4.62%6.46%+28.4
SKU018Kitchen4.96%6.31%+21.4
SKU043Kitchen5.98%8.91%+32.9
SKU031Furniture6.03%7.39%+18.5
SKU014Lighting7.04%8.57%+17.8
SKU027Home Decor7.11%8.79%+19.2
SKU012Home Decor7.11%8.54%+16.7
SKU037Home Decor7.24%8.40%+13.7
SKU001Furniture7.26%10.54%+31.1
SKU045Storage7.36%10.06%+26.8
SKU007Home Decor7.64%8.85%+13.7
SKU033Kitchen7.67%8.58%+10.6
SKU029Lighting7.69%10.69%+28.1
SKU040Storage7.72%9.78%+21.1
SKU042Home Decor7.76%9.15%+15.1
SKU026Furniture7.90%9.38%+15.8
SKU019Lighting7.93%11.10%+28.6
SKU035Storage7.95%7.43%-7.0
SKU010Storage8.18%11.83%+30.8
SKU005Storage8.50%10.59%+19.7
SKU016Furniture8.95%10.65%+15.9
SKU017Home Decor8.96%13.30%+32.6
SKU047Home Decor8.98%11.24%+20.2
SKU034Lighting9.11%12.00%+24.1
SKU023Kitchen9.15%9.94%+7.9
SKU020Storage9.22%13.62%+32.3
SKU022Home Decor9.93%12.53%+20.8
SKU009Lighting9.99%13.97%+28.5
SKU008Kitchen10.00%14.76%+32.2
SKU024Lighting10.08%11.96%+15.8
SKU002Home Decor10.27%17.10%+40.0
SKU044Lighting10.27%11.92%+13.8
SKU038Kitchen10.39%14.97%+30.6
SKU021Furniture10.60%15.28%+30.6
SKU030Storage10.81%15.74%+31.3
SKU013Kitchen11.60%13.69%+15.3
SKU041Furniture11.91%12.05%+1.2
SKU003Kitchen12.13%18.86%+35.7
SKU046Furniture12.57%13.83%+9.1
SKU028Kitchen12.74%22.80%+44.1
SKU006Furniture12.77%16.61%+23.1
SKU050Storage14.63%17.47%+16.2
SKU032Home Decor15.04%16.45%+8.6
SKU048Kitchen15.26%16.88%+9.6
SKU004Lighting15.33%15.38%+0.4
SKU015Storage15.83%20.32%+22.1
SKU036Furniture16.40%25.14%+34.8
SKU025Storage18.16%22.81%+20.4
SKU039Lighting20.51%25.32%+19.0
SKU011Furniture25.34%34.26%+26.0
Overall WAPE
8.75%
naive 11.16%
MAPE
10.8%
naive 13.8%
Bias
+0.32%
naive +0.31%
SKUs won
49/50
folds won 6/6

11. Prediction intervals & December holdout

Relative residuals (actual − forecast) ÷ forecast from the backtest, per horizon, give the 10th/90th percentiles for an 80% band and a sigma used by the risk layer.

Interval calibration by horizon
Horizon hq10 (rel.)q90 (rel.)rel. sigma
1-0.140.170.14
2-0.150.170.14
3-0.160.150.13
4-0.170.160.14
5-0.150.140.13
6-0.160.140.14
7-0.160.150.14
8-0.170.180.15
Top-revenue SKU: history, baseline, forecast with 80% band, December holdout dots.
December-2025 holdout (never trained on)Value
Holdout weeks4
WAPE model9.26%
WAPE seasonal-naive12.42%
Bias model-0.15%
80% interval coverage79.5% (target 80%)

12. Feature importance

FeatureGainShare
sku_id2,781,54352.02%
mean41,436,03926.85%
mean8652,55412.20%
mean13160,1793.00%
week_of_year119,2742.23%
lag52_mean357,9461.08%
season56,1801.05%
lag52_target24,3600.46%
weeks_since_launch18,4990.35%
month16,4890.31%
promo_days_target8,0620.15%
lag15,5820.10%
unit_price4,1880.08%
trend4_131,5480.03%
level_ratio_521,1580.02%
holiday_days_target1,1370.02%
lag136530.01%
h4600.01%
lag84080.01%
category3340.01%
std43260.01%
lag22540.00%
lag32020.00%
lag42010.00%

13. Risk scoring formulas (transparent, no black box)

D_LT = forecast demand over the lead time (weekly forecast interpolated to days) · σ_LT = summed weekly sigma
P_lt = P[ D_LT > on_hand ] = 1 − Φ((on_hand − D_LT)/σ_LT)   ← stock runs out before the open order lands
P_rop = P[ D_LT + safety_stock > on_hand + on_order ]   ← inventory position already below the reorder point
Stockout score = max(P_lt, P_rop)
weeks_of_cover = (on_hand + on_order) ÷ average weekly forecast
Overstock score = clip((weeks_of_cover − 8) ÷ 8, 0, 1)   → 0 at ≤8 weeks, 0.5 at 12, 1 at ≥16
Sales at risk (₹) = max(0, D_LT − on_hand, D_LT + SS − on_hand − on_order) × list price
Locked capital (₹) = max(0, on_hand + on_order − 8-week demand) × unit cost
Reorder qty = demand over (lead time + 4-week review) + safety stock − (on_hand + on_order)
QuadrantRule (threshold 0.5)Action
Reorder nowstockout ≥ 0.5 and overstock < 0.5Raise a replenishment order (quantity given)
Markdown / clearoverstock ≥ 0.5 and stockout < 0.5Promote / discount; defer open orders
Watch / volatileboth ≥ 0.5Investigate erratic demand manually
Healthyboth < 0.5No action
Calibration note: a first version compared 8-week demand with stock on hand and flagged 36/50 SKUs for reorder — technically true, practically useless for a team that reorders monthly. Reframing stockout as “will it run out before the next order can land” produced the 8 / 8 / 34 triage below.

14. Risk results — all 50 SKUs

Full risk table, ranked by value at stake
#SKUCategoryQuadrantStockoutOverstockP_ltP_ropOHOOLT dSSFcst/wkD_LTD_8wCover wkDays OHOrder qtyExcessSales at riskLocked capital₹ at stakeMarginAction
1SKU020StorageMarkdown / clear0.000.910.000.001,002353312988.5037.90708.3015.3079.200646.70₹0₹43.3 L₹43.3 LnegativePromote or discount — 15 weeks of cover vs 8-week horizon
2SKU006FurnitureMarkdown / clear0.001.000.000.003594851312944.2075.00353.4019.1056.900490.60₹0₹29.5 L₹29.5 LpositivePromote or discount — 19 weeks of cover vs 8-week horizon
3SKU012Home DecorReorder now1.000.001.001.0047601216171.50281.001,371.800.601.909190.00₹20.5 L₹0₹20.5 LpositiveRaise a replenishment order for ~919 units before stock runs out
4SKU011FurnitureMarkdown / clear0.001.000.000.00753269317917.506.50140.3058.30300.700881.70₹0₹15.2 L₹15.2 LnegativePromote or discount — 58 weeks of cover vs 8-week horizon
5SKU036FurnitureMarkdown / clear0.001.000.000.0038313537732.0013.30255.7016.2083.900262.30₹0₹14.2 L₹14.2 LpositivePromote or discount — 16 weeks of cover vs 8-week horizon
6SKU025StorageMarkdown / clear0.001.000.000.001,124611412117.1031.10137.2069.10458.9001,047.80₹0₹14.0 L₹14.0 LpositivePromote or discount — 69 weeks of cover vs 8-week horizon
7SKU015StorageMarkdown / clear0.001.000.000.00187245911923.5026.20188.2018.4055.600243.80₹0₹13.3 L₹13.3 LpositivePromote or discount — 18 weeks of cover vs 8-week horizon
8SKU017Home DecorReorder now1.000.000.991.0095261210888.30140.80706.401.407.505060.00₹10.8 L₹0₹10.8 LpositiveRaise a replenishment order for ~506 units before stock runs out
9SKU031FurnitureReorder now1.000.001.001.0093401328124.50207.00995.701.105.206170.00₹9.9 L₹0₹9.9 LpositiveRaise a replenishment order for ~617 units before stock runs out
10SKU004LightingMarkdown / clear0.001.000.000.0025925469829.0023.90231.7017.7062.600281.30₹0₹4.1 L₹4.1 LpositivePromote or discount — 18 weeks of cover vs 8-week horizon
11SKU050StorageMarkdown / clear0.001.000.000.00328525146938.9071.80311.4021.9059.000541.60₹0₹3.8 L₹3.8 LpositivePromote or discount — 22 weeks of cover vs 8-week horizon
12SKU038KitchenHealthy0.000.210.000.00159310125648.4080.50387.509.7023.00081.50₹0₹3.8 L₹3.8 LnegativeNo action needed; leave as is
13SKU042Home DecorReorder now0.980.000.980.0190120641148.30125.101,186.401.404.205590.00₹3.2 L₹0₹3.2 LpositiveRaise a replenishment order for ~559 units before stock runs out
14SKU001FurnitureHealthy0.000.240.000.00771147415992.7051.10741.609.9058.200176.40₹0₹3.1 L₹3.1 LpositiveNo action needed; leave as is
15SKU049LightingHealthy0.000.070.000.007805995206161.80106.101,294.508.5033.70084.50₹0₹1.7 L₹1.7 LpositiveNo action needed; leave as is
16SKU040StorageReorder now1.000.001.001.00672993697.50114.30780.401.004.805120.00₹1.3 L₹0₹1.3 LnegativeRaise a replenishment order for ~512 units before stock runs out
17SKU034LightingReorder now1.000.001.000.1834103534130.2091.701,041.701.101.805360.00₹87,590₹0₹87,590positiveRaise a replenishment order for ~536 units before stock runs out
18SKU010StorageReorder now1.000.001.000.9983701322109.50196.10876.001.405.305190.00₹75,057₹0₹75,057negativeRaise a replenishment order for ~519 units before stock runs out
19SKU032Home DecorHealthy0.000.040.000.00278214116059.3086.50474.508.3032.80017.50₹0₹66,658₹66,658positiveNo action needed; leave as is
20SKU002Home DecorHealthy0.000.030.000.005262939467.6026.70540.608.2054.50014.40₹0₹55,866₹55,866negativeNo action needed; leave as is
21SKU003KitchenHealthy0.000.270.000.003491238835.6014.30285.2010.1068.50075.80₹0₹44,709₹44,709positiveNo action needed; leave as is
22SKU023KitchenReorder now0.900.000.900.3213079124198.10157.50784.602.109.304180.00₹38,906₹0₹38,906negativeRaise a replenishment order for ~418 units before stock runs out
23SKU005StorageHealthy0.000.000.000.004712916129108.1087.30865.207.0030.5000.00₹0₹0₹0positiveNo action needed; leave as is
24SKU007Home DecorHealthy0.140.000.140.00207460898165.00180.201,320.304.008.804130.00₹0₹0₹0negativeNo action needed; leave as is
25SKU008KitchenHealthy0.000.000.000.00325155391102.9042.90823.004.7022.101070.00₹0₹0₹0positiveNo action needed; leave as is
26SKU009LightingHealthy0.070.000.070.00758976368.9062.10551.502.407.602420.00₹0₹0₹0negativeNo action needed; leave as is
27SKU013KitchenHealthy0.000.000.000.001519543286.1048.40688.802.9012.302160.00₹0₹0₹0positiveNo action needed; leave as is
28SKU014LightingHealthy0.000.000.000.0030933410130136.70190.201,093.704.7015.802970.00₹0₹0₹0positiveNo action needed; leave as is
29SKU016FurnitureHealthy0.000.000.000.00255187113084.80126.70678.405.2021.10900.00₹0₹0₹0negativeNo action needed; leave as is
30SKU018KitchenHealthy0.030.000.030.00235102866163.10185.801,304.502.1010.107000.00₹0₹0₹0negativeNo action needed; leave as is
31SKU019LightingHealthy0.200.000.200.002271141425102.80202.70822.303.3015.502910.00₹0₹0₹0positiveNo action needed; leave as is
32SKU021FurnitureHealthy0.010.000.010.00889793654.8064.90438.203.4011.201680.00₹0₹0₹0positiveNo action needed; leave as is
33SKU022Home DecorHealthy0.010.000.010.009519487668.3070.40546.604.209.701820.00₹0₹0₹0positiveNo action needed; leave as is
34SKU024LightingHealthy0.000.000.000.0034118146082.10160.10657.004.4029.101940.00₹0₹0₹0negativeNo action needed; leave as is
35SKU026FurnitureHealthy0.000.000.000.0016070531144.80103.801,158.501.607.705120.00₹0₹0₹0positiveNo action needed; leave as is
36SKU027Home DecorHealthy0.000.000.000.00567103383164.3064.901,314.404.1024.202070.00₹0₹0₹0positiveNo action needed; leave as is
37SKU028KitchenHealthy0.000.000.000.00174551111437.0055.70295.906.2032.901080.00₹0₹0₹0negativeNo action needed; leave as is
38SKU029LightingHealthy0.000.000.000.002615205196105.1070.10840.607.4017.4000.00₹0₹0₹0positiveNo action needed; leave as is
39SKU030StorageHealthy0.000.000.000.001663294331.4041.30251.106.3037.00300.00₹0₹0₹0positiveNo action needed; leave as is
40SKU033KitchenHealthy0.420.000.420.002081681255122.00202.50975.803.1011.903910.00₹0₹0₹0negativeNo action needed; leave as is
41SKU035StorageHealthy0.000.000.000.00670768137137.60155.101,100.905.4034.102140.00₹0₹0₹0positiveNo action needed; leave as is
42SKU037Home DecorHealthy0.000.000.000.003556878315159.00183.001,272.406.6015.602230.00₹0₹0₹0negativeNo action needed; leave as is
43SKU039LightingHealthy0.000.000.000.00933531718.906.50151.506.8034.4000.00₹0₹0₹0positiveNo action needed; leave as is
44SKU041FurnitureHealthy0.000.000.000.004404047876.3039.50610.106.3040.4000.00₹0₹0₹0positiveNo action needed; leave as is
45SKU043KitchenHealthy0.000.000.000.0055921112142149.90232.801,199.405.1026.102520.00₹0₹0₹0positiveNo action needed; leave as is
46SKU044LightingHealthy0.000.000.000.002233955562.1037.60496.504.2025.20730.00₹0₹0₹0positiveNo action needed; leave as is
47SKU045StorageHealthy0.000.000.000.00307142488161.1089.601,288.802.8013.304450.00₹0₹0₹0positiveNo action needed; leave as is
48SKU046FurnitureHealthy0.000.000.000.0011918244662.0032.10496.004.9013.40430.00₹0₹0₹0positiveNo action needed; leave as is
49SKU047Home DecorHealthy0.000.000.000.0020112994490.70107.70725.603.6015.502540.00₹0₹0₹0positiveNo action needed; leave as is
50SKU048KitchenHealthy0.000.000.000.001065032254.0021.40432.302.9013.701230.00₹0₹0₹0negativeNo action needed; leave as is

15. Reorder & markdown lists

Reorder now — 8 SKUs
SKUProductCategoryOrder qtySales at riskDays of stock
SKU012Product 012Home Decor919₹20.5 L1.90
SKU017Product 017Home Decor506₹10.8 L7.50
SKU031Product 031Furniture617₹9.9 L5.20
SKU042Product 042Home Decor559₹3.2 L4.20
SKU040Product 040Storage512₹1.3 L4.80
SKU034Product 034Lighting536₹87,5901.80
SKU010Product 010Storage519₹75,0575.30
SKU023Product 023Kitchen418₹38,9069.30
Markdown / clear — 8 SKUs
SKUProductCategoryExcess unitsLocked capitalWeeks cover
SKU020Product 020Storage647₹43.3 L15.30
SKU006Product 006Furniture491₹29.5 L19.10
SKU011Product 011Furniture882₹15.2 L58.30
SKU036Product 036Furniture262₹14.2 L16.20
SKU025Product 025Storage1048₹14.0 L69.10
SKU015Product 015Storage244₹13.3 L18.40
SKU004Product 004Lighting281₹4.1 L17.70
SKU050Product 050Storage542₹3.8 L21.90

16. Rupee impact

By category
CategorySKUsSales at riskLocked capital
Furniture10₹9.9 L₹62.0 L
Home Decor10₹34.6 L₹1.2 L
Kitchen10₹38,906₹4.2 L
Lighting10₹87,590₹5.8 L
Storage10₹2.1 L₹74.4 L
MeasureValue
Sales at risk (lead-time shortfall × price)₹47.9 L
Locked capital (excess beyond 8 weeks × cost)₹1.48 Cr
Inventory on hand at cost (50 sellable SKUs)₹5.12 Cr
Unmapped inventory (150 SKUs, no sales)₹17.49 Cr
QuadrantsHealthy: 34, Markdown / clear: 8, Reorder now: 8

17. Dashboard — how it works

The dashboard (index.html) is a static page: src/export_web.py packs the forecast, risk table, 26 weeks of history per SKU, backtest metrics and the data-quality log into data/dashboard.json (~92 KB). The page loads it once, then everything — filters, sorting, charts, what-if — runs in the browser. risk_engine.js is a line-for-line port of src/risk.py, so the what-if slider and the API give the same numbers as the batch job.

TabWhat it showsNotes
Overview5 KPI cards, decisioning bubble grid (click a bubble → explorer), rupee impact by category, top priorities, total demand with forecast bandfilters: category, quadrant, search
Reorder & markdown listReorder list with order qty and days of stock; markdown list with weeks of cover and margin flag; sortable full table; CSV exportsort by any column
Forecast explorer & what-ifPer-SKU history + forecast + 80% band + seasonal-naive + Dec holdout dots; lead-time slider, on-hand/on-order/safety inputs → instant re-score with a plain-language “why”stretch goal from the brief
Model accuracyBacktest by fold, horizon, category; feature importance; honest limitations text—
Data qualityThe pipeline’s data-quality log and dataset summary—
Scoring APIEndpoint documentation and a live “try it” boxcalls /api/score
StatesLoading spinner while fetching; explicit error card if data is missing; empty-state cards when filters match nothingacceptance criterion D5.4
Overview
Reorder & markdown lists
Forecast explorer & what-if
Model accuracy

A Streamlit edition (app/streamlit_app.py) with identical logic is included for Streamlit Community Cloud, as recommended in the brief.

18. Scoring API

MethodEndpointReturns
GET/api/healthstatus, forecast origin, model, backtest WAPE
GET/api/score?sku=SKU0128-week forecast (mean, lo80, hi80, naive) + risk + action for one SKU
GET/api/score?sku=SKU012&lead_time_days=21&on_hand=30what-if override (also on_order, safety_stock)
GET/api/score?sku=SKU012,SKU020 · ?all=truebatch (summary totals + quadrants)
POST/api/score {"skus":[…],"overrides":{…}}batch with overrides
—errorsunknown SKU → 404 · bad number → 400 · empty → 400 with example · bad JSON → 400 · other method → 405
{
 "forecast_origin": "2025-11-30",
 "count": 1,
 "results": [
  {
   "sku_id": "SKU012",
   "product_name": "Product 012",
   "category": "Home Decor",
   "inventory_position": {
    "on_hand": 47,
    "on_order": 60,
    "lead_time_days": 21,
    "safety_stock": 16,
    "overridden": true
   },
   "forecast": {
    "origin": "2025-11-30",
    "horizon_weeks": 8,
    "weeks": [
     "2025-12-07",
     "2025-12-14",
     "2025-12-21",
     "2025-12-28",
     "2026-01-04",
     "2026-01-11",
     "2026-01-18",
     "2026-01-25"
    ],
    "mean": [
     162.6,
     165.8,
     163.9,
     166.7,
     171.9,
     178.5,
     181.7,
     180.6
    ],
    "\u2026": "lo80, hi80, seasonal_naive, total_8w"
   },
   "risk": {
    "stockout_score": 1.0,
    "overstock_score": 0.0,
    "quadrant": "Reorder now",
    "action": "Raise a replenishment order for ~N units before stock runs out",
    "\u2026": "p_stockout_lead_time, weeks_of_cover, reorder_qty, sales_at_risk_inr, locked_capital_inr"
   }
  }
 ]
}

Served by a Netlify serverless function (netlify/functions/score.mjs); a FastAPI twin (service/main.py, docs at /docs) is included for Render.

19. Reproducibility & tests

pip install -r requirements.txt
python src/run_all.py      # pipeline → forecast + backtest → risk → figures → dashboard data   (~60 s)
python -m pytest tests -q  # metrics, risk quadrants, output consistency, leakage guard, API errors
python reports/build_reports.py    # PDFs: EDA memo, executive readout, project report, video scripts
python src/build_analysis_page.py  # this page
PathPurpose
src/pipeline.pyD1 ingest · clean · weekly features · data-quality log
src/forecast.pyD3 baseline · LightGBM · rolling-origin backtest · intervals · forecast
src/risk.pyD4 stockout/overstock scoring · actions · rupee impact
src/eda.pyD2 figures + EDA summary
src/export_web.pydashboard data bundle
web/D5 static dashboard + risk_engine.js + this page
netlify/functions/score.mjsD6 serverless scoring API
app/streamlit_app.py · service/main.pyStreamlit + FastAPI editions
notebooks/01–03executed notebooks with outputs
reports/D2 memo · D7 readout · project report · video scripts · figures
tests/5 pytest tests

A fresh end-to-end rerun reproduced the headline numbers bit-for-bit (seed 42).

20. Assumptions, limitations & monitoring

TopicAssumption / limit
Stock positionLatest monthly snapshot = opening position at the origin; on-order arrives at end of lead time
Future calendarPromotions/holidays after 31-Dec-2025 unknown → assumed 0
History depthTwo years = one prior seasonal cycle; new SKUs lean on category peers
DistributionLead-time demand ≈ normal; sigma scaled from backtest relative errors per horizon
ScopeNo price optimisation, supplier selection, live integrations or automated POs (per brief)
MonitoringRetrain monthly; alert if 4-week WAPE loses to seasonal-naive for 2 consecutive months or |bias| > 5%; track realised stockouts among “Healthy” SKUs
Bottom line: demand is predictable to within about 9%. NorthBay’s problem is timing, not demand — fast sellers ordered too late, slow sellers too often. FORESIGHT turns that into two short lists every month.