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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Cells and figures:
|---|---|
| `list_instruments` | loaders, and whether each can actually run on this machine |
| `find_cells` | local files by project + number range; empty cellpy → `offer_raw` |
| `load_cell` | a handle, cycle count, mass, summary column names |
| `load_cell` | a handle, cycle count, mass, optional nominal_capacity, summary column names |
| `list_cells` | what is loaded |
| `describe_plot_families` | the 20 summary families, marked available or missing-columns |
| `collect` | a handle, row count, columns, `is_grouped`, direction counts |
Expand Down
9 changes: 9 additions & 0 deletions src/cellpy_mcp/cells.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,17 @@ def load_cell(
path: str,
instrument: str | None = None,
mass_mg: float | None = None,
nominal_capacity: float | str | None = None,
) -> dict:
"""Load one cell file and return a handle plus what the data can support.

`mass_mg` is the active-material mass. Without it every `*_gravimetric`
column is computed against a default of 1.0 mg — the numbers still
appear, they are simply wrong, so supply it when you know it.

`nominal_capacity` is passed to `cellpy.get` as-is: a number uses
cellpy units (default mAh/g); a string may carry a unit. There is no
lab default — omit it to keep whatever cellpy already uses.
"""
import cellpy

Expand All @@ -139,6 +144,8 @@ def load_cell(
kwargs["instrument"] = instrument
if mass_mg is not None:
kwargs["mass"] = mass_mg
if nominal_capacity is not None:
kwargs["nominal_capacity"] = nominal_capacity

cell = cellpy.get(**kwargs)
handle = state.handle("cell")
Expand All @@ -153,6 +160,8 @@ def load_cell(
"last_cycle": cycles[-1] if cycles else None,
"mass_mg": cell.mass,
"mass_was_supplied": mass_mg is not None,
"nominal_capacity": cell.nominal_capacity,
"nominal_capacity_was_supplied": nominal_capacity is not None,
# Names only. The frame itself stays here.
"summary_columns": sorted(cell.data.summary.columns),
}
Expand Down
76 changes: 76 additions & 0 deletions tests/test_cell_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,82 @@ async def steps(call):
assert "cellpy" in drive(steps)["refused"]


class _FakeLoadedCell:
cell_name = "demo"
mass = 2.1
nominal_capacity = 320

class data:
class summary:
columns = []

def get_cycle_numbers(self):
return [1]


def test_load_cell_forwards_nominal_capacity(drive, root, monkeypatch):
(root / "demo.cellpy").write_text("x")
seen = {}

def fake_get(**kwargs):
seen.update(kwargs)
return _FakeLoadedCell()

monkeypatch.setattr("cellpy.get", fake_get)

async def steps(call):
return await call(
"load_cell", path="demo.cellpy", mass_mg=2.1, nominal_capacity=320
)

out = drive(steps)
assert seen["mass"] == 2.1
assert seen["nominal_capacity"] == 320
assert out["nominal_capacity"] == 320
assert out["nominal_capacity_was_supplied"] is True


def test_load_cell_omits_nominal_capacity_when_not_given(drive, root, monkeypatch):
(root / "demo.cellpy").write_text("x")
seen = {}

def fake_get(**kwargs):
seen.update(kwargs)
return _FakeLoadedCell()

monkeypatch.setattr("cellpy.get", fake_get)

async def steps(call):
return await call("load_cell", path="demo.cellpy", mass_mg=2.1)

out = drive(steps)
assert "nominal_capacity" not in seen
assert out["nominal_capacity_was_supplied"] is False


def test_load_cell_forwards_nominal_capacity_string_with_unit(drive, root, monkeypatch):
(root / "demo.cellpy").write_text("x")
seen = {}

def fake_get(**kwargs):
seen.update(kwargs)
return _FakeLoadedCell()

monkeypatch.setattr("cellpy.get", fake_get)

async def steps(call):
return await call(
"load_cell",
path="demo.cellpy",
mass_mg=2.1,
nominal_capacity="320 mAh/g",
)

out = drive(steps)
assert seen["nominal_capacity"] == "320 mAh/g"
assert out["nominal_capacity_was_supplied"] is True


def test_the_whole_arc(drive, demo_cell, root):
"""Load -> collect -> render -> export, as a client actually does it."""

Expand Down
Loading