> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ionworks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 2026 07 13

<Update label="July 13, 2026" description="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.

  <AccordionGroup>
    <Accordion title="Studio">
      **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.
    </Accordion>

    <Accordion title="Pipeline">
      **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.
    </Accordion>

    <Accordion title="Python API">
      **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.
    </Accordion>

    <Accordion title="Data Processing">
      **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.
    </Accordion>

    <Accordion title="Protocol Simulator">
      **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.
    </Accordion>
  </AccordionGroup>
</Update>
