star_reporter — LaTeX report assembly

Purpose

Assembles in-memory tables, figures, and conclusions into report.tex (and an optional PDF).
Every item lands in a top-pinned minipagenever a bare \begin{figure} float — so many
narrow tables and full-width figures flow across pages instead of locking into one unbreakable box.

Role in the system

  • The final stage of the analysis pipeline: run → log → analyze → report.
  • Driven only by orchestrator (the one module that calls runner / data_analyzer / plotter / star_reporter).
  • Consumes prepared DataFrames from data_analyzer and figure paths already rendered by plotter.
    The reporter never recomputes a metric or draws a pixel — DataFrames are a presentation boundary.
  • Style defaults come from analysis/report_style.yaml; output paths follow the run-stamp layout
    (logs/logs_<date>/<stem>/reports/run_PMHHMM/).

Inputs / Outputs

  • In: tables / figures / conclusions (flat) or sections (ordered ReportSections),
    a report dir, a title, and a compile_pdf flag. Inputs are duck-typed — DataFrames, paths, or
    mappings carrying frame / path / rows are all coerced.
  • Out: report.tex (always), report.pdf (if requested and LaTeX is present),
    report_compile.log. Returns the .tex path; the PDF path is stored on the instance.

Key methods / functions

  • Reporter.generate — write report.tex, optionally compile the PDF — analysis/star_reporter.py:556
  • Reporter.document_tex — full document string: preamble → conclusions → sections → end — :570
  • Reporter.section_blocks — flat tables/figures collapse into one “Results” section — :580
  • Reporter.compile_pdf — run latexmk/pdflatex; skip gracefully when LaTeX is absent — :600
  • latex_command — pick latexmk (preferred) or pdflatex, else None — module-level :17 (promoted out of Reporter 2026-06-22, no instance state)
  • ReportSection.render — each table then figure in its own full-width, page-breakable minipage — :490
  • ReportTable.minipage_tex / ReportFigure.minipage_tex — float-free tabular / image bodies — :421 / :466
  • formatted_title — dotted metric key → LaTeX math (special cases, _oplus, subscripts) — :380
  • latex_escape — escape LaTeX-special chars (incl. </>\textless/\textgreater) — :95
  • minipage — top-pinned, centered minipage wrapper (the \vspace{0pt} idiom) — :364

Footguns

[t] minipages need \vspace{0pt} or a tall first image blows the page

All reports use side-by-side \begin{minipage}[t] blocks. The [t] alignment requires \vspace{0pt}
immediately after \begin{minipage}: without it a tall first image sets the baseline at its BOTTOM and
pushes the second column off the page. This bug is invisible in the generated .tex and was only caught
on a real PDF. (analysis/INSIGHTS.md star_reporter; high_level_plans/analysis_plan.md reflection)

Never reintroduce the legacy float path

The smokes assert the ABSENCE of 0.82\textwidth and \begin{figure}[htbp]. ReportSection.render
always uses full-width (\textwidth) per-item minipages so page breaks fall BETWEEN items. Do not
swap back to bare floats or fixed-width side-by-side boxes. (analysis/INSIGHTS.md star_reporter)

preformatted=True for already-formatted variant columns

table_column_name title-cases headers unless preformatted=True. Comparison pivot headers like
"0.2" or "0.40 (Derate Off)" are already display-ready; title-casing turns "0.2" into "0 2".
Pass preformatted=True whenever variant-name columns are numeric or pre-formatted.
(analysis/INSIGHTS.md star_reporter)

OT1 renders bare </> as upside-down punctuation

latex_escape maps them to \textless{} / \textgreater{}. Never feed an un-escaped metric key
string straight into a table cell. (analysis/INSIGHTS.md star_reporter)

Pseudocode (one report)

Reporter(report_dir, tables|figures|sections, conclusions, compile_pdf)
  report_dir /= run_stamp()              # run_PMHHMM — co-locate with this run's figures/npz
  document_tex():
    preamble (geometry, graphicx, booktabs, caption)
    conclusion itemize (descriptive lines, one per value-bearing table)
    sections:  flat tables/figures -> one "Results" section, else render in order
      each ReportSection.render -> per-item \textwidth minipage, \vspace{1em} between
  write report.tex
  if compile_pdf: latexmk|pdflatex  (absent -> warn + report_compile.log, return None)

orchestrator · data_analyzer · plotter · logger · terminology