diff --git a/docs/source/project_structure.md b/docs/source/project_structure.md index 0a0fa03..39a6974 100644 --- a/docs/source/project_structure.md +++ b/docs/source/project_structure.md @@ -51,7 +51,7 @@ oceanarray/ │ │ ├── helpers.py # Shared colormap/style helpers │ │ └── _cli_legacy.py # Legacy CLI plots (pending `oceanarray plot ` redesign) │ │ -│ ├── report/ # [report] HTML report generation +│ ├── reports/ # [report] HTML report generation │ │ ├── __init__.py # Public entry-points (MooringReport, etc.) │ │ ├── _mooring.py # Mooring summary template + MooringReport orchestrator │ │ ├── _instrument.py # Per-instrument report ({mooring}_{serial}_report.html) @@ -172,7 +172,7 @@ Three-tier architecture (see `.claude/plotters_update-20260718.md` for the rules - **Tier 1** (`plotters/primitives.py`): low-level axes primitives, no domain knowledge - **Tier 2** (`plotters/current.py`, `timeseries.py`, `hydrography.py`, etc.): domain functions that know about oceanographic variables -- **Tier 3** (`report/_plots.py`): report wrappers that call Tier-2 functions and return +- **Tier 3** (`reports/_plots.py`): report wrappers that call Tier-2 functions and return base64 PNG strings for embedding in HTML --- @@ -183,13 +183,13 @@ Report types, each in its own module: | Report | Module | Output file | |--------|--------|-------------| -| Mooring summary | `report/_mooring.py` | `{mooring}_report.html` | -| Per-instrument | `report/_instrument.py` | `{mooring}_{serial}_report.html` | -| Stack | `report/_stack.py` | `{mooring}_stack_report.html` | -| Grid | `report/_grid.py` | `{mooring}_grid_report.html` | -| Array | `report/_array.py` | `{array}_array_report.html` | +| Mooring summary | `reports/_mooring.py` | `{mooring}_report.html` | +| Per-instrument | `reports/_instrument.py` | `{mooring}_{serial}_report.html` | +| Stack | `reports/_stack.py` | `{mooring}_stack_report.html` | +| Grid | `reports/_grid.py` | `{mooring}_grid_report.html` | +| Array | `reports/_array.py` | `{array}_array_report.html` | -All figures are generated by `report/_plots.py` (Tier 3) and embedded as base64 PNGs. +All figures are generated by `reports/_plots.py` (Tier 3) and embedded as base64 PNGs. --- diff --git a/oceanarray/cli.py b/oceanarray/cli.py index 5eab7b6..832a956 100644 --- a/oceanarray/cli.py +++ b/oceanarray/cli.py @@ -345,7 +345,7 @@ def cmd_report(args: argparse.Namespace) -> int: return 0 if getattr(args, "array", False): - from .report._array import generate_array_report + from .reports._array import generate_array_report if getattr(args, "pdf", False) or getattr(args, "all_reports", False): _status( @@ -366,7 +366,7 @@ def cmd_report(args: argparse.Namespace) -> int: ) return 0 if result else 1 - from .report import MooringReport + from .reports import MooringReport _status("section", f"Report: {args.mooring}") serials = getattr(args, "serial", None) @@ -387,7 +387,7 @@ def cmd_report(args: argparse.Namespace) -> int: stack=all_reports or args.stack, ) if getattr(args, "cruise_table", False): - from .report._recovery_table import generate_recovery_table + from .reports._recovery_table import generate_recovery_table mooring_proc = proc_root / args.mooring out_dir = Path( @@ -401,7 +401,7 @@ def cmd_report(args: argparse.Namespace) -> int: force=args.force, ) if getattr(args, "pdf", False) or all_reports: - from .report import combine_mooring_pdf + from .reports import combine_mooring_pdf # Combine reads the same directory generate() wrote to; both resolve it # through paths.resolve_report_dir so they can never drift. diff --git a/oceanarray/paths.py b/oceanarray/paths.py index ecf6c20..9f84b44 100644 --- a/oceanarray/paths.py +++ b/oceanarray/paths.py @@ -48,7 +48,7 @@ def resolve_report_dir( """Return the directory a mooring's HTML report pages are written to. Single source of truth for report output-dir resolution, mirrored by both - :meth:`oceanarray.report.MooringReport.generate` and the PDF combiner so the + :meth:`oceanarray.reports.MooringReport.generate` and the PDF combiner so the two never drift. Priority: explicit *outdir* wins; otherwise a central *report_dir* nests each mooring under ``report_dir/``; otherwise the default ``proc_root//report``. diff --git a/oceanarray/plotters/current.py b/oceanarray/plotters/current.py index 3f1ab9e..5c9587c 100644 --- a/oceanarray/plotters/current.py +++ b/oceanarray/plotters/current.py @@ -1506,7 +1506,7 @@ def draw_adcp_hodograph( label_far = f"{float(range_vals[i_far]):.0f} m range" label_near = f"{float(range_vals[i_near]):.0f} m range" - from oceanarray.report._plots import _draw_hodograph_pair + from oceanarray.reports._plots import _draw_hodograph_pair fig, axes = plt.subplots(2, 2, figsize=(params.W_FULL, 9)) fig.subplots_adjust(hspace=0.55, wspace=0.45) diff --git a/oceanarray/plotters/timeseries.py b/oceanarray/plotters/timeseries.py index abc3d50..2d0d852 100644 --- a/oceanarray/plotters/timeseries.py +++ b/oceanarray/plotters/timeseries.py @@ -255,7 +255,7 @@ def draw_grid_velocity_stacked(ds: "xr.Dataset") -> "Optional[plt.Figure]": """ import matplotlib.pyplot as plt - from ..report._plots import _velocity_panel_style + from ..reports._plots import _velocity_panel_style vel_vars = [ "east_velocity", diff --git a/oceanarray/report/__init__.py b/oceanarray/reports/__init__.py similarity index 100% rename from oceanarray/report/__init__.py rename to oceanarray/reports/__init__.py diff --git a/oceanarray/report/_array.py b/oceanarray/reports/_array.py similarity index 100% rename from oceanarray/report/_array.py rename to oceanarray/reports/_array.py diff --git a/oceanarray/report/_css.py b/oceanarray/reports/_css.py similarity index 100% rename from oceanarray/report/_css.py rename to oceanarray/reports/_css.py diff --git a/oceanarray/report/_encode.py b/oceanarray/reports/_encode.py similarity index 100% rename from oceanarray/report/_encode.py rename to oceanarray/reports/_encode.py diff --git a/oceanarray/report/_grid.py b/oceanarray/reports/_grid.py similarity index 100% rename from oceanarray/report/_grid.py rename to oceanarray/reports/_grid.py diff --git a/oceanarray/report/_html_helpers.py b/oceanarray/reports/_html_helpers.py similarity index 100% rename from oceanarray/report/_html_helpers.py rename to oceanarray/reports/_html_helpers.py diff --git a/oceanarray/report/_instrument.py b/oceanarray/reports/_instrument.py similarity index 100% rename from oceanarray/report/_instrument.py rename to oceanarray/reports/_instrument.py diff --git a/oceanarray/report/_mooring.py b/oceanarray/reports/_mooring.py similarity index 100% rename from oceanarray/report/_mooring.py rename to oceanarray/reports/_mooring.py diff --git a/oceanarray/report/_pdf.py b/oceanarray/reports/_pdf.py similarity index 98% rename from oceanarray/report/_pdf.py rename to oceanarray/reports/_pdf.py index e68b0c7..fa0dfe0 100644 --- a/oceanarray/report/_pdf.py +++ b/oceanarray/reports/_pdf.py @@ -1,6 +1,6 @@ """Combine a mooring's per-report HTML files into a single A4 PDF. -The HTML reports written by :class:`~oceanarray.report._mooring.MooringReport` +The HTML reports written by :class:`~oceanarray.reports._mooring.MooringReport` are the single source of truth. This module post-processes those files with WeasyPrint — it does not touch report generation or the Jinja templates. Print layout (A4 page size, margins, page numbers, page-break avoidance, hidden nav diff --git a/oceanarray/report/_plots.py b/oceanarray/reports/_plots.py similarity index 100% rename from oceanarray/report/_plots.py rename to oceanarray/reports/_plots.py diff --git a/oceanarray/report/_recovery_table.py b/oceanarray/reports/_recovery_table.py similarity index 100% rename from oceanarray/report/_recovery_table.py rename to oceanarray/reports/_recovery_table.py diff --git a/oceanarray/report/_stack.py b/oceanarray/reports/_stack.py similarity index 100% rename from oceanarray/report/_stack.py rename to oceanarray/reports/_stack.py diff --git a/pyproject.toml b/pyproject.toml index 213efa0..32fd00b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,14 +143,14 @@ ignore = [ "oceanarray/tools/rapid_interp.py" = ["ANN", "D", "F841", "SLF001", "TRY003", "BLE001"] # legacy physics code; rewrite deferred # Report generators: every function wraps rendering in try/except → None on failure. # This is the established pattern for keeping HTML reports partially working on bad data. -"oceanarray/report/_plots.py" = ["BLE001", "TRY300", "TRY301", "TRY003"] +"oceanarray/reports/_plots.py" = ["BLE001", "TRY300", "TRY301", "TRY003"] # Vendored byte-identical from ../ctdcast (shared report foundation, spec §9); kept # identical for the planned cross-repo hash check, so lint exemptions live here rather # than as edits that would fork the copy. Same try/except-to-None pattern as _plots.py. -"oceanarray/report/_encode.py" = ["TRY301", "TRY003", "D413"] -"oceanarray/report/_css.py" = ["D413"] # vendored; docstring section-blank-line style -"oceanarray/report/_html_helpers.py" = ["BLE001", "TRY300"] -"oceanarray/report/_grid.py" = ["BLE001", "TRY300"] -"oceanarray/report/_instrument.py" = ["BLE001", "TRY300", "ARG001"] -"oceanarray/report/_mooring.py" = ["BLE001", "TRY003", "TRY300"] -"oceanarray/report/_stack.py" = ["BLE001", "TRY300", "ARG001", "F841"] +"oceanarray/reports/_encode.py" = ["TRY301", "TRY003", "D413"] +"oceanarray/reports/_css.py" = ["D413"] # vendored; docstring section-blank-line style +"oceanarray/reports/_html_helpers.py" = ["BLE001", "TRY300"] +"oceanarray/reports/_grid.py" = ["BLE001", "TRY300"] +"oceanarray/reports/_instrument.py" = ["BLE001", "TRY300", "ARG001"] +"oceanarray/reports/_mooring.py" = ["BLE001", "TRY003", "TRY300"] +"oceanarray/reports/_stack.py" = ["BLE001", "TRY300", "ARG001", "F841"] diff --git a/tests/conftest.py b/tests/conftest.py index 9825ef7..8173029 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,11 +28,11 @@ def _raise_on_plot_error(monkeypatch): """Make figure-generation failures raise during tests instead of vanishing. - Flips :data:`oceanarray.report._plots.RAISE_ON_PLOT_ERROR` on for the + Flips :data:`oceanarray.reports._plots.RAISE_ON_PLOT_ERROR` on for the duration of every test so a broken figure surfaces as a test failure rather than a silently-absent panel. Reverted automatically by ``monkeypatch``. """ - from oceanarray.report import _plots + from oceanarray.reports import _plots monkeypatch.setattr(_plots, "RAISE_ON_PLOT_ERROR", True, raising=False) diff --git a/tests/integration/test_pipeline.py b/tests/integration/test_pipeline.py index 75a9baf..18f6287 100644 --- a/tests/integration/test_pipeline.py +++ b/tests/integration/test_pipeline.py @@ -211,7 +211,7 @@ class TestInstrumentReport: @pytest.fixture(autouse=True) def run(self, proc_root_stage3_fresh): - from oceanarray.report import MooringReport + from oceanarray.reports import MooringReport reporter = MooringReport(proc_dir=str(proc_root_stage3_fresh)) result = reporter.generate( diff --git a/tests/integration/test_recovery_table.py b/tests/integration/test_recovery_table.py index 9748b99..8a9e6a9 100644 --- a/tests/integration/test_recovery_table.py +++ b/tests/integration/test_recovery_table.py @@ -9,7 +9,7 @@ missing-variable guards that only surface on a real NetCDF. """ -from oceanarray.report._recovery_table import generate_recovery_table +from oceanarray.reports._recovery_table import generate_recovery_table MOORING = "dune2_1_2026" diff --git a/tests/unit/test_array.py b/tests/unit/test_array.py index 3c2d806..0934356 100644 --- a/tests/unit/test_array.py +++ b/tests/unit/test_array.py @@ -1,4 +1,4 @@ -"""Unit tests for pure helper functions in oceanarray.report._array. +"""Unit tests for pure helper functions in oceanarray.reports._array. Tests cover _parse_decdeg, _lat_lon_from_cfg, _count_instruments, and _build_type_summary — all functions with no I/O dependencies that can be @@ -7,7 +7,7 @@ import pytest -from oceanarray.report._array import ( +from oceanarray.reports._array import ( _build_type_summary, _count_instruments, _lat_lon_from_cfg, diff --git a/tests/unit/test_paths.py b/tests/unit/test_paths.py index 5661c0c..3c42ef1 100644 --- a/tests/unit/test_paths.py +++ b/tests/unit/test_paths.py @@ -65,7 +65,7 @@ def test_all_call_sites_agree(): """ from oceanarray.processors.stage3 import _safe_serial as s3 from oceanarray.processors.helpers import _safe_serial as h - from oceanarray.report._html_helpers import _safe_serial as r + from oceanarray.reports._html_helpers import _safe_serial as r raw = "16430, R01-024" results = {safe_serial(raw), s3(raw), h(raw), r(raw)} diff --git a/tests/unit/test_pdf_report.py b/tests/unit/test_pdf_report.py index c3b4b21..f61435a 100644 --- a/tests/unit/test_pdf_report.py +++ b/tests/unit/test_pdf_report.py @@ -1,4 +1,4 @@ -"""Unit tests for oceanarray/report/_pdf.py (HTML-to-PDF combination). +"""Unit tests for oceanarray/reports/_pdf.py (HTML-to-PDF combination). `_ordered_report_files` is pure and tested without WeasyPrint; the render test is gated behind an importorskip because WeasyPrint is an optional extra. @@ -9,8 +9,8 @@ import pytest import yaml -from oceanarray.report import MooringReport, combine_mooring_pdf -from oceanarray.report._pdf import _ordered_report_files +from oceanarray.reports import MooringReport, combine_mooring_pdf +from oceanarray.reports._pdf import _ordered_report_files # Minimal mooring YAML that MooringReport.generate() can render a summary from # (mirrors the fixture in test_report.py::TestMooringReport). @@ -185,7 +185,7 @@ def test_all_pdf_best_effort_but_explicit_pdf_fails( self, tmp_path: Path, monkeypatch ) -> None: """If the PDF can't be built: `--all` keeps HTML success (0); `--pdf` fails (1).""" - import oceanarray.report as report_pkg + import oceanarray.reports as report_pkg from oceanarray.cli import build_parser def _boom(*_a, **_k): diff --git a/tests/unit/test_plot_guard.py b/tests/unit/test_plot_guard.py index 4fee934..f892b76 100644 --- a/tests/unit/test_plot_guard.py +++ b/tests/unit/test_plot_guard.py @@ -11,7 +11,7 @@ import matplotlib.pyplot as plt import pytest -from oceanarray.report import _plots +from oceanarray.reports import _plots # --------------------------------------------------------------------------- diff --git a/tests/unit/test_recovery_table.py b/tests/unit/test_recovery_table.py index dc2db2b..6af85af 100644 --- a/tests/unit/test_recovery_table.py +++ b/tests/unit/test_recovery_table.py @@ -1,10 +1,10 @@ -"""Unit tests for the pure helpers in oceanarray.report._recovery_table. +"""Unit tests for the pure helpers in oceanarray.reports._recovery_table. These are string/number formatters with no I/O — tested directly (the HTML render path is exercised by the integration test). """ -from oceanarray.report._recovery_table import ( +from oceanarray.reports._recovery_table import ( _fmt_drift, _instrument_label, _interval_s, diff --git a/tests/unit/test_report.py b/tests/unit/test_report.py index bd52594..2f9ad3f 100644 --- a/tests/unit/test_report.py +++ b/tests/unit/test_report.py @@ -1,4 +1,4 @@ -"""Unit tests for oceanarray/report/ package. +"""Unit tests for oceanarray/reports/ package. Three test classes: TestHtmlHelpers — pure functions in _html_helpers.py @@ -13,7 +13,7 @@ import yaml from pathlib import Path -from oceanarray.report._html_helpers import ( +from oceanarray.reports._html_helpers import ( _duration_str, _fmt_dt, _fmt_minmax, @@ -26,7 +26,7 @@ _safe_serial, _stage_files, ) -from oceanarray.report._mooring import MooringReport +from oceanarray.reports._mooring import MooringReport # --------------------------------------------------------------------------- @@ -461,7 +461,7 @@ def mooring_setup(self, tmp_path): # generate_instrument_pages def test_generate_instrument_pages_creates_html(self, mooring_setup): - from oceanarray.report._instrument import generate_instrument_pages + from oceanarray.reports._instrument import generate_instrument_pages setup = mooring_setup report = MooringReport(proc_dir=str(setup["proc_dir"].parent)) @@ -484,7 +484,7 @@ def test_generate_instrument_pages_creates_html(self, mooring_setup): assert len(pages) >= 1 def test_generate_instrument_pages_html_contains_serial(self, mooring_setup): - from oceanarray.report._instrument import generate_instrument_pages + from oceanarray.reports._instrument import generate_instrument_pages setup = mooring_setup report = MooringReport(proc_dir=str(setup["proc_dir"].parent)) @@ -508,7 +508,7 @@ def test_generate_instrument_pages_html_contains_serial(self, mooring_setup): def test_generate_instrument_pages_no_nc(self, tmp_path): """Should complete without error even when no NC files exist.""" - from oceanarray.report._instrument import generate_instrument_pages + from oceanarray.reports._instrument import generate_instrument_pages proc_dir = tmp_path / "proc" / "TEST_M1" proc_dir.mkdir(parents=True) @@ -548,7 +548,7 @@ def test_generate_instrument_pages_no_nc(self, tmp_path): # generate_stack_page def test_generate_stack_page_creates_html(self, mooring_setup): - from oceanarray.report._stack import generate_stack_page + from oceanarray.reports._stack import generate_stack_page setup = mooring_setup stack_nc = setup["proc_dir"] / "TEST_M1_stack.nc" @@ -569,7 +569,7 @@ def test_generate_stack_page_creates_html(self, mooring_setup): assert out.stat().st_size > 0 def test_generate_stack_page_html_structure(self, mooring_setup): - from oceanarray.report._stack import generate_stack_page + from oceanarray.reports._stack import generate_stack_page setup = mooring_setup stack_nc = setup["proc_dir"] / "TEST_M1_stack.nc" @@ -592,7 +592,7 @@ def test_generate_stack_page_html_structure(self, mooring_setup): # generate_grid_page def test_generate_grid_page_creates_html(self, mooring_setup): - from oceanarray.report._grid import generate_grid_page + from oceanarray.reports._grid import generate_grid_page setup = mooring_setup grid_nc = setup["proc_dir"] / "TEST_M1_grid.nc" @@ -613,7 +613,7 @@ def test_generate_grid_page_creates_html(self, mooring_setup): assert out.stat().st_size > 0 def test_generate_grid_page_html_contains_mooring(self, mooring_setup): - from oceanarray.report._grid import generate_grid_page + from oceanarray.reports._grid import generate_grid_page setup = mooring_setup grid_nc = setup["proc_dir"] / "TEST_M1_grid.nc" diff --git a/tests/unit/test_report_golden.py b/tests/unit/test_report_golden.py index 3864f98..224e946 100644 --- a/tests/unit/test_report_golden.py +++ b/tests/unit/test_report_golden.py @@ -21,7 +21,7 @@ import pytest -from oceanarray.report import MooringReport +from oceanarray.reports import MooringReport from oceanarray.utilities import _safe_rel _FIXTURES = pathlib.Path(__file__).resolve().parents[1] / "fixtures" diff --git a/tests/unit/test_report_tokens.py b/tests/unit/test_report_tokens.py index 66c9f8f..c34bac8 100644 --- a/tests/unit/test_report_tokens.py +++ b/tests/unit/test_report_tokens.py @@ -19,7 +19,7 @@ import pytest from oceanarray.config import report_tokens as tok -from oceanarray.report import _encode +from oceanarray.reports import _encode # ---------------------------------------------------------------------------