Skip to main content
Methodology
Back to settings

Grading methodology

How Ads Manager assigns the A–F grade.

72Grade B
A grade is a weighted score.

Every campaign with at least one impression gets a 0–100 score: a weighted sum of seven dimensions, with weights that shift by campaign objective. The score maps to a letter grade so you can triage at a glance, and the dimensions tell you what to fix.

The grade scale

Scores are bucketed into five fixed bands. The thresholds are static — they are not calibrated against other accounts or a platform-wide median.

A85–100
Excellent. Maintain.
B70–84
Good. Optimize edges.
C55–69
Average. Review.
D35–54
Poor. Pause or fix.
F0–34
Critical. Stop spend.

Campaigns with zero impressions in the analysis window are shown as “insufficient data” instead of a grade. Below roughly 1,000 impressions, ratios like CTR are statistically noisy — treat young campaigns’ grades as indicative, not final.

Seven dimensions

The composite score is the weighted sum of these seven dimensions, each scored 0–100 against the benchmark table for the campaign’s platform and objective. When a dimension’s input isn’t available (no previous period for trend, no reach data for saturation), it is excluded and the remaining weights are renormalized — a missing input never scores a disguised neutral. A former eighth dimension (creative health) was removed: its data was never fed, so it silently pulled every score toward a neutral default.

CTR

Click-through rate against the benchmark table for the campaign's platform and objective (e.g. 'good' is ~1.4% on Meta feed but ~3.5% on Google Search). Banded: ≥excellent scores 100, ≥good 72, ≥average 48, ≥poor 20.

Feeds: clicks ÷ impressions (from platform reporting)
CPC

Cost per click against the platform×objective benchmark (e.g. 'good' is ~€0.90 on Meta, ~€4.50 on Google Search). Banded: ≤excellent scores 100, ≤good 75, ≤average 50, ≤poor 20.

Feeds: spend ÷ clicks
CPM

Cost per 1,000 impressions against the platform×objective benchmark. Banded: ≤excellent scores 100, ≤good 75, ≤average 50, ≤poor 25.

Feeds: spend ÷ impressions × 1000
Budget

Utilization of the daily budget, normalized by the length of the analysis window: 80–105% of the intended spend scores 100; over- and under-spend score lower. A user-set budget target (campaign targets) takes precedence over the platform budget.

Feeds: period spend ÷ (daily budget × days in window)
Conversions

Conversion rate per click: ≥5% scores 100, ≥2% scores 70, ≥1% scores 40. Zero conversions scores 0. Counted conversions are purchases or leads as reported by each platform. Conversion VALUE (revenue) is ingested where the platform reports it (Meta, Google, Pinterest), enabling a real ROAS in the campaign list and narration — but the score itself stays count-based, not revenue-weighted.

Feeds: conversions ÷ clicks
Trend

CTR/CPC/CPM momentum vs the window of equal length immediately before the analysis window (±5% bands). The campaign analysis fetches that previous window explicitly. When no previous-period data exists, the dimension is EXCLUDED and the remaining weights are renormalized — it never scores a hidden neutral.

Feeds: current vs previous period CTR, CPC, CPM
Saturation

Ad frequency: ≤1.5 scores 100, ≤2.5 scores 75, ≤3.5 scores 50, above scores 25. Frequency is derived as impressions ÷ reach when the platform reports reach. Without reach data the dimension is EXCLUDED (weights renormalized) — never a fake 75.

Feeds: average frequency (impressions ÷ people reached)

Weight shifts by objective

A brand-awareness campaign shouldn’t be penalised for weak conversions the same way a conversion campaign would. Five weight profiles cover the objective families; campaigns whose objective doesn’t match any profile default to the traffic profile.

ObjectiveCTRCPCCPMBudgetConvTrendSatur
Traffic30%30%10%10%5%15%0%
Conversions15%20%10%10%35%10%0%
Awareness10%5%35%15%0%15%20%
Leads20%15%10%10%30%15%0%
Engagement30%10%15%15%5%15%10%

When scores compute

Scores are computed on demand, every time a page that shows them loads — there is no background recompute schedule and no manual “recompute” button. The default analysis window is the last 30 days (some surfaces use 7 days); the budget dimension normalizes spend by the window length so a 30-day window isn’t judged against a single day’s budget.

Data sources & honest limits

Grades pull exclusively from official platform reporting APIs (Meta Graph, Google Ads, TikTok, Snapchat, Pinterest, Microsoft, LinkedIn — depending on which accounts you connect). We never scrape ad-account UIs, and we never share your data with third parties. OAuth tokens are encrypted at rest (AES-256-GCM).

  • The CTR/CPC/CPM benchmark bands start from static per-platform×objective tables, banded from public 2024–25 industry reports (good ≈ published median). Once an account has roughly 14 days of synced history, those bands are re-anchored to the account’s own trailing-90-day median (from the History page): the center blends toward the account median — capped so the industry table always keeps a vote, ramping up with more history — while the band shape (excellent:good:average:poor ratios) stays the industry one. With thin history the static table is used unchanged, so two accounts on the same platform×objective can be graded against different bars.
  • Conversion value(revenue) is ingested only where the platform reports it (Meta’s action_values, Google’s conversions_value, Pinterest’s TOTAL_CHECKOUT_VALUE_IN_MICRO_DOLLAR). A real ROAS (value ÷ spend) is then shown — in the campaign-list ROAS sort, the LLM narration, and the autopilot roas_floor guardrail —onlywhere that value is > 0. It is never a proxy: platforms that don’t report value show “—” and fall back to cost per conversion (CPA). ROAS does not enter the 0–100 score itself.
  • “Conversions” are what each platform reports as purchases or leads; attribution models differ per platform and are not normalized.

Retention.Insights are read on demand from the platforms’ APIs. Synced history (the History page) is kept until you disconnect the source account.

Formula

The composite score is a weighted sum over the available dimension scores, renormalized by the total weight of the dimensions that actually had data. Below is the canonical formula with the conversion-objective weights:

score = (0.15 * ctr + 0.20 * cpc + 0.10 * cpm
       + 0.10 * budget + 0.35 * conversions
       + 0.10 * trend + 0.00 * saturation) / Σ(available weights)

each dimension ∈ [0, 100], scored against the platform×objective benchmarks;
dimensions with no input data are excluded and the sum renormalized

grade = A if score ≥ 85
      | B if score ≥ 70
      | C if score ≥ 55
      | D if score ≥ 35
      | F otherwise

zero impressions in the window → "insufficient data", no grade

Weights live in src/lib/ai/scoring/weights.ts, thresholds in dimensions.ts, grade bands in categories.ts. Changing them requires a deploy.

Grades are advisory. The platform never pauses or scales spend without an explicit Autopilot rule.