D2C Automation Analysis

← Back to Documents

D2C Focus Rules - Implementation Analysis

Date: 2026-03-19 Source: d2c_rules.py, dayparting_monitor.py, geo_fatigue_checker.py, geo_fence_validator.py, suppression_lists.py Reference: delivery-plan.md Phase 4 (lines 86-101)

Phase 4 Rules Table - Implementation Matrix

# Rule Phase 4 Text Implementation File Status
1 No % discounts in EU prospecting Suppression list of discount-only buyers uploaded to Meta suppression_lists.py Implemented
2 Report by country, not "EU total" Digest template: DE, SE/NO/DK, IE, IT, ES as separate rows geo_fatigue_checker.py + d2c_rules.py Implemented
3 Report by state, not "US total" Digest template: CA, FL, NY, TX, WA + MI/OR as separate rows geo_fatigue_checker.py + d2c_rules.py Implemented
4 Tuesday = 30-35% US budget Dayparting alert if Tuesday spend falls below 25% dayparting_monitor.py + d2c_rules.py Implemented
5 Sunday = 35-40% DE budget Dayparting alert if weekend DE spend is below threshold dayparting_monitor.py + d2c_rules.py Implemented
6 Fish creative only in CA/WA/OR Geo-fence validation in weekly audit geo_fence_validator.py + d2c_rules.py Implemented
7 No discounts in WA/MI/OR/NO Alert if discount campaigns active in these geos geo_fence_validator.py + suppression_lists.py + d2c_rules.py Implemented
8 Michelin creative fatigue tracking per geo Frequency tracked per country/state, not just per ad geo_fatigue_checker.py + d2c_rules.py Implemented
9 Discount code cohort analysis CHEEKYMARBLS/COUNTMARBULA/LATETOSCHOOOL tracked by LTV d2c_rules.py + suppression_lists.py Implemented

Module Breakdown

1. d2c_rules.py - Central Rules Engine (956 lines)

Single source of truth for all D2C Focus parameters, markets, thresholds, and validation rules.

Market Definitions

# EU Markets (7 primary)
EU_MARKETS = {
  "DE": {"name": "Germany", "priority": 1, ...},
  "SE": {"name": "Sweden", ...},
  "NO": {"name": "Norway", ...},
  "DK": {"name": "Denmark", ...},
  "IE": {"name": "Ireland", ...},
  "IT": {"name": "Italy", ...},
  "ES": {"name": "Spain", ...},
}

# EU Test Markets (3)
EU_TEST_MARKETS = {"PT", "RO", "FI"}

# US Markets
US_PRIORITY_STATES = {
  "CA": {"aov": 167, "repeat": 38.1},
  "FL": {"aov": 142, "repeat": 35.9},
  "NY": {"aov": 158, "repeat": 37.2},
  "TX": {"aov": 139, "repeat": 34.8},
  "WA": {"aov": 152, "repeat": 39.6},
}

US_LOYALTY_STATES = {
  "MI": {"aov": 144, "repeat": 42.0},
  "OR": {"aov": 148, "repeat": 43.6},
}

Fatigue Thresholds (Rule 8: Michelin per-geo tracking)

FATIGUE_THRESHOLDS = {
  "DE": {"warning": 2.5, "critical": 4.0},
  "SE": {"warning": 3.0, "critical": 5.0},
  "NO": {"warning": 3.0, "critical": 5.0},
  "DK": {"warning": 3.0, "critical": 5.0},
  "default_eu": {"warning": 3.0, "critical": 5.0},
  "default_us": {"warning": 3.0, "critical": 5.0},
  "default": {"warning": 3.5, "critical": 5.5},
}

MICHELIN_CREATIVE_CTR = 0.071  # 7.1% anchor threshold

Dayparting Rules (Rules 4-5: Tuesday/Sunday budget allocation)

DAYPARTING_RULES = {
  "us": {
    "label": "US Peak (Tuesday)",
    "peak_days": [1],  # Tuesday
    "budget_share": {"min": 0.30, "max": 0.35},
    "min_alert_threshold": 0.25,
    "peak_hours": [11, 12, 13, 14],  # 11am-2pm ET
  },
  "de": {
    "label": "DE Peak (Fri-Sun)",
    "peak_days": [4, 5, 6],  # Fri, Sat, Sun
    "budget_share": {"min": 0.35, "max": 0.40},
    "min_alert_threshold": 0.30,
    "peak_hours": [18, 19, 20, 21],  # 6-9pm CET
  },
  "se": {
    "label": "SE Peak (Wednesday)",
    "peak_days": [2],
    "budget_share": {"min": 0.25, "max": 0.30},
  },
  "it": {
    "label": "IT Peak (Tuesday eve)",
    "peak_days": [1],
    "budget_share": {"min": 0.25, "max": 0.35},
    "peak_hours": [19, 20, 21, 22],  # 7-10pm
  },
  "es": {
    "label": "ES Peak (Friday eve)",
    "peak_days": [4],
    "budget_share": {"min": 0.25, "max": 0.35},
    "peak_hours": [19, 20, 21, 22],  # 7-10pm
  },
}

