Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions agentprof/report/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def generate_report(
summary=summary,
report_md_path=str(markdown_path),
report_json_path=str(json_path),
report_html_path=None,
)
)

Expand All @@ -100,6 +101,7 @@ def generate_report(
total_wasted_cost_usd=_sum_decimals(issue.wasted_cost_usd for issue in issues),
report_md_path=markdown_path,
report_json_path=json_path,
report_html_path=None,
)


Expand Down
1 change: 1 addition & 0 deletions agentprof/report/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ class ReportBuildResult(BaseModel):
total_wasted_cost_usd: Decimal = Field(default=Decimal("0"))
report_md_path: Path
report_json_path: Path
report_html_path: Path | None = None
18 changes: 15 additions & 3 deletions agentprof/store/duckdb_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class ReportRecord:
summary: dict[str, Any]
report_md_path: str | None
report_json_path: str | None
report_html_path: str | None = None


MIGRATIONS = (
Expand Down Expand Up @@ -289,6 +290,7 @@ class ReportRecord:
summary_json TEXT NOT NULL DEFAULT '{}',
report_md_path TEXT,
report_json_path TEXT,
report_html_path TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT current_timestamp
);
""",
Expand All @@ -299,6 +301,13 @@ class ReportRecord:
sql="""
ALTER TABLE normalized_spans ADD COLUMN IF NOT EXISTS input_retry_fingerprint TEXT;
ALTER TABLE normalized_spans ADD COLUMN IF NOT EXISTS output_retry_fingerprint TEXT;
""",
),
Migration(
version=3,
name="add_report_html_path",
sql="""
ALTER TABLE reports ADD COLUMN IF NOT EXISTS report_html_path TEXT;
""",
),
)
Expand Down Expand Up @@ -927,8 +936,9 @@ def upsert_report(self, record: ReportRecord) -> None:
window_end,
summary_json,
report_md_path,
report_json_path
) VALUES (?, ?, ?, ?, ?, ?, ?)
report_json_path,
report_html_path
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
[
record.report_id,
Expand All @@ -943,6 +953,7 @@ def upsert_report(self, record: ReportRecord) -> None:
),
record.report_md_path,
record.report_json_path,
record.report_html_path,
],
)
connection.execute("COMMIT")
Expand All @@ -955,7 +966,7 @@ def fetch_reports(self, *, report_id: str | None = None) -> list[ReportRecord]:
query = """
SELECT report_id, project, CAST(window_start AS VARCHAR),
CAST(window_end AS VARCHAR), summary_json, report_md_path,
report_json_path
report_json_path, report_html_path
FROM reports
"""
params: list[str] = []
Expand All @@ -976,6 +987,7 @@ def fetch_reports(self, *, report_id: str | None = None) -> list[ReportRecord]:
summary=json.loads(row[4]),
report_md_path=row[5],
report_json_path=row[6],
report_html_path=row[7],
)
for row in rows
]
Expand Down
3 changes: 3 additions & 0 deletions tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def test_generate_report_writes_markdown_json_and_store_row(tmp_path: Path) -> N
assert result.total_wasted_cost_usd == Decimal("0.020000000")
assert result.report_md_path.is_file()
assert result.report_json_path.is_file()
assert result.report_html_path is None

assert payload["report_id"] == "test-report"
assert payload["project"] == "tracer"
Expand All @@ -61,6 +62,8 @@ def test_generate_report_writes_markdown_json_and_store_row(tmp_path: Path) -> N
assert reports[0].project == "tracer"
assert reports[0].summary["issue_count"] == 1
assert reports[0].report_md_path == str(result.report_md_path)
assert reports[0].report_json_path == str(result.report_json_path)
assert reports[0].report_html_path is None
assert store.stats()["reports"] == 1


Expand Down
78 changes: 77 additions & 1 deletion tests/test_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from agentprof.store.duckdb_store import TABLES, DuckDBStore
from agentprof.store.duckdb_store import TABLES, DuckDBStore, ReportRecord


def test_store_creates_schema(tmp_path: Path) -> None:
Expand All @@ -16,6 +16,7 @@ def test_store_creates_schema(tmp_path: Path) -> None:
assert store.migrations() == [
(1, "initial_store_schema"),
(2, "add_normalized_span_retry_fingerprints"),
(3, "add_report_html_path"),
]


Expand All @@ -38,9 +39,84 @@ def test_store_reset_recreates_schema(tmp_path: Path) -> None:
assert store.migrations() == [
(1, "initial_store_schema"),
(2, "add_normalized_span_retry_fingerprints"),
(3, "add_report_html_path"),
]


def test_report_html_path_migration_updates_existing_reports_table(
tmp_path: Path,
) -> None:
store = DuckDBStore(tmp_path / "agentprof.duckdb")
with store.connect() as connection:
connection.execute(
"""
CREATE TABLE schema_migrations (
version INTEGER PRIMARY KEY,
name TEXT NOT NULL,
applied_at TIMESTAMPTZ NOT NULL DEFAULT current_timestamp
)
"""
)
connection.execute(
"""
CREATE TABLE reports (
report_id TEXT PRIMARY KEY,
project TEXT,
window_start TIMESTAMPTZ,
window_end TIMESTAMPTZ,
summary_json TEXT NOT NULL DEFAULT '{}',
report_md_path TEXT,
report_json_path TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT current_timestamp
)
"""
)
connection.executemany(
"INSERT INTO schema_migrations (version, name) VALUES (?, ?)",
[
(1, "initial_store_schema"),
(2, "add_normalized_span_retry_fingerprints"),
],
)

store.ensure_schema()

with store.connect() as connection:
columns = {
row[1]
for row in connection.execute("PRAGMA table_info('reports')").fetchall()
}

assert store.migrations() == [
(1, "initial_store_schema"),
(2, "add_normalized_span_retry_fingerprints"),
(3, "add_report_html_path"),
]
assert "report_html_path" in columns


def test_store_round_trips_report_html_path(tmp_path: Path) -> None:
store = DuckDBStore(tmp_path / "agentprof.duckdb")

store.upsert_report(
ReportRecord(
report_id="html-report",
project="tracer",
window_start=None,
window_end=None,
summary={"issue_count": 0},
report_md_path=".agentprof/reports/html-report.md",
report_json_path=".agentprof/reports/html-report.json",
report_html_path=".agentprof/reports/html-report.html",
)
)

reports = store.fetch_reports(report_id="html-report")

assert len(reports) == 1
assert reports[0].report_html_path == ".agentprof/reports/html-report.html"


def test_migrations_is_read_only_for_missing_store(tmp_path: Path) -> None:
store = DuckDBStore(tmp_path / "missing.duckdb")

Expand Down
Loading