| """ |
| Pydantic models for the AdAudit environment. |
| |
| Defines Action, Observation, and State types that conform to the OpenEnv spec. |
| """ |
|
|
| from typing import Any, Dict, List, Literal, Optional |
|
|
| from pydantic import BaseModel, Field, field_validator |
|
|
| from openenv.core.env_server.types import Action, Observation, State |
|
|
|
|
| |
| |
| |
|
|
| class AdAuditAction(Action): |
| """Single discrete action the agent takes each day.""" |
|
|
| action_type: Literal[ |
| "monitor", |
| "investigate_publisher", |
| "flag_fraud", |
| "submit_report", |
| "invalid", |
| ] = Field(..., description="The type of action to take") |
|
|
| publisher_id: Optional[str] = Field( |
| default=None, description="Target publisher for investigate/flag actions" |
| ) |
|
|
| |
| tool: Optional[Literal[ |
| "click_timestamps", |
| "ip_distribution", |
| "device_fingerprints", |
| "referral_urls", |
| "viewability_scores", |
| "conversion_quality", |
| ]] = Field(default=None, description="Investigation tool to use") |
|
|
| |
| fraud_type: Optional[Literal[ |
| "bot_traffic", |
| "domain_spoofing", |
| "click_injection", |
| ]] = Field(default=None, description="Fraud type to flag") |
| evidence: Optional[List[str]] = Field( |
| default=None, |
| description="Evidence tool names, comma-separated (e.g. click_timestamps, ip_distribution)", |
| ) |
|
|
| @field_validator("evidence", mode="before") |
| @classmethod |
| def _coerce_evidence(cls, v): |
| if isinstance(v, str): |
| import json |
| try: |
| parsed = json.loads(v) |
| if isinstance(parsed, list): |
| return parsed |
| except (json.JSONDecodeError, ValueError): |
| pass |
| |
| stripped = v.strip("[] ") |
| return [s.strip().strip("'\"") for s in stripped.split(",") if s.strip()] |
| return v |
|
|
| |
| summary: Optional[str] = Field(default=None) |
|
|
|
|
| |
| |
| |
|
|
| class DailyPublisherMetrics(BaseModel): |
| """Traffic metrics for one publisher on one day.""" |
|
|
| publisher_id: str |
| name: str |
| impressions: int |
| clicks: int |
| conversions: int |
| spend: float |
| ctr: float |
| cvr: float |
|
|
|
|
| class BudgetStatus(BaseModel): |
| """Campaign and investigation budget snapshot.""" |
|
|
| total_campaign_budget: float |
| spent_so_far: float |
| remaining: float |
| investigation_budget_remaining: int |
| daily_spend_rate: float |
|
|
|
|
| |
| |
| |
|
|
| class AdAuditObservation(Observation): |
| """What the agent sees after each step. |
| |
| Inherits ``done``, ``reward``, and ``metadata`` from the OpenEnv |
| ``Observation`` base class. ``reward`` carries the daily P&L. |
| """ |
|
|
| day: int = Field(..., description="Current campaign day (1-30)") |
| campaign_day_total: int = Field(default=14) |
|
|
| daily_metrics: List[DailyPublisherMetrics] = Field(default_factory=list) |
| cumulative_metrics: List[DailyPublisherMetrics] = Field(default_factory=list) |
|
|
| trend_data: str = Field(default="", description="Trend summary") |
| investigation_results: Optional[Dict[str, Any]] = Field( |
| default=None, description="Structured metrics from investigation tool" |
| ) |
| alerts: List[str] = Field(default_factory=list) |
|
|
| budget_status: Optional[BudgetStatus] = None |
| publisher_status: Dict[str, str] = Field( |
| default_factory=dict, |
| description="publisher_id -> active|flagged", |
| ) |
|
|
| cumulative_reward: float = Field(default=0.0) |
| done_reason: Optional[str] = Field(default=None) |
|
|
|
|
| |
| |
| |
|
|
| class PublisherState(BaseModel): |
| """Public publisher state (visible via /state).""" |
|
|
| publisher_id: str |
| name: str |
| is_flagged: bool = False |
| budget_allocation: float = 0.0 |
| tools_used: List[str] = Field(default_factory=list) |
| day_flagged: Optional[int] = None |
|
|
|
|
| class AdAuditState(State): |
| """Full internal state for debugging and grading. |
| |
| Inherits ``episode_id`` and ``step_count`` from OpenEnv ``State``. |
| """ |
|
|
| case_id: str = "" |
| current_day: int = 0 |
|
|
| publishers: List[PublisherState] = Field(default_factory=list) |
|
|
| action_history: List[Dict[str, Any]] = Field(default_factory=list) |
| daily_rewards: List[float] = Field(default_factory=list) |
| cumulative_reward: float = 0.0 |
|
|
| investigation_budget_total: int = 0 |
| investigation_budget_used: int = 0 |
|
|
| flags_submitted: List[Dict[str, Any]] = Field(default_factory=list) |
|
|
| grader_inputs: Dict[str, Any] = Field(default_factory=dict) |
|
|