Metadata Profiling - dathere/qsv GitHub Wiki
Tier: Advanced
Command covered: profile
Note
Per-command flag reference lives in /docs/help/profile.md. This page is the workflow layer โ what profile produces, which metadata vocabulary to target, and how the pieces compose.
qsv profile turns a CSV (local path or URL) into a .metadata.json document that re-expresses the dataset in a standard metadata vocabulary โ DCAT-US v3, DCAT-AP v3, Croissant 1.1, or Geoconnex โ plus a CKAN-shaped block that DataPusher+ consumes. It is qsv's FAIRification command: point it at data and get publish-ready, harvestable metadata.
Under the hood it runs the same statistical + frequency analysis DataPusher+ (DP+) runs in CKAN, builds a Jinja2 evaluation context from the results, and โ when a CKAN scheming YAML spec is supplied โ evaluates the spec's formula / suggestion_formula templates against that context. The Jinja2 helpers/filters are a native Rust port of DP+'s jinja2_helpers.py, built on minijinja.
profile emits a single <input>.metadata.json carrying up to five top-level blocks:
| Block | Source | Purpose |
|---|---|---|
dpp |
inferred signals | lat/lon/date columns, file size, row count, encoding โ the legacy DataPusher+ inference block |
stats |
qsv stats |
per-column summary statistics |
frequency |
qsv frequency |
per-column value counts |
ckan |
derived | CKAN-shaped package + resources block DP+ uses to prepopulate CKAN |
projection |
active profile | the dataset re-expressed in the chosen metadata vocabulary |
Skip blocks you don't need: --no-projection drops the projection block (keeping dpp/stats/frequency/ckan); --no-ckan drops the CKAN block.
Note
Stats are computed in-process. profile does not shell out to qsv schema โ it builds its own stats cache internally (mode = ProfileSchema: schema stats + quartiles + mode) so the descriptive-statistics projection always surfaces the full extended-stat set on a fresh run. It does shell out to qsv frequency, qsv count, and (when a spec declares validators) qsv validate.
--profile <name|path> selects the projection vocabulary. Bundled names:
--profile |
Vocabulary | Consumed by | Validator (--validate) |
|---|---|---|---|
dcat-us-v3 (default)
|
DCAT-US v3 JSON (canonical, unprefixed โ no longer JSON-LD as of 24.0.0) | data.gov harvesters | vendored GSA JSON Schema |
dcat-ap-v3 |
DCAT-AP v3 | EU data portals |
pyshacl over bundled SHACL shapes |
croissant |
Croissant 1.1 JSON-LD | mlcommons / Hugging Face / Kaggle | mlcroissant |
geoconnex |
Geoconnex JSON-LD | Internet of Water tooling |
pyshacl over bundled SHACL shapes |
geoconnex is gated behind the geoconnex cargo feature. No prebuilt binary includes it: build from source with -F geoconnex (it is part of distrib_features/all_features), or -F datapusher_plus,geoconnex for qsvdp. On a prebuilt, --profile geoconnex fails with unknown --profile.
--profile also accepts a path to a custom YAML profile. Embedded names always win over same-named files, so give custom profiles a non-clashing name. See resources/profiles/README.md for the schema and authoring guide.
Warning
Breaking in 24.0.0: the DCAT-US v3 projection is emitted in its canonical form โ a breaking change to the projection block (#4632). GSA dropped the JSON-LD form of DCAT-US v3: its canonical examples use unprefixed keys and no @context, and the CURIE-keyed output 23.0.1 and earlier emitted is rejected by data.gov's own validator at https://harvest.data.gov/validate/. The projection now emits title, description, contactPoint, dataset, distribution, โฆ with matching @type values (Dataset, Distribution, Catalog, โฆ). Anything reading projection.dct:* / projection.dcat:* must read the bare names instead. The ckan block DataPusher+ consumes is unchanged, and dcat-us:bureauCode, dcat-us:programCode and dcat-us:accessLevel keep their prefixes (they are Project Open Data fields, not v3 properties). Publisher markup discovered with dct:-style keys is normalized, so it still fills the matching slot.
--validate checks the emitted projection against the active profile's declared validators (see table above). By default violations are appended to projection_warnings. Add --strict to fail the command on Required-severity findings instead of just warning (24.0.0+; on 23.0.1 and earlier it aborted on any finding).
Step-by-step, from a bare CSV to a projection that passes --strict: Recipe: DCAT-US v3 for data.gov.
Note
New in 24.0.0: DCAT-US validation is stricter and more precise (#4632, #4633):
-
--strictaborts only on Required-severity findings, with severity read from the GSA schema's ownrequirementLevelannotations for the class that owns the path. Previously everytype/enum/patternfailure counted as Recommended, while--strictaborted on any finding at all. -
--validateasserts string formats. JSON Schema 2020-12 treatsformatas an annotation only, so a malformeddate-timeor URL used to pass locally and fail at data.gov. Dates are normalized to UTC (Z); an offset-less CKAN timestamp gets aZ, and anything unparseable is omitted rather than emitted invalid. - Unknown keys in the projection are reported โ typos, removed properties, wrong namespaces.
-
The vendored GSA schema bundle is refreshed to
1ca074b6:publisheris now Recommended rather than Mandatory,inSeriesis gone, andaccessRestriction,cuiRestriction,useRestriction,inventoriedandlicenseare new. -
The validator is tested against GSA's own
good/badconformance fixtures, and the emitted Catalog passes https://harvest.data.gov/validate/.
Important
RFC 4180 structural failures from qsv validate (emitted when a spec declares validators) are always appended as warnings, regardless of --strict.
External validators (pyshacl, mlcroissant) are Python tools you must have installed for --validate to run them. For bundled profiles they always run, because the profile content is vetted at qsv release time. For a profile loaded from an arbitrary YAML file, the external validator declared by validation.external is not spawned unless you pass --allow-external-validator โ otherwise the run emits a Recommended-severity warning instead, so an untrusted YAML can't silently execute arbitrary commands. Findings from mlcroissant are reported as Required, since it reports only on documents that fail validation (25.0.0; before that they were Recommended).
When <input> is a URL whose response carries DCAT markup (HTTP Link: rel=describedBy), profile discovers the publisher's stated metadata and merges it as a base layer beneath the inferred projection. Disable with --no-dcat-discovery; tune the per-probe timeout with --dcat-discovery-timeout <secs> (default 5). (24.0.0+: discovery decodes a landing page by its declared charset, so a windows-1252 page no longer ends discovery silently.) Stdin and URL inputs are materialized to a tempfile so the rest of the pipeline sees a normal file path; the output's input field reads stdin for piped input.
--initial-context <json> provides seed values for the package / resource dicts plus optional JSON-Pointer overrides for the final projection. Top-level keys: package, resource, dataset_info. Wrap any leaf as {"value": ..., "force": true} to mark it as overriding both URL-discovered DCAT markup and qsv's own inference:
-
dataset_infoentries override their target path verbatim. -
package/resourceentries route through the active profile'sfield_mappings:table (e.g.package.title force=truelands at/projection/titleโ/projection/dct:titleon 23.0.1 and earlier โ beating inference and discovery). - Forced values for slots the profile doesn't surface are silently dropped (no-op).
See tests/resources/profile/dcat-init-context.README.md for a fully-populated example. (This flag replaces the older --package-meta / --resource-meta flags.)
--spec <yaml> supplies a CKAN scheming YAML spec. profile then evaluates the spec's formula / suggestion_formula Jinja2 templates against the analysis context to compute derived fields โ spatial/temporal extents, accrual periodicity, etc. Without a spec, only the inferred dpp block is emitted and no formulas are evaluated. For an example spec, see DP+'s dataset-druf.yaml.
# Quick: dpp/stats/frequency + default DCAT-US v3 projection.
qsv profile data.csv # โ data.csv.metadata.json
# Pipe stdin; output defaults to stdin.metadata.json.
cat data.csv | qsv profile
# URL input: discover the publisher's DCAT markup and merge it as a base layer.
qsv profile https://data.example.gov/datasets/sample.csv
# Seed publisher/contact info; write to a chosen output path.
qsv profile data.csv --initial-context publisher.json -o data.metadata.json
# data.gov-style harvest: validate against DCAT-US v3 JSON Schema,
# abort on violations, wrap in a Catalog envelope.
qsv profile data.csv --validate --strict --catalog -o data.metadata.json
# DCAT-AP v3 for EU portals (pyshacl validates the bundled SHACL shapes).
qsv profile open-data.csv --profile dcat-ap-v3 --validate --strict
# Croissant JSON-LD for an ML dataset (mlcroissant validates the output).
qsv profile train.csv --profile croissant --validate -o train.croissant.json
# Embed per-column value-frequency RecordSets in the Croissant projection.
qsv profile train.csv --profile croissant --croissant-frequency
# Geoconnex JSON-LD for hydrologic data (needs the `geoconnex` feature).
qsv profile gages.csv --profile geoconnex --validate --strict
# Evaluate a CKAN scheming spec: Jinja2 formulas compute derived fields.
qsv profile data.csv --spec dataset-druf.yaml -o data.metadata.json
# CKAN-only output: drop the projection block, keep dpp/stats/frequency/ckan.
qsv profile data.csv --no-projection --spec dataset-druf.yaml
# Custom YAML profile from disk (use a non-clashing name).
qsv profile data.csv --profile ./my-org-dcat.yaml --validateThe croissant profile renders per-column descriptive statistics as Croissant annotations (Median, FirstQuartile, ThirdQuartile, Mode, ArithmeticMean, StandardDeviation, Variance, Minimum, Maximum, Range, Sum, โฆ). These come from the in-process extended stats, so they appear on a fresh run without needing a pre-built --everything stats cache. Add --croissant-frequency to also embed per-column value-frequency distributions as inline cr:RecordSets (one <col>-frequency RecordSet of {value, count, percentage} rows per column); the raw counts always remain in the top-level frequency block regardless.
profile is feature-gated (profile cargo feature). It is present in qsv (full) and qsvdp (DataPusher+ optimized). It is not in qsvlite. Note qsvdp enables profile but not schema โ profile doesn't need schema, since it computes stats in-process.
-
/docs/help/profile.mdโ canonical flag reference -
resources/profiles/README.mdโ profile YAML schema & authoring guide -
Validation & Schema โ
validate/schema/sniff -
Aggregation & Statistics โ stats โ the stats
profilecomputes -
Aggregation & Statistics โ frequency โ the frequency pass
profileruns -
Visualization (viz) โ
viz smartturns the same stats/frequency profile into an interactive dashboard - Integrations (CKAN, DuckDB, Python, CI/CD) โ CKAN / DataPusher+ context
-
Binary Variants โ which builds include
profile -
tests/test_profile.rsโ extensive worked examples