Metadata#
BDF writes rich metadata alongside each dataset (as JSON-LD embedded in an HTML landing page and/or sidecar files), so data are self-describing and web-discoverable.
Design Principles#
BDF favors schema.org for maximum interoperability on the semantic web (and rich presentation in Dataset Search), while also supporting CSVW for precise tabular schemas. Quantity meanings are grounded in the BDF application ontology to keep terminology consistent across tools and datasets.
Content#
BDF metadata covers three main categories:
Bibliographic (who/what/when). This includes information about what the dataset is and who made it. The purpose of bibliographic metadata is to enable proper citations, credit, reproducibility, and searching. It features fields like:
title, description, keywords
creators and contributors
date, version, and license
provenance
Content (what’s in the table). This describes the quantities in your dataset so tools and search engines can understand them. BDF supports descriptions of table quantities using both schema.org and csvw markup. This mapping is handled automatically by BDF.
Distribution (where and how to get the file). This describes the actual downloadable artifact(s). It includes files like:
file URL(s), media type (e.g., text/csv, application/parquet)
size/checksums (optional), content variants (raw/processed)
landing page vs. direct download (schema.org/DataDownload)
import bdf
from bdf.metadata import Dataset, Creator, DataDownload, Battery
# Read the raw source data and display the header
# This export stores its time columns in milliseconds under seconds headers;
# bdf.read detects the mismatch against the recorded timestamps and raises
# unless we explicitly opt into the repair with reconcile_time=True.
df, source_meta = bdf.read(
"https://zenodo.org/records/17295469/files/FZJ__INR21700__20250606__HPPC__25degC__Digatron.csv",
reconcile_time=True,
)
df = df.to_pandas()
df.head()
/home/runner/work/battery-data-format/battery-data-format/src/bdf/table_parsers.py:195: UserWarning: tz defaulted to UTC; pass tz=... if data was recorded in a different timezone
result = self.normalizer.normalize(
/tmp/ipykernel_2428/1333297562.py:5: UserWarning: 'Test Time / s' values appear to be milliseconds, not the declared seconds (increments disagree with 'Unix Time / s' by ~1000x); rescaled to seconds as requested (reconcile_time=True). Recorded in metadata['time_reconciliation'].
df, source_meta = bdf.read(
/tmp/ipykernel_2428/1333297562.py:5: UserWarning: 'Step Time / s' values appear to be milliseconds, not the declared seconds (increments disagree with 'Unix Time / s' by ~1000x); rescaled to seconds as requested (reconcile_time=True). Recorded in metadata['time_reconciliation'].
df, source_meta = bdf.read(
| Test Time / s | Voltage / V | Current / A | Unix Time / s | Step ID | Step Type | Ambient Temperature / degC | Step Time / s | Charging Capacity / Ah | Discharging Capacity / Ah | Net Capacity / Ah | Step Cumulative Capacity / Ah | Charging Energy / Wh | Discharging Energy / Wh | Net Energy / Wh | Step Cumulative Energy / Wh | Temperature T1 / degC | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.640 | 3.738163 | 0.000000 | 1.749201e+09 | 4 | CHA | 25.0625 | 0.003 | 0.000000 | 0.0 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 0.000000 | 25.8125 |
| 1 | 1.649 | 3.814422 | 4.999872 | 1.749201e+09 | 4 | CHA | 25.0625 | 1.012 | 0.001396 | 0.0 | 0.001396 | 0.001396 | 0.005320 | 0.0 | 0.005320 | 0.005320 | 25.8125 |
| 2 | 2.647 | 3.818646 | 4.999872 | 1.749201e+09 | 4 | CHA | 25.0625 | 2.010 | 0.002780 | 0.0 | 0.002780 | 0.002780 | 0.010605 | 0.0 | 0.010605 | 0.010605 | 25.8125 |
| 3 | 3.651 | 3.822092 | 4.999872 | 1.749201e+09 | 4 | CHA | 25.0625 | 3.014 | 0.004176 | 0.0 | 0.004176 | 0.004176 | 0.015937 | 0.0 | 0.015937 | 0.015937 | 25.7500 |
| 4 | 4.649 | 3.825094 | 4.999872 | 1.749201e+09 | 4 | CHA | 25.0625 | 4.012 | 0.005562 | 0.0 | 0.005562 | 0.005562 | 0.021237 | 0.0 | 0.021237 | 0.021237 | 25.7500 |
meta = Dataset(
title="Digatron INR21700 HPPC",
creators=[Creator(name="Example Creator", orcid="0000-0002-0000-0010", given_name="Example", family_name="Creator", affiliation="Your Lab")],
description="HPPC characterization of an INR21700 cell at 25°C on a Digatron cycler.",
keywords=["li-ion", "inr21700", "hppc"],
license="CC-BY-4.0",
url="https://zenodo.org/records/17295469",
version="1.0.0",
publication_date="2025-06-06",
)
dist = DataDownload(
url="https://zenodo.org/records/17295469/files/FZJ__INR21700__20250606__HPPC__25degC__Digatron.csv",
name="Digatron CSV file",
encoding_format="text/csv",
description="Primary CSV export from Digatron cycler."
)
battery = Battery(
id="g20m7",
model = "G20M7",
manufacturer = "Google",
iec_code = "ICP6/65/75",
form_factor = "prismatic",
nominal_voltage_v = 3.90,
rated_capacity_ah = 4.835,
mass_g = 63,
volume_l = 0.02925,
pe_materials = ["cobalt"],
ne_materials = ["graphite"]
)
# Save the metadata to JSON-LD
meta.save_jsonld(
"./out/metadata/dataset.schemaorg.jsonld",
dataset_uri="https://doi.org/10.5281/zenodo.16994937#digatron-csv-li-ion-hppc",
identifier="digatron-csv-li-ion-hppc",
distributions=[dist],
df=df
)
PosixPath('out/metadata/dataset.schemaorg.jsonld')
# Save the metadata as rich results html for Google / Semantic Web integration
meta.save_rich_results_html(
"./out/metadata/dataset.schemaorg.html",
title="Digatron INR21700 HPPC",
graphify=True,
dataset_uri="https://doi.org/10.5281/zenodo.16994937#digatron-csv-li-ion-hppc",
identifier="digatron-csv-li-ion-hppc",
distributions=[dist],
df=df
)
PosixPath('out/metadata/dataset.schemaorg.html')