Guide 2 — Describing a cell#

A cell spec is the product datasheet, as data. This guide authors one at datasheet level and publishes it, then gives a taste of the material-level depth BattINFO can carry — Guide 5 goes all the way down.

Estimated time: 15 minutes Prerequisites: Guide 1 — Concepts

Everything this guide writes lands in _scratch/guide-02/.

[1]:
import json
from pathlib import Path

# This notebook runs from its own folder (docs/guides). Everything it
# writes lands in a throwaway scratch folder next to it.
SCRATCH = Path("_scratch/guide-02").resolve()
SCRATCH.mkdir(parents=True, exist_ok=True)

Part 1 — The datasheet path#

Use this when you have a datasheet and want to describe a commercial cell quickly.

[2]:
from battinfo import CellSpec, publish

cell_spec = CellSpec(
    manufacturer="Samsung SDI",
    model="INR21700-50E",
    format="cylindrical",
    chemistry="Li-ion",
    positive_electrode_basis="NMC",
    negative_electrode_basis="graphite",
    size_code="R21700",
    source={
        "type": "datasheet",
        "name": "INR21700-50E Product Specification Rev 1.0",
        "url": "https://example.com/samsung-inr21700-50e.pdf",
    },
    properties={
        "nominal_capacity":                      {"value": 5.0,   "unit": "Ah"},
        "nominal_voltage":                       {"value": 3.6,   "unit": "V"},
        "rated_energy":                          {"value": 18.0,  "unit": "Wh"},
        "mass":                                  {"value": 68.0,  "unit": "g"},
        "height":                                {"value": 70.15, "unit": "mm"},
        "diameter":                              {"value": 21.0,  "unit": "mm"},
        "maximum_continuous_charging_current":   {"value": 4.875, "unit": "A"},
        "maximum_continuous_discharging_current":{"value": 9.8,   "unit": "A"},
        "maximum_charging_temperature":          {"value": 45,    "unit": "°C"},
        "minimum_discharging_temperature":       {"value": -20,   "unit": "°C"},
        "cycle_life":                            {"value_text": ">500", "unit": "count"},
    },
)