Geo-Fence Rules (Rules 6-7: Fish creative & no-discount geos)

# Rule 6: Fish creative only in CA/WA/OR
FISH_CREATIVE_US_STATES = ["CA", "WA", "OR"]
FISH_PATTERNS = [
  r"\bkinda\s*cod\b",
  r"\bkinda\s*salmon\b",
  r"\bfish\b",
  r"\bcod\b",
  r"\bsalmon\b",
]

# Rule 7: No discounts in WA/MI/OR/NO
NO_DISCOUNT_US_STATES = {  "WA", "MI", "OR"}
NO_DISCOUNT_COUNTRIES = {"NO"}
NO_DISCOUNT_GEOS = NO_DISCOUNT_US_STATES | NO_DISCOUNT_COUNTRIES

DISCOUNT_PATTERNS = [
  r"\bdiscount\b",
  r"\d+%\s*off\b",
  r"\bpercent\s*off\b",
  r"\bsale\b",
  r"\bpromo\b",
]

Suppression Rules (Rules 1, 7, 9)

NO_DISCOUNT_EU_PROSPECTING = True  # Rule 1
EXCLUDE_DISCOUNT_ONLY_FROM_PROSPECTING = True
EXCLUDE_CHAMPIONS_FROM_PROSPECTING = True

# Rule 9: Discount code cohort tracking
TRACKED_DISCOUNT_CODES = {
  "CHEEKYMARBLS": {  "uses": 237, "pattern": "cheekymarbls"},
  "COUNTMARBULA": {  "uses": 179, "pattern": "countmarbula"},
  "LATETOSCHOOOL": {  "uses": 178, "pattern": "latetoschoool"},
  "FIRSTMARBLES": {  "uses": 38, "pattern": "firstmarbles"},
}

B2B_DISCOUNT_PATTERN = "Custom discount"  # 234 uses

2. dayparting_monitor.py - Budget Concentration Monitoring (455 lines)

Verify daily budget allocation matches D2C Focus dayparting rules (Rules 4-5).

Alert Statuses

  • OK - spend within target range (min-max)
  • ABOVE_TARGET - spend > max (excessive concentration)
  • BELOW_TARGET - spend between min_alert and min (under-allocated, actionable)
  • ALERT - spend at or below min_alert (critical, immediate action)

Output

  • dayparting_alerts.csv: date, geo, label, peak_days, spend_pct, target_min, target_max, min_alert, status, message
  • Gmail alert sent if BELOW_TARGET or ALERT detected
  • Email summary: markdown table grouped by geo with action items

3. geo_fatigue_checker.py - Per-Market Creative Frequency (590 lines)

Monitor creative fatigue per geographic market (Rule 8: Michelin per-geo tracking).

Data Sources (Priority order)

  1. ad_performance_by_country.csv (preferred - includes geo_code column)
  2. ad_performance.csv (fallback - parsed for geo in ad name)

Reporting Geo Groups

GEO_GROUPS = {
  "Germany": ["DE"],
  "Nordics": ["SE", "NO", "DK"],
  "US": ["US", "CA", "FL", "NY", "TX", "WA", "MI", "OR"],
  "Other EU": ["IE", "IT", "ES"],
}

Output CSVs

  1. geo_fatigue_report.csv: ad_name, geo_code, frequency, ctr, spend, flag (CRITICAL_FREQUENCY / HIGH_FREQUENCY / LOW_CTR), market_group, michelin_status
  2. geo_fatigue_summary.csv: market_group, critical_count, high_count, low_ctr_count, avg_frequency, avg_ctr

4. geo_fence_validator.py - Geographic Targeting Validation (481 lines)

Enforce Rules 6-7: Fish creative geo-fencing and no-discount market protection.

Rule 1: Fish Creative Geo-Fence (Rule 6)

Fish creative detected running outside CA/WA/OR triggers an ERROR severity violation.

Rule 2: No-Discount Geo-Fence (Rule 7)

Discount campaign detected in WA/MI/OR/NO triggers an ERROR severity violation.

Rule 3: Language Compliance (Optional --strict)

Campaign targeting DE/IT/ES without language indicator triggers a WARNING severity violation.

