Skip to content

mostlyright.qc

Phase 3.4 — QC Engine Alpha + sidecar + crosscheck.

Phase 3.4 v0.1.0 scope: alpha QC engine that flags-and-keeps observation rows + writes a sidecar parquet using QC_SIDECAR_SCHEMA. 5-8 alpha rules cover the obvious physical-bounds + cross-source-disagreement cases. Forecast QC + climate QC defer to v0.2.

Surface:

  • QCEngine — bitfield rule registry + apply().
  • QCRule — Protocol every rule satisfies.
  • ALPHA_RULES — the 5-8 rules registered in v0.1.0.
  • crosscheck_iem_ghcnh(df)() — IEM vs GHCNh disagreement detection.
ALPHA_RULESThe 5 alpha rules registered in v0.1.0.
crosscheck_iem_ghcnh(iem_df, ghcnh_df, *[, …])Cross-check IEM and GHCNh temperatures; return disagreement rows.
QCEngine()Apply registered rules to observation DataFrames; emit bitfield + sidecar.
QCRule(*args, **kwargs)Protocol every QC rule satisfies.

mostlyright.qc.ALPHA_RULES : tuple[_RuleSpec, …] = (_RuleSpec(rule_id=‘temp_c.out_of_range’, bit_position=0, description=‘Temperature outside [-89C, 57C] (world-record bounds).’, evaluator=<function _rule_temp_out_of_range>), _RuleSpec(rule_id=‘dew_point_c.exceeds_temp’, bit_position=1, description=‘Dewpoint greater than temperature (physically impossible).’, evaluator=<function _rule_dewpoint_exceeds_temp>), _RuleSpec(rule_id=‘wind_speed_ms.negative’, bit_position=2, description=‘Wind speed negative.’, evaluator=<function _rule_wind_speed_negative>), _RuleSpec(rule_id=‘wind_dir_deg.out_of_range’, bit_position=3, description=‘Wind direction outside [0, 360].’, evaluator=<function _rule_wind_dir_out_of_range>), _RuleSpec(rule_id=‘slp_hpa.out_of_range’, bit_position=4, description=‘Sea-level pressure outside [870, 1085] mb.’, evaluator=<function _rule_slp_out_of_range>))

Section titled “mostlyright.qc.ALPHA_RULES : tuple[_RuleSpec, …] = (_RuleSpec(rule_id=‘temp_c.out_of_range’, bit_position=0, description=‘Temperature outside [-89C, 57C] (world-record bounds).’, evaluator=<function _rule_temp_out_of_range>), _RuleSpec(rule_id=‘dew_point_c.exceeds_temp’, bit_position=1, description=‘Dewpoint greater than temperature (physically impossible).’, evaluator=<function _rule_dewpoint_exceeds_temp>), _RuleSpec(rule_id=‘wind_speed_ms.negative’, bit_position=2, description=‘Wind speed negative.’, evaluator=<function _rule_wind_speed_negative>), _RuleSpec(rule_id=‘wind_dir_deg.out_of_range’, bit_position=3, description=‘Wind direction outside [0, 360].’, evaluator=<function _rule_wind_dir_out_of_range>), _RuleSpec(rule_id=‘slp_hpa.out_of_range’, bit_position=4, description=‘Sea-level pressure outside [870, 1085] mb.’, evaluator=<function _rule_slp_out_of_range>))”

The 5 alpha rules registered in v0.1.0. Bit positions are stable — Phase 3.4+ additions append at the next bit. Removal target: never (would break stored sidecar parquets).

Bases: object

Apply registered rules to observation DataFrames; emit bitfield + sidecar.

Apply all registered rules; return df with obs_qc_status bitfield column appended (and the per-rule bit-position scheme documented in ALPHA_RULES).

  • Return type: DataFrame
  • Parameters: df (pd.DataFrame)

Build the QC sidecar rows for a flagged DataFrame.

Returns one dict per (row, rule_fired) pair conforming to schema.observation_qc.v1.

  • Return type: list[dict]
  • Parameters: df (DataFrame)

rules : ClassVar[tuple[_RuleSpec, ...]] = (_RuleSpec(rule_id=‘temp_c.out_of_range’, bit_position=0, description=‘Temperature outside [-89C, 57C] (world-record bounds).’, evaluator=<function _rule_temp_out_of_range>), _RuleSpec(rule_id=‘dew_point_c.exceeds_temp’, bit_position=1, description=‘Dewpoint greater than temperature (physically impossible).’, evaluator=<function _rule_dewpoint_exceeds_temp>), _RuleSpec(rule_id=‘wind_speed_ms.negative’, bit_position=2, description=‘Wind speed negative.’, evaluator=<function _rule_wind_speed_negative>), _RuleSpec(rule_id=‘wind_dir_deg.out_of_range’, bit_position=3, description=‘Wind direction outside [0, 360].’, evaluator=<function _rule_wind_dir_out_of_range>), _RuleSpec(rule_id=‘slp_hpa.out_of_range’, bit_position=4, description=‘Sea-level pressure outside [870, 1085] mb.’, evaluator=<function _rule_slp_out_of_range>))

Section titled “rules : ClassVar[tuple[_RuleSpec, ...]] = (_RuleSpec(rule_id=‘temp_c.out_of_range’, bit_position=0, description=‘Temperature outside [-89C, 57C] (world-record bounds).’, evaluator=<function _rule_temp_out_of_range>), _RuleSpec(rule_id=‘dew_point_c.exceeds_temp’, bit_position=1, description=‘Dewpoint greater than temperature (physically impossible).’, evaluator=<function _rule_dewpoint_exceeds_temp>), _RuleSpec(rule_id=‘wind_speed_ms.negative’, bit_position=2, description=‘Wind speed negative.’, evaluator=<function _rule_wind_speed_negative>), _RuleSpec(rule_id=‘wind_dir_deg.out_of_range’, bit_position=3, description=‘Wind direction outside [0, 360].’, evaluator=<function _rule_wind_dir_out_of_range>), _RuleSpec(rule_id=‘slp_hpa.out_of_range’, bit_position=4, description=‘Sea-level pressure outside [870, 1085] mb.’, evaluator=<function _rule_slp_out_of_range>))”

class mostlyright.qc.QCRule(*args, **kwargs)

Section titled “class mostlyright.qc.QCRule(*args, **kwargs)”

Bases: Protocol

Protocol every QC rule satisfies.

Return a boolean Series where True = rule fired.

  • Return type: Series
  • Parameters: df (DataFrame)

mostlyright.qc.crosscheck_iem_ghcnh(iem_df, ghcnh_df, , tol_c=2.0)

Section titled “mostlyright.qc.crosscheck_iem_ghcnh(iem_df, ghcnh_df, , tol_c=2.0)”

Cross-check IEM and GHCNh temperatures; return disagreement rows.

For every (station, event_time) present in both inputs, compare temp_c values. Rows where |iem.temp_c - ghcnh.temp_c| > tol_c are returned with both values + delta so the QC engine can flag them.

  • Parameters:
    • iem_df (DataFrame) – Observation DataFrame with source IEM.
    • ghcnh_df (DataFrame) – Observation DataFrame with source GHCNh.
    • tol_c (float) – Maximum acceptable delta in °C (default 2.0).
  • Return type: DataFrame
  • Returns: DataFrame with rows where the two sources disagree above tol_c. Columns: station, event_time, temp_c_iem, temp_c_ghcnh, delta_c.

Phase 6 W2-T4: accepts pandas OR polars input for both arms; returns the same backend the caller passed (matches first arg’s backend if they differ — both polars or both pandas is the supported case).