160 lines
6.1 KiB
Python
160 lines
6.1 KiB
Python
"""Stable read projections for evaluation Campaigns."""
|
|
|
|
from typing import Any, Optional
|
|
|
|
from sqlmodel import Session
|
|
|
|
from agenteval.evaluation.campaign_scheduler import clock_offset, elapsed_seconds
|
|
from agenteval.evaluation.comparison import load_comparison_view
|
|
from agenteval.evaluation.report import (
|
|
build_campaign_timeline,
|
|
generate_campaign_report,
|
|
summarize_campaign_progress,
|
|
)
|
|
from agenteval.exploration.summary import summarize_campaign_exploration
|
|
from agenteval.storage.db import iso_utc, utc_now
|
|
from agenteval.storage.repository import (
|
|
CampaignAnalysisRepository,
|
|
CampaignRepository,
|
|
RunRepository,
|
|
ScenarioRepository,
|
|
TargetRepository,
|
|
)
|
|
|
|
|
|
class CampaignReadModel:
|
|
"""One interface for Campaign list, detail, report and export projections."""
|
|
|
|
def __init__(self, session: Session):
|
|
self._session = session
|
|
self._campaigns = CampaignRepository(session)
|
|
self._runs = RunRepository(session)
|
|
self._scenarios = ScenarioRepository(session)
|
|
self._analyses = CampaignAnalysisRepository(session)
|
|
|
|
def list_items(self) -> list[dict[str, Any]]:
|
|
campaigns = self._campaigns.list_all()
|
|
campaign_ids = [campaign.id for campaign in campaigns if campaign.id]
|
|
runs_by_campaign = self._runs.list_by_campaigns(campaign_ids)
|
|
items = []
|
|
for campaign in campaigns:
|
|
data = campaign.model_dump()
|
|
data["progress"] = summarize_campaign_progress(
|
|
campaign,
|
|
runs_by_campaign.get(campaign.id, []),
|
|
)
|
|
items.append(data)
|
|
return items
|
|
|
|
def detail(self, campaign_id: str) -> Optional[dict[str, Any]]:
|
|
campaign = self._campaigns.get(campaign_id)
|
|
if campaign is None:
|
|
return None
|
|
data = campaign.model_dump()
|
|
runs = self._runs.list_by_campaign(campaign_id)
|
|
current_offset = 0.0
|
|
if campaign.started_at is not None:
|
|
current_offset = min(
|
|
clock_offset(
|
|
elapsed_seconds=elapsed_seconds(now=utc_now(), started_at=campaign.started_at),
|
|
time_scale=campaign.time_scale,
|
|
),
|
|
float(campaign.window_seconds),
|
|
)
|
|
data["progress"] = {
|
|
"current_offset_seconds": current_offset,
|
|
"spawned_runs": len(runs),
|
|
"completed_runs": sum(1 for run in runs if run.status.value == "completed"),
|
|
}
|
|
return data
|
|
|
|
def report(self, campaign_id: str) -> Optional[dict[str, Any]]:
|
|
campaign = self._campaigns.get(campaign_id)
|
|
if campaign is None:
|
|
return None
|
|
report = self._core_report(campaign)
|
|
exploration = summarize_campaign_exploration(self._session, campaign_id)
|
|
if exploration is not None:
|
|
report["exploration"] = exploration
|
|
return report
|
|
|
|
def timeline(self, campaign_id: str) -> Optional[dict[str, Any]]:
|
|
campaign = self._campaigns.get(campaign_id)
|
|
if campaign is None:
|
|
return None
|
|
entries = build_campaign_timeline(
|
|
campaign,
|
|
self._runs.list_by_campaign(campaign_id),
|
|
scenario_names=self._scenarios.name_map(),
|
|
)
|
|
return {"entries": entries}
|
|
|
|
def analysis(self, campaign_id: str) -> Optional[dict[str, Any]]:
|
|
if self._campaigns.get(campaign_id) is None:
|
|
return None
|
|
row = self._analyses.get_by_campaign(campaign_id)
|
|
if row is None:
|
|
return {"status": "none"}
|
|
return {
|
|
"status": row.status,
|
|
"result": row.get_result(),
|
|
"error": row.error,
|
|
"model_config_id": row.model_config_id,
|
|
"triggered_by": row.triggered_by,
|
|
"updated_at": iso_utc(row.updated_at),
|
|
}
|
|
|
|
def comparison(self, campaign_id: str) -> Optional[dict[str, Any]]:
|
|
campaign = self._campaigns.get(campaign_id)
|
|
return load_comparison_view(self._session, campaign) if campaign is not None else None
|
|
|
|
def full_view(self, campaign_id: str) -> Optional[dict[str, Any]]:
|
|
campaign = self._campaigns.get(campaign_id)
|
|
if campaign is None:
|
|
return None
|
|
report = self._core_report(campaign)
|
|
exploration = summarize_campaign_exploration(self._session, campaign_id)
|
|
analysis = self.analysis(campaign_id)
|
|
comparison = load_comparison_view(self._session, campaign)
|
|
return {
|
|
"report": report,
|
|
"exploration": exploration,
|
|
"analysis": analysis.get("result") if analysis and analysis.get("status") == "completed" else None,
|
|
"comparison": comparison if comparison.get("status") != "none" else None,
|
|
}
|
|
|
|
def markdown_projection(self, campaign_id: str) -> Optional[dict[str, Any]]:
|
|
campaign = self._campaigns.get(campaign_id)
|
|
view = self.full_view(campaign_id)
|
|
if campaign is None or view is None:
|
|
return None
|
|
|
|
comparison = None
|
|
comparison_view = view["comparison"]
|
|
if comparison_view and comparison_view.get("status") == "completed":
|
|
row = comparison_view.get("comparison") or {}
|
|
baseline = row.get("baseline") or {}
|
|
comparison = {
|
|
"result": row.get("result"),
|
|
"baseline_name": baseline.get("name"),
|
|
"baseline_completed_at": baseline.get("completed_at"),
|
|
"model_name": row.get("model_name"),
|
|
"updated_at": row.get("updated_at"),
|
|
"metric_diff": comparison_view.get("metric_diff"),
|
|
}
|
|
|
|
target = TargetRepository(self._session).get(campaign.target_id)
|
|
return {
|
|
**view,
|
|
"comparison": comparison,
|
|
"target_name": target.name if target else None,
|
|
"scenario_names": self._scenarios.name_map(),
|
|
}
|
|
|
|
def _core_report(self, campaign) -> dict[str, Any]:
|
|
return generate_campaign_report(
|
|
campaign,
|
|
self._runs.list_by_campaign(campaign.id),
|
|
scenario_names=self._scenarios.name_map(),
|
|
)
|