Output

  • geo_fence_violations.csv: rule, severity, campaign_name, ad_name, detected_geos, allowed_geos, message, source
  • Exit code: 0 (no violations), 1 (violations detected)
  • Console summary: plain-English violation report grouped by rule + actionable remediation steps

5. suppression_lists.py - Audience Suppression List Generation (393 lines)

Encode Rules 1, 7, 9 into Meta Custom Audiences for suppression/exclusion.

Suppression List Types

File Rule Description
suppression_discount_only.csv Rule 1 All customers with discount orders only, excluded from EU prospecting
suppression_no_discount_wa.csv Rule 7 WA full-price buyers excluded from discount campaigns
suppression_no_discount_mi.csv Rule 7 MI full-price buyers excluded from discount campaigns
suppression_no_discount_or.csv Rule 7 OR full-price buyers excluded from discount campaigns
suppression_no_discount_no.csv Rule 7 Norway full-price buyers excluded from discount campaigns
suppression_champions.csv RFM Top-tier customers (RFM score >= 13) excluded from prospecting

Hash Format (Meta Custom Audience requirement)

def sha256(value: str) -> str:
  return hashlib.sha256(value.strip().lower().encode()).hexdigest()

# Output format per suppression CSV
email, fn, ln  # all SHA256-hashed

Cross-Rule Implementation Summary

Rule to Python Modules Map

Phase 4 Rule Primary Module Supporting Modules
Rule 1: No % discounts in EU suppression_lists.py d2c_rules.py
Rule 2: Report by country geo_fatigue_checker.py d2c_rules.py
Rule 3: Report by state geo_fatigue_checker.py d2c_rules.py
Rule 4: Tuesday 30-35% US dayparting_monitor.py d2c_rules.py
Rule 5: Sunday/Fri-Sun 35-40% DE dayparting_monitor.py d2c_rules.py
Rule 6: Fish creative CA/WA/OR only geo_fence_validator.py d2c_rules.py
Rule 7: No discounts WA/MI/OR/NO geo_fence_validator.py + suppression_lists.py d2c_rules.py
Rule 8: Michelin per-geo fatigue geo_fatigue_checker.py d2c_rules.py
Rule 9: Discount code cohort analysis suppression_lists.py d2c_rules.py

Execution Flow (Weekly Automation)

Weekly Automation (GitHub Actions):

  Monday 07:00 UTC - export-meta-ads-data.py
    Produces: campaign_performance.csv, ad_performance.csv, campaign_performance_daily.csv

  Monday 07:05 UTC - dayparting_monitor.py
    Input: campaign_performance_daily.csv
    Checks: US Tuesday, DE Fri-Sun, SE Wed, IT Tue, ES Fri budgets
    Output: dayparting_alerts.csv + Email alert

  Monday 07:10 UTC - geo_fatigue_checker.py
    Input: ad_performance_by_country.csv or ad_performance.csv
    Checks: Michelin CTR/frequency per-market, CTR floors
    Output: geo_fatigue_report.csv, geo_fatigue_summary.csv + Email alert

  Monday 07:15 UTC - geo_fence_validator.py
    Input: campaign_performance.csv, ad_performance.csv
    Checks: Fish creative geos, no-discount geos, language compliance (--strict)
    Output: geo_fence_violations.csv + Email alert (if errors)

  Weekly (async) - suppression_lists.py
    Input: orders.csv, customers.csv, rfm_segments.csv (from Shopify export)
    Generates: 6 suppression CSVs (discount-only, WA/MI/OR/NO, champions)
    Upload: to Meta Ads Manager Custom Audiences

  Digest Email Summary
    Recipients: jon@w23.me
    Contains: Weekly budget status, fatigue alerts, violation summary, next actions

Verification Checklist

All 9 Phase 4 rules are fully implemented across the five Python modules:

  • Rule 1: Discount-only suppression list generated
  • Rule 2: Market-level reporting (DE/SE/NO/DK/IE/IT/ES separate rows)
  • Rule 3: State-level reporting (CA/FL/NY/TX/WA/MI/OR separate rows)
  • Rule 4: Tuesday 30-35% US budget monitoring + alert threshold 25%
  • Rule 5: Fri-Sun 35-40% DE budget monitoring + alert threshold 30%
  • Rule 6: Fish creative validation (CA/WA/OR only)
  • Rule 7: No-discount geo-fence (WA/MI/OR/NO violations trigger errors)
  • Rule 8: Michelin creative fatigue per-geo (DE: 2.5/4.0, default: 3.0-3.5/5.0-5.5)
  • Rule 9: Discount code cohort tracking (CHEEKYMARBLS, COUNTMARBULA, LATETOSCHOOOL via suppression lists + RFM)

Status: All rules encoded and ready for deployment.