Cell Measurement¶
Client for managing cell measurements and time series data uploads.
For usage examples and guides, see Data API on docs.ionworks.com.
Cell measurement client for managing battery cell test data.
This module provides the CellMeasurementClient for uploading,
retrieving, and managing measurement data from battery cell testing. It
supports time series data (signed URL upload, redirect-based download),
file measurements (signed URL upload, redirect-based download), and
properties measurements (direct JSON).
- ionworks.api.cell_measurement.PROCESSING_TIMEOUT_SECONDS = 600.0¶
How long to wait for the server to derive an upload’s steps before giving up. Generous: the work is queued, so it covers both a busy queue and a large file, and giving up only stops the waiting, never the processing.
- ionworks.api.cell_measurement.PROCESSING_POLL_SECONDS = 2.0¶
Seconds between
processing_statuspolls while waiting.
- ionworks.api.cell_measurement.ATTACHMENT_POOL_WORKERS = 4¶
Worker threads used to upload a measurement’s attachments in parallel.
- ionworks.api.cell_measurement.ATTACHMENT_MAX_CONCURRENCY = 1¶
Multipart connections per attachment upload. Parts in flight are ATTACHMENT_POOL_WORKERS x this, and each must beat the storage edge’s timeout.
- class ionworks.api.cell_measurement.CellMeasurementClient(client: Any)[source]¶
Bases:
objectClient for managing cell measurement data.
- UPLOAD_TIMEOUT: tuple[float, float] = (10, 300)¶
Timeout for uploads as (connect, read) in seconds. Passed through to the shared transport, so overriding it takes effect on both paths; on the multipart path the read timeout applies per part, not per transfer.
- __init__(client: Any) None[source]¶
Initialize the CellMeasurementClient.
- Parameters:
client (Any) – The HTTP client instance for making API calls.
- list(cell_instance_id: str, limit: int | None = None, offset: int | None = None, measurement_type: str | None = None, *, name: str | None = None, name_exact: str | None = None, created_by_email: str | None = None, started_after: str | None = None, started_before: str | None = None, created_after: str | None = None, created_before: str | None = None, updated_after: str | None = None, updated_before: str | None = None, tags_all: Iterable[str] | None = None, tags_any: Iterable[str] | None = None, order_by: str | None = None, order: str | None = None) PaginatedList[CellMeasurement][source]¶
List cell measurements for a cell instance with optional filtering.
Always returns a
PaginatedListwhich behaves like a regularlist. Uselimitandoffsetto control the page.- Parameters:
cell_instance_id (str) – The ID of the cell instance.
limit (int | None, optional) – Maximum number of measurements to return per page.
offset (int | None, optional) – Number of measurements to skip for pagination.
measurement_type (str | None, optional) – Filter by measurement type (e.g.
"time_series","file","properties"). If None, returns all types.name (str | None, optional) – Case-insensitive substring match on measurement name.
name_exact (str | None, optional) – Exact match on measurement name. Takes precedence over
name.created_by_email (str | None, optional) – Case-insensitive substring match on the creator’s email.
started_after (str | None, optional) – ISO datetime; return measurements started after this time.
started_before (str | None, optional) – ISO datetime; return measurements started before this time.
created_after (str | None, optional) – ISO datetime; return measurements created after this time.
created_before (str | None, optional) – ISO datetime; return measurements created before this time.
updated_after (str | None, optional) – ISO datetime; return measurements updated after this time.
updated_before (str | None, optional) – ISO datetime; return measurements updated before this time.
tags_all (Iterable[str] | None, optional) – Only measurements carrying every one of these tags. Matched case-insensitively.
tags_any (Iterable[str] | None, optional) – Only measurements carrying at least one of these tags. Matched case-insensitively.
order_by (str | None, optional) – Column to sort by (
"name","created_at","updated_at","start_time").order (str | None, optional) – Sort direction:
"asc"or"desc".
- Returns:
All measurements for the cell instance.
- Return type:
- list_by_project(project_id: str, limit: int | None = None, offset: int | None = None, measurement_type: str | None = None, *, q: str | None = None, tags_all: Iterable[str] | None = None, tags_any: Iterable[str] | None = None, order_by: str | None = None, order: str | None = None) PaginatedList[CellMeasurement][source]¶
List every cell measurement in a project, across all its cells.
The way to find measurements by tag:
list_by_project(project_id, tags_all=["hppc", "25degC"]).- Parameters:
project_id (str) – The project to list measurements from.
limit (int | None, optional) – Maximum number of measurements to return per page (server default 25, at most 1000).
offset (int | None, optional) – Number of measurements to skip for pagination.
measurement_type (str | None, optional) – Filter by measurement type (
"time_series","file","properties").q (str | None, optional) – Free-text search over name, notes, protocol name and tags.
tags_all (Iterable[str] | None, optional) – Only measurements carrying every one of these tags. Matched case-insensitively.
tags_any (Iterable[str] | None, optional) – Only measurements carrying at least one of these tags. Matched case-insensitively.
order_by (str | None, optional) – Column to sort by (
"name","created_at","updated_at","start_time").order (str | None, optional) – Sort direction:
"asc"or"desc".
- Returns:
The page of measurements.
- Return type:
- list_tags(project_id: str, q: str | None = None, limit: int | None = None) PaginatedList[MeasurementTagUsage][source]¶
List the distinct tags used on a project’s measurements.
- Parameters:
- Returns:
Tags with the number of measurements carrying each, most used first.
totalsays whether more exist than were returned.- Return type:
- add_tags(measurement_ids: str | Iterable[str], tags: Iterable[str]) int[source]¶
Add tags to one or more measurements.
Tags a measurement already has are left alone, so calling this twice is harmless. Each added tag takes the spelling already used in its project.
- Parameters:
- Returns:
Number of measurements whose tags changed.
- Return type:
- Raises:
IonworksError –
NOT_FOUND(404) if a measurement does not exist or is not visible to you;FORBIDDEN(403) if you can read but not update one;BAD_REQUEST(400) if a tag is invalid or a measurement would exceed 50 tags.
- remove_tags(measurement_ids: str | Iterable[str], tags: Iterable[str]) int[source]¶
Remove tags from one or more measurements, matched case-insensitively.
- Parameters:
- Returns:
Number of measurements whose tags changed.
- Return type:
- Raises:
IonworksError –
NOT_FOUND(404) orFORBIDDEN(403) as foradd_tags().
- get(measurement_id: str) CellMeasurement[source]¶
Get a specific cell measurement by its ID only.
- detail(measurement_id: str, use_cache: bool = True, include_steps: bool = True, include_cycles: bool = True, include_time_series: bool = True) CellMeasurementDetail[source]¶
Fetch measurement data.
Automatically adapts to the measurement type:
time_series: fetches steps, cycles, and time series data
file: fetches signed download URLs for files
properties: returns properties from the measurement metadata
Uses flat endpoints with parallel requests and downloads time series and steps via signed URL.
Use the
include_*flags to skip fetching data you don’t need, which avoids unnecessary requests. These flags only apply to time_series-type measurements.- Parameters:
measurement_id (str) – The ID of the cell measurement.
use_cache (bool, optional) – If True (default), check the local file cache before making API calls and store results for future use. Applies to time_series measurements (steps, cycles, and time series data). File URLs are never cached because signed URLs are short-lived.
include_steps (bool, optional) – Whether to fetch step data. Defaults to True. Only applies to time_series measurements.
include_cycles (bool, optional) – Whether to fetch cycle metrics. Defaults to True. Only applies to time_series measurements.
include_time_series (bool, optional) – Whether to fetch time series data. Defaults to True. Only applies to time_series measurements.
- Returns:
Measurement details with requested data fields. Fields not requested will be None.
- Return type:
- steps(measurement_id: str, use_cache: bool = True) DataFrame | DataFrame[source]¶
Download step data via redirect.
Downloads the raw parquet file directly from storage.
- cycles(measurement_id: str, use_cache: bool = True) DataFrame | DataFrame[source]¶
Get cycle metrics for a measurement.
- steps_and_cycles(measurement_id: str, use_cache: bool = True) StepsAndCycles[source]¶
Get steps and cycles in one call.
More efficient than calling
steps()andcycles()separately since cycles are derived from steps on the server.- Parameters:
- Returns:
Object with
stepsandcyclesDataFrames.- Return type:
- time_series(measurement_id: str, use_cache: bool = True, row_range: tuple[int, int] | None = None) DataFrame | DataFrame[source]¶
Download time series as a parquet file.
The API endpoint redirects to storage; the HTTP client follows the redirect automatically and returns the raw parquet bytes.
- Parameters:
measurement_id (str) – The ID of the cell measurement.
use_cache (bool, optional) – If True (default), check the local file cache before making an API call and store the result for future use.
row_range (tuple[int, int], optional) – Half-open window of absolute row offsets to fetch. Only the row groups holding those rows are transferred, which for a window of a long history is a fraction of the file. Omit for the whole series.
- Returns:
The requested rows, or the full time series when
row_rangeis omitted.- Return type:
DataFrame
- update(measurement_id: str, data: dict[str, Any]) CellMeasurement[source]¶
Update an existing cell measurement.
- Parameters:
measurement_id (str) – The ID of the cell measurement to update.
data (dict[str, Any]) – Dictionary containing the fields to update. This is the path for attaching/detaching a
channel_id(passNoneto detach), for settingend_timeto mark a run complete, and for attaching aprotocol_idonce the protocol a run executed is known (it must belong to the measurement’s own project). Passingtagsreplaces the measurement’s tags; useadd_tags()/remove_tags()to change some without restating the rest.
- Returns:
The updated cell measurement.
- Return type:
- Raises:
IonworksError – When attaching a
channel_idbreaks a channel rule:BAD_REQUEST(400) for a cross-project channel, a non-time_series measurement, or a missingstart_time;CONFLICT(409) for an out-of-commission channel or a time span overlapping another run already on that channel.
- attach_raw_data(cell_measurement_id: str, raw_data_ids: list[str]) None[source]¶
Attach raw-data records to this measurement (bulk, idempotent).
- detach_raw_data(cell_measurement_id: str, raw_data_id: str) None[source]¶
Remove one raw-data link from this measurement.
- watch(measurement_id: str) None[source]¶
Start watching a measurement (the “watch this channel” action).
A watch is per-user and keyed on the measurement, but reads as watching the channel the measurement is running on: it powers the lab “my channels” view (
client.lab.watched_channels) and clears itself once the test finishes, so you are no longer watching a channel whose test has ended. Idempotent – watching an already-watched measurement is a no-op.- Parameters:
measurement_id (str) – The ID of the (live) measurement to watch – typically the one on test on a channel (
channel.measurement.idin the lab status).- Raises:
IonworksError –
NOT_FOUND(404) if the measurement does not exist.
- unwatch(measurement_id: str) None[source]¶
Stop watching a measurement (the “unwatch this channel” action).
Idempotent – unwatching a measurement you are not watching is a no-op.
- Parameters:
measurement_id (str) – The ID of the measurement to stop watching.
- list_raw_data(cell_measurement_id: str, limit: int = 100, offset: int = 0) PaginatedList[RawData][source]¶
List raw-data records linked to this measurement.
- Parameters:
- Returns:
Raw-data records linked to this measurement.
- Return type:
- wait_for_processing(measurement_ids: str | Iterable[str], timeout: float = 600.0) dict[str, dict][source]¶
Block until the server finishes deriving uploads’ steps.
Polls
processing_statusuntil every measurement is terminal. Steps are excluded from the reads, since the server refuses them while processing is in flight.Pass a whole batch in one call: uploads process concurrently, so the batch takes about as long as its slowest member and shares one timeout. Not fail-fast — every failure is reported once, together, when the batch settles.
- Parameters:
- Returns:
The last measurement payload read for each id, keyed by id. Returned so a caller that needs the settled row does not have to re-read what the final poll already fetched.
- Return type:
- Raises:
MeasurementProcessingError – If the server rejected any upload, or if any did not finish within timeout. Inspect
failuresfor the per-measurement reasons. A timeout does not cancel the work — it only stops waiting — so a measurement may still complete afterwards.
- create(cell_instance_id: str, measurement_detail: dict[str, Any], validate_strict: bool = False, rated_capacity: float | None = None, voltage_window: tuple[float, float] | None = None, skip_checks: Iterable[str] | None = None, wait_for_processing: bool = True) CellMeasurementBundleResponse[source]¶
Create a new cell measurement with steps and time series data.
Uses signed URL upload for better performance with large datasets. Data is uploaded directly to storage as parquet, bypassing backend JSON parsing. No database record is created until the upload is confirmed, preventing orphaned records if upload fails.
- Parameters:
cell_instance_id (str) – The ID of the cell instance to create the measurement for.
measurement_detail (dict[str, Any]) –
Dictionary containing ‘measurement’, ‘steps’, and ‘time_series’.
measurement: dict with ‘name’ (required) and ‘notes’. An optional ‘data_type’ key routes validation:
"ocp"for open-circuit potential data and"eis"for impedance spectra (validates the EIS columns and, in strict mode, theZ_Imsign and impedance magnitude). It is a client-side validation hint only and is not persisted. May also carrychannel_id(the physical lab channel the test ran on) andstart_time. Ifchannel_idis set, this measurement is a channel-linked run, which requiresstart_timeand forbids overlapping another run on the same channel — see the Raises note below and themanage-equipmentskill. May also carrytags(a list of free-form labels, normalised as increate_properties()).time_series: pandas DataFrame or dict with time series data
steps: optional dict with pre-calculated steps data
validate_strict (bool, optional) – If False (default), runs only the always-on checks (positive current convention, time starts at 0, time monotonic, step count sequential, cumulative capacity/energy reset per step). If True, additionally runs: minimum points per step, cycle constant within step, time-gap check (> 5 h), and — when the corresponding inputs are provided — voltage continuity and consecutive-full-step / per-step capacity checks.
rated_capacity (float, optional) – Rated (nominal) cell capacity in A.h. Used only when
validate_strict=Truetogether with astepsentry inmeasurement_detail; enables the consecutive same-direction full-step check (hard) and the per-step capacity 500 % soft warning.voltage_window (tuple[float, float], optional) – Rated
(V_min, V_max)voltage window of the cell. Used only whenvalidate_strict=True; enables the voltage-continuity check that detects time series rows that are out of chronological order.skip_checks (Iterable[str], optional) – Names of strict-mode checks to skip while keeping
validate_strict=Truefor everything else. Prefer this over disabling strict mode entirely when only a single check is a known false positive for the dataset. Seeionworks.api.validators.STRICT_CHECK_NAMESfor valid names.wait_for_processing (bool, optional) –
Block until the server has finished deriving the steps, raising if it could not. Defaults to True, because this call returning is otherwise no evidence the upload was usable: the record is created before the file is parsed, so a file the server cannot read still yields a successful response.
Pass False when uploading a batch. The server processes uploads concurrently, so submitting all of them and then waiting on every id at once with
wait_for_processing()takes about as long as the slowest single upload, where waiting inside each call would take the sum.
- Returns:
Response containing the created measurement, steps count, and file path.
steps_createdis always 0 — steps are derived after the call returns, so the count is not known yet.- Return type:
- Raises:
MeasurementProcessingError – If
wait_for_processingand the server could not process the upload, or did not finish in time.MeasurementValidationError – If data validation fails. Non-strict checks cover the sign convention, time axis, step-count monotonicity, and cumulative reset. Strict-only checks additionally cover time gaps, voltage continuity, and consecutive same-direction full steps.
IonworksError – When a
channel_idinmeasurementbreaks a channel rule:BAD_REQUEST(HTTP 400) if the channel is in another project, the measurement is nottime_series, orstart_timeis missing;CONFLICT(HTTP 409) if the channel is out of commission or the run’s time span overlaps another measurement already on that channel.
- extend(measurement_id: str, time_series: Any, wait_for_processing: bool = True) CellMeasurementBundleResponse[source]¶
Append a delta time series to an unfinished measurement.
Uploads the delta parquet via signed URL, then confirms so the server stitches it on and recomputes steps. Client-side checks are lighter than
create(): empty frame, required columns, and monotonic time only — time need not start at zero.Stitching runs after confirm returns, so this waits for it by default (see wait_for_processing). A measurement must be done processing and may have only one extend in flight; both refusals are a
409, so retry after waiting rather than treating them as fatal.- Parameters:
measurement_id (str) – ID of the unfinished time_series measurement to extend.
time_series (Any) – Delta rows as a pandas/polars DataFrame or dict. Must include
Time [s]andStep count, be non-empty, and have monotonically non-decreasing time.wait_for_processing (bool, optional) – Block until the server has stitched the delta on, and raise if it could not. Defaults to True. Pass False to return as soon as the work is queued, then wait yourself with
wait_for_processing()— note that a failed extend leaves the measurement readable, so it reports as ready and you must checkprocessing_errorto see whether the delta actually applied.
- Returns:
Updated measurement fields.
steps_createdis always 0 — steps are recomputed after this call returns, so the count is not known here.- Return type:
- Raises:
IonworksError – If the delta is empty, missing required columns, or the server returns no extend upload URL.
MeasurementValidationError – If
Time [s]is not monotonically non-decreasing.MeasurementProcessingError – If
wait_for_processingand the server could not apply the delta (or did not finish in time).
- create_or_get(cell_instance_id: str, measurement_detail: dict[str, Any], validate_strict: bool = False, rated_capacity: float | None = None, voltage_window: tuple[float, float] | None = None, skip_checks: Iterable[str] | None = None, wait_for_processing: bool = True) CellMeasurement[source]¶
Create a new cell measurement or get existing.
Always returns a
CellMeasurementregardless of whether the measurement was newly created or already existed.- Parameters:
cell_instance_id (str) – The ID of the cell instance.
measurement_detail (dict[str, Any]) – Dictionary containing
measurementandtime_series(same ascreate()).validate_strict (bool, optional) – If False (default), skips strict validation. If True, runs strict validation including minimum points per step.
rated_capacity (float, optional) – Rated (nominal) cell capacity in A.h. Forwarded to
create()to enable the consecutive-full-step and per-step capacity checks.voltage_window (tuple[float, float], optional) – Rated
(V_min, V_max)voltage window. Forwarded tocreate()to enable the voltage-continuity check.skip_checks (Iterable[str], optional) – Names of strict-mode checks to skip. Forwarded to
create(). Prefer skipping a specific check over disabling strict mode entirely.wait_for_processing (bool, optional) – Forwarded to
create(). Defaults to True. Pass False when uploading a batch and wait on the ids together afterwards withwait_for_processing(). Note that a measurement returned because it already existed has processed long ago, so only the newly created ids are worth waiting on.
- Returns:
The measurement (newly created or existing).
- Return type:
- create_properties(cell_instance_id: str, name: str, properties: dict[str, Any], *, notes: str | None = None, protocol: dict[str, Any] | None = None, start_time: str | None = None, end_time: str | None = None, test_setup: dict[str, Any] | None = None, tags: Iterable[str] | None = None) CellMeasurement[source]¶
Create a properties-type measurement (no file upload).
Use this for manual measurements like thickness, weight, or capacity that are stored as key-value pairs with units.
- Parameters:
cell_instance_id (str) – The ID of the parent cell instance.
name (str) – Name for the measurement.
properties (dict[str, Any]) – Key-value measurements using Quantity format for numerics. Example:
{"thickness": {"value": 0.52, "unit": "mm"}}.notes (str | None, optional) – Optional notes for the measurement.
protocol (dict[str, Any] | None, optional) – Protocol information.
start_time (str | None, optional) – ISO-formatted start time for the measurement.
end_time (str | None, optional) – ISO-formatted end time. Set once the test is complete; leave None while it is still running.
test_setup (dict[str, Any] | None, optional) – Test setup metadata.
tags (Iterable[str] | None, optional) – Free-form labels, e.g.
["baseline", "batch-7"]. Whitespace is trimmed, duplicates are dropped case-insensitively, and a tag already used in the project keeps its existing spelling. At most 50 tags of up to 50 characters each.
- Returns:
The created measurement.
- Return type:
- ALLOWED_IMAGE_EXTENSIONS: frozenset[str] = frozenset({'bmp', 'gif', 'jpeg', 'jpg', 'png', 'tiff', 'webp'})¶
Allowed image file extensions for upload.
- create_file(cell_instance_id: str, name: str, filepaths: list[str], *, validate_images: bool = False, notes: str | None = None, protocol: dict[str, Any] | None = None, start_time: str | None = None, end_time: str | None = None, test_setup: dict[str, Any] | None = None, tags: Iterable[str] | None = None) CellMeasurementBundleResponse[source]¶
Create a file-type measurement by uploading files.
Uses a three-step signed URL flow: initiate (get signed URLs), upload each file, then confirm (create DB record). Supports any file type (PDFs, numpy arrays, images, etc.).
Set
validate_images=Trueto opt into client-side image validation when uploading image files specifically.- Parameters:
cell_instance_id (str) – The ID of the parent cell instance.
name (str) – Name for the measurement.
validate_images (bool, optional) – When True, validate that each file is a real image with an allowed extension before uploading. Defaults to False.
notes (str | None, optional) – Optional notes for the measurement.
protocol (dict[str, Any] | None, optional) – Protocol information.
start_time (str | None, optional) – ISO-formatted start time for the measurement.
end_time (str | None, optional) – ISO-formatted end time. Set once the test is complete; leave None while it is still running.
test_setup (dict[str, Any] | None, optional) – Test setup metadata.
tags (Iterable[str] | None, optional) – Free-form labels, e.g.
["baseline", "batch-7"]. Whitespace is trimmed, duplicates are dropped case-insensitively, and a tag already used in the project keeps its existing spelling. At most 50 tags of up to 50 characters each.
- Returns:
Response containing the created measurement and steps_created (0).
- Return type:
- Raises:
IonworksError – If
validate_imagesis True and any file is not a valid image, or if upload fails.FileNotFoundError – If any file path does not exist.
- list_files(measurement_id: str) list[str][source]¶
List filenames in a file-type measurement.
- Parameters:
measurement_id (str) – The ID of a file-type cell measurement.
- Returns:
Filenames stored in the measurement.
- Return type:
- Raises:
IonworksError – If the measurement is not a file type or does not exist.
- download_files(measurement_id: str, use_cache: bool = True, filenames: list[str] | None = None) dict[str, bytes][source]¶
Download files for a file-type measurement.
When
filenamesis provided thelist_filesround-trip is skipped, which avoids the slow storage-listing endpoint. Use this when you already know the filenames (e.g. from a previouslist_filescall or from the upload response).- Parameters:
measurement_id (str) – The ID of a file-type cell measurement.
use_cache (bool, optional) – If True (default), check the local file cache before downloading and store results for future use.
filenames (list[str] | None, optional) – Explicit list of filenames to download. When provided, the
GET /cell_measurements/{id}/fileslisting call is skipped.
- Returns:
Mapping of filename to file content bytes.
- Return type:
- Raises:
IonworksError – If the measurement is not a file type, does not exist, or a download fails.
- get_file(measurement_id: str, filename: str) bytes[source]¶
Download a single file from a file-type measurement.
This is a convenience wrapper that fetches one file directly without any listing call, making it the fastest way to retrieve a known file.
- Parameters:
- Returns:
Raw file content.
- Return type:
- Raises:
IonworksError – If the measurement or file does not exist.