01What this model is, and isn't
This is a seasonality-and-efficiency allocation model. Given a fixed annual media pool and a month-by-month revenue (TTV) target, it phases that pool across the 12 months so the months historically best at converting spend into TTV carry more of the load, adjusted for how current market demand compares to history.
The tool labels itself Pre-MMM. This is important because it is not a marketing mix model, and it does not model:
- Marginal returns. It reasons entirely in terms of average historical efficiency (TTV divided by spend for the whole month), never the return on the next dollar. Moving money into a month assumes that month's next dollar performs like its average dollar, which this model never actually tests.
- Channel or creative-level detail. Everything is a single blended monthly efficiency number. There is no breakdown by channel, campaign, or creative.
- Booking lag or attribution windows. Every dollar of spend and every dollar of TTV is bucketed into the calendar month it fell in, with no adjustment for the delay between media exposure and a booking landing.
What it does do with real rigour: take three years of actual month-by-month efficiency, blend them with adjustable recency weighting, correct that blend for an independent read of current market demand, and use a single slider to set how much weight that efficiency read gets against simply mirroring the revenue target's own shape.
02The building block: MER and the seasonality index
Everything starts from Media Efficiency Ratio (MER), TTV divided by spend, calculated per month and per fiscal year for three years of TripADeal history (FY23/24, FY24/25, FY25/26).
mer[year][month] = ttv[year][month] / spend[year][month]
The annual baseline for each year is spend-weighted, not a simple average of the 12 monthly MERs:
annual[year] = sum(ttv[year]) / sum(spend[year])
Note
Because it's spend-weighted, months where TripADeal historically spent more pull the annual baseline toward their own efficiency more than low-spend months do. Two years with identical average monthly MER but different spend distributions produce different annual baselines.
Each month is then indexed against its own year's baseline. This is the number used everywhere downstream:
idx[year][month] = mer[year][month] / annual[year]
An idx of 1.20 means that month ran 20% more efficiently than its year's average month. An idx of 0.80 means the opposite. This is a relative read: it strips out year-on-year growth or decline in overall efficiency, so the three years become comparable regardless of whether one year was simply a better year overall.
Best-ever and the Proven/Stretch split
For every calendar month, the tool takes the single best MER any of the three years achieved:
best[month] = max(mer[FY23/24][month], mer[FY24/25][month], mer[FY25/26][month])
The Recommendation table's Proven/Stretch tag compares the model's required MER for that month (TTV target divided by recommended spend) against this ceiling. Proven means the plan asks for no more efficiency than history has already delivered at least once. Stretch means it asks for something no year has ever actually achieved in that month.
Spread and signal confidence
For each month, the tool also measures how much the three years' seasonality indices disagree with each other:
spread[month] = max(idx across 3 years) - min(idx across 3 years)
signal = spread < 0.25 -> HIGH confidence
spread < 0.60 -> MEDIUM confidence
otherwise -> LOW confidence
A small spread means the three years broadly agree that a month is seasonally strong or weak, so the pattern is probably real. A large spread means the years disagree, which usually points to one-off campaign timing or a single unusual year masquerading as seasonality.
03Blending three years of history
The three years' seasonality indices combine into a single blended index per month using adjustable recency weights, the numbers in the "Weighting & seasonality" grid, one weight per year per month:
blend[month] = (idx[FY23/24][month]·W[0][month] + idx[FY24/25][month]·W[1][month] + idx[FY25/26][month]·W[2][month])
────────────────────────────────────────────────────────────────────────
W[0][month] + W[1][month] + W[2][month]
Because the formula divides by the sum of the three weights, they don't need to add to 100%. The result is driven by the ratio between them, not their absolute size, and the tool renormalises automatically. If all three weights for a month are 0, the formula falls back to a blend of exactly 1: perfectly average, no seasonality signal at all. That default is silent, so it's easy to miss if a month's weights are ever cleared entirely.
The default starting weights
The tool doesn't open on flat 20/30/50 recency weighting (FY23/24 / FY24/25 / FY25/26) for every month. The default "Current" preset applies 20/30/50 as a baseline, then overrides five specific months to 30/50/20 instead (Jul, Dec, Feb, Mar, Apr), and further overrides July uniquely to 30/70/0. That means zero weight on FY25/26 for July specifically.
The tool's own interface requires an explanation for any non-default weight, logged in the "Event & dates" row, and flags a month if one is missing.
04The Google demand signal
Historical seasonality alone can't distinguish "this month is efficient because of something structural about the calendar" from "this month was efficient three years running by coincidence, and market conditions have since changed." The demand signal is the model's attempt to correct for the second case, using external data TripADeal's own spend doesn't directly control.
Seven possible sources, each independently switchable:
| Source | Default | What it measures |
| Google Trends | On | Search interest in the package/tour category generally. The only source fully independent of TripADeal's own spend, ranking, or bidding. |
| Search Console, brand | Off | Organic impressions on TripADeal-brand searches. Partly caused by TripADeal's own brand media spend, so not fully independent. |
| GA4 direct sessions | Off | People who typed the URL directly. A clean brand-demand read, unaffected by ranking or bidding. |
| Search Console, non-brand Risky | Off | Impressions on category searches TripADeal ranks for. Moves when SEO ranking changes, independent of any real change in market demand. |
| Paid search, non-brand themes Risky | Off | Driven directly by TripADeal's own paid search budget and bidding. This is a media output, not a market read. |
| GA4 organic sessions Risky | Off | Sits downstream of demand, ranking, and click-through all at once, so it moves for reasons unrelated to the market. |
| Google Ads impressions Risky | Off | Directly driven by TripADeal's own ad budget and bidding strategy. The tool's own description marks this diagnostic only. |
Default state, as shipped
Only Google Trends is switched on by default. Every number in the model, unless someone has changed the toggles, is demand-corrected using search-volume trend data alone. None of the other six sources contribute anything unless manually enabled.
Circularity risk
Four of the seven sources are flagged "risky" in the tool's own copy specifically because they're driven by TripADeal's own spend or ranking rather than the underlying market. If any of those are switched on, a demand signal that's partly caused by past media spend ends up feeding a decision about future media spend, which can produce a self-reinforcing loop rather than a genuine correction. The arithmetic itself isn't broken here, and the tool's "risky" labelling already discloses the issue. The impact is on interpretation, not calculation: which sources are switched on determines whether the resulting number reads as an independent market signal or partly as an echo of past spend.
How the sources are blended
For every active source, both its available years of data get pulled in and pooled, weighted by the same recency weights (W) used for the historical MER blend above, into one running numerator and denominator per month:
for each active source, for each year that source has data:
numerator[month] += source_index[year][month] × W[year][month]
denominator[month] += W[year][month]
demand_raw[month] = numerator[month] / denominator[month]
Note
This pools every active source-year combination into one shared sum. It does not average per-source results together afterward. A source with three years of coverage contributes three terms to the sum; a source with only two years (several do: Search Console only has FY24/25 partial and FY25/26) contributes two. Sources with more historical coverage carry more implicit weight in the blend simply because they contribute more terms, not because anyone weighted them higher on purpose.
The pooled result is then indexed so the 12-month average sits at exactly 1.0:
demand.idx[month] = demand_raw[month] / average(demand_raw across all 12 months)
05The demand correction strength slider
This is the "Demand correction strength" slider on the main screen. It controls how hard the demand index above is allowed to bend the historical seasonality blend:
adj[month] = blend[month] / demand.idx[month] ^ strength
strength = slider value / 100 (0.00 to 1.00)
At 0%, demand.idx is raised to the power of 0, which equals 1 for any number, so the division does nothing. adj equals the pure historical blend from Section 2, completely unadjusted for current demand.
At 100%, the full demand index divides straight into the historical blend.
Mechanically: a month where current demand runs above its own annual average (demand.idx greater than 1) gets divided down. Its adjusted index falls, and that later translates into more budget for that month, not less. A month where demand is currently depressed gets pushed the other way.
Interpretation, not documented intent
The model contains no comment or specification explaining the intended reasoning here, so the description above is a best-effort reading of the arithmetic, not a documented design choice. One reasonable interpretation: a month that was historically efficient and is currently seeing elevated independent demand deserves more confidence and more budget, while a month that was historically efficient partly because of temporarily favourable demand that isn't repeating this year should be corrected down. A Deloitte-trained reviewer will likely ask why more demand means more budget rather than less, given diminishing returns, since this model doesn't account for diminishing returns at all (see the limitations section). The model as it stands has no documented answer to that question.
The default sits at 50%, a halfway point between ignoring demand entirely and fully deferring to it.
06The efficiency vs. effectiveness dial
Once adj is computed above, it's renormalised so the TTV-weighted average sits at 1 across the year:
nf = sum(adj[month] × TTV27[month] for all months) / sum(TTV27)
norm = adj / nf
The dial then blends this fully-adjusted index against a flat baseline of 1, meaning no adjustment at all:
applied[month] = 1 + (norm[month] - 1) × dial
dial = slider value / 100 (0.00 to 1.00)
What each end of the dial means, precisely:
- 0% (pure "Effectiveness"): applied = 1 for every month, regardless of everything computed above. Required spend per month becomes exactly proportional to that month's share of the TTV target. The budget shape simply mirrors the revenue target's shape, with no efficiency or demand reasoning applied at all.
- 100% (pure "Efficiency"): applied = norm exactly. The full three-year, demand-corrected efficiency read determines the shape of the plan, even where that pulls budget away from the months carrying the largest revenue targets.
The required spend per month, and the raw (pre-lock, pre-override) allocation, follow directly:
need[month] = TTV27[month] / applied[month]
raw[month] = need[month] / sum(need) × total pool
The default sits at 25%, closer to the "chase the revenue target's shape" end than the "chase historical efficiency" end.
07Locking months, actuals, and reforecasting
Once a month has actually happened, locking it freezes its number and treats it as effectively spent. The model doesn't re-optimise history, only what's still open:
lockedSpend[month] = if locked: (actual spend, if entered - else the manual override, if any - else the raw model number)
committed = sum of all locked spend, plus all fixed (non-locked) overrides
left = total pool minus committed
The remaining, still-open months are then scaled so they exactly consume whatever's left in the pool. This is a proportional redistribution of the raw model shape, not a fresh re-optimisation of the open months in isolation:
scale = left / (sum of raw[month] for every still-open month)
fin[month] = locked or overridden amount, if set - else raw[month] × scale
The reforecast panel's numbers follow mechanically from this. TTV banked and spend committed sum the locked months (using actual TTV where entered, else the original target). MER to date divides one by the other. Budget remaining and TTV still to find are the pool and target totals minus what's already banked. MER required from here divides the two.
08Manual overrides
A manual override on a month not yet locked pins that month's spend to an exact dollar figure, removed from the "open" pool that gets proportionally scaled. Every other open month absorbs the effect of that override through the scale factor above. The tool ships with one override already set by default: December is pre-set to $1.5m, independent of anything a user has entered.
09Known limitations
Six of these are stated directly in the tool's own "Known gaps" tab. The last three come from reading the underlying code closely.
- Several months require efficiency above the best any year has ever delivered. True of the current live plan too. This reflects the level of the FY27 target itself, not how the model phases the budget.
- Average returns, not marginal returns. Moving money into a month assumes its next dollar performs like its average dollar, a claim this model never tests. A proper marketing mix model is the only way to answer that.
- Booking lag is unmeasured. Every dollar and every TTV figure is bucketed into the calendar month it fell in, with no decay curve for the gap between exposure and booking.
- Five months carry non-default weights as shipped (see the blending section). Each should reflect a specific, documented event logged in the "Event & dates" row.
- Demand isn't cleanly separated from media-driven effects unless the toggles are deliberately kept to the non-risky sources.
- Historical spend reflects earlier planning decisions, not a controlled experiment. A month that was deliberately underspent in the past shows inflated efficiency for that reason alone, so the seasonality index is directional, not a precise physical constant.
- Only Google Trends is on by default. Any demand-adjustment number quoted from an unmodified session reflects one data source, not the full suite the tool supports.
- The demand-correction direction is an interpretation, not a documented design choice. It should not be presented as settled reasoning without further verification.
10Sensitivity checks worth running
None of these change any saved data. They're safe to try and revert, or just skip "Save checkpoint" while exploring.
Isolate the demand correction
Set "Demand correction strength" to 0%, note the plan, then set it to 100%. The gap between the two is the entire effect that slider has, in isolation, on top of pure historical seasonality.
Isolate the efficiency/effectiveness dial
Set the dial to 0% (plan mirrors the TTV target exactly) and then 100% (plan follows pure historical and demand-adjusted efficiency). The live plan sits at a blend point between these two extremes.
Test single-source sensitivity
With everything else fixed, toggle one Google source on at a time from the default all-off-except-Trends state. Whichever toggle moves the plan the most deserves the closest look, especially the four flagged "risky."
Compare weighting presets
Switch between "Current" (the default, with its five month-specific overrides) and "Default" (flat 20/30/50 everywhere) to see how much of the live plan's shape comes from those specific overrides versus the underlying three-year data.