Avanti Opal
A marketing budget allocation engine — and a manifesto for why it exists.
I got tired of the spreadsheet.
Not tired in the way people say it at conferences. Tired in the actual, physical sense — the kind that builds up over months of opening the same file, dragging the same formulas, manually computing marginal ROAS in a tab no one else will ever read, watching a media buyer override the number anyway because their gut said otherwise.
Marketing analytics, at the operational level, is mostly this: you write a formula, someone changes the budget, the formula is wrong now, you fix it, someone changes the budget again. The “quant work” in paid media isn’t particularly sophisticated — it’s just repetitive. Arithmetic disguised as strategy. And the real cost isn’t the time it takes. It’s that the repetition crowds out the thinking that would actually matter.
I didn’t want a better spreadsheet. I wanted something that could hold an opinion — one that was legible, auditable, and that I could argue with.
So I built Avanti. The name means “forward” in Sanskrit — not as a mission statement, just as a direction. The first versions were rough. A state machine and some STO logic living in a single HTML file, running entirely in the browser, processing CSVs. No backend. No database. No platform integrations. Just the data you fed it and the logic baked into the file.
Over the course of a year, version by version, the engine grew. The state machine got richer. The capital allocator went through multiple structural revisions. The LTV layer was added, debated, revised. The saturation curve fitting came later. So did the CRM compatibility layer, the attribution inflation auditor, the regime topology enforcement, the decay override. Each addition was a response to a specific failure — a real account, a real decision, a moment where the previous version of the engine said the wrong thing and I had to understand why.
Opal is where it stands today. 8,800 lines. A 12-stage pipeline. Three operating regimes. A capital allocator with five distinct decision paths. This document is an attempt to explain what it actually does — without softening the edges of what it cannot.
Not a dashboard. An opinion engine.
The distinction matters. A dashboard shows you what happened. Avanti v11.3c tells you what to do next and why — and it signs its reasoning. Every output is traceable. Every recommendation carries a rationale chain: which tier of logic produced it, what the alternative was, what would need to change for the decision to reverse.
The engine runs entirely client-side. You upload a CSV of daily channel-level spend and revenue data, map the columns, choose a regime, and run. The output is a structured set of decisions — action queue, capital routing table, policy audit log — built from a pipeline that processes your data through twelve sequential stages.
The Pipeline
Reads raw CSV rows and constructs one node per channel per window. Computes metrics, fits saturation curves where variance allows, detects anomalies, and assigns structural history.
Tier 1 gate. Checks measurement confidence per node. If platform ROAS is significantly higher than independent revenue, the node is flagged with SEVERE_INFLATION and frozen from routing.
Classifies every channel’s maturity state: NEW, LEARNING, STABILIZING, MATURE, FATIGUED, RECOVERING. The state machine uses hysteresis bands to prevent oscillation between states on noisy data.
Checks whether a channel’s assigned role (creation, capture, retention) has drifted structurally based on its revenue behaviour. A prospecting campaign spending like a retargeting campaign gets reclassified before regime routing runs.
Under Discovery mode, enforces hard structural caps: capture channels cannot exceed 30% of portfolio spend; creation channels must hold at least 35%. Nodes violating these caps are constrained before capital is allocated.
Maps upstream-downstream relationships between creation and capture channels. A retargeting node that depends on a prospecting node for audience supply gets a funding dependency flag, protecting the upstream channel from extraction.
The Spending Threshold Optimiser. Uses marginal ROAS from the fitted saturation curve (or average ROAS when a curve is unavailable) to classify each channel as elastic, inelastic, or saturating. This classification determines whether a channel is a capital recipient or a donor.
If long-term value data is present and the governance authority is strong enough, the LTV layer can override STO decisions — protecting a channel the STO wanted to cut, or capping one it wanted to scale.
Divides the total portfolio into four zones: frozen (untouchable), reserve (locked under low confidence), experimental (ring-fenced for new channels), and deployable (available for reallocation).
The capital allocator. Extracts from donors, scores recipients by a weighted formula incorporating ROAS, LTV, erosion risk, trend direction, and role. Enforces velocity caps per channel and zero-sum accounting across the portfolio.
Builds the audit trail. For every node, records the STO intent, any overriding tier, the final actuator, and the regime context string that produced the decision.
Translates the allocation output into operator-facing directives: plain-language instructions, urgency ratings, LTV context notes, and materiality classification. What you actually read and act on.
The engine doesn’t have one view of performance.
The single most consequential decision before running the engine is choosing a regime. This is not a filter or a display preference — it changes what the engine considers good, what it considers wasteful, and how aggressively it moves capital. The same portfolio, the same data, will produce materially different recommendations under different regimes.
| Regime | What it optimises for | How it behaves |
|---|---|---|
| EFFICIENCY MAX | Margin defense. Lower blended CAC. Ruthless waste elimination. | Elastic threshold is set high (1.56× marginal ROAS). Extraction from underperformers is moderate (0.8× coefficient). Velocity cap is tight at 20%. LTV-backed channels get a 1.5× weight boost; LTV-weak channels get 0.5×. Cannibalization prior applied to capture and retention nodes — retargeting and email ROAS are discounted because some of that revenue would have happened anyway. |
| VOLUME / SCALE | Top-line conversion volume. Accepts higher CAC in exchange for scale. | Elastic threshold drops to 0.90×. Extraction from donors is aggressive (1.6× coefficient). Velocity cap opens to 40%. The regime rewards channels producing revenue now, not channels with good long-term story. |
| MARKET SHARE / DISCOVERY | Demand creation. Funding under-tested paths. Building future pipeline. | Creation channels are guaranteed at least 35% of portfolio spend. Capture channels are hard-capped at 30%. 25% of the movable budget is ring-fenced for genuinely new channels. Evaluation metric for experimental nodes is signal accumulation — not ROAS. |
The regime is a belief, not a setting. Choosing Efficiency when you should be in Discovery doesn’t produce efficient outcomes — it produces a well-optimised portfolio that slowly loses market share. The engine will execute your regime faithfully. Getting the regime right is the human job.
What the engine can actually do.
Detects when platform-reported ROAS is significantly higher than independent revenue (from CRM, GA4, or a mapped comparison column). Computes a per-node inflation factor. Nodes above a 2× inflation ratio are flagged SEVERE_INFLATION and frozen from routing. The directive tells you the inflation factor, identifies the likely cause (attribution model, pixel configuration, view-through counting), and gives specific remediation steps.
For channels with sufficient variance in daily spend (CV ≥ 0.08), the engine fits a logarithmic spend-to-revenue curve. Marginal ROAS is computed from the derivative of that curve at the channel’s current spend level — not from window average. This matters: a channel at 4× average ROAS might have a marginal ROAS of 1.8× if it’s already deep into diminishing returns.
When long-term value signals are present — direct LTV data, LTV:CAC ratios, repeat purchase proxies, cohort scores — the engine adjusts routing decisions accordingly. A channel with strong downstream value is protected from extraction even when its window ROAS looks weak. A channel with high current ROAS but decaying cohort value gets an advisory penalty on its allocation weight.
The engine distinguishes between a channel that is underperforming and a channel that is actively deteriorating. If the ROAS trend is a statistically confirmed accelerating decline — high R², large directional drop — the channel’s extraction rate is upgraded to STARVE regardless of its current absolute performance. The capital is extracted before it strands.
Every channel is classified as demand creation (prospecting, awareness), demand capture (retargeting, brand search), or demand retention (email, loyalty). Under Efficiency mode, capture and retention channels receive a structural discount on their allocation weight because some fraction of their reported conversions would have occurred without paid intervention. The prior is not punitive — it is calibrating.
8% of the Deployable pool is allocated to channels the engine cannot read — channels with noisy, inconclusive ROAS trends that have enough history to be real but not enough signal to be classified. Selection is random among eligible channels. No performance metric influences which ones are chosen. The explicit purpose: breakouts look like noise before they look like signal.
Native schema guidance for Meta Ads Manager, Google Ads, Shopify Channel Performance, WooCommerce, HubSpot, Salesforce, Klaviyo, Northbeam, Triple Whale, Rockerbox, and GA4. The engine auto-detects column patterns and surfacesspecific mapping instructions for each source format.
All capital recommendations are enforced as zero-sum within the existing portfolio. Every gain is explicitly sourced from a specific donor or internal redistribution. The total recommended spend equals the original total spend. There is no synthetic capital, no phantom liquidity, no budget inflation. Every increase comes from somewhere and that somewhere is named.
What it cannot do, and why that’s honest.
Most tools in this category describe what they can do in their marketing and quietly omit what they cannot. This engine has a different policy. The limits are structural, not embarrassing — they reflect deliberate design choices about what a client-side, data-driven, operator-facing tool should and should not try to do.
The engine knows revenue changed. It does not know why. Creative fatigue, competitive CPM pressure, audience overlap, algorithm shifts — these are detectable in trend shape but not in cause. The fatigued state and mechanism tags (creative fatigue, auction pressure, landing page degradation) are diagnostic hypotheses, not confirmed diagnoses.
The engine operates entirely on your own data. It cannot observe competitor spend, competitor creative, platform auction dynamics, or market-level CPM trends. A channel that appears to be declining may be declining because a competitor entered the auction and raised CPMs by 40%. The engine will see the output — rising CAC, falling ROAS — but not the cause.
The engine does not track what it recommended last cycle, whether those recommendations were acted on, or what happened after. Every run is independent. This limits its ability to learn from execution outcomes — a channel that was scaled and then saturated looks the same to the engine as a channel that was never tested at that spend level.
The LTV governance system requires actual long-term value signals: direct LTV columns, LTV:CAC ratios, repeat purchase proxies, or cohort bucket scores. On accounts that only provide spend and revenue — the majority of real exports — the LTV basis defaults to PERFORMANCE_ONLY and the advisory penalty fires uniformly across all nodes. This is structurally correct but means the LTV layer produces little differentiation on thin datasets.
Channels can be scaled at a maximum of 20–40% of their weekly run rate per cycle, depending on regime. This prevents learning phase resets on Meta and Google (budget increases above roughly 20% in a short window force the algorithm to relearn audience selection, degrading performance for 7–14 days). The cap is operationally correct. It also means the engine cannot execute a full breakout scale in a single recommendation — that requires staged implementation over multiple cycles.
The engine cannot tell you which regime to run. Efficiency, Volume, and Discovery produce different — sometimes contradictory — recommendations on identical data. The correct regime depends on business context, growth stage, margin targets, and operator judgment. Selecting the wrong regime produces a perfectly executed wrong strategy.
Where most of the argument actually lives.
The capital allocator is the most contested part of the engine — the part that has been rewritten most often, critiqued most sharply, and improved most substantially over the course of development. It is worth describing in some detail.
trustAdjustedROAS
× ( 1 + ltvScore )
× ( 1 − erosionRisk )
× authorityMultiplier
× regimeMultiplier
× advisoryPenalty
× trendMultiplier
× cannibalizationFraction
Each channel’s score is a product of eight factors. The product is then normalised across all scaling candidates, and each channel receives a proportional share of the available capital — subject to a per-channel velocity ceiling that prevents over-scaling in a single cycle.
The allocator operates on real scarcity. The available pool is determined by what can be extracted from underperforming channels, with a minimum floor to ensure the engine produces at least some movement even when every channel is performing above threshold. Every recommendation is zero-sum: the total recommended spend equals the original total. Gains are explicitly sourced.
For portfolios where every channel is above the elastic threshold — no donors, no underperformers — the engine switches to internal redistribution: it takes from the bottom quartile (by ROAS) and gives to the top performers. This produces meaningful moves rather than spreading a synthetic floor across 26 nodes and calling it reallocation.
What this replaces, and what it doesn’t.
There have been several external critiques of this engine, and they deserve acknowledgment because some of them are correct. The allocator is more governor than aggressive optimizer — it is designed to prevent bad decisions more than to maximise upside. It will not tell you to double a budget overnight. It will not ignore a declining trend because the channel “feels strategic.” It produces explainable decisions over optimal ones when the two conflict.
This is a deliberate choice, not a failure of ambition. The operators using this engine run paid media accounts where a wrong recommendation has a real cost — a client relationship, a CAC that blows out for two weeks, a budget that gets misallocated in a way that is visible to stakeholders. In that context, “explainable and directionally correct” is more valuable than “theoretically optimal and opaque.”
The engine will outperform any team running on instinct and spreadsheets. It will underperform a genuinely skilled operator with better forward information. It is designed for the first situation, not a replacement for the second.
Where it genuinely adds value: accounts with 5–30 channels, two or more years of daily history, multiple platforms running simultaneously, and operators who need to justify budget decisions to clients or finance teams. The audit trail alone — the policy audit log, the regime context string, the rationale chain — changes the quality of the conversation around budget allocation. You stop arguing about intuitions and start arguing about assumptions, which is a better argument to have.
Where it does not add value: accounts with fewer than 4 channels and 30 days of history. Accounts where creative testing is the primary driver of performance variance and budget allocation is secondary. Accounts where platform-reported ROAS is the only available measurement and there is no way to audit it independently.
The engine is not on this website.
What you are reading is a description of Avanti v11.3c. The engine itself — all 8,800 lines of it, the full pipeline, the allocator, the saturation curve fitting, the LTV governance logic — is not hosted here, and that is deliberate.
The engine runs as a single self-contained HTML file. Everything that makes it work lives in that file: the state machine, the capital allocation logic, the regime parameters, the scoring formula, the audit trail generation. Putting that file on a public URL would mean putting the source code on a public URL. Anyone with a browser and thirty seconds could read, copy, or adapt it. That is not a trade I am willing to make.
The work took two years. The IP stays protected. If you want to see it run, I will show you.
If you are a brand, an agency, or an operator running paid media at scale and you want to see what the engine actually produces on real data — bring a CSV export from your ad platform, and I will run it live. You will see the full output: the action queue, the capital routing table, the saturation curves, the audit log, the LTV diagnostics. Everything described in this document, against your own numbers.
No deck. No recorded demo. A live run on your data, with you in the room, so you can ask why it said what it said.
bring your data · i’ll bring the engine
The spreadsheet is still open.
The engine is better.
Avanti v11.3c is not finished. No version of this engine has ever been finished. Each deployment reveals a gap, a wrong assumption, a case the logic didn’t anticipate. That is not a problem — it is the nature of building something that has to be honest about a complicated world.
What matters is that it signs its reasoning. You can agree with it, override it, or argue with it — but you cannot misunderstand what it said.