Skip to content

Harden Parquet IO against mixed-type parameter_value columns#118

Merged
jirhiker merged 1 commit into
mainfrom
fix/parquet-mixed-parameter-value
Jul 10, 2026
Merged

Harden Parquet IO against mixed-type parameter_value columns#118
jirhiker merged 1 commit into
mainfrom
fix/parquet-mixed-parameter-value

Conversation

@jirhiker

Copy link
Copy Markdown
Member

Problem

Source steps crash in the Dagster Parquet handoff when an analyte's parameter_value column mixes numeric and qualitative values:

pyarrow.lib.ArrowInvalid: Could not convert 'ND' ... for column parameter_value
pyarrow.lib.ArrowInvalid: Could not convert '< MRL 10 MG/L' ...
pyarrow.lib.ArrowTypeError: Expected bytes, got a 'float' object ...

WQP non-detects arrive as ResultMeasureValue='ND'; NMED DWB emits below-reporting-limit markers like '< MRL 0.1 MG/L'. In _apply_unit_conversion numeric results convert to float while float('ND') raises and the raw string is deliberately kept ("Keeping ... for time series data"). pyarrow infers one logical type per column and can't hold both — crashing whichever way it guessed (double → ArrowInvalid, string → ArrowTypeError).

Observed sources/analytes: wqp (arsenic, carbonate), nmed_dwb (calcium, chloride). Any analyte with a non-detect / below-MRL result hits it.

Fix

backend/persisters/geodataframe.py: new _frame_to_parquet_bytes helper scans each column and, if it mixes numeric values with non-numeric strings, casts the whole column to str before writing (nulls preserved). Both dicts_to_parquet_bytes (records/sites) and timeseries_to_parquet_bytes (observations) route through it. Order-independent — covers both the double-inferred and string-inferred crash variants.

Qualitative markers are intentional time-series data, so they are kept rather than dropped. Every downstream consumer already float()-coerces and skips non-numeric values (backend.trend_stats.daily_series), so string storage is safe.

Note: parameter_value dtype now varies by batch — float64 when clean, str when any qualitative value is present. Any future reader must not assume a fixed dtype.

Verification

Against real pyarrow: both crash variants ('ND'/'< MRL ...' with floats, in either value ordering) round-trip with markers intact; all-numeric batches keep float64. Local persister suite green.

🤖 Generated with Claude Code

WQP non-detects arrive as ResultMeasureValue='ND'. In _apply_unit_conversion
numeric results convert to float while float('ND') raises and the raw string is
kept ("Keeping ... for time series data"). The result is a parameter_value
column mixing floats and qualitative strings, which crashed the Parquet handoff:

  pyarrow.lib.ArrowInvalid: Could not convert 'ND' with type str: tried to
  convert to double ... for column parameter_value

pyarrow infers one logical type per column and can't hold both. Rather than drop
the qualitative markers (they are intentional time-series data), stringify any
object column that mixes numeric values with non-numeric strings before writing.

- backend/persisters/geodataframe.py: new _frame_to_parquet_bytes helper scans
  columns and casts mixed numeric/str columns to str, preserving nulls. Both
  dicts_to_parquet_bytes (records/sites) and timeseries_to_parquet_bytes
  (observations) route through it, since the records path can carry the same mix.

Qualitative markers ('ND', '<0.5') now survive the round-trip; all-numeric
batches keep their float64 dtype. Every downstream consumer already
float()-coerces and skips non-numeric values (backend.trend_stats.daily_series),
so string storage in ND-bearing batches is safe. Note: parameter_value dtype now
varies by batch (float64 when clean, str when any qualitative value present).

Verified against pyarrow: the mixed column round-trips with markers intact and
all-numeric batches stay float64; local persister suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

Your pull request is automatically being deployed to Dagster Cloud.

Location Status Link Updated
die-orchestration View in Cloud Jul 10, 2026 at 09:40 PM (UTC)

@jirhiker
jirhiker merged commit d275422 into main Jul 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant