Overview
When your Meta CAC starts climbing, it is rarely obvious whether a specific ad has peaked or whether the whole account is under pressure. Without a structured way to assess each ad’s health relative to its own history and the campaign blend, you end up pausing ads reactively or, worse, leaving fatigued ads running and burning budget. This report solves two connected problems. First, it classifies every active ad into a precise fatigue state each day, so you always know which ads to act on and which to leave running. Second, it forecasts how your viable ad portfolio will evolve over the next 60 days and tells you exactly how many new ads you need to launch each week to sustain a target daily spend.How fatigue is classified
Each ad is assessed daily using two comparison axes:pct_worse_than_peak— how much worse the ad’s rolling CAC is compared to its own historical best (peak_cac_normalised)pct_vs_blended— how much worse the ad’s rolling CAC is compared to the campaign-type blended average across all active ads
INACTIVE - no impressions today
INACTIVE - no impressions today
RAMPING - below spend or age thresholds
RAMPING - below spend or age thresholds
UNKNOWN - missing CAC data
UNKNOWN - missing CAC data
LOW_EFFICIENCY - CAC 20%+ above campaign-type blend
LOW_EFFICIENCY - CAC 20%+ above campaign-type blend
IMPROVING - recovering from a past peak decline
IMPROVING - recovering from a past peak decline
FATIGUED - fully degraded and confirmed declining
FATIGUED - fully degraded and confirmed declining
FATIGUING - declining from its own peak
FATIGUING - declining from its own peak
HEALTHY - within tolerance on both axes
HEALTHY - within tolerance on both axes
fatigue_score between 0 and 1, where 0 is fully healthy and 1 is fully fatigued. Use this field to sort ads by severity or to build calculated fields in Looker Studio.
How the forecast works
A viable ad is one the survival model expects to still be in a non-fatigued state, meaning not yet classified as FATIGUED or LOW_EFFICIENCY. Viability is expressed as a probability derived from historical survival curves: at each ad age, the model looks at what fraction of real ads in your account remained non-fatigued at that age, and uses that as the survival rate. An ad with a survival rate of 0.8 contributes 0.8 to theexpected_viable_ads count.
As an ad ages, that survival rate decays to reflect the increasing likelihood it has crossed a fatigue threshold, so older ads contribute progressively less to projected spend until they effectively age out of the portfolio.
The forecast takes all currently active ads on the reference date and applies a blended survival curve derived from historical data. The survival curve represents the probability that an ad launched on any given day remains viable as it ages.
Beyond the maximum observed ad age in the historical data, the model extrapolates using a 5% daily decay applied to the last observed survival rate.
Two summary outputs are produced:
avg_viable_lifespan— the expected number of days an ad remains viable after launch. It is used to calculate how many new ads you need to launch per day to keep your portfolio at a given size, because a longer lifespan means each ad covers more days before it needs replacing.cumulative_survival_rateat day t — if you launched exactly one new ad per day from today, this is the number of viable ads you would have from that stream on day t. It is the building block for the reactive launch model.
ALL row. Always filter to campaign_type = 'ALL' when building totals in Looker Studio to avoid double-counting across campaign types.
The forecast horizon is 60 days forward from the reference date.
Planning with a target daily spend
The Looker Studio data source accepts@target_daily_spend as a user-input parameter. This allows you to set a daily spend goal and see whether your current ad portfolio is sufficient to support it, and how many new ads you need to launch to close any gap.
Key outputs from the planning model:
Data dictionary
Ad fatigue status (demo-meta_ad_fatigue_status)
Ad fatigue status (demo-meta_ad_fatigue_status)
COUNT_DISTINCT(ad) for ad counts and SUM(spend) / SUM(new_orders) for CAC. Filter is_active_today = TRUE for active ad metrics.Ad fatigue forecast (demo-meta_ad_fatigue_forecast)
Ad fatigue forecast (demo-meta_ad_fatigue_forecast)
campaign_type = 'ALL' for portfolio totals.Ad fatigue forecast - Looker Studio layer (demo-meta_ad_fatigue_forecast_looker_studio)
Ad fatigue forecast - Looker Studio layer (demo-meta_ad_fatigue_forecast_looker_studio)
@target_daily_spend parameter. Filter campaign_type = 'ALL' for portfolio totals.Questions this report answers
Which ads should I pause or refresh right now?
Which ads should I pause or refresh right now?
fatigue_status = 'FATIGUED' to find ads that have deteriorated significantly from their own peak, are not recovering, and confirmed that decline the previous day. These are the strongest candidates for pausing or replacing with a refreshed creative. Ads showing FATIGUING are early warnings: worth reviewing but not necessarily pausing yet (unless performance warrants).Am I launching enough new ads to sustain my target spend?
Am I launching enough new ads to sustain my target spend?
@target_daily_spend parameter in the Looker Studio report and check portfolio_adequacy_pct. If it is below 100%, your current portfolio cannot cover the target. Use target_ad_velocity_7d to understand your weekly creative output requirement and compare it against your actual launch rate to close the gap.Why does my portfolio dip at weekends in the forecast?
Why does my portfolio dip at weekends in the forecast?
How does the model decide an ad is 'fatigued' vs just having a bad day?
How does the model decide an ad is 'fatigued' vs just having a bad day?
How far ahead can I trust the forecast?
How far ahead can I trust the forecast?
Need help? Contact our team or book a demo.