dateutil / phonenumbers) once
per row. They are now resolved with a vectorized fast path and a per-row
fallback, with an optional compiled kernel for the phone tail.
The three-tier resolver
Date and phone transforms resolve each value with the cheapest tier that can, falling through only for the rows it can’t:- Vectorized Polars fast path — resolves the well-formed common case in
Rust (multi-format
str.to_datefor dates; a NANP-shape regex for phones), leaving anything it isn’t certain about unresolved. - Native kernel (optional, phone only) — the
goldenflow-nativeRust kernel runs on just the residual rows. - Per-row reference — the original
dateutil/phonenumberspath settles whatever the first two tiers left.
Measured on a realistic messy 1M-row frame:
date_iso8601 76× faster,
phone_e164 19×, phone_digits 4.9× — roughly 14× end-to-end on
a mixed date/phone/text run, with no change to the cleaned values.Optional native kernel
The kernels live ingoldenflow-core (a pyo3-free Rust crate); goldenflow-native
is a thin PyO3 shim over it, shipped as a separate compiled runtime (abi3) — the
same split as polars / polars-runtime. The pure-Python goldenflow wheel
works on its own; the native kernel is opt-in and accelerates:
- the phone residual the Polars fast path can’t reach (alpha numbers like
1-800-FLOWERS, extensions,+1-prefixed forms), and - the checksummed / structural identifiers — payment card (Luhn), IBAN
(mod-97), ISBN, EAN/UPC, EU VAT, SWIFT/BIC, US ABA routing, and IMEI — via owned
Rust kernels with pure-Python fallbacks proven byte-identical over a committed
corpus. In
automode the native kernel also backs the email, URL, numeric, categorical, name (incl.name_transliterate/name_script), and US-address families — anywhere a component kernel exists.
Controlling it
TheGOLDENFLOW_NATIVE environment variable selects the path (reference-mode,
mirroring GOLDENMATCH_NATIVE):
Dates are intentionally not a native kernel — the Polars fast path already
resolves them in vectorized Rust, so a per-row compiled parser would be
slower.
phone_national stays pure Python, and phone_validate stays
pure-Python by design (its only native symbol implements is_valid, not the
product-chosen is_possible spec). phone_digits is pure Polars.Fused columnar apply
When a column has a run of two or more consecutive owned transforms of one dtype (e.g.strip → lowercase → collapse_whitespace → remove_punctuation,
name_transliterate → name_proper → strip_titles, or the numeric
round → clamp → abs_value), the native path fuses them into a single pass
over the Arrow buffer instead of crossing the Python/Polars/Arrow boundary once
per transform. The output and the audit trail are byte-identical to applying
the transforms one at a time; the win is lower memory (fusion avoids
materializing one intermediate column per transform) plus a smaller wall-time
drop.
Measured at 5M rows: ~20% lower peak RSS and a modest wall speedup, growing
with row count. It is on by default when the native kernel is available
(needs goldenflow-native >= 0.14.0 for the nullable URL/company/email ops,
>= 0.13.0 for numeric + parameterized, >= 0.12.0 for the no-arg string
families); older wheels fall back gracefully.
The fused set covers three kernel shapes: the owned, total (never-null)
string transforms (text + email + name-normalizer families, plus the
parameterized
truncate/pad); the owned numeric f64 transforms
(round/clamp/abs_value/fill_zero) on a Float64 column; and the
Option-returning URL / company / email transforms (url_normalize,
company_normalize, email_mask, …), where a value a kernel can’t parse
becomes a null cell that passes through the rest of the run. The engine
recomputes the fusable set as a run advances, so a parser that changes a
column’s dtype mid-chain (e.g. currency_strip: str→f64) lets the string head
and the numeric tail each fuse; a nullable run may also mix in the total string
ops. Only *_validate (boolean), multi-column split/merge, and residual-tier
(phone/date) transforms stay on the per-transform path — automatically, no
configuration needed.Cross-surface (WASM / TypeScript)
The identifier kernels also compile to WebAssembly (goldenflow-wasm). The
TypeScript package keeps pure-TS as the default and permanent fallback; an
opt-in enableWasm() (async, stays pure-TS on failure) routes the identifier
transforms through the WASM kernel — the TS analog of goldenflow[native]. One
shared oracle corpus is asserted byte-identical across native, WASM-TS, and
pure-Python.
Why it’s safe
The parity contract is enforced by tests that compare the full output against the pure references (dateutil / phonenumbers for date/phone; the
goldenflow-core Rust oracle for identifiers) over large corpora, and CI lanes
build the native kernel and the WASM artifact and run those suites with each
active. Turning any kernel on or off only changes speed, never the cleaned data.