Generate Reports#
This guide explains how to use oxo-flow's reporting system to produce structured HTML, JSON, Markdown, and PDF reports from workflow executions.
Overview#
Reports are built from execution truth: run a workflow first, then the report shows what actually happened — expanded commands, per-rule exit codes, real files with recorded checksums, and failure diagnosis for failed runs. Without a checkpoint the report shows template-level data only, and never claims a workflow ran when it didn't.
Reports are assembled from pluggable sections (key-value pairs, tables, narrative text) — see reporting-system for the architecture.
Basic Report Generation#
Generate a report from a workflow file:
# HTML report (default; format also inferred from the -o extension)
oxo-flow report pipeline.oxoflow
# JSON report (the versioned data model behind every view)
oxo-flow report pipeline.oxoflow -o report.json
# Markdown report (git-friendly GFM tables)
oxo-flow report pipeline.oxoflow -f md -o REPORT.md
# Write to a specific file
oxo-flow report pipeline.oxoflow -f html -o results/report.html
Reproducible CI output: --ci pins the generation timestamp
(SOURCE_DATE_EPOCH when set) and all lists are sorted, so identical
state yields byte-identical reports — safe to diff and commit.
Report Contents#
A generated report includes:
| Section | Contents |
|---|---|
| Dashboard | Pipeline status (honest: "No execution data" when nothing ran), task count, total runtime |
| Execution Status | Per-rule status, wall time, and exit codes (requires a checkpoint) |
| Failure Diagnosis | Failed rules: exit code, affected downstream rules, stderr excerpt, suggested next step (only when rules failed) |
| Clinical Compliance | Static capability statement — only for explicitly clinical workflows or when listed in [report].sections; generates no clinical data |
| Workflow Information | Name, version, total rules, detected domain, [config] variables |
| Commands | The commands that actually ran (expanded); declared templates with an explicit fallback label when no execution record exists |
| File Manifest | Real files: checkpoint-recorded inputs (path, size, mtime) and outputs (sha256 + on-disk size/mtime) |
| Metrics | QC metrics parsed from real tool outputs under the workdir (fastp, flagstat, STAR, featureCounts, bcftools, kraken2), one subsection per tool × sample, with Pass/Warn/Fail flags — hidden when nothing parses |
| Sample Matrix | Rule × sample status grid from the checkpoint's expanded instance names — hidden without a checkpoint or sample definitions |
| Resource Accounting | Cluster accounting imported from an sacct CSV via --acct (Rule/State/Elapsed/CPU Time/Max RSS) |
| Environment | Engine version, platform, and the environments the workflow's rules declare |
| Provenance | Engine version, workflow file sha256, checkpoint location |
| Task Summary | Per-rule table of tasks, types, inputs, outputs, environments, and resources |
Sections adapt to available execution data — for example, Execution
Status and Failure Diagnosis only appear with a checkpoint — and
every section, including Task Summary, can be filtered via
[report].sections. List them all with oxo-flow report WF --list-sections.
Report Configuration in .oxoflow#
Add a [report] section to your workflow file to filter report sections:
Note — partial support: of the three fields,
sectionsfilters which registered sections the report includes, by generator name (universal,execution-status,failure-diagnosis,clinical-compliance,workflow-info,commands,file-manifest,environment,metrics,sample-matrix,provenance,task-summary).templateis consumed (see below);formatis parsed but still unsupported: setting it makes the command warn (or fail under--strict) instead of silently ignoring it — output formats are selected via the CLI-fflag.
Fields#
| Field | Type | Description |
|---|---|---|
template |
String | Report template: the built-in name "report.html", or a template file path (resolved relative to the workflow file's directory first, then the current directory). Applies to HTML output only; a render failure warns and falls back to the default renderer (exit 2 under --strict) |
format |
Array | Parsed but not supported yet — setting it warns (or fails under --strict); use -f to select the output format |
sections |
Array | Sections to include, by generator name (see above) |
Custom templates#
[report].template wires a custom Tera
template into HTML rendering:
[report]
template = "report.html" # the built-in template (the default)
# template = "my_report.tera" # or a file: workflow dir first, then cwd
The scaffolded report-template.tera (from
oxo-flow report --init-template) is a good starting point. The built-in
template IS report.html; custom template files whose names do not end in
.html/.htm/.xml (like the scaffold) are registered under
custom.html so Tera HTML-escapes {{ variables }} exactly like the
built-in template. With -f json/-f md/-f pdf the template is skipped
with a note — it applies to HTML output only.
HTML Reports#
HTML reports are self-contained single-file documents with embedded CSS (dark mode, print styles, accessible landmarks). User-controlled strings — workflow and rule names, commands, file paths — are HTML-escaped, so reports are safe to open and share. They can be opened in any browser:
oxo-flow report pipeline.oxoflow -f html -o report.html
open report.html # macOS
xdg-open report.html # Linux
JSON Reports#
JSON reports are the same data model, machine-readable, with
schema_version, a command: "report" tag, and provenance fields
(workflow_path, checkpoint_path):
oxo-flow report pipeline.oxoflow -f json -o report.json
# stdout stays pipe-safe: diagnostics and --ai output go to stderr
oxo-flow report pipeline.oxoflow -f json | jq .schema_version
Example output structure:
{
"schema_version": 1,
"command": "report",
"title": "my-pipeline Report",
"generated_at": "2026-08-13T03:47:43Z",
"workflow_name": "my-pipeline",
"workflow_version": "1.0.0",
"checkpoint_path": "/path/to/.oxo-flow/checkpoint.json",
"workflow_path": "/path/to/my-pipeline.oxoflow",
"sections": [
{
"title": "Workflow Information",
"id": "workflow-info",
"content": {
"type": "KeyValue",
"pairs": [
["Name", "my-pipeline"],
["Version", "1.0.0"],
["Total Rules", "7"],
["Detected Domain", "Generic"]
]
},
"subsections": []
}
],
"metadata": {}
}
PDF Reports#
PDF reports are converted from the HTML report via the external
wkhtmltopdf binary:
If wkhtmltopdf is not installed, the command degrades gracefully: it
writes a printable HTML file (report.html) and warns, instead of
failing. Note the wkhtmltopdf project upstream has been archived — HTML
output is the portable, supported-by-default format.
You can also output the raw wkhtmltopdf command for scripting or custom
processing:
R-friendly Export (--r-data)#
--r-data <DIR> writes two TSV files for downstream R analysis (in
addition to the report itself):
oxo-flow report --run results/experiment1 --r-data analysis/
# analysis/sample_table.tsv sample group
# analysis/metrics.tsv rule wall_time_secs max_memory_mb status
sample_table.tsv maps every sample to its group (from [[sample_groups]]
and [[pairs]]); metrics.tsv has one row per rule with wall time, peak
memory, and status (success / failed / -). Without a checkpoint the
files carry headers only, with a note explaining why. Values are
TSV-sanitized (tabs and newlines replaced) so names can never break the
column layout.
Automatic Snapshots#
Every oxo-flow run / oxo-flow resume automatically writes a JSON
report snapshot after the run — no reporting step needed:
.oxo-flow/reports/report-<UTC timestamp>.json— the full report data model (a-Nsuffix when two snapshots land in the same second).oxo-flow/reports/index.json— a JSON array of{generated_at, workflow, checkpoint, report}entries, sorted bygenerated_at; the last entry is the newest snapshot
Disable the behavior with --no-report-snapshot (on run or resume).
Snapshot failures are warnings — a reporting hiccup never fails a run.
report's own auto-discovered output lands in the same directory.
Programmatic Report Generation#
You can also generate reports programmatically using the core library:
use oxo_flow_core::report::{Report, ReportSection, ReportContent};
let mut report = Report::new("My Report", "pipeline", "1.0.0");
report.add_section(ReportSection {
title: "Summary".to_string(),
id: "summary".to_string(),
content: ReportContent::KeyValue {
pairs: vec![
("Total Samples".to_string(), "24".to_string()),
("Pass Rate".to_string(), "95.8%".to_string()),
],
},
subsections: vec![],
});
let html = report.to_html();
let json = report.to_json().unwrap();
let markdown = report.to_markdown();
See Also#
- Reporting System — architecture and section model
reportcommand — CLI reference