Metadata-Version: 2.5
Name: cellpy-simple-gui
Version: 0.2.1
Summary: A simple, good-looking desktop GUI for exploring battery cell data with cellpy (>= 2.1).
Project-URL: Homepage, https://github.com/cellpy/cellpy-simple-gui
Project-URL: Repository, https://github.com/cellpy/cellpy-simple-gui
Project-URL: Issues, https://github.com/cellpy/cellpy-simple-gui/issues
Project-URL: Deployment guide, https://github.com/cellpy/cellpy-simple-gui/blob/main/docs/deployment.md
Project-URL: Windows installer, https://github.com/cellpy/cellpy-simple-gui/blob/main/docs/windows-installer.md
Project-URL: cellpy, https://github.com/jepegit/cellpy
Author: cellpy
License-Expression: MIT
License-File: LICENSE
Keywords: battery,cellpy,cycling,data-analysis,electrochemistry,gui
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: Chemistry
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.13
Requires-Dist: cellpy>=2.1.5.post7
Requires-Dist: fastapi>=0.115
Requires-Dist: jinja2>=3.1
Requires-Dist: loguru>=0.7.3
Requires-Dist: openpyxl>=3.1
Requires-Dist: pandas>=3.0
Requires-Dist: plotly>=5.22
Requires-Dist: polars>=1.0
Requires-Dist: pydantic-settings>=2.3
Requires-Dist: pydantic>=2.7
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: tables
Requires-Dist: uvicorn[standard]>=0.30
Provides-Extra: build
Requires-Dist: pyinstaller>=6.10; extra == 'build'
Provides-Extra: desktop
Requires-Dist: pywebview>=6.0; extra == 'desktop'
Provides-Extra: dev
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: pyyaml>=6; extra == 'dev'
Provides-Extra: e2e
Requires-Dist: playwright>=1.40; extra == 'e2e'
Provides-Extra: export
Requires-Dist: kaleido<1.4,>=0.1; extra == 'export'
Provides-Extra: mcp
Requires-Dist: mcp>=2.0; extra == 'mcp'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://github.com/cellpy/cellpy-simple-gui">
    <img src="assets/cellpy-icon-long.svg" width="300" alt="cellpy" />
  </a>
</p>

<h1 align="center">cellpy simple gui</h1>

<p align="center"><b>Battery cell data, explored — a small desktop app built on <a href="https://github.com/jepegit/cellpy">cellpy</a> (≥ 2.1).</b></p>

<p align="center">
  Load <code>.cellpy</code> files, raw instrument exports or whole batch journals, compare cells
  with cellpy's own summary and cycle plots, group and average them, save the lot as a
  portable project, and export the numbers and figures.
</p>

<p align="center">
  <a href="https://pypi.org/project/cellpy-simple-gui/"><img alt="PyPI" src="https://img.shields.io/pypi/v/cellpy-simple-gui?color=2f6fde&label=PyPI" /></a>
  <a href="https://github.com/cellpy/cellpy-simple-gui/releases/latest"><img alt="Latest release (Windows installer)" src="https://img.shields.io/github/v/release/cellpy/cellpy-simple-gui?color=1f8f6b&label=Windows%20installer&logo=windows&logoColor=white" /></a>
  <img alt="Python ≥ 3.13" src="https://img.shields.io/badge/python-%E2%89%A5%203.13-3776ab?logo=python&logoColor=white" />
  <a href="https://github.com/jepegit/cellpy"><img alt="cellpy ≥ 2.1" src="https://img.shields.io/badge/cellpy-%E2%89%A5%202.1-1f8f6b" /></a>
  <a href="https://github.com/cellpy/cellpy-simple-gui/actions/workflows/essential-tests.yml"><img alt="Essential tests" src="https://github.com/cellpy/cellpy-simple-gui/actions/workflows/essential-tests.yml/badge.svg" /></a>
  <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-7a7a7a" /></a>
</p>

<p align="center">
  <a href="https://github.com/cellpy/cellpy-simple-gui/releases/latest"><b>Download</b></a> ·
  <a href="#install"><b>Install</b></a> ·
  <a href="#quick-start-from-a-clone"><b>Quick start</b></a> ·
  <a href="#features"><b>Features</b></a> ·
  <a href="#run-it-as-a-server"><b>Server</b></a> ·
  <a href="#building-your-own-cellpy-app"><b>Build your own</b></a> ·
  <a href="docs/"><b>Docs</b></a>
</p>

> **Get it:** Windows users — grab the installer from the
> [**latest release**](https://github.com/cellpy/cellpy-simple-gui/releases/latest)
> (no Python, no admin; the [newest build from `main`](https://github.com/cellpy/cellpy-simple-gui/releases/tag/continuous)
> is there too). Everyone else — `uv tool install "cellpy-simple-gui[desktop]"`.
> Details under [Install](#install).

<p align="center">
  <img src="docs/img/demo.gif" width="100%" alt="Demo: load the bundled demo cells, group them, open the cell explorer, import a raw Neware file through the Add cells dialog, and switch to the light theme" />
</p>

<p align="center"><sub>
  Load the demo cells → group them → cell explorer → import a raw Neware file through <b>Add cells…</b> → back to the summary → light theme.
  Everything runs locally: a <a href="https://fastapi.tiangolo.com/">FastAPI</a> backend inside a native
  <a href="https://pywebview.flowrl.com/">pywebview</a> window (or your browser).
</sub></p>

## Some examples

<table>
  <tr>
    <td colspan="2" align="center">
      <img src="docs/img/summary_collect.png" alt="Cycle summary — capacity and coulombic efficiency for three grouped cells" />
      <br /><sub><b>Cycle summary.</b> cellpy's <code>collect_summaries</code> across every selected cell — capacity + CE here, with a plot-type selector, gravimetric / areal / absolute basis and independent or shared y-scales.</sub>
    </td>
  </tr>
  <tr>
    <td width="50%" align="center">
      <img src="docs/img/add_cells_staged.png" alt="Add cells dialog with a staged list of files to load" />
      <br /><sub><b>Add cells…</b> Drop, browse, paste paths or glob patterns, or find files in a remote folder — everything lands in one <em>staged list</em> you review before a single button loads it.</sub>
    </td>
    <td width="50%" align="center">
      <img src="docs/img/add_cells_raw.png" alt="Add cells dialog, raw instrument files tab with Neware preset" />
      <br /><sub><b>Raw instrument files.</b> Instruments are discovered from cellpy at runtime; set mass, area, nominal capacity and cycle mode once and import.</sub>
    </td>
  </tr>
  <tr>
    <td width="50%" align="center">
      <img src="docs/img/cell_collect.png" alt="Cell explorer — voltage versus capacity curves for selected cycles" />
      <br /><sub><b>Cell explorer.</b> Voltage–capacity curves for any set of cycles, with per-cell metric tiles.</sub>
    </td>
    <td width="50%" align="center">
      <img src="docs/img/cell_dqdv.png" alt="Cell explorer showing incremental capacity (dQ/dV) curves" />
      <br /><sub><b>dQ/dV.</b> The same cycles as incremental capacity (or differential voltage), charge / discharge / both.</sub>
    </td>
  </tr>
  <tr>
    <td width="50%" align="center">
      <img src="docs/img/cycles_per_cell.png" alt="Cycles collector — cycle curves for every selected cell, one panel per cell" />
      <br /><sub><b>Cycles collector.</b> The same three curve types across <em>every</em> selected cell, laid out per cycle, per cell, or as a film plot.</sub>
    </td>
    <td width="50%" align="center">
      <img src="docs/img/summary_grouped_light.png" alt="Grouped cycle summary in the light theme with a mean ± std spread band" />
      <br /><sub><b>Groups &amp; light theme.</b> Average cells per group with a mean ± std spread band — here with the light theme on.</sub>
    </td>
  </tr>
  <tr>
    <td width="50%" align="center">
      <img src="docs/img/manage_cells.png" alt="Manage cells modal — filter, sort, select by group, remove" />
      <br /><sub><b>Manage cells.</b> Rename, regroup, select by group, filter and sort the journal of loaded cells.</sub>
    </td>
    <td width="50%" align="center">
      <img src="docs/img/projects_dark.png" alt="Projects panel — save and reopen portable project folders" />
      <br /><sub><b>Projects.</b> Save the loaded set plus grouping, labels and selection to a portable folder; reopen it later.</sub>
    </td>
  </tr>
  <tr>
    <td colspan="2" align="center">
      <img src="docs/img/plot_types.png" alt="Four summary plot types: capacity, coulombic efficiency, C-rate and internal resistance" />
      <br /><sub><b>Plot types.</b> Capacity, coulombic efficiency, C-rate, internal resistance — plus cumulated CE, end voltages and capacity loss; <a href="#developer-mode">developer mode</a> unlocks every family cellpy registers.</sub>
    </td>
  </tr>
</table>

---

## Features

- **Demo cells** — three bundled examples, plus bundled raw-file demos.
- **`.cellpy` and legacy `.h5` files.**
- **Raw instrument files** — Arbin `.res`, Maccor (text), Neware, PEC, and others. Mass, area, nominal capacity, cycle mode.
- **Add cells** — drop, browse, upload, pasted paths, globs, or a remote folder. Staged list before load. Batch journals (`.json`).
- **Cycle summary** — capacity, CE, cumulated CE, end voltages, internal resistance, C-rate, capacity loss. Gravimetric, areal, or absolute. Group mean ± std. Shared or independent y-scales.
- **Cell explorer** — voltage–capacity, dQ/dV, dV/dQ. Compare cells, overlaid or side by side.
- **Cycles collector** — per cycle, per cell, or film plot.
- **Cell list** — rename, group, select, filter, sort, remove.
- **Projects** — save and reopen the loaded cells, grouping, labels, and selection. Unsaved edits show as `name*`.
- **Export** — CSV, Excel, Parquet, JSON; figures as PNG, SVG, PDF.
- **Light and dark themes.**
- **Background loading** with progress.
- **Instruments** from the installed cellpy, including each loader's sub-models.
- **Developer mode** — every summary family cellpy registers, raw traces, diagnostics (`run --dev`).
- **Logging** — `CSG_LOG_LEVEL`, default `INFO`.

## Install

Requires **Python ≥ 3.13**. With [uv](https://docs.astral.sh/uv/):

```bash
uv tool install "cellpy-simple-gui[desktop]"
cellpy-simple-gui
```

Or run it once without installing anything:

```bash
uvx --from "cellpy-simple-gui[desktop]" cellpy-simple-gui
```

pipx works too: `pipx install "cellpy-simple-gui[desktop]"`.

The `[desktop]` extra is what gives you the **native window**. Leave it off and
you get a fully working app that opens in your browser instead — which is what a
server wants, and why it is an extra rather than a dependency.

Two heavier routes, if you would rather not have Python in the picture:

- **Windows installer** — 178 MB, no admin, Start-menu entry. Download the
  [latest release](https://github.com/cellpy/cellpy-simple-gui/releases/latest),
  or the [newest build from `main`](https://github.com/cellpy/cellpy-simple-gui/releases/tag/continuous)
  (rebuilt on every code change). Each installer is also a `.zip` of that same
  file, for machines that block an `.exe` download. See [`docs/windows-installer.md`](docs/windows-installer.md).
- **Container** — `docker compose up`. See [`docs/deployment.md`](docs/deployment.md).

---

## Quick start (from a clone)

Requires **Python ≥ 3.13** and [uv](https://docs.astral.sh/uv/).

```bash
uv sync --extra desktop
```

The native window lives in the `desktop` extra so a served instance need not
install GUI libraries. Plain `uv sync` gives you a fully working app that opens
in your browser instead.

Then from this folder:

```bash
run                 # Windows (cmd / PowerShell)
./run               # macOS / Linux / Git Bash
```

That opens the app in a native desktop window. Prefer your normal browser?

```bash
run --server              # local server + browser tab
run --server --no-open    # headless: just serve
```

The helpers use `uv run --extra export --extra desktop`, so figure export and the
native window both work. Equivalent without them:
`uv run --extra export --extra desktop cellpy-simple-gui` (same flags).
Then click **Load demo cells** and explore.

### Build the Windows installer

```powershell
pwsh packaging/build_installer.ps1
```

Produces a ~178 MB per-user installer: no admin, Start-menu entry, clean
uninstall that leaves your projects alone. It is **unsigned**, so SmartScreen
will warn on first run — [`docs/windows-installer.md`](docs/windows-installer.md)
explains exactly what you will see, why, and what fixing it costs, along with
where the app writes its logs when something goes wrong.

Cutting a release of any of the three artefacts:
[`docs/releasing.md`](docs/releasing.md).

### Run it as a server

```bash
docker compose up --build      # or: docker build -t cellpy-simple-gui .
```

One container serves **one person** — the library, the job manager and cellpy's
config session are process-global, by design. The per-launch token is **not**
authentication: put it behind a reverse proxy with TLS and real auth, or keep it
on a network you already trust. [`docs/deployment.md`](docs/deployment.md) has
the full story, including the things that bite (volume ownership, cellpy's own
directories, and why server-side figure export is not in the image).

### Developer mode

```bash
run --dev                 # or set CSG_DEV_MODE=1
```

The regular UI shows a curated set of plot types, because cellpy registers far
more than are useful on any one dataset. Developer mode adds **every summary
family cellpy registers**, grouped by whether the loaded cells can actually plot
them — the rest are listed but disabled, showing which summary columns are
missing rather than rendering a blank chart. Availability comes from cellpy
itself (`family.summary_options()`), so families whose columns the *collector*
builds — CV splits, the full-cell standards — are offered rather than hidden. It
also raises the glob/batch file cap (10 → 500) for stress-testing. A **DEV**
badge marks the session.

Two extra Cell-explorer views appear alongside the regular ones:

| View | What it shows |
|---|---|
| **Raw traces** | The raw time series (voltage/current/capacity, or all of it) |
| **Raw + step/cycle info** | Raw traces annotated with step and cycle boundaries |

Raw data is big — 155k rows for a single demo cell — so **Max points** caps what
is drawn (cellpy does the downsampling itself since 2.1.2,
[#867](https://github.com/jepegit/cellpy/issues/867)) and the chart says when it
applied: *"showing 1,882 of 155,754 points"*. For raw *data* rather than a
picture, use **Export cells → csv**.

Clicking the **DEV** badge opens **Diagnostics**:

- **Logs** — a live tail of the last 2000 records, filterable by level. cellpy's
  own records arrive here too (they go through stdlib logging), so a failed load
  can be diagnosed without opening `cellpy_debug.log`.
- **Jobs** — every job this session with how long it waited for a worker and how
  long it ran.

**copy** puts either view on the clipboard as plain text, ready for a bug report.

Off by default and not reachable from the UI: regular users get the curated set.

> First run downloads a few small example cells from the cellpy example-data
> repository (then cached). Everything else is fully offline.

## Projects on disk

A project is a portable folder — move it, zip it, share it:

```
<project>/
├── project.json      # manifest: name, timestamps, versions, per-cell grouping/labels/selection
├── cellpy.toml       # optional: cellpy settings pinned to this project
└── data/
    ├── c1.cellpy     # every loaded cell saved as a self-contained cellpy file
    └── c2.cellpy
```

Projects live under `~/.cellpy_simple_gui/projects/` by default; **Save** writes the
current set there and **Open** restores it (physical quantities come from the
`.cellpy` files, organisational metadata from the manifest). Changes in the UI
are **not** written until you Save.

That split is also why Save is quick. Re-saving reuses a `.cellpy` file whenever
the cell it holds provably has not changed — nothing edited it since it was
read, the file is still there, and its size and timestamp still match. Renaming
or regrouping touches only `project.json`. Editing mass, area, nominal capacity
or cycle mode rewrites the summary, so those cells are written out again; if
anything is uncertain, the file is rewritten. Save-As always produces a complete
project.

### Moving the data directory

`CSG_DATA_DIR` decides where projects live. It defaults to
`~/.cellpy_simple_gui`, so desktop installs need nothing; a container points it
at a volume:

```bash
CSG_DATA_DIR=/data cellpy-simple-gui --server --no-open
```

`~` and relative paths are expanded and resolved, and reading the setting never
creates anything — whoever writes creates what it needs.

**cellpy keeps its own directories**, separate from the app's, and they default
under `$HOME` too. Anything running where `$HOME` is not persistent should place
them deliberately. Every field under `[paths]` has an environment override,
`CELLPY_PATHS__<FIELD>`:

```bash
CELLPY_PATHS__CELLPYDATADIR=/data/cellpy/cellpyfiles
CELLPY_PATHS__OUTDATADIR=/data/cellpy/out
CELLPY_PATHS__EXAMPLESDIR=/data/cellpy/examples   # bundled demo cells cache here
CELLPY_PATHS__FILELOGDIR=/data/cellpy/logs
```

Overrides apply per field, so setting one leaves the rest alone. The **cellpy
version badge** in the app shows every setting and which layer it came from,
which is the quickest way to confirm a deployment is reading what you think.

### Local vs served: what the app may touch

Typing `D:\data\*.res` into the app is the point of a desktop tool. Answering
requests over a network with that same freedom is arbitrary read and write on
the **host**, behind nothing but a per-launch token — so the app has two modes,
chosen by the bind address:

| | bound to | paths |
|---|---|---|
| **local** | loopback | anywhere you can reach, as always |
| **served** | anything else | inside `CSG_DATA_DIR` only |

In served mode, absolute paths, `..`, drive letters, UNC paths and symlinks that
point outward are all refused — the *resolved* location is what gets checked, so
a link that looks innocent does not get through. Globs are rooted at the data
directory and their results filtered, because `**` through a symlinked directory
is the classic way out.

One case the bind address gets wrong: an instance on loopback **published by a
reverse proxy** looks local from inside, because the proxy connects from
loopback. Force it:

```bash
CSG_ALLOW_HOST_PATHS=0
```

`/api/system/capabilities` reports `host_paths_allowed` and `sandbox_root`, so
you can confirm which rules an instance is running under.

> A served instance currently reads files that are already inside its data
> directory — mount your data there. Browser upload is
> [#133](https://github.com/cellpy/cellpy-simple-gui/issues/133).

### Remote files (SSH / SFTP)

On a **desktop** (local) instance you can paste a single remote file URI into
the path field of **Add cells… → cellpy files** or **Raw instrument files**
(it is staged as *remote*; there is no existence check until the load runs):

```text
sftp://user@hostname/home/user/lab/raw/20160805_test001_45_cc_01.res
```

Supported schemes: `ssh://`, `sftp://`, `scp://` (cellpy’s `OtherPath`). cellpy
copies the file to a local temp dir, then opens it. Served instances refuse
remote URIs — use upload or a volume mount instead.

Credentials come from cellpy’s own env / `~/.env_cellpy` (the app already loads
them with cellpy’s config stack):

| Variable | Purpose |
| --- | --- |
| `CELLPY_KEY_FILENAME` | Path to an SSH private key (preferred) |
| `CELLPY_PASSWORD` | Password fallback |

Use a **fully resolvable hostname** (or IP) in the URI — OpenSSH `Host` aliases
often fail with Paramiko (cellpy #687). See cellpy’s
[remote paths](https://cellpy.readthedocs.io/) docs for more.

#### Find files in a remote folder

Globs (`*.res`) do not work on remote URIs. Instead, open **Find in a remote
folder…** inside the *Add cells…* dialog (cellpy files or raw tab), paste a
remote **directory** URI (`sftp://user@hostname/home/user/lab/raw/my_project/`)
and, optionally, a name filter (`cc_01` means *contains*; `2016*_cc_*` is used
as a glob). The app walks the folder recursively with cellpy’s `filefinder` —
on the raw tab it looks for the selected instrument’s extension(s), on the
cellpy tab for `.cellpy` / `.h5` — and the matches appear in the **staged
list**, capped at “max”, where you can remove any of them before pressing
**Load N files** / **Import N files**. Prefer a project-scoped folder: listing
a whole shared raw-data tree over SFTP is slow. Served instances refuse remote
find like any other remote URI.

### Per-project cellpy settings

Drop a `cellpy.toml` next to `project.json` and the app activates it as cellpy's
**project** configuration layer whenever that project is open — so the project
pins the settings its data was analysed with (cycle mode, units, defaults)
instead of being silently re-interpreted under whatever your global config says
today:

```toml
[reader]
cycle_mode = "cathode"

[units]
mass = "g"
```

A **cellpy.toml** chip appears next to the project name while those settings are
active; closing the project restores your own configuration. Settings never leak
from one project to the next. Click the chip (or the cellpy version badge) to see
every setting and which layer it came from — defaults, your user `cellpy.toml`,
the project file, or the environment.

Rather than writing the file by hand, open that panel with a project loaded and
hit **Pin settings to project**: it captures the `reader`, `units` and
`defaults` currently in effect. Only those sections are written — `paths` would
bake your machine's directory layout into a folder meant to be portable, and
leaving out `instruments`/`db` means the file structurally cannot contain a
credential.

The app never writes your user `cellpy.toml`; that file belongs to you and is
shared with your notebooks and the `cellpy` CLI.

## How it is built

Three layers, each testable on its own. The UI never imports cellpy; the core
never imports the web framework.

```
┌──────────────────────────────────────────────────────────────┐
│  desktop shell (pywebview window)                             │
│    └── web/  Alpine.js + Plotly.js  (served by the backend)   │
├──────────────────────────────────────────────────────────────┤
│  api/   FastAPI (127.0.0.1 + per-launch token)                │
│         routers · JobManager (threads + SSE progress)         │
├──────────────────────────────────────────────────────────────┤
│  core/  pure Python — no web imports                          │
│         models · library · plotting · export                  │
│         cellpy_adapter.py  ← the ONLY module that imports cellpy
└──────────────────────────────────────────────────────────────┘
                              │
                          cellpy ≥ 2.1
```

```
src/cellpy_simple_gui/
├── core/
│   ├── cellpy_adapter.py   # every cellpy call lives here (get, summary, get_cap, example_data)
│   ├── models.py           # Pydantic domain models (CellMeta, SummaryPlotSpec, …)
│   ├── library.py          # in-memory library of loaded cells = source of truth
│   ├── collect.py          # bridges the library into cellpy.collect / .plotting (from_cells)
│   ├── plotting.py         # thin: delegates figures to cellpy via collect.py
│   ├── projects.py         # save/open portable project folders (manifest + .cellpy files)
│   ├── files.py            # glob/path expansion with a max cap + messages
│   ├── paths.py            # the sandbox: what a served instance may read and write
│   ├── uploads.py          # files brought in through the browser
│   └── export.py           # csv / xlsx / parquet / json from cellpy collections
├── api/
│   ├── app.py              # FastAPI factory + index route
│   ├── jobs.py             # tiny thread-pool JobManager (progress + cancel)
│   └── routers/            # cells · plots · export · jobs · projects · ingest · system
├── web/                    # templates/ (Jinja) + static/ (css, js, vendored Plotly & Alpine)
├── logging_setup.py        # loguru terminal logging + stdlib bridge
├── server.py               # uvicorn-in-a-thread helper
├── desktop.py              # pywebview launcher
└── __main__.py             # entry point (desktop / --server)
```

**Powered by cellpy, not around it.** Summaries, grouping (incl. group averaging
with spread), per-cell cycle curves, the plots and the multi-format export are all
produced by cellpy's own `collect` / `plotting` subsystems. On **cellpy ≥ 2.1.1**
the app feeds them a real `Batch` via `cellpy.collect.from_cells(...)` built from
the in-memory library (`collect.py`), and discovers instruments via
`cellpy.list_instruments()`. The cellpy surface stays isolated to
`cellpy_adapter.py` + `collect.py`.


## Building your own cellpy app

This app is a reference implementation. It is a poor thing to start *from* — at
~8,000 lines, most of what you would read is machinery you have not decided you
want yet.

[`examples/starter/`](examples/starter/) is the starting point instead: one file,
~340 lines, that loads cells, builds a cellpy `Collection`, plots it and exports
the numbers, and nothing else.

```bash
uv run examples/starter/app.py     # then open http://127.0.0.1:8000
```

It carries its own dependencies in a PEP 723 header, so `uv run --script app.py`
also works once you have copied it somewhere else — which is what it is for. See
[`examples/starter/README.md`](examples/starter/README.md) for the four cellpy
calls it is built from, how to add a plot, and where in this repository to look
when you outgrow it.

Then [`docs/guides/`](docs/guides/) — seven guides organised by what you are
trying to do rather than by module:

| | Guide | You are trying to |
|---|---|---|
| 1 | [Getting cells into memory](docs/guides/01-loading-cells.md) | open a file, and find out why one will not open |
| 2 | [Cells into a Collection](docs/guides/02-collections.md) | get many cells into one frame you can plot and export |
| 3 | [Plotting a collection](docs/guides/03-plotting.md) | draw it, and build a plot menu that is not a hard-coded list |
| 4 | [Exporting data and figures](docs/guides/04-exporting.md) | get numbers and images out, in memory |
| 5 | [Configuration](docs/guides/05-configuration.md) | find out where cellpy is reading from, and change it safely |
| 6 | [Process state and threading](docs/guides/06-state-and-threading.md) | call cellpy from a worker thread without cross-talk |
| 7 | [What cellpy will and will not do for you](docs/guides/07-delegation.md) | decide whether to write the code or let cellpy do it |

Every Python block in those guides is executed by
[`tests/test_guides.py`](tests/test_guides.py), so a guide whose code stops
working against a new cellpy breaks CI rather than the reader.

### If you are a coding agent

Same knowledge, different shape:

| | |
|---|---|
| [`llms.txt`](llms.txt) | index of everything here, per the [llms.txt](https://llmstxt.org) convention |
| [`llms-full.txt`](llms-full.txt) | the guides, the API reference and the starter app in one file (~120 KB) |
| [`docs/api-reference.md`](docs/api-reference.md) | the 44 cellpy calls that matter, with signatures **introspected from the installed cellpy** and one line each of what a signature cannot tell you |
| [`skills/cellpy-app/`](skills/cellpy-app/) | a Claude Skill packaging the guides — copy it into `.claude/skills/` |

All four are generated ([`tools/`](tools/)) and checked in CI, so they cannot
drift from the guides or from the installed cellpy. Regenerate after a cellpy
upgrade:

```bash
uv run tools/gen_api_reference.py && uv run tools/gen_llms_txt.py
```


## Development

```bash
uv sync --extra dev
uv run pytest            # core unit tests + FastAPI integration tests
uv run pytest -m essential   # critical-path subset (also run in CI)
```

The suite passes without the `desktop` extra — that is the point of it being an
extra, and one test skips accordingly. Add `--extra desktop` to run that one too.

Optional Playwright GUI smoke tests (server/browser mode, not pywebview):

```bash
uv sync --extra dev --extra e2e
uv run playwright install chromium
uv run pytest -m e2e
```

Without the `e2e` extra / Chromium browsers, those tests skip so a plain
`uv run pytest` stays green.

GitHub Actions runs the `essential` marker. One workflow decides internally
whether the change touched code, so every PR gets exactly one `essential` check
run — and a green one means either "tests passed" or "no code changed", never
"a second workflow reported first". If the base commit cannot be resolved, it
runs the tests rather than guessing.

Optional static figure export (**Export ▾ → Figure → PNG / SVG / PDF**, via kaleido):

```bash
uv sync --extra export
```

Without that extra, data export still works; figure formats show a toast pointing
at the install command above.

### Why we develop this app

The app is meant to inspire other users of cellpy to make their own apps. We
also use the development of this app to find pain-points in the cellpy library.

Already now, building this surfaced a number of cellpy rough edges, filed upstream as
[jepegit/cellpy#785–#791](https://github.com/jepegit/cellpy/issues/785) — **most were fixed in cellpy 2.1.1**. The full write-up and per-item status is in
[CELLPY_PAINPOINTS.md](CELLPY_PAINPOINTS.md).

## Status

This is an MVP / reference implementation

Done: load `.cellpy`/`.h5` files, **raw-file ingestion** (Arbin `.res` / Maccor / Neware /
PEC → cellpy), cycle-summary & cell-explorer plotting, editable cell list + Manage cells
modal, CSV/Excel/Parquet/JSON export with feedback, **save/open/close portable projects**,
batch-journal loading with error toasts, loguru console logging, and essential-test CI.

> Arbin `.res` loads on Windows through the Access ODBC driver (no separate mdbtools needed
> in this environment). Instruments needing extra engines will surface a clear error.

### Future plans

- packaging into a Windows installer (PyInstaller + InnoSetup, bundling WebView2).
- show-case more of the plots

## License

See [LICENSE](LICENSE).
