Spaces:
Sleeping
Sleeping
| """ | |
| Episode loader abstraction. | |
| Decouples the rest of the codebase from the concrete `generate_episode` call | |
| so the Referee, tests, and (future) replay tooling can swap in alternative | |
| sources of episodes (synthetic, file-backed, recorded human, etc.) without | |
| touching environment code. | |
| """ | |
| from __future__ import annotations | |
| import random | |
| from typing import Any, Callable, Dict, Optional, Protocol, runtime_checkable | |
| from uuid import uuid4 | |
| from .ad_generator import ( | |
| Ad, | |
| GeneratedEpisode, | |
| generate_episode, | |
| generate_proposal_data, | |
| ) | |
| from .tool_registry import InvestigationToolRegistry | |
| class EpisodeLoader(Protocol): | |
| """Pluggable episode source. | |
| Implementations must accept a `seed` and `task_id` keyword and return a | |
| fully-formed `GeneratedEpisode` (deterministic given the seed). | |
| """ | |
| def load(self, *, seed: int, task_id: str) -> GeneratedEpisode: ... | |
| class SyntheticEpisodeLoader: | |
| """ | |
| Default loader: defers to `data.ad_generator.generate_episode`. | |
| Used by the InvestigatorEnvironment in standalone (R1) mode and by the | |
| Referee in Phase 1. | |
| """ | |
| def __init__(self, *, default_task_id: str = "task_1") -> None: | |
| self.default_task_id = default_task_id | |
| def load( | |
| self, *, seed: Optional[int] = None, task_id: Optional[str] = None | |
| ) -> GeneratedEpisode: | |
| effective_seed = seed if seed is not None else hash(uuid4()) & 0xFFFFFFFF | |
| effective_task_id = task_id or self.default_task_id | |
| return generate_episode(effective_seed, effective_task_id) | |
| class CallableEpisodeLoader: | |
| """ | |
| Test/eval loader that wraps an arbitrary callable. | |
| Useful for golden-replay tests where you want to inject a hand-crafted | |
| `GeneratedEpisode` without re-running the random sampler. | |
| """ | |
| def __init__( | |
| self, fn: Callable[..., GeneratedEpisode] | |
| ) -> None: | |
| self._fn = fn | |
| def load( | |
| self, *, seed: Optional[int] = None, task_id: Optional[str] = None | |
| ) -> GeneratedEpisode: | |
| return self._fn(seed=seed, task_id=task_id) | |
| def extend_episode_with_proposal( | |
| *, | |
| episode: GeneratedEpisode, | |
| registry: InvestigationToolRegistry, | |
| seed: int, | |
| ad_copy: str, | |
| category: str, | |
| landing_page_blurb: Optional[str] = None, | |
| targeting_summary: Optional[str] = None, | |
| ad_id: Optional[str] = None, | |
| ) -> Ad: | |
| """ | |
| Append a Fraudster-proposed ad to `episode` and register its | |
| investigation data on `registry`. | |
| Both the episode (canonical state) and the registry (lookup view) are | |
| updated so the Investigator can immediately investigate the new ad via | |
| the same code path it uses for synthetic ads. | |
| Returns the newly-created Ad. | |
| """ | |
| rng = random.Random(seed) | |
| # Pick the next free ad_id slot (ad_001, ad_002, ...). | |
| if ad_id is None: | |
| existing_ids = {a.ad_id for a in episode.ads} | set(registry.known_ads()) | |
| next_idx = len(episode.ads) + 1 | |
| while True: | |
| candidate = f"ad_{next_idx:03d}" | |
| if candidate not in existing_ids: | |
| ad_id = candidate | |
| break | |
| next_idx += 1 | |
| ad, investigation_data, profile, campaign, landing_page = generate_proposal_data( | |
| rng=rng, | |
| ad_id=ad_id, | |
| ad_copy=ad_copy, | |
| category=category, | |
| landing_page_blurb=landing_page_blurb, | |
| targeting_summary=targeting_summary, | |
| existing_ads=list(episode.ads), | |
| ) | |
| episode.ads.append(ad) | |
| episode.advertiser_profiles[ad_id] = profile | |
| episode.campaign_profiles[ad_id] = campaign | |
| episode.landing_pages[ad_id] = landing_page | |
| episode.investigation_data[ad_id] = dict(investigation_data) | |
| registry.register_ad(ad_id, investigation_data) | |
| return ad | |
| __all__ = [ | |
| "EpisodeLoader", | |
| "SyntheticEpisodeLoader", | |
| "CallableEpisodeLoader", | |
| "extend_episode_with_proposal", | |
| ] | |