What happened
During a single examples/viz/refresh_external_dashboards.sh run, two committed gallery dashboards silently lost their country-level choropleth panel. No error, no warning on stderr, no non-zero exit — the pages just came out with one fewer panel and everything after it renumbered.
| Page |
Command |
Panels before |
after |
smart_world_events.html |
qsv viz smart world_events_dated.csv |
8 |
7 |
smart_world_choropleth.html |
qsv viz smart world_cities.csv --dictionary infer |
13 |
12 |
In both cases the missing panel is the choropleth, and in both cases it sat at panel index 2:
smart_world_events.html
before: indicator, scattergeo, choropleth, scatterpolar, scatter, scattergeo, violin
after : indicator, scattergeo, scatterpolar, scatter, scattergeo, violin
smart_world_choropleth.html
before: indicator, scattergeo, choropleth, violin, bar, heatmap, bar, bar, bar, violin, box, violin
after : indicator, scattergeo, violin, bar, heatmap, bar, bar, bar, violin, box, violin
Both are country-level choropleths keyed by ISO3 codes (["MOZ","TUR","USA",...] and ["COD","NGA","EGY",...]).
Why it matters
examples/viz/*.html are committed artifacts published to GitHub Pages by viz-gallery-pages.yml. A silently-omitted panel gets committed and published looking entirely plausible — there is nothing in the output that says a panel was dropped. It was only caught because a version-bump diff showed an unexpected net-negative line count on those two files.
viz smart does report other omissions on the page itself (#4491-era "what it left out" chrome) and on stderr, so a dropped choropleth being completely silent looks like a gap rather than a design choice.
It is not deterministic
Re-running the generator restored the panel in both cases, and I could not reproduce it in isolation. Direct runs of the identical command produced the full 8 panels 7 times out of 7:
| Run |
Panels |
| pre-bump binary, bare command |
8 |
| post-bump binary, bare command |
8 |
with --preview-threshold 500 (what the generator adds) |
8 |
| cold stats cache (sidecars deleted first) |
8 |
| warm stats cache |
8 |
with QSV_CENSUS_API_KEY unset |
8 |
gen_gallery.py re-run |
8 |
Ruled out
- Not the DataTables 3.0.4 bump it was found during. Built the pre-bump binary and ran the identical command: 8 panels. Post-bump: 8 panels.
- Not
--preview-threshold 500. The generator adds it in run_html; adding it by hand still gives 8. (world_events_dated.csv is 265 rows, so the threshold is inert anyway.)
- Not a stale or cold stats cache. Deleted
world_events_dated.stats.* and ran cold, then warm — 8 both times.
- Not
QSV_CENSUS_API_KEY. The refresh was run under env -u QSV_CENSUS_API_KEY; running with and without the key both give 8.
- Not generator parallelism.
gen_gallery.py runs figures serially — no thread/process pool.
The one thing that distinguishes the bad run
Both bad outputs came from the same refresh_external_dashboards.sh invocation, which builds five large dashboards first (Pittsburgh 311 at 269 MB, Boston 311 at 146 MB, PA crashes at 396 MB) and only then runs gen_gallery.py. Every good output came from a gen_gallery.py run that did not follow those heavy builds. That is a correlation across one sample, not a diagnosis — but it is the only variable I did not manage to eliminate, and it points at state or resource pressure carried across invocations rather than at anything in the two figures' own inputs.
Suggested direction
Two independent things worth considering:
- Find the non-determinism. Whatever decides that a country choropleth is worth a panel can evidently answer "no" for inputs where it normally answers "yes". Whether that is a geo-resolution step, a stats-cache read, or something order-dependent, the useful property is that it currently fails closed and silently.
- Make it loud regardless. A dropped panel should say so — on stderr at minimum, and ideally in the page's own omissions note, the same way capped parcats combinations and out-of-region points already do. That turns this class of bug into something a regen diff or a CI log surfaces immediately, instead of something caught by eye months later.
A cheap guard for the artifacts specifically: have gen_gallery.py compare each regenerated page's panel count against the committed one and warn on a decrease, alongside its existing warn_stale_data_viewers / warn_stale_parcats_pages checks.
Environment
qsv 23.0.0-jemalloc-apply;fetch;foreach;geocode;Luau 0.736;magika;prompt;to;viz;viz_static;
polars-0.55.1:py-1.44.2;self_update-16-16 (aarch64-apple-darwin compiled with Rust 1.98;
macOS 27.0-Darwin 27.0.0;Apple M4 Max-16)
Found while regenerating the gallery for the DataTables dt-3.0.4/b-4.0.3/cc-2.0.2/sb-2.0.1 bump. The committed artifacts there are correct — every page was checked for panel-count parity against HEAD before staging — so this issue is about the underlying non-determinism, not about a bad artifact in the tree.
What happened
During a single
examples/viz/refresh_external_dashboards.shrun, two committed gallery dashboards silently lost their country-level choropleth panel. No error, no warning on stderr, no non-zero exit — the pages just came out with one fewer panel and everything after it renumbered.smart_world_events.htmlqsv viz smart world_events_dated.csvsmart_world_choropleth.htmlqsv viz smart world_cities.csv --dictionary inferIn both cases the missing panel is the
choropleth, and in both cases it sat at panel index 2:Both are country-level choropleths keyed by ISO3 codes (
["MOZ","TUR","USA",...]and["COD","NGA","EGY",...]).Why it matters
examples/viz/*.htmlare committed artifacts published to GitHub Pages byviz-gallery-pages.yml. A silently-omitted panel gets committed and published looking entirely plausible — there is nothing in the output that says a panel was dropped. It was only caught because a version-bump diff showed an unexpected net-negative line count on those two files.viz smartdoes report other omissions on the page itself (#4491-era "what it left out" chrome) and on stderr, so a dropped choropleth being completely silent looks like a gap rather than a design choice.It is not deterministic
Re-running the generator restored the panel in both cases, and I could not reproduce it in isolation. Direct runs of the identical command produced the full 8 panels 7 times out of 7:
--preview-threshold 500(what the generator adds)QSV_CENSUS_API_KEYunsetgen_gallery.pyre-runRuled out
--preview-threshold 500. The generator adds it inrun_html; adding it by hand still gives 8. (world_events_dated.csvis 265 rows, so the threshold is inert anyway.)world_events_dated.stats.*and ran cold, then warm — 8 both times.QSV_CENSUS_API_KEY. The refresh was run underenv -u QSV_CENSUS_API_KEY; running with and without the key both give 8.gen_gallery.pyruns figures serially — no thread/process pool.The one thing that distinguishes the bad run
Both bad outputs came from the same
refresh_external_dashboards.shinvocation, which builds five large dashboards first (Pittsburgh 311 at 269 MB, Boston 311 at 146 MB, PA crashes at 396 MB) and only then runsgen_gallery.py. Every good output came from agen_gallery.pyrun that did not follow those heavy builds. That is a correlation across one sample, not a diagnosis — but it is the only variable I did not manage to eliminate, and it points at state or resource pressure carried across invocations rather than at anything in the two figures' own inputs.Suggested direction
Two independent things worth considering:
A cheap guard for the artifacts specifically: have
gen_gallery.pycompare each regenerated page's panel count against the committed one and warn on a decrease, alongside its existingwarn_stale_data_viewers/warn_stale_parcats_pageschecks.Environment
Found while regenerating the gallery for the DataTables
dt-3.0.4/b-4.0.3/cc-2.0.2/sb-2.0.1bump. The committed artifacts there are correct — every page was checked for panel-count parity againstHEADbefore staging — so this issue is about the underlying non-determinism, not about a bad artifact in the tree.