Skip to content

@mostlyrightmd/core/qc

QCEngine — orchestrates per-rule evaluation and OR-aggregates each rule’s bit into the per-row obsQcStatus bitfield column.

Defaults to ALPHA_RULES; custom rule sets can be injected via the constructor (used for testing, future rule additions, or downstream-defined custom rules).

The obsQcStatus column name is camelCase (the TS convention) — Python uses snake_case obs_qc_status. The JSON serializer converts it for the wire format (jsonDumps emits snake_case on export).

JS bitwise OR (|) operates on 32-bit signed integers, so this engine accepts rules with bitPosition in [0, 31]. A defensive RangeError fires at construction if any rule violates that ceiling.

new QCEngine(rules): QCEngine

readonly QCRule[] = ALPHA_RULES

QCEngine

readonly rules: readonly QCRule[]

apply<Row>(rows): readonly Row & object[]

Apply all registered rules to rows; return new rows with an obsQcStatus bitfield column appended.

obsQcStatus[i] has bit N set iff this.rules[N].evaluate(rows)[i] === true. Source rows are not mutated; output rows are fresh objects. Empty input → empty output (no throw).

Each rule’s evaluate(rows) is called exactly once: the rule sees the full row array and returns a parallel boolean[].

Row extends Record<string, unknown>

readonly Row[]

readonly Row & object[]

Disagreement row emitted by crosscheckIemGhcnh. Keys are camelCase; serialized snake_case equivalents are event_time, temp_c_iem, temp_c_ghcnh, and delta_c.

readonly deltaC: number

readonly eventTime: string

readonly station: string

readonly tempCGhcnh: number

readonly tempCIem: number


Options for crosscheckIemGhcnh.

optional tolC: number

Maximum acceptable absolute delta in °C between paired IEM/GHCNh temp_c values. Defaults to 2.0 °C. A delta strictly greater than tolC produces a disagreement row; a delta equal to it does not.


A QC rule: ruleId and bit position (both read from the codegen table) plus a per-row evaluator. evaluate(rows) returns a boolean[] of length === rows.length where true means the rule fired for that row.

readonly bitPosition: number

readonly description: string

readonly field: string

readonly ruleId: string

evaluate(rows): boolean[]

readonly Record<string, unknown>[]

boolean[]

const ALPHA_RULES: ReadonlyArray<QCRule>

The 5 alpha rules, indexed by bit position (0..4). Order matches the codegen QC_ALPHA_RULES, which is sorted by bit_position. A rule added to the codegen table needs a matching evaluator here, or the drift guard below throws at module load.

crosscheckIemGhcnh(iemRows, ghcnhRows, opts): readonly CrosscheckDisagreement[]

Cross-check IEM and GHCNh temperatures; return rows where the two sources disagree above opts.tolC (default 2.0 °C).

Algorithm:

  1. If iemRows.length === 0 || ghcnhRows.length === 0 → return [] (matches Python qc.py:212-215).
  2. Validate station + eventTime present (string) on every input row; throw Error on first violation (parity with Python ValueError at qc.py:217-220).
  3. Build iemMap: Map<string, IemRow> keyed by ${row.station}|${row.eventTime}. On duplicate keys the last IEM row wins — deterministic, and a documented deviation from Python’s pd.merge (which would cartesian-product duplicates).
  4. For each GHCNh row, look up the matching IEM row by composite key. If missing → skip. If either temp_c is null / non-finite → skip.
  5. If Math.abs(iem.temp_c - ghcnh.temp_c) > tolC → emit a disagreement row. Strict >, not >=.

Output array order matches the iteration order of ghcnhRows (deterministic, independent of iemRows order).

Pure: input arrays are not mutated.

readonly CrosscheckRowIn[]

IEM observation rows.

readonly CrosscheckRowIn[]

GHCNh observation rows.

CrosscheckOptions = {}

Tolerance options. tolC default = 2.0.

readonly CrosscheckDisagreement[]

Error if any iem or ghcnh row is missing station or eventTime (or they are not strings).


evalDewpointExceedsTemp(rows): boolean[]

Bit 1 — Dewpoint > temperature (physically impossible; strict >).

readonly Record<string, unknown>[]

boolean[]


evalSlpOutOfRange(rows): boolean[]

Bit 4 — Sea-level pressure outside [870, 1085] mb.

readonly Record<string, unknown>[]

boolean[]


evalTempOutOfRange(rows): boolean[]

Bit 0 — Temperature outside [-89C, 57C] (world-record bounds).

readonly Record<string, unknown>[]

boolean[]


evalWindDirOutOfRange(rows): boolean[]

Bit 3 — Wind direction outside [0, 360] (inclusive).

readonly Record<string, unknown>[]

boolean[]


evalWindSpeedNegative(rows): boolean[]

Bit 2 — Wind speed negative.

readonly Record<string, unknown>[]

boolean[]

Re-exports QC_ALPHA_RULES