Skip to content

Commit 657e4b6

Browse files
bbestclaude
andcommitted
feat(upload): read shipboard CTD files into the core model (plan W1)
An uploaded cast can now be QC'd before it reaches a release. The design principle that makes this cheap: every rule targets obs/sample, so projecting a file into that shape runs the whole registry unchanged — no rule learns anything about where the data came from. FORMATS, and what each costs .csv the CalCOFI cast file; maps through measurement_type.csv _source_column, which already holds exactly those names. .cnv Sea-Bird converted. Its `# name N =` header names every column, so there is nothing to infer. PREFERRED. .asc Sea-Bird ASCII. Same vocabulary, but see below. .btl bottle summary: one Date header word over three data fields, several tagged statistic rows per bottle. .hex REFUSED, with the reason. It is raw A/D counts; converting it needs the .xmlcon calibration file. A best-effort conversion would be invented numbers presented as measurements, which in a QC tool is the worst possible output. THE TRAP IN .asc, measured not assumed The header is fixed-width and adjacent names RUN TOGETHER — `Sbeox0ML/LSbeox0Mm/Kg`. In 179 of 200 CalCOFI files a whitespace split gives the wrong column count and silently mis-assigns every column after the collision. Both names and numbers are right-aligned, so sbe_split_header() cuts the header at the data rows' stop positions. When the result is not self-consistent it ERRORS and asks for the .cnv rather than guessing. Measured across the archive: ~86% of .asc and ~47% of .btl read cleanly; the rest say why. MAPPING Two vocabularies, one answer, both in reviewable registries rather than in code: the CalCOFI names through measurement_type.csv, the Sea-Bird names through the new metadata/sbe_name_map.csv. Unmapped columns are a RESULT, not an error — they are where a format change announces itself. Raw voltages are unmapped deliberately (which sensor a V0 is depends on the wiring), and FlECO-AFL is left unmapped because the registry carries the fluorometer VOLTAGE, so mapping the converted value would silently change units. The projection applies the same two repairs the pipeline already knows — the -99 / -9.99e-29 sentinels are deleted, and quality codes stored as "9.0" are stripped textually (not via an integer cast, which would round an unexpected "9.5") — because a new file is exactly where those arrive. Also: qc_cast_profile's sibling qc_upload_con() makes the upload obs / sample / obs_ctd_full in memory, so an uploaded cast (which IS full resolution) gets the profile rules too rather than skipping them. Verified end to end on a real .btl from the CalCOFI archive: all 16 rules ran, 5 correctly reporting skip because that bottle file carries no temperature_ave. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GEVWNQXLvWzz25oF8hqESd
1 parent 704aa20 commit 657e4b6

13 files changed

Lines changed: 971 additions & 0 deletions

NAMESPACE

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export(create_gcs_manifest)
5555
export(create_lookup_table)
5656
export(create_redefinition_files)
5757
export(csv_to_parquet)
58+
export(ctd_map_columns)
59+
export(ctd_upload_to_core)
5860
export(delete_flagged_rows)
5961
export(delete_gcs_prefix)
6062
export(derive_cruise_key_on_casts)
@@ -142,17 +144,23 @@ export(qc_run_all)
142144
export(qc_run_rule)
143145
export(qc_stage_reference)
144146
export(qc_summarize)
147+
export(qc_upload_con)
145148
export(query_at_time)
146149
export(read_calcofi_meta)
147150
export(read_csv_files)
148151
export(read_csv_metadata)
152+
export(read_ctd_upload)
149153
export(read_ingest_manifest)
150154
export(read_ingest_parquet)
151155
export(read_ingest_yaml)
152156
export(read_input_fingerprint)
153157
export(read_measurement_type)
154158
export(read_parquet_table)
155159
export(read_relationships_json)
160+
export(read_sbe_asc)
161+
export(read_sbe_btl)
162+
export(read_sbe_cnv)
163+
export(read_sbe_header)
156164
export(record_schema_version)
157165
export(register_measurement_types)
158166
export(release_excluded_datasets)
@@ -162,6 +170,7 @@ export(report_ship_matches)
162170
export(sample_arm_self)
163171
export(save_duckdb_to_gcs)
164172
export(save_working_ducklake)
173+
export(sbe_split_header)
165174
export(scan_metadata_gaps)
166175
export(set_duckdb_comments)
167176
export(show_fields_redefine)

NEWS.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,37 @@ core-projection `switch()` arms produced.
2929

3030
New `Suggests: terra` (only for `qc_stage_reference(gebco_tif = )`).
3131

32+
## Uploads: shipboard files -> the core model
33+
34+
An uploaded cast can now be checked before it ever reaches a release. The design
35+
principle that makes it cheap: every rule targets `obs` / `sample`, so projecting
36+
a file into that shape runs the whole registry unchanged.
37+
38+
- **`read_ctd_upload()`** — dispatches on extension: `.csv` (CalCOFI cast file),
39+
`.cnv`, `.asc`, `.btl`. `.hex` is **refused with its reason** — it is raw A/D
40+
counts and needs the `.xmlcon` calibration file, so any conversion without it
41+
would be invented numbers.
42+
- **`sbe_split_header()`** — the trap that makes `.asc` hard: the header is
43+
fixed-width and adjacent names run together (`Sbeox0ML/LSbeox0Mm/Kg`) in **179
44+
of 200** CalCOFI files, so a whitespace split mis-assigns every column after the
45+
collision. Names and numbers are right-aligned, so columns are cut at the data
46+
rows' stop positions — and when the result is not self-consistent it **errors
47+
rather than guessing**, asking for the `.cnv` whose header is unambiguous.
48+
Measured: ~86% of `.asc` and ~47% of `.btl` read cleanly; the rest say why.
49+
- **`read_sbe_cnv()` / `read_sbe_asc()` / `read_sbe_btl()` / `read_sbe_header()`**
50+
— including `bad_flag``NA`, and the `.btl` quirks (one `Date` header word over
51+
three data fields; several tagged statistic rows per bottle).
52+
- **`ctd_map_columns()`** — CalCOFI names map through `measurement_type.csv`
53+
`_source_column`; Sea-Bird names through the new
54+
`metadata/sbe_name_map.csv`. **Unmapped columns are a result, not an error**
55+
they are where a format change announces itself.
56+
- **`ctd_upload_to_core()`** — the projection, applying the same `-99` /
57+
`-9.99e-29` sentinel deletion and `"9.0"``"9"` quality-code repair the
58+
pipeline already knows, because a new file is exactly where those arrive.
59+
- **`qc_upload_con()`** — an in-memory connection where the upload *is*
60+
`obs` / `sample` / `obs_ctd_full`. Nothing touches a release; it dies with the
61+
session.
62+
3263
## Cast profiles for review
3364

3465
- **`qc_cast_profile()`** — the full-resolution scans for the physical cast a

0 commit comments

Comments
 (0)