cellpycore 0.2.3__tar.gz → 0.2.4__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.4/.issueflows/01-current-issues/issue138_original.md +85 -0
- cellpycore-0.2.4/.issueflows/01-current-issues/issue138_plan.md +18 -0
- cellpycore-0.2.4/.issueflows/01-current-issues/issue138_status.md +26 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/HISTORY.md +18 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/PKG-INFO +1 -1
- {cellpycore-0.2.3 → cellpycore-0.2.4}/pyproject.toml +1 -1
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/cell_core.py +6 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/config.py +4 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/headers.py +2 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/mapping.py +5 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/summarizers.py +168 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/units/__init__.py +2 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/units/converters.py +30 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/uv.lock +1 -1
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.aliases +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/rules/cellpy-core-migration.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/rules/graphify.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/rules/issueflow-rules.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/rules/kiss.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/rules/this-project.mdc +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/caveman/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/grill-me/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-archive/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-cleanup/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-close/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-comments/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-cycle/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-epic/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-fix/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-graphify/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-history-update/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-init/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-pause/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-pick/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-plan/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-start/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-status/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-version-bump/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.cursor/skills/iflow-yolo/SKILL.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.gitattributes +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.gitignore +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/00-tools/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/00-tools/README.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/01-current-issues/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/02-partly-solved-issues/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/02-partly-solved-issues/issue100_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/02-partly-solved-issues/issue100_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/02-partly-solved-issues/issue100_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue10_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue10_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue10_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue110_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue110_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue110_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue112_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue112_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue112_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue114_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue114_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue114_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue115_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue115_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue115_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue116_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue116_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue116_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue117_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue117_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue117_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue118_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue118_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue118_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue121_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue121_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue121_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue12_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue12_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue12_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue136_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue136_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue136_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue139_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue139_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue139_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue13_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue13_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue13_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue21_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue21_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue21_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue22_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue22_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue22_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue23_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue23_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue23_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue24_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue24_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue29_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue29_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue29_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue30_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue30_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue30_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue32_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue32_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue32_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue34_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue34_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue36_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue36_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue39_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue39_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue39_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue40_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue40_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue40_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue41_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue41_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue41_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue42_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue42_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue42_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue43_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue43_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue43_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue44_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue44_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue45_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue45_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue45_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue50_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue50_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue50_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue54_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue54_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue54_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue55_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue55_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue55_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue56_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue56_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue56_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue62_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue62_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue62_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue64_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue64_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue64_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue66_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue66_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue66_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue67_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue67_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue67_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue68_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue68_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue68_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue69_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue69_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue69_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue70_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue70_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue70_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue72_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue72_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue77_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue77_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue77_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue82_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue82_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue84_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue84_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue84_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue86_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue86_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue86_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue91_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue91_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue91_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue95_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue95_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue95_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue96_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue96_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue96_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue97_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue97_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue97_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue98_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue98_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue98_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue99_original.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue99_plan.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/03-solved-issues/issue99_status.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/bdf-io-placement.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/cellpy-core-integration-into-cellpy.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/cellpy-core-integration-roadmap.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/cellpy-core-migration.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/code-review-2026-07.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/column-headers-review.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/legacy-bridge-test-id.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/legacy-raw-energy-mapping.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/metadata-scaffolding.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/nom-cap-naming.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/rawcols-dtype-map.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/release-procedure.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/selector-dead-code-deferral.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/step-c-rate-split.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/step-table-polars-migration.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/summary-extractors.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/test-data-and-fixtures.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/test-metadata-and-merging.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/this-project.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/04-designs-and-guides/zensical-docs.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/05-epics/.gitkeep +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/config.toml +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.pre-commit-config.yaml +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.python-version +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.readthedocs.yaml +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/.vscode/settings.json +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/AGENTS.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/LICENSE +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/README.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/ROADMAP.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/SCRATCHPAD.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/graphify-out/.graphify_labels.json +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/graphify-out/.graphify_root +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/graphify-out/GRAPH_REPORT.md +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/graphify-out/graph.html +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/graphify-out/graph.json +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/graphify-out/manifest.json +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/curves.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/exceptions.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/extractors.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/limits.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/meta.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/meta_mapping.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/mock_core.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/legacy/selectors.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/merge.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/metadata/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/metadata/io.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/metadata/models.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/py.typed +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/settings_base.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/testing/__init__.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/testing/mock_data.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/timestamps.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/src/cellpycore/units/spec.py +0 -0
- {cellpycore-0.2.3 → cellpycore-0.2.4}/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.
|
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.4] - 2026-07-23
|
|
8
|
+
|
|
9
|
+
- Add Equivalent Full Cycles (EFC) utilities (#138). `summarizers.throughput_to_raw`
|
|
10
|
+
integrates `|current|` over test time (trapezoidal, windowed per `test_id`) to add
|
|
11
|
+
`test_cumulated_capacity_throughput` and `equivalent_full_cycles` to the raw frame —
|
|
12
|
+
for continuous / BMS / field data without cycle boundaries. `summarizers.efc_to_summary`
|
|
13
|
+
adds the same two columns to the per-cycle summary from the cumulated charge+discharge
|
|
14
|
+
capacities. Both use `EFC = throughput / (2 x nominal capacity)`. Missing current values
|
|
15
|
+
and negative time deltas contribute zero and are logged as warnings.
|
|
16
|
+
- EFC is emitted unconditionally by `add_scaled_summary_columns` (native + legacy bridge),
|
|
17
|
+
so it reaches default-schema cellpy users; the legacy bridge renames the summary columns
|
|
18
|
+
to `cumulated_capacity_throughput` / `equivalent_full_cycles`.
|
|
19
|
+
- New spec columns in `docs/specifications/cycle-table.md` (with the
|
|
20
|
+
`EFC = normalized_cycle_index x (1 + CE) / 2` relationship noted) and the raw
|
|
21
|
+
derived-columns table in `harmonized-raw.md`.
|
|
22
|
+
- `units.calculate_throughput_conversion_factor(current_unit, time_unit)` computes the
|
|
23
|
+
current·time → charge factor (e.g. A·s → mAh) the pint way, mirroring
|
|
24
|
+
`calculate_current_conversion_factor`.
|
|
7
25
|
## [0.2.3] - 2026-07-19
|
|
8
26
|
|
|
9
27
|
- Bridge legacy raw ``charge_energy`` / ``discharge_energy`` to
|
|
@@ -621,6 +621,9 @@ class CellpyCellCore:
|
|
|
621
621
|
data = summarizers.equivalent_cycles_to_summary(
|
|
622
622
|
data, schema, nom_cap_abs, normalization_cycles, step_txt
|
|
623
623
|
)
|
|
624
|
+
data = summarizers.efc_to_summary(
|
|
625
|
+
data, schema, nom_cap_abs, normalization_cycles, step_txt
|
|
626
|
+
)
|
|
624
627
|
|
|
625
628
|
# Note: the C-rates are added by make_core_summary (using the step-table
|
|
626
629
|
# rates, independent of nom_cap, matching legacy cellpy). Adding them again
|
|
@@ -1131,6 +1134,9 @@ class OldCellpyCellCore(CellpyCellCore):
|
|
|
1131
1134
|
summarizers.equivalent_cycles_to_summary(
|
|
1132
1135
|
nd, native_schema, nom_cap_abs, normalization_cycles, step_txt
|
|
1133
1136
|
)
|
|
1137
|
+
summarizers.efc_to_summary(
|
|
1138
|
+
nd, native_schema, nom_cap_abs, normalization_cycles, step_txt
|
|
1139
|
+
)
|
|
1134
1140
|
specific_columns = native_schema.cycle.specific_columns
|
|
1135
1141
|
for mode in specifics:
|
|
1136
1142
|
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",
|
|
@@ -1001,6 +1001,174 @@ def equivalent_cycles_to_summary(
|
|
|
1001
1001
|
return data
|
|
1002
1002
|
|
|
1003
1003
|
|
|
1004
|
+
def throughput_to_raw(
|
|
1005
|
+
data: Data,
|
|
1006
|
+
schema: Optional[Schema] = None,
|
|
1007
|
+
nom_cap_abs: float = 1.0,
|
|
1008
|
+
conversion_factor: float = 1.0,
|
|
1009
|
+
*,
|
|
1010
|
+
nom_cap: Optional[float] = None,
|
|
1011
|
+
) -> Data:
|
|
1012
|
+
"""Add cumulative capacity throughput and equivalent full cycles to the raw frame.
|
|
1013
|
+
|
|
1014
|
+
Polars-native. Integrates the absolute current over test time
|
|
1015
|
+
(trapezoidal ``sum(|current| * dt)``, cumulated per test) and derives the
|
|
1016
|
+
equivalent full cycles (EFC) from it (issue #138):
|
|
1017
|
+
|
|
1018
|
+
- ``test_cumulated_capacity_throughput = cum_sum(mean(|I|) * dt * conversion_factor)``
|
|
1019
|
+
- ``equivalent_full_cycles = test_cumulated_capacity_throughput / (2 * nom_cap_abs)``
|
|
1020
|
+
|
|
1021
|
+
Works on continuous time-series data (e.g. BMS / field datasets) without
|
|
1022
|
+
any step table, summary, or cycle boundaries. Negative time deltas (file
|
|
1023
|
+
seams, timer resets) and missing current values contribute zero; both are
|
|
1024
|
+
logged as warnings rather than passing silently.
|
|
1025
|
+
|
|
1026
|
+
Args:
|
|
1027
|
+
data (Data): The data object (only ``data.raw`` is used).
|
|
1028
|
+
schema: The column-header schema to use. Defaults to the native
|
|
1029
|
+
cellpy-core schema when not provided.
|
|
1030
|
+
nom_cap_abs (float): The absolute nominal capacity, in the unit that
|
|
1031
|
+
``conversion_factor`` converts the throughput into (default: 1.0).
|
|
1032
|
+
conversion_factor (float): Precomputed factor converting
|
|
1033
|
+
``current_unit * time_unit`` to the capacity unit of ``nom_cap_abs``
|
|
1034
|
+
(by value; default 1.0 = no conversion, e.g. ``1000 / 3600`` for
|
|
1035
|
+
A·s -> mAh).
|
|
1036
|
+
nom_cap (float): Deprecated alias for ``nom_cap_abs``.
|
|
1037
|
+
|
|
1038
|
+
Returns:
|
|
1039
|
+
Data: The data object with the two columns added to ``data.raw``.
|
|
1040
|
+
"""
|
|
1041
|
+
nom_cap_abs = _resolve_nom_cap_abs(nom_cap_abs, nom_cap)
|
|
1042
|
+
if schema is None:
|
|
1043
|
+
schema = default_schema()
|
|
1044
|
+
hdr_raw = schema.raw
|
|
1045
|
+
hdr_out = schema.cycle
|
|
1046
|
+
|
|
1047
|
+
# The engine is polars-native; accept a pandas frame for convenience.
|
|
1048
|
+
raw = data.raw
|
|
1049
|
+
was_pandas = not isinstance(raw, pl.DataFrame)
|
|
1050
|
+
if was_pandas:
|
|
1051
|
+
raw = pl.from_pandas(raw)
|
|
1052
|
+
|
|
1053
|
+
# Window per test when the frame carries a test_id (merged objects); a
|
|
1054
|
+
# plain single-test / BMS time-series frame without one is fine too.
|
|
1055
|
+
per_test = hdr_raw.test_id in raw.columns
|
|
1056
|
+
|
|
1057
|
+
def _over(expr: pl.Expr) -> pl.Expr:
|
|
1058
|
+
return expr.over(hdr_raw.test_id) if per_test else expr
|
|
1059
|
+
|
|
1060
|
+
current_abs = pl.col(hdr_raw.current).abs()
|
|
1061
|
+
diff_t = _over(pl.col(hdr_raw.test_time).diff())
|
|
1062
|
+
|
|
1063
|
+
# Both failure modes are silent otherwise: a gap in the current trace would
|
|
1064
|
+
# contribute zero, and an unsorted / reset time column would be swallowed by
|
|
1065
|
+
# the clip below. Count them once and say so.
|
|
1066
|
+
n_missing, n_backwards = raw.select(
|
|
1067
|
+
current_abs.is_null().sum().alias("missing"),
|
|
1068
|
+
(diff_t < 0.0).sum().alias("backwards"),
|
|
1069
|
+
).row(0)
|
|
1070
|
+
if n_missing:
|
|
1071
|
+
logger.warning(
|
|
1072
|
+
f"{n_missing} missing current value(s) counted as zero current in the "
|
|
1073
|
+
"throughput integral"
|
|
1074
|
+
)
|
|
1075
|
+
if n_backwards:
|
|
1076
|
+
logger.warning(
|
|
1077
|
+
f"{n_backwards} negative time delta(s) contributed zero throughput "
|
|
1078
|
+
"(unsorted rows, file seam, or timer reset?)"
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
prev_current = _over(current_abs.shift(1))
|
|
1082
|
+
dt = diff_t.fill_null(0.0).clip(lower_bound=0.0)
|
|
1083
|
+
|
|
1084
|
+
# Trapezoidal. A right-hand rule (|I_i| * dt) charges the whole preceding
|
|
1085
|
+
# rest to the first sample of a pulse, which matters on the irregularly
|
|
1086
|
+
# sampled field / BMS data this is meant for.
|
|
1087
|
+
mean_current = (current_abs.fill_null(0.0) + prev_current.fill_null(0.0)) / 2.0
|
|
1088
|
+
increment = mean_current * dt * conversion_factor
|
|
1089
|
+
throughput_col = hdr_out.test_cumulated_capacity_throughput
|
|
1090
|
+
raw = (
|
|
1091
|
+
raw.with_columns(increment.alias(throughput_col))
|
|
1092
|
+
.with_columns(_over(pl.col(throughput_col).cum_sum()).alias(throughput_col))
|
|
1093
|
+
.with_columns(
|
|
1094
|
+
(pl.col(throughput_col) / (2.0 * nom_cap_abs)).alias(
|
|
1095
|
+
hdr_out.equivalent_full_cycles
|
|
1096
|
+
)
|
|
1097
|
+
)
|
|
1098
|
+
)
|
|
1099
|
+
|
|
1100
|
+
data.raw = raw.to_pandas() if was_pandas else raw
|
|
1101
|
+
return data
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
def efc_to_summary(
|
|
1105
|
+
data: Data,
|
|
1106
|
+
schema: Optional[Schema] = None,
|
|
1107
|
+
nom_cap_abs: float = 1.0,
|
|
1108
|
+
normalization_cycles: Union[Sequence, int, None] = None,
|
|
1109
|
+
step_txt: Optional[str] = None,
|
|
1110
|
+
*,
|
|
1111
|
+
nom_cap: Optional[float] = None,
|
|
1112
|
+
) -> Data:
|
|
1113
|
+
"""Add capacity throughput and equivalent full cycles (EFC) to the summary.
|
|
1114
|
+
|
|
1115
|
+
Polars-native (issue #138):
|
|
1116
|
+
|
|
1117
|
+
- ``test_cumulated_capacity_throughput = test_cumulated_charge_capacity +
|
|
1118
|
+
test_cumulated_discharge_capacity``
|
|
1119
|
+
- ``equivalent_full_cycles = test_cumulated_capacity_throughput / (2 * nom_cap_abs)``
|
|
1120
|
+
|
|
1121
|
+
The throughput is capacity-based (same unit as the summary capacities, so
|
|
1122
|
+
``nom_cap_abs`` must be in that unit); for raw current-integration on
|
|
1123
|
+
time-series data without cycles, see ``throughput_to_raw``.
|
|
1124
|
+
|
|
1125
|
+
Args:
|
|
1126
|
+
data (Data): The data object.
|
|
1127
|
+
schema: The column-header schema to use. Defaults to the native
|
|
1128
|
+
cellpy-core schema when not provided.
|
|
1129
|
+
nom_cap_abs (float): The absolute nominal capacity (default: 1.0).
|
|
1130
|
+
normalization_cycles (Union[Sequence, int, None]): The cycles for
|
|
1131
|
+
normalization; when given, ``nom_cap_abs`` is derived from them.
|
|
1132
|
+
step_txt (str): The summary capacity column used to derive
|
|
1133
|
+
``nom_cap_abs`` from ``normalization_cycles`` (defaults to the
|
|
1134
|
+
native cycle charge-capacity column).
|
|
1135
|
+
nom_cap (float): Deprecated alias for ``nom_cap_abs``.
|
|
1136
|
+
|
|
1137
|
+
Returns:
|
|
1138
|
+
Data: The data object with the two columns added to the summary.
|
|
1139
|
+
"""
|
|
1140
|
+
nom_cap_abs = _resolve_nom_cap_abs(nom_cap_abs, nom_cap)
|
|
1141
|
+
if schema is None:
|
|
1142
|
+
schema = default_schema()
|
|
1143
|
+
headers_summary = schema.cycle
|
|
1144
|
+
|
|
1145
|
+
if step_txt is None:
|
|
1146
|
+
step_txt = headers_summary.charge_capacity
|
|
1147
|
+
|
|
1148
|
+
# The engine is polars-native; accept a pandas frame for convenience.
|
|
1149
|
+
summary = data.summary
|
|
1150
|
+
if not isinstance(summary, pl.DataFrame):
|
|
1151
|
+
summary = pl.from_pandas(summary)
|
|
1152
|
+
|
|
1153
|
+
if normalization_cycles is not None:
|
|
1154
|
+
nom_cap_abs = _calculate_nominal_capacity_from_cycles(
|
|
1155
|
+
summary, schema, normalization_cycles, step_txt
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
data.summary = summary.with_columns(
|
|
1159
|
+
(
|
|
1160
|
+
pl.col(headers_summary.test_cumulated_charge_capacity)
|
|
1161
|
+
+ pl.col(headers_summary.test_cumulated_discharge_capacity)
|
|
1162
|
+
).alias(headers_summary.test_cumulated_capacity_throughput)
|
|
1163
|
+
).with_columns(
|
|
1164
|
+
(
|
|
1165
|
+
pl.col(headers_summary.test_cumulated_capacity_throughput)
|
|
1166
|
+
/ (2.0 * nom_cap_abs)
|
|
1167
|
+
).alias(headers_summary.equivalent_full_cycles)
|
|
1168
|
+
)
|
|
1169
|
+
return data
|
|
1170
|
+
|
|
1171
|
+
|
|
1004
1172
|
def c_rates_to_summary(
|
|
1005
1173
|
data: Data,
|
|
1006
1174
|
schema: Optional[Schema] = None,
|
|
@@ -7,6 +7,7 @@ from cellpycore.units.converters import (
|
|
|
7
7
|
calculate_scaler,
|
|
8
8
|
calculate_specific_conversion_factors,
|
|
9
9
|
calculate_specific_converters,
|
|
10
|
+
calculate_throughput_conversion_factor,
|
|
10
11
|
convert_value,
|
|
11
12
|
get_cellpy_units,
|
|
12
13
|
get_converter_to_specific,
|
|
@@ -24,6 +25,7 @@ __all__ = [
|
|
|
24
25
|
"calculate_scaler",
|
|
25
26
|
"calculate_specific_conversion_factors",
|
|
26
27
|
"calculate_specific_converters",
|
|
28
|
+
"calculate_throughput_conversion_factor",
|
|
27
29
|
"convert_value",
|
|
28
30
|
"get_cellpy_units",
|
|
29
31
|
"get_converter_to_specific",
|
|
@@ -559,6 +559,36 @@ def calculate_current_conversion_factor(
|
|
|
559
559
|
return factor.to_reduced_units().m
|
|
560
560
|
|
|
561
561
|
|
|
562
|
+
def calculate_throughput_conversion_factor(
|
|
563
|
+
raw_current_unit: str,
|
|
564
|
+
raw_time_unit: str,
|
|
565
|
+
*,
|
|
566
|
+
to_units: Optional[CellpyUnits] = None,
|
|
567
|
+
) -> float:
|
|
568
|
+
"""Compute the factor converting ``current * time`` to the charge unit.
|
|
569
|
+
|
|
570
|
+
The returned float is suitable for ``conversion_factor`` in
|
|
571
|
+
``summarizers.throughput_to_raw``: the raw current integral is in
|
|
572
|
+
``current_unit * time_unit`` (e.g. A·s) and the nominal capacity it is
|
|
573
|
+
divided by is a charge (e.g. mAh), so the integral needs scaling first
|
|
574
|
+
(issue #138).
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
raw_current_unit: Pint-compatible unit string for the raw current
|
|
578
|
+
column (e.g. ``"A"``).
|
|
579
|
+
raw_time_unit: Pint-compatible unit string for the raw test-time
|
|
580
|
+
column (e.g. ``"s"``).
|
|
581
|
+
to_units: Output unit spec; defaults to ``CellpyUnits()``.
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
Dimensionless conversion factor (raw current·time → output charge).
|
|
585
|
+
"""
|
|
586
|
+
output_units = to_units or get_cellpy_units()
|
|
587
|
+
return calculate_scaler(
|
|
588
|
+
f"{raw_current_unit}*{raw_time_unit}", output_units["charge"]
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
|
|
562
592
|
# Per-property aliases for spec labels whose bare string means something else
|
|
563
593
|
# (or nothing) to pint. The spec keeps the legacy label; conversion/validation
|
|
564
594
|
# uses the pint-parsable form. Decision recorded on issue #115:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/02-partly-solved-issues/issue100_original.md
RENAMED
|
File without changes
|
|
File without changes
|
{cellpycore-0.2.3 → cellpycore-0.2.4}/.issueflows/02-partly-solved-issues/issue100_status.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|