D2C Focus Rules - Implementation Analysis
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)
ad_performance_by_country.csv(preferred - includes geo_code column)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
- geo_fatigue_report.csv: ad_name, geo_code, frequency, ctr, spend, flag (CRITICAL_FREQUENCY / HIGH_FREQUENCY / LOW_CTR), market_group, michelin_status
- 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.