Matlab Extract Battery Features
MathWorksOfficialCreated by MathWorks
- Skill
- Tool integration
- Science and research
- BSD-2-Clause
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured interface for the MATLAB Predictive Maintenance Toolbox to extract diagnostic features from battery test data. It supports multi-cycle cycling tests, HPPC/pulse tests, EIS impedance sweeps, and in-field operational segments. The skill automates the segmentation and feature extraction process, outputting a structured feature table ready for downstream analysis like SOH or RUL estimation. It is designed to handle various data shapes and provides clear reporting on column mappings and data quality, ensuring consistent and reproducible feature engineering.
What it does
- Segments raw battery cycling data into charge, discharge, and rest phases.
- Extracts capacity, energy, and cycle-level statistics from cycling tests.
- Computes internal resistance and polarization features from pulse/HPPC tests.
- Analyzes impedance spectroscopy (EIS) data to extract resistance and arc features.
- Generates differential curves (IC/DV/DT) for aging mechanism analysis.
- Processes single-segment in-field data for operational condition monitoring.
When to use it
- When you need to extract features from raw voltage, current, and temperature traces.
- For analyzing battery degradation, SOH, or RUL using extracted feature tables.
- When performing cell screening or comparing performance across multiple battery cells.
- For extracting physical aging indicators like IC peak shifts or impedance growth.
- When you have raw data from bench cyclers, BMS logs, or drive/charge events.
Advantages
- Uses performance-optimized, native MATLAB functions for reliable feature extraction.
- Automates complex segmentation and peak detection tasks.
- Provides a standardized workflow for diverse battery test types.
- Ensures consistent data handling through explicit mapping and validation steps.
- Reduces manual coding effort for common electrochemical analysis tasks.
Usage examples
- “Extract features from my battery cycling data to help with SOH modeling.”
- The agent identifies the cycling data structure, maps the voltage/current/time columns, runs the parser and extractor, and presents a feature table with cycle-level metrics.
- “What is the charge-transfer resistance from this EIS file?”
- The agent processes the frequency, Zreal, and Zimag data using the EIS feature extraction function and reports the extracted resistance values.
- “Calculate the internal resistance from these HPPC pulse tests.”
- The agent identifies the pulse segments, applies the pulse feature extraction function, and returns a table containing resistance features for each pulse.
- “Can you extract features from this partial discharge segment logged by my BMS?”
- The agent uses the measurement features function to compute statistical and cumulative features from the single discharge segment.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/matlab/matlab-extract-battery-features/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip matlab-extract-battery-features.zip -d .agents/skills
Documentation by MathWorks
Battery Feature Extraction
Router for MATLAB's Predictive Maintenance Toolbox battery feature extraction functions. Detailed workflows live in references/*.md — load only the file that matches the inspected data type.
When to Use
Trigger on the domain questions asked about battery voltage / current / temperature measurements — from bench cycler / HPPC / EIS tests or from in-field / in-use operational data logged by a BMS or captured during a drive or charge event. The skill produces the feature table that feeds each question's downstream analysis. Concretely:
Degradation, SOH, RUL, capacity/energy fade. "How much has this cell degraded over 1000 cycles?" · "Track SOH." · "Estimate remaining useful life." · "Which cell is fading fastest?"
Aging mechanism from differential curves. "How are the dQ/dV peaks shifting?" · "Loss of lithium inventory vs. active-material loss?" · "Plot IC curves at cycles 1, 250, 500, 1000." · "What's the DV signature of aging?"
Internal resistance from HPPC / pulse tests. "What's R0 at 50% SOC?" · "How has DCR grown?" · "Get polarization resistance and recovery rate per pulse."
EIS / impedance analysis. "Fit the Nyquist plot." · "What's the charge-transfer resistance?" · "How has R0 grown from EIS?" · "Extract arc widths and Warburg tail."
Segmentation of cycling data. "Split this into charge / discharge / rest." · "Separate CC and CV portions of each cycle." · "Give me only the discharge segments." (With or without downstream feature extraction.)
In-field / operational condition monitoring. Features from a single measured segment that has no multi-cycle test structure — a partial charge or discharge logged by a BMS, one CC segment, or a drive/charge event. "Extract features from this field charge segment." · "Summarize this discharge from the vehicle log." · "What condition-monitoring features can I get from this partial charge?" (Served by
batteryMeasurementFeatures, which works stats-only when no cycle/time structure is present.)Fast-charging stress / plating indicators. "Is CV duration growing?" · "Any thermal signature of plating (DT)?" · "Recovery slowdown at high SOC?"
Cell screening / cell-to-cell comparison. "Compare these 10 cells." · "Rank by fade rate." · "Which cells are outliers?"
Feature engineering for ML. "Give me a feature table for SOH modeling." · "Which features best correlate with capacity fade?"
Batch processing of multiple test files through any of the above.
When NOT to Use
First-look / data classification / orientation questions on battery data: "What kind of test is this?" · "Describe this data." · "Tell me about this file." · "Analyze cycling.csv and tell me what it contains." These are inspection; the skill fires later when the user asks one of the analyses above.
Role-framing without a specific analysis in the current turn. A system prompt setting a "battery reliability agent" persona, a filename containing "battery" / "cycling" / "SOH", or a bare "analyze this" verb is not enough — the current user turn must name one of the specific analyses.
Data import or vendor-format conversion (Arbin
.res, Neware.nda, Maccor, BioLogic → MATLAB table).Plotting raw voltage / current / temperature vs time or capacity without first segmenting or computing an IC/DV/DT curve — that's a plain plotting task.
Cleaning, resampling, interpolating, or filtering time-series that does not feed into segmentation, differential curves, or feature extraction.
SOH / RUL model training or prediction — or any ML training — on an already-extracted feature table. This skill is the upstream feature step; modeling and prediction are downstream.
Battery simulation, equivalent-circuit modeling, or Simulink battery plant models.
Non-electrochemical data (fuel cells, supercapacitors, generic sensor streams).
This skill covers the 7 PMT battery feature extraction functions. These functions work natively with MATLAB tables and vectors, handle segmentation and peak detection internally, and are performance-optimized. Prefer these PMT functions over manual feature computation (e.g., hand-coded cumtrapz loops, manual peak finding). Override only if the user explicitly requests otherwise.
API Overview
The Predictive Maintenance Toolbox provides 7 public functions for battery feature extraction, organized by the kind of measurement they consume:
Battery Data
│
┌────────────────┬─────────────┴────────┬────────────────────┐
│ │ │ │
Multi-cycle Single measured Pulse tests EIS tests
cycling test segment (HPPC / GITT) (impedance)
│ (bench or in-field) │ │
▼ │ ▼ ▼
batteryTest- │ batteryPulseFeatures batteryEISFeatures
DataParser │ │ │
+ │ ▼ ▼
batteryTest- │ Pulse feature EIS feature
FeatureExtractor │ table table
│ │
▼ ├── batteryMeasurementFeatures → Measurement feature table
Full-pipeline │
feature table ├── batteryDifferentialCurves → IC / DV / DT curves
│
└── batteryDifferentialCurveFeatures → Curve peak feature tableThe single measured segment branch is the in-field / operational path: its functions take raw V / I / T / t from one charge or discharge segment and need no multi-cycle test structure, so they serve BMS-logged or drive/charge-event data as well as a segment pulled from a bench test.
| Function | Input | Output | Use When | Available From |
|---|---|---|---|---|
batteryTestDataParser | Raw cycling table | Parser object (segmented data) | Multi-cycle data needing segmentation | R2024b |
batteryTestFeatureExtractor | Options | Extractor object | Full pipeline: parser → all cycling features | R2024b |
batteryMeasurementFeatures | V, I, T, t vectors | Statistical + cumulative feature table | Any single-phase segment (partial or full charge/discharge) | R2026a |
batteryDifferentialCurves | V, I, T, t vectors | dQ/dV, dV/dQ, dT/dV tables | Constant-current segment only (CC charge or CC discharge) | R2026a |
batteryDifferentialCurveFeatures | Curve + x-axis | Peak feature table | Extracting features from differential curves | R2026a |
batteryPulseFeatures | V, I, t (or table) | Pulse feature table + pulse data | HPPC/pulse test resistance features | R2026b |
batteryEISFeatures | Zr, Zi, F vectors | EIS feature table | Impedance spectroscopy analysis | R2026b |
Load the Right Reference Before Proceeding
Step 1 — inspect the data shape. Load the file directly from the exact path the user gave, resolved against the current working folder. Do not recursively search temp, home, or system directories to locate it — a recursive dir(..., '**', ...) over such a tree can stall for over a minute. Then read the file and answer two questions:
Granularity. Is each row a per-sample raw record (many rows per cycle: voltage / current sampled at 1 Hz or faster), or is each row a per-cycle / per-cell summary (one row per cycle per cell, with columns like
Capacity,SOH,CV_hold_duration)?Multi-cell. Is there a
CellID/Cell/SerialNumbercolumn identifying multiple cells in the same file?
Step 2 — route by shape:
| Data shape inspected | Route |
|---|---|
| Raw per-sample multi-cycle cycling table with cycle and step index columns (one cell, or multi-cell — see note below) | references/cycling-pipeline.md |
| Raw per-sample cycling table with step index but no cycle index — either multiple cycles the parser can synthesize a cycle index for, or a single full charge-discharge cycle (see reference for the multi- vs single-cycle rule) | references/cycling-pipeline.md |
| Raw per-sample single-phase cycling segment (partial charge, one discharge, CC segment) with no cycle index and no full charge-discharge boundary | references/individual-cycling-functions.md |
| Raw short current pulses separated by rest periods (HPPC / GITT) | references/pulse-features.md |
Raw impedance sweep with Frequency, Zreal, Zimag | references/eis-features.md |
Already-aggregated per-cell / per-cycle summary — one row per (cell, cycle) with pre-computed Capacity, SOH, CV_hold_duration, RPT_capacity, temperature summaries, etc. (no per-sample V/I/t traces) | No reference loads. See § Data Is Already Aggregated below. |
Additionally, when tuning Tolerance, WindowSize, IC/DV/DT smoothing, or diagnosing "not CC data" errors | Also load references/parameter-tuning.md |
Multi-battery-cell data. When the data covers more than one battery cell, see the loaded reference's § Preprocessing: Data Layout for detection and combine/iterate rules, and § Postprocessing: Feature Table Organization for the featuresPerCell{k} output convention.
Data Is Already Aggregated — Skill Does Not Apply
If each row is already a per-cycle or per-cell summary (e.g., a CSV with columns CellID, Cycle, Capacity_Ah, SOH, CV_hold_s, Temp_max_C), the PMT battery* functions covered by this skill have nothing to add — they compute those very features from raw traces, and you already have them. Tell the user this, then handle the cell-ranking / degradation-comparison task with plain table operations:
Rank cells by fade rate: fit a slope of
CapacityvsCycleperCellID, sort ascending.Compare mechanisms by trending
CV_hold_s, temperature statistics, or resistance summaries per cell.Visualize with
tiledlayout: capacity-vs-cycle families, SOH trajectories, CV-hold growth.
This is standard MATLAB table wrangling — no PMT function is required. If a downstream skill fits better (e.g. matlab-analyze-data for the ranking workflow), route there instead of loading a reference from this skill.
Application × data shape. When the user names an application (SOH, RUL, Fast Charging, Cell Screening) that could be served by multiple data types, still route by inspected shape first — application alone doesn't pick a workflow. Then read the matching reference's "Feature Selection for Common Applications" section for the recipe.
Workflow Skeleton
Every reference in this skill follows the same phase template. Load the matching reference for the phase-by-phase details; the skeleton is:
| # | Phase | What it does |
|---|---|---|
| 1 | Identify inputs | Map data columns to the function's parameters. Workflow-specific vocabulary table lives at the top of each reference. General rules (vocabulary-first, datetime > numeric, unit conversion) below. |
| 2 | Data layout | Detect single-file vs multi-file, single-cell vs multi-cell, single-sweep vs multi-sweep; combine or split accordingly. |
| 3 | Call functions | Issue the primary computation call(s). |
| 4 | Verify results | Cross-check outputs against independent evidence. Cycling uses verifySegmentation + checkCyclingProtocol; individual-cycling uses verifySingleSegment. Pulse and EIS skip this phase — no verifier helper exists. |
| 5 | Tune parameters | Only if Phase 4 flagged an issue (or the caller reports symptoms of a wrong setting). Sweep tuning parameters and re-call. |
| 6 | Postprocess / organize | Attach tag columns (CellID, ExperimentIndex), keep per-cell tables in a cell array, etc. |
| 7 | Report + next steps | Present the feature table with the key-decisions summary and the Next Steps offer (§ Stop After Extraction rule below). |
Reference-to-phase map:
| Reference | Phases | Verify helper | Tuning reference |
|---|---|---|---|
cycling-pipeline.md (+ cycling/*.md on demand) | 1-7 | verifySegmentation or verifyAgainstSchedule, + checkCyclingProtocol | cycling/tolerance-tuning.md or cycling/schedule-verification.md; parameter-tuning.md (IC/DV/DT smoothing) |
individual-cycling-functions.md | 1-7 | verifySingleSegment | parameter-tuning.md (IC/DV/DT smoothing, NoiseTolerance) |
pulse-features.md | 1-3, 5-7 (no Verify) | — | inline (ValidPulseDurationRange, MinCurrentChange) |
eis-features.md | 1-3, 5-7 (no Verify) | — | inline (NumArcs, multi-sweep splitting) |
Interaction Rules
Cross-cutting rules that apply to every workflow — do not skip regardless of which reference you loaded.
Interaction Model: Stop or Proceed
Do not stop and ask before every extraction. Most decisions have a deterministic rule — apply it, extract, and report what you did so the user can correct it. Only stop when a decision is genuinely ambiguous and guessing it wrong would fail silently (plausible-but-wrong features). The rule applies to every workflow — cycling pipeline, individual cycling functions, pulse, and EIS.
STOP and ask the user first — but only when one of these is true:
General (all workflows):
Two or more candidate columns of the same type for one variable (e.g. two numeric elapsed-time columns, two current-like columns) — no rule can choose between them.
Raw unnamed numeric matrix — column roles must be inferred, not read from names.
A required variable cannot be mapped — a needed column is missing or its role is unclear.
Cycling pipeline only:
checkCyclingProtocolfinds neither phase consistent — no phase can be recommended.Anomalous cycles detected — never silently exclude them.
Otherwise PROCEED, then REPORT. Apply the deterministic rules — datetime/duration beats numeric time; set DT=true iff temperature is present; set CC=true whenever IC/DV/DT is requested; for cycling, use checkCyclingProtocol's RecommendedPhase — write and run the extraction, then present in a single after-extraction turn:
the column mappings used (noting any alternatives considered and why the chosen one won),
any workflow-specific decisions — cycling:
CyclingPhaseand its consistency reason; pulse: pulse-detection thresholds if adjusted; EIS: frequency range or sweep selected if the file had multiple,any data-quality findings (time-gap indices, invalid-point count, negative Zreal, missing rests, etc.) as a report line — not a gate,
any goal-vs-data tension, if present (see the loaded reference — e.g. cycling-pipeline.md Phase 4),
the resulting feature table, followed by the "what next?" offer.
Close the report with an explicit undo invitation, e.g. "If any mapping or the phase choice looks wrong, tell me and I'll re-run." This preserves the safety net without a blocking question.
Stop After Extraction — Offer Next Steps
Once the feature table is extracted and displayed, stop and present available next steps to the user. Do not autonomously proceed to visualization, feature ranking, or script saving. The stopping turn must have three parts:
Feature-table summary — M features across N rows / cycles / pulses / sweeps.
Key decisions + undo invitation — the autonomous choices you made, each stated so the user can override it, because these were decisions the user never explicitly approved:
Column mappings used (with any alternatives considered) and any unit conversion applied.
The workflow-specific settings the loaded reference tells you to report (cycling: CyclingPhase + consistency reason + feature categories; pulse: pulse-detection settings; EIS: frequency range / sweep selection; individual cycling: segment identified). The full workflow-specific template lives in each reference's § Report to the user after extraction.
Undo invitation, pointed at those choices — e.g. "I chose the time column, CyclingPhase, and feature categories automatically — tell me and I'll re-run with different ones." Do not use a generic "if anything looks wrong, let me know."
When Phase 4 flagged a concern, this is required and must name the finding — e.g.
verifySegmentationreturnedTuningRecommended,checkCyclingProtocolreported anomalous cycles, or pulse/EIS detection returned empty/failed. State the flag and the concrete re-run it implies ("3 cycles segmented inconsistently — I can re-run with a tighter Tolerance, or exclude them").
Next-steps offer — the four standing options:
Visualize: plots appropriate to the workflow (see the loaded reference's Phase 7 Visualize hints; cycling loads
references/cycling/postprocess.md)Rank features: identify most discriminative features for your analysis goal
Export: save the feature table to a .mat or .csv file
Refine: adjust feature categories, phase, or smoothing parameters
Only execute a next step when the user explicitly requests it. This applies even if the original prompt contains phrasing like "show me" or "plot" — extract first, present the table with the key decisions, then offer to visualize.
Column Mapping: General Rules
Workflow-specific column vocabularies live in the loaded reference's Phase 1 (Identify inputs). The rules below apply regardless of which vocabulary you're consulting.
Vocabulary first — try common vendor names before declaring a column missing. The 4 mainstream cyclers (Arbin, Neware, Maccor, BioLogic) and typical EIS/potentiostat exports each use different conventions. Match case-insensitively; strip units suffixes (_V, _A, /mA, /V, /Hz, /Ohm); a match on any name in the reference's Phase 1 vocabulary is sufficient — no need to stop-and-ask.
Unit conversion when the column name carries units. If the current column is I/mA (milliamps), convert to Amps before passing to the function (data.I_A = data.("I/mA") / 1000); same for mV → V. Report the conversion in the after-extraction summary.
Prefer datetime/duration over numeric elapsed time. If one candidate is
datetime/durationtype and another is numeric seconds, the datetime column wins — this is a rule, not a judgment call. Apply it and report it rather than asking. Note the alternative you considered in the after-extraction report (e.g., "I found bothTest_Time(numeric) andDateTime(datetime type) — usedDateTimeas it is a proper datetime column").Stop only when the rule can't decide. If two candidates share the same type (e.g. two numeric elapsed-time columns) so no rule can pick, ask. Likewise if a required variable has no clear column.
Report the mappings after extracting — with any workflow-specific decisions, data-quality findings, and an undo invitation. See § Stop After Extraction above and the loaded reference's Phase 7 for the workflow-specific template.
Unnamed Data (Matrices Without Column Names)
When data is a raw numeric matrix (no column names), infer column roles from value characteristics, then confirm with the user before proceeding:
EIS data: Frequency spans orders of magnitude (all positive), Zreal is non-negative with narrow range, Zimag can be negative with similar magnitude to Zreal
Pulse data: Current has distinct pulse/rest pattern, voltage shows step responses, time is monotonically increasing
Otherwise: If the data does not clearly match EIS or pulse patterns, ask the user what type of data it is rather than guessing
Present the inferred mapping and ask the user to confirm. Example:
Your data is a 641×3 matrix with no column names. Based on value ranges:
Column 1: spans 0.05 to 10000 (likely Frequency)
Column 2: all positive, range 13–22 (likely Zreal)
Column 3: range -14 to 6, mixed sign (likely Zimag)
Does this mapping look correct?
Cross-Function Pitfalls
Only two pitfalls are truly cross-cutting. Workflow-specific pitfalls (time format, phase mixing, DT requirements, feature-count overfitting, CC data requirements, EIS sign conventions, pulse-detection thresholds, etc.) live in the loaded reference's own § Pitfalls section — consult it before extracting.
Manual loops when a PMT function exists. If the user asks for capacity fade, IC peaks, pulse R0, EIS Rct, etc., use the PMT function — do not hand-code
cumtrapz,findpeaks, or manual segmentation loops.Wrong units. Current in Amps (not mA), voltage in Volts. Convert unit-suffixed columns before passing them to any PMT function.
Copyright 2026 The MathWorks, Inc.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.1 KiB
- LICENSELicence file added by AutoGineer1.4 KiB
- SKILL.md21.6 KiB
- manifest.yaml469 B
- references/cycling-pipeline.md21.1 KiB
- references/cycling/feature-selection.md2.6 KiB
- references/cycling/multi-cell.md5.1 KiB
- references/cycling/postprocess.md2.4 KiB
- references/cycling/schedule-verification.md4.2 KiB
- references/cycling/tolerance-tuning.md2.4 KiB
- references/eis-features.md10.8 KiB
- references/individual-cycling-functions.md10.1 KiB
- references/parameter-tuning.md14.2 KiB
- references/pulse-features.md12.6 KiB
- scripts/checkCyclingProtocol.m9.2 KiB
- scripts/sweepScheduleAndReport.m3.7 KiB
- scripts/verifyAgainstSchedule.m8.5 KiB
- scripts/verifySegmentation.m6.3 KiB
- scripts/verifySingleSegment.m3.0 KiB
scripts/verifyAgainstSchedule.m
function report = verifyAgainstSchedule(parser, schedule)
%verifyAgainstSchedule Verify segmentation against a user-provided schedule.
% report = verifyAgainstSchedule(parser, schedule) segments the parser
% data and checks each (Cycle, Step) group against the declared schedule.
% Complements verifySegmentation for the case where the tester schedule
% is known and can be asserted directly.
%
% Inputs:
% parser - configured batteryTestDataParser
% schedule - struct array; one entry per Step_Index with fields:
% .Step - integer Step_Index
% .Type - "CC" | "CV" | "Rest" | "CCCV"
% .MinCoverage - (CC/CV/Rest) fraction of samples that must
% match Type. Default: 0.85 for CC/CV, 0.95 for Rest
% .MinCC - (CCCV) minimum CC-labeled fraction. Default 0.05
% .MinCV - (CCCV) minimum CV-labeled fraction. Default 0.05
% .TransitionVoltage - (CCCV, optional) declared CC→CV transition V
% .TransitionTol - (CCCV) tolerance on TransitionVoltage. Default 0.05
% .MaxUndefinedFrac - Max Undefined fraction allowed. Default 0.15
% .MinSamples - Minimum sample count; below this the step is
% marked as data-collection failure. Default 5
%
% Output:
% report.PerGroup - table: (Cycle, Step, Expected, Coverage, TransitionV,
% Pass, Reason, N)
% report.PerCycle - table: (Cycle, AllStepsPass, NumFail, FirstFailReason)
% report.PassCount - number of cycles that passed all steps
% report.NumCycles - total cycles
% report.AgingSignals - table: (Cycle, plus per-CCCV-step: TransitionV,
% CCFrac, CVFrac, TaperDuration)
arguments
% No class constraint on parser: the public batteryTestDataParser factory
% returns an object whose only class name is internal
% (predmaint.internal.battery.BatteryTestDataParser); naming it in shipped
% code is brittle across PMT releases.
parser (1,1)
schedule (1,:) struct
end
cycleVar = parser.CycleIndexVariable;
stepVar = parser.StepIndexVariable;
voltVar = parser.VoltageVariable;
timeVar = parser.TimeVariable;
seg = segmentData(parser);
cycles = unique(seg.(cycleVar));
perGroup = {};
agingRows = {};
for ci = 1:numel(cycles)
cyc = cycles(ci);
ageRow = struct('Cycle', cyc);
for k = 1:numel(schedule)
entry = schedule(k);
entry = fillDefaults(entry);
mask = seg.(cycleVar) == cyc & seg.(stepVar) == entry.Step;
n = sum(mask);
if n < entry.MinSamples
perGroup(end+1,:) = {cyc, entry.Step, entry.Type, 0, NaN, false, ...
sprintf("data-collection: only %d samples (< %d)", n, entry.MinSamples), n}; %#ok<AGROW>
continue
end
rows = seg(mask, :);
modes = string(rows.CyclingModes);
V = rows.(voltVar);
t = rows.(timeVar);
switch entry.Type
case {"CC","CV","Rest"}
cov = mean(modes == entry.Type);
pass = cov >= entry.MinCoverage;
reason = "OK";
if ~pass
dominant = modeDominant(modes);
reason = sprintf("expected %s coverage >= %.2f, got %.2f (dominant: %s)", ...
entry.Type, entry.MinCoverage, cov, dominant);
end
perGroup(end+1,:) = {cyc, entry.Step, entry.Type, cov, NaN, pass, reason, n}; %#ok<AGROW>
case "CCCV"
nCC = sum(modes == "CC");
nCV = sum(modes == "CV");
nRest = sum(modes == "Rest");
nUnd = sum(modes == "Undefined");
total = n;
ccFrac = nCC / total;
cvFrac = nCV / total;
undFrac = nUnd / total;
hasCC = ccFrac >= entry.MinCC;
hasCV = cvFrac >= entry.MinCV;
noRest = nRest == 0;
undOK = undFrac <= entry.MaxUndefinedFrac;
% Order: median t of CC < median t of CV
if hasCC && hasCV
tSec = elapsed(t);
tCCmed = median(tSec(modes == "CC"));
tCVmed = median(tSec(modes == "CV"));
orderOK = tCCmed < tCVmed;
% Transition voltage = voltage at last CC sample
lastCC = find(modes == "CC", 1, "last");
vTransition = V(lastCC);
else
orderOK = false;
vTransition = NaN;
end
if isfield(entry, 'TransitionVoltage') && ~isnan(entry.TransitionVoltage) && ~isnan(vTransition)
transOK = abs(vTransition - entry.TransitionVoltage) <= entry.TransitionTol;
else
transOK = true;
end
pass = hasCC && hasCV && noRest && undOK && orderOK && transOK;
reason = "OK";
if ~pass
reasons = strings(0);
if ~hasCC, reasons(end+1) = sprintf("CC frac %.2f < %.2f", ccFrac, entry.MinCC); end
if ~hasCV, reasons(end+1) = sprintf("CV frac %.2f < %.2f", cvFrac, entry.MinCV); end
if ~noRest, reasons(end+1) = "Rest samples in composite step"; end
if ~undOK, reasons(end+1) = sprintf("Undefined frac %.2f > %.2f", undFrac, entry.MaxUndefinedFrac); end
if hasCC && hasCV && ~orderOK, reasons(end+1) = "CV precedes CC"; end
if ~transOK, reasons(end+1) = sprintf("V_transition %.3f off target %.3f", vTransition, entry.TransitionVoltage); end
reason = strjoin(reasons, "; ");
end
perGroup(end+1,:) = {cyc, entry.Step, entry.Type, ccFrac, vTransition, pass, reason, n}; %#ok<AGROW>
% Aging signals from CCCV steps
fld = sprintf("Step%d_TransV", entry.Step);
ageRow.(fld) = vTransition;
fld = sprintf("Step%d_CCFrac", entry.Step);
ageRow.(fld) = ccFrac;
fld = sprintf("Step%d_CVFrac", entry.Step);
ageRow.(fld) = cvFrac;
fld = sprintf("Step%d_TaperN", entry.Step);
ageRow.(fld) = nCV;
end
end
agingRows(end+1,:) = {ageRow}; %#ok<AGROW>
end
perGroupT = cell2table(perGroup, 'VariableNames', ...
{'Cycle','Step','Expected','Coverage','TransitionV','Pass','Reason','N'});
% Per-cycle roll-up
cycUnique = unique(perGroupT.Cycle);
perCycle = table('Size',[numel(cycUnique),4], ...
'VariableTypes',{'double','logical','double','string'}, ...
'VariableNames',{'Cycle','AllStepsPass','NumFail','FirstFailReason'});
for i=1:numel(cycUnique)
cyc = cycUnique(i);
sub = perGroupT(perGroupT.Cycle==cyc, :);
perCycle.Cycle(i) = cyc;
perCycle.NumFail(i) = sum(~sub.Pass);
perCycle.AllStepsPass(i) = all(sub.Pass);
if perCycle.AllStepsPass(i)
perCycle.FirstFailReason(i) = "";
else
firstFail = find(~sub.Pass, 1);
perCycle.FirstFailReason(i) = sprintf("step %d: %s", sub.Step(firstFail), sub.Reason(firstFail));
end
end
report.PerGroup = perGroupT;
report.PerCycle = perCycle;
report.PassCount = sum(perCycle.AllStepsPass);
report.NumCycles = height(perCycle);
report.AgingSignals = agingRows;
end
function entry = fillDefaults(entry)
if ~isfield(entry,'MinCoverage') || isempty(entry.MinCoverage)
if entry.Type == "Rest"
entry.MinCoverage = 0.95;
else
entry.MinCoverage = 0.85;
end
end
if ~isfield(entry,'MinCC') || isempty(entry.MinCC), entry.MinCC = 0.05; end
if ~isfield(entry,'MinCV') || isempty(entry.MinCV), entry.MinCV = 0.05; end
if ~isfield(entry,'TransitionVoltage') || isempty(entry.TransitionVoltage), entry.TransitionVoltage = NaN; end
if ~isfield(entry,'TransitionTol') || isempty(entry.TransitionTol), entry.TransitionTol = 0.05; end
if ~isfield(entry,'MaxUndefinedFrac') || isempty(entry.MaxUndefinedFrac), entry.MaxUndefinedFrac = 0.15; end
if ~isfield(entry,'MinSamples') || isempty(entry.MinSamples), entry.MinSamples = 5; end
end
function d = modeDominant(modes)
[u, ~, ic] = unique(modes);
counts = accumarray(ic, 1);
[~, idx] = max(counts);
d = u(idx);
end
function s = elapsed(t)
if isdatetime(t) || isduration(t)
s = seconds(t - t(1));
else
s = t - t(1);
end
end
% Copyright 2026 The MathWorks, Inc.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- MathWorks
- Publisher
- MathWorks (Official)
- Source
- matlab/matlab-agentic-toolkit · skills-catalog/control-systems/matlab-extract-battery-features · commit 2a7f215
- Licence
- BSD 2-Clause License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by MathWorks.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-extract-battery-features.zip