> ## 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.

# Analyses

> Store features extracted from a cell measurement — like ECM parameters from EIS or LLI/LAM points from RPT data — as parquet-backed analysis records

An **analysis** captures features extracted from a single [cell
measurement](/data/measurements) — for example, ECM parameters fitted to an
EIS sweep, an LLI/LAM degradation point derived from an RPT, or a DCIR value
computed from an HPPC pulse. Each analysis is one row tied to one parent
measurement, plus a parquet file holding the extracted values.

Use analyses when you want to persist the output of a post-processing step
alongside the measurement it came from, so downstream tools (fitting,
visualization, reporting) can find it later without re-running the extractor.

## When to use an analysis

Reach for an analysis whenever you have derived, tabular results that:

* Belong to exactly one measurement (the "source of truth" measurement).
* You want to keep as structured columns rather than as a raw file
  attachment on a [file measurement](/data/measurements#file).
* May be produced by different extractors over time — analyses are
  free-form on `analysis_type`, so you can add new kinds without a schema
  change.

Typical examples:

| `analysis_type`    | Extracted from               | Typical columns                                  |
| ------------------ | ---------------------------- | ------------------------------------------------ |
| `ecm_from_eis`     | EIS time-series measurement  | `R0`, `R1`, `C1`, ... with units like `Ohm`, `F` |
| `lam_lli_from_rpt` | RPT time-series measurement  | `LAM_pe`, `LAM_ne`, `LLI` with unit `%`          |
| `dcir_from_hppc`   | HPPC time-series measurement | `dcir_charge`, `dcir_discharge` at various SOCs  |

`analysis_type` is a free-form string — any non-empty value is accepted, so
you can define your own extractor types freely. `ecm_from_eis`,
`lam_lli_from_rpt`, and `dcir_from_hppc` are the advisory set the Studio UI
surfaces by default.

## Anatomy of an analysis

Every analysis record carries:

| Field                                                             | Description                                                                                        |
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `measurement_id`                                                  | ID of the parent cell measurement this analysis is derived from.                                   |
| `name`                                                            | User-provided name for the analysis.                                                               |
| `analysis_type`                                                   | Kind of analysis (free-form string, e.g. `"ecm_from_eis"`).                                        |
| `columns`                                                         | List of `{name, unit, dtype?}` specs describing the parquet columns, for header preview in the UI. |
| `metadata`                                                        | Loose JSON object — source RPT/cycle identity, extractor parameters, solver settings, etc.         |
| `notes`                                                           | Free-text description of how the analysis was performed.                                           |
| `source_pipeline_id`                                              | Optional. ID of the pipeline run that produced this analysis.                                      |
| `source_simple_pipeline_id`                                       | Optional. ID of the simple pipeline that produced this analysis.                                   |
| `source_analysis_id`                                              | Optional. ID of another analysis this analysis was computed from.                                  |
| `source_label`                                                    | Optional. Free-text provenance note.                                                               |
| `id`, `organization_id`, `created_by`, `created_at`, `updated_at` | Populated by the server.                                                                           |

At most **one** of the three `source_*_id` fields may be set — an analysis
has a single upstream Ionworks record, or none. `source_label` is
independent and can be set on its own. See
[Tracking analysis provenance](#tracking-analysis-provenance) for how to
use these fields.

The tabular values themselves live in a parquet file stored in the
`measurement-data` bucket alongside the parent measurement. Signed download
URLs are minted on demand.

Analyses are managed through the [Python API client](/api-client), which
exposes them as `client.analysis`. `create()` accepts a DataFrame directly —
the client serialises it to parquet and uploads it for you.

## Creating an analysis

```python theme={null}
from ionworks import Ionworks, AnalysisType
import polars as pl

client = Ionworks()

df = pl.DataFrame({"R0": [0.012], "R1": [0.008], "C1": [1.4]})

analysis = client.analysis.create(
    measurement_id="msmt_abc123",
    name="ECM fit at 25 °C, 50% SOC",
    analysis_type=AnalysisType.ECM_FROM_EIS,  # or the string "ecm_from_eis"
    data=df,
    columns=[
        {"name": "R0", "unit": "Ohm"},
        {"name": "R1", "unit": "Ohm"},
        {"name": "C1", "unit": "F"},
    ],
    metadata={"soc_pct": 50, "temperature_degc": 25, "fit_rms": 0.0021},
    notes="Fitted with Randles model, 1e-3 to 1e5 Hz sweep",
)
print(analysis.id)
```

`analysis_type` is free-form — pass any string, or one of the
`AnalysisType` StrEnum members (`ECM_FROM_EIS`, `LAM_LLI_FROM_RPT`,
`DCIR_FROM_HPPC`) for the well-known values.

## Listing and fetching analyses

Provide **exactly one** of `measurement_id` or `project_id`. Filter by
`measurement_id` to list the analyses derived from a single measurement, or by
`project_id` to list every analysis across the measurements in a project.
Supplying both, or neither, is an error.

```python theme={null}
# All analyses for a single measurement
for a in client.analysis.list(measurement_id="msmt_abc123"):
    print(a.id, a.analysis_type, a.name)

# All analyses across a project (limit is capped at 100)
page = client.analysis.list(project_id="proj_xyz", limit=100, offset=0)
print(page.total)

# Single analysis record
analysis = client.analysis.get("ana_...")
```

## Downloading extracted-feature data

The parquet file is not returned inline. `get_data()` fetches a signed URL
and reads the parquet directly, returning a DataFrame in the
[configured backend](/api-client#dataframe-backend):

```python theme={null}
df = client.analysis.get_data("ana_...")
print(df.head())
```

Use `get_download_url()` if you'd rather download the raw parquet yourself.
The signed URL is valid for 5 minutes:

```python theme={null}
url = client.analysis.get_download_url("ana_...")  # valid for 5 minutes
```

## Updating metadata

`update()` changes row-level fields only — it does **not** replace the
parquet. Any subset of `name`, `analysis_type`, `columns`, `metadata`,
`notes`, `source_pipeline_id`, `source_simple_pipeline_id`,
`source_analysis_id`, and `source_label` may be supplied. See
[Tracking analysis provenance](#tracking-analysis-provenance) for the
source fields.

```python theme={null}
client.analysis.update("ana_...", {
    "name": "ECM fit at 25 °C, 50% SOC (v2)",
    "metadata": {"soc_pct": 50, "temperature_degc": 25, "fit_rms": 0.0018},
})
```

To replace the parquet itself, delete the analysis and re-create it.

## Deleting an analysis

Removes the row and its parquet file.

```python theme={null}
client.analysis.delete("ana_...")
```

## Browsing analyses in Studio

A measurement's detail page has an **Analyses** section that lists every
analysis derived from that measurement. Each entry shows the analysis name,
its `analysis_type`, and when it was created; click the analysis **name** to
open the analysis detail page and its extracted-feature table.

## Viewing an analysis in Studio

Every analysis has a detail page in Ionworks Studio that renders the parquet
data as an interactive plot alongside its metadata, columns, and notes. Open
it from the analyses list on the parent measurement, or navigate directly if
you have the URL.

### Breadcrumb trail

The header shows the full lineage of the analysis so you can walk back up to
any ancestor:

**Cell specification → Cell instance → Measurement → Analysis**

Each segment is a link. If lineage lookup fails for any reason, the trail
falls back to a single **Measurement** link — the page still loads.

### Data preview plot

The right-hand panel plots two numeric columns from the parquet against each
other. Use the **X axis** and **Y axis** dropdowns above the plot to pick any
pair whose values can be represented safely as JavaScript numbers. Non-numeric
columns (strings, booleans) and integer columns with values outside
JavaScript's safe-integer range (large IDs or timestamps that stay `int64`)
are not selectable.

* Axis labels use the `name [unit]` format declared in the analysis's
  `columns` spec, so pick meaningful `unit` values when you `create()` an
  analysis.
* The trace renders as `lines+markers` when the selected X column is
  monotonically non-decreasing, and as `markers` only otherwise — useful for
  scatter-style outputs like ECM parameter sweeps.
* On first load the axes default to the first two numeric columns.

### Row limits

The preview is capped to keep the browser responsive:

* The table shows the first **100 rows** of the parquet.
* The plot materializes at most the first **10,000 rows**.
* A caption below the plot reports the true row count and, when the file is
  larger than the plot cap, notes that the plot is truncated.

To work with the full dataset, use **Download parquet** in the header (which
mints a fresh signed URL) or fetch the data from Python:

```python theme={null}
df = client.analysis.get_data("ana_...")
```

### Source measurement link

The **View source measurement** button on the left column jumps straight to
the parent measurement's detail view — the same target as the measurement
segment of the breadcrumb. These cross-links make it easy to trace a fitted
ECM parameter or a degradation point back to the raw cycling data it was
extracted from, and to see at a glance which measurements already have
analyses attached.

## Tracking analysis provenance

An analysis's `measurement_id` says which measurement it was extracted
from. The `source_*` fields answer a different question: **which
computation produced it?** — a pipeline run, a simple pipeline, or another
analysis it was chained off — plus a free-text `source_label` for
provenance that isn't a linkable Ionworks record.

| Field                       | Description                                              |
| --------------------------- | -------------------------------------------------------- |
| `source_pipeline_id`        | The pipeline run whose output produced this analysis.    |
| `source_simple_pipeline_id` | The simple pipeline that produced this analysis.         |
| `source_analysis_id`        | The upstream analysis this one was computed from.        |
| `source_label`              | Free-text note (e.g. `"ECM refit with tighter bounds"`). |

At most one of the three `source_*_id` fields may be set on a given
analysis, and each referenced row must exist or the request is rejected;
the check does not also verify that the row is visible to you, so a
source ID for a record you can't otherwise see is set but only reads back
as unresolved when someone tries to follow the link. `source_label` is
independent — set it on its own when the source is not another Ionworks
record.

Set the fields at create time by passing them through
`client.analysis.update()` after creating the analysis, or when driving
the REST API directly by including them as `Form(...)` fields on `POST
/analyses`:

```python theme={null}
analysis = client.analysis.create(
    measurement_id="msmt_abc123",
    name="ECM fit at 25 °C, 50% SOC",
    analysis_type="ecm_from_eis",
    data=df,
    columns=[{"name": "R0", "unit": "Ohm"}],
)

# Record which pipeline produced it
client.analysis.update(analysis.id, {
    "source_pipeline_id": "pipe_xyz789",
    "source_label": "ECM refit with tighter bounds",
})
```

In Studio, the analysis detail page shows a **Source** row alongside the
existing "View source measurement" button. When the source resolves to a
pipeline or another analysis you have access to, the label is a link
straight to its detail page. Datasets on the [materials
page](/data/materials#tracking-dataset-provenance) use the same model, so
you can follow a curve → the fit that produced it → the analysis that
seeded the fit without leaving Studio.

## Analyses vs. file measurements

Both attach data to a measurement, but they serve different purposes:

|            | Analysis                                               | [File measurement](/data/measurements#file)  |
| ---------- | ------------------------------------------------------ | -------------------------------------------- |
| Parent     | One cell measurement                                   | A cell instance directly                     |
| Data shape | Tabular parquet with declared columns/units            | Arbitrary files (images, PDFs, numpy arrays) |
| Use for    | Extracted features, fit parameters, degradation points | SEM photos, X-ray CTs, post-mortem docs      |

Reach for an analysis when the output is structured tabular data derived
from an existing measurement. Reach for a file measurement when the data
is a standalone artifact tied to the cell itself.

## Next steps

<CardGroup cols={2}>
  <Card title="Measurements" icon="flask" href="/data/measurements">
    Time series, properties, and file measurement types — the parents of every analysis.
  </Card>

  <Card title="Reading data" icon="download" href="/data/reading">
    List, filter, and retrieve measurements together with their analyses.
  </Card>
</CardGroup>
