Skip to content

Reporting

REPORT.md and CAMPAIGN.md generation.

report

Generating structured Markdown reports from trace and score records.

All reports are derived from machine-recorded execution traces and audited scores.

render_report

render_report(trace, workflow, score, experiment, goal=None)

Renders REPORT.md content for a single run.

Parameters:

Name Type Description Default
trace Trace

Completed execution Trace.

required
workflow Workflow

Workflow that was executed.

required
score Score | None

Optional Score object.

required
experiment Experiment

Experiment definition.

required
goal Goal | None

Optional project Goal.

None

Returns:

Type Description
str

Formatted Markdown report string.

Source code in python/src/science_adk/report.py
def render_report(
    trace: Trace,
    workflow: Workflow,
    score: Score | None,
    experiment: Experiment,
    goal: Goal | None = None,
) -> str:
    """Renders REPORT.md content for a single run.

    Args:
      trace: Completed execution Trace.
      workflow: Workflow that was executed.
      score: Optional Score object.
      experiment: Experiment definition.
      goal: Optional project Goal.

    Returns:
      Formatted Markdown report string.
    """
    lines: list[str] = [
        f"# {experiment.id} — run {trace.run_id}",
        "",
        _verdict_line(trace, score),
        "",
        "## Hypothesis",
        "",
        experiment.hypothesis or "_Not stated._",
    ]

    if goal and goal.question:
        lines += ["", "## Research question", "", goal.question]

    if score and score.observed_value is not None:
        lines += ["", "## Result", ""]
        metric = score.target_metric or "metric"
        lines.append(f"**{metric}: {score.observed_value:g}**")
        if score.target_value is not None:
            verdict = "meets" if score.meets_target else "does not meet"
            lines.append("")
            lines.append(f"This {verdict} the target of {score.target_value:g}.")

    lines += [
        "",
        "## What ran",
        "",
        "| node | kind | state | time |",
        "|---|---|---|---|",
    ]
    for node in workflow.nodes:
        record = trace.node(node.id)
        state = record.state if record else "not reached"
        duration = f"{record.duration_s:.2f}s" if record and record.duration_s else "—"
        lines.append(f"| `{node.id}` | {node.kind} | {state} | {duration} |")

    failures = trace.failed_nodes
    if failures:
        lines += ["", "## Failures", ""]
        for record in failures:
            lines += [f"**`{record.node_id}`** — {record.error}", ""]
            if record.traceback:
                lines += ["```", record.traceback.strip()[-1500:], "```", ""]

    outputs = _output_table(trace)
    if outputs:
        lines += [
            "",
            "## Outputs",
            "",
            "| node | port | value |",
            "|---|---|---|",
        ]
        lines += outputs

    figures = _figures(trace)
    if figures:
        lines += ["", "## Figures", ""]
        for path in figures:
            lines += [f"![{path}]({path})", ""]

    if score:
        lines += ["", "## Gates", ""]
        for gate in score.gates:
            mark = "✓" if gate.passed else "✗"
            lines.append(f"- {mark} **{gate.name}** — {gate.detail}")

        if score.audited:
            lines += ["", "## Audit", ""]
            for pillar, value in sorted(score.pillars.items()):
                lines.append(f"- {pillar.replace('_', ' ')}: **{value:.2f}**")
            lines += [
                "",
                f"**Overall: {score.value:.2f}**",
                "",
                score.rationale,
            ]
        else:
            lines += [
                "",
                "## Audit",
                "",
                "_Not yet audited. Until an audit is recorded this run has no "
                "score and must not be presented as a finding._",
            ]

    provenance = _provenance_summary(trace)
    if provenance:
        lines += ["", "## Provenance", "", *provenance]

    lines += [
        "",
        "---",
        "",
        f"_Generated from `trace.json` for run {trace.run_id}. "
        "Every number above came from the recorded execution._",
    ]
    return "\n".join(lines) + "\n"

render_campaign

render_campaign(goal, experiments, learning_count=0)

Renders CAMPAIGN.md content summarizing project progress.

Parameters:

Name Type Description Default
goal Goal

The research Goal.

required
experiments list[tuple[Experiment, Score | None]]

List of (Experiment, Score) pairs.

required
learning_count int

Count of recorded learnings.

0

Returns:

Type Description
str

Formatted Markdown campaign overview.

Source code in python/src/science_adk/report.py
def render_campaign(
    goal: Goal,
    experiments: list[tuple[Experiment, Score | None]],
    learning_count: int = 0,
) -> str:
    """Renders CAMPAIGN.md content summarizing project progress.

    Args:
      goal: The research Goal.
      experiments: List of (Experiment, Score) pairs.
      learning_count: Count of recorded learnings.

    Returns:
      Formatted Markdown campaign overview.
    """
    lines = ["# Campaign", ""]

    if goal.question:
        lines += ["## Question", "", goal.question, ""]
    if goal.target_metric:
        target = (
            f" (target: {goal.target_value:g})" if goal.target_value is not None else ""
        )
        lines += [f"**Measuring:** {goal.target_metric}{target}", ""]

    if not experiments:
        lines += ["_No experiments yet._", ""]
        return "\n".join(lines)

    ranked = sorted(
        experiments,
        key=lambda pair: (pair[1].value if pair[1] else -1.0),
        reverse=True,
    )
    lines += [
        "## Experiments",
        "",
        "| experiment | status | score | observed | parent |",
        "|---|---|---|---|---|",
    ]
    for experiment, score in ranked:
        value = f"{score.value:.2f}" if score and score.audited else "—"
        observed = (
            f"{score.observed_value:g}"
            if score and score.observed_value is not None
            else "—"
        )
        parent = f"`{experiment.parent}`" if experiment.parent else "—"
        lines.append(
            f"| `{experiment.id}` | {experiment.status} | {value} | {observed}"
            f" | {parent} |"
        )

    if any(e.parent for e, _ in experiments):
        lines += ["", "## Lineage", "", "```mermaid", "graph TD"]
        for experiment, score in experiments:
            label = experiment.id
            if score and score.audited:
                label += f"<br/>{score.value:.2f}"
            lines.append(f'    {_node_id(experiment.id)}["{label}"]')
        for experiment, _ in experiments:
            if experiment.parent:
                lines.append(
                    f"    {_node_id(experiment.parent)} --> {_node_id(experiment.id)}"
                )
        lines.append("```")

    best = ranked[0] if ranked and ranked[0][1] and ranked[0][1].audited else None
    if best:
        lines += [
            "",
            "## Where this stands",
            "",
            f"Best so far: **`{best[0].id}`** at {best[1].value:.2f}.",
        ]
        if best[1].target_value is not None:
            if best[1].meets_target:
                lines.append("The target has been met.")
            else:
                lines.append(
                    f"The target of {best[1].target_value:g} has not yet been met."
                )

    if learning_count:
        lines += [
            "",
            f"{learning_count} recorded learning(s) — see `LEARNINGS.md`.",
        ]

    lines += ["", "---", "", "_Generated by `science-adk campaign`._"]
    return "\n".join(lines) + "\n"