Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .issueflows/03-solved-issues/issue151_original.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Issue #151: CellMeta.uuid: cell-level join key for external metadata sources

Source: https://github.com/cellpy/cellpy-core/issues/151
Labels: enhancement
Captured: 2026-09-26

---

## Context

jepegit/cellpy#784 (Epic M, external metadata sources) adds a `MetadataSource` Protocol and a persisted back-link (`ExternalLink`: `source_name`, `external_id`, `source_uri`) on the cellpy side. The design (cellpy-design-and-development `active/cellpy2-metadata-source-integration.md` §3.3, metadata-plan OQ6) also parks a **cell-level** id — `CellMeta.uuid` — next to the existing `TestMeta.uuid`, so a physical cell can be linked to a lab database record independently of any one test run.

`CellMeta` lives in `cellpycore.metadata.models`, so this is a core-first change (then PyPI release, then cellpy re-pin per jepegit/cellpy Epic S).

## Spec

- Add `uuid: Optional[str] = None` to `CellMeta` (same conventions as `TestMeta.uuid`: minted by the consumer, never required, round-trips through `to_dict` / `from_dict` and the legacy mapping tables as a core-only field).
- Add `"uuid"` to `meta_mapping.CORE_ONLY_CELL` so `apply_test_meta_to_legacy` in cellpy skips it.
- Update `docs/specifications/harmonized-raw.md` CellMeta table.

## Acceptance

- `CellMeta(uuid="…")` round-trips through the metadata archive helpers.
- Existing cellpy tests that build `CellMeta()` are unaffected (default `None`).
- Release note entry; cellpy re-pin tracked in jepegit/cellpy.
18 changes: 18 additions & 0 deletions .issueflows/03-solved-issues/issue151_status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Status: #151 CellMeta.uuid

- [x] Done

## What was done (2026-09-26)

- `CellMeta.uuid: Optional[str] = None` (docstring: cell-level id, consumer-minted,
distinct from `TestMeta.uuid`).
- `legacy.meta_mapping.CORE_ONLY_CELL` gains `"uuid"` so totality tests hold and
cellpy's `apply_test_meta_to_legacy` skips it.
- Spec table in `docs/specifications/harmonized-raw.md` (core-only CellMeta fields).
- Tests: JSON round-trip + core-only mapping; suite 289 passed, ruff clean.
- HISTORY.md `[Unreleased]` entry.

## Follow-up (not here)

PyPI release of cellpy-core and cellpy re-pin (jepegit/cellpy Epic S); cellpy's
BatBase adapter can then set `cell.uuid` from the journal row.
3 changes: 3 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- `CellMeta.uuid`: optional cell-level id (join key for external metadata sources),
core-only in the legacy mapping so `apply_test_meta_to_legacy` skips it. (#151)

## [0.2.6] - 2026-09-08

- `update_core_data` treats an empty `new_raw` as a no-op (skips the derived refresh
Expand Down
7 changes: 7 additions & 0 deletions docs/specifications/harmonized-raw.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ geometry) and `CellpyMetaIndividualTest` (test-dependent: `channel_index`, `crea
full field set; they can sit in `TestMeta` or a sibling `CellMeta` record (decision
deferred — see the design note).

The `CellMeta` dataclass additionally carries two core-only fields legacy never had:

| Field | Data type | Unit | Sample data | Comment |
| --- | --- | --- | --- | --- |
| uuid | str(36) | - | "0d1f3a7c-…" | stable, globally-unique id for the **physical cell**, independent of any test run (`TestMeta.uuid` identifies the run); minted by the consumer, e.g. to join the cell to a lab-database record (issue #151, jepegit/cellpy#784) |
| volume | float | cm**3 | 0.12 | cell / electrode volume for the volumetric specific-capacity mode (issue #117) |

## Follow-ups
- **step_types**
- 'charge', 'discharge', 'rest'
Expand Down
5 changes: 3 additions & 2 deletions src/cellpycore/legacy/meta_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,9 @@
}

# Core fields legacy never had. Migration fills them from context (provenance
# stamped by the loading framework; ``volume`` is the new volumetric-mode field).
CORE_ONLY_CELL = frozenset({"volume"})
# stamped by the loading framework; ``volume`` is the new volumetric-mode field;
# ``uuid`` is the cell-level join key for external metadata sources, #151).
CORE_ONLY_CELL = frozenset({"volume", "uuid"})
CORE_ONLY_TEST = frozenset(
{
"uuid",
Expand Down
6 changes: 6 additions & 0 deletions src/cellpycore/metadata/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ class CellMeta:
``CellMeta()`` is valid scaffolding and carries no obligation to be populated.

Attributes:
uuid: Stable, globally-unique id for the physical cell, independent of
any one test run (``TestMeta.uuid`` identifies the run). Minted by
the consumer — typically to join the cell to a lab-database record
(external metadata sources, jepegit/cellpy#784) — never required
(issue #151).
material: Active-material name / identifier.
mass: Active-material mass.
tot_mass: Total material mass.
Expand All @@ -95,6 +100,7 @@ class CellMeta:
comment: Free-text comment.
"""

uuid: Optional[str] = None
material: Optional[str] = None
mass: Optional[float] = None
tot_mass: Optional[float] = None
Expand Down
7 changes: 7 additions & 0 deletions tests/test_meta_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,10 @@ class LegacyCommon:
cell_empty, test_empty = meta_mapping.legacy_meta_to_core(None, None)
assert cell_empty == CellMeta()
assert test_empty == TestMeta()


def test_cell_uuid_is_core_only():
"""``CellMeta.uuid`` is never filled from legacy meta (issue #151)."""
assert "uuid" in meta_mapping.CORE_ONLY_CELL
cell, _ = meta_mapping.legacy_meta_to_core({"mass": 1.0}, {})
assert cell.uuid is None
13 changes: 13 additions & 0 deletions tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,16 @@ def test_merge_without_renumber_raises_on_collision():
def test_persistence_stubs_raise_not_implemented(call):
with pytest.raises(NotImplementedError):
call()


def test_cell_meta_uuid_round_trips_and_defaults_to_none():
"""CellMeta.uuid is a consumer-minted cell-level id (issue #151)."""
assert CellMeta().uuid is None
meta = TestMeta(
test_id=0,
uuid="test-run-uuid",
cell=CellMeta(uuid="0d1f3a7c-cell", mass=1.0),
)
restored = from_json(TestMeta, to_json(meta))
assert restored.cell.uuid == "0d1f3a7c-cell"
assert restored.uuid == "test-run-uuid" # test-run id stays separate
Loading