Campaign Calendar Simulator
Marketing wanted to know what next quarter's promo calendar would do to subscriptions — before running it. So they got a calendar they can drag offers onto, with the forecast redrawing as they go.
Open the live demo- Role
- Requirements, model, and build
- Context
- RCS MediaGroup
- Period
- 2025 – 2026
- Stack
-
- Python
- FastAPI
- JavaScript
- Object storage
- Excel export
- Reference inputs baseline, seasonality, offer lift
- Object storage flat files, refreshed by batch
- In-memory service gzipped JSON, one round trip
- Browser simulation redraws on every drag
Objective
Commercial planning ran on a spreadsheet and an argument. Every promotional calendar raised the same three questions: how many new subscriptions should we expect, how much of that is just seasonality, and how much are we stealing from the weeks either side.
The point was not to build a better forecast in a notebook. It was to put the forecast where the decision happens, so a planner could try six calendars in an afternoon instead of briefing an analyst and waiting two days.
Approach
The model is deliberately transparent rather than clever. Daily baseline, multiplied by a seasonality curve, multiplied by the strength of whatever offer is live that day, with an explicit cannibalisation term that pulls down the days around an aggressive promo. Every factor is a value a planner can see, question and override for a single day.
That transparency was the requirement. A gradient-boosted model that no one can argue with does not get used in a negotiation. A multiplicative model with visible factors does, because when someone says “that Tuesday looks wrong” you can point at the number they disagree with.
Retention arrived later as a second area: renewal and churn by product and duration, one view per metric, plus a deep-dive across eleven dimensions — since the value of an acquisition depends entirely on whether it renews.
The tool grew into sections owned by different teams, which produced the view I did not plan and would now build first: a recap that shows the total and every input it depends on, each marked with the team that owns it and whether it has landed. A forecast nobody can audit gets argued with; a forecast that says “step five is still missing, and it is yours” gets finished. The actuals view is the other half of that honesty — plan against reality, after excluding the test orders, gifts and refunds the model never tried to predict in the first place.
Architecture
Reference inputs (baseline, seasonality, offer catalogue, promo lift, cannibalisation coefficients, retention aggregates) are produced by scheduled batch jobs and land as flat files in object storage. The service reads them, holds them in memory and serves the UI; the simulation itself runs client-side, which is what makes dragging an offer feel instant.
Per-user calendar state is persisted server-side, so a planner can close the tab mid-scenario and come back to it. Authentication is a token issued against a credentials file mounted from a secret store — small tool, small threat model, no identity provider needed.
One detail I would repeat: the largest input file is tens of megabytes of daily retention detail, so responses are gzipped at the middleware level. It turned a sluggish tab into an instant one for the cost of one line.
What I owned
The brief, the model and the product: sitting with the commercial team to work out what they actually needed to decide, the multiplicative model and its calibration, the API, and the vanilla-JavaScript frontend (calendar, chart, offer modals, Excel export).
Deployment and infrastructure were our data engineers’. Their conventions are encoded in the skills I write, so what I hand over is deploy-ready without me stepping into their job.
Outcome
Promotional planning moved from a spreadsheet plus an analyst request to a tool the commercial team opens themselves. The valuable side effect was vocabulary: once “cannibalisation” was a visible number on a chart, the conversation about whether a promo was worth running got noticeably shorter.