Fetches datasets from CES open-data endpoints and writes them to a filesystem tree according to a JSON schedule config.
This repository can be run standalone; it does not require the parent datasets repo as long as the required environment variables, credentials, and Python dependencies are present.
ces-export talks to the CES OD_001 / OD_002 / OD_003 endpoints, chooses an organization, plans dataset/date windows from config/datasets.json, downloads the payloads, and then optionally merges or postprocesses them.
It supports:
- per-dataset schedules
- per-format enable/disable flags
- chunking large time ranges into smaller windows
- CSV merging
- RDF/XML graph merge with a repair retry path
- postprocessing such as CSV → XLSX and RDF/XML → JSON-LD
- dry-run and selective dataset inclusion/exclusion
run.sh— wrapper that launches the Python package undersystemd-runwithLoadCredential=config/datasets.json— dataset scheduling and format/chunking configces_export/__main__.py— CLI entrypointces_export/ces_api.py— OD_001 / OD_002 / OD_003 HTTP callsces_export/settings.py— loads credentials and endpoint URLs from systemd credentialsces_export/planner.py— expands schedules into concrete jobsces_export/runner.py— executes jobs, writes chunks, merges and postprocessesces_export/mergers.py— CSV and RDF/XML merge logicces_export/postprocess.py— extra output conversions
Set up a virtual environment and install the required Python packages before running:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtThen:
- prepare
CES_SECRETS_DIR - set
CES_ORG_NAME - run
./run.sh --out-dir /path/to/output
This repository is intended to be run via run.sh, which wraps the Python package with systemd-run and LoadCredential=.
It therefore requires:
systemd-run- a systemd version that supports
LoadCredential= - permission to run the
sudocommands used byrun.sh - read access to the credential files referenced by
CES_SECRETS_DIR
This module also requires non-public credentials:
CES_ORG_NAMECES_SECRETS_DIRCES_EXPORT_OUT_DIR(or pass--out-dirto the wrapper / CLI)- credential files inside
CES_SECRETS_DIR:APIKEYUSERPASSURI
Optional:
CES_CONFIG— alternate config path. Default:config/datasets.jsonCES_RUN_USER— Unix user forsystemd-runhttp_proxy,https_proxy,HTTP_PROXY,HTTPS_PROXY,NO_PROXYPYTHON_BIN— defaults topython3if unset
At runtime the Python package itself expects CREDENTIALS_DIRECTORY, which is supplied automatically by systemd-run -p LoadCredential=... in run.sh.
The CES endpoint URLs are not hardcoded in the Python package.
They are loaded at runtime from CREDENTIALS_DIRECTORY/URI, where URI is a machine-local JSON file populated from the endpoint information provided by MFSR. The file should have the following structure:
{
"od001": "https://.../API_OD_001",
"od002": "https://.../API_OD_002",
"od003": "https://.../API_OD_003"
}run.sh passes this file into the service as the URI systemd credential.
Typical env-driven invocation:
source /opt/ces-export/runner-env.sh
./run.shTypical repo-level invocation:
./run.sh --out-dir /path/to/outputShow the command that would be run:
./run.sh --print-cmd --out-dir /path/to/outputThe wrapper always passes:
--config <path>--org-name "$CES_ORG_NAME"- any extra CLI arguments supplied by the caller
From python -m ces_export:
--config PATH— required config JSON--hierarchy-node-code CODE— exact OD_003 code--org-name TEXT— substring match against OD_003 org names--list-orgs— print organizations and exit--list-orgs-filter TEXT— filter for--list-orgs--no-cache-org— do not read/write.hierarchy_node_code.txt--today YYYY-MM-DD— override current date for testing--out-dir PATH— sets the output dir--dry-run— print planned work only--force— ignore matching metadata and refetch--start-year N,--end-year N— override schedule year bounds--include-dataset NAME— include only selected dataset(s)--exclude-dataset NAME— skip selected dataset(s)
See config/datasets.json.
Important parts:
defaults.formats.<fmt>— default per-format behaviordatasets.<name>.schedules[]— one or more schedules per datasetdatasets.<name>.formats.<fmt>— dataset-specific format overrides
Format options:
enabledwindow.modewindow.sizemerge_strategypostprocesskeep_chunks
Common window modes used by the repo:
nonecalendar_month
Merge strategies implemented by the runner:
csv_headerrdfxml_graphconcatskip_if_chunkedkeep_chunks
- load config JSON
- resolve output directory from
--out-dirorCES_EXPORT_OUT_DIR - load CES credentials and endpoint URLs from
CREDENTIALS_DIRECTORY - fetch OD_003 organizations
- choose organization by
--hierarchy-node-code,--org-name,CES_ORG_NAME, or cached code - build concrete export jobs from schedules
- for each job:
- skip if metadata already matches and the main output exists
- split date ranges into chunks according to
window - submit an OD_001 request whose payload contains:
datasetNamehierarchyNodeCodedateFromdateTofileFormat
- the OD_001 request body is sent as JSON with:
operation: "opendata"payload: "...", wherepayloadis the Base64-encoded JSON object listed above
- the OD_001 request creates an asynchronous export request and returns an integer
requestId - poll
OD_002/<requestId>until the export is ready- while the export is still being prepared, OD_002 may return
status: "new"and laterstatus: "processing" - when the export is ready, OD_002 returns
status: "done" - in the
doneresponse, the exported dataset is returned in the sameOD_002response underpayload payloadis a Base64-encoded string containing the raw output file bytes (for example CSV or XML, depending on the requested format)- the runner decodes that Base64 string and writes the resulting bytes to the chunk file
- while the export is still being prepared, OD_002 may return
- write chunk payloads
- merge or keep chunks according to
merge_strategy - run postprocessing steps
- write metadata and chunk manifest files
Depending on the schedule and format, a dataset may produce:
- a single merged file
- multiple chunk files plus a manifest
- postprocessed derivatives such as
.xlsxor.jsonld
The runner does not pretend every dataset produces one merged file. RunResult records whether the dataset was skipped, merged, or left as chunks.
For RDF/XML merges, the runner first attempts a normal graph parse/merge. If that fails, it retries after applying ces_export/rdfxml_repair.py to the chunks. The manifest records whether the merge succeeded immediately, succeeded after repair, or failed after the retry.
The CES flow is asynchronous:
OD_003lists available organizations / hierarchy nodesOD_001creates an export requestOD_002/<requestId>is polled until the request finishes
In the current implementation:
OD_001is called as a JSONPOST- the top-level request body contains
operationandpayload payloadis not raw JSON; it is a Base64-encoded JSON object- after
OD_002returnsstatus: "done", the runner reads the dataset from the same OD_002 response fieldpayload - that
payloadis Base64-decoded into raw file bytes and written directly to disk responsePathis not used by the current implementation