Skip to content

Python Data API

BatteryProc 3.5 adds a supported, data-oriented public API so other tools can consume its parsed and processed battery data directly — without invoking the CLI, scraping log text, or reading generated files.

from batteryproc import ProcessOptions, process_file

result = process_file("cell_data.xlsx", ProcessOptions(write_artifacts=False))

processed = result.processed          # ProcessedCell
processed.capacity_summary            # reconciled per-cycle capacity table
processed.canonical_record            # cross-format canonical record table
processed.resolved_mass_g             # mass used for normalization (or None)
processed.mass_source                 # "cli" | "config" | "infer" | "prompt" | None
processed.diagnostics                 # structured, machine-readable diagnostics

With write_artifacts=False the call produces no plots, no files, and no interactive prompts — process_file() never blocks on input() unless you explicitly opt in with ProcessOptions(prompt_for_mass=True).

The CLI and the API share one analysis core: batteryproc <file> and process_file(file) run the exact same computation.

Pipeline functions

process_file() composes three reusable building blocks, all importable from the package root:

Function Input → Output Responsibility
parse_file(path, ParseOptions) file → ParsedCell Parser dispatch. The source file is parsed exactly once.
analyze_cell(parsed, AnalysisOptions) ParsedCell → ProcessedCell Pure computation: safety checks, mass resolution, summaries, reconciliation. No filesystem writes or prompts.
write_artifacts(processed, ArtifactOptions) ProcessedCell → list[ArtifactRecord] Optional plot/CSV production.

process_file() remains the normal convenience entry point; process_files() is the supported batch entry point. Callers that need custom orchestration (e.g. stop-after-N-failures) can call process_file in a loop or compose the building blocks themselves.

from batteryproc import AnalysisOptions, analyze_cell, parse_file

parsed = parse_file("cell_data.xlsx")
processed = analyze_cell(parsed, AnalysisOptions(mass_g=0.0064, compute_energy=True))

Supported formats

batteryproc.supported_extensions() is the source of truth for supported file formats (('.csv', '.mpt', '.txt', '.xlsx') at the time of writing — always consult the function rather than hard-coding the list). Neware support is .xlsx only (legacy .xls is not supported). parse_file raises WrongDataTypeError for unsupported extensions and ParserError for files the registered parser cannot read. Through process_file, an unsupported extension yields status="skipped" with error_category=None plus an unsupported_extension diagnostic, while a parse failure yields status="failure" with error_category=FileErrorCategory.PARSE plus a parser_failed diagnostic.

The ProcessedCell result

FileResult.processed carries the complete analytical result:

  • Source tables — source_record / source_cycle: the parser's format-specific standardized output (not raw instrument bytes).
  • Canonical table — canonical_record: BatteryProc's cross-format normalized representation (schema below).
  • Derived summaries — capacity_summary (always computed on success), energy_summary (None unless requested via ProcessOptions(energy=True) / averaging / AnalysisOptions(compute_energy=True)), first_cycle_profile, dqdv. recomputed_capacity_summary / recomputed_energy_summary hold the formulas-engine output before reconciliation against file-reported metrics; the reconciled tables are authoritative.
  • Mass provenance — mass_hint_g, resolved_mass_g, mass_source, resolved_cell_weight_g.
  • Provenance & metadata — parser_id, source_columns (canonical → original column mapping), canonical_schema_version, step_plan, step_signature, cycle_metadata, processing_options, processing_metadata (includes the batteryproc version).
  • Diagnostics — structured Diagnostic entries (codes below).

FileResult.cell (CellData) is retained for averaging/comparison compatibility; ProcessedCell is the route to complete processed data.

Returned DataFrames are live references, not defensive copies — treat them as read-only or call .copy() before mutating.

Canonical record schema (version 1.0)

ProcessedCell.canonical_record is produced by NormalizedInputs.to_frame(). Its version is published as batteryproc.CANONICAL_RECORD_SCHEMA_VERSION and stamped on every result as ProcessedCell.canonical_schema_version.

Column Unit Dtype Nullability Origin
time_s seconds float64 required, non-null measured (unwrapped to be monotonic per segment)
current_a amperes; positive = charge, negative = discharge float64 required, non-null measured (sign/unit normalized)
voltage_v volts float64 required, non-null measured
capacity_mAh milliamp-hours (cumulative) float64 optional column; values may be NaN measured or parser-derived
cycle_index — Int64 (nullable) optional column; values may be NA parser-provided or synthesized
step_type instrument step label string optional column parser-provided
mass_g grams float64 optional column parser-provided metadata
weight_kg kilograms float64 optional column parser-provided metadata

Index behaviour: a plain 0-based RangeIndex; cycle number is a column, never the index. Optional columns are omitted entirely when the source provides no data for them (check column in frame.columns). ProcessedCell.source_columns records which original column supplied each canonical column. If the required channels cannot be located, canonical_record is None and a missing_required_channel diagnostic is emitted.

Summary table schemas

capacity_summary (and recomputed_capacity_summary) columns:

Column Unit
Cycle Index —
Charge_mAh/g mAh per gram of active material
Discharge_mAh/g mAh per gram of active material
CE_percent %
CapacityRetention_percent % relative to the first non-zero discharge

energy_summary (and recomputed_energy_summary) columns: Cycle Index, Charge_mWh/g, Discharge_mWh/g (mWh per gram). Both tables use a RangeIndex with cycle number as a column. When no mass is available, specific quantities fall back to masses already present in the source data (see the mass contract below).

Mass resolution contract

Mass interpretation is owned by BatteryProc. One resolver applies this precedence order:

  1. Explicit per-call option (ProcessOptions.mass_g / AnalysisOptions.mass_g, the CLI's --mass-g).
  2. Configuration file value (metrics config mass_g).
  3. Parser-provided mass hint discovered in the source file (mass_hint_g).
  4. Interactive prompt — only when prompt_for_mass=True (CLI behaviour).
  5. Missing mass, represented explicitly: resolved_mass_g is None, mass_source is None, plus a mass_missing diagnostic.

mass_source reports which step won: "cli", "config", "infer", or "prompt". An inferred mass additionally emits a mass_inferred (severity info) diagnostic.

Force and safety semantics

force_process=True means exactly one thing: the caller accepts processing despite a BatteryProc safety/validation condition — the bad-cell capacity check and the too-new/incomplete-data check. It does not affect caching, artifact overwriting, downstream validation, or diagnostic emission (the bad_cell diagnostic is still emitted, downgraded to a warning with context={"forced": True}).

Diagnostics

Outcomes are classified by stable machine-readable codes (batteryproc.DiagnosticCode); messages are descriptive only. Each Diagnostic carries code, severity (info/warning/error), stage (parse/normalize/metrics/validation/artifact), source_path, and a small context mapping.

Code Meaning
unsupported_extension No parser registered for the file extension.
parser_failed The parser could not read the file.
missing_required_channel Canonical normalization could not locate time/current/voltage.
mass_missing No active-material mass could be resolved.
mass_inferred Mass was discovered in the source file.
cycle_summary_unavailable No per-cycle capacity summary could be computed.
first_cycle_incomplete The cell is still on its first cycle; summaries unavailable.
file_too_new Too little data to process safely (context={"blocked": True}).
bad_cell Failed the bad-cell capacity check (blocked or forced in context).
wrong_data_type The file is not the expected kind of data.
permission_denied The file could not be read.
unexpected_error An unclassified processing error.
artifact_write_failed Reserved for artifact-stage failures.

Side-effect guarantees

  • ProcessOptions(write_artifacts=False) disables every file and plot side effect, including creation of the output directory.
  • prompt_for_mass defaults to False for the API; process_file never blocks on input() unless it is enabled. The CLI sets it to True.
  • analyze_cell performs no filesystem writes regardless of options. It preserves the legacy CLI warning messages on stderr for bad-cell outcomes (the same conditions are always reported as structured diagnostics, which are the supported machine-readable channel).
  • Exceptions raised while writing artifacts (e.g. a failing matplotlib backend or a full disk) propagate from write_artifacts to the caller; the artifact_write_failed diagnostic code is reserved for a future non-raising mode.
  • Artifacts written by write_artifacts are described by ArtifactRecord entries (artifact_type, path, media_type, created).

Compatibility

The data API ships in BatteryProc 3.5.0 as an additive, semver-minor change: existing process_file/process_files callers, FileResult fields, CellData averaging, and all CLI behaviour are unchanged. One intentional behavioural difference is documented above: programmatic processing no longer prompts for mass by default. The canonical record schema, summary schemas, and diagnostic codes are versioned compatibility contracts; downstream consumers should pin the package version and assert CANONICAL_RECORD_SCHEMA_VERSION.

Downstream consumer example

from pathlib import Path

from batteryproc import (
    CANONICAL_RECORD_SCHEMA_VERSION,
    DiagnosticCode,
    ProcessOptions,
    process_file,
    supported_extensions,
)

def ingest(path: Path) -> dict:
    if path.suffix.lower() not in supported_extensions():
        raise ValueError(f"unsupported format: {path.suffix}")

    result = process_file(path, ProcessOptions(write_artifacts=False))
    if not result.ok:
        codes = [d.code for d in result.diagnostics]
        raise RuntimeError(f"processing failed: {codes}")

    cell = result.processed
    assert cell.canonical_schema_version == CANONICAL_RECORD_SCHEMA_VERSION
    if cell.has_diagnostic(DiagnosticCode.MASS_MISSING):
        ...  # decide how to handle unnormalized capacity

    return {
        "records": cell.canonical_record,
        "capacity": cell.capacity_summary,
        "mass_g": cell.resolved_mass_g,
        "mass_source": cell.mass_source,
        "parser": cell.parser_id,
    }