Skip to content

Repository files navigation

ReportFast

ReportFast builds static HTML reports of pathology slides. A report is one file of cards; every card links into the xOpat v3 viewer with the whole session carried in the URL fragment. Nothing is served, no JavaScript runs in the page, and the file can be mailed as it is.

The point is to remove the failure nobody notices. A link pointing at data the tile server cannot open still loads the viewer, still renders a card, and shows it black with no error anywhere. The library exists to make that pair of mistakes — a wrong reference, a wrong index — impossible to make quietly.

The intended way to use it

One command installs the library; the skill comes with it:

uv add "report-fast[mlflow] @ git+https://github.com/RationAI/reporting.git"

The skill — the procedure an agent follows — ships inside the package, and reportfast skill show prints it. Nothing installs anything. A project that wants Claude Code to load the skill as its own commits a skills/ directory, or installs it through Claude Code's own mechanism; either way the library's job ends at carrying the file.

Then describe the report: which cases, which slides, which overlays, what colours. The agent writes a Python script, the library builds the sessions and renders the page. There is no build command — a build would be a second, worse way to write the same loop over files. The script is the report's record: it names the folders, the colours, the order and the layout, and unlike a spec file it can be re-run.

Everything below is for the cases where the agent is not doing it.

Nothing here requires an agent, or one vendor over another. The package is plain Python with one dependency, and the thing it asks for is a script. The skill is a markdown file with a name and a description at the top — readable by a human, by another agent, or by any tool that takes instructions from a file — and no code path in the library calls an agent or a model. What an agent buys is that the procedure in the skill gets applied consistently to a folder of slides; read the same file and write the script yourself, and the result is the same report.

What the library provides

  • The session object. XopatSession holds the document the viewer boots from and turns it into a URL. Build it from a slide and overlays, or read back a session someone else authored — a dict, JSON text, a file, a pasted viewer link. Fields the library has never heard of survive untouched, so its vocabulary is never the viewer's ceiling.
  • Sources. Slides and overlays from a mounted folder or from an MLflow run's artifacts, matched to each other by file stem. case_matrix reports what paired and what dropped, which is the difference between a finding and a wrong folder.
  • Two page components. SlideCard and SlideGrid, plus Section for grouping and Report's title, subtitle and preamble as the page's whole prose budget. Custom blocks are BaseComponent subclasses.
  • MLflow integration. Read a run's artifacts as DataIDs without downloading them, and publish a report back to a run — which happens only when publish() is called.
  • Reproducible output. The same report renders byte-identical HTML: no timestamps, ids derived from position rather than chance.

What it deliberately does not have: a schema, a manifest format, a CLI that composes a report, a probe that runs at build time, a copy of the viewer's feature list. Each was built, and each was deleted; DESIGN.md has the reasons.

Install

Not on PyPI.

uv add "report-fast @ git+https://github.com/RationAI/reporting.git"
uv add "report-fast[mlflow] @ git+https://github.com/RationAI/reporting.git"  # + MLflow
uv add --editable /path/to/reporting                                # a checkout

One optional extra, mlflow (>=2.8,<4). The deployment runs two MLflow tracking servers (a 2.16 one and an s3-backed 3.16 one); both client major versions read both, and the only measured break is a 3.x client publishing to the 2.16 server — a loud 404, not a silent one. Which server a report is built against is a per-call choice (Mlflow(tracking_uri=…) / $MLFLOW_TRACKING_URI); the artifact prefix travels with the server. Everything else needs only python-fasthtml, and import report_fast works without the extra at all. Python 3.10+.

Setting up your agent

The library needs nothing else. The agent needs to know the skill exists — apply the one subsection that matches your agent; the steps are one-time.

Claude Code

Claude loads skills from folders it scans, so install the library and copy the bundled skill where Claude looks — once per machine, then every project picks it up:

site_packages=$(dirname "$(python -c 'import report_fast; print(report_fast.__file__)')")
cp -r "$site_packages/skill" ~/.claude/skills/reportfast

(From a working checkout of this repo rather than an install, import report_fast resolves to the checkout, which holds no bundled copy — take the source directly instead: cp -r skills/reportfast ~/.claude/skills/reportfast.)

A new session then sees the skill and loads it automatically when a task matches its description. One caveat: that folder is a copy, so after a package update re-run the two lines — the wheel carries the corrections, and a stale copy is a stale procedure. To run Claude Code on the institution's models rather than Anthropic's subscription, Cerit documents the VS Code integration here.

Any other agent (Qwen Code, Codex, …)

Other agents read a startup-instructions file instead of scanning skill folders — AGENTS.md, QWEN.md, or whatever yours loads at session start. Commit a pointer to it in the project where reports get built:

echo 'Before writing any report script, run `uvx report-fast skill show` and follow it, including the references it names.' >> AGENTS.md

The sentence is a pointer, not a copy: the agent runs the command and reads the skill fresh from the package, so there is nothing to re-copy when the package updates. uvx fetches the package on the spot, so this works even before the library is installed in the project's environment. Commit it once and every teammate's sessions pick it up.

No agent

