> ## 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 — the backend accepts any non-empty
value, so a new extractor never needs a migration or client release.
`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.                                           |
| `id`, `organization_id`, `created_by`, `created_at`, `updated_at` | Populated by the server.                                                                           |

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`, and
`notes` may be supplied.

```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_...")
```

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