| """
|
| models.py β Typed Pydantic models for Meta Ads Attribution OpenEnv.
|
| Full OpenEnv spec: Observation, Action, Reward, State.
|
| """
|
|
|
| from __future__ import annotations
|
| from typing import Any, Dict, List, Literal, Optional
|
| from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
|
|
|
|
|
| class AdSetMetrics(BaseModel):
|
| adset_id: str
|
| adset_name: str
|
| budget: float
|
| spent: float
|
| impressions: int
|
| link_clicks: int
|
| reported_conversions: int
|
| true_conversions: int
|
| reported_roas: float
|
| true_roas: float
|
| audience_segment: str
|
| is_paused: bool = False
|
|
|
|
|
| class CampaignData(BaseModel):
|
| campaign_id: str
|
| campaign_name: str
|
| objective: str
|
| total_budget: float
|
| budget_spent: float
|
| impressions: int
|
| link_clicks: int
|
| reported_conversions: int
|
| true_conversions: int
|
| reported_cpa: float
|
| true_cpa: float
|
| reported_roas: float
|
| true_roas: float
|
| attribution_window: str
|
| pixel_signal_quality: float
|
| ios_traffic_pct: float
|
| conversions_api_enabled: bool
|
| capi_coverage: float = 0.0
|
| aem_enabled: bool
|
| utm_tracking: bool
|
| modeled_conversions_enabled: bool = False
|
| attribution_reporting_mode: Literal["observed", "modeled"] = "observed"
|
| server_signal_quality: float = 0.0
|
| pixel_match_quality: float = 0.0
|
| conversion_delay_index: float = 1.0
|
| avg_conversion_delay_days: float = 4.0
|
| adsets: List[AdSetMetrics] = Field(default_factory=list)
|
|
|
|
|
| class PendingConversion(BaseModel):
|
| source_adset_id: str
|
| clicks: int
|
| expected_conversions: int
|
| value: int = 0
|
| delay_days_remaining: int
|
| original_delay_days: int
|
|
|
|
|
|
|
|
|
|
|
|
|
| class Observation(BaseModel):
|
| task_id: str
|
| difficulty: Literal["easy", "medium", "hard"]
|
| step_count: int
|
| max_steps: int
|
| campaign_data: CampaignData
|
| reported_conversions: int
|
| estimated_true_conversions: int
|
| attribution_gap_pct: float
|
| pixel_signal_quality: float
|
| ios_traffic_pct: float
|
| budget_remaining: float
|
| roas_reported: float
|
| roas_true: float
|
| attribution_confidence: float = 0.0
|
| capi_coverage: float = 0.0
|
| pixel_match_quality: float = 0.0
|
| conversion_delay_index: float = 1.0
|
| avg_conversion_delay_days: float = 4.0
|
| pending_delayed_conversions: int = 0
|
| modeled_conversions_accumulated: int = 0
|
| tracked_conversions_accumulated: int = 0
|
| delayed_conversion_release_events: int = 0
|
| cumulative_delayed_conversions: int = 0
|
| issues_resolved_count: int = 0
|
| available_actions: List[str]
|
| context: str
|
| done: bool = False
|
|
|
| class Config:
|
| json_schema_extra = {
|
| "example": {
|
| "task_id": "easy_attribution_window",
|
| "difficulty": "easy",
|
| "step_count": 0,
|
| "max_steps": 5,
|
| "reported_conversions": 42,
|
| "estimated_true_conversions": 110,
|
| "attribution_gap_pct": 0.618,
|
| "pixel_signal_quality": 0.85,
|
| "ios_traffic_pct": 0.30,
|
| "roas_reported": 1.4,
|
| "roas_true": 3.7,
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| ActionType = Literal[
|
| "promote_ad",
|
| "reduce_budget",
|
| "investigate_attribution",
|
| "switch_to_modeled_conversions",
|
| "adjust_attribution_window",
|
| "enable_conversions_api",
|
| "adjust_budget_allocation",
|
| "change_bid_strategy",
|
| "add_utm_tracking",
|
| "segment_audience",
|
| "enable_aggregated_event_measurement",
|
| "pause_underperforming_adsets",
|
| "reallocate_to_top_performers",
|
| "no_op",
|
| ]
|
|
|
| VALID_ATTRIBUTION_WINDOWS = ["1d_click", "7d_click", "28d_click", "1d_view", "7d_click_1d_view"]
|
| VALID_BID_STRATEGIES = ["lowest_cost", "cost_cap", "bid_cap", "value_optimisation"]
|
|
|
|
|
| class Action(BaseModel):
|
| action_type: ActionType
|
| parameters: Dict[str, Any] = Field(default_factory=dict)
|
| reasoning: Optional[str] = None
|
|
|
| class Config:
|
| json_schema_extra = {
|
| "example": {
|
| "action_type": "adjust_attribution_window",
|
| "parameters": {"window": "7d_click"},
|
| "reasoning": "The current 1-day window is too narrow and misses purchases that happen 2β7 days after the click.",
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
| class RewardComponents(BaseModel):
|
| attribution_accuracy: float = 0.0
|
| roas_improvement: float = 0.0
|
| signal_quality_gain: float = 0.0
|
| action_validity: float = 0.0
|
| step_efficiency: float = 0.0
|
| timing_quality: float = 0.0
|
| uncertainty_handling: float = 0.0
|
| redundancy_penalty: float = 0.0
|
| long_term_gain: float = 0.0
|
| issue_resolution_progress: float = 0.0
|
|
|
|
|
| class Reward(BaseModel):
|
| total: float = Field(ge=-1.0, le=1.0)
|
| components: RewardComponents
|
| explanation: str = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
| class EnvState(BaseModel):
|
| task_id: str
|
| difficulty: Literal["easy", "medium", "hard"]
|
| step_count: int = 0
|
| max_steps: int = 10
|
| campaign: CampaignData
|
| cumulative_reward: float = 0.0
|
| done: bool = False
|
| history: List[Dict[str, Any]] = Field(default_factory=list)
|
| issues_resolved: List[str] = Field(default_factory=list)
|
| issues_remaining: List[str] = Field(default_factory=list)
|
| day: int = 0
|
| action_counts: Dict[str, int] = Field(default_factory=dict)
|
| pending_delayed_conversions: List[PendingConversion] = Field(default_factory=list)
|
| pending_conversions: List[PendingConversion] = Field(default_factory=list)
|
| hidden_delayed_conversions: List[PendingConversion] = Field(default_factory=list)
|
| delayed_true_conversions_total: int = 0
|
| delayed_reported_conversions_total: int = 0
|
| tracked_conversions_total: int = 0
|
| modeled_conversions_total: int = 0
|
| growth_momentum: float = 1.0
|
| tracking_reliability: float = 0.5
|
| attribution_investigation_level: float = 0.0
|
| attribution_gap_history: List[float] = Field(default_factory=list)
|
| roas_history: List[float] = Field(default_factory=list)
|
| signal_quality_history: List[float] = Field(default_factory=list)
|
| optimal_steps_hint: int = 4
|
| optimal_steps: int = 4
|
| scenario_delay_range: List[int] = Field(default_factory=lambda: [2, 7])
|
| tracking_investigated: bool = False
|
| uncertainty_reintroduced: bool = False
|
| hidden_conversions_pool: int = 0
|
| delayed_conversion_release_last_step: int = 0
|
| tracked_conversion_release_last_step: int = 0
|
| modeled_conversion_release_last_step: int = 0
|
| convergence_reached: bool = False
|
| conversion_rate_range: List[float] = Field(default_factory=lambda: [0.08, 0.12])
|
| max_generated_conversions_per_step: int = 35
|
| max_released_conversions_per_step: int = 35
|
| target_true_conversions: int = 250
|
| delayed_reward_buffer: float = 0.0
|
| delayed_reward_released_last_step: float = 0.0
|
| terminal_bonus_last_step: float = 0.0
|
| risk_events: List[str] = Field(default_factory=list)
|
| budget_optimization_multiplier: float = 1.0
|
| confidence_score: float = 0.5
|
| attribution_confidence: float = 0.5
|
| episode_risk_initialized: bool = False
|
| easy_meaningful_actions_taken: int = 0
|
| reasoning_log: List[str] = Field(default_factory=list)
|
| early_wrong_decision: bool = False
|
| recovered_after_wrong_decision: bool = False
|
| random_seed: int = 42
|
| last_action_type: str = ""
|
| repeated_action_count: int = 0
|
| action_impact_memory: Dict[str, float] = Field(default_factory=dict)
|
| convergence_stagnation_count: int = 0
|
| episode_rare_events: List[str] = Field(default_factory=list) |