23.0.0
Pre-release[23.0.0] - 2026-09-12 👑 The "Context is King" Release 🧠
Highlights
250 commits since 22.0.1, and the theme is context - the surrounding facts that turn a number into a finding. A count of 4,312 says nothing; 4,312 per 100,000 residents says where to look.
The headliner is the Data Schematic's deep US Census integration. A choropleth colored by raw counts is largely a population map - the big region wins because it is big. A choropleth colored by a rate is a finding. Closing that gap used to mean sourcing boundary files, sourcing population tables, joining both to your data and computing the rate by hand; viz smart now does all of it from nothing but a CSV. --geojson auto fetches US boundaries from the Census TIGERweb REST services, --denominator census fetches the matching population denominators, and a --locations column of county names - not just FIPS codes - resolves against the Census's own name table with nothing supplied but the CSV, while --geocode extends the same path to city-name columns. Census Designated Places make census:place the full place universe rather than incorporated places alone (#4394, #4395, #4397, #4417, #4540).
describegpt closes the loop, which is what makes this more than a flag. It derives an x-qsv.denominator from a population column and declares the geography that denominator describes, so the neuro-symbolic dictionary carries the denominator and viz charts the rate without the reader ever naming either. It also knows when not to: a denominator that is constant across regions, or one measured over a different geography than the region key, is refused rather than charted as a rescaled count.
And the human is not merely in the loop - the human stays in control. Everything above is an LLM proposal, and the artifact that actually drives a Data Schematic is the editable JSON Schema data dictionary sitting beside your CSV: an inferred dictionary is a draft, and the reviewed copy is the artifact of record. Domain experts fine-tune those inferences in a terminal UI - the visual-data-dictionary skill's edit_dictionary.py walks every column, edits the five fields that steer panel selection (role, concept, label, description, aggregation), and previews how each edit reroutes the column before you render (--summary prints the current routing non-interactively). The control is real but bounded, which is the point: qsv re-verifies every hint on read, so a hand-edit can correct the model where it guessed wrong, but cannot assert a hint the data does not support - a declared money measure on a zero-padded code column is refused, not drawn. Human judgment overrides the model; neither overrides the data.
Also new, and notable in its own right: readstat converts SAS (.sas7bdat, .xpt), Stata (.dta) and SPSS (.sav, .zsav, .por) files to CSV, so the decades of public-sector and academic data locked in proprietary statistical formats join the qsv pipeline without a licence (#4542).
Context also means units. describegpt now proposes a per-field UCUM x-qsv.unit and a first-class measure.money concept, and viz carries both into KPI tiles, panel subtitles, hover text and axis titles - so a Data Schematic says "18.4 °C" and "$192B" rather than a bare float and an SI "G".
And because a reader cannot use context they never find, a Data Schematic now opens with a guided Tour - budgeted by --tour-steps <n> and followed by first-visit hint beacons on the affordances a newcomer has no reason to look for. The Tour is narrated by an LLM, and --tour-audience decides who it is talking to. It defaults to "Explain like I'm 10", but the same dataset will happily brief "the mayor", "the City Council", "my 11th grade Civics class", "my grandma" or "an urban planner" - one flag, and the numbers stay identical while the story around them is re-pitched. The audience shapes the tour prose only: field labels and descriptions keep their normal register, so re-pitching the narration never quietly rewrites the data dictionary. You can even have a non-English Tour narration with the --language option.
Underneath all of it: a sustained correctness sweep. A four-batch review of stats.rs fixed panics, undefined behavior, tempfile leaks, stats-cache validity and a cluster of outright wrong results; count stopped disagreeing with itself on files containing blank lines; and the auto-indexing machinery was rewritten to be race-free, atomic and permission-preserving. Numbers you cannot trust are not context - they are noise.
Detailed Changelog: https://github.com/dathere/qsv/blob/master/CHANGELOG.md#v23-changes
Full Commit log: 22.0.1...23.0.0