Skip to content

Module 4 — Cost per Unit (CPU)

Hoda Rezvanjoo edited this page May 28, 2026 · 1 revision

File: modules/module4.py · Result: Module4Result

Module 4 turns "budget × KPI value" into "cost per unit KPI" for every canonical count KPI on every active (platform, goal):

cpu_per_goal[platform][goal][var] = platform_budget / kpi_value

This is the productivity backbone the LP optimises against.

What it doesn't do

  • No CPU for rate KPIs. "Cost per percentage point" isn't meaningful in this model; rate KPIs (where present) are forecast in Module 6 but not LP'd.
  • No imputation. A missing or zero KPI value drops the row with a warning recorded in skipped_rows. Module 5 sees only cells that survived this step.

Outlier sweep

CPU_OUTLIER_MULTIPLE = 100.0. Any CPU greater than 100× the per-goal median is flagged and skipped. This catches:

  • Typos (5 leads instead of 500)
  • Unit errors (£1 ad spend for "100 impressions" when the export was in thousands)
  • Near-zero KPI values that would otherwise produce an effectively infinite "productivity" and dominate the LP

Skipped rows surface in the diagnostics panel rather than being silently dropped, so the user can see what was filtered and why.

Policy snapshot

Module 4 also stamps the current values of min_spend_per_platform, min_budget_per_goal, scenario_multipliers, and scenario_goal_multipliers onto its result. Module 5 then reads a single immutable snapshot rather than reaching back into WizardState, eliminating any risk of mid-solve mutation.

This pattern — modules consume snapshots, not live state — is what keeps each module unit-testable in isolation.

Clone this wiki locally