From 23e83908c2073c7f11c2e7aa83d1829cc10edb4a Mon Sep 17 00:00:00 2001 From: serversdown Date: Fri, 29 May 2026 22:17:43 +0000 Subject: [PATCH] report_pdf: fix PVS overlapping stats table, drop NA caption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related fixes to the per-channel stats block: 1. Pin the stats table's position via an explicit bbox= on ax.table() so the bottom edge is at a known axes-fraction Y. The previous loc="upper left" + tbl.scale(1, 1.4) combo let matplotlib choose row heights based on text size, which made the table extend further below the axes than the hard-coded PVS line at y=-0.08 expected. Result was the "Peak Vector Sum X in/s" string landing horizontally inside the Peak Displacement row. With bbox=[0, 1-N*0.12, 0.80, N*0.12] the table is pinned to a precise rectangle (12% axes-fraction per row × N rows tall). _draw_stats_table now stashes the bottom Y on the axes for the PVS helper to reference, so the geometry stays in sync. 2. Center PVS horizontally (ha="center" at x=0.5 instead of ha="left" at x=0). The previous left-edge alignment put PVS at the same X as the label column, which read as "off-center" once the rest of the stats data was column-aligned further right. 3. Drop the "NA: Not Applicable" caption. It existed to explain "—" placeholder cells, but "—" is universally understood and the caption was always visually squished against the PVS line below. Less cruft on the page; one fewer position to manage. Verified against a real BE12599 histogram event (5 data rows) and a real UM12947 IDFW waveform event (6 data rows) — both layouts clear the table cleanly with no overlap. Co-Authored-By: Claude Opus 4.7 (1M context) --- sfm/report_pdf.py | 87 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/sfm/report_pdf.py b/sfm/report_pdf.py index 6618d9a..25859d1 100644 --- a/sfm/report_pdf.py +++ b/sfm/report_pdf.py @@ -638,14 +638,7 @@ def _draw_channel_stats_waveform(ax, rd: ReportData) -> None: ("Sensor Check", "sensor_check", ""), ] _draw_stats_table(ax, rd, rows_spec) - if rd.peak_vector_sum_ips is not None: - line = f"Peak Vector Sum {rd.peak_vector_sum_ips:.3f} in/s" - if rd.peak_vector_sum_time_s is not None: - line += f" At {rd.peak_vector_sum_time_s:.3f} sec." - ax.text(0.0, -0.08, line, fontsize=9, weight="bold", - ha="left", va="top", transform=ax.transAxes) - ax.text(0.0, -0.18, "NA: Not Applicable", fontsize=7, color="#888", - ha="left", va="top", transform=ax.transAxes) + _draw_pvs_summary(ax, rd, n_data_rows=len(rows_spec)) def _draw_channel_stats_histogram(ax, rd: ReportData) -> None: @@ -663,20 +656,54 @@ def _draw_channel_stats_histogram(ax, rd: ReportData) -> None: ("Sensor Check", "sensor_check", ""), ] _draw_stats_table(ax, rd, rows_spec) - if rd.peak_vector_sum_ips is not None: - line = f"Peak Vector Sum {rd.peak_vector_sum_ips:.3f} in/s" - # Histograms: "0.091 in/s on May 27, 2026 At 06:06:14" - # The when_str is "HH:MM:SS Month DD, YYYY" — reformat for BW match. - if rd.peak_vector_sum_when_str: - parts = rd.peak_vector_sum_when_str.split(" ", 1) - if len(parts) == 2: - line += f" on {parts[1]} At {parts[0]}" - else: - line += f" on {rd.peak_vector_sum_when_str}" - ax.text(0.0, -0.08, line, fontsize=9, weight="bold", - ha="left", va="top", transform=ax.transAxes) - ax.text(0.0, -0.18, "NA: Not Applicable", fontsize=7, color="#888", - ha="left", va="top", transform=ax.transAxes) + _draw_pvs_summary(ax, rd, n_data_rows=len(rows_spec), histogram_when=True) + + +def _draw_pvs_summary( + ax, + rd: ReportData, + *, + n_data_rows: int, + histogram_when: bool = False, +) -> None: + """Render the Peak Vector Sum + 'NA: Not Applicable' caption below the + stats table. + + Reads ``ax._stats_table_bottom`` (set by ``_draw_stats_table`` when + it pins the table via an explicit ``bbox``) so the PVS line lands + just below the table's known bottom edge instead of guessing at the + geometry. + + Centered horizontally for visual balance (the previous left-aligned + x=0 landed under the label column, not the data, which looked off). + """ + if rd.peak_vector_sum_ips is None: + return + + line = f"Peak Vector Sum {rd.peak_vector_sum_ips:.3f} in/s" + if histogram_when and rd.peak_vector_sum_when_str: + # Histogram absolute date+time. when_str is "HH:MM:SS Month DD, YYYY"; + # reformat to " on At