一键导入
marketing-analytics-lab-engine
Multi-platform marketing analytics engine with canonical data model for unified campaign data ingestion and KPI analysis
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Multi-platform marketing analytics engine with canonical data model for unified campaign data ingestion and KPI analysis
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Agent-driven marketing studio that generates complete launch asset suites (logo reveals, demos, launch videos, social clips, OG assets) from one /marketing command
ScrapeBox Ultimate SEO Automation toolkit for large-scale search optimization, backlink analysis, rank tracking, and link building workflows on Windows.
SEO automation toolkit for backlink analysis, link building, rank tracking, and large-scale search optimization workflows on Windows
Comprehensive SEO automation toolkit for link building, backlink analysis, rank tracking, and large-scale search optimization workflows on Windows.
SEO automation toolkit for link building, backlink analysis, rank tracking, and large-scale search optimization workflows
Large-scale SEO automation toolkit for backlink analysis, link building, keyword harvesting, and rank tracking workflows on Windows.
| name | marketing-analytics-lab-engine |
| description | Multi-platform marketing analytics engine with canonical data model for unified campaign data ingestion and KPI analysis |
| triggers | ["ingest marketing campaign data from Google Ads or Meta","validate marketing data against canonical schema","create adapter for new marketing platform","load and process campaign CSV exports","unify multi-platform marketing data","set up marketing analytics pipeline","map platform-specific fields to canonical model","validate campaign records with Pydantic"] |
Skill by ara.so — Marketing Skills collection.
Marketing Analytics Lab is a Python-based marketing data ingestion engine that enforces a canonical schema across multiple advertising platforms (Google Ads, Meta Ads, etc.). It uses an adapter pattern to transform platform-specific CSV exports into a unified CampaignRecord format with strict Pydantic validation before any analytics processing.
git clone https://github.com/shayantimes/marketing-analytics-lab.git
cd marketing-analytics-lab
pip install -r requirements.txt
Core dependencies:
The system follows a strict data pipeline:
Raw CSV → Adapter → Canonical Record → Validation → KPI Engine (planned)
Key components:
CampaignRecord: Canonical data model (single source of truth)BaseCampaignAdapter: Abstract adapter contractUnifiedLoader: Source-aware entry point that routes to correct adapterAll marketing data must conform to the CampaignRecord canonical schema:
from dataclasses import dataclass
from datetime import date
from typing import Optional
@dataclass
class CampaignRecord:
source: str # 'google_ads', 'meta_ads', etc.
date: str # 'YYYY-MM-DD'
platform_campaign_id: str # Platform's internal campaign ID
campaign_name: str # Human-readable campaign name
impressions: int # Ad impressions
clicks: int # Ad clicks
cost: float # Total cost in USD
conversions: Optional[float] # Number of conversions
revenue: Optional[float] # Total revenue in USD
Pydantic validation schema:
from pydantic import BaseModel, Field, field_validator
from datetime import datetime
class CampaignRecord(BaseModel):
source: str = Field(..., min_length=1)
date: str
platform_campaign_id: str = Field(..., min_length=1)
campaign_name: str = Field(..., min_length=1)
impressions: int = Field(..., ge=0)
clicks: int = Field(..., ge=0)
cost: float = Field(..., ge=0)
conversions: float | None = Field(None, ge=0)
revenue: float | None = Field(None, ge=0)
@field_validator('date')
@classmethod
def validate_date(cls, v):
try:
datetime.strptime(v, '%Y-%m-%d')
except ValueError:
raise ValueError('Date must be in YYYY-MM-DD format')
return v
from src.loaders.unified_loader import UnifiedLoader
# Load Google Ads data
records = UnifiedLoader.load(
source='google_ads',
filepath='data/sample_google_ads_campaign.csv'
)
# Iterate through validated canonical records
for record in records:
print(f"Campaign: {record.campaign_name}")
print(f"CTR: {record.clicks / record.impressions * 100:.2f}%")
print(f"Cost: ${record.cost:.2f}")
Currently implemented:
google_ads: Google Ads CSV exportsIn progress:
meta_ads: Meta (Facebook/Instagram) AdsTo add support for a new marketing platform:
# src/adapters/your_platform/campaign_adapter.py
import pandas as pd
from src.adapters.base import BaseCampaignAdapter
from src.core.canonical.campaign_record import CampaignRecord
class YourPlatformAdapter(BaseCampaignAdapter):
"""Adapter for YourPlatform campaign data."""
def adapt(self, df: pd.DataFrame) -> list[CampaignRecord]:
"""
Transform platform-specific DataFrame to canonical CampaignRecord list.
Expected CSV columns from YourPlatform:
- campaign_id
- campaign_title
- report_date
- views
- link_clicks
- spend
- purchases
- total_revenue
"""
records = []
for _, row in df.iterrows():
record = CampaignRecord(
source='your_platform',
date=self._normalize_date(row['report_date']),
platform_campaign_id=str(row['campaign_id']),
campaign_name=row['campaign_title'],
impressions=int(row['views']),
clicks=int(row['link_clicks']),
cost=float(row['spend']),
conversions=float(row['purchases']) if pd.notna(row.get('purchases')) else None,
revenue=float(row['total_revenue']) if pd.notna(row.get('total_revenue')) else None
)
records.append(record)
return records
def _normalize_date(self, date_str: str) -> str:
"""Convert platform date format to YYYY-MM-DD."""
# Example: "05/01/2026" → "2026-05-01"
from datetime import datetime
dt = datetime.strptime(date_str, '%m/%d/%Y')
return dt.strftime('%Y-%m-%d')
# src/loaders/unified_loader.py
from src.adapters.your_platform.campaign_adapter import YourPlatformAdapter
class UnifiedLoader:
@staticmethod
def load(source: str, filepath: str) -> list[CampaignRecord]:
df = pd.read_csv(filepath)
if source == 'google_ads':
adapter = GoogleAdsCampaignAdapter()
elif source == 'meta_ads':
adapter = MetaAdsAdapter()
elif source == 'your_platform':
adapter = YourPlatformAdapter()
else:
raise ValueError(f"Unsupported source: {source}")
return adapter.adapt(df)
# tests/test_your_platform_adapter.py
import pytest
import pandas as pd
from src.adapters.your_platform.campaign_adapter import YourPlatformAdapter
def test_your_platform_adapter():
sample_data = pd.DataFrame({
'campaign_id': ['camp_123'],
'campaign_title': ['Summer Sale'],
'report_date': ['05/15/2026'],
'views': [10000],
'link_clicks': [500],
'spend': [250.00],
'purchases': [20],
'total_revenue': [1500.00]
})
adapter = YourPlatformAdapter()
records = adapter.adapt(sample_data)
assert len(records) == 1
assert records[0].source == 'your_platform'
assert records[0].campaign_name == 'Summer Sale'
assert records[0].impressions == 10000
assert records[0].clicks == 500
from src.contracts.validator import validate_record
from src.core.canonical.campaign_record import CampaignRecord
record = CampaignRecord(
source='google_ads',
date='2026-05-01',
platform_campaign_id='123',
campaign_name='Brand Search',
impressions=10000,
clicks=500,
cost=250.0,
conversions=20.0,
revenue=1500.0
)
is_valid, errors = validate_record(record)
if is_valid:
print("✓ Record is valid")
else:
print(f"✗ Validation errors: {errors}")
from src.contracts.validator import validate_record
valid_records = []
invalid_records = []
for record in records:
is_valid, errors = validate_record(record)
if is_valid:
valid_records.append(record)
else:
invalid_records.append({
'record': record,
'errors': errors
})
print(f"Valid: {len(valid_records)}, Invalid: {len(invalid_records)}")
# Report errors
for item in invalid_records:
print(f"Campaign: {item['record'].campaign_name}")
print(f"Errors: {item['errors']}")
def calculate_kpis(record):
"""Calculate common marketing KPIs from a CampaignRecord."""
kpis = {}
# Click-Through Rate
if record.impressions > 0:
kpis['ctr'] = (record.clicks / record.impressions) * 100
# Cost Per Click
if record.clicks > 0:
kpis['cpc'] = record.cost / record.clicks
# Cost Per Acquisition
if record.conversions and record.conversions > 0:
kpis['cpa'] = record.cost / record.conversions
# Return on Ad Spend
if record.revenue and record.cost > 0:
kpis['roas'] = record.revenue / record.cost
# Conversion Rate
if record.clicks > 0 and record.conversions:
kpis['conversion_rate'] = (record.conversions / record.clicks) * 100
return kpis
# Usage
for record in records:
kpis = calculate_kpis(record)
print(f"{record.campaign_name}:")
print(f" CTR: {kpis.get('ctr', 0):.2f}%")
print(f" CPC: ${kpis.get('cpc', 0):.2f}")
print(f" ROAS: {kpis.get('roas', 0):.2f}x")
from collections import defaultdict
from datetime import datetime
def aggregate_by_date(records):
"""Aggregate metrics by date."""
daily_stats = defaultdict(lambda: {
'impressions': 0,
'clicks': 0,
'cost': 0.0,
'conversions': 0.0,
'revenue': 0.0
})
for record in records:
date = record.date
daily_stats[date]['impressions'] += record.impressions
daily_stats[date]['clicks'] += record.clicks
daily_stats[date]['cost'] += record.cost
daily_stats[date]['conversions'] += record.conversions or 0
daily_stats[date]['revenue'] += record.revenue or 0
return dict(daily_stats)
# Usage
daily = aggregate_by_date(records)
for date, stats in sorted(daily.items()):
print(f"{date}: ${stats['cost']:.2f} spent, {stats['clicks']} clicks")
def compare_platforms(records):
"""Compare performance across platforms."""
platform_stats = defaultdict(lambda: {
'campaigns': 0,
'total_cost': 0.0,
'total_revenue': 0.0,
'total_conversions': 0.0
})
for record in records:
source = record.source
platform_stats[source]['campaigns'] += 1
platform_stats[source]['total_cost'] += record.cost
platform_stats[source]['total_revenue'] += record.revenue or 0
platform_stats[source]['total_conversions'] += record.conversions or 0
# Calculate ROAS per platform
for source, stats in platform_stats.items():
if stats['total_cost'] > 0:
stats['roas'] = stats['total_revenue'] / stats['total_cost']
return dict(platform_stats)
# Usage
comparison = compare_platforms(records)
for platform, stats in comparison.items():
print(f"{platform}:")
print(f" Campaigns: {stats['campaigns']}")
print(f" ROAS: {stats.get('roas', 0):.2f}x")
Date,Campaign ID,Campaign,Impressions,Clicks,Cost,Conversions,Conv. value
2026-05-01,123,Brand Search,10000,500,250.00,20,1500.00
2026-05-02,123,Brand Search,12000,600,300.00,25,1800.00
Column mapping:
Date → dateCampaign ID → platform_campaign_idCampaign → campaign_nameImpressions → impressionsClicks → clicksCost → costConversions → conversionsConv. value → revenuepytest tests/
pytest tests/test_validator.py -v
pytest --cov=src tests/
from src.loaders.unified_loader import UnifiedLoader
from src.contracts.validator import validate_record
# Load data from multiple sources
google_records = UnifiedLoader.load('google_ads', 'data/google_ads.csv')
meta_records = UnifiedLoader.load('meta_ads', 'data/meta_ads.csv')
# Combine all records
all_records = google_records + meta_records
# Validate and filter
valid_records = []
for record in all_records:
is_valid, errors = validate_record(record)
if is_valid:
valid_records.append(record)
else:
print(f"Skipping invalid record: {errors}")
# Process valid records
for record in valid_records:
# Your analytics logic here
pass
from src.loaders.unified_loader import UnifiedLoader
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def safe_load_campaigns(source: str, filepath: str):
"""Load campaigns with error handling."""
try:
records = UnifiedLoader.load(source, filepath)
logger.info(f"Loaded {len(records)} records from {source}")
return records
except FileNotFoundError:
logger.error(f"File not found: {filepath}")
return []
except ValueError as e:
logger.error(f"Invalid source or data: {e}")
return []
except Exception as e:
logger.error(f"Unexpected error: {e}")
return []
Problem: UnifiedLoader doesn't recognize the source name.
Solution: Ensure source name matches exactly (case-sensitive):
# ✓ Correct
records = UnifiedLoader.load('google_ads', 'data.csv')
# ✗ Wrong
records = UnifiedLoader.load('Google Ads', 'data.csv') # Space and caps
Problem: Date format doesn't match YYYY-MM-DD.
Solution: Normalize dates in your adapter:
from datetime import datetime
def _normalize_date(self, date_str: str) -> str:
# Handle multiple formats
for fmt in ['%Y-%m-%d', '%m/%d/%Y', '%d-%m-%Y']:
try:
dt = datetime.strptime(date_str, fmt)
return dt.strftime('%Y-%m-%d')
except ValueError:
continue
raise ValueError(f"Unrecognized date format: {date_str}")
Problem: CSV doesn't have all required columns.
Solution: Handle missing fields with defaults in adapter:
def adapt(self, df: pd.DataFrame) -> list[CampaignRecord]:
records = []
for _, row in df.iterrows():
record = CampaignRecord(
source='platform_name',
date=row['date'],
platform_campaign_id=str(row['campaign_id']),
campaign_name=row['campaign_name'],
impressions=int(row['impressions']),
clicks=int(row['clicks']),
cost=float(row['cost']),
conversions=float(row['conversions']) if 'conversions' in row and pd.notna(row['conversions']) else None,
revenue=float(row['revenue']) if 'revenue' in row and pd.notna(row['revenue']) else None
)
records.append(record)
return records
Problem: Numeric fields contain non-numeric values.
Solution: Clean data in adapter before creating records:
def _safe_int(self, value) -> int:
try:
return int(float(value))
except (ValueError, TypeError):
return 0
def _safe_float(self, value) -> float:
try:
return float(value)
except (ValueError, TypeError):
return 0.0
The project roadmap includes:
Check the project README for current implementation status.