Checking the Data Directory¶
check-data examines a data directory against what the application actually reads, and reports anything that would stop it working — or that is present but shouldn't be.
Run it after assembling a data pack, after receiving one from a colleague, and before deploying to a server.
Renamed from validate-data
The tool was called validate-data and was written in shell. It is now a subcommand of the application itself. Every old invocation still works: dt validate-data, nix run .#validate-data and ./scripts/validate-data.sh's successor ./scripts/check-data.sh all reach the same code.
Why it lives in the application¶
The checks used to be a shell reimplementation of the runtime's expectations, which meant the project held two descriptions of a valid data directory — and the shell one could fall behind without anyone noticing.
check-data is now part of the binary. It opens the GeoPackage with the same connection settings internal/geodata uses, and loads the tilesets through internal/tiles.NewMBTilesStore — the very function the server calls. What the checker can read, the application can read.
The remaining expectations — table names, required columns, which files matter — are declared once in internal/datacheck/spec.go, and internal/datacheck/spec_test.go reads the runtime packages back and fails the build if the code starts referencing a table or a data file the spec does not describe. Drift becomes a failing test rather than a silently wrong report.
Running it¶
The Nix app is the application binary, so it needs nothing else installed.
Exit codes¶
| Code | Meaning |
|---|---|
0 | No errors. Warnings may still be present; the application should run. |
1 | One or more errors. The application will not work correctly against this directory. |
2 | The data directory does not exist or could not be examined. |
This makes it usable as a deployment gate:
Do not gate a deployment on dt check-data
dt dispatches through make, and GNU make reports its own exit status 2 whenever a recipe fails, so it collapses "the data has errors" and "the directory is unreadable" into one code. Use nix run .#check-data, ./scripts/check-data.sh or the binary directly when the distinction matters.
For a machine-readable result, --json emits the whole report — findings, severities and the classified inventory — as JSON:
What it checks¶
GeoPackage¶
datapack.gpkgexists and opens read-only as SQLitecatchments_lev12,scenario_currentandscenario_referenceare present and non-empty- the
rtree_catchments_lev12_geomspatial index exists catchments_lev12hasHYBAS_IDandHYBAS_ID_intscenario_currentandscenario_referencehavecatchment_id_int— the join key- the optional
_lower/_upperwhisker tables are reported when missing
Each table is reported with its row and column counts, so a truncated import is visible at a glance rather than only failing later.
Map tiles¶
- the tilesets are opened through the server's own loader
- a tileset named exactly
africaexists. This is the check that most often fails. The tileset name comes from the filename and the server hardcodesafrica; anything else yields a blank map, and the report says which names it found instead. - each tileset's zoom range and format are reported
- any tileset the server never requests is flagged as dead weight in a pack
Indicator metadata¶
metadata.csvparses, and has theColumnNamecolumn without which the entire file is discarded- every
ColumnNameis cross-checked against the real columns inscenario_current - columns present in the data with no metadata row are counted, since they render with raw names and no units
- repeated
ColumnNamevalues are reported, because the last row silently wins
The cross-check is the most valuable thing the tool does. A metadata row naming a column that does not exist is ignored at runtime, so the indicator disappears from the UI with nothing in the logs.
The dot-versus-space fault
When names do not match, the checker re-compares them ignoring punctuation. If that recovers them, it says so and names the fix:
INDICATOR METADATA metadata.csv 102.1 KiB
✓ parsed 503 rows · 25 columns
✗ cross-check 344 row(s) name a column that does not exist in scenario_current
— those indicators never appear in the UI, and nothing is logged
· herbs_diet_kgkm2_Obligate.grazer did you mean "herbs_diet_kgkm2_Obligate grazer"?
· fix 344 of the 344 differ from a real column by punctuation only
typically metadata.csv was exported from R, where make.names()
replaces spaces with dots; re-export with check.names = FALSE
Without this check the symptom is simply "most of the indicators are missing from the dropdowns", with nothing anywhere to explain it.
Note that the 344 examples count as one error, not 344: they are one mistake seen many times.
Lookup tables¶
NPP_by_treecover.csvanddeltaSOC_bytcc_Mgha.csvexist and have acatchIDcolumnherb_traits_ready.csvexists and parses
Missing lookups do not stop the application, but ecological recalculation falls back to proportional scaling, so target editing produces different numbers.
Every file, classified¶
The report ends with an inventory that puts each top-level entry into one of four roles:
| Role | Meaning | In a data pack? |
|---|---|---|
| Read by the app | Opened at runtime | Yes |
| Build inputs | Consumed by dt geopackage to produce datapack.gpkg | No |
| User data | sites/ and images/, written by the application | No |
| Extraneous | Nothing in the project reads it | No |
This distinction matters: a developer's data directory legitimately holds a couple of gigabytes of source CSVs that the application never opens. Those are build inputs, not faults. Only genuinely unrecognised content — stray scripts, editor lock files, notes — is reported as extraneous, and then only as a warning.
Example output¶
Decision Theatre — Data Directory Report
/home/user/DecisionTheatre/data · 15.3 GiB · 52 files
GEOPACKAGE datapack.gpkg 3.2 GiB
✓ catchments_lev12 147 837 rows · 19 columns
✓ scenario_current 147 837 rows · 505 columns
✓ scenario_reference 147 837 rows · 505 columns
✓ rtree_catchments_lev12_geom 147 837 rows · 5 columns
MAP TILES mbtiles/ 10.1 GiB
✓ africa zoom 2–15 · pbf
✓ style.json present — overrides the built-in style
LOOKUP TABLES *.csv
✓ NPP_by_treecover.csv 154 394 rows · 12 columns · 27.2 MiB
✓ deltaSOC_bytcc_Mgha.csv 147 837 rows · 11 columns · 27.8 MiB
DIRECTORY CONTENTS
! R scripts not read by the application (directory, 31 files)
it will be left out of a data pack
INVENTORY
READ BY THE APP 13.4 GiB · included in a data pack
mbtiles/ 10.1 GiB, 4 files
datapack.gpkg 3.2 GiB
…
BUILD INPUTS 1.9 GiB · inputs to 'dt geopackage'; excluded from a data pack
reference_upper.csv 429.7 MiB
…
no errors · 2 warnings
Extending the checks¶
Add the expectation to internal/datacheck/spec.go, citing the source location that reads it, then implement the check in internal/datacheck/check.go and cover it in check_test.go. The tests build a real SQLite GeoPackage and MBTiles file in a temporary directory, then break one thing at a time — so a new check is easy to test honestly.
A check whose ReadBy no longer points at real code is a check that has outlived its purpose; spec_test.go will not catch that for you, so remove it deliberately.
See also Building a Data Pack.