fix: Conductivity unit handling: verify in readers, normalise in pipeline - #140
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes silent conductivity unit mismatches (notably SBE37 ASCII reporting S/m vs mS/cm) by having readers verify units against data magnitude while centralizing actual conductivity value conversion in the pipeline’s unit_handling stage, ensuring derived quantities don’t silently end up off by ×10.
Changes:
- Added conductivity unit inference/conversion helpers and corresponding unit tests + real SBE fixtures.
- Introduced
ConductivityNormalizerpipeline handler to normalize conductivity values to canonicalmS cm-1before derivation, plus a derivation-stage guard warning. - Updated unit normalization/knowledge metadata (conductivity canonical unit, temperature expected units) and pipeline profiles; added coverage configuration.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/readers/utils/test_conductivity_units.py | Unit tests for conductivity unit inference/conversion helpers |
| tests/readers/test_sbe_hex_reader.py | Integration tests asserting HEX reader/pipeline conductivity units and value ranges |
| tests/readers/test_sbe_ascii_reader.py | ASCII reader integration tests for conductivity unit reporting and pipeline normalization |
| tests/readers/fixtures/7508_caldip.xmlcon | Added SBE37 XMLCON fixture for HEX integration testing |
| tests/readers/fixtures/7508_caldip_short.hex | Added SBE37 HEX fixture for integration testing |
| tests/readers/fixtures/4049_caldip_short.asc | Added SBE37 ASCII fixture for integration testing |
| tests/pipelines/unit_handling/test_unit_handling_handlers.py | Expanded handler tests; added ConductivityNormalizer coverage |
| tests/pipelines/test_pipeline_profiles.py | Ensures default profile includes conductivity normalization handler |
| seasenselib/readers/utils/conductivity_units.py | New helper for magnitude-based unit inference and conversion to mS cm-1 |
| seasenselib/readers/utils/init.py | Exposes new conductivity helper utilities |
| seasenselib/readers/sbe_cnv_reader.py | Verifies declared conductivity units vs magnitude (warn-only) |
| seasenselib/readers/sbe_ascii_reader.py | Sets SBE37 ASCII conductivity units to S/m and verifies magnitude |
| seasenselib/pipeline/unit_handling/stage.py | Wires in ConductivityNormalizer and records relabels/conversions metadata |
| seasenselib/pipeline/unit_handling/handlers/unit_normalizer.py | Renames conversion records to relabels + adds declared-vs-expected check |
| seasenselib/pipeline/unit_handling/handlers/conductivity_normalizer.py | New handler converting conductivity values to mS cm-1 |
| seasenselib/pipeline/unit_handling/handlers/init.py | Exports ConductivityNormalizer |
| seasenselib/pipeline/derivation/stage.py | Adds guard warning if conductivity arrives in S/m units |
| seasenselib/parameters.py | Updates canonical units for conductivity + temperature-related parameters |
| seasenselib/knowledge/pipeline/unit_handling/unit_normalizations.json | Adds conductivity relabel rules; removes incorrect Sverdrup normalization |
| seasenselib/knowledge/pipeline/unit_handling/expected_units.json | Updates expected conductivity + temperature expected units |
| seasenselib/knowledge/pipeline/metadata_enrichment/parameters_metadata.json | Updates canonical units for conductivity/temperature-related params |
| seasenselib/config/pipeline/full.json | Enables conductivity_normalize in full pipeline profile |
| seasenselib/config/pipeline/default.json | Enables conductivity_normalize in default pipeline profile |
| pyproject.toml | Adds coverage configuration for pytest --cov |
Suppressed comments (1)
seasenselib/readers/utils/conductivity_units.py:93
- The out-of-range error message hardcodes uS/cm as 500–1000000, but the intended uS/cm inference band (per the module docstring and the ranges table) is ~10000+. Keeping this consistent avoids misleading diagnostics when inference fails.
raise ValueError(
f"Cannot infer conductivity unit: median {median:.4g} fits no known "
f"range (S/m 0.5–10, mS/cm 10–1000, uS/cm 500–1000000). "
f"Check the data or supply units explicitly."
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+24
to
+28
| _COND_RANGES: dict[str, tuple[float, float]] = { | ||
| "S/m": (0.5, 10.0), | ||
| "mS/cm": (10.0, 1000.0), | ||
| "uS/cm": (500.0, 1_000_000.0), | ||
| } |
Comment on lines
+16
to
22
| "potential_temperature": "degree_C", | ||
| "power_supply_input": "V", | ||
| "pressure": "dbar", | ||
| "roll": "degrees", | ||
| "speed_of_sound": "m/s", | ||
| "temperature": "K", | ||
| "temperature": "degree_C", | ||
| "up_velocity": "m/s" |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Collaborator
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: ysorge <32022559+ysorge@users.noreply.github.com>
ysorge
self-requested a review
August 10, 2026 23:34
Contributor
Co-authored-by: ysorge <32022559+ysorge@users.noreply.github.com>
ysorge
approved these changes
Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SeaBird SBE37 ASCII files output conductivity in S/m (~3.5), not mS/cm (~35). The values differ by a factor of 10 and are both plausible-looking, so a unit mismatch propagates silently into derived quantities.
Design decision — readers stay clean, pipeline converts: Each reader reports the unit exactly as the instrument file specifies it. SBE37 ASCII outputs S/m; CNV files carry whatever pycnv reads from the channel name. Readers also call
infer_conductivity_unit()to cross-check the declared unit against the actual data magnitude and warn on disagreement, but they do not modify values. The alternative — converting in every reader — would scatter the same mS/cm conversion across a growing number of reader classes. Centralising it in a single pipeline handler makes the rule easy to find, test, and extend to new readers without touching existing ones.Reader changes (verify, don't convert): Readers now call
infer_conductivity_unit()and report the accurate unit from the file. For SBE37 ASCII that is"S/m"; for CNV files it is whatever pycnv parses from the channel name ("mS/cm"or"S/m").New pipeline handler (
ConductivityNormalizer): A newconductivity_normalizehandler in theunit_handlingstage converts all conductivity variables tomS cm-1before the derivation stage runs. It usesinfer_conductivity_unit()+to_mS_cm()from the newreaders/utils/conductivity_units.pyhelper, stores aconductivity_normalised_fromprovenance attribute, and is a no-op if the variable is already inmS cm-1. Enabled by default in thedefaultandfullpipeline profiles. All three unit-change paths — string relabelling (UnitNormalizer), value conversion (ConductivityNormalizer), and pint-based conversion (UnitConverter) — all write intocontext.metadata['unit_conversions'].Derivation guard: The derivation stage now checks all conductivity variables at entry and emits a
UserWarningif any carry"S/m"or"S m-1"units, naming the handler to enable.Unit normalizer improvements: Added relabelling rules
"S/m" → "S m-1"and"mS/cm" → "mS cm-1"tounit_normalizations.json. Added anelifbranch toUnitNormalizer.normalize()so it also compares a declared unit against the expected unit when both are present (previously it only checked when units were absent).Knowledge file updates: Conductivity canonical unit changed from
"S m-1"to"mS cm-1"inparameters.py,parameters_metadata.json, andexpected_units.json. Temperature and potential temperature expected units corrected from"K"to"degree_C"inexpected_units.json. Removed erroneous"Sverdrup/m": "Sv/m"entry fromunit_normalizations.json(Sverdrup is volume transport, not conductance). Temperature units inparameters.pyandparameters_metadata.jsonupdated from"ITS-90, deg C"/"degC"to"degree_C"(CF canonical); a comment records the ITS-90 assumption and links to the CCHDO convention.Coverage config: Added
[tool.coverage.run]and[tool.coverage.report]topyproject.tomlso thatpytest --cov=seasenselibreports onlyseasenselib/lines and omitstests/.New files
seasenselib/readers/utils/conductivity_units.py—infer_conductivity_unit(values, declared)andto_mS_cm(values, from_unit)helpersseasenselib/pipeline/unit_handling/handlers/conductivity_normalizer.py—ConductivityNormalizerhandlertests/readers/utils/test_conductivity_units.py— 13 unit tests for the helperstests/readers/fixtures/4049_caldip_short.asc— SBE37-SM ASCII fixture (SN 4049)tests/readers/fixtures/7508_caldip_short.hex— SBE37-SM hex fixture (SN 7508)tests/readers/fixtures/7508_caldip.xmlcon— companion calibration for 7508 hex