Methodology
How sdmx-readiness decides whether an SDMx endpoint is ready to move from SDMx 2.1
straight to SDMx 3.1, and why each choice was made. Read this before changing a rule or
comparing two organisations' reports. The rules themselves are in RULES.md;
open validation questions are in RULES_REVIEW.md.
1. What "SDMx 3.1 readiness" means here
SDMx 3.0 is not a target. Changes introduced in 3.0 still count, because 3.1 contains them. Readiness has two independent parts:
| Axis | Question | Measured by |
|---|---|---|
| Content | Can the structures we publish as SDMx 2.1 (codelists, DSDs, dataflows, constraints, ...) be expressed in SDMx 3.1 without loss, and can they adopt semantic versioning? | Rules (RULES.md) on every maintainable artefact, plus a per-artefact migration plan |
| Platform | Does our web service already speak SDMx 3.1? | Capability probes (sdmx_readiness/capabilities.py) |
The tool does not produce a single score. Organisations differ in size and practice, and a percentage would hide what matters most: which artefacts need a decision.
Scope so far: public dissemination endpoints. Authenticated endpoints are supported
(auth = "bearer" or "dotstat-adfs") but have not been the focus.
2. Pipeline
flowchart LR A[endpoints.toml] --> B[plan_queries<br/>one query per<br/>resource x format] B --> C[Fetcher<br/>HTTP + auth] C --> D[(raw/ + manifest.json)] D --> E[parse<br/>SDMX-ML 2.1 / 3.1] E --> F[Inventory 2.1<br/>primary] E --> G[Inventory 3.1<br/>if served] F --> H[rules] G --> H F --> I[migration plan] H --> I F --> J[capability probes] H --> K[report: report.html,<br/>summary.md, CSVs, report.xlsx] I --> K J --> K
--from-cache <previous output folder> restarts from raw/. A report can therefore be
rebuilt (e.g. after a rule change) without calling the endpoint again.
3. What is fetched, and why
-
SDMX-ML 2.1, every maintainable resource,
all/all/all, noreferences=. This is what the organisation publishes today and what the rules are evaluated on. The original script asked for?references=allonce per artefact type. Since every type is fetched anyway, that only downloaded the same artefacts several times. -
SDMX-ML 3.1 from REST v2 (
v2_base/structure/{type}/*/*/*), if the server offers it. Comparing it with 2.1 shows what the server's own conversion drops or re-versions (XF rules). On 2026-10-23 no scanned endpoint offered it: SPC, SIS-CC QA, the Swiss FSO and the SDMx Global Registry all answered 406. The XF rules are therefore inactive for now, and the 406 is reported as a platform gap. The 3.1dataflowquery is sent first; when it gets 406 or 415, the other 3.1 queries are not sent (recorded asunsupported, "not sent"). That keeps a scan to ~27 requests instead of 60, which matters for servers with quotas (the OECD Data API allows 60 per hour). A server that answers a 3.1 query with SDMX-ML 3.0 (Fusion Metadata Registry, Eurostat) is alsounsupported. -
No SDMX-ML 3.0. It is not the target, and a 3.0 conversion says little about a 3.1 one.
-
Codelists are fetched with
detail=allstubs(id, agency, version,isFinal; no codes). SDMX-ML 2.1 codelists have no outgoing references, so nothing is lost; on SPC this is 92 kB instead of 24 MB. 3.1 codelists can extend other codelists, so they are fetched in full when 3.1 is the only format.--full-codeliststurns stubs off. -
HTTP status meaning (
client.outcome_for):Status Outcome 200 ok204, 404 empty(the NSI answers 404 "No Results Found" for an empty resource)400, 405, 406, 415, 422, 501 unsupported401, 403 forbiddenanything else errorA class counts as fetched only when its query was
okorempty. REF-001 never reports a missing target whose class was not fetched. -
Up to 4 queries at once (
--concurrency); results do not depend on it. 5xx and network errors are retried twice. HTTP 429 (rate limited) waits forRetry-After(60 s if absent, at most 120 s) and is retried up to 5 times. -
Size cap (
--max-mb, default 1000 MB; 200 MB on the web app). Some servers send a gzip file as the body withoutContent-Encoding(Eurostat's dissemination API: its DSDs are ~2 GB once decompressed); such bodies are decompressed, and the cap applies to the decompressed size. A query over the cap is anerror, and the scan is marked incomplete.
4. Parsing
sdmx_readiness/parse.py turns each message into Artefact and Reference objects.
- Structural references. A reference is any of:
- a 2.1
<Ref>with anagencyID(a<Ref>without one points inside the same artefact and is ignored); - a 2.1
<URN>element; - a 3.1 element whose text is a URN.
- a 2.1
- Item references (Concept, Category, ...) are mapped to their owning scheme, because versions belong to maintainable artefacts.
- 2.1 schema defaults are applied:
- a missing
version/maintainableParentVersionis1.0; - a missing
isFinalisfalse; - a missing
ContentConstraint/@typeisActual; - a missing
TextFormat/@textTypeisString; - a missing
isMultiLingualistrue(3.1 default:false).
- a missing
- URNs inside annotations are kept with
kind=annotation. They are not SDMx references, but .Stat uses them (DSD annotationMETADATA-> MSD URN) and migration tools do not rewrite them. - Finality in 3.1. SDMx 3.x has no
isFinal. For 3.1 messages the parser reads the .Stat NSI annotationNonFinalArtefact=true, on the artefact itself and not on its items. - DSD / MSD facts (2.1 only) are recorded as
Artefact.props["dsd"]andprops["msd"]. They cover dimension order and positions, concept roles, attribute attachments, groups, ReportingYearStartDay, textual attributes withoutisMultiLingual, and MSD targets and report structures. Keys are documented inparse._dsd_facts/_msd_facts.
5. Version grammar
sdmx_readiness/versions.py is the only place that knows the grammar. It follows the
SDMX-ML 3.1 schema (SDMXCommonReferences.xsd):
| Kind | Example | 3.1 schema type |
|---|---|---|
| semver-stable | 1.2.0 |
SemanticVersionNumberType |
| semver-extended | 1.2.0-draft |
SemanticVersionNumberType |
| legacy | 1.2, 2 |
LegacyVersionNumberType (0|[1-9]\d*)(\.(0|[1-9]\d*))?: no leading zeros |
| wildcard (references only) | 1.2+.0, 2+.0.0, 1.2.0+, * |
SemanticVersionReferenceType, WildcardType |
| invalid | 1.03, 01.0, 1.0.0.0, 1.0.0+build, 1.*, 1.2+.0-draft |
none |
SDMX-ML 2.1 accepted any dotted number ([0-9]+(\.[0-9]+)*). Versions such as 1.03 are
therefore valid today and invalid in 3.1 (VER-001).
6. Severity, basis, change
Every rule carries three labels, defined in sdmx_readiness/rules/engine.py:
-
Severity: what the owner must do.
- BLOCKER: cannot be represented in 3.1 without a decision, a remodel or a loss.
- WARNING: must change, but mechanically; or valid but unsafe / silently different.
- INFO: planning facts and guidelines.
-
Basis: how strong the evidence is. The standard documents disclaim normativity themselves:
- Section 2: "This document is not normative but provides a detailed view of the information model on which the normative SDMX specifications are based".
- Section 6 §3.1: "It is not normative, and it does not provide any rules for the use of the standards, such as those found in SDMX-ML: Schema and Documentation".
Hence four levels:
Basis Meaning SCHEMA Enforced or stated by the SDMX-ML 3.1 XSD STANDARD A MUST/SHOULD in a 3.1 document that the schema does not enforce, e.g. the Section 6 semantic-versioning annex GUIDELINE Non-normative guidance, a MAY, or our own derivation OBSERVED Endpoint content or behaviour -
Change:
mechanical,decision,remodelornone.
7. Conversions deliberately not reported
Every 2.1 DSD contains these constructs, and any converter must rewrite them:
assignmentStatus->usagePrimaryMeasure->Measure- AttributeRelationship
None->Dataflow - AttributeRelationship
PrimaryMeasure->Observation - dimension
typeattribute (removed) TimeDimension/@position(prohibited)
Reporting them would flag every DSD and say nothing about any one of them. Some of the value mappings are not documented in the standard; they are listed for validation in RULES_REVIEW.md §6.
8. Metrics (summary.md "Headline")
- Versioned artefacts: all maintainables except external stubs and the classes that
have no version in SDMx 3.1, namely organisation schemes and Categorisations (
versionprohibited in the 3.1 XSD). Artefacts of other agencies are included; the "Inventory by agency" table separates them. - Semantic versioning adoption = semver (stable + extended) / versioned artefacts.
- Final share =
isFinal=true/ versioned artefacts whose finality is known. Non-final artefacts becomeX.Y.0-draft, so this share is the best predictor of migration effort. - Findings are counted per rule and per distinct artefact.
9. Migration plan
migration_plan.csv has one row per artefact; the columns are defined in
sdmx_readiness/migration.py.
- Mechanical mapping (Section 6, 14.5):
X.Y->X.Y.0;isFinal=false->X.Y.0-draft. Unknown finality counts as non-final, because the Section 6 FAQ says that without semver "pre-SDMX 3.0 final artefacts will be migrated as non-final and mutable". - Latest minor per major (SDMx TWG guidance
sdmx-twg/semver, branchSDMX-3.1; not normative): only the highestX.Yof each majorXis migrated. Older minors are split into two groups:- still referenced: repoint the referrers, or migrate them too;
- unreferenced: candidates to retire.
- Tier: the bottom-up order along structural references. Tier 0 references nothing on the endpoint. A semver artefact may only reference semver artefacts, so migration goes up the tree: codelists -> concept schemes -> DSDs -> dataflows.
- Unversioned classes (Categorisations, organisation schemes) get "drop version".
10. Platform probes
One sample dataflow is used, preferably one with an Actual constraint (i.e. one that holds
data). It is requested once per media type, and only the response headers are read. The
SDMx 3.1 media types come from sdmx-rest v2.2.2:
| Content | Media types |
|---|---|
| Structure | structure+xml;version=3.1.0, structure+json;version=2.1.0 |
| Availability | structure+xml;version=3.1.0 |
| Data | data+csv;version=2.1.0, data+xml;version=3.1.0, data+json;version=2.1.0 |
One SDMX-ML 2.1 probe is kept as a baseline. Results mean:
| Result | Meaning |
|---|---|
supported |
200, and the Content-Type echoes the requested version |
fallback |
200 with another version, or none: the server ignored the request (SPC answers an SDMX-CSV 2.1 request with SDMX-CSV 1.0) |
not supported |
406 / 415 / 501 |
11. Known limitations
- Wildcard references are not resolved; REF-001 skips them.
- 3.1-only features are optional and not assessed:
evolvingStructure, the dataflowDimensionConstraint,MetadataAttributeUsage. - MSD content beyond targets and report structures is not checked.
- When 2.1 is available, codelist extensions (3.1 only) are not assessed: 3.1 codelists are stubs and the rules run on 2.1.
- SDMX-JSON structure messages are probed but not parsed.
- The XF rules have never run on real data, because no server offered SDMX-ML 3.1 at the time of writing.
12. Changes from loop_artefacts_and_versions.py
The original OECD script is in git history (first commit). The side-by-side run is in reports/comparison.
| Original behaviour | Consequence | Now |
|---|---|---|
Rules fire only when the parent is X.Y.Z or a reference contains - |
An endpoint fully on X.Y (SPC) gets "No invalid reference versions found", which reads as "ready" |
Adoption, finality, model changes, DSD checks and a migration plan are reported for every endpoint |
2.1 concept references (maintainableParentVersion) ignored |
DSD -> concept-scheme violations only found in the 3.0 pass | Item references mapped to their scheme |
| 3.0 pass matched tags by 2.1 resource names | 3.0 DataConstraint / Hierarchy silently skipped |
3.0 dropped; 3.1 output compared by class |
| Every URN in any descendant text treated as a reference | Annotation URNs reported as violations | Separate annotation kind (REF-002, INFO) |
| Semver Categorisations checked | Reported although 3.1 removes their version | Categorisations excluded from semver rules |
^\d+\.\d+\.\d+$ as the semver test |
Accepts 01.0.0 |
3.1 schema grammar |
isFinal defaults to false in 3.0 |
Every 3.0 artefact treated as non-final | 2.1 schema defaults; 3.1 has no isFinal |
| No structure checks | Time-dimension position, measure dimensions, attachment groups, MSD targets ... not seen | DSD-, MSD-, MOD-* |
?references=all for each of 7 types |
Same artefacts downloaded up to 7 times (7.5 min on SPC) | One query per type, 4 at a time (5-15 s on SPC; 15-20 s one at a time) |
| Token read once at start-up | Long scans fail when the token expires | Token requested per call |
Hard-coded endpoint, output in ./tests/ |
Code had to be edited to change endpoint | endpoints.toml, reports/<endpoint>/<date>/ |
Rule mapping:
- original rule 1 -> VER-003, generalised to any non-semver target;
- original rules 2-3 -> VER-004 (stable -> draft) and VER-005 (final -> non-final).
13. Sources
- SDMX-ML schemas, tags
v2.1andv3.1.0: https://github.com/sdmx-twg/sdmx-ml. In particular:SDMXCommonReferences.xsdSDMXStructureDataStructure.xsdSDMXStructureMetadataStructure.xsdSDMXStructureBase.xsdSDMXStructureConstraint.xsdSDMXStructureCategorisation.xsdSDMXStructureOrganisation.xsd
- SDMx 3.1 Section 1, Framework: https://sdmx.org/wp-content/uploads/SDMx_3-1-0_SECTION_1_FINAL.pdf
- SDMx 3.1 Section 2, Information Model: https://sdmx.org/wp-content/uploads/SDMX_3-1-0_SECTION_2_FINAL.pdf
- SDMx 3.1 Section 6, Technical notes: https://sdmx.org/wp-content/uploads/SDMX_3-1-0_SECTION_6_FINAL.pdf
- SDMx 3.1 Summary of major changes: https://sdmx.org/wp-content/uploads/SDMX_3-1-0_Major_Changes_FINAL-1_0.pdf
- SDMx 3.0 Summary of major changes: https://sdmx.org/wp-content/uploads/SDMX_3-0-0_Major_Changes_FINAL-1_0.pdf
- SDMx TWG, Versioning in SDMx (3.1 guidance): https://github.com/sdmx-twg/semver/blob/SDMX-3.1/Versioning in SDMX.md
- SDMx REST API, tag
v2.2.2: https://github.com/sdmx-twg/sdmx-rest - SDMx Guidelines for the design of DSDs 1.0: https://sdmx.org/wp-content/uploads/SDMX_Guidelines_for_DSDs_1.0.pdf