@@ -8,34 +8,54 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88
99## [ Unreleased]
1010
11- Project-infrastructure round. No public-API change; no numerical
12- change. Re-license + docs site + governance scaffolding + CI hardening.
13-
14- ### Changed
15-
16- - ** Re-licensed MIT → Apache 2.0.** `` LICENSE `` is the Apache 2.0
17- text with copyright ``2024-2026 Jae Hoon Seo, Marine Structural
18- Mechanics and Integrity Lab (SMI Lab), Inha University``;
19- `` pyproject.toml `` classifier and README badge updated; the
20- bundled `` _examples/ `` and `` reference_decks/ `` README provenance
21- notes re-state the new licence. The book-citation reference to
22- "MIT Press" in `` mooring.py `` is unrelated to project licensing
23- and is preserved.
24- - ** Upstream-data directories relocated to `` external/ `` ** (issue
25- raised in static review). ``docs/{BModes, OpenFAST_files, MoorPy,
26- RAFT, references}`` → `` external/{...}`` ; the `` docs/`` tree is
27- now reserved for the Sphinx documentation site. `` .gitignore `` ,
28- local development notes, README, RELEASE_CHECKLIST,
29- every case-study `` run.py `` , every visualisation script, the
30- reference-turbine `` build.py `` , and 30 tracked files in total
31- point at `` external/ `` after the rewrite. Integration tests
32- unchanged behaviourally; they skip when the data is absent.
33- - ** `` docs/RELEASE_CHECKLIST.md `` moved to
34- `` https://pybmodes.readthedocs.io/en/latest/release_checklist.html `` ** to slot into the new Sphinx
35- developer-guide toctree.
11+ (nothing yet)
12+
13+ ## [ 1.8.0] — 2026-05-21
14+
15+ Multi-phase architecture refactor + project-infrastructure round. No
16+ public-API change; no numerical change. Three threads landed:
17+
18+ - ** Phase 1** — re-license (MIT → Apache 2.0), Sphinx documentation
19+ site on Read the Docs, governance + CI hardening, strict mypy /
20+ ruff ratchet, unified `` ParseError `` hierarchy, centralised
21+ numerical options dataclasses.
22+ - ** Phase 2** — every `` pybmodes `` CLI subcommand is now a typed
23+ library entry point under :mod:` pybmodes.workflows ` ; `` cli.py ``
24+ shrinks from ~ 1500 to ~ 520 lines and is purely
25+ argparse + delegation. Seven new workflow functions and seven
26+ result dataclasses; thirty-nine direct workflow tests.
27+ - ** Phase 3** — three biggest source files broken up by concern:
28+ `` campbell.py `` (1301 LOC → 7-file `` campbell/ `` sub-package),
29+ `` mooring.py `` (1202 LOC → 6-file `` mooring/ `` sub-package),
30+ `` models/tower.py `` private helpers extracted to
31+ `` models/_shared.py `` (cross-model) and `` models/_platform.py ``
32+ (tower-specific platform-scalar parsers). Every public name
33+ is preserved; existing imports keep working through the
34+ `` __init__.py `` re-export layer.
3635
3736### Added
3837
38+ - ** `` pybmodes.workflows.* `` typed-result library API** (Phase 2 PRs
39+ B1–B3). Seven entry-points, one per CLI subcommand, each returning
40+ a :class:` WorkflowResult ` subclass with `` exit_code `` /
41+ `` messages `` / `` errors `` plus the workflow's typed payload:
42+
43+ - :func:` run_validate ` → :class:` ValidateResult `
44+ - :func:` run_examples_copy ` → :class:` ExamplesResult `
45+ - :func:` run_patch ` → :class:` PatchResult ` (five mutually-
46+ supportive output modes: default in-place, `` backup `` ,
47+ `` output_dir `` , `` dry_run `` , `` diff `` )
48+ - :func:` run_report ` → :class:` ReportResult `
49+ - :func:` run_batch ` → :class:` BatchResult `
50+ - :func:` run_campbell ` → :class:` CampbellWorkflowResult `
51+ - :func:` run_windio ` → :class:` WindioResult ` plus
52+ :func:` discover_windio_inputs ` returning a
53+ :class:` WindioDiscovery ` dataclass.
54+
55+ Lets notebooks and external scripts run the same flows the CLI
56+ uses without `` subprocess `` (the original 0.x workflows-via-stdout
57+ contract is preserved via the slim CLI wrappers).
58+
3959- ** Sphinx documentation site** (`` docs/ `` ) hosted on Read the Docs
4060 (`` .readthedocs.yaml `` ). Flat reStructuredText tree — 13 pages:
4161 `` index `` , `` installation `` , `` quickstart `` , `` theory `` ,
@@ -88,6 +108,99 @@ change. Re-license + docs site + governance scaffolding + CI hardening.
88108 sdist, wheel, and cross-install version sanity; uploads the
89109 dist and docs as artifacts. Pre-tag gate that goes beyond the
90110 per-PR CI.
111+ - ** Unified :class:` pybmodes.io.ParseError ` hierarchy** (Phase 1 PR
112+ A3). Six per-format subclasses
113+ (`` BmiParseError `` , `` ElastoDynParseError `` , `` MoorDynParseError `` ,
114+ `` OutParseError `` , `` SubDynParseError `` , `` WindIOParseError `` )
115+ let library callers catch parse errors precisely. Twenty-nine
116+ regression tests pin hashability and `` __eq__ `` semantics
117+ (Codex caught the original `` @dataclass `` shadowing of
118+ `` __hash__ `` post-merge).
119+ - ** Centralised numerical options dataclasses** (Phase 1 PR A1):
120+ :class:` pybmodes.options.SolverOptions ` ,
121+ :class:` pybmodes.options.FitOptions ` ,
122+ :class:` pybmodes.options.CheckOptions ` . `` frozen=True `` dataclasses
123+ that surface every magic number from `` fem/solver `` ,
124+ `` elastodyn/params `` , and `` checks `` as named, defaulted
125+ parameters.
126+
127+ ### Changed
128+
129+ - ** Re-licensed MIT → Apache 2.0.** `` LICENSE `` is the Apache 2.0
130+ text with copyright ``2024-2026 Jae Hoon Seo, Marine Structural
131+ Mechanics and Integrity Lab (SMI Lab), Inha University``;
132+ `` pyproject.toml `` classifier and README badge updated; the
133+ bundled `` _examples/ `` and `` reference_decks/ `` README provenance
134+ notes re-state the new licence. The book-citation reference to
135+ "MIT Press" in `` mooring.py `` is unrelated to project licensing
136+ and is preserved.
137+ - ** Upstream-data directories relocated to `` external/ `` ** (issue
138+ raised in static review). ``docs/{BModes, OpenFAST_files, MoorPy,
139+ RAFT, references}`` → `` external/{...}`` ; the `` docs/`` tree is
140+ now reserved for the Sphinx documentation site. `` .gitignore `` ,
141+ local development notes, README, RELEASE_CHECKLIST,
142+ every case-study `` run.py `` , every visualisation script, the
143+ reference-turbine `` build.py `` , and 30 tracked files in total
144+ point at `` external/ `` after the rewrite. Integration tests
145+ unchanged behaviourally; they skip when the data is absent.
146+ - ** `` docs/RELEASE_CHECKLIST.md `` moved to
147+ `` https://pybmodes.readthedocs.io/en/latest/release_checklist.html `` ** to slot into the new Sphinx
148+ developer-guide toctree.
149+ - ** Strict mypy expanded** (Phase 1 PR A2). Five additional modules
150+ joined the strict-typing override list:
151+ `` pybmodes.models.tower `` , `` pybmodes.models.blade `` ,
152+ `` pybmodes.campbell `` , `` pybmodes.mac `` , `` pybmodes.io.bmi `` ,
153+ `` pybmodes.options `` .
154+
155+ ### Refactored
156+
157+ - ** CLI subcommands extracted to typed workflow functions** (Phase 2
158+ PRs B1–B3, merged in #69 , #71 , #72 ; #70 is the static-review
159+ P3 follow-up to B1). `` cli.py `` shrinks from ~ 1500 LOC to ~ 520
160+ LOC and is purely argparse + delegation. Every subcommand
161+ (validate / examples / patch / report / batch / campbell /
162+ windio) is now library-callable with a typed result. CLI flag /
163+ exit-code contract preserved byte-for-byte; the existing CLI
164+ smoke tests (`` test_validate.py `` , `` test_examples_cli.py `` ,
165+ `` test_batch.py `` , `` test_report.py `` , `` test_campbell.py `` ,
166+ `` test_windio_cli.py `` ) pass unchanged. Static-review P3 fix on
167+ PR B1: :func:` pybmodes.workflows.run_examples_copy ` now
168+ preserves the accumulated skipped-bundle warning when a later
169+ bundle hits a destination conflict; regression test in
170+ `` test_workflows.py `` pins the contract.
171+ - ** `` pybmodes.campbell `` split into a sub-package** (Phase 3 PR
172+ C1, merged in #73 ). The 1301-LOC monolith becomes seven files —
173+ `` result.py `` (271 LOC, :class:` CampbellResult ` dataclass + NPZ
174+ / CSV serialization), `` _models.py `` (210 LOC, input
175+ dispatcher), `` _classify.py `` (119 LOC, mode-naming
176+ heuristics), `` _mac.py `` (90 LOC, MAC + Hungarian assignment),
177+ `` _sweep.py `` (347 LOC, rotor-speed sweep drivers + public
178+ `` campbell_sweep `` ), `` _plot.py `` (393 LOC, engineering-report
179+ diagram). `` __init__.py `` re-exports every public and underscore-
180+ private name existing tests import by name; no test edits
181+ required.
182+ - ** `` pybmodes.mooring `` split into a sub-package** (Phase 3 PR
183+ C2, merged in #74 ). 1202-LOC monolith → six files —
184+ `` types.py `` (248 LOC, :class:` LineType ` / :class:` Point ` /
185+ :class:` Line ` + :meth:` Line.solve_static ` ), `` system.py `` (608
186+ LOC, :class:` MooringSystem ` + two `` from_* `` parsers),
187+ `` _catenary.py `` (113 LOC, residual + analytical Jacobian for
188+ Jonkman 2007 B-1/B-2 + B-7/B-8), `` _rotation.py `` (41 LOC,
189+ 3-2-1 Euler primitive), `` _moordyn_parser.py `` (255 LOC,
190+ MoorDyn `` .dat `` section / row tokenisers). Public API
191+ (:class:` LineType ` , :class:` Point ` , :class:` Line ` ,
192+ :class:` MooringSystem ` ) byte-identical via `` __init__.py ``
193+ re-exports.
194+ - ** `` models/tower.py `` private helpers extracted** (Phase 3 PR
195+ C3, merged in #75 ). `` _run_validation_and_warn `` →
196+ `` models/_shared.py `` (cross-model; `` RotatingBlade.from_elastodyn ``
197+ was already reaching across, now imports directly).
198+ `` _scan_platform_fields `` + `` _platform_inertia_matrix `` →
199+ `` models/_platform.py `` (tower-specific). `` tower.py `` ends
200+ at 968 LOC (down from 1083) with all three names re-exported
201+ for back-compat. The :class:` Tower ` class stayed together;
202+ a planned mixin-based split was rejected by Plan-agent review
203+ as architecturally hollow.
91204
92205### Fixed
93206
@@ -104,6 +217,11 @@ change. Re-license + docs site + governance scaffolding + CI hardening.
104217 `` axhline `` s) keep the original full-axis comb. Backward-compatible
105218 on `` rpm.min() == 0 `` . Regression test ``test_plot_campbell_blade_
106219 label_anchored_on_curve_for_positive_rpm`` pins the new behaviour.
220+ - ** `` pybmodes.io.ParseError `` hashability** (PR #68 Codex P2
221+ follow-up). The original `` @dataclass `` decorator on the
222+ `` ParseError `` base shadowed `` __hash__ `` to `` None `` , breaking
223+ `` set(exceptions) `` callers. Switched to `` @dataclass(eq=False) ``
224+ with 29 regression tests in `` test_io_errors.py `` .
107225
108226## [ 1.7.0] — 2026-05-18
109227
0 commit comments