Skip to main content

marketing-analytics-lab-engine

Multi-platform marketing analytics engine with canonical data model for unified campaign data ingestion and KPI analysis

Aller à l'installation

Informations de source

Dépôt
reason-machines/marketing-skills
Dernière activité de la source
29 juin 2026 à 09:11
Langue détectée de SKILL.md
anglais
Étoiles
10
Forks
1

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
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"]
# Marketing Analytics Lab Engine > Skill by [ara.so](https://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. ## Installation ```bash git clone https://github.com/shayantimes/marketing-analytics-lab.git cd marketing-analytics-lab pip install -r requirements.txt ``` **Core dependencies:** - Python 3.11+ - pandas - pydantic v2 - pytest (for testing) ## Project Architecture 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 contract - `UnifiedLoader`: Source-aware entry point that routes to correct adapter - Validation layer: Pydantic-based contract enforcement ## Core Data Model All marketing data must conform to the `CampaignRecord` canonical schema: ```python 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:** ```python 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 ``` ## Loading Campaign Data ### Basic Usage ```python 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}") ``` ### Supported Sources Currently implemented: - `google_ads`: Google Ads CSV exports In progress: - `meta_ads`: Meta (Facebook/Instagram) Ads ## Creating a New Platform Adapter To add support for a new marketing platform: ### 1. Create Adapter Class ```python # 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') ``` ### 2. Register in UnifiedLoader ```python # 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) ``` ### 3. Test the Adapter ```python # 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 ``` ## Validation ### Validate Individual Records ```python 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}") ``` ### Batch Validation ```python 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']}") ``` ## Working with Campaign Data ### Calculate Basic KPIs ```python 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") ``` ### Aggregate by Date ```python 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") ``` ### Compare Platforms ```python 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") ``` ## CSV Format Requirements ### Google Ads Expected Format ```csv 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` → `date` - `Campaign ID` → `platform_campaign_id` - `Campaign` → `campaign_name` - `Impressions` → `impressions` - `Clicks` → `clicks` - `Cost` → `cost` - `Conversions` → `conversions` - `Conv. value` → `revenue` ## Testing ### Run All Tests ```bash pytest tests/ ``` ### Run Specific Test ```bash pytest tests/test_validator.py -v ``` ### Test Coverage ```bash pytest --cov=src tests/ ``` ## Common Patterns ### End-to-End Pipeline ```python from src.loaders.unified_loader import UnifiedLoader from src.contracts.validator import validate_record
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub