flowfreq 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. flowfreq-0.8.0/CHANGELOG.md +365 -0
  2. flowfreq-0.8.0/LICENSE +21 -0
  3. flowfreq-0.8.0/MANIFEST.in +11 -0
  4. flowfreq-0.8.0/PKG-INFO +344 -0
  5. flowfreq-0.8.0/README.md +303 -0
  6. flowfreq-0.8.0/flowfreq/__init__.py +272 -0
  7. flowfreq-0.8.0/flowfreq/_detrat.py +202 -0
  8. flowfreq-0.8.0/flowfreq/_mse_ema.py +273 -0
  9. flowfreq-0.8.0/flowfreq/_p3_moments.py +399 -0
  10. flowfreq-0.8.0/flowfreq/_var_emab.py +276 -0
  11. flowfreq-0.8.0/flowfreq/_var_mom.py +309 -0
  12. flowfreq-0.8.0/flowfreq/batch.py +129 -0
  13. flowfreq-0.8.0/flowfreq/bulletin17c.py +2076 -0
  14. flowfreq-0.8.0/flowfreq/cli.py +157 -0
  15. flowfreq-0.8.0/flowfreq/core.py +474 -0
  16. flowfreq-0.8.0/flowfreq/data/gage_attributes.csv +4 -0
  17. flowfreq-0.8.0/flowfreq/engine.py +231 -0
  18. flowfreq-0.8.0/flowfreq/flowio.py +133 -0
  19. flowfreq-0.8.0/flowfreq/fortran_engine.py +597 -0
  20. flowfreq-0.8.0/flowfreq/freq_plot.py +669 -0
  21. flowfreq-0.8.0/flowfreq/hydrograph.py +380 -0
  22. flowfreq-0.8.0/flowfreq/lowflow.py +721 -0
  23. flowfreq-0.8.0/flowfreq/peakfqr/__init__.py +41 -0
  24. flowfreq-0.8.0/flowfreq/plots.py +255 -0
  25. flowfreq-0.8.0/flowfreq/py.typed +0 -0
  26. flowfreq-0.8.0/flowfreq/qppq.py +937 -0
  27. flowfreq-0.8.0/flowfreq/regime.py +1266 -0
  28. flowfreq-0.8.0/flowfreq/report.py +253 -0
  29. flowfreq-0.8.0/flowfreq/streamstats.py +1314 -0
  30. flowfreq-0.8.0/flowfreq/transpose.py +1319 -0
  31. flowfreq-0.8.0/flowfreq/usgs.py +1085 -0
  32. flowfreq-0.8.0/flowfreq/validation/__init__.py +22 -0
  33. flowfreq-0.8.0/flowfreq/validation/benchmarks.py +300 -0
  34. flowfreq-0.8.0/flowfreq/validation/comparisons.py +317 -0
  35. flowfreq-0.8.0/flowfreq/validation/data/big_sandy_03606500.json +157 -0
  36. flowfreq-0.8.0/flowfreq/validation/reference.py +352 -0
  37. flowfreq-0.8.0/flowfreq/validation/reports.py +88 -0
  38. flowfreq-0.8.0/flowfreq/workflow.py +574 -0
  39. flowfreq-0.8.0/flowfreq.egg-info/PKG-INFO +344 -0
  40. flowfreq-0.8.0/flowfreq.egg-info/SOURCES.txt +44 -0
  41. flowfreq-0.8.0/flowfreq.egg-info/dependency_links.txt +1 -0
  42. flowfreq-0.8.0/flowfreq.egg-info/entry_points.txt +2 -0
  43. flowfreq-0.8.0/flowfreq.egg-info/requires.txt +18 -0
  44. flowfreq-0.8.0/flowfreq.egg-info/top_level.txt +1 -0
  45. flowfreq-0.8.0/pyproject.toml +254 -0
  46. flowfreq-0.8.0/setup.cfg +4 -0
@@ -0,0 +1,365 @@
1
+ # Changelog
2
+
3
+ All notable changes to FlowFreq (formerly HydroLib) are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.8.0]
11
+
12
+ ### Added
13
+ - **`flowfreq.streamstats`** (Phase 1) -- USGS StreamStats watershed delineation and
14
+ basin-characteristics retrieval for a pour point: `pourpoint` snap, `ss-delineate`
15
+ `delineate/sshydro`, `ss-hydro`, per `docs/STREAMSTATS_MODULE_DESIGN.md`. Validates
16
+ every response against what it's supposed to contain rather than trusting a 200 (an
17
+ unsnappable point returns HTTP 200 with a `WarningMsg` string rather than an error);
18
+ typed/indexable characteristics with provenance; an offline-capable cache; a batch
19
+ entry point that returns results and failures side by side rather than aborting on
20
+ one bad point. Confirmed against the live service, reproducing the design doc's
21
+ published values exactly. Flow-statistics regression estimation (NSS) is deliberately
22
+ out of scope for this phase, and so is the watershed polygon itself --
23
+ `WatershedCharacteristics.polygon_geojson` is always `None`; no call in the verified
24
+ protocol returns it, and getting it is an open question for later.
25
+ - **`flowfreq.streamstats`** (Phase 2) -- NSS (National Streamflow Statistics)
26
+ flow-statistic estimation from real basin characteristics, per
27
+ `docs/STREAMSTATS_NSS_ADDENDUM.md`: `list_statistic_groups()`,
28
+ `estimate_flow_statistics()`, `batch_estimate_flow_statistics()`. Every estimate
29
+ carries NSS's own regression-equation string and a resolved citation
30
+ (title/author/DOI). Validates every parameter against the region's own valid range
31
+ *before* submission -- confirmed live that NSS returns a plausible, silently-wrong
32
+ extrapolated value for an out-of-range input with no warning at all. Region
33
+ selection across NSS's several regressionRegions per state is not automatic (no
34
+ available call filters by location without a watershed polygon, which Phase 1 does
35
+ not produce) -- every geographically-plausible region is returned, unlabelled as to
36
+ which is geographically correct; picking it is the caller's responsibility.
37
+
38
+ ### Fixed
39
+ - **`USGSgage.download_daily_flow`**: the timeout was hardcoded at 30s even after an
40
+ earlier fix made a full period-of-record request (tens of thousands of rows for a
41
+ long-running site) the default -- now configurable, default 60s. The default end date
42
+ used local wall-clock time rather than UTC, so a host clock behind UTC could silently
43
+ request a narrower range than intended; now computed in UTC. `start_date`/`end_date`
44
+ are now validated (parseable, `start_date` not after `end_date`) before any request is
45
+ sent, rather than reaching NWIS unvalidated.
46
+
47
+ ## [0.7.0]
48
+
49
+ ### Added
50
+ - **`flowfreq.transpose`** -- moving a computed statistic from a gaged donor basin to a
51
+ nearby ungaged target by drainage-area ratio, `Q_t(p) = Q_d(p) * (A_t/A_d)**b(p)`, with
52
+ `b(p)` from the applicable published regression rather than assumed to be 1. Three
53
+ functions, deliberately separate: `transpose_frequency` (floods),
54
+ `transpose_duration` (flow-duration statistics) and `transpose_low_flow`.
55
+
56
+ The failure mode this is built around is silent: a wrong exponent does not raise, it
57
+ returns a plausible, monotone, right-order-of-magnitude discharge that is wrong by
58
+ 10-30%. So `RegressionExponents` will not construct without a citation, every quantile
59
+ records whether its exponent was published, interpolated or extrapolated, and
60
+ `TransposedResults` deliberately carries no LP3 moments -- no distribution was fitted at
61
+ the target, and reporting the donor's would invent a fit nobody performed.
62
+
63
+ `probability_kind` ("aep" / "exceedance" / "non_exceedance") makes the category error
64
+ unreachable rather than merely documented: a flood regression's exponent describes flood
65
+ response and says nothing about 7Q10, so handing one to `transpose_low_flow` raises.
66
+ Separate functions were not enough on their own, because the *arguments* were still
67
+ interchangeable.
68
+
69
+ Transposed curves are checked for monotonicity in both directions and raise if they
70
+ invert. That was a real defect found during development: with a groundwater-dominated
71
+ donor (flat dry end) and an exponent set falling toward the dry end -- the combination
72
+ the duration literature actually describes -- `transpose_duration` could return
73
+ `Q99 > Q95` at area ratios inside the supported band.
74
+
75
+ - **`flowfreq.qppq`** -- QPPQ daily-series transfer through two flow-duration curves, with
76
+ an invertible empirical `FlowDurationCurve`, seasonal curve construction, donor ranking,
77
+ goodness-of-fit and a leave-one-out harness.
78
+
79
+ Includes a measured result that contradicts the obvious fix. QPPQ assumes donor and
80
+ target sit at the same position in their own curves on the same day; in a snowmelt basin
81
+ a higher target melts later, breaking that systematically. Grouping the curves by season
82
+ is the intuitive remedy and *makes it worse*: on a 25-day imposed offset, log-NSE was
83
+ 0.273 annual, 0.040 for three seasons, 0.605 monthly, and 0.895 once the donor series was
84
+ lagged. A coarse season is longer than the offset being corrected. `estimate_donor_lag`
85
+ recovers the offset from rank correlation; `center_of_timing` gives the route for a
86
+ target with no record, since melt timing regresses on basin elevation.
87
+
88
+ `performance()` reports NSE, log-NSE, KGE and dry-end bias together, because an estimate
89
+ that is exact through the freshet and wrong by 10x in September still scores NSE > 0.99.
90
+
91
+ - **`regime.flow_duration_curve`** -- duration statistics were previously reachable only as
92
+ a by-product of drawing a figure, at nine hardcoded percentiles.
93
+ `Hydrograph.plot_flow_duration_curve` now delegates to it, so the table it has always
94
+ returned is one computation rather than two that can drift.
95
+
96
+ ### Fixed
97
+ - `fortran_engine.build_emafit_arrays` reused the scalar loop-local names `ql`/`qu`/`tl`
98
+ for the arrays of the same name later in the function -- harmless at runtime, a real
99
+ mypy error.
100
+
101
+ ## [0.6.1]
102
+
103
+ ### Added
104
+ - `plot_peak_flows_with_thresholds` gained a `yscale: str = "log"` parameter (same convention
105
+ `plot_frequency_curve` already uses), the last gap against `flowfreq-app`'s
106
+ `plot_peak_timeseries` -- a per-plot linear/log toggle the app exposes as a real sidebar
107
+ control. Verified live that switching the app over loses nothing numerically: the
108
+ quantile-line values this function computes analytically from `lp3_params` match
109
+ `run_ffa`'s actual `quantile_df["Flow (cfs)"]` to 0.0% (uncensored and censored sites both
110
+ checked, including the app's full return-period list), and `core.log_pearson3_cdf` matches
111
+ the app's own max-peak-recurrence formula to ~1e-15.
112
+
113
+ ## [0.6.0]
114
+
115
+ ### Added
116
+ - `plot_peak_flows_with_thresholds`'s `mgbt_threshold` now also draws peaks below the cut as
117
+ hollow outline bars, matching what `flowfreq-app`'s own `plot_peak_timeseries` did that this
118
+ function previously didn't -- the third and last of the three features the app carried
119
+ (0.5.0 moved the other two, return-period lines and the max-peak annotation). A new
120
+ `mgbt_threshold_source` argument labels the threshold line's legend entry (`"override"` vs.
121
+ the default `"MGBT"`), matching the app's own PILF-source distinction. This closes the plot
122
+ dedupe: `flowfreq-app` can now switch to this function and delete its own copy entirely.
123
+
124
+ ### Fixed
125
+ - `fortran_engine.build_emafit_arrays` reused the scalar loop-local names `ql`/`qu`/`tl` for
126
+ the final `ql`/`qu`/`tl` arrays later in the same function -- harmless at runtime (Python
127
+ doesn't care), but a real mypy violation (`error: Incompatible types in assignment`).
128
+ Renamed the loop-locals to `row_ql`/`row_qu`/`row_tl`.
129
+ - **`make clean` never wiped `.mypy_cache`**, which is how the error above shipped in 0.5.0
130
+ despite `clean-verify` passing repeatedly: mypy's incremental cache can mask a real error on
131
+ a file it already has a (stale, error-free) entry for, the exact failure mode
132
+ `clean-verify` exists to rule out for the rest of the tree. `clean` now removes it too.
133
+
134
+ ## [0.5.0]
135
+
136
+ ### Added
137
+ - **The vendored Fortran as a selectable analysis engine.** `Bulletin17C.run_analysis(engine=)`
138
+ accepts `"fortran"` alongside the default `"native"` (which stays the default forever); the
139
+ real feature is `flowfreq.workflow.compare_engines`, which runs both and returns an
140
+ `EngineComparisonReport` with `.max_quantile_deviation_pct` and `.to_markdown()`, and the
141
+ `flowfreq compare` CLI subcommand built on it. Both require the built f2py extension and
142
+ raise the library's existing, actionable `ImportError` rather than falling back to committed
143
+ golden files -- a comparison meant for a LOMR/CLOMR submittal has to mean "measured against
144
+ PeakFQ 8.1.0 just now," not "replayed from a file in this repository" (`docs/
145
+ FORTRAN_ENGINE_DESIGN.md` section 9). New module `flowfreq/fortran_engine.py`: a library-side
146
+ interval builder translating a `Bulletin17C` input set into `emafitpr`'s `ql/qu/tl/tu/dtype`
147
+ arrays, following `siteQT` (`vendor/peakfqr/R/readInputs.R`) rather than the parity suite's
148
+ test-only builder. Handles gap years with and without a declared perception threshold (the
149
+ worked example in the design doc: site 12363000's at-site skew is +0.435 omitting the four
150
+ unmeasured years, +0.250 censoring them at the lower threshold -- both reproduced exactly
151
+ through the new builder), historic-peak flagging restricted to the USGS historic flag rather
152
+ than the whole historical period, zero flows floored at `Qmin = 1e-20` (peakfq has no special
153
+ zero-flow case), a user-supplied PILF threshold, and overlapping perception-threshold periods
154
+ (last-declared wins, per `siteQT`'s own documented priority rule). The adapter from
155
+ `ReferenceResult` to `FrequencyResults` never synthesizes a field the Fortran did not report --
156
+ `ema_iterations`/`ema_converged` are `None`, not a guess. Verified live against the built
157
+ extension on all four parity sites: Big Sandy 0.0587%, Powder River 0.1003%, site 12363000
158
+ 0.1057% (matching the design doc's own worked example), all under tolerance; Cains Coulee
159
+ 9.741%, correctly surfaced as a failure -- it is the site carrying the pre-existing, still-open
160
+ `skew_weighted` residual (see P3 below), not a defect in this feature.
161
+ - Tests for the four modules that had none: `hydrograph.py`, `plots.py`, `batch.py`, and
162
+ `cli.py` (the last covering `validate`/`benchmark` and `compare`'s extension-agnostic paths;
163
+ `compare`'s Fortran-backed happy path lives in `tests/fortran_parity/test_live_cli_compare.py`
164
+ instead). Writing the `batch.py` tests surfaced a real, pre-existing defect, not introduced
165
+ here and not fixed by this release: `batch.run_multi_site` passes NWIS records straight from
166
+ `usgs.fetch_nwis_batch` (plain dicts) into `B17CEngine.fit`, which reads `.flow` as an
167
+ attribute -- every real multi-site call has therefore been failing per-site, silently, caught
168
+ by a broad `except Exception` and turned into `{"error": ...}`. Pinned as
169
+ `tests/test_batch.py::TestAnalyzeSites::test_real_fetch_output_shape_is_analyzable`,
170
+ `xfail(strict=True)`, so it stops the build the moment someone's fix makes it pass.
171
+ - `plot_peak_flows_with_thresholds` gained opt-in return-period reference lines and a max-peak
172
+ recurrence annotation via a new `lp3_params=(mean_log, std_log, skew)` argument -- the
173
+ library half of retiring `flowfreq-app`'s own duplicate plotting code. The app side (deleting
174
+ its local copy) is a separate, later change gated on a release and a pin bump.
175
+
176
+ ## [0.4.0]
177
+
178
+ ### Added
179
+ - mypy runs in CI, enforced on the modules that already pass; the rest are
180
+ exempted individually in `pyproject.toml` so the debt is countable and shrinks
181
+ by deleting a stanza. The library ships `py.typed`, so downstream checkers
182
+ trust these annotations -- nothing verified them before.
183
+ - `make cov` and a CI coverage step. `pytest-cov` had been a declared dev
184
+ dependency that nothing invoked.
185
+ - `make clean-verify`, which wipes build artifacts and every `__pycache__`
186
+ before running the gate, so a green result reflects the tree rather than
187
+ whatever was left lying around.
188
+
189
+ ### Changed
190
+ - **`B17CEngine.fit` now uses the Bulletin 17C Eq. 7-2 station skew. The
191
+ numbers this public API returns have moved.** It computed
192
+ `((x - mean)**3).mean() / std**3`, the biased population coefficient, while
193
+ `Bulletin17C.run_analysis` in this same library used the unbiased sample
194
+ estimator `n * sum((x - mean)**3) / ((n-1)(n-2) * std**3)`. The two differ by
195
+ `n**2 / ((n-1)(n-2))` -- 7.2% at n=44, 39% at n=10, and short records are
196
+ ordinary in flood frequency work.
197
+
198
+ Measured on Big Sandy (n=44): station skew moves from -0.1748 to -0.1874,
199
+ which is now exactly what the Bulletin 17C path reports for the same record.
200
+ Quantiles move **Q2 +0.13%, Q10 -0.10%, Q100 -0.57%, Q500 -0.93%**. Anything
201
+ derived from `B17CEngine` moves with it, including `batch.batch_summary_table`
202
+ and `plots`. If you have reported a discharge from this class, it will not
203
+ reproduce under 0.4.0 -- pin `v0.3.0` if you need the old figures, and expect
204
+ the 0.4.0 value to be the defensible one.
205
+
206
+ `Bulletin17C` is unaffected: it was always correct, and the release exists to
207
+ make the two agree. Recorded in 0.3.0's test suite as a strict xfail; the
208
+ three tests that replace it in `tests/test_engine.py` now guard against a
209
+ revert, one of them naming the old estimator explicitly.
210
+
211
+ One deliberate difference remains: `Bulletin17C` clips the station skew to
212
+ ±3.0 (`MAX_ABS_SKEW`) and `B17CEngine` does not, so the two can still diverge
213
+ on a record with extreme skew. That is a separate question from the estimator
214
+ and was left alone.
215
+
216
+ - `flowfreq.freq_plot.plot_frequency_curve_streamlit` is now
217
+ `plot_frequency_curve`. The old name remains as an alias, so pinned consumers
218
+ keep working; it can go once none use it. The module imports matplotlib and
219
+ returns a `Figure` -- the suffix was always a misnomer and became misleading
220
+ once the app moved to its own repository.
221
+
222
+ ### Fixed
223
+ - Four public signatures annotated names their modules never imported, so
224
+ `typing.get_type_hints()` raised `NameError` on `engine.B17CEngine
225
+ .frequency_table`, `batch.batch_summary_table`,
226
+ `freq_plot.plot_peak_flows_with_thresholds` and `Bulletin17C.validate`.
227
+ `from __future__ import annotations` kept it from raising at import, which is
228
+ why it went unnoticed. The first three now resolve; `validate` keeps a
229
+ `TYPE_CHECKING` import to avoid inverting the package's layering, and says so.
230
+
231
+ - `import flowfreq.peakfqr` without the f2py extension built raised a bare
232
+ `ModuleNotFoundError` naming a private submodule. It now explains that the
233
+ extension is built on demand, gives the command and the toolchain, and says
234
+ that nothing else in the library depends on it -- the native EMA is the
235
+ default path. Its docstring also cited `_shared/peakfqr/src/emafit.f`, a path
236
+ that does not exist here; the sources are under `vendor/peakfqr/`.
237
+
238
+ ## [0.3.0]
239
+
240
+ ### Changed
241
+ - **Split into two repositories.** This repo is the analysis library; the Streamlit
242
+ application moved to [pinhead001/flowfreq-app](https://github.com/pinhead001/flowfreq-app),
243
+ which installs this library as a pinned dependency. `app/` and its three test
244
+ modules are gone from here, along with the `smoke` make target and the CI job
245
+ that ran it.
246
+ - `flowfreq.workflow` — new module holding the high-level entry points that used to
247
+ live in the app: `run_ffa`, `compute_skew_tables`, `build_skew_curves_dict`. The
248
+ display formatters stayed with the app.
249
+ - The gage attributes table moved into the package at `flowfreq/data/`, replacing a
250
+ `package-data` entry that reached outside the package and only ever resolved in a
251
+ source checkout.
252
+ - **Renamed to `flowfreq`** — package, import name, distribution and display name.
253
+ `from hydrolib.core import kfactor` is now `from flowfreq.core import kfactor`;
254
+ the console script is `flowfreq`. Historical entries below keep the old name,
255
+ since they describe what shipped at the time.
256
+
257
+ The old name was unusable. Deltares publishes `hydrolib` and `hydrolib-core` on
258
+ PyPI, both installing a top-level `hydrolib/` package, and `hydrolib-core` ships
259
+ `hydrolib/core/` against this project's `hydrolib/core.py`. Installed together
260
+ one silently destroys the other -- in one order this library's entire API
261
+ disappears, in the other neither package imports at all -- and `pip check`
262
+ reports nothing wrong. `flowfreq` names what the library does (flood *and*
263
+ low-flow frequency) and cannot be mistaken for HYDROLIB.
264
+
265
+ ### Added
266
+ - Native Python port of `var_mom` and its dependency tree (`mn2mvarb`/`mse_ema`, `detrat`,
267
+ `VAR_EMAB`/`regmoms`/`ci_ema_m3b`), verified routine-by-routine against the vendored
268
+ peakfq 8.1.0 Fortran. See TODO.md's P3 section for the full account.
269
+ - `MethodOfMoments` now applies the Bulletin 17B conditional-probability adjustment: a
270
+ low-outlier (PILF) threshold — Grubbs-Beck or user-supplied — censors the fit instead of
271
+ only being reported.
272
+
273
+ ### Changed
274
+ - `ExpectedMomentsAlgorithm` confidence intervals are now Cohn's asymmetric bounds
275
+ (`hydrolib._var_emab.var_emab`) instead of the symmetric `log_Q ± z*se` approximation.
276
+ - `ExpectedMomentsAlgorithm`'s at-site EMA moment iteration on censored intervals now uses
277
+ the Fortran-verified truncated-moment code (`hydrolib._p3_moments.m_p3`) and the correct
278
+ bias-correction sample size, closing a real accuracy gap on any record with censored
279
+ intervals (Big Sandy's historical gap years included, not just MGBT-flagged PILFs).
280
+ - Regional skew weighting now includes ADJE's censoring bias adjustment and the Halloween
281
+ determinant ratio (`detrat`), matching peakfq's default `at_site_option`.
282
+
283
+ ### Fixed
284
+ - `hydrolib/peakfqsa/` (a subprocess wrapper around a PeakfqSA binary that does not exist)
285
+ removed; it was mock-tested only. `hydrolib/validation/reference.py` covers what it
286
+ contributed, pointed at references that actually exist.
287
+ - Bare `except:` in `usgs.py` narrowed to the actual failure modes.
288
+ - `analyze_gage()` no longer prints unconditionally to stdout; uses `logging` like the rest
289
+ of the library.
290
+
291
+ ### Removed
292
+ - `hydrolib/peakfqsa/` and its mock-only test suite (see Fixed, above).
293
+
294
+ ---
295
+
296
+ ## [0.2.0] - 2026-08-31
297
+
298
+ ### Added
299
+ - Instantaneous (unit-value) flow retrieval from USGS NWIS
300
+ - Low-flow frequency analysis module (`hydrolib.lowflow`)
301
+ - Annual n-day low-flow frequency with LP3 or lognormal distribution
302
+ - Climatic/water/calendar year definitions
303
+ - Zero-flow-year handling
304
+ - Analytic and bootstrap confidence intervals
305
+ - Flow regime metrics module (`hydrolib.regime`)
306
+ - Richards-Baker flashiness index
307
+ - TQmean metric
308
+ - Baseflow separation (UKIH, Lyne-Hollick, HYSEP variants)
309
+ - Monthly and seasonal flow summaries
310
+ - Diel (sub-daily) variation analysis
311
+ - Within-day flow range and coefficient of variation
312
+ - Timezone-correct local-day grouping
313
+ - Flow series I/O with Parquet backend (`hydrolib.flowio`)
314
+ - Save/load for daily and instantaneous flow data
315
+
316
+ ### Changed
317
+ - Improved EMA algorithm convergence handling for edge cases
318
+ - Documentation vignettes reorganized (Low-Flow & Flow Regime guide)
319
+
320
+ ### Fixed
321
+ - MGBT outlier detection edge case with small sample sizes
322
+ - Flow duration curve calculation precision
323
+
324
+ ---
325
+
326
+ ## [0.1.0] - 2026-01-28
327
+
328
+ ### Added
329
+ - **USGS Data Retrieval** — Download mean daily, annual peak, and instantaneous flow from NWIS
330
+ - **Bulletin 17C Analysis**
331
+ - Expected Moments Algorithm (EMA) — USGS standard method
332
+ - Method of Moments (MOM) fallback
333
+ - Weighted regional skew (MSE weighting per B17C Appendix 6)
334
+ - Multiple Grubbs-Beck test (MGBT) for low outlier detection
335
+ - 90% confidence intervals (5%/95% limits)
336
+ - **Hydrograph Plotting**
337
+ - Daily time series plots
338
+ - Summary hydrographs (day of water year with percentile bands)
339
+ - Flow duration curves
340
+ - **Frequency Curve Plotting**
341
+ - Log-probability axis
342
+ - LP3 fitted curve with confidence interval band
343
+ - Multi-skew overlay (station / weighted / regional)
344
+ - **Streamlit Web Application**
345
+ - Interactive single/multi-gage analysis
346
+ - Regional skew input controls
347
+ - ZIP export (PNG plots, CSV data, LP3 parameters)
348
+ - Multi-gage comparison tables
349
+ - **CLI Tools**
350
+ - `hydrolib validate` — EMA validation against reference fixtures
351
+ - `hydrolib benchmark` — Numerical benchmarking (text/JSON output)
352
+ - **Technical Reports** — Automated Markdown report generation
353
+ - **Validation Framework** — Parity testing against USGS Fortran reference implementation
354
+
355
+ ### Fixed
356
+ - Initial release
357
+
358
+ ---
359
+
360
+ ## Notes on Versioning
361
+
362
+ - **0.x.x** — Pre-release. API may change without warning.
363
+ - **1.0.0** — Stable API. Breaking changes require major version bump.
364
+
365
+ For upgrade guidance, see the [migration guides](docs/) directory.
flowfreq-0.8.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris Nelson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ # Without this file, setuptools' default sdist file-finder pulls in
2
+ # tests/test_*.py individually (matching its built-in test-file heuristic)
3
+ # but not tests/__init__.py, tests/fixtures/, or the tests/fortran_parity/,
4
+ # tests/integration/, tests/validation/ subpackages -- an sdist a user
5
+ # actually tried to test from would fail on the first import. None of it is
6
+ # runnable from an sdist anyway (vendor/ and the fixture data it needs
7
+ # aren't shipped either), so exclude the whole tree rather than ship a
8
+ # partial, broken copy.
9
+ prune tests
10
+
11
+ include CHANGELOG.md