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(Noneunless requested viaProcessOptions(energy=True)/ averaging /AnalysisOptions(compute_energy=True)),first_cycle_profile,dqdv.recomputed_capacity_summary/recomputed_energy_summaryhold 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 thebatteryprocversion). - Diagnostics — structured
Diagnosticentries (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:
- Explicit per-call option (
ProcessOptions.mass_g/AnalysisOptions.mass_g, the CLI's--mass-g). - Configuration file value (
metrics config mass_g). - Parser-provided mass hint discovered in the source file (
mass_hint_g). - Interactive prompt — only when
prompt_for_mass=True(CLI behaviour). - Missing mass, represented explicitly:
resolved_mass_g is None,mass_source is None, plus amass_missingdiagnostic.
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_massdefaults toFalsefor the API;process_filenever blocks oninput()unless it is enabled. The CLI sets it toTrue.analyze_cellperforms 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_artifactsto the caller; theartifact_write_faileddiagnostic code is reserved for a future non-raising mode. - Artifacts written by
write_artifactsare described byArtifactRecordentries (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,
}