result = publish(cell_spec, destination="local", root=SCRATCH)
print("IRI:", result.canonical_iri)
IRI: https://w3id.org/battinfo/spec/03yx-y1kk-q3mq-ck3w
<repo>\src\battinfo\transform\cell_spec_node.py:337: UserWarning: semantic.value_text_only: 'cycle_life' on https://w3id.org/battinfo/spec/03yx-y1kk-q3mq-ck3w carries only value_text - the JSON-LD export emits no numeric quantity value for it.
  property_nodes = cell_spec_property_nodes(
<repo>\src\battinfo\transform\cell_spec_node.py:337: UserWarning: semantic.value_text_only: 'cycle_life' on https://w3id.org/battinfo/spec/03yx-y1kk-q3mq-ck3w carries only value_text - the JSON-LD export emits no numeric quantity value for it.
  property_nodes = cell_spec_property_nodes(
[3]:
# Inspect the canonical record
record = json.loads(Path(result.debug_paths["canonical_record_path"]).read_text(encoding="utf-8"))
print(json.dumps(record, indent=2))
{
  "schema_version": "0.2.0",
  "cell_spec": {
    "id": "https://w3id.org/battinfo/spec/03yx-y1kk-q3mq-ck3w",
    "short_id": "03yxy1",
    "identifier": "cell-spec:03yx-y1kk-q3mq-ck3w",
    "name": "Samsung SDI INR21700-50E",
    "model": "INR21700-50E",
    "manufacturer": {
      "type": "Organization",
      "name": "Samsung SDI"
    },
    "cell_format": "cylindrical",
    "chemistry": "Li-ion",
    "positive_electrode_basis": "NMC",
    "negative_electrode_basis": "graphite",
    "size_code": "R21700"
  },
  "properties": {
    "nominal_capacity": {
      "value": 5.0,
      "unit": "Ah"
    },
    "nominal_voltage": {
      "value": 3.6,
      "unit": "V"
    },
    "rated_energy": {
      "value": 18.0,
      "unit": "Wh"
    },
    "mass": {
      "value": 68.0,
      "unit": "g"
    },
    "height": {
      "value": 70.15,
      "unit": "mm"
    },
    "diameter": {
      "value": 21.0,
      "unit": "mm"
    },
    "maximum_continuous_charging_current": {
      "value": 4.875,
      "unit": "A"
    },
    "maximum_continuous_discharging_current": {
      "value": 9.8,
      "unit": "A"
    },
    "maximum_charging_temperature": {
      "value": 45,
      "unit": "\u00b0C"
    },
    "minimum_discharging_temperature": {
      "value": -20,
      "unit": "\u00b0C"
    },
    "cycle_life": {
      "value_text": ">500",
      "unit": "count"
    }
  },
  "provenance": {
    "source_type": "datasheet",
    "source_name": "INR21700-50E Product Specification Rev 1.0",
    "source_url": "https://example.com/samsung-inr21700-50e.pdf",
    "retrieved_at": 1783548126,
    "battinfo_version": "0.7.0"
  }
}
[4]:
# Produce the resolver JSON-LD
from battinfo.api import publish_record

output = publish_record(
    result.debug_paths["canonical_record_path"],
    target_root=SCRATCH / "resolver",
)
jsonld = json.loads(Path(output["output_dir"], "index.jsonld").read_text(encoding="utf-8"))

print("@type:", jsonld["@type"])
print()
print("Properties:")
for prop in jsonld.get("hasProperty", []):
    t = prop["@type"][0] if isinstance(prop["@type"], list) else prop["@type"]
    v = prop.get("hasNumericalPart", {}).get("hasNumberValue", "—")
    u = prop.get("hasMeasurementUnit", "").split("#")[-1]
    print(f"  {t}: {v} [{u}]")
<repo>\src\battinfo\transform\cell_spec_node.py:337: UserWarning: semantic.value_text_only: 'cycle_life' on https://w3id.org/battinfo/spec/03yx-y1kk-q3mq-ck3w carries only value_text - the JSON-LD export emits no numeric quantity value for it.
  property_nodes = cell_spec_property_nodes(
<repo>\src\battinfo\transform\cell_spec_node.py:337: UserWarning: semantic.value_text_only: 'cycle_life' on https://w3id.org/battinfo/spec/03yx-y1kk-q3mq-ck3w carries only value_text - the JSON-LD export emits no numeric quantity value for it.
  property_nodes = cell_spec_property_nodes(
@type: ['BatteryCellSpecification', 'schema:CreativeWork']

Properties:
  NominalCapacity: 5.0 [AmpereHour]
  NominalVoltage: 3.6 [Volt]
  NominalEnergy: 18.0 [WattHour]
  Mass: 68.0 [Gram]
  Height: 70.15 [MilliMetre]
  Diameter: 21.0 [MilliMetre]
  MaximumContinuousChargingCurrent: 4.875 [Ampere]
  MaximumContinuousDischargingCurrent: 9.8 [Ampere]
  MaximumChargingTemperature: 45 [EMMO_36a9bf69_483b_42fd_8a0c_7ac9206320bc]
  MinimumDischargingTemperature: -20 [EMMO_36a9bf69_483b_42fd_8a0c_7ac9206320bc]
  CycleLife: — [EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978]

Part 2 — A taste of material-level description#

Datasheet fields stop at the can. For research cells you often know what is inside — and BattINFO applies the same spec-and-instance thinking to materials that it applies to cells:

  • a component (material(...)) describes a constituent in the context of this cell — its role and its fraction of a coating or a mixture;

  • a material spec is a standalone, reusable record for the substance itself, with its own IRI, shared by every cell that uses it.

Salts are materials like any other constituent — they take the same material(...) form as solvents, with the concentration carried inline:

[5]:
from battinfo.authoring import electrolyte_recipe, material

lp30 = electrolyte_recipe(
    family="organic",
    salt=material("LiPF6", concentration={"value": 1.0, "unit": "mol/L"}),
    solvents=[
        material("EC",  volume_fraction={"value": 50, "unit": "%"}),
        material("DMC", volume_fraction={"value": 50, "unit": "%"}),
    ],
    comment="LP30",
)

print("Salt:    ", lp30.salt.name, lp30.salt.property.get("concentration"))
print("Solvents:", [s.name for s in lp30.solvent_mixture.component])
Salt:     LiPF6 {'value': 1.0, 'unit': 'mol/L'}
Solvents: ['EC', 'DMC']

Any component can be lifted into a standalone material spec — a record with a deterministic IRI (the same material always mints the same IRI, so specs deduplicate across cells) — and the component then references it:

[6]:
from battinfo.materials import link_component_to_spec, material_spec_from_component

lipf6 = material_spec_from_component(lp30.salt, material_class="electrolyte_salt")
lipf6_id = lipf6["material_spec"]["id"]
print("Material-spec IRI:", lipf6_id)

linked_salt = link_component_to_spec(lp30.salt, lipf6_id)
print("Component reference:", linked_salt["material_spec_id"])
Material-spec IRI: https://w3id.org/battinfo/spec/xcv1-hpy1-b0bw-z5s2
Component reference: https://w3id.org/battinfo/spec/xcv1-hpy1-b0bw-z5s2

That is the whole model — the full bottom-up build (electrode bills of materials, loadings, separator, construction, and the rich JSON-LD graph they produce) is Guide 5 — Cell descriptors.

Next#