No setup exists, because none is needed: reportfast skill show prints the procedure, and a person who follows it and writes the script produces the same report. Every code path in the library is ordinary Python with no model call.

Python

from pathlib import Path

from report_fast import Report, SlideGrid, XopatSession

sessions = [
    XopatSession.from_slide(path, name=path.stem)
    for path in sorted(Path("/mnt/slides").glob("*.tif"))
]
Report(title="Cohort QC", subtitle=f"{len(sessions)} slides").add(
    SlideGrid(sessions=sessions)
).write("report.html")

Masks over slides, matched by stem — case_001.svs and case_001.tiff are one case:

from report_fast import Drive, Mask, MlflowRun, Report, SlideGrid, case_matrix

matrix = case_matrix(
    Drive("/mnt/data/colon/dysplasia"),
    [
        Mask("Tissue", Drive("/mnt/data/tissue_masks"), color="#ffff00", opacity=0.5),
        Mask("Grades", MlflowRun("41d5e1d7d43641ea8f645f9b7945e9f7", "annot_masks"),
             classes=3, palette=["#ffffff", "#ff0000", "#00ff00"]),
    ],
    only=["1094_18_HE_0", "8625_13_HE_A"],   # the cohort, in report order
    min_layers=2,
)
Report(title="Dysplasia QC", blocks=[SlideGrid(sessions=matrix.sessions)]).write("report.html")
print(matrix.coverage, matrix.dropped)       # what paired; what got filtered

Nothing is written unless a path is named: write() returns the path, to_html() returns the string and touches no disk. Other entry points are XopatSession.from_url / from_file / from_config for sessions that already exist, sessions_from_folder, Mlflow for runs, and XopatEndpoint to aim one call at another deployment.

Publishing is asked for

Mlflow.publish() is the only name in the library that uploads, and it is a separate line in a script rather than a flag that could be set. Nothing implies it — not a config key, not a CI job, not an obvious-looking request: the workflow file has no publish step and a test asserts that it never gains one. An artifact logged to somebody's run is in the record, and there is no clean way to take it back out.

Credentials go through the environment, never into a script or a report.

On reproducibility: the record of a report is its script, not a copy of its inputs, so publish() stores no config by default — the script names the folders, colours, order and layout and can be re-run, which a frozen config cannot. Where a run should carry the inputs anyway, publish(extra_dir=…) logs a directory beside the page under report/conf; SKILL.md, What a report is recorded by, writes a provenance manifest into one.

Configuration

XOPAT_BASE_URL (viewer root), XOPAT_WSI_BASE_URL (tile server — its own mount, not under the viewer's), XOPAT_IMAGE_PROTOCOL, XOPAT_MOUNT_ROOT (the prefix stripped to form a DataID), MLFLOW_TRACKING_URI, MLFLOW_WEB_URL, and REPORTFAST_MLFLOW_ARTIFACT_PREFIX (the DataID namespace a deployment's tile server registers MLflow artifacts under — wrong value, no error, no overlays; see the skill). Each can also be passed per call. references/deployment.md lists every one with its default and where it is read.

Two facts that cost time when unknown:

  • The path selects the viewer version — this host serves v2 at /xopat/ and v3 at /v3/, and a v3 session opened by v2 looks like it loaded.
  • Slide paths are shared state. A session names slides by path, never by bytes, so the machine building a report and the tile server must see the same file under the same root. Nothing checks this; it surfaces when someone opens the page.

The CLI is the skill

reportfast reads: it prints the procedure an agent follows, and writes nothing.

skill show print SKILL.md; --reference references/deployment.md prints a bundled file, including the examples/*.json session fixtures

Exit codes: 0 fine, 1 the file asked for is not in the bundle, 4 used wrongly. There used to be skill install and skill where; see Reversals in DESIGN.md.

Where the detail lives

Looking for Read
The procedure an agent follows reportfast skill show, or skills/reportfast/SKILL.md
Session shape, params, the black-card checklist reportfast skill show --reference references/xopat-v3.md
Deployment coordinates, DataID construction, environment reportfast skill show --reference references/deployment.md
MLflow: artifacts, addresses, publishing reportfast skill show --reference references/mlflow.md
A real session to imitate reportfast skill show --reference examples/dysplasia_case.json
Why it is built this way, and what was ruled out DESIGN.md

Repository layout and tests

report_fast/ splits into the wire format (xopat.py, session.py, layer.py), rendering (core.py, components/), and the edges (masks.py, mlflow.py, skill.py, __main__.py). Nothing here copies the viewer's vocabulary: layer.py is the one file naming a shader type, and everything else about the viewer comes from its own source — see test_there_is_no_copy_of_the_viewer_vocabulary.

uv run pytest -q          # no server, no mount, no slide opened, no network
uv run ruff check .

Each test file also runs standalone (uv run python tests/test_cli.py). CI runs both on Python 3.10 and 3.12 and has no publish step. Three properties are asserted rather than assumed: output is byte-identical for the same inputs, no workflow publishes, and every API name the skill's prose offers actually exists.

About

agentic tool for creation of reports

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages