Reports¶
chronicle.reports renders an experiment's hypothesis, takeaways, or research document to a compiled PDF (via the chronicle-tex service) and hands you the bytes.
Reach it two ways: the stateless chronicle.reports API (every call takes the experiment id + kind), or the per-experiment sugar exp.reports.<kind> that binds both for you. Prefer the sugar.
Report kinds¶
exp.reports exposes one accessor per kind:
| Accessor | Kind | For |
|---|---|---|
exp.reports.hypothesis |
hypothesis |
Pre-experiment hypothesis document |
exp.reports.takeaways |
takeaways |
Post-experiment analysis |
exp.reports.research |
research |
General research writing (lit review, synthesis) |
Render¶
Two modes, selected by the experiment's report_settings.<kind>.mode:
- Template (default) — pass a structured
payload; the server fills a.textemplate. - Freeform — pass a raw
tex_body; opt in per kind viaupdate_settingsfirst.
exp = chronicle.experiments.create(hypothesis_summary="...", config_yaml="...")
# Template mode (default) — sugar form.
report = exp.reports.hypothesis.render(payload={
"title": "Ripple study",
"abstract": "...",
"hypothesis": "...",
"plan": ["sweep coefficient", "measure convergence"],
})
# Equivalent stateless form.
report = chronicle.reports.render(exp.id, "hypothesis", payload={"title": "Ripple study", ...})
# Freeform mode — raw .tex instead of a payload.
report = exp.reports.takeaways.render(tex_body=open("draft.tex").read())
Pass template_asset_id to render against a specific template asset instead of the default. render returns a Report handle.
Compile status¶
render produces an asset whose compile_status is one of:
| Status | Terminal? | Meaning |
|---|---|---|
pending |
no | Render accepted; compile not finished |
compiled |
yes | PDF is available |
failed |
yes | Compile failed — see compile_log |
stale |
yes | Inputs changed since this compile |
A missing field reads as unknown. (Authoritative list: reference/client.md.)
Compile is async, but usually fast
On most deployments the render endpoint compiles synchronously, so compile_status is already compiled when render returns. Where the compile worker runs out-of-band, poll with wait_for_compile.
report.wait_for_compile(timeout=60.0, poll_interval=1.0) # polls refresh() until terminal
if report.compile_status == "compiled":
report.download_pdf("hypothesis.pdf")
wait_for_compile returns self once compile_status is terminal or timeout elapses — it does not raise on timeout, so always re-check .compile_status.
The Report handle¶
A Report wraps the asset metadata plus, once content is fetched, the compile body.
Properties: id, asset_type, name, asset_config, compile_status, mode (template/freeform), template_asset_id (or None), compile_request_id (or None), compile_log (Tectonic stdout/stderr — None until content is fetched).
Methods (mutators return self for chaining):
refresh()— re-fetch asset + content.report.refresh().compile_status.wait_for_compile(timeout=60.0, poll_interval=1.0)— poll until terminal.download_pdf(path=None)— return the PDF bytes; write topathif given. RaisesRuntimeErrorifcompile_statusisn'tcompiledor no PDF exists.download_source()— return the rendered.texsource as a string.
compile_log and PDF/source content load on the first download_pdf / download_source / refresh, or eagerly via chronicle.reports.get(asset_id).
Settings¶
update_settings replaces the experiment's report_settings — that's where you opt a kind into freeform mode:
chronicle.reports.update_settings(exp.id, settings={
"hypothesis": {"mode": "freeform", "freeform_prompt": "Write a one-page hypothesis focusing on..."},
})
Frozen at commit
report_settings is frozen once the experiment is committed — update_settings returns 409 Conflict after that.