Guide 5 — Cell descriptors#

Cell descriptors go beyond datasheet-level specifications. They capture electrode composition, electrolyte formulation, separator properties, and cell construction — the detail needed for research-grade records and curated cell libraries.

Estimated time: 40 minutes Prerequisites: Guide 2 — Describing a cell

Everything writes under _scratch/guide-05/.

New words? The plain-language glossary decodes EMMO, IRI, JSON-LD, and the rest in two sentences each.

[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-05").resolve()
SCRATCH.mkdir(parents=True, exist_ok=True)

Step 1: Individual materials#

[2]:
from battinfo.authoring import material, properties

# Positive electrode
lfp   = material("LFP",          mass_fraction={"value": 90,  "unit": "%"},
                  comment="LiFePO4, olivine structure, D50 ~ 3 µm (Aleees GEN2)")
pvdf  = material("PVDF",         mass_fraction={"value":  5,  "unit": "%"})
c65   = material("Carbon black", mass_fraction={"value":  5,  "unit": "%"},
                  comment="Imerys C65")

# Negative electrode
graphite = material("Graphite",  mass_fraction={"value": 95.5, "unit": "%"},
                     comment="Natural graphite, D50 ~ 20 µm")
cmc  = material("CMC", mass_fraction={"value": 1.5, "unit": "%"})
sbr  = material("SBR", mass_fraction={"value": 2.5, "unit": "%"})
c_ng = material("Carbon black", mass_fraction={"value": 0.5, "unit": "%"})

print("Materials defined.")
Materials defined.

Step 2: Bills of materials#

[3]:
from battinfo.authoring import bom

positive_bom = bom(active_material=lfp,      binder=pvdf,       additive=c65)
negative_bom = bom(active_material=graphite, binder=[cmc, sbr], additive=c_ng)

print("Positive active:", positive_bom.active_material[0].name)
print("Negative binders:", [m.name for m in negative_bom.binder])
Positive active: LFP
Negative binders: ['CMC', 'SBR']

Step 3: Electrodes#

[4]:
from battinfo.authoring import electrode

positive_electrode = electrode(
    bom=positive_bom,
    loading={"value": 12.5, "unit": "mg/cm2"},
    calendered_density={"value": 2.4, "unit": "g/cm3"},
    current_collector="Aluminium foil",
    current_collector_thickness={"value": 16.0, "unit": "µm"},
    coating_comment="NMP-cast, roll-calendered to 85% theoretical density",
)

negative_electrode = electrode(
    bom=negative_bom,
    loading={"value": 7.8, "unit": "mg/cm2"},
    calendered_density={"value": 1.55, "unit": "g/cm3"},
    current_collector="Copper foil",
    current_collector_thickness={"value": 10.0, "unit": "µm"},
)

print("Positive loading:", positive_electrode.coating.property.get("loading"))
print("Negative density:", negative_electrode.coating.property.get("calendered_density"))
Positive loading: {'value': 12.5, 'unit': 'mg/cm2'}
Negative density: {'value': 1.55, 'unit': 'g/cm3'}

Step 4: Electrolyte#

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

# Salts are materials like any other constituent: the same material(...) form
# as solvents, with the concentration carried inline.
lp30 = electrolyte_recipe(
    family="organic",
    salt=mat("LiPF6", concentration={"value": 1.0, "unit": "mol/L"}),
    solvents=[
        mat("EC",  volume_fraction={"value": 50, "unit": "%"}),
        mat("DMC", volume_fraction={"value": 50, "unit": "%"}),
    ],
    additives=[
        mat("VC",  mass_fraction={"value": 2.0, "unit": "%"}, comment="SEI-forming additive"),
        mat("FEC", mass_fraction={"value": 1.0, "unit": "%"}, comment="Fluoroethylene carbonate"),
    ],
    comment="LP30 + 2% VC + 1% FEC",
)

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

Step 5: Separator#

[6]:
from battinfo.authoring import separator_spec

sep = separator_spec(
    material="Polypropylene",
    thickness={"value": 25.0, "unit": "µm"},
    properties=properties(porosity={"value": 41, "unit": "%"}),
    comment="Celgard 2500, single-layer PP",
)

print("Material:", sep.material)
print("Thickness:", sep.property.get("thickness"))
print("Porosity:", sep.property.get("porosity"))
Material: Polypropylene
Thickness: {'value': 25.0, 'unit': 'µm'}
Porosity: {'value': 41, 'unit': '%'}

Step 6: Construction#

[7]:
from battinfo.authoring import construction

build = construction(
    assembly_type="wound",
    layering="single_layer",
    layer_count=1,
    comment="Single jelly-roll, wound on 4 mm mandrel",
)

print("Assembly:", build.assembly_type, "/", build.layering)
Assembly: wound / single_layer

Step 7: Provenance#

[8]:
from battinfo.authoring import source

prov = source(
    type="datasheet",
    name="ANR26650M1-B Product Datasheet Rev B",
    url="https://example.com/a123-anr26650m1-b.pdf",
    retrieved_at=1746230400,
    curated_by="Jane Smith",
)

print("Source type:", prov.type)
print("Curated by:", prov.curated_by)
Source type: datasheet
Curated by: Jane Smith

Step 8: Cell description#

[9]:
from battinfo.authoring import cell_description

# Use the known IRI for the A123 ANR26650M1-B example cell spec.
# For new records: publish a CellSpec first to get the IRI, then use it here.
A123_IRI = "https://w3id.org/battinfo/spec/pge5-wer6-2q82-v9k0"

spec = cell_description(
    id=A123_IRI,
    manufacturer="A123 Systems",
    model="ANR26650M1-B",
    format="cylindrical",
    chemistry="Li-ion",
    positive_electrode_basis="LFP",
    negative_electrode_basis="graphite",
    size_code="R26650",
    positive_electrode=positive_electrode,
    negative_electrode=negative_electrode,
    electrolyte=lp30,
    separator=sep,
    construction=build,
    source=prov,
    comment=[
        "Descriptor curated from manufacturer datasheet and published literature.",
        "Electrode loadings estimated from capacity balance and assumed density.",
    ],
)

print("Cell ID:", spec.id)
print("Model:", spec.model)
Cell ID: https://w3id.org/battinfo/spec/pge5-wer6-2q82-v9k0
Model: ANR26650M1-B

Step 9: Materials as first-class records#

Every material embedded above can also stand alone. extract_material_specs walks the descriptor — electrode coatings, electrolyte, separator — and lifts each distinct material into a standalone material-spec record with a deterministic IRI (the same material always mints the same IRI, so specs deduplicate across cells). Components then reference their spec via material_spec_id — the same spec-and-instance pattern cells use:

[10]:
from battinfo.materials import extract_material_specs

material_specs = extract_material_specs(spec.model_dump())
print(f"{len(material_specs)} standalone material spec(s):")
for record in material_specs:
    m = record["material_spec"]
    print(f"  {m['name']:14s} {m.get('kind', '?'):16s} {m['id']}")
14 standalone material spec(s):
  LFP            lfp              https://w3id.org/battinfo/spec/6rq6-s3tx-b6ym-ht0g
  PVDF           pvdf             https://w3id.org/battinfo/spec/je56-4c60-jxdz-050c
  Carbon black   carbon_black     https://w3id.org/battinfo/spec/8yq1-rsc0-h6nh-3xhj
  Aluminium foil aluminium        https://w3id.org/battinfo/spec/1b8w-qcg7-th5c-841r
  Graphite       graphite         https://w3id.org/battinfo/spec/qdbk-w86d-ae1x-jxgz
  CMC            cmc              https://w3id.org/battinfo/spec/5k8a-bj67-7v5w-6vvd
  SBR            sbr              https://w3id.org/battinfo/spec/ttr0-rstn-9d7z-0c4w
  Copper foil    copper           https://w3id.org/battinfo/spec/axx1-0s0z-aa7g-yd1m
  LiPF6          lipf6            https://w3id.org/battinfo/spec/aeqc-tqax-m5f1-g6qq
  EC             ec               https://w3id.org/battinfo/spec/yb44-wr9q-hwc0-vwbw
  DMC            dmc              https://w3id.org/battinfo/spec/ymew-zf7h-kz96-gm2q
  VC             vc               https://w3id.org/battinfo/spec/cqgd-n9q6-vyxr-6mrv
  FEC            fec              https://w3id.org/battinfo/spec/v5nx-qbaq-gn9e-fn2w
  Polypropylene  polypropylene    https://w3id.org/battinfo/spec/w0f6-0bss-sdt2-fz11

Step 10: Serialise the descriptor#

[11]:
# Write the descriptor to disk for inspection and downstream use.
desc_dir = SCRATCH / "descriptors"
desc_dir.mkdir(parents=True, exist_ok=True)
desc_path = desc_dir / "a123-anr26650m1-b.descriptor.json"
spec_dict = {"schema_version": spec.schema_version, "specification": spec.to_json()}
desc_path.write_text(json.dumps(spec_dict, indent=2, ensure_ascii=False), encoding="utf-8")
print("Written:", desc_path)
print("Top-level keys:", list(spec_dict.keys())[:8])
Written: <repo>\docs\guides\_scratch\guide-05\descriptors\a123-anr26650m1-b.descriptor.json
Top-level keys: ['schema_version', 'specification']

Step 11: Map to domain-battery JSON-LD#

The spec node in the JSON-LD is the description of the product: its name, manufacturer and model. Everything physical, the cell’s EMMO classes and the electrodes, electrolyte and separator built above, sits on the described cell under isDescriptionFor, an individual named <spec IRI>#described. That is where to read the electrodes from:

[12]:
from battinfo.transform.json_to_jsonld import to_jsonld

descriptor_doc = json.loads(desc_path.read_text(encoding="utf-8"))
desc_jsonld = to_jsonld(descriptor_doc, target="domain-battery")

spec_node = desc_jsonld["@graph"][0]
described = spec_node["isDescriptionFor"]
print("Spec @type:          ", spec_node["@type"])
print("Described cell:      ", described["@id"])
print("Described cell @type:", described["@type"])
print()
print("Positive electrode @type:", described["hasPositiveElectrode"]["@type"])
print("Negative electrode @type:", described["hasNegativeElectrode"]["@type"])
print("Electrolyte @type:       ", described["hasElectrolyte"]["@type"])
print("Separator @type:         ", described["hasSeparator"]["@type"])
Spec @type:           ['BatteryCellSpecification', 'schema:ProductModel', 'schema:CreativeWork']
Described cell:       https://w3id.org/battinfo/spec/pge5-wer6-2q82-v9k0#described
Described cell @type: ['BatteryCell', 'CylindricalBattery', 'LithiumIonBattery', 'LithiumIonIronPhosphateBattery', 'LithiumIonGraphiteBattery']

Positive electrode @type: LithiumIronPhosphateElectrode
Negative electrode @type: GraphiteElectrode
Electrolyte @type:        OrganicElectrolyte
Separator @type:          ['Polypropylene', 'Separator']
[13]:
# Export the domain-battery JSON-LD to disk
desc_jsonld_path = SCRATCH / "descriptors" / "a123-anr26650m1-b.domain-battery.jsonld"
desc_jsonld_path.write_text(
    json.dumps(desc_jsonld, indent=2, ensure_ascii=False),
    encoding="utf-8",
)
print(f"Domain-battery JSON-LD exported to: {desc_jsonld_path}")
print()
print(json.dumps(desc_jsonld, indent=2))
Domain-battery JSON-LD exported to: <repo>\docs\guides\_scratch\guide-05\descriptors\a123-anr26650m1-b.domain-battery.jsonld

{
  "@context": [
    "https://w3id.org/emmo/domain/battery/context",
    {
      "schema": "https://schema.org/",
      "dcterms": "http://purl.org/dc/terms/"
    }
  ],
  "@graph": [
    {
      "@type": [
        "BatteryCellSpecification",
        "schema:ProductModel",
        "schema:CreativeWork"
      ],
      "isDescriptionFor": {
        "@type": [
          "BatteryCell",
          "CylindricalBattery",
          "LithiumIonBattery",
          "LithiumIonIronPhosphateBattery",
          "LithiumIonGraphiteBattery"
        ],
        "@id": "https://w3id.org/battinfo/spec/pge5-wer6-2q82-v9k0#described",
        "hasPositiveElectrode": {
          "hasCoating": {
            "@type": "ElectrodeCoating",
            "hasActiveMaterial": {
              "@type": [
                "LithiumIronPhosphate",
                "ActiveMaterial"
              ],
              "schema:name": "LFP",
              "hasProperty": {
                "@type": [
                  "MassFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "MassFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.9
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              },
              "schema:description": "LiFePO4, olivine structure, D50 ~ 3 \u00b5m (Aleees GEN2)"
            },
            "hasBinder": {
              "@type": [
                "PolyvinylideneFluoride",
                "Binder"
              ],
              "schema:name": "PVDF",
              "hasProperty": {
                "@type": [
                  "MassFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "MassFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.05
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              }
            },
            "hasConductiveAdditive": {
              "@type": [
                "CarbonBlack",
                "ConductiveAdditive"
              ],
              "schema:name": "Carbon black",
              "hasProperty": {
                "@type": [
                  "MassFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "MassFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.05
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              },
              "schema:description": "Imerys C65"
            },
            "hasProperty": [
              {
                "@type": [
                  "CalenderedDensity",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "CalenderedDensity",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 2.4
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#GramPerCubicCentiMetre"
              },
              {
                "@type": [
                  "ActiveMassLoading",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "ActiveMassLoading",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 12.5
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#MilliGramPerSquareCentiMetre"
              }
            ],
            "schema:description": "NMP-cast, roll-calendered to 85% theoretical density"
          },
          "hasCurrentCollector": {
            "@type": [
              "CurrentCollector",
              "Aluminium",
              "Foil"
            ],
            "schema:name": "Aluminium foil",
            "hasProperty": {
              "@type": [
                "Thickness",
                "ConventionalProperty"
              ],
              "skos:prefLabel": "Thickness",
              "hasNumericalPart": {
                "@type": "RealData",
                "hasNumberValue": 16.0
              },
              "hasMeasurementUnit": "https://w3id.org/emmo#MicroMetre"
            }
          },
          "@type": "LithiumIronPhosphateElectrode"
        },
        "hasNegativeElectrode": {
          "hasCoating": {
            "@type": "ElectrodeCoating",
            "hasActiveMaterial": {
              "@type": [
                "Graphite",
                "ActiveMaterial"
              ],
              "schema:name": "Graphite",
              "hasProperty": {
                "@type": [
                  "MassFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "MassFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.955
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              },
              "schema:description": "Natural graphite, D50 ~ 20 \u00b5m"
            },
            "hasBinder": [
              {
                "@type": [
                  "CarboxymethylCellulose",
                  "Binder"
                ],
                "schema:name": "CMC",
                "hasProperty": {
                  "@type": [
                    "MassFraction",
                    "ConventionalProperty"
                  ],
                  "skos:prefLabel": "MassFraction",
                  "hasNumericalPart": {
                    "@type": "RealData",
                    "hasNumberValue": 0.015
                  },
                  "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
                }
              },
              {
                "@type": [
                  "StyreneButadiene",
                  "Binder"
                ],
                "schema:name": "SBR",
                "hasProperty": {
                  "@type": [
                    "MassFraction",
                    "ConventionalProperty"
                  ],
                  "skos:prefLabel": "MassFraction",
                  "hasNumericalPart": {
                    "@type": "RealData",
                    "hasNumberValue": 0.025
                  },
                  "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
                }
              }
            ],
            "hasConductiveAdditive": {
              "@type": [
                "CarbonBlack",
                "ConductiveAdditive"
              ],
              "schema:name": "Carbon black",
              "hasProperty": {
                "@type": [
                  "MassFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "MassFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.005
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              }
            },
            "hasProperty": [
              {
                "@type": [
                  "CalenderedDensity",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "CalenderedDensity",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 1.55
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#GramPerCubicCentiMetre"
              },
              {
                "@type": [
                  "ActiveMassLoading",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "ActiveMassLoading",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 7.8
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#MilliGramPerSquareCentiMetre"
              }
            ]
          },
          "hasCurrentCollector": {
            "@type": [
              "CurrentCollector",
              "Copper",
              "Foil"
            ],
            "schema:name": "Copper foil",
            "hasProperty": {
              "@type": [
                "Thickness",
                "ConventionalProperty"
              ],
              "skos:prefLabel": "Thickness",
              "hasNumericalPart": {
                "@type": "RealData",
                "hasNumberValue": 10.0
              },
              "hasMeasurementUnit": "https://w3id.org/emmo#MicroMetre"
            }
          },
          "@type": "GraphiteElectrode"
        },
        "hasElectrolyte": {
          "@type": "OrganicElectrolyte",
          "hasSolute": {
            "@type": [
              "LithiumHexafluorophosphate",
              "Solute"
            ],
            "schema:name": "LiPF6",
            "hasProperty": {
              "@type": [
                "AmountConcentration",
                "ConventionalProperty"
              ],
              "skos:prefLabel": "AmountConcentration",
              "hasNumericalPart": {
                "@type": "RealData",
                "hasNumberValue": 1.0
              },
              "hasMeasurementUnit": "https://w3id.org/emmo#MolePerLitre"
            },
            "schema:inChIKey": "AXPLOJNSKRXQPA-UHFFFAOYSA-N",
            "schema:smiles": "[Li+].F[P-](F)(F)(F)(F)F",
            "schema:sameAs": {
              "@id": "https://pubchem.ncbi.nlm.nih.gov/compound/23688915"
            }
          },
          "hasSolvent": [
            {
              "@type": [
                "EthyleneCarbonate",
                "Solvent"
              ],
              "schema:name": "EC",
              "hasProperty": {
                "@type": [
                  "VolumeFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "VolumeFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.5
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              },
              "schema:inChIKey": "KMTRUDSVKNLOMY-UHFFFAOYSA-N",
              "schema:smiles": "C1COC(=O)O1",
              "schema:sameAs": {
                "@id": "https://pubchem.ncbi.nlm.nih.gov/compound/7303"
              }
            },
            {
              "@type": [
                "DimethylCarbonate",
                "Solvent"
              ],
              "schema:name": "DMC",
              "hasProperty": {
                "@type": [
                  "VolumeFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "VolumeFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.5
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              },
              "schema:inChIKey": "IEJIGPNLZYLLBP-UHFFFAOYSA-N",
              "schema:smiles": "COC(=O)OC",
              "schema:sameAs": {
                "@id": "https://pubchem.ncbi.nlm.nih.gov/compound/12021"
              }
            }
          ],
          "hasAdditive": [
            {
              "@type": [
                "VinyleneCarbonate",
                "ElectrolyteAdditive"
              ],
              "schema:name": "VC",
              "hasProperty": {
                "@type": [
                  "MassFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "MassFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.02
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              },
              "schema:inChIKey": "VAYTZRYEBVHVLE-UHFFFAOYSA-N",
              "schema:smiles": "C1=COC(=O)O1",
              "schema:sameAs": {
                "@id": "https://pubchem.ncbi.nlm.nih.gov/compound/13385"
              },
              "schema:description": "SEI-forming additive"
            },
            {
              "@type": [
                "FluoroethyleneCarbonate",
                "ElectrolyteAdditive"
              ],
              "schema:name": "FEC",
              "hasProperty": {
                "@type": [
                  "MassFraction",
                  "ConventionalProperty"
                ],
                "skos:prefLabel": "MassFraction",
                "hasNumericalPart": {
                  "@type": "RealData",
                  "hasNumberValue": 0.01
                },
                "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
              },
              "schema:inChIKey": "SBLRHMKNNHXPHG-UHFFFAOYSA-N",
              "schema:smiles": "C1C(OC(=O)O1)F",
              "schema:sameAs": {
                "@id": "https://pubchem.ncbi.nlm.nih.gov/compound/2769656"
              },
              "schema:description": "Fluoroethylene carbonate"
            }
          ],
          "schema:description": "LP30 + 2% VC + 1% FEC"
        },
        "hasSeparator": {
          "@type": [
            "Polypropylene",
            "Separator"
          ],
          "schema:name": "Polypropylene",
          "rdfs:comment": "Celgard 2500, single-layer PP",
          "hasProperty": [
            {
              "@type": [
                "Porosity",
                "ConventionalProperty"
              ],
              "skos:prefLabel": "Porosity",
              "hasNumericalPart": {
                "@type": "RealData",
                "hasNumberValue": 0.41
              },
              "hasMeasurementUnit": "https://w3id.org/emmo#EMMO_5ebd5e01_0ed3_49a2_a30d_cd05cbe72978"
            },
            {
              "@type": [
                "Thickness",
                "ConventionalProperty"
              ],
              "skos:prefLabel": "Thickness",
              "hasNumericalPart": {
                "@type": "RealData",
                "hasNumberValue": 25.0
              },
              "hasMeasurementUnit": "https://w3id.org/emmo#MicroMetre"
            }
          ],
          "schema:description": "Celgard 2500, single-layer PP"
        },
        "schema:additionalProperty": [
          {
            "@type": "schema:PropertyValue",
            "schema:propertyID": "construction.assembly_type",
            "schema:name": "Assembly Type",
            "schema:value": "wound"
          },
          {
            "@type": "schema:PropertyValue",
            "schema:propertyID": "construction.layering",
            "schema:name": "Layering",
            "schema:value": "single_layer"
          },
          {
            "@type": "schema:PropertyValue",
            "schema:propertyID": "construction.layer_count",
            "schema:name": "Layer Count",
            "schema:value": 1
          },
          {
            "@type": "schema:PropertyValue",
            "schema:propertyID": "construction.comment",
            "schema:name": "Construction Comment",
            "schema:value": "Single jelly-roll, wound on 4 mm mandrel"
          }
        ]
      },
      "@id": "https://w3id.org/battinfo/spec/pge5-wer6-2q82-v9k0",
      "schema:name": "A123 Systems ANR26650M1-B",
      "schema:model": "ANR26650M1-B",
      "schema:manufacturer": {
        "@type": "schema:Organization",
        "schema:name": "A123 Systems"
      },
      "schema:size": "R26650",
      "schema:description": [
        "Descriptor curated from manufacturer datasheet and published literature.",
        "Electrode loadings estimated from capacity balance and assumed density."
      ],
      "schema:schemaVersion": "0.2.0"
    }
  ]
}

Step 12: Validate the descriptor JSON-LD#

[14]:
from battinfo.validate.jsonld import validate_jsonld_report

# Validate the domain-battery JSON-LD produced from the descriptor
report = validate_jsonld_report(desc_jsonld)
print("ok:", report.ok, "| errors:", len(report.errors), "| warnings:", len(report.warnings))
for issue in report.issues:
    print(f"  [{issue.severity}] {issue.code}: {issue.message}")
ok: True | errors: 0 | warnings: 0

Step 13: Publish#

The descriptor is the cell-spec record — publish it directly and the composition travels with it (note the positive_electrode key in the saved record, and that the IRI set in Step 8 is preserved):

[15]:
from battinfo import publish
from battinfo.api import publish_record

pub = publish(spec, destination="local", root=SCRATCH / "published")
record = json.loads(Path(pub.debug_paths["canonical_record_path"]).read_text(encoding="utf-8"))

print("IRI:         ", pub.canonical_iri)
print("Record keys: ", list(record.keys()))
IRI:          https://w3id.org/battinfo/spec/pge5-wer6-2q82-v9k0
Record keys:  ['schema_version', 'cell_spec', 'properties', 'provenance', 'notes', 'construction', 'positive_electrode', 'negative_electrode', 'electrolyte', 'separator']
[16]:
output = publish_record(
    pub.debug_paths["canonical_record_path"],
    target_root=SCRATCH / "resolver",
)
jsonld_out = json.loads(
    Path(output["output_dir"], "index.jsonld").read_text(encoding="utf-8")
)

print("@type:", jsonld_out["@type"])
@type: ['BatteryCellSpecification', 'schema:ProductModel', 'schema:CreativeWork']

Next#

Return to Guide 3 — Linked records to connect this descriptor cell spec to instances, tests, and datasets.