Models#

Pydantic models for API request and response validation.

Pydantic models for the Ionworks API client.

These models use extra=”allow” to accept any fields from the API response, letting the API handle validation. Required fields are kept minimal.

class ionworks.models.PaginatedList(items, total)[source]#

Bases: Generic[_T]

A list-like container that also carries pagination metadata.

Returned by list() methods when limit or offset is provided. Behaves like a regular list for iteration, indexing, truthiness, and len() so callers can treat it interchangeably with list[T].

Parameters:
  • items (list[_T]) – The page of results.

  • total (int) – Total number of matching records across all pages.

__init__(items, total)[source]#
Parameters:
Return type:

None

items#
total#
property count: int#

Number of items in this page (same as len(self)).

ionworks.models.create_or_get(*, create, get_by_id, find_by_name, name, resource_label)[source]#

Create a resource, or return the existing one on a name conflict.

Shared conflict-resolution used by the equipment sub-clients. Calls create; on an IonworksError that is a CONFLICT (error_code == "CONFLICT" or HTTP 409), resolves the existing resource — first by the existing_id echoed in the error detail, then by find_by_name as a fallback. Any non-conflict error propagates unchanged.

Parameters:
  • create (Callable[[], _T]) – Zero-arg thunk that performs the create and returns the new resource.

  • get_by_id (Callable[[str], _T]) – Fetch a resource by id (used with the conflict’s existing_id).

  • find_by_name (Callable[[str], _T | None]) – Look up the existing resource by its (conflicting) name; returns None if not found.

  • name (str | None) – The name that was being created, used for the fallback lookup and the error message.

  • resource_label (str) – Human-readable resource name for the “duplicate but not found” error (e.g. "Cycler").

Returns:

The newly created resource, or the pre-existing one on conflict.

Return type:

_T

Raises:

ValueError – If the create reported a duplicate but the existing resource could not be resolved by id or name.

class ionworks.models.CellSpecification(*, id, name, **extra_data)[source]#

Bases: BaseModel

Cell specification model - accepts any fields from the API.

The API returns nested component/material data and ratings objects. This model is permissive to allow the API to define the schema.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
class ionworks.models.Site(*, id, name, **extra_data)[source]#

Bases: BaseModel

Site model - accepts any fields from the API.

A site is an organization-scoped physical location that owns cyclers.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
class ionworks.models.Cycler(*, id, name, site_id, project_id, manufacturer=None, model=None, version=None, serial_number=None, firmware_version=None, hostname=None, ip_address=None, port=None, last_calibrated_at=None, calibration_interval_days=None, calibration_due_at=None, **extra_data)[source]#

Bases: BaseModel

Cycler model - accepts any fields from the API.

A cycler is battery test equipment that belongs to a site, is owned by one project, and owns channels.

Parameters:
  • id (str)

  • name (str)

  • site_id (str)

  • project_id (str)

  • manufacturer (str | None)

  • model (str | None)

  • version (str | None)

  • serial_number (str | None)

  • firmware_version (str | None)

  • hostname (str | None)

  • ip_address (str | None)

  • port (int | None)

  • last_calibrated_at (datetime | None)

  • calibration_interval_days (int | None)

  • calibration_due_at (datetime | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
site_id: str#
project_id: str#
manufacturer: str | None#

Manufacturer (make) of the cycler, e.g. “Arbin”. Nullable.

model: str | None#

Model (type) of the cycler, e.g. “LBT-5V”. Nullable.

version: str | None#

Hardware/product version. Distinct from firmware_version, which changes without the hardware changing. Nullable.

serial_number: str | None#

Manufacturer serial number. Nullable.

firmware_version: str | None#

Firmware revision currently installed. Nullable.

hostname: str | None#

DNS name of the control host on the lab network. Nullable.

ip_address: str | None#

IP address of the control host. Nullable.

port: int | None#

TCP port the control software listens on (1-65535). Nullable.

last_calibrated_at: datetime | None#

When the cycler was last calibrated. Null means never calibrated or not tracked — read as unknown, not as overdue.

calibration_interval_days: int | None#

Maximum time allowed between calibrations, in days. Null means the cycler is not on a calibration schedule.

calibration_due_at: datetime | None#

last_calibrated_at plus calibration_interval_days. Read-only — derived by the server, so it cannot disagree with those two fields. Null when either is unset.

Type:

When calibration next falls due

class ionworks.models.CyclerServiceEvent(*, id, cycler_id, project_id, event_type, scheduled_for=None, scheduled_until=None, performed_at=None, notes=None, created_by=None, performed_by=None, **extra_data)[source]#

Bases: BaseModel

One instrument-level service on a cycler.

Calibration and preventive maintenance are performed on the instrument, so a 40-channel cycler going in for its annual calibration is one event, not 40 unrelated channel outages. Opening an event takes every channel out of service together; completing it returns them together.

A cycler has at most one open event (performed_at is None) at a time.

Parameters:
  • id (str)

  • cycler_id (str)

  • project_id (str)

  • event_type (str)

  • scheduled_for (datetime | None)

  • scheduled_until (datetime | None)

  • performed_at (datetime | None)

  • notes (str | None)

  • created_by (str | None)

  • performed_by (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
cycler_id: str#
project_id: str#
event_type: str#

One of “calibration”, “preventive_maintenance”, “firmware”, “repair”, “other”.

scheduled_for: datetime | None#

When the visit is booked. None on an event recorded after the fact.

scheduled_until: datetime | None#

When the booked visit is expected to end, making the booking a span rather than a start instant. None when no end was given. A plan, not a guarantee — a visit may run past it.

performed_at: datetime | None#

When the work was done. None means the event is still open.

notes: str | None#
created_by: str | None#
performed_by: str | None#
class ionworks.models.Channel(*, id, name, cycler_id, project_id, notes=None, out_of_commission=False, max_amps=None, min_volts=None, max_volts=None, **extra_data)[source]#

Bases: BaseModel

Channel model - accepts any fields from the API.

A channel is an individual test channel belonging to a cycler; it inherits its cycler’s project.

Parameters:
  • id (str)

  • name (str)

  • cycler_id (str)

  • project_id (str)

  • notes (str | None)

  • out_of_commission (bool)

  • max_amps (float | None)

  • min_volts (float | None)

  • max_volts (float | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
cycler_id: str#
project_id: str#
notes: str | None#

Free-text notes on the channel. Nullable.

out_of_commission: bool#

Whether the channel is deliberately out of service (broken / maintenance). Shown as out-of-service in the Lab view.

max_amps: float | None#

Rated maximum current in amps (A). Nullable = unrated.

min_volts: float | None#

Rated voltage window in volts (V). Nullable = unrated.

max_volts: float | None#
class ionworks.models.ChannelIncident(*, id, channel_id, project_id, service_event_id=None, category='other', notes=None, service_scheduled_at=None, service_scheduled_until=None, started_at, resolved_at=None, resolution_notes=None, started_by=None, resolved_by=None, is_estimated=False, **extra_data)[source]#

Bases: BaseModel

One span during which a channel was out of service.

Opened when a channel is marked out of commission and closed when it returns, so a channel’s downtime is a history of spans rather than just the current out_of_commission flag. A channel has at most one open incident (resolved_at is None) at a time.

An outage is either instrument-level or channel-local, and service_event_id is what tells them apart: it is set for every channel taken down by a cycler calibration or PM visit, and None for a fault the channel had on its own (a failed relay is not a cycler event).

Parameters:
  • id (str)

  • channel_id (str)

  • project_id (str)

  • service_event_id (str | None)

  • category (str)

  • notes (str | None)

  • service_scheduled_at (datetime | None)

  • service_scheduled_until (datetime | None)

  • started_at (datetime)

  • resolved_at (datetime | None)

  • resolution_notes (str | None)

  • started_by (str | None)

  • resolved_by (str | None)

  • is_estimated (bool)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
channel_id: str#
project_id: str#
service_event_id: str | None#

The instrument-level service this outage belongs to, when it is one. None for a channel-local fault.

category: str#

One of “hardware_failure”, “maintenance”, “calibration”, “decommissioned”, “other”.

notes: str | None#
service_scheduled_at: datetime | None#

When the booked service is due to happen. None on unplanned outages and on backfilled rows, whose service was never scheduled.

service_scheduled_until: datetime | None#

When the booked service is expected to end, making the outage a span rather than a start instant. A plan, not a guarantee — an outage may run past it, which is what is_overdue_to_return reports.

started_at: datetime#

When the channel went out of service.

resolved_at: datetime | None#

When the channel returned. None means the outage is still open.

resolution_notes: str | None#
started_by: str | None#
resolved_by: str | None#
is_estimated: bool#

True for backfilled rows whose started_at is derived from the channel’s last-modified time and is only an upper bound. Exclude these from mean-time-to-repair arithmetic.

property is_overdue_to_return: bool#

Whether an open outage has run past the end its service was booked to.

Derived when you ask rather than stored, because overrunning a booked window is a plan being wrong, not a record being invalid — the platform deliberately accepts it. Only an open outage can be overdue: once the channel is back it is back, however late.

Returns:

True when the outage is still open, has a booked end, and that end is in the past.

Return type:

bool

class ionworks.models.ChannelState(*values)[source]#

Bases: StrEnum

Derived occupancy state of a channel in the lab view.

free = 'free'#
occupied = 'occupied'#
stale = 'stale'#
out_of_commission = 'out_of_commission'#
class ionworks.models.LabMeasurementSummary(*, id, name, start_time=None, estimated_end_time=None, updated_at, cell_instance_id, cell_instance_name=None, cell_specification_name=None, protocol_name=None, watched=False, **extra_data)[source]#

Bases: BaseModel

The measurement occupying a channel (slim view for the lab wall).

Parameters:
  • id (str)

  • name (str)

  • start_time (str | None)

  • estimated_end_time (str | None)

  • updated_at (str)

  • cell_instance_id (str)

  • cell_instance_name (str | None)

  • cell_specification_name (str | None)

  • protocol_name (str | None)

  • watched (bool)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
start_time: str | None#

ISO time the test started, if known. Nullable.

estimated_end_time: str | None#

ISO forecast finish time, if an estimate has been computed. Nullable. Informational only – doesn’t affect the derived occupancy state or the staleness window. The note explaining the estimate and the timestamp it was computed at aren’t part of this slim summary; fetch the full measurement (client.cell_measurement.get(measurement_id)) for those.

updated_at: str#

ISO time the measurement was last updated (drives staleness).

cell_instance_id: str#
cell_instance_name: str | None#
cell_specification_name: str | None#

Name of the cell instance’s cell specification, if known. Nullable.

protocol_name: str | None#
watched: bool#

Whether the current user is watching this measurement. A watch on the live measurement reads as watching its channel (see LabClient.watch / list_watched / watched_channels).

class ionworks.models.LabChannel(*, id, name, state, measurement=None, notes=None, out_of_commission=False, max_amps=None, min_volts=None, max_volts=None, **extra_data)[source]#

Bases: BaseModel

A channel with its derived occupancy state.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
state: ChannelState#
measurement: LabMeasurementSummary | None#

The measurement driving the state (occupied/stale); None when free/OOC.

notes: str | None#
out_of_commission: bool#
max_amps: float | None#
min_volts: float | None#
max_volts: float | None#
class ionworks.models.LabCycler(*, id, name, manufacturer=None, model=None, channel_count, occupied, stale, free, out_of_commission=0, channels=[], **extra_data)[source]#

Bases: BaseModel

A cycler with its channels and per-cycler occupancy counts.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
manufacturer: str | None#
model: str | None#
channel_count: int#
occupied: int#
stale: int#
free: int#
out_of_commission: int#
channels: list[LabChannel]#
class ionworks.models.LabSite(*, id, name, cyclers=[], **extra_data)[source]#

Bases: BaseModel

A site grouping its cyclers for the lab wall.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
cyclers: list[LabCycler]#
class ionworks.models.LabStatus(*, sites=[], occupied=0, stale=0, free=0, out_of_commission=0, **extra_data)[source]#

Bases: BaseModel

The full lab-view tree for a project plus project-level counts.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

sites: list[LabSite]#
occupied: int#
stale: int#
free: int#
out_of_commission: int#
class ionworks.models.FlatChannel(*, channel, cycler_id, cycler_name, site_id, site_name)[source]#

Bases: BaseModel

A lab channel flattened with its parent cycler and site context.

Returned by LabClient.free_channels / stale_channels so an answer reads as “CH3 on Maccor-1 at Boston Lab” without re-walking the tree.

Parameters:
channel: LabChannel#
cycler_id: str#
cycler_name: str#
site_id: str#
site_name: str#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.Utilization(*, percent, occupied, stale, free, out_of_commission, total)[source]#

Bases: BaseModel

Project lab utilization: the frontend headline percent plus raw counts.

percent is the busy (occupied + stale) share of all channels (out-of-commission included in the denominator), rounded to a whole number to match the lab wall. 0 when there are no channels.

Parameters:
percent: int#
occupied: int#
stale: int#
free: int#
out_of_commission: int#
total: int#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.CellInstance(*, id, name, cell_specification_id, **extra_data)[source]#

Bases: BaseModel

Cell instance model - accepts any fields from the API.

Parameters:
  • id (str)

  • name (str)

  • cell_specification_id (str)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
cell_specification_id: str#
class ionworks.models.MeasurementType(*values)[source]#

Bases: StrEnum

Type of data stored in a cell measurement.

time_series = 'time_series'#
file = 'file'#
properties = 'properties'#
class ionworks.models.CellMeasurement(*, id, name, cell_instance_id, measurement_type=MeasurementType.time_series, channel_id=None, start_time=None, end_time=None, estimated_end_time=None, estimated_end_time_note=None, estimated_end_time_calculated_at=None, processing_status=None, processing_error=None, **extra_data)[source]#

Bases: BaseModel

Cell measurement model - accepts any fields from the API.

Parameters:
  • id (str)

  • name (str)

  • cell_instance_id (str)

  • measurement_type (MeasurementType)

  • channel_id (str | None)

  • start_time (str | None)

  • end_time (str | None)

  • estimated_end_time (str | None)

  • estimated_end_time_note (str | None)

  • estimated_end_time_calculated_at (str | None)

  • processing_status (str | None)

  • processing_error (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
cell_instance_id: str#
measurement_type: MeasurementType#
channel_id: str | None#

Optional channel the measurement was recorded on. Nullable. Only valid on time_series measurements, and requires start_time to be set.

start_time: str | None#

ISO-formatted time the test started. Nullable, but required when channel_id is set (it defines when the channel became occupied).

end_time: str | None#

ISO-formatted time the test finished. Nullable — a null end_time means the test is still running (e.g. in the lab equipment view). Set it once the measurement is complete. Must not precede start_time.

estimated_end_time: str | None#

ISO forecast finish time for a still-running test, if an estimate has been computed. Nullable. Distinct from end_time (the actual finish); informational only.

estimated_end_time_note: str | None#

Free-text explanation of how estimated_end_time was derived. Nullable.

estimated_end_time_calculated_at: str | None#

ISO-formatted time estimated_end_time was last computed. Nullable.

processing_status: str | None#

Lifecycle of server-side step processing for uploaded time_series measurements: pending or running while the steps are still being derived, ready once they are available, failed if the upload could not be processed. file and properties measurements never process steps and are always ready.

processing_error: str | None#

Why step processing failed, when processing_status is failed.

class ionworks.models.CellMeasurementBundleResponse(*, id, name, cell_instance_id, measurement_type=MeasurementType.time_series, channel_id=None, start_time=None, end_time=None, estimated_end_time=None, estimated_end_time_note=None, estimated_end_time_calculated_at=None, processing_status=None, processing_error=None, steps_created, **extra_data)[source]#

Bases: CellMeasurement

Flat response from creating a measurement bundle.

Measurement fields (id, name, measurement_type, etc.) are at the top level alongside upload metadata.

Parameters:
  • id (str)

  • name (str)

  • cell_instance_id (str)

  • measurement_type (MeasurementType)

  • channel_id (str | None)

  • start_time (str | None)

  • end_time (str | None)

  • estimated_end_time (str | None)

  • estimated_end_time_note (str | None)

  • estimated_end_time_calculated_at (str | None)

  • processing_status (str | None)

  • processing_error (str | None)

  • steps_created (int)

  • extra_data (Any)

steps_created: int#
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.UploadInfo(*, filename=None, signed_url, token, path)[source]#

Bases: BaseModel

Signed URL info for a single upload target.

Parameters:
  • filename (str | None)

  • signed_url (str)

  • token (str)

  • path (str)

filename: str | None#
signed_url: str#
token: str#
path: str#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.InitiateUploadResponse(*, measurement_id, uploads)[source]#

Bases: BaseModel

Response from the initiate-upload endpoint for signed URL uploads.

Parameters:
measurement_id: str#
uploads: list[UploadInfo]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.InitiateRawDataUploadResponse(*, raw_data_id, uploads)[source]#

Bases: BaseModel

Response from the raw-data initiate-upload endpoint.

Parameters:
raw_data_id: str#
uploads: list[UploadInfo]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.CellMeasurementDetail(*, id, name, cell_instance_id, measurement_type=MeasurementType.time_series, channel_id=None, start_time=None, end_time=None, estimated_end_time=None, estimated_end_time_note=None, estimated_end_time_calculated_at=None, processing_status=None, processing_error=None, specification_id=None, instance_id=None, steps=None, time_series=None, cycles=None, files=None, **extra_data)[source]#

Bases: CellMeasurement

Flat detail model for a measurement with steps and time series.

Measurement fields (id, name, measurement_type, etc.) are at the top level alongside optional data payloads. Returns minimal data by default: foreign keys for parent objects rather than nested objects. Use the spec/instance clients to fetch parent objects if needed.

Parameters:
  • id (str)

  • name (str)

  • cell_instance_id (str)

  • measurement_type (MeasurementType)

  • channel_id (str | None)

  • start_time (str | None)

  • end_time (str | None)

  • estimated_end_time (str | None)

  • estimated_end_time_note (str | None)

  • estimated_end_time_calculated_at (str | None)

  • processing_status (str | None)

  • processing_error (str | None)

  • specification_id (str | None)

  • instance_id (str | None)

  • steps (DataFrame | DataFrame | None)

  • time_series (DataFrame | DataFrame | None)

  • cycles (DataFrame | DataFrame | None)

  • files (dict[str, bytes] | None)

  • extra_data (Any)

model_config = {'arbitrary_types_allowed': True, 'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

specification_id: str | None#
instance_id: str | None#
steps: DataFrame | None#
time_series: DataFrame | None#
cycles: DataFrame | None#
files: dict[str, bytes] | None#
classmethod convert_dict_to_df(v)[source]#

Convert dictionary to DataFrame (polars or pandas based on config).

Parameters:

v (Any)

Return type:

Any

class ionworks.models.CellInstanceDetail(*, instance, specification_id, measurements)[source]#

Bases: BaseModel

Detail model for a cell instance with all measurements.

Returns a foreign key for the parent specification rather than a nested object. Use client.cell_spec.get(detail .specification_id) to fetch the full specification.

Parameters:
instance: CellInstance#
specification_id: str#
measurements: list[CellMeasurementDetail]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.CyclerDetail(*, cycler, site_id, channels)[source]#

Bases: BaseModel

Detail model for a cycler with all its channels.

Returns a foreign key for the parent site rather than a nested object. Use client.site.get(detail.site_id) to fetch the full site.

Parameters:
cycler: Cycler#
site_id: str#
channels: list[Channel]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.SiteDetail(*, site, cyclers)[source]#

Bases: BaseModel

Detail model for a site with all its cyclers.

Each cycler is expanded to a CyclerDetail, so its channels are included too.

Parameters:
site: Site#
cyclers: list[CyclerDetail]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.ColumnSpec(*, name, unit='', source_column_index, **extra_data)[source]#

Bases: BaseModel

Column descriptor for a material property dataset.

Parameters:
  • name (str)

  • unit (str)

  • source_column_index (int)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str#
unit: str#
source_column_index: int#
class ionworks.models.MaterialPropertyDataset(*, id, name, columns=[], data_version, nan_counts=None, created_at=None, source_pipeline_id=None, source_simple_pipeline_id=None, source_analysis_id=None, source_label=None, **extra_data)[source]#

Bases: BaseModel

Material property dataset record as returned by the API.

Parameters:
  • id (str)

  • name (str)

  • columns (list[ColumnSpec])

  • data_version (int)

  • nan_counts (dict[str, int] | None)

  • created_at (str | None)

  • source_pipeline_id (str | None)

  • source_simple_pipeline_id (str | None)

  • source_analysis_id (str | None)

  • source_label (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
columns: list[ColumnSpec]#
data_version: int#
nan_counts: dict[str, int] | None#
created_at: str | None#
source_pipeline_id: str | None#
source_simple_pipeline_id: str | None#
source_analysis_id: str | None#
source_label: str | None#
class ionworks.models.AnalysisType(*values)[source]#

Bases: StrEnum

Well-known analysis_type values.

Members are plain strings (StrEnum), so they can be passed directly wherever an analysis_type string is expected, e.g. client.analysis.create(..., analysis_type=AnalysisType.ECM_FROM_EIS).

This set is advisory, not exhaustive: the API does not restrict analysis_type to these values — any non-empty string is accepted, so a new extractor can use a raw string without waiting for an SDK release.

ECM_FROM_EIS = 'ecm_from_eis'#
LAM_LLI_FROM_RPT = 'lam_lli_from_rpt'#
DCIR_FROM_HPPC = 'dcir_from_hppc'#
ionworks.models.KNOWN_ANALYSIS_TYPES: list[str] = ['ecm_from_eis', 'lam_lli_from_rpt', 'dcir_from_hppc']#

The well-known analysis-type string values as a plain list, for iteration or display. Derived from AnalysisType; prefer the enum for authoring.

class ionworks.models.AnalysisColumnSpec(*, name, unit='', dtype=None, **extra_data)[source]#

Bases: BaseModel

Column descriptor for an analysis parquet.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

name: str#
unit: str#
dtype: str | None#
class ionworks.models.Analysis(*, id, measurement_id, project_id=None, name, analysis_type, columns=[], metadata={}, notes=None, source_pipeline_id=None, source_simple_pipeline_id=None, source_analysis_id=None, source_label=None, created_at=None, **extra_data)[source]#

Bases: BaseModel

Analysis record as returned by the API.

An analysis holds features extracted from a single cell_measurement (e.g. ECM parameters from EIS, LLI/LAM from RPT), stored as a parquet table plus loose metadata.

Parameters:
  • id (str)

  • measurement_id (str)

  • project_id (str | None)

  • name (str)

  • analysis_type (str)

  • columns (list[AnalysisColumnSpec])

  • metadata (dict)

  • notes (str | None)

  • source_pipeline_id (str | None)

  • source_simple_pipeline_id (str | None)

  • source_analysis_id (str | None)

  • source_label (str | None)

  • created_at (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
measurement_id: str#
project_id: str | None#
name: str#
analysis_type: str#
columns: list[AnalysisColumnSpec]#
metadata: dict#
notes: str | None#
source_pipeline_id: str | None#
source_simple_pipeline_id: str | None#
source_analysis_id: str | None#
source_label: str | None#
created_at: str | None#
class ionworks.models.Material(*, id, name, manufacturer=None, product_id=None, **extra_data)[source]#

Bases: BaseModel

Material record as returned by the API.

Parameters:
  • id (str)

  • name (str)

  • manufacturer (str | None)

  • product_id (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
manufacturer: str | None#
product_id: str | None#
class ionworks.models.Project(*, id, name, organization_id, **extra_data)[source]#

Bases: BaseModel

Project model - accepts any fields from the API.

Parameters:
  • id (str)

  • name (str)

  • organization_id (str)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
organization_id: str#
class ionworks.models.SearchResult(*, id, entity_type, name, project_id=None, parent_id=None, **extra_data)[source]#

Bases: BaseModel

A single result from the global search endpoint.

Accepts any extra fields the API may add in future.

Parameters:
  • id (str)

  • entity_type (str)

  • name (str)

  • project_id (str | None)

  • parent_id (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
entity_type: str#
name: str#
project_id: str | None#

Project the entity belongs to (or a fallback project for org-scoped entities). None when the API could not associate a project.

parent_id: str | None#

Parent entity ID needed for navigation (e.g. cell_specification_id for a cell_instance). None when the ID alone is sufficient.

class ionworks.models.SearchResponse(*, results, query, total, limit, offset, **extra_data)[source]#

Bases: BaseModel

Paginated response from the global search endpoint.

Iterates over results so callers can treat it like a list of SearchResult (for hit in response, response[0], len(response), if response:) while still reading the pagination metadata (total, limit, offset).

Notes

Because __iter__() yields SearchResult objects (not Pydantic’s default (field_name, value) pairs), dict(response) does not work. Use model_dump() to get a plain dict of fields.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

results: list[SearchResult]#
query: str#
total: int#
limit: int#
offset: int#
class ionworks.models.Model(*, id, name, config=None, simulation_settings=None, **extra_data)[source]#

Bases: BaseModel

Custom model model - accepts any fields from the API.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
config: dict[str, Any] | None#

Model config (e.g. {"type": "SPMe"}). The get response includes it; the create response may omit it, in which case this is None.

simulation_settings: dict[str, Any] | None#

Persistent simulation settings (mesh + solver) re-applied whenever the model is simulated: a flat bag of var_pts / submesh_types / spatial_methods / solver. None uses the model defaults.

class ionworks.models.ParameterizedModel(*, id, name, simulation_settings=None, **extra_data)[source]#

Bases: BaseModel

Parameterized model model - accepts any fields from the API.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
simulation_settings: dict[str, Any] | None#

Parameter-specific persistent simulation settings (mesh + solver); takes precedence over the base model’s settings when the model is simulated. None inherits the base model / defaults.

class ionworks.models.RawData(*, id, project_id, name, filename, source=None, **extra_data)[source]#

Bases: BaseModel

A raw-data record.

Represents an original uploaded file stored as-is, scoped to an organization and project. The API defines the full schema; extra fields are accepted.

Parameters:
  • id (str)

  • project_id (str)

  • name (str)

  • filename (str)

  • source (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
project_id: str#
name: str#
filename: str#
source: str | None#
class ionworks.models.Protocol(*, id, name, organization_id, project_id=None, description=None, protocol_config=None, parameters_schema=None, source_protocol=None, created_by_email=None, **extra_data)[source]#

Bases: BaseModel

A saved, project-scoped protocol.

Stored server-side as an experiment_template: a named UCP protocol plus the schema of any parameters it leaves open. Simulations and planned measurements both reference a protocol by id rather than carrying the protocol text, so the same protocol runs identically everywhere.

Heavy columns (protocol_config, parameters_schema, source_protocol, …) are omitted from list responses unless requested via include; on a listed protocol they are None rather than empty. Fetch the full record with get().

Parameters:
  • id (str)

  • name (str)

  • organization_id (str)

  • project_id (str | None)

  • description (str | None)

  • protocol_config (dict[str, Any] | None)

  • parameters_schema (dict[str, Any] | None)

  • source_protocol (str | None)

  • created_by_email (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
organization_id: str#
project_id: str | None#

Project that owns the protocol. Every protocol reachable through the API is project-scoped; None only appears on unmigrated legacy rows.

description: str | None#
protocol_config: dict[str, Any] | None#

The protocol itself, in UCP form. None when not included in a list.

parameters_schema: dict[str, Any] | None#

Schema of the parameters the protocol leaves open (input[...] references). None when not included in a list.

source_protocol: str | None#

Original protocol text as the user entered it, when one was recorded.

created_by_email: str | None#
class ionworks.models.ParsedProtocol(*, parsed_protocol_ucp, parsed_protocol_human='', raw_protocol='', cycler_type=None, required_drive_cycles=<factory>, available_drive_cycles=<factory>, required_subroutines=<factory>, available_subroutines=<factory>, error=None, **extra_data)[source]#

Bases: BaseModel

Result of parsing a vendor protocol file into UCP.

Returned by parse_file(). A parsed protocol is not saved — pass ucp to create() to store it.

Drive cycles and subroutines referenced by the file may not be embedded in it. Those the parser recovered are listed in available_drive_cycles / available_subroutines; those still needed are in the required_ lists and must be supplied before the protocol will simulate.

Parameters:
  • parsed_protocol_ucp (str)

  • parsed_protocol_human (str)

  • raw_protocol (str)

  • cycler_type (str | None)

  • required_drive_cycles (list[str])

  • available_drive_cycles (list[str])

  • required_subroutines (list[str])

  • available_subroutines (list[str])

  • error (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow', 'populate_by_name': True, 'validate_by_alias': True, 'validate_by_name': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ucp: str#

The parsed protocol as UCP YAML.

human_readable: str#

Human-readable rendering of the same protocol.

raw_protocol: str#

The original file’s text, decoded with replacement for invalid bytes.

cycler_type: str | None#

Detected cycler vendor, when the parser could identify one.

required_drive_cycles: list[str]#
available_drive_cycles: list[str]#
required_subroutines: list[str]#
available_subroutines: list[str]#
error: str | None#

Set when the file parsed only partially; ucp may be incomplete.

class ionworks.models.Study(*, id, name, **extra_data)[source]#

Bases: BaseModel

Study model - accepts any fields from the API.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
class ionworks.models.PlannedMeasurementStatus(*values)[source]#

Bases: StrEnum

Lifecycle state of a planned measurement.

A measurement is first requested, then a scheduler assigns it a channel and time window (scheduled). Once the real run starts it becomes in_progress, then completed; it may be cancelled at any point before completion.

requested = 'requested'#
scheduled = 'scheduled'#
in_progress = 'in_progress'#
completed = 'completed'#
cancelled = 'cancelled'#
class ionworks.models.PlannedMeasurement(*, id, name, project_id, status, protocol_id=None, cell_specification_id=None, cell_instance_id=None, channel_id=None, planned_start_time=None, planned_end_time=None, estimated_duration_seconds=None, requested_by_email=None, scheduled_by_email=None, **extra_data)[source]#

Bases: BaseModel

Planned measurement model - accepts any fields from the API.

A planned measurement is a future, project-scoped test request. When status is scheduled it also reserves channel_id over the [planned_start_time, planned_end_time) window; a requested row carries only an estimated_duration_seconds and no channel.

Parameters:
  • id (str)

  • name (str)

  • project_id (str)

  • status (PlannedMeasurementStatus)

  • protocol_id (str | None)

  • cell_specification_id (str | None)

  • cell_instance_id (str | None)

  • channel_id (str | None)

  • planned_start_time (datetime | None)

  • planned_end_time (datetime | None)

  • estimated_duration_seconds (int | None)

  • requested_by_email (str | None)

  • scheduled_by_email (str | None)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str#
project_id: str#
status: PlannedMeasurementStatus#
protocol_id: str | None#

Named protocol (experiment_template) the measurement will run. Required on create; resolve its display name via the linked protocol.

cell_specification_id: str | None#

Cell specification the requester wants tested. Required on create.

cell_instance_id: str | None#

Cell instance the future measurement will run on. Nullable.

channel_id: str | None#

Channel reserved once scheduled. Nullable until scheduled.

planned_start_time: datetime | None#

Planned reservation window. Nullable until scheduled.

planned_end_time: datetime | None#
estimated_duration_seconds: int | None#

Expected run duration before scheduled times exist (requested rows).

requested_by_email: str | None#

Email of the user who requested the plan (flattened from the embed).

scheduled_by_email: str | None#

Email of the user who scheduled the plan; None until scheduled.

class ionworks.models.AutoScheduleAssignment(*, planned_measurement_id, planned_measurement_name, planned_measurement_updated_at, channel_id=None, channel_name=None, cycler_name=None, planned_start_time=None, planned_end_time=None, unscheduled_reason=None)[source]#

Bases: BaseModel

One proposed planned-measurement reservation.

A proposal may include an unscheduled assignment when the project has no in-service channel able to take the test. Only assignments with is_scheduled set should be passed to ionworks.planned_measurement.PlannedMeasurementClient.apply_auto_schedule_proposal().

Parameters:
  • planned_measurement_id (str)

  • planned_measurement_name (str)

  • planned_measurement_updated_at (datetime)

  • channel_id (str | None)

  • channel_name (str | None)

  • cycler_name (str | None)

  • planned_start_time (datetime | None)

  • planned_end_time (datetime | None)

  • unscheduled_reason (str | None)

planned_measurement_id: str#
planned_measurement_name: str#
planned_measurement_updated_at: datetime#
channel_id: str | None#
channel_name: str | None#
cycler_name: str | None#

Owning cycler’s name. Channel names repeat across cyclers, so the pair identifies the hardware being reserved.

planned_start_time: datetime | None#
planned_end_time: datetime | None#
unscheduled_reason: str | None#
property is_scheduled: bool#

Whether this assignment is complete and can be applied.

model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.AutoScheduleProposal(*, generated_at, assignments)[source]#

Bases: BaseModel

A transient earliest-gap proposal for selected requested tests.

Parameters:
generated_at: datetime#
assignments: list[AutoScheduleAssignment]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ionworks.models.Optimization(*, id, name=None, job_id, project_id, **extra_data)[source]#

Bases: BaseModel

Optimization model - accepts any fields from the API.

Parameters:
  • id (str)

  • name (str | None)

  • job_id (str)

  • project_id (str)

  • extra_data (Any)

model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

id: str#
name: str | None#
job_id: str#
project_id: str#
class ionworks.models.SimulationUsage(*, usage=0, limit=None, **extra_data)[source]#

Bases: BaseModel

Current simulation usage and its configured limit, in hours.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

usage: float#

Simulated battery time consumed this period, in hours.

limit: float | None#

Configured monthly limit in hours, or None when unconstrained.

class ionworks.models.ComputeUsage(*, usage=0, usage_by_type={}, limit=None, **extra_data)[source]#

Bases: BaseModel

Current compute usage and its configured limit, in hours.

usage is the total across all job types; usage_by_type breaks it down by job type (informational — the limit applies to the total).

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

usage: float#

Total backend compute time consumed this period, in hours.

usage_by_type: dict[str, float]#

Compute time per job type (simulation, datafit, optimization, validation, pipeline), in hours. Sums to usage.

limit: float | None#

Configured monthly limit in hours, or None when unconstrained.

class ionworks.models.OrganizationUsage(*, period_start, period_end, simulation=SimulationUsage(usage=0, limit=None), compute=ComputeUsage(usage=0, usage_by_type={}, limit=None), **extra_data)[source]#

Bases: BaseModel

An organization’s usage and limits for the current billing period.

Returned by usage(). Usage is aggregated across all members of the organization and resets on the first of each month. Simulation usage is a single figure; compute usage carries a per-job-type breakdown plus the total. All values are in hours. A None limit means that usage type is unconstrained.

Parameters:
model_config = {'extra': 'allow'}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

period_start: datetime#

Start of the current billing period (inclusive).

period_end: datetime#

End of the current billing period (exclusive) — the next reset.

simulation: SimulationUsage#
compute: ComputeUsage#
class ionworks.models.StepsAndCycles(*, steps, cycles)[source]#

Bases: BaseModel

Steps and cycle metrics for a measurement.

Returned by the /steps_and_cycles endpoint which fetches both in one call (cycles are derived from steps).

Parameters:
model_config = {'arbitrary_types_allowed': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

steps: DataFrame#
cycles: DataFrame#
classmethod convert_dict_to_df(v)[source]#

Convert dictionary to DataFrame.

Parameters:

v (Any)

Return type:

Any