Add privacy-safe demand forecasting case study

Synthetic monthly SKU/region demand notebook comparing a trimmed-mean
baseline against gradient boosting, with a gated TimesFM build-vs-buy
comparison. Includes rolling-origin validation, executive narrative,
architecture diagram, one-page PDF, and LinkedIn draft.

No company data, credentials, or private implementation details.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Gabriel Pereira
2026-09-11 16:37:33 -03:00
commit 930534dde2
13 changed files with 3624 additions and 0 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
* text=auto eol=lf
*.pdf binary
*.png binary

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.venv/
.env
.ipynb_checkpoints/
__pycache__/
*.py[cod]
data/
private/

51
README.md Normal file
View File

@@ -0,0 +1,51 @@
# Demand Forecasting Case Study
Privacy-safe demonstration of a demand-forecasting delivery flow using only synthetic data, written as an executive-readable AI delivery case study.
## What it demonstrates
- Monthly demand by synthetic SKU and region.
- A baseline forecast: the mean of the preceding eight observed months after removing one minimum and one maximum.
- One gradient-boosting model using lag and calendar features.
- Leakage-safe rolling-origin validation for a 12-month horizon.
- A manual trigger boundary that can later be connected to Airflow.
- Annotated charts and written observations connecting modeling choices to delivery governance.
- An optional, gated build-vs-buy comparison against a pretrained TimesFM foundation model (`RUN_TIMESFM=1`), evaluating operational cost alongside accuracy instead of switching models by default.
The notebook calculates evaluation metrics for model selection, but the published case-study narrative intentionally avoids numeric business claims.
The notebook is structured for an executive audience: it starts with the business question, explains why the baseline exists, visualizes the signal and validation trade-off, and closes with delivery implications for a lean AI task force.
## Run
```bash
uv sync
uv run jupyter notebook notebooks/forecasting_case_study.ipynb
```
To execute the notebook non-interactively:
```bash
uv run jupyter execute notebooks/forecasting_case_study.ipynb --inplace
```
To also run the optional TimesFM foundation-model comparison (downloads ~500M-parameter weights
from Hugging Face; needs `torch`/`transformers` and internet access):
```bash
RUN_TIMESFM=1 uv run --with torch --with transformers jupyter execute notebooks/forecasting_case_study.ipynb --inplace
```
## Privacy boundary
This repository contains no company rows, identifiers, schemas, credentials, private URLs, or copied business values. Review `docs/publication-checklist.md` before publishing.
## Artifacts
- `notebooks/forecasting_case_study.ipynb` — reproducible analysis.
- `docs/architecture.mmd` — editable architecture diagram.
- `docs/architecture.svg` — editable/exportable diagram asset used by the handout.
- `docs/case-study-one-page.md` — Markdown source for the interview handout.
- `docs/case-study-one-page.html` — print-ready handout source.
- `docs/case-study-one-page.pdf` — generated one-page interview handout.
- `docs/linkedin-post.md` — publication draft.

16
docs/architecture.mmd Normal file
View File

@@ -0,0 +1,16 @@
flowchart LR
A[Synthetic monthly demand] --> B[Prepare dates and features]
B --> C[Trimmed-mean baseline]
B --> D[Gradient-boosting model]
C --> E[Rolling validation]
D --> E
E --> F[Select by sMAPE]
F --> G[12-month forecast]
H[Manual trigger today] -.-> B
I[Future Airflow boundary] -.-> B
subgraph Delivery["Delivery ownership"]
J[Business question] --> K[Reproducible evidence]
K --> L[Review and adoption]
end

39
docs/architecture.svg Normal file
View File

@@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1100" height="430" viewBox="0 0 1100 430" role="img" aria-labelledby="title desc">
<title id="title">Demand forecasting delivery architecture</title>
<desc id="desc">Synthetic demand flows through preparation, baseline and model, rolling validation, selection, and a twelve-month forecast. Manual triggering today has a future Airflow boundary.</desc>
<style>
.box { fill: #f5f7fb; stroke: #243b53; stroke-width: 2; rx: 12; }
.accent { fill: #d9f0ff; }
.text { fill: #102a43; font: 18px sans-serif; text-anchor: middle; }
.small { fill: #486581; font: 15px sans-serif; text-anchor: middle; }
.arrow { stroke: #486581; stroke-width: 2.5; fill: none; marker-end: url(#arrowhead); }
.dashed { stroke-dasharray: 8 7; }
</style>
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#486581"/>
</marker>
</defs>
<rect class="box accent" x="25" y="155" width="155" height="70"/>
<text class="text" x="102" y="185">Synthetic</text><text class="text" x="102" y="208">demand</text>
<rect class="box" x="220" y="155" width="165" height="70"/>
<text class="text" x="302" y="185">Prepare</text><text class="text" x="302" y="208">features</text>
<rect class="box" x="425" y="80" width="180" height="70"/>
<text class="text" x="515" y="110">Trimmed-mean</text><text class="text" x="515" y="133">baseline</text>
<rect class="box" x="425" y="230" width="180" height="70"/>
<text class="text" x="515" y="260">Gradient-boosting</text><text class="text" x="515" y="283">model</text>
<rect class="box" x="650" y="155" width="175" height="70"/>
<text class="text" x="737" y="185">Rolling</text><text class="text" x="737" y="208">validation</text>
<rect class="box" x="870" y="155" width="190" height="70"/>
<text class="text" x="965" y="185">12-month</text><text class="text" x="965" y="208">forecast</text>
<path class="arrow" d="M180 190 H220"/>
<path class="arrow" d="M385 190 H410 V115 H425"/>
<path class="arrow" d="M385 190 H410 V265 H425"/>
<path class="arrow" d="M605 115 H625 V190 H650"/>
<path class="arrow" d="M605 265 H625 V190 H650"/>
<path class="arrow" d="M825 190 H870"/>
<text class="small" x="300" y="45">Manual trigger today</text>
<path class="arrow dashed" d="M300 55 V145"/>
<text class="small" x="740" y="365">Future orchestration boundary</text>
<path class="arrow dashed" d="M740 350 V235"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,55 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Demand Forecasting Case Study</title>
<style>
@page { size: A4; margin: 14mm; }
body { color: #102a43; font: 10.5pt Arial, sans-serif; line-height: 1.35; margin: 0; }
h1 { color: #0b4f71; font-size: 24pt; margin: 0 0 4pt; }
h2 { color: #0b4f71; font-size: 13pt; margin: 10pt 0 3pt; }
p { margin: 4pt 0; }
.subtitle { color: #486581; font-size: 11pt; margin-bottom: 8pt; }
.card { background: #f5f7fb; border-left: 4px solid #2f80a8; padding: 7pt 9pt; }
img { display: block; margin: 5pt auto; max-width: 100%; }
.columns { display: grid; grid-template-columns: 1fr 1fr; gap: 12pt; }
ul { margin: 4pt 0 0 16pt; padding: 0; }
li { margin: 2pt 0; }
footer { border-top: 1px solid #bcccdc; color: #627d98; font-size: 8.5pt; margin-top: 9pt; padding-top: 5pt; }
</style>
</head>
<body>
<h1>From demand question to forecast evidence</h1>
<div class="subtitle">A privacy-safe, player/coach delivery case study using synthetic monthly demand</div>
<div class="card">
<strong>The case.</strong> Build a repeatable forecasting flow without exposing operational data.
The public version preserves the delivery decisions that matter: preparation, an auditable
baseline, model comparison, validation, and a clear operating boundary.
</div>
<h2>Architecture</h2>
<img src="architecture.svg" alt="Demand forecasting delivery architecture">
<div class="columns">
<div>
<h2>Modeling choices</h2>
<ul>
<li>Monthly demand by synthetic SKU and region.</li>
<li>Baseline: previous eight months, excluding one minimum and one maximum.</li>
<li>One gradient-boosting model with lag and calendar features.</li>
<li>Rolling-origin validation for a 12-month horizon.</li>
</ul>
</div>
<div>
<h2>Delivery choices</h2>
<ul>
<li>Start with a baseline stakeholders can audit.</li>
<li>Add complexity only when validation earns it.</li>
<li>Keep triggering manual while the workflow is proved.</li>
<li>Leave a clean seam for future orchestration.</li>
</ul>
</div>
</div>
<h2>Evidence</h2>
<p>The notebook computes validation metrics and produces comparison charts. This handout makes no numeric business claims because the data is synthetic.</p>
<footer>Public-safe artifact: no company rows, identifiers, credentials, private URLs, or copied business values.</footer>
</body>
</html>

View File

@@ -0,0 +1,25 @@
# From demand question to forecast evidence
## The case
Build a repeatable forecasting flow without exposing operational data. The public version uses synthetic monthly demand by SKU and region while preserving the delivery decisions that matter: data preparation, a transparent baseline, model comparison, validation, and a clear operating boundary.
## Architecture
```mermaid
flowchart LR
source[Synthetic demand] --> prep[Preparation/features]
prep --> baseline[Trimmed-mean baseline]
prep --> model[Gradient boosting]
baseline --> validate[Rolling validation]
model --> validate
validate --> forecast[12-month forecast]
```
## Delivery narrative
Start with a baseline that stakeholders can audit. Add one model only when validation shows a useful improvement. Keep the trigger manual while the workflow is being proved, then expose the same boundary to an orchestrator such as Airflow.
## Evidence
The notebook computes validation metrics and produces comparison charts. This handout deliberately makes no numeric business claims because the data is synthetic.

Binary file not shown.

13
docs/linkedin-post.md Normal file
View File

@@ -0,0 +1,13 @@
# LinkedIn draft
I built a privacy-safe demand-forecasting case study to show how I approach AI delivery as a player/coach.
The demo starts with synthetic monthly demand by SKU and region, then compares:
- an auditable baseline based on the previous eight months;
- one gradient-boosting model with lag and calendar features;
- rolling time-based validation instead of a random train/test split.
The important part is not adding the most models. It is making the path from business question to reproducible evidence explicit: prepare the data, establish a baseline, validate the model, document the operating boundary, and leave a clean seam for future orchestration.
The public repository contains no company data or private implementation details.

View File

@@ -0,0 +1,9 @@
# Publication checklist
- [ ] No credentials, tokens, private keys, `.env` files, or secret names.
- [ ] No company rows, identifiers, product names, plant names, or business values.
- [ ] No private schemas, endpoints, account names, or internal URLs.
- [ ] All charts and metrics come from synthetic data.
- [ ] Numeric results are labeled illustrative and are not presented as business outcomes.
- [ ] Notebook executes from a clean environment with `uv`.
- [ ] Diagram and PDF contain only generic architecture terms.

File diff suppressed because one or more lines are too long

16
pyproject.toml Normal file
View File

@@ -0,0 +1,16 @@
[project]
name = "ml-forecasting-case-study"
version = "0.1.0"
description = "Privacy-safe demand forecasting case study"
requires-python = ">=3.11"
dependencies = [
"jupyter",
"matplotlib",
"nbclient",
"numpy",
"pandas",
"scikit-learn",
]
[tool.uv]
package = false

2736
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff