Skip to main content
July 13, 2026
EIS upload validation in the Python API, and faster pipeline detail page and ECM fits

EIS upload validation

cell_measurement.create(...) now accepts a data_type="eis" hint on the measurement dict. When set, validate_measurement_data runs a structural columns check (always on, requires Frequency [Hz], Z_Re [Ohm], Z_Im [Ohm]) plus two strict-only, individually-skippable heuristic checks. eis_zim_sign flags a spectrum whose capacitive band is predominantly positive — i.e. the raw Im(Z) sign is flipped, with the fix hint Z_Im = -Z_Im. eis_impedance_magnitude catches order-of-magnitude unit errors by comparing min(Z_Re) against a wide 100× band around a capacity- derived estimate — no data fetch, just a sanity check against the supplied rated_capacity. Both fire only on order-of-magnitude issues (a 1000× unit error, a fully flipped sign) so a plausible spectrum is never touched. Under the hood, the BioLogic reader now stores the raw imaginary part directly as Z_Im [Ohm] (negative across the capacitive arc), matching the validator’s convention.

Faster pipeline detail page and ECM fits

Phase 1 load-time wins on the pipeline detail page: the bulk element endpoint no longer ships the multi-MB job_config.parameters / cost / optimizer for every element (those keys already hydrate lazily on expand), and the frontend computeCumulativeParameters no longer re-runs O(N²) per render on every 5s poll tick. Large element types (DATA_FIT / ARRAY_DATA_FIT / VALIDATION) also stop double-storing their full objectives blob in both storage and the pipeline_elements DB row on newly created pipelines — the DB copy was never read. Separately, pipeline ECM fits reuse the fit’s own V_model for the final voltage (dropping a full symbolic build + CasADi compile), fit unknown initial SOC directly as an outer parameter (removing the coarse warm-up fit), and gain a lossless O(N) fast path on PiecewiseLinearTimeseries — all with bit-for-bit fit quality preserved.
Improvements
  • Protocol detail pages now show a Simulations section listing every simulation that uses the protocol, with the same bulk actions (add to study, rerun, delete) available on the parameterized-model simulation list.
  • Pipeline bulk cancel is now instant. DB status writes stay inline so the UI flips immediately with optimistic patches + rollback, and the slow Anyscale/Ray job-termination is offloaded to the background task queue. A single bulk-cancel endpoint replaces the per-pipeline loop the frontend was previously doing.
  • The Cycle Filter now clamps First N, Last N, and Range values that exceed the number of available cycles: it returns all available cycles and shows Showing all N cycles in neutral text. Explicit single-cycle typos (e.g. 0,50,200 on a 100-cycle measurement) still error, with a short, actionable message instead of the previous thousands-of-numbers wall of text.
  • Compute hours are now a first-class usage dimension alongside simulation hours, driven by each org’s contract (usage_limit can hold simulation and/or compute keys in seconds). The account drawer renders one meter per configured usage type, gated on a combined loading signal with placeholder cards sized to the last-known meter count, so the list no longer briefly collapses to zero at load.
Fixes
  • Deleting an organization as a regular Admin (non-super-admin) now returns a proper 403 Forbidden. Previously RLS silently blocked the delete but the endpoint returned 204, giving a false success while the org remained in the database.
  • Current sign-convention documentation corrected across backend field descriptions, the API discovery endpoint, and the data-upload docs: positive = discharge, negative = charge (matching the platform’s actual data, the ionworksdata validators, and the AI step-filter agent prompt, which already used the correct convention). Descriptions of external cycler conventions that legitimately differ (e.g. BioLogic / Arbin native positive = charge) are unchanged.
Improvements
  • Default population size for the DE and PSO optimizers is now capped at 40. Explicit population_size values still bypass the cap; only the auto-scaled default changed, and only above the cap.
Fixes
  • Pipeline.from_schema — the schema→runtime path used by local runs — now matches the server’s parser path in three places where it had drifted. The OCP interpolant metadata key defaults to {} (a {"data": df} input was previously rejected as missing the metadata key). Transform classes (Log10, Log, Inverse, …) resolve from iwp.transforms rather than top-level iwp, so schema-provided transforms are no longer silently dropped. And the vacuous multistart guard (Multiple multistarts are being used, but all initial guesses are identical) that fired as a downstream symptom of those drops is gone as a consequence.
Improvements
  • New ionworks-mcp package — a lightweight, read-only MCP (Model Context Protocol) server that exposes the Ionworks public API to MCP clients like Claude Desktop and Claude Code. It’s a thin wrapper over the ionworks-api Python SDK (auth, retries, caching and pagination inherited) with 15 tools spanning capability discovery, the cell hierarchy (specs, instances, measurements with time series, steps and cycles), and materials plus material-property datasets. Uses the same IONWORKS_API_KEY env var, launches over stdio, and mandatorily row- windows any DataFrame-returning tool so time-series calls never dump an unbounded payload (default 500 rows, max 5000, offset paging, optional columns projection). Nothing is ever written or mutated.
  • GET /organizations/current/usage is now public and returns per- dimension usage (simulation, compute) with limits, so SDK clients can read their own quotas without an admin token.
Improvements
  • Neware BTSDA .xlsx files with no explicit sheets option now auto-select the record sheet — where the time series actually lives — instead of defaulting to the metadata-only first sheet and failing with a missing Timestamp error. The previous “you must pass sheets={type: 'name', value: 'record'}” gotcha is now automatic; files without a record sheet still fall back to the first-sheet default.
Fixes
  • The Maccor reader now maps Cycle C — the cumulative cycle counter, the true cycle number — to Cycle from cycler. Previously it mapped Cycle P, a program/loop counter that is often 0 and can reset within a procedure, so any file containing both columns reported meaningless zeros for the cycle number.
Fixes
  • Arbin schedules that reference a subschedule (SubSchedule step) no longer fail parsing with Duplicate step name found: 'End Test'. The reserved End Test terminal is exempted from the duplicate-name check — each subschedule keeps its own copy, so any goto: "End Test" from any scope still resolves at simulation time. Genuinely-named block collisions (e.g. a user Loop_X appearing in two scopes) still error as they should.