cellpycore 0.2.3__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cellpycore-0.2.3/.issueflows/02-partly-solved-issues → cellpycore-0.2.5/.issueflows/03-solved-issues}/issue100_status.md +1 -1
- cellpycore-0.2.5/.issueflows/03-solved-issues/issue138_original.md +85 -0
- cellpycore-0.2.5/.issueflows/03-solved-issues/issue138_plan.md +18 -0
- cellpycore-0.2.5/.issueflows/03-solved-issues/issue138_status.md +26 -0
- cellpycore-0.2.5/.issueflows/03-solved-issues/issue142_original.md +48 -0
- cellpycore-0.2.5/.issueflows/03-solved-issues/issue142_plan.md +86 -0
- cellpycore-0.2.5/.issueflows/03-solved-issues/issue142_status.md +20 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/cellpy-core-integration-roadmap.md +14 -10
- cellpycore-0.2.5/.vscode/settings.json +14 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/HISTORY.md +20 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/PKG-INFO +2 -2
- {cellpycore-0.2.3 → cellpycore-0.2.5}/pyproject.toml +1 -1
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/cell_core.py +46 -18
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/config.py +4 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/headers.py +2 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/mapping.py +5 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/summarizers.py +168 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/units/__init__.py +2 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/units/converters.py +30 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/uv.lock +1 -1
- cellpycore-0.2.3/.vscode/settings.json +0 -7
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.aliases +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/rules/cellpy-core-migration.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/rules/graphify.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/rules/issueflow-rules.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/rules/kiss.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/rules/this-project.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/caveman/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/grill-me/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-archive/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-cleanup/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-close/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-comments/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-cycle/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-epic/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-fix/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-graphify/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-history-update/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-init/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-pause/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-pick/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-plan/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-start/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-status/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-version-bump/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.cursor/skills/iflow-yolo/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.gitattributes +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.gitignore +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/00-tools/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/00-tools/README.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/01-current-issues/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/02-partly-solved-issues/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/.gitkeep +0 -0
- {cellpycore-0.2.3/.issueflows/02-partly-solved-issues → cellpycore-0.2.5/.issueflows/03-solved-issues}/issue100_original.md +0 -0
- {cellpycore-0.2.3/.issueflows/02-partly-solved-issues → cellpycore-0.2.5/.issueflows/03-solved-issues}/issue100_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue10_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue10_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue10_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue110_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue110_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue110_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue112_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue112_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue112_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue114_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue114_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue114_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue115_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue115_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue115_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue116_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue116_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue116_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue117_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue117_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue117_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue118_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue118_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue118_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue121_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue121_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue121_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue12_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue12_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue12_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue136_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue136_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue136_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue139_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue139_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue139_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue13_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue13_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue13_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue21_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue21_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue21_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue22_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue22_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue22_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue23_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue23_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue23_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue24_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue24_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue29_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue29_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue29_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue30_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue30_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue30_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue32_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue32_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue32_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue34_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue34_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue36_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue36_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue39_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue39_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue39_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue40_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue40_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue40_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue41_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue41_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue41_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue42_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue42_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue42_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue43_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue43_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue43_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue44_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue44_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue45_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue45_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue45_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue50_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue50_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue50_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue54_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue54_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue54_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue55_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue55_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue55_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue56_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue56_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue56_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue62_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue62_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue62_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue64_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue64_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue64_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue66_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue66_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue66_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue67_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue67_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue67_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue68_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue68_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue68_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue69_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue69_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue69_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue70_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue70_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue70_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue72_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue72_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue77_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue77_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue77_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue82_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue82_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue84_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue84_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue84_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue86_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue86_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue86_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue91_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue91_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue91_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue95_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue95_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue95_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue96_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue96_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue96_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue97_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue97_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue97_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue98_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue98_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue98_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue99_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue99_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/03-solved-issues/issue99_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/bdf-io-placement.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/cellpy-core-integration-into-cellpy.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/cellpy-core-migration.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/code-review-2026-07.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/column-headers-review.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/legacy-bridge-test-id.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/legacy-raw-energy-mapping.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/metadata-scaffolding.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/nom-cap-naming.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/rawcols-dtype-map.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/release-procedure.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/selector-dead-code-deferral.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/step-c-rate-split.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/step-table-polars-migration.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/summary-extractors.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/test-data-and-fixtures.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/test-metadata-and-merging.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/this-project.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/04-designs-and-guides/zensical-docs.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/05-epics/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.issueflows/config.toml +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.pre-commit-config.yaml +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.python-version +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/.readthedocs.yaml +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/AGENTS.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/LICENSE +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/README.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/ROADMAP.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/SCRATCHPAD.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/graphify-out/.graphify_labels.json +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/graphify-out/.graphify_root +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/graphify-out/GRAPH_REPORT.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/graphify-out/graph.html +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/graphify-out/graph.json +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/graphify-out/manifest.json +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/curves.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/exceptions.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/extractors.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/limits.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/meta.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/meta_mapping.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/mock_core.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/legacy/selectors.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/merge.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/metadata/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/metadata/io.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/metadata/models.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/py.typed +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/settings_base.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/testing/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/testing/mock_data.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/timestamps.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/src/cellpycore/units/spec.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.5}/zensical.toml +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Issue #138: Add a built-in utility for calculating Equivalent Full Cycles (EFC) from time-series data.
|
|
2
|
+
|
|
3
|
+
Author: jepegit
|
|
4
|
+
URL: https://github.com/cellpy/cellpy-core/issues/138
|
|
5
|
+
|
|
6
|
+
Cellpy currently provides cycle-based summaries and performance metrics for cycling experiments. However, many battery datasets particularly operational, field, and BMS datasets do not contain well-defined charge/discharge cycles. Instead, they consist of continuous time-series measurements of voltage, current, temperature, and time.
|
|
7
|
+
|
|
8
|
+
For these datasets, researchers and engineers often use Equivalent Full Cycles (EFC) as a throughput-based ageing metric. Currently, users must calculate EFC outside of cellpy and maintain custom implementations, leading to duplicated effort and inconsistent definitions across projects.
|
|
9
|
+
|
|
10
|
+
As battery research increasingly incorporates operational and real-world datasets, the lack of a built-in EFC calculation makes it more difficult to compare ageing across datasets and integrate operational data into existing cellpy workflows.
|
|
11
|
+
|
|
12
|
+
## Proposed Solution
|
|
13
|
+
|
|
14
|
+
Add a built-in utility for calculating **Equivalent Full Cycles (EFC)** from time-series data.
|
|
15
|
+
|
|
16
|
+
Example usage:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
cell.calculate_efc(nominal_capacity=300.0)
|
|
20
|
+
````
|
|
21
|
+
|
|
22
|
+
or
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
cell.make_summary(add_efc=True)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The implementation could:
|
|
29
|
+
|
|
30
|
+
* Calculate cumulative charge throughput from current and time:
|
|
31
|
+
|
|
32
|
+
$$
|
|
33
|
+
Q_{throughput} = \sum |I| \Delta t
|
|
34
|
+
$$
|
|
35
|
+
|
|
36
|
+
* Compute EFC as:
|
|
37
|
+
|
|
38
|
+
$$
|
|
39
|
+
EFC = \frac{Q_{throughput}}{2Q_{nom}}
|
|
40
|
+
$$
|
|
41
|
+
|
|
42
|
+
where $$Q_{nom}$$ is the nominal capacity.
|
|
43
|
+
|
|
44
|
+
* Optionally add:
|
|
45
|
+
* Instantaneous cumulative EFC to the raw dataframe.
|
|
46
|
+
* EFC columns to the summary dataframe.
|
|
47
|
+
* Energy-throughput-based equivalents (optional future extension).
|
|
48
|
+
* Support for SoC-based EFC calculations when SoC data is available.
|
|
49
|
+
|
|
50
|
+
Suggested outputs:
|
|
51
|
+
|
|
52
|
+
* `cumulative_ah_throughput`
|
|
53
|
+
* `cumulative_energy_throughput`
|
|
54
|
+
* `equivalent_full_cycles`
|
|
55
|
+
|
|
56
|
+
This would provide a standardized and reproducible EFC implementation directly within the cellpy ecosystem.
|
|
57
|
+
|
|
58
|
+
***
|
|
59
|
+
|
|
60
|
+
**Describe alternatives you've considered**
|
|
61
|
+
|
|
62
|
+
Current alternatives include:
|
|
63
|
+
|
|
64
|
+
* Implementing custom EFC calculations in downstream analysis scripts.
|
|
65
|
+
* Adding project-specific preprocessing steps prior to importing data into cellpy.
|
|
66
|
+
|
|
67
|
+
While these approaches work, they lead to code duplication and make it harder to compare results across projects and users. A native cellpy implementation would provide a common reference method and improve reproducibility.
|
|
68
|
+
|
|
69
|
+
***
|
|
70
|
+
|
|
71
|
+
**Additional context**
|
|
72
|
+
|
|
73
|
+
This feature would be particularly useful for:
|
|
74
|
+
|
|
75
|
+
* Operational battery datasets.
|
|
76
|
+
* BMS and field-monitoring data.
|
|
77
|
+
* Fleet and stationary storage applications.
|
|
78
|
+
* Machine learning pipelines where degradation progress is tracked using throughput-based metrics.
|
|
79
|
+
* Continuous time-series datasets that do not contain explicit cycle boundaries.
|
|
80
|
+
|
|
81
|
+
As cellpy expands beyond traditional cycling experiments and is increasingly used for large-scale battery data analysis, a standardized EFC utility would help bridge the gap between laboratory cycling data and real-world operational datasets.
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
```
|
|
85
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Issue #138 — Plan: Equivalent Full Cycles (EFC) utility
|
|
2
|
+
|
|
3
|
+
Confirmed approach (user-approved 2026-07-18):
|
|
4
|
+
|
|
5
|
+
- **Both surfaces**: raw-level current integration + summary-level per-cycle columns.
|
|
6
|
+
- **Summary basis**: existing `test_cumulated_charge_capacity + test_cumulated_discharge_capacity` (unit-consistent with nom_cap, no conversion factor).
|
|
7
|
+
- **Skipped**: energy throughput, SoC-based EFC (issue marks optional/future); `make_summary(add_efc=True)` wiring (standalone functions match `equivalent_cycles_to_summary` precedent); legacy-bridge columns (native-only feature).
|
|
8
|
+
|
|
9
|
+
## Changes
|
|
10
|
+
|
|
11
|
+
1. `summarizers.py`: two free functions next to `equivalent_cycles_to_summary`:
|
|
12
|
+
- `throughput_to_raw(data, schema=None, nom_cap_abs=1.0, conversion_factor=1.0, *, nom_cap=None)` — adds `test_cumulated_capacity_throughput` (= cum_sum(|current|·Δt·factor), windowed over test_id when present, negative Δt clipped to 0) and `equivalent_full_cycles` (= throughput / (2·nom_cap_abs)) to `data.raw`. `conversion_factor` converts current·time units to the capacity unit (e.g. A·s→mAh = 1000/3600), caller-supplied per codebase convention.
|
|
13
|
+
- `efc_to_summary(data, schema=None, nom_cap_abs=1.0, normalization_cycles=None, step_txt=None, *, nom_cap=None)` — same two columns on the summary, throughput = cumulated charge + discharge capacity; supports `normalization_cycles` like its sibling.
|
|
14
|
+
2. `config.py` `CycleCols`: the two new column-name fields (raw function reuses the same names via `schema.cycle`).
|
|
15
|
+
3. `docs/specifications/cycle-table.md`: two new spec rows (conformance-locked).
|
|
16
|
+
4. `legacy/mapping.py`: names added to `NATIVE_ONLY_CYCLE` (no legacy counterpart).
|
|
17
|
+
5. `tests/test_config_columns.py`: names added to `CYCLE_EXPECTED`.
|
|
18
|
+
6. `tests/test_efc.py` (new): raw integration vs hand-computed Σ|I|Δt, rest-rows-add-nothing, monotonicity, conversion factor, pandas round-trip, summary identity vs cumulated capacities.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Issue #138 — Status
|
|
2
|
+
|
|
3
|
+
- [x] Done
|
|
4
|
+
|
|
5
|
+
## 2026-07-18
|
|
6
|
+
|
|
7
|
+
- Implemented `throughput_to_raw` and `efc_to_summary` in `src/cellpycore/summarizers.py`.
|
|
8
|
+
- Registered `test_cumulated_capacity_throughput` and `equivalent_full_cycles` on `CycleCols` (`config.py`), in `docs/specifications/cycle-table.md`, in `tests/test_config_columns.py`, and as `NATIVE_ONLY_CYCLE` in `legacy/mapping.py`.
|
|
9
|
+
- Added `tests/test_efc.py` (4 tests). Full suite: 265 passed.
|
|
10
|
+
- End-to-end sanity run on mock data with A·s→mAh factor verified by hand (≈194 mAh throughput → EFC ≈ 0.324 at nom_cap 300 mAh).
|
|
11
|
+
|
|
12
|
+
## 2026-07-23 (revision after review)
|
|
13
|
+
|
|
14
|
+
- **Integration fixed**: right-Riemann `|I_i|·dt` → trapezoidal `mean(|I|)·dt` in `throughput_to_raw`. The old rule charged a whole preceding rest to the first sample of a pulse — wrong for the irregularly sampled field/BMS data this targets.
|
|
15
|
+
- **Silent failures now logged**: missing current values and negative time deltas are counted and emitted as `logger.warning` instead of passing as zero.
|
|
16
|
+
- **Legacy names **: `equivalent_full_cycles` and `cumulated_capacity_throughput` added to legacy `HeadersSummary` + `CYCLE_PAIRS` + `LEGACY_ATTR_TO_SCHEMA`; removed from `NATIVE_ONLY_CYCLE`. EFC now reaches default-schema cellpy users, not only `native_schema=True`.
|
|
17
|
+
- **Wired into `add_scaled_summary_columns`** (native + legacy bridge), unconditionally — no `add_efc` flag (`nom_cap_abs` is already resolved there, next to `equivalent_cycles_to_summary`).
|
|
18
|
+
- **Units helper**: `calculate_throughput_conversion_factor(current_unit, time_unit)` in `units/converters.py` so cellpy computes the A·s→charge factor the pint way (mirrors `calculate_current_conversion_factor`), instead of hand-derived `1000/3600` at call sites.
|
|
19
|
+
- **Docs**: raw derived-columns table in `harmonized-raw.md`; `cycle-table.md` notes `EFC = normalized_cycle_index·(1+CE)/2`.
|
|
20
|
+
- **Tests**: analytic case (1 A × 3600 s → EFC 0.5), trapezoidal-vs-right-Riemann, bad-input warnings, native + legacy-bridge wiring, units factor. The old `_expected_raw_throughput` helper mirrored the implementation and could not fail; kept but now backed by the analytic + bridge cases. Full suite: **271 passed**.
|
|
21
|
+
|
|
22
|
+
## Remaining
|
|
23
|
+
|
|
24
|
+
- **Blocking cellpy PR**: bump `cellpycore==0.2.4` pin in cellpy `pyproject.toml`, add `helpers.add_efc` wrapper (mirrors `add_normalized_cycle_index`) for the `cell.calculate_efc(...)` API from the issue. Plot label (`plotutils.py:823`) stays as-is, `equivalent_full_cycles` gets no axis-label entry (falls back to column name).
|
|
25
|
+
- Version bump to **0.3.0** on close (new spec columns = schema change).
|
|
26
|
+
- Deferred (per approved plan): energy throughput, SoC-based EFC.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Issue #142: Harden cycle_mode unwrapping in OldCellpyCellCore / _cycle_mode_to_test_mode
|
|
2
|
+
|
|
3
|
+
Source: https://github.com/cellpy/cellpy-core/issues/142
|
|
4
|
+
|
|
5
|
+
## Original issue text
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Found while working **jepegit/cellpy#668** (v1.x batch notebook): after a custom `update_cell` → `make_step_table()` → `make_summary()`, the legacy bridge crashes with:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
AttributeError: 'list' object has no attribute 'strip'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
in `cellpycore.cell_core._cycle_mode_to_test_mode` when `OldCellpyCellCore.cycle_mode` is still list-shaped (often double-nested from cellpy-file meta, e.g. `[['anode']]`).
|
|
16
|
+
|
|
17
|
+
### Why this is a core issue (not just v1.x)
|
|
18
|
+
|
|
19
|
+
- `cellpy` **master** already mitigates on the **consumer load path** via recursive `test_meta._unwrap` after `meta_test_dependent.update(as_list=True, …)` (and has unit tests).
|
|
20
|
+
- The **engine/bridge** still assumes a scalar string:
|
|
21
|
+
- `OldCellpyCellCore.cycle_mode` getter only does one-level `m[0]`
|
|
22
|
+
- setter **keeps** list values (`[x.lower() for x in cycle_mode]`) instead of storing a scalar
|
|
23
|
+
- `_cycle_mode_to_test_mode` calls `cycle_mode.strip()` with no list/tuple handling
|
|
24
|
+
|
|
25
|
+
Any path that skips consumer unwrap (legacy loads, in-memory meta mutation, older cellpy pins) can still hit this. v1.x (#668) will add a consumer-side unwrap backport; core should still be defensive.
|
|
26
|
+
|
|
27
|
+
## What to do
|
|
28
|
+
|
|
29
|
+
1. **Getter** (`OldCellpyCellCore.cycle_mode`): recursively unwrap 1-element `list`/`tuple` to a scalar (same semantics as cellpy `test_meta._unwrap`).
|
|
30
|
+
2. **Setter**: unwrap first, then store a **scalar** string (or `None`) — do not persist a list of lowered strings.
|
|
31
|
+
3. **`_cycle_mode_to_test_mode`**: if given a list/tuple, unwrap before `.strip()`; keep existing string / `None` / unknown-spelling behaviour.
|
|
32
|
+
4. **Tests** covering at least:
|
|
33
|
+
- `'anode'` / `'full_cell'`
|
|
34
|
+
- `['anode']`
|
|
35
|
+
- `[['anode']]`
|
|
36
|
+
- `None`
|
|
37
|
+
- make_summary / make_core_summary does not raise on nested list meta
|
|
38
|
+
|
|
39
|
+
## Acceptance
|
|
40
|
+
|
|
41
|
+
- Nested list `cycle_mode` no longer crashes summarization through the legacy bridge.
|
|
42
|
+
- Existing CE / inverted-convention tests (issue #129 era) stay green.
|
|
43
|
+
- No API break for callers that already pass a plain string.
|
|
44
|
+
|
|
45
|
+
## Links
|
|
46
|
+
|
|
47
|
+
- Downstream report: https://github.com/jepegit/cellpy/issues/668
|
|
48
|
+
- Related consumer mitigation on master: unwrap in `cellpy.readers.cellpy_file.read` / `test_meta._unwrap`
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Issue #142 — plan: harden `cycle_mode` unwrapping
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Stop nested list-shaped `cycle_mode` (e.g. `[['anode']]`) from crashing
|
|
6
|
+
`_cycle_mode_to_test_mode` / summarization on the legacy bridge; store and
|
|
7
|
+
expose a scalar string (or `None`) from the `cycle_mode` property.
|
|
8
|
+
|
|
9
|
+
## Constraints
|
|
10
|
+
|
|
11
|
+
- Back-compat: plain string callers unchanged; issue #127 spelling tables and
|
|
12
|
+
issue #129 bridge CE polarity tests stay green.
|
|
13
|
+
- KISS: no new package/module; private helper next to existing translator in
|
|
14
|
+
`cell_core.py`. No numpy dependency (cellpy’s `_unwrap` also peels numpy
|
|
15
|
+
scalars / NaN — out of scope here; meta on the bridge is Python
|
|
16
|
+
`list`/`tuple`/`str`/`None`).
|
|
17
|
+
- Metadata boundary unchanged: still optional; only normalize this one field
|
|
18
|
+
at the property / translator edge.
|
|
19
|
+
- Scope: getter, setter, `_cycle_mode_to_test_mode` (+ tests). No cellpy
|
|
20
|
+
consumer changes (jepegit/cellpy#668 stays separate).
|
|
21
|
+
|
|
22
|
+
### Prior art
|
|
23
|
+
|
|
24
|
+
- `cellpy.readers.test_meta._unwrap` — recursive peel of 1-element
|
|
25
|
+
`list`/`tuple`; multi-element left as-is. **Mirror** that rule in core
|
|
26
|
+
(list/tuple only).
|
|
27
|
+
- `_cycle_mode_to_test_mode` + `#127` tests in `tests/test_schema.py` —
|
|
28
|
+
spelling / warning contract; **extend**, don’t rewrite.
|
|
29
|
+
- `CellpyCellCore.cycle_mode` getter already does one-level `m[0]`; setter
|
|
30
|
+
wrongly keeps a lowered **list**. `OldCellpyCellCore` inherits this
|
|
31
|
+
property (no override).
|
|
32
|
+
- `test_bridge_summary_respects_cycle_mode` (`tests/test_golden.py`) — CE
|
|
33
|
+
polarity regression for string `"anode"`; keep green; add a nested-list
|
|
34
|
+
smoke if cheap.
|
|
35
|
+
- Toolbox: empty (nothing reusable). Graph: `cycle_mode` /
|
|
36
|
+
`_cycle_mode_to_test_mode` / `OldCellpyCellCore` communities confirm the
|
|
37
|
+
change set is `cell_core.py` + `test_schema.py`.
|
|
38
|
+
|
|
39
|
+
## Approach
|
|
40
|
+
|
|
41
|
+
1. Add `_unwrap_meta_scalar(value)` in `cell_core.py` (module-private):
|
|
42
|
+
while `list`/`tuple` and `len == 1`, recurse; else return value.
|
|
43
|
+
2. **Getter:** unwrap `meta_test_dependent.cycle_mode`; if still
|
|
44
|
+
`list`/`tuple`, take `[0]` when non-empty else `None` (preserves today’s
|
|
45
|
+
first-element behaviour for multi-element placeholders).
|
|
46
|
+
3. **Setter:** unwrap first; if still sequence, take first or `None`; then
|
|
47
|
+
store **scalar** `str.lower()` or `None` on both meta and `_cycle_mode`
|
|
48
|
+
(never a list of lowered strings). Handle `None` without calling `.lower()`.
|
|
49
|
+
4. **`_cycle_mode_to_test_mode`:** unwrap (+ first-element if still sequence)
|
|
50
|
+
before `.strip()`; keep existing `None` / empty / known / unknown+warn
|
|
51
|
+
behaviour. Type hint can widen to `Optional[str | list | tuple]` or stay
|
|
52
|
+
loose — behaviour matters more than the annotation.
|
|
53
|
+
5. No public API export of the helper.
|
|
54
|
+
|
|
55
|
+
Multi-element lists (`['anode','cathode']`) are not a real cellpy-file shape
|
|
56
|
+
today; taking `[0]` matches the old getter and avoids a new failure mode.
|
|
57
|
+
|
|
58
|
+
## Files to touch
|
|
59
|
+
|
|
60
|
+
| Path | Change |
|
|
61
|
+
|------|--------|
|
|
62
|
+
| [`src/cellpycore/cell_core.py`](../../src/cellpycore/cell_core.py) | `_unwrap_meta_scalar`; harden getter / setter / `_cycle_mode_to_test_mode` |
|
|
63
|
+
| [`tests/test_schema.py`](../../tests/test_schema.py) | Parametrize unwrap cases next to `#127` block; nested-list → INVERTED; setter stores scalar; optional `make_core_summary` / `make_summary` smoke with nested meta |
|
|
64
|
+
|
|
65
|
+
## Test strategy
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv run pytest tests/test_schema.py -k cycle_mode
|
|
69
|
+
uv run pytest # full suite before close
|
|
70
|
+
uv run ruff check && uv run ruff format --check
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
New coverage (minimum from issue):
|
|
74
|
+
|
|
75
|
+
- `_cycle_mode_to_test_mode`: `'anode'`, `'full_cell'`, `['anode']`,
|
|
76
|
+
`[['anode']]`, `None` (and keep existing spelling params green).
|
|
77
|
+
- Getter/setter: set `[['anode']]` or assign list → property returns
|
|
78
|
+
`"anode"`; stored meta is a `str`, not a list.
|
|
79
|
+
- Smoke: `OldCellpyCellCore` (or `CellpyCellCore`) with nested list on
|
|
80
|
+
`meta_test_dependent.cycle_mode` → `make_core_summary` / `make_summary`
|
|
81
|
+
does not raise.
|
|
82
|
+
|
|
83
|
+
## Open questions
|
|
84
|
+
|
|
85
|
+
None blocking — defaults above match the issue + cellpy `_unwrap`. Say if
|
|
86
|
+
you want multi-element lists to warn instead of silently taking `[0]`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Issue #142 — status
|
|
2
|
+
|
|
3
|
+
- [x] Done
|
|
4
|
+
|
|
5
|
+
## What's done
|
|
6
|
+
|
|
7
|
+
- Plan confirmed (`issue142_plan.md`).
|
|
8
|
+
- Added `_unwrap_meta_scalar` / `_as_cycle_mode_scalar` in `cell_core.py`.
|
|
9
|
+
- Hardened `CellpyCellCore.cycle_mode` getter/setter to store/return scalar
|
|
10
|
+
(or `None`); nested lists no longer persist as lowered lists.
|
|
11
|
+
- `_cycle_mode_to_test_mode` unwraps list/tuple before `.strip()`.
|
|
12
|
+
- Tests in `tests/test_schema.py` (issue #142 block): translator shapes,
|
|
13
|
+
setter scalar store, getter unwrap, `OldCellpyCellCore.make_core_summary`
|
|
14
|
+
smoke with `[['anode']]` meta.
|
|
15
|
+
- Full suite: 281 passed; ruff green.
|
|
16
|
+
- `HISTORY.md` Unreleased bullet added.
|
|
17
|
+
|
|
18
|
+
## Remaining work
|
|
19
|
+
|
|
20
|
+
- None (close / PR).
|
|
@@ -46,7 +46,7 @@ Reconciled against the actual repo state on **2026-06-26**. Status legend: ✅ d
|
|
|
46
46
|
| STEP-09 Harmonize headers | ✅ done | `config.Cols` + spec tests; `legacy/mapping.py` gives lossless/total `config.Cols` ↔ legacy `Headers*` round-trip (`tests/test_header_mapping.py`) (#34/#35) |
|
|
47
47
|
| STEP-10 Metadata scaffolding | ✅ done | `cellpycore.metadata` (`models.py`/`io.py`); `TestMeta`/`CellMeta`/`TestMetaCollection` + (de)serialize/merge; graceful-degradation guard (`tests/test_metadata.py`) (#37) |
|
|
48
48
|
| STEP-11 Timestamp representation | ✅ done | `epoch_time_utc` + `first/last_epoch_time_utc` are int64-ns UTC; `cellpycore.timestamps` conversion helpers + fixture regenerated (`tests/test_timestamps.py`) (#32, PR #38) |
|
|
49
|
-
| STEP-12 Unit-handling boundary |
|
|
49
|
+
| STEP-12 Unit-handling boundary | ✅ done | `CellpyUnits` schema in `units/spec.py` (#40/#112) + `units.py` tooling behind the optional `units` extra; factors cross the seam by value; `cellpy` now delegates `get_converter_to_specific` / `nominal_capacity_as_absolute` to `cellpycore.units` (jepegit/cellpy #451) — the duplicate converters are retired |
|
|
50
50
|
|
|
51
51
|
Per-step `Status:` lines below repeat this for context; this table is the quick reference.
|
|
52
52
|
The forward work beyond these twelve steps (STEP-13+) is enumerated in
|
|
@@ -266,12 +266,15 @@ Adopt the internal int64-ns timestamp representation.
|
|
|
266
266
|
|
|
267
267
|
## STEP-12 Unit-handling boundary (scaffolding/tooling in core; population & policy upstream)
|
|
268
268
|
|
|
269
|
-
**Status:**
|
|
269
|
+
**Status:** ✅ done — the `CellpyUnits` schema lives in `cellpycore.units.spec`
|
|
270
270
|
(#40, #112) and the pint-based conversion tooling (`cellpycore.units`:
|
|
271
271
|
`get_converter_to_specific`, `nominal_capacity_as_absolute`, `Q`, output-unit defaults)
|
|
272
|
-
|
|
273
|
-
bridge, and conversion factors
|
|
274
|
-
|
|
272
|
+
exists behind the optional `units` extra, attached to the `OldCellpyCellCore`
|
|
273
|
+
bridge, and conversion factors cross the seam **by value**. The final cellpy-side move
|
|
274
|
+
has landed: `CellpyCell.get_converter_to_specific` / `nominal_capacity_as_absolute`
|
|
275
|
+
(and `convert_value` / `calculate_scaler`) now delegate to `cellpycore.units`
|
|
276
|
+
(jepegit/cellpy #451, unit plan Phase 2), guarded by converter-parity fixtures on both
|
|
277
|
+
sides — the duplicated converters are retired.
|
|
275
278
|
|
|
276
279
|
**Codebase:** `cellpy-core` (scaffolding/tooling — `legacy.py` + `units.py`); population
|
|
277
280
|
(`raw_units` from loaders) and the decision to delegate are `cellpy`'s opt-in.
|
|
@@ -299,8 +302,9 @@ already did for headers and the step engine).
|
|
|
299
302
|
|
|
300
303
|
## Remaining work (STEP-13+) — finalized 2026-06-28 (issue #39)
|
|
301
304
|
|
|
302
|
-
The original twelve steps are complete except for the
|
|
303
|
-
|
|
305
|
+
The original twelve steps are complete except for the one intentionally-continuous
|
|
306
|
+
one (STEP-06 golden fixtures); STEP-12 (unit boundary) closed once the cellpy-side
|
|
307
|
+
converter delegation landed (jepegit/cellpy #451). Going through the
|
|
304
308
|
roadmap and the companion design docs surfaced the remaining work below. Each item is now
|
|
305
309
|
tracked as its own **cellpy-core** GitHub issue (the cellpy-side delegation work stays on
|
|
306
310
|
jepegit/cellpy). Captured under issue #39.
|
|
@@ -339,12 +343,12 @@ flowchart TD
|
|
|
339
343
|
S08 --> S09["STEP-09 Harmonize headers ✅"]
|
|
340
344
|
S08 --> S10["STEP-10 Metadata scaffolding ✅"]
|
|
341
345
|
S08 --> S11["STEP-11 Timestamp representation ✅"]
|
|
342
|
-
S03 --> S12["STEP-12 Unit-handling boundary
|
|
346
|
+
S03 --> S12["STEP-12 Unit-handling boundary ✅"]
|
|
343
347
|
S07["STEP-07 Build-backend swap ✅"] -.independent.-> S08
|
|
344
348
|
|
|
345
349
|
classDef done fill:#d4edda,stroke:#28a745,color:#155724;
|
|
346
350
|
classDef partly fill:#fff3cd,stroke:#ffc107,color:#856404;
|
|
347
351
|
classDef todo fill:#f8d7da,stroke:#dc3545,color:#721c24;
|
|
348
|
-
class S01,S02,S03,S04,S05,S07,S08,S09,S10,S11 done;
|
|
349
|
-
class S06
|
|
352
|
+
class S01,S02,S03,S04,S05,S07,S08,S09,S10,S11,S12 done;
|
|
353
|
+
class S06 partly;
|
|
350
354
|
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"python.testing.pytestArgs": [
|
|
3
|
+
"tests"
|
|
4
|
+
],
|
|
5
|
+
"python.testing.unittestEnabled": false,
|
|
6
|
+
"python.testing.pytestEnabled": true,
|
|
7
|
+
"python-envs.pythonProjects": [
|
|
8
|
+
{
|
|
9
|
+
"path": ".",
|
|
10
|
+
"envManager": "ms-python.python:venv",
|
|
11
|
+
"packageManager": "ms-python.python:pip"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
- Harden `cycle_mode` unwrapping in `OldCellpyCellCore` / `_cycle_mode_to_test_mode`. (#142)
|
|
8
|
+
|
|
9
|
+
## [0.2.4] - 2026-07-23
|
|
10
|
+
|
|
11
|
+
- Add Equivalent Full Cycles (EFC) utilities (#138). `summarizers.throughput_to_raw`
|
|
12
|
+
integrates `|current|` over test time (trapezoidal, windowed per `test_id`) to add
|
|
13
|
+
`test_cumulated_capacity_throughput` and `equivalent_full_cycles` to the raw frame —
|
|
14
|
+
for continuous / BMS / field data without cycle boundaries. `summarizers.efc_to_summary`
|
|
15
|
+
adds the same two columns to the per-cycle summary from the cumulated charge+discharge
|
|
16
|
+
capacities. Both use `EFC = throughput / (2 x nominal capacity)`. Missing current values
|
|
17
|
+
and negative time deltas contribute zero and are logged as warnings.
|
|
18
|
+
- EFC is emitted unconditionally by `add_scaled_summary_columns` (native + legacy bridge),
|
|
19
|
+
so it reaches default-schema cellpy users; the legacy bridge renames the summary columns
|
|
20
|
+
to `cumulated_capacity_throughput` / `equivalent_full_cycles`.
|
|
21
|
+
- New spec columns in `docs/specifications/cycle-table.md` (with the
|
|
22
|
+
`EFC = normalized_cycle_index x (1 + CE) / 2` relationship noted) and the raw
|
|
23
|
+
derived-columns table in `harmonized-raw.md`.
|
|
24
|
+
- `units.calculate_throughput_conversion_factor(current_unit, time_unit)` computes the
|
|
25
|
+
current·time → charge factor (e.g. A·s → mAh) the pint way, mirroring
|
|
26
|
+
`calculate_current_conversion_factor`.
|
|
7
27
|
## [0.2.3] - 2026-07-19
|
|
8
28
|
|
|
9
29
|
- Bridge legacy raw ``charge_energy`` / ``discharge_energy`` to
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import logging
|
|
3
3
|
import time
|
|
4
|
-
from typing import Callable, Iterable, List, Optional, Sequence, TypeVar, Union
|
|
4
|
+
from typing import Any, Callable, Iterable, List, Optional, Sequence, TypeVar, Union
|
|
5
5
|
|
|
6
6
|
from cellpycore import config
|
|
7
7
|
from cellpycore.exceptions import NoDataFound
|
|
@@ -24,7 +24,31 @@ _NORMAL_CYCLE_MODES = frozenset(
|
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def _unwrap_meta_scalar(value: Any) -> Any:
|
|
28
|
+
"""Recursively peel 1-element ``list``/``tuple`` wrappers.
|
|
29
|
+
|
|
30
|
+
Mirrors cellpy's ``test_meta._unwrap`` for the shapes that show up in
|
|
31
|
+
cellpy-file meta (often nested once, e.g. ``[['anode']]``). Multi-element
|
|
32
|
+
sequences are left unchanged.
|
|
33
|
+
"""
|
|
34
|
+
if isinstance(value, (list, tuple)) and len(value) == 1:
|
|
35
|
+
return _unwrap_meta_scalar(value[0])
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _as_cycle_mode_scalar(value: Any) -> Optional[str]:
|
|
40
|
+
"""Normalize ``cycle_mode`` to a scalar string or ``None``.
|
|
41
|
+
|
|
42
|
+
Unwraps 1-element list/tuple nesting, then takes the first element of any
|
|
43
|
+
remaining multi-element sequence (legacy getter behaviour).
|
|
44
|
+
"""
|
|
45
|
+
value = _unwrap_meta_scalar(value)
|
|
46
|
+
if isinstance(value, (list, tuple)):
|
|
47
|
+
return value[0] if value else None
|
|
48
|
+
return value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _cycle_mode_to_test_mode(cycle_mode: Any) -> config.TestMode:
|
|
28
52
|
"""Map a legacy ``cycle_mode`` string to a :class:`config.TestMode`.
|
|
29
53
|
|
|
30
54
|
The inverted (anode half-cell) convention is selected by any of ``"anode"``
|
|
@@ -33,16 +57,19 @@ def _cycle_mode_to_test_mode(cycle_mode: Optional[str]) -> config.TestMode:
|
|
|
33
57
|
``None`` and the recognized normal-convention spellings map to
|
|
34
58
|
``TestMode.NORMAL``.
|
|
35
59
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
``
|
|
60
|
+
Nested 1-element ``list``/``tuple`` wrappers (cellpy-file meta shape) are
|
|
61
|
+
unwrapped before matching. An *unrecognized* non-empty value also maps to
|
|
62
|
+
``NORMAL`` but logs a warning, so a mistyped or unmapped mode fails loudly
|
|
63
|
+
instead of silently flipping the ``coulombic_efficiency`` /
|
|
64
|
+
``coulombic_difference`` sign conventions.
|
|
39
65
|
|
|
40
66
|
Args:
|
|
41
|
-
cycle_mode: Legacy mode string, or ``None``.
|
|
67
|
+
cycle_mode: Legacy mode string, nested list/tuple wrappers, or ``None``.
|
|
42
68
|
|
|
43
69
|
Returns:
|
|
44
70
|
The corresponding ``TestMode`` for summary sign conventions.
|
|
45
71
|
"""
|
|
72
|
+
cycle_mode = _as_cycle_mode_scalar(cycle_mode)
|
|
46
73
|
if cycle_mode is None:
|
|
47
74
|
return config.TestMode.NORMAL
|
|
48
75
|
key = cycle_mode.strip().lower()
|
|
@@ -370,23 +397,18 @@ class CellpyCellCore:
|
|
|
370
397
|
# TODO: v2.0 edit this from scalar to list
|
|
371
398
|
try:
|
|
372
399
|
data = self.data
|
|
373
|
-
|
|
374
|
-
#
|
|
375
|
-
|
|
376
|
-
# Temporary fix to make sure that cycle_mode is a scalar:
|
|
377
|
-
if isinstance(m, (tuple, list)):
|
|
378
|
-
return m[0]
|
|
379
|
-
return m
|
|
400
|
+
# cellpy-file meta often stores 1-element (sometimes nested) lists;
|
|
401
|
+
# expose a scalar for the current engine.
|
|
402
|
+
return _as_cycle_mode_scalar(data.meta_test_dependent.cycle_mode)
|
|
380
403
|
except NoDataFound:
|
|
381
404
|
return self._cycle_mode
|
|
382
405
|
|
|
383
406
|
@cycle_mode.setter
|
|
384
|
-
def cycle_mode(self, cycle_mode:
|
|
385
|
-
if isinstance(cycle_mode, (tuple, list)):
|
|
386
|
-
cycle_mode = [cycle_mode.lower() for cycle_mode in cycle_mode]
|
|
387
|
-
else:
|
|
388
|
-
cycle_mode = cycle_mode.lower()
|
|
407
|
+
def cycle_mode(self, cycle_mode: Any):
|
|
389
408
|
# TODO: v2.0 edit this from scalar to list
|
|
409
|
+
cycle_mode = _as_cycle_mode_scalar(cycle_mode)
|
|
410
|
+
if cycle_mode is not None:
|
|
411
|
+
cycle_mode = cycle_mode.lower()
|
|
390
412
|
logger.debug(f"-> cycle_mode: {cycle_mode}")
|
|
391
413
|
try:
|
|
392
414
|
data = self.data
|
|
@@ -621,6 +643,9 @@ class CellpyCellCore:
|
|
|
621
643
|
data = summarizers.equivalent_cycles_to_summary(
|
|
622
644
|
data, schema, nom_cap_abs, normalization_cycles, step_txt
|
|
623
645
|
)
|
|
646
|
+
data = summarizers.efc_to_summary(
|
|
647
|
+
data, schema, nom_cap_abs, normalization_cycles, step_txt
|
|
648
|
+
)
|
|
624
649
|
|
|
625
650
|
# Note: the C-rates are added by make_core_summary (using the step-table
|
|
626
651
|
# rates, independent of nom_cap, matching legacy cellpy). Adding them again
|
|
@@ -1131,6 +1156,9 @@ class OldCellpyCellCore(CellpyCellCore):
|
|
|
1131
1156
|
summarizers.equivalent_cycles_to_summary(
|
|
1132
1157
|
nd, native_schema, nom_cap_abs, normalization_cycles, step_txt
|
|
1133
1158
|
)
|
|
1159
|
+
summarizers.efc_to_summary(
|
|
1160
|
+
nd, native_schema, nom_cap_abs, normalization_cycles, step_txt
|
|
1161
|
+
)
|
|
1134
1162
|
specific_columns = native_schema.cycle.specific_columns
|
|
1135
1163
|
for mode in specifics:
|
|
1136
1164
|
factor = self._resolve_specific_conversion_factor(
|
|
@@ -444,6 +444,10 @@ class CycleCols(Cols):
|
|
|
444
444
|
# preserving single-value IR targets (the four ``ir_start/end_*`` columns
|
|
445
445
|
# above remain reserved for the richer future IR model).
|
|
446
446
|
normalized_cycle_index: str = "normalized_cycle_index"
|
|
447
|
+
# Throughput-based ageing metrics (issue #138). ``throughput_to_raw`` reuses
|
|
448
|
+
# these same names for the columns it adds to the raw frame.
|
|
449
|
+
test_cumulated_capacity_throughput: str = "test_cumulated_capacity_throughput"
|
|
450
|
+
equivalent_full_cycles: str = "equivalent_full_cycles"
|
|
447
451
|
charge_c_rate: str = "charge_c_rate"
|
|
448
452
|
discharge_c_rate: str = "discharge_c_rate"
|
|
449
453
|
ir_charge: str = "ir_charge"
|
|
@@ -122,6 +122,8 @@ class HeadersSummary(BaseHeaders):
|
|
|
122
122
|
cumulated_ric_sei: str = "cumulated_ric_sei"
|
|
123
123
|
cumulated_ric: str = "cumulated_ric"
|
|
124
124
|
normalized_cycle_index: str = "normalized_cycle_index"
|
|
125
|
+
cumulated_capacity_throughput: str = "cumulated_capacity_throughput"
|
|
126
|
+
equivalent_full_cycles: str = "equivalent_full_cycles"
|
|
125
127
|
low_level: str = "low_level"
|
|
126
128
|
high_level: str = "high_level"
|
|
127
129
|
|
|
@@ -121,6 +121,9 @@ CYCLE_PAIRS = [
|
|
|
121
121
|
("charge_c_rate", "charge_c_rate"),
|
|
122
122
|
("discharge_c_rate", "discharge_c_rate"),
|
|
123
123
|
("normalized_cycle_index", "normalized_cycle_index"),
|
|
124
|
+
# Throughput-based ageing metrics (issue #138).
|
|
125
|
+
("test_cumulated_capacity_throughput", "cumulated_capacity_throughput"),
|
|
126
|
+
("equivalent_full_cycles", "equivalent_full_cycles"),
|
|
124
127
|
]
|
|
125
128
|
|
|
126
129
|
# -----------------------------------------------------------------------------
|
|
@@ -366,6 +369,8 @@ LEGACY_ATTR_TO_SCHEMA = {
|
|
|
366
369
|
"cumulated_coulombic_difference": "test_cumulated_coulombic_difference",
|
|
367
370
|
"cumulated_discharge_capacity": "test_cumulated_discharge_capacity",
|
|
368
371
|
"cumulated_discharge_capacity_loss": "test_cumulated_discharge_capacity_loss",
|
|
372
|
+
"cumulated_capacity_throughput": "test_cumulated_capacity_throughput",
|
|
373
|
+
"equivalent_full_cycles": "equivalent_full_cycles",
|
|
369
374
|
"cycle_index": "cycle_num",
|
|
370
375
|
"data_point": "datapoint_num_last",
|
|
371
376
|
"discharge_c_rate": "discharge_c_rate",
|