Ceres Publish Dataset — HuggingFace Export
Recurring workflow to export the Ceres open data index to HuggingFace as a curated Parquet dataset. Ceres v0.5.0 is the baseline for the reproducible snapshot contract: manifest, integrity checks, coverage/quality reports, alias-aware duplicate provenance, identity index, and snapshot changelogs.
Tracking issue: https://github.com/AndreaBozzo/Ceres/issues/89
Checklist
- Run data quality checks (noise, duplicates, new portals)
- Export:
ceres export --format parquet --output ~/ceres-open-data-index
- Remove any stale per-portal parquet files from previous exports (e.g. renamed portals)
- Update
README.md dataset card (counts, portal table, snapshot date, bias notes)
- Version the snapshot (commit + tag)
- Push to HuggingFace
- (Optional) Regenerate HuggingFace Space visualization
Export Command
ceres export --format parquet --output ~/ceres-open-data-index
The export streams all non-stale datasets from PostgreSQL, applies curation, and writes:
all.parquet — Canonical complete flattened dataset
data/<portal-name>.parquet — Per-portal subsets (repeat rows from all.parquet; never sum into the canonical total)
identity.parquet — Slim per-record fingerprint (source_portal, original_id, content_hash) used to diff snapshots; one row per all.parquet row
metadata.json — Versioned snapshot manifest: stable snapshot_id, UTC generated_at, Ceres version/commit, portal-config checksum, duplicate_detection provenance (method/version/alias_groups), curation row counts, per-portal inclusion status, and SHA-256 checksums for every file
reports.json — Machine-readable coverage and quality report: coverage by portal/type/profile/language, field-completeness rates (description, license, organization, tags, modification date), and curation outcomes (raw, exported, filtered, duplicate-flagged, duplicate-detection method/version, excluded portals)
report.md — Human-readable summary of reports.json for the dataset card / release notes
changelog.json / changelog.md — Snapshot-to-snapshot diff (added/changed/removed/unchanged, with per-portal summaries), written only when --previous <DIR> points at a prior snapshot; otherwise a zeroed baseline changelog with compared: false
Verify the SHA-256 checksums in metadata.json before publishing a copied or mirrored snapshot. reports.json is derived from the same export pass, so its figures agree with the manifest.
Diffing against the previous snapshot
To publish a changelog, point --previous at the prior published snapshot directory:
ceres export --format parquet --output ~/ceres-open-data-index --previous ~/ceres-open-data-index
The diff is keyed by the stable identity (source_portal + original_id) read from each snapshot's identity.parquet. "Changed" means the content_hash (SHA-256 of title+description) differs; source modification timestamps are not used because portal coverage of them is incomplete.
Portal names are resolved from ~/.config/ceres/portals.toml. Portals not in the config fall back to hostname-based naming (e.g. https://data.gov.ro becomes data-gov-ro).
The export takes ~30-40 minutes for 900k+ datasets due to JSONB flattening.
Export Schema (14 columns)
| Column |
Type |
Description |
original_id |
string |
Dataset ID from source portal |
source_portal |
string |
Portal base URL |
portal_name |
string |
Human-readable portal name |
url |
string |
Direct URL to dataset page |
title |
string |
Dataset title |
description |
string |
Dataset description (nullable) |
tags |
string |
Comma-separated tag names (nullable) |
organization |
string |
Publishing organization (nullable) |
license |
string |
License title or identifier (nullable) |
metadata_created |
string |
Original creation date ISO 8601 (nullable) |
metadata_modified |
string |
Last modification date ISO 8601 (nullable) |
first_seen_at |
string |
When Ceres first indexed this dataset (RFC 3339) |
language |
string |
Primary language code (nullable) |
is_duplicate |
boolean |
Heuristic signal: same title (case-insensitive) appears on another portal. Not canonical deduplication |
Curation Rules (applied automatically during export)
- Noise filter: Removes datasets where title < 5 chars, description is empty, or title contains "test"/"prova"/"esempio" (case-insensitive substring match)
- Duplicate flag (heuristic, not canonical dedup): Same title (case-insensitive) across different portals sets
is_duplicate=true. Duplicates are kept, not removed. Portals may declare aliases = [...] in portals.toml; aliased/mirror URLs are folded onto their canonical portal first, so a mirror is not counted as an independent source. The matching rule and version are recorded in metadata.json under duplicate_detection. Core SQL: SELECT LOWER(title) FROM datasets GROUP BY LOWER(title) HAVING COUNT(DISTINCT <canonicalized source_portal>) > 1
- Metadata flattening: Tags from
metadata.tags[].name, organization from metadata.organization.title, license from metadata.license_title
HuggingFace Dataset Repository
- Location:
~/ceres-open-data-index
- Remote:
https://huggingface.co/datasets/AndreaBozzo/ceres-open-data-index
- Branch:
main
- LFS:
*.parquet tracked via Git-LFS (configured in .gitattributes)
Commit and push workflow
cd ~/ceres-open-data-index
git add all.parquet identity.parquet data/ metadata.json reports.json report.md changelog.json changelog.md README.md
git commit -m "vN: Month Year export (X portals, Yk datasets)"
git tag vN
git push origin main
git push origin vN
README Dataset Card — What to Update
The README at ~/ceres-open-data-index/README.md uses HuggingFace dataset card format with YAML frontmatter. Update these sections:
- YAML frontmatter:
language list (add new language codes), size_categories, tags
- Opening line: Total datasets count, portal count, country count
- Files section: Row count, file count
- Portal table (
### Splits by portal): Add new portals, update all counts, sort by count descending
- Curation section: Noise filtering count, duplicate flagging count
- Update frequency: Snapshot date
- Known biases: Geographic skew percentages, language distribution, portal selection notes
- Citation block: Snapshot date, portal count
Curation counts and per-portal totals come from metadata.json; coverage breakdowns (by type/profile/language) and field-completeness rates come from reports.json (or the rendered report.md). Both are generated by the export.
Export History
| Version |
Date |
Exported |
Filtered |
Duplicates |
Portals |
Countries |
| v1 |
2026-02-12 |
230,315 |
4,693 |
58,364 |
23 |
8 + intl |
| v2 |
2026-02-25 |
349,836 |
6,532 |
59,062 |
25 |
9 + intl |
| v3 |
2026-03-30 |
890,143 |
10,331 |
75,527 |
32 |
13 + intl |
Key Files
| File |
Purpose |
~/.config/ceres/portals.toml |
Portal config — controls name resolution during export |
~/ceres-open-data-index/README.md |
HuggingFace dataset card |
~/ceres-open-data-index/metadata.json |
Versioned snapshot manifest (generated, do not edit) |
~/ceres-open-data-index/reports.json |
Coverage and quality report (generated, do not edit) |
~/ceres-open-data-index/report.md |
Human-readable coverage/quality summary (generated) |
crates/ceres-core/src/parquet_export.rs |
Export logic (noise filter, duplicate flagging, flattening, manifest, reports) |
crates/ceres-db/src/repository.rs |
SQL queries (duplicate detection, dataset streaming) |
HuggingFace Space (Optional)
The visualization dashboard at https://huggingface.co/spaces/AndreaBozzo/Ceres is a static Plotly site generated from the exported data.
- Location:
~/Documenti/Ceres(huggingfacespace)
- Generator:
~/Documenti/open-data-galaxy/galaxy_constellation.py
- Regenerate after pushing the dataset, then push the Space repo separately
Pre-export Data Quality Checks
# Overall stats
ceres stats
# Check noise candidates in DB (optional)
docker exec ceres_db psql -U ceres_user -d ceres_db -c "
SELECT COUNT(*) FILTER (WHERE LENGTH(title) < 5) as tiny_titles,
COUNT(*) FILTER (WHERE description IS NULL OR TRIM(description) = '') as empty_desc,
COUNT(*) FILTER (WHERE LOWER(title) LIKE '%test%' OR LOWER(title) LIKE '%prova%' OR LOWER(title) LIKE '%esempio%') as noise_titles
FROM datasets WHERE NOT is_stale;
"
1---2name: ceres-publish-dataset3description: Use when publishing a HuggingFace dataset export for the Ceres Open Data Index. Covers the full workflow — data quality checks, Parquet export, snapshot manifest, coverage/quality reports, snapshot changelog, README dataset card update, versioning, and push to HuggingFace.4---56# Ceres Publish Dataset — HuggingFace Export78Recurring workflow to export the Ceres open data index to [HuggingFace](https://huggingface.co/datasets/AndreaBozzo/ceres-open-data-index) as a curated Parquet dataset. Ceres v0.5.0 is the baseline for the reproducible snapshot contract: manifest, integrity checks, coverage/quality reports, alias-aware duplicate provenance, identity index, and snapshot changelogs.910**Tracking issue:** https://github.com/AndreaBozzo/Ceres/issues/891112## Checklist13141. Run data quality checks (noise, duplicates, new portals)152. Export: `ceres export --format parquet --output ~/ceres-open-data-index`163. Remove any stale per-portal parquet files from previous exports (e.g. renamed portals)174. Update `README.md` dataset card (counts, portal table, snapshot date, bias notes)185. Version the snapshot (commit + tag)196. Push to HuggingFace207. (Optional) Regenerate HuggingFace Space visualization2122## Export Command2324```bash25ceres export --format parquet --output ~/ceres-open-data-index26```2728The export streams all non-stale datasets from PostgreSQL, applies curation, and writes:29- `all.parquet` — Canonical complete flattened dataset30- `data/<portal-name>.parquet` — Per-portal subsets (repeat rows from `all.parquet`; never sum into the canonical total)31- `identity.parquet` — Slim per-record fingerprint (`source_portal`, `original_id`, `content_hash`) used to diff snapshots; one row per `all.parquet` row32- `metadata.json` — Versioned snapshot manifest: stable `snapshot_id`, UTC `generated_at`, Ceres version/commit, portal-config checksum, `duplicate_detection` provenance (method/version/alias_groups), curation row counts, per-portal inclusion status, and SHA-256 checksums for every file33- `reports.json` — Machine-readable coverage and quality report: coverage by portal/type/profile/language, field-completeness rates (description, license, organization, tags, modification date), and curation outcomes (raw, exported, filtered, duplicate-flagged, duplicate-detection method/version, excluded portals)34- `report.md` — Human-readable summary of `reports.json` for the dataset card / release notes35- `changelog.json` / `changelog.md` — Snapshot-to-snapshot diff (added/changed/removed/unchanged, with per-portal summaries), written only when `--previous <DIR>` points at a prior snapshot; otherwise a zeroed baseline changelog with `compared: false`3637Verify the SHA-256 checksums in `metadata.json` before publishing a copied or mirrored snapshot. `reports.json` is derived from the same export pass, so its figures agree with the manifest.3839### Diffing against the previous snapshot4041To publish a changelog, point `--previous` at the prior published snapshot directory:4243```bash44ceres export --format parquet --output ~/ceres-open-data-index --previous ~/ceres-open-data-index45```4647The diff is keyed by the stable identity (`source_portal` + `original_id`) read from each snapshot's `identity.parquet`. "Changed" means the `content_hash` (SHA-256 of title+description) differs; source modification timestamps are not used because portal coverage of them is incomplete.4849Portal names are resolved from `~/.config/ceres/portals.toml`. Portals not in the config fall back to hostname-based naming (e.g. `https://data.gov.ro` becomes `data-gov-ro`).5051The export takes ~30-40 minutes for 900k+ datasets due to JSONB flattening.5253## Export Schema (14 columns)5455| Column | Type | Description |56|--------|------|-------------|57| `original_id` | string | Dataset ID from source portal |58| `source_portal` | string | Portal base URL |59| `portal_name` | string | Human-readable portal name |60| `url` | string | Direct URL to dataset page |61| `title` | string | Dataset title |62| `description` | string | Dataset description (nullable) |63| `tags` | string | Comma-separated tag names (nullable) |64| `organization` | string | Publishing organization (nullable) |65| `license` | string | License title or identifier (nullable) |66| `metadata_created` | string | Original creation date ISO 8601 (nullable) |67| `metadata_modified` | string | Last modification date ISO 8601 (nullable) |68| `first_seen_at` | string | When Ceres first indexed this dataset (RFC 3339) |69| `language` | string | Primary language code (nullable) |70| `is_duplicate` | boolean | Heuristic signal: same title (case-insensitive) appears on another portal. Not canonical deduplication |7172## Curation Rules (applied automatically during export)7374- **Noise filter**: Removes datasets where title < 5 chars, description is empty, or title contains "test"/"prova"/"esempio" (case-insensitive substring match)75- **Duplicate flag** (heuristic, not canonical dedup): Same title (case-insensitive) across different portals sets `is_duplicate=true`. Duplicates are kept, not removed. Portals may declare `aliases = [...]` in `portals.toml`; aliased/mirror URLs are folded onto their canonical portal first, so a mirror is not counted as an independent source. The matching rule and version are recorded in `metadata.json` under `duplicate_detection`. Core SQL: `SELECT LOWER(title) FROM datasets GROUP BY LOWER(title) HAVING COUNT(DISTINCT <canonicalized source_portal>) > 1`76- **Metadata flattening**: Tags from `metadata.tags[].name`, organization from `metadata.organization.title`, license from `metadata.license_title`7778## HuggingFace Dataset Repository7980- **Location:** `~/ceres-open-data-index`81- **Remote:** `https://huggingface.co/datasets/AndreaBozzo/ceres-open-data-index`82- **Branch:** `main`83- **LFS:** `*.parquet` tracked via Git-LFS (configured in `.gitattributes`)8485### Commit and push workflow8687```bash88cd ~/ceres-open-data-index89git add all.parquet identity.parquet data/ metadata.json reports.json report.md changelog.json changelog.md README.md90git commit -m "vN: Month Year export (X portals, Yk datasets)"91git tag vN92git push origin main93git push origin vN94```9596## README Dataset Card — What to Update9798The README at `~/ceres-open-data-index/README.md` uses HuggingFace dataset card format with YAML frontmatter. Update these sections:991001. **YAML frontmatter**: `language` list (add new language codes), `size_categories`, `tags`1012. **Opening line**: Total datasets count, portal count, country count1023. **Files section**: Row count, file count1034. **Portal table** (`### Splits by portal`): Add new portals, update all counts, sort by count descending1045. **Curation section**: Noise filtering count, duplicate flagging count1056. **Update frequency**: Snapshot date1067. **Known biases**: Geographic skew percentages, language distribution, portal selection notes1078. **Citation block**: Snapshot date, portal count108109Curation counts and per-portal totals come from `metadata.json`; coverage breakdowns (by type/profile/language) and field-completeness rates come from `reports.json` (or the rendered `report.md`). Both are generated by the export.110111## Export History112113| Version | Date | Exported | Filtered | Duplicates | Portals | Countries |114|---------|------|----------|----------|------------|---------|-----------|115| v1 | 2026-02-12 | 230,315 | 4,693 | 58,364 | 23 | 8 + intl |116| v2 | 2026-02-25 | 349,836 | 6,532 | 59,062 | 25 | 9 + intl |117| v3 | 2026-03-30 | 890,143 | 10,331 | 75,527 | 32 | 13 + intl |118119## Key Files120121| File | Purpose |122|------|---------|123| `~/.config/ceres/portals.toml` | Portal config — controls name resolution during export |124| `~/ceres-open-data-index/README.md` | HuggingFace dataset card |125| `~/ceres-open-data-index/metadata.json` | Versioned snapshot manifest (generated, do not edit) |126| `~/ceres-open-data-index/reports.json` | Coverage and quality report (generated, do not edit) |127| `~/ceres-open-data-index/report.md` | Human-readable coverage/quality summary (generated) |128| `crates/ceres-core/src/parquet_export.rs` | Export logic (noise filter, duplicate flagging, flattening, manifest, reports) |129| `crates/ceres-db/src/repository.rs` | SQL queries (duplicate detection, dataset streaming) |130131## HuggingFace Space (Optional)132133The visualization dashboard at `https://huggingface.co/spaces/AndreaBozzo/Ceres` is a static Plotly site generated from the exported data.134135- **Location:** `~/Documenti/Ceres(huggingfacespace)`136- **Generator:** `~/Documenti/open-data-galaxy/galaxy_constellation.py`137- Regenerate after pushing the dataset, then push the Space repo separately138139## Pre-export Data Quality Checks140141```bash142# Overall stats143ceres stats144145# Check noise candidates in DB (optional)146docker exec ceres_db psql -U ceres_user -d ceres_db -c "147 SELECT COUNT(*) FILTER (WHERE LENGTH(title) < 5) as tiny_titles,148 COUNT(*) FILTER (WHERE description IS NULL OR TRIM(description) = '') as empty_desc,149 COUNT(*) FILTER (WHERE LOWER(title) LIKE '%test%' OR LOWER(title) LIKE '%prova%' OR LOWER(title) LIKE '%esempio%') as noise_titles150 FROM datasets WHERE NOT is_stale;151"152```