Recommendation Methodology
DURUM.ai generates recommendations (Insight DURUM) from your campaign data, your funnel, your sales and, if you connected them, your recorded calls. This page describes exactly how the engine decides, with the real thresholds, and what it does not do.
Seven Principles
Every recommendation follows these rules. If a rule cannot be satisfied, the engine generates nothing rather than risk misleading you.
1. Reliability Before Volume
Three guards apply before any recommendation:
- No data, no recommendation. If the selected period contains neither ad spend nor any event (lead, booking, sale), the engine stops.
- Data confidence index. The engine computes a
dataConfidencebetween 0 and 1 (formula below). Below0.3, no recommendation is generated. - Attribution coverage. Coverage is the share of sales linked to an ad. Rules that depend on campaign-level revenue (for example
increase_budgetandchannel_role) require at least 50% coverage (attributionFloor, default profile value). Between 50% and 70%, the recommendation carries the note "partial UTM attribution"; below 50%, the note "weak UTM attribution, recommendation based on partial data".
Finally, any recommendation whose confidence is below 40% is discarded before display.
2. Never a Fixed Number Promise
Every monetary impact estimate is a range, never a single figure. The range is built around a midpoint with a spread of plus or minus 40% by default (rangeSpread, from 30% to 60% depending on the business profile). For a midpoint of $50K, you will see "$30K to $70K".
Each impact is introduced by "Illustrative scenario" and accompanied by this disclaimer:
Estimate based on recent data. Depends on execution and market conditions.
It is never a guaranteed result. It is an order of magnitude to help you prioritize.
3. Full Context or Nothing (Creative Fatigue)
The engine does not conclude "creative fatigue" from a simple CTR drop. Diagnosing an ad first requires at least 1,000 impressions and $50 of spend (otherwise the verdict is insufficient_data). It then cross-references:
- the CTR trend (last 7 days vs the previous 7 days);
- the CVR (conversion rate) trend, when per-ad conversions are available;
- the CPM trend;
- frequency (impressions / reach);
- the ad's age (
days_active); - the seasonal calendar.
Seasonal windows during which the engine refuses to conclude fatigue: Black Friday and Cyber Monday (November 20 to December 3), Holiday season (December 15 to January 5), Boxing Day (December 26 to 31), Valentine's Day (February 7 to 15), quarter ends (March 25 to April 2, June 25 to July 2, September 25 to October 2, December 20 to 31) and back to school (August 15 to September 5).
The returned verdict, in evaluation order:
| Verdict | Condition | Reading |
|---|---|---|
seasonal | Active seasonal window | Atypical metrics, no conclusion |
budget_issue | CPM up 25% or more, CTR and CVR stable (variation under 10%) | Bidding or competition problem, not creative |
landing_issue | CVR down 25% or more, CTR stable | The problem is downstream of the click (page or offer) |
saturation | Frequency above 3 and CTR down 15% or more | Change the audience before changing the creative |
fatigue | CTR down 20% or more, frequency at or below 3 | The message no longer resonates: new creatives |
healthy | Ad active for more than 21 days without degradation, or no clear signal | Nothing to do |
This diagnosis feeds the creative_concentration rule, triggered when two ads concentrate more than 70% of spend.
4. Respect for People (Rep Coaching)
The engine judges results, never conformity to a style. The coach_rep rule only fires when:
- at least two reps have 15 or more shows (
meetingShows) in the period; - the rep in question has a close rate below the team average;
- the gap with the best rep exceeds 30 points.
A rep who closes above average is never flagged, whatever their style. The talk_ratio rule (talk share above 55% over at least 8 analyzed calls) requires a minimum of 15 recordings for the account, stays at low priority and is suppressed when coach_rep already covers the same rep.
5. Flexibility by Business Profile
Thresholds depend on the type of business. Six profiles exist in the engine:
| Profile | Example | Minimum samples (leads / bookings / shows / calls) | Minimum attribution coverage |
|---|---|---|---|
b2c_high_ticket (default) | Coaching, training, services from $2K to $10K | 15 / 10 / 8 / 15 | 50% |
b2c_ecom | Physical products from $50 to $500, high volumes | 50 / n/a / n/a / n/a | 60% |
b2b_midmarket | $5K to $50K ticket, 30 to 90 day cycle | 10 / 5 / 5 / 10 | 40% |
b2b_enterprise | $50K+ contracts, 6+ month cycle | 5 / 3 / 3 / 5 | 30% |
saas_plg | Self-serve, $10 to $500 per month | 30 / n/a / n/a / n/a | 70% |
services_pro | Lawyer, accountant, consultant, low volumes | 5 / 3 / 2 / 5 | 40% |
The profile is stored on the client account (business_profile). No interface lets you choose it yet: the Recommendations widget uses the default profile.
6. Dynamic Priority
A ROAS that has been negative for 3 days does not have the urgency of one negative for 14 days. For the rules concerned, modulatePriority adds three components:
| Component | Points |
|---|---|
| Duration of the problem | 14 days and more: 3, 7 to 13 days: 2, 3 to 6 days: 1 |
| Magnitude (share of budget affected) | 30% and more: 3, 15% to 29%: 2, 5% to 14%: 1 |
| Trend | Worsening: +1, improving: -1 |
Score of 5 or more: priority high. From 2 to 4: medium. Otherwise: low.
7. Attribution
DURUM.ai offers five attribution models. The selector sits above the tables of the CEO View and the Ads page; a model can be pinned as the account default (default_attribution_model).
| Model | Code | Description |
|---|---|---|
| Hybrid Durum (default) | hybrid | First ad if the sale happens within 30 days, last ad beyond |
| Linear | linear | Equal credit to each ad in the journey |
| First Touch | first_touch | Credit to the first ad seen |
| Last Touch | last_touch | Credit to the last ad seen |
| Position (40-20-40) | position | 40% first, 40% last, 20% middle (adjustable weights) |
Funnel KPIs (leads, bookings, sales) are model-agnostic: they do not change with the model. In the recommendation engine, the position model is approximated as 40% first touch + 40% last touch (no middle touch in per-ad data), and the Recommendations widget uses the hybrid model. The Settings, Attribution page (/settings/attribution) explains each model.
Thresholds and Formulas
Data Confidence Index
Before any recommendation, the engine computes:
dataConfidence = min(spend, events) x 0.7 + attribution x 0.3| Component | Value |
|---|---|
spend (days with spend) | 28 days and more: 1.0, 14 to 27: 0.7, 7 to 13: 0.4, otherwise 0.2 |
events (leads + bookings + sales) | 30 and more: 1.0, 10 to 29: 0.6, otherwise 0.2 |
attribution (share of sales linked to an ad) | 70% and more: 1.0, 50% to 69%: 0.75, 30% to 49%: 0.5, otherwise 0.3 (0.7 when there are no sales at all) |
If dataConfidence < 0.3, no recommendation is generated.
Minimum Samples by Rule
| Rule | Minimum required |
|---|---|
Rep coaching (coach_rep) | 15 shows per rep, at least 2 reps |
Spend anomaly (spend_anomaly) | 16 days of history; z-score above 2.5 vs the 14-day rolling average; the current day is excluded |
| Creative fatigue | 1,000 impressions and $50 of spend per ad |
Weak funnel link (chain_link), source variance (source_variance) | 15 leads |
No-show per rep (rep_noshow) | 10 bookings per rep |
Close rate drop (close_rate_drop) | 8 shows |
Talk ratio (talk_ratio) | 15 analyzed recordings, 8 calls for the rep |
These minimums are those of the default profile (see principle 5).
Impact Measurement Framework
When a stored recommendation is moved to the "implemented" status, a snapshot of the last 30 days of KPIs is captured (capture_reco_baseline). A daily job (update_reco_baselines) re-measures the same KPIs at T+7, T+30 and T+90 days. The get_reco_quality_by_rule function then computes, per rule, the average ROAS and close rate change at T+30. This is what makes it possible to judge whether a rule has a real effect.
What the Engine Does Not Do
- No recommendation without data. A period with no spend and no event, or a
dataConfidencebelow 0.3: nothing is displayed. - No offline attribution (inbound calls, walk-ins). Only digital journeys are covered.
- No display below 40% confidence. A signal that is too weak is discarded rather than shown with a warning.
Lifecycle of a Recommendation
Widget recommendations are recomputed on every display from the selected period and filters. When the conditions are no longer met (for example a ROAS back above 1), the recommendation disappears on its own.
For developers: a stored recommendation (ai_recommendations) carries one status among pending, acknowledged, in_progress, implemented, dismissed, auto_resolved and expired. A pending recommendation expires automatically after 7 days (daily job). A dismissal is stored with a reason (dismiss_reason) among pas_pertinent, deja_fait, trop_risque, budget_insuffisant and autre. Recommendations marked implemented or dismissed do not reappear in the widget.
FAQ
Why Does a Recommendation Disappear?
Because it is recomputed on every display: if the observation is no longer true for the selected period, it is no longer generated. Stored recommendations also expire after 7 days without action.
Why Do Recommendations Change When I Change the Period?
Because the engine only reads the displayed period and filters. A shorter period contains fewer days of spend and fewer events, which lowers the confidence index and can silence some rules.
Why Are Some Impacts Ranges?
Because the future is not a promise. A range reflects the real uncertainty of a projection based on recent history.
Contact
A question about a specific recommendation? Write to info@durum.ai citing the recommendation title and the displayed period.