cellpycore 0.2.5__tar.gz → 0.2.6__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.6/.issueflows/03-solved-issues/issue147_original.md +15 -0
- cellpycore-0.2.6/.issueflows/03-solved-issues/issue147_plan.md +99 -0
- cellpycore-0.2.6/.issueflows/03-solved-issues/issue147_status.md +24 -0
- cellpycore-0.2.6/.issueflows/03-solved-issues/issue148_original.md +15 -0
- cellpycore-0.2.6/.issueflows/03-solved-issues/issue148_plan.md +144 -0
- cellpycore-0.2.6/.issueflows/03-solved-issues/issue148_status.md +27 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/HISTORY.md +11 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/PKG-INFO +1 -1
- {cellpycore-0.2.5 → cellpycore-0.2.6}/pyproject.toml +1 -1
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/cell_core.py +11 -3
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/merge.py +37 -9
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/summarizers.py +18 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/uv.lock +2 -2
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.aliases +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/cellpy-core-migration.mdc +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/graphify.mdc +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/issueflow-rules.mdc +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/kiss.mdc +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/this-project.mdc +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/caveman/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/grill-me/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-archive/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-cleanup/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-close/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-comments/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-cycle/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-epic/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-fix/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-graphify/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-history-update/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-init/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-pause/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-pick/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-plan/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-start/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-status/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-version-bump/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-yolo/SKILL.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.gitattributes +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.gitignore +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/00-tools/.gitkeep +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/00-tools/README.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/01-current-issues/.gitkeep +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/02-partly-solved-issues/.gitkeep +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/.gitkeep +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue100_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue100_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue100_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue10_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue10_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue10_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue110_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue110_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue110_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue112_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue112_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue112_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue114_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue114_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue114_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue115_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue115_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue115_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue116_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue116_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue116_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue117_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue117_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue117_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue118_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue118_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue118_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue121_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue121_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue121_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue12_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue12_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue12_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue136_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue136_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue136_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue138_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue138_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue138_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue139_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue139_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue139_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue13_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue13_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue13_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue142_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue142_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue142_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue21_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue21_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue21_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue22_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue22_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue22_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue23_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue23_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue23_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue24_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue24_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue29_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue29_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue29_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue30_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue30_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue30_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue32_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue32_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue32_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue34_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue34_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue36_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue36_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue39_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue39_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue39_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue40_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue40_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue40_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue41_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue41_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue41_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue42_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue42_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue42_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue43_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue43_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue43_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue44_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue44_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue45_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue45_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue45_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue50_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue50_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue50_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue54_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue54_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue54_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue55_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue55_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue55_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue56_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue56_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue56_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue62_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue62_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue62_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue64_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue64_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue64_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue66_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue66_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue66_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue67_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue67_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue67_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue68_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue68_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue68_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue69_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue69_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue69_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue70_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue70_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue70_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue72_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue72_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue77_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue77_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue77_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue82_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue82_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue84_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue84_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue84_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue86_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue86_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue86_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue91_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue91_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue91_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue95_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue95_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue95_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue96_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue96_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue96_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue97_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue97_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue97_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue98_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue98_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue98_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue99_original.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue99_plan.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue99_status.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/.gitkeep +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/bdf-io-placement.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/cellpy-core-integration-into-cellpy.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/cellpy-core-integration-roadmap.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/cellpy-core-migration.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/code-review-2026-07.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/column-headers-review.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/legacy-bridge-test-id.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/legacy-raw-energy-mapping.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/metadata-scaffolding.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/nom-cap-naming.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/rawcols-dtype-map.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/release-procedure.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/selector-dead-code-deferral.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/step-c-rate-split.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/step-table-polars-migration.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/summary-extractors.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/test-data-and-fixtures.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/test-metadata-and-merging.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/this-project.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/zensical-docs.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/05-epics/.gitkeep +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/config.toml +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.pre-commit-config.yaml +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.python-version +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.readthedocs.yaml +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/.vscode/settings.json +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/AGENTS.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/LICENSE +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/README.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/ROADMAP.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/SCRATCHPAD.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/.graphify_labels.json +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/.graphify_root +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/GRAPH_REPORT.md +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/graph.html +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/graph.json +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/manifest.json +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/__init__.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/config.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/curves.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/exceptions.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/extractors.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/__init__.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/headers.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/limits.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/mapping.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/meta.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/meta_mapping.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/mock_core.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/selectors.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/metadata/__init__.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/metadata/io.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/metadata/models.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/py.typed +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/settings_base.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/testing/__init__.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/testing/mock_data.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/timestamps.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/units/__init__.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/units/converters.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/units/spec.py +0 -0
- {cellpycore-0.2.5 → cellpycore-0.2.6}/zensical.toml +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Issue #147: update_core_data: empty new_raw with refresh_derived duplicates c-rate columns
|
|
2
|
+
|
|
3
|
+
Source: https://github.com/cellpy/cellpy-core/issues/147
|
|
4
|
+
|
|
5
|
+
## Original issue text
|
|
6
|
+
|
|
7
|
+
Found by the cellpy L6 golden-equality oracle (jepegit/cellpy#778).
|
|
8
|
+
|
|
9
|
+
`CellpyCellCore.update_core_data(data, new_raw=<empty frame>)` short-circuits inside `merge.update_data` (returns `_copy_data(data)`), but `refresh_derived=True` (default) then runs `summarizers.c_rates_to_summary` on a summary that already carries `charge_c_rate` / `discharge_c_rate`. The join adds `charge_c_rate_right` / `discharge_c_rate_right`.
|
|
10
|
+
|
|
11
|
+
Expected: an empty `new_raw` is a no-op (frames identical to the input).
|
|
12
|
+
|
|
13
|
+
Repro (cellpy 2.1.4 / cellpycore 0.2.5): `tests/test_incremental_update.py::test_empty_tail_is_noop` in jepegit/cellpy (strict xfail until fixed).
|
|
14
|
+
|
|
15
|
+
Suggested fix: skip the `refresh_derived` block when `new_raw` is empty, or make `c_rates_to_summary` drop existing c-rate columns before joining (idempotent).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Issue #147 — plan
|
|
2
|
+
|
|
3
|
+
Source: https://github.com/cellpy/cellpy-core/issues/147
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
`CellpyCellCore.update_core_data(data, new_raw=<empty>)` must be a true no-op:
|
|
8
|
+
returned frames identical to the input (no `charge_c_rate_right` /
|
|
9
|
+
`discharge_c_rate_right` / `ir_*_right` columns).
|
|
10
|
+
|
|
11
|
+
## Constraints
|
|
12
|
+
|
|
13
|
+
- Polars-native engine; accept pandas frames for convenience (existing pattern).
|
|
14
|
+
- Input `Data` must not be mutated (`update_data` contract; keep the `_copy_data` return).
|
|
15
|
+
- Non-empty `new_raw` path unchanged (oracle tests in `tests/test_merge.py` stay green).
|
|
16
|
+
- Google-style docstrings; `ruff check` + `ruff format --check` clean.
|
|
17
|
+
- Downstream: cellpy `tests/test_incremental_update.py::test_empty_tail_is_noop` is
|
|
18
|
+
`xfail(strict=True)` against this issue. Once cellpy bumps its `cellpycore` pin to a
|
|
19
|
+
release containing this fix, that xfail marker must be removed (cellpy follow-up, out
|
|
20
|
+
of scope here — note in PR body).
|
|
21
|
+
|
|
22
|
+
### Prior art
|
|
23
|
+
|
|
24
|
+
- `merge.update_data` (`src/cellpycore/merge.py:221`): already short-circuits on
|
|
25
|
+
`new_raw is None or _frame_is_empty(new_raw)` → `_copy_data(data)`. The bug is that
|
|
26
|
+
`update_core_data` does not honour that short-circuit before `refresh_derived`.
|
|
27
|
+
- `merge._frame_is_empty` (`merge.py:389`): polars/pandas-agnostic emptiness check —
|
|
28
|
+
reuse it in `cell_core.py` instead of writing a new one.
|
|
29
|
+
- `summarizers.c_rates_to_summary` (`summarizers.py:1172`) and
|
|
30
|
+
`summarizers.ir_to_summary` (`summarizers.py:1274`): both do a bare
|
|
31
|
+
`summary.join(..., how="left")` and are therefore **not idempotent** — calling either
|
|
32
|
+
twice on the same summary yields `*_right` columns. `ir_to_summary` has the identical
|
|
33
|
+
latent defect (only masked because the issue repro has no IR column in raw).
|
|
34
|
+
- `tests/test_merge.py::test_update_core_data_refresh_derived` (line 336): existing
|
|
35
|
+
test for the derived-refresh path; new tests go beside it, reusing `_single_test_raw`,
|
|
36
|
+
`_process_raw`, `_schema` helpers.
|
|
37
|
+
- Toolbox (`.issueflows/00-tools/`) empty; graphify report not needed (three
|
|
38
|
+
known call sites, all located by grep).
|
|
39
|
+
|
|
40
|
+
## Approach
|
|
41
|
+
|
|
42
|
+
Two small changes — a direct no-op fix plus idempotency hardening of the two
|
|
43
|
+
summarizers so the same class of bug cannot recur from another caller.
|
|
44
|
+
|
|
45
|
+
1. **`update_core_data`: return early on empty `new_raw`.**
|
|
46
|
+
After the pandas→polars coercion, if `_frame_is_empty(new_raw)` (or `new_raw is None`),
|
|
47
|
+
return `update_data(...)`'s result directly (it already returns `_copy_data(data)`)
|
|
48
|
+
and skip the whole `refresh_derived` block. Cheapest, and matches the documented
|
|
49
|
+
"empty tail is a no-op" expectation. Docstring: add one sentence stating that an
|
|
50
|
+
empty `new_raw` returns an unmodified copy and skips derived refresh.
|
|
51
|
+
|
|
52
|
+
2. **Make `c_rates_to_summary` and `ir_to_summary` idempotent.**
|
|
53
|
+
Before the join, drop the target columns if already present:
|
|
54
|
+
`summary = summary.drop([c for c in (charge_c_rate, discharge_c_rate) if c in summary.columns])`
|
|
55
|
+
(same for `ir_charge` / `ir_discharge`). Re-running then overwrites instead of
|
|
56
|
+
producing `*_right`. This is the "either" alternative from the issue; doing both
|
|
57
|
+
keeps the no-op guarantee cheap (1) and removes the footgun for any other caller (2).
|
|
58
|
+
|
|
59
|
+
Order: 1 → 2 → tests → ruff.
|
|
60
|
+
|
|
61
|
+
## Files to touch
|
|
62
|
+
|
|
63
|
+
- `src/cellpycore/cell_core.py` — `update_core_data`: early return on empty `new_raw`
|
|
64
|
+
(import `_frame_is_empty` alongside `update_data` from `cellpycore.merge`); docstring note.
|
|
65
|
+
- `src/cellpycore/summarizers.py` — `c_rates_to_summary`, `ir_to_summary`: drop
|
|
66
|
+
pre-existing target columns before the join; one docstring line each ("idempotent:
|
|
67
|
+
existing columns are replaced").
|
|
68
|
+
- `tests/test_merge.py` — new tests (see below).
|
|
69
|
+
- `CHANGELOG.md` (if present — check at close) — fix entry.
|
|
70
|
+
|
|
71
|
+
## Test strategy
|
|
72
|
+
|
|
73
|
+
Run: `uv run pytest` then `uv run ruff check && uv run ruff format --check`.
|
|
74
|
+
|
|
75
|
+
New tests in `tests/test_merge.py`:
|
|
76
|
+
|
|
77
|
+
- `test_update_core_data_empty_new_raw_is_noop` — build `base` via `_process_raw`, run
|
|
78
|
+
`cell.update_core_data(base, extension.clear())` (polars empty frame with same
|
|
79
|
+
schema) with default `refresh_derived=True`; assert `updated.summary.columns ==
|
|
80
|
+
base.summary.columns`, no column ends with `_right`, and `raw`/`steps`/`summary`
|
|
81
|
+
`.equals()` the input. Also assert input `base` untouched.
|
|
82
|
+
- `test_update_core_data_empty_pandas_new_raw_is_noop` — same with
|
|
83
|
+
`extension.to_pandas().iloc[0:0]` (the cellpy repro shape).
|
|
84
|
+
- `test_c_rates_to_summary_idempotent` — call twice on processed data; columns
|
|
85
|
+
unchanged after second call, values equal.
|
|
86
|
+
- `test_ir_to_summary_idempotent` — same for IR (needs raw with
|
|
87
|
+
`internal_resistance` column; check `_single_test_raw` provides it, else add it).
|
|
88
|
+
|
|
89
|
+
Existing oracle tests (`test_update_overlap_matches_full_recompute_oracle`,
|
|
90
|
+
`test_update_gap_append_matches_full_recompute_oracle`,
|
|
91
|
+
`test_update_core_data_refresh_derived`, `tests/test_e2e.py`) guard the non-empty path.
|
|
92
|
+
|
|
93
|
+
## Open questions
|
|
94
|
+
|
|
95
|
+
- Scope of step 2 (idempotency hardening of both summarizers): include it, or ship only
|
|
96
|
+
the early return (step 1)? Recommendation: include — two-line change each, closes the
|
|
97
|
+
same defect in `ir_to_summary`, and the issue lists it as an acceptable fix.
|
|
98
|
+
- Version bump at close: `patch` (bug fix, no API change). Cellpy needs a new
|
|
99
|
+
`cellpycore` release to drop its xfail.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Issue #147 — status
|
|
2
|
+
|
|
3
|
+
- [x] Done
|
|
4
|
+
|
|
5
|
+
## What's done
|
|
6
|
+
|
|
7
|
+
- Plan accepted (2026-09-08): early return in `update_core_data` on empty `new_raw`
|
|
8
|
+
+ idempotent `c_rates_to_summary` / `ir_to_summary`.
|
|
9
|
+
- `cell_core.update_core_data`: returns the `update_data` copy directly when `new_raw`
|
|
10
|
+
is `None` or empty (skips `refresh_derived`); docstring updated.
|
|
11
|
+
- `summarizers.c_rates_to_summary` / `ir_to_summary`: drop pre-existing target columns
|
|
12
|
+
before the join via new `_drop_existing` helper → idempotent; docstrings updated.
|
|
13
|
+
- Tests (`tests/test_merge.py`): `test_update_core_data_empty_new_raw_is_noop`,
|
|
14
|
+
`test_update_core_data_empty_pandas_new_raw_is_noop`,
|
|
15
|
+
`test_c_rates_to_summary_idempotent`, `test_ir_to_summary_idempotent`.
|
|
16
|
+
Verified all 4 fail on `main` code, pass with fix.
|
|
17
|
+
- Full suite: 285 passed. `ruff check` + `ruff format --check` clean.
|
|
18
|
+
- `HISTORY.md` bullet under `[Unreleased]`. No version bump (0.2.5 already pending release).
|
|
19
|
+
|
|
20
|
+
## Remaining work
|
|
21
|
+
|
|
22
|
+
- None in this repo.
|
|
23
|
+
- Follow-up (cellpy repo, after `cellpycore` pin bump to a release with this fix): remove
|
|
24
|
+
strict `xfail` on `tests/test_incremental_update.py::test_empty_tail_is_noop`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Issue #148: update_data: gap-append keeps the partial trailing step (step spanning the cut is split)
|
|
2
|
+
|
|
3
|
+
Source: https://github.com/cellpy/cellpy-core/issues/148
|
|
4
|
+
|
|
5
|
+
## Original issue text
|
|
6
|
+
|
|
7
|
+
Found by the cellpy L6 golden-equality oracle (jepegit/cellpy#778).
|
|
8
|
+
|
|
9
|
+
When `new_raw` starts strictly after the existing `datapoint_num` max (`gap_append=True`), `merge.update_data` keeps *all* existing step rows and rebuilds only from the first new datapoint. If the existing raw ends mid-step (the tester step continues in the new rows), that step ends up as two rows: the kept partial row and a new partial row. Downstream the per-cycle summary C-rate drifts (e.g. `discharge_c_rate` 248.6919 vs 248.68871 on `neware_uio.csv` cut at row 4000).
|
|
10
|
+
|
|
11
|
+
With any overlap (>= 1 already-seen row) `_trim_steps_for_overlap` rebuilds from the start of the spanning step and the result is identical to a full load.
|
|
12
|
+
|
|
13
|
+
Expected: gap-append should also trim the trailing step of `kept_steps` when the first new row continues the same (cycle, step) and rebuild from that step's `datapoint_num_first`; alternatively document that callers must re-read from the start of the last step.
|
|
14
|
+
|
|
15
|
+
Repro: `tests/test_incremental_update.py::test_gap_append_mid_step_equals_full_load` in jepegit/cellpy (strict xfail until fixed).
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Issue #148 — plan
|
|
2
|
+
|
|
3
|
+
Source: https://github.com/cellpy/cellpy-core/issues/148
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
`merge.update_data` gap-append (`new_raw` starts strictly after existing
|
|
8
|
+
`datapoint_num` / `source_datapoint_num` max) must not split a tester step that
|
|
9
|
+
continues across the cut. Resulting steps + summary match a full
|
|
10
|
+
`make_step_table` / `make_summary` on the concatenated raw (same contract as
|
|
11
|
+
the overlap path).
|
|
12
|
+
|
|
13
|
+
## Constraints
|
|
14
|
+
|
|
15
|
+
- Polars-native; do not mutate the input `Data` (`_copy_data` / new frames).
|
|
16
|
+
- Overlap path (`r2_start <= r1_max`) stays unchanged — already trims via
|
|
17
|
+
`_trim_steps_for_overlap` and the existing oracle stays green.
|
|
18
|
+
- Gap-append that starts on a **new** (cycle, step) stays as today: keep every
|
|
19
|
+
existing step row, rebuild from the first new `datapoint_num`.
|
|
20
|
+
- Single-`test_id` only (`update_data` v1). Match `(cycle_num, step_num)` as
|
|
21
|
+
the issue states; do not invent `sub_step_num` matching (engine still
|
|
22
|
+
hardcodes sub-step `1`).
|
|
23
|
+
- Google-style docstrings; `ruff check` + `ruff format --check` clean.
|
|
24
|
+
- Downstream: cellpy `tests/test_incremental_update.py::test_gap_append_mid_step_equals_full_load`
|
|
25
|
+
is `xfail(strict=True)` against this issue. Dropping that marker is a cellpy
|
|
26
|
+
follow-up after the next `cellpycore` pin bump — note in PR body, out of
|
|
27
|
+
scope here.
|
|
28
|
+
|
|
29
|
+
### Prior art
|
|
30
|
+
|
|
31
|
+
- `merge.update_data` (`src/cellpycore/merge.py`): `gap_append = r2_start > r1_max`
|
|
32
|
+
keeps **all** `steps_pl` and sets `from_data_point` to `min(new datapoint_num)`.
|
|
33
|
+
That is the bug. Overlap branch already calls `_trim_steps_for_overlap`.
|
|
34
|
+
- `_trim_steps_for_overlap` (`merge.py`): finds the step whose
|
|
35
|
+
`[datapoint_num_first, datapoint_num_last]` contains the first overlapping
|
|
36
|
+
datapoint, drops that step and later ones, returns `from_data_point` = that
|
|
37
|
+
step's first. On a **gap**, the first new datapoint is *after* every existing
|
|
38
|
+
`datapoint_num_last`, so this helper currently keeps all steps — it cannot
|
|
39
|
+
detect continuation by range alone.
|
|
40
|
+
- `summarizers.make_step_table(..., from_data_point=)` (`summarizers.py`):
|
|
41
|
+
filters `datapoint_num >= from_data_point` then `group_by(test_id, cycle_num,
|
|
42
|
+
step_num, sub)`. Rebuilding from the spanning step's first datapoint
|
|
43
|
+
concatenates the kept partial raw + new rows into one step group. That is
|
|
44
|
+
why overlap works and why gap-append must use the same `from_data_point`.
|
|
45
|
+
- Issue #86 original algorithm: *"If r2(0) >= r1(last): find what step r2(0)
|
|
46
|
+
is in. That step and all the ones after will then belong to R2."* Gap-append
|
|
47
|
+
skipped that "find the step" part; this issue completes it.
|
|
48
|
+
- `tests/test_merge.py::test_update_gap_append_matches_full_recompute_oracle`:
|
|
49
|
+
cut is at `source_datapoint_num == 20` (`_single_test_raw` n_cycles=3) —
|
|
50
|
+
**step boundary** (start of cycle 3 step 1), so it does not catch this bug.
|
|
51
|
+
New tests go beside it; reuse `_single_test_raw`, `_process_raw`,
|
|
52
|
+
`_steps_oracle_equal`, `_summary_oracle_equal`.
|
|
53
|
+
- `step-c-rate-split.md`: `update_data` still appends `c_rate` to rebuilt rows
|
|
54
|
+
when kept steps carry it — keep that. Split steps are why downstream
|
|
55
|
+
`discharge_c_rate` drifts.
|
|
56
|
+
- Toolbox (`.issueflows/00-tools/`) empty. Graphify community 76 is the
|
|
57
|
+
`merge.py` helper cluster (`_trim_steps_for_overlap`, `update_data`); no
|
|
58
|
+
extra helper to reuse.
|
|
59
|
+
|
|
60
|
+
## Approach
|
|
61
|
+
|
|
62
|
+
Implement the issue's primary expected behaviour (trim), not the
|
|
63
|
+
"document that callers must re-read" alternative.
|
|
64
|
+
|
|
65
|
+
1. **Continuation trim on gap-append.** After detecting `gap_append`, still
|
|
66
|
+
keep **all** existing raw (`kept_raw = raw_pl` — new rows do not repeat the
|
|
67
|
+
partial step). Trim steps: if the first new row's `(cycle_num, step_num)`
|
|
68
|
+
equals the **last** existing step's `(cycle_num, step_num)`, drop that last
|
|
69
|
+
step and set `from_data_point` to its `datapoint_num_first`. Else keep all
|
|
70
|
+
steps and `from_data_point = min(new datapoint_num)` (current behaviour).
|
|
71
|
+
|
|
72
|
+
Implement as a **continuation fallback inside** `_trim_steps_for_overlap`
|
|
73
|
+
(when `overlap_idx is None`), then call that helper from the gap-append
|
|
74
|
+
branch with `new_pl` as the "incoming" frame. One trim function, two
|
|
75
|
+
callers. Do not add a second helper unless the fallback makes the overlap
|
|
76
|
+
docstring dishonest — if so, a tiny `_trim_steps_for_gap_append` next to it
|
|
77
|
+
is the fallback (same predicate).
|
|
78
|
+
|
|
79
|
+
Predicate (last step vs first incoming row, after sort by
|
|
80
|
+
`datapoint_num` / `datapoint_num_first`):
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
last[shdr.cycle_num] == first_new[nhdr.cycle_num]
|
|
84
|
+
and last[shdr.step_num] == first_new[nhdr.step_num]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
2. **Do not trim `kept_raw` on gap.** Overlap drops existing rows with
|
|
88
|
+
`partition >= r2_start` because `new_raw` re-sends them. Gap does not.
|
|
89
|
+
`make_step_table(from_data_point=spanning_first)` then sees kept partial
|
|
90
|
+
points **plus** new points and rebuilds one step row.
|
|
91
|
+
|
|
92
|
+
3. **Docs.** One sentence on `update_data` and on `_trim_steps_for_overlap`:
|
|
93
|
+
gap-append that continues the trailing `(cycle, step)` rebuilds that step
|
|
94
|
+
from `datapoint_num_first`, same as overlap.
|
|
95
|
+
|
|
96
|
+
Order: helper + `update_data` call site → tests → ruff → `HISTORY.md` at
|
|
97
|
+
close.
|
|
98
|
+
|
|
99
|
+
## Files to touch
|
|
100
|
+
|
|
101
|
+
- `src/cellpycore/merge.py` — continuation fallback in
|
|
102
|
+
`_trim_steps_for_overlap` (or sibling helper); gap-append branch calls it
|
|
103
|
+
instead of keeping all steps; docstring.
|
|
104
|
+
- `tests/test_merge.py` — new oracle test(s) (see below).
|
|
105
|
+
- `HISTORY.md` — `[Unreleased]` fix bullet at close.
|
|
106
|
+
|
|
107
|
+
No `cell_core.py` change unless a docstring cross-ref is useful;
|
|
108
|
+
`update_core_data` already forwards to `update_data`.
|
|
109
|
+
|
|
110
|
+
## Test strategy
|
|
111
|
+
|
|
112
|
+
Run: `uv run pytest` then `uv run ruff check && uv run ruff format --check`.
|
|
113
|
+
|
|
114
|
+
New tests in `tests/test_merge.py`, same helpers as the existing update
|
|
115
|
+
oracles:
|
|
116
|
+
|
|
117
|
+
- `test_update_gap_append_mid_step_matches_full_recompute_oracle` — the
|
|
118
|
+
issue. `_single_test_raw(..., n_cycles=2)`: cycle 2 step 1 is datapoints
|
|
119
|
+
10–14. `base_raw` = `source_datapoint_num <= 12`, `extension` =
|
|
120
|
+
`source_datapoint_num >= 13` (`r2_start > r1_max` → gap). Assert
|
|
121
|
+
`_steps_oracle_equal` + `_summary_oracle_equal` vs `_process_raw(full_raw)`.
|
|
122
|
+
Also `updated.steps.height == oracle.steps.height` (no extra split row).
|
|
123
|
+
- `test_update_core_data_gap_append_mid_step_c_rate_matches_full` — same cut
|
|
124
|
+
through `CellpyCellCore.update_core_data` (default `refresh_derived=True`);
|
|
125
|
+
`discharge_c_rate` / `charge_c_rate` match a fully processed oracle
|
|
126
|
+
(`pytest.approx`). Guards the cellpy L6 symptom.
|
|
127
|
+
|
|
128
|
+
Existing `test_update_gap_append_matches_full_recompute_oracle` (step-boundary
|
|
129
|
+
gap) and `test_update_overlap_matches_full_recompute_oracle` stay green.
|
|
130
|
+
Optional e2e mid-step cut on the Arbin fixture is **out of scope** (unit
|
|
131
|
+
oracle is enough; fixture may be absent).
|
|
132
|
+
|
|
133
|
+
## Open questions
|
|
134
|
+
|
|
135
|
+
- Trim vs document-only (issue lists both)? **Recommend trim** — overlap
|
|
136
|
+
already does this; #86's update algorithm already said "find what step
|
|
137
|
+
r2(0) is in". Document-only would leave core wrong and keep the cellpy
|
|
138
|
+
xfail forever.
|
|
139
|
+
- Extend `_trim_steps_for_overlap` vs a new `_trim_steps_for_gap_append`?
|
|
140
|
+
**Recommend extend** (continuation fallback when the first incoming
|
|
141
|
+
datapoint is past every `datapoint_num_last`). New helper if that muddies
|
|
142
|
+
the overlap-only name.
|
|
143
|
+
- Version bump at close: **patch** (behaviour bug, no API change). Cellpy
|
|
144
|
+
needs a new `cellpycore` release to drop its xfail.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Issue #148 — status
|
|
2
|
+
|
|
3
|
+
- [x] Done
|
|
4
|
+
|
|
5
|
+
## What's done
|
|
6
|
+
|
|
7
|
+
- Plan accepted (2026-09-08): trim trailing step on gap-append continuation;
|
|
8
|
+
rebuild from that step's `datapoint_num_first`.
|
|
9
|
+
- `_trim_steps_for_overlap`: continuation fallback when the first incoming
|
|
10
|
+
datapoint is past every existing step but `(cycle_num, step_num)` matches
|
|
11
|
+
the last kept step.
|
|
12
|
+
- `update_data` gap-append branch now calls `_trim_steps_for_overlap` on
|
|
13
|
+
`new_pl`; post-offset `from_data_point` still follows a non-continuation
|
|
14
|
+
gap.
|
|
15
|
+
- Tests: `test_update_gap_append_mid_step_matches_full_recompute_oracle`,
|
|
16
|
+
`test_update_core_data_gap_append_mid_step_c_rate_matches_full`.
|
|
17
|
+
- `uv run pytest`: 287 passed, 3 deselected. `ruff check` + `ruff format --check`
|
|
18
|
+
clean.
|
|
19
|
+
- Version bump `0.2.5` → `0.2.6`. `HISTORY.md` promoted (`#147`/`#148` under
|
|
20
|
+
`[0.2.6]`; `#142` backfilled under `[0.2.5]` to match tag `v0.2.5`).
|
|
21
|
+
|
|
22
|
+
## Remaining work
|
|
23
|
+
|
|
24
|
+
- None in this repo.
|
|
25
|
+
- Follow-up (cellpy repo, after `cellpycore` pin bump to a release with this
|
|
26
|
+
fix): remove strict `xfail` on
|
|
27
|
+
`tests/test_incremental_update.py::test_gap_append_mid_step_equals_full_load`.
|
|
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.6] - 2026-09-08
|
|
8
|
+
|
|
9
|
+
- `update_core_data` treats an empty `new_raw` as a no-op (skips the derived refresh
|
|
10
|
+
instead of re-joining `*_c_rate` / `ir_*` columns as `*_right` duplicates);
|
|
11
|
+
`c_rates_to_summary` and `ir_to_summary` are now idempotent. (#147)
|
|
12
|
+
- `update_data` gap-append that continues the trailing `(cycle, step)` now
|
|
13
|
+
rebuilds that step from `datapoint_num_first` instead of splitting it into
|
|
14
|
+
two partial rows (summary C-rate matches a full load). (#148)
|
|
15
|
+
|
|
16
|
+
## [0.2.5] - 2026-09-08
|
|
17
|
+
|
|
7
18
|
- Harden `cycle_mode` unwrapping in `OldCellpyCellCore` / `_cycle_mode_to_test_mode`. (#142)
|
|
8
19
|
|
|
9
20
|
## [0.2.4] - 2026-07-23
|
|
@@ -552,16 +552,18 @@ class CellpyCellCore:
|
|
|
552
552
|
**kwargs: Forwarded to ``update_data`` / ``make_step_table``.
|
|
553
553
|
|
|
554
554
|
Returns:
|
|
555
|
-
A new updated ``Data`` object. The input is not modified.
|
|
555
|
+
A new updated ``Data`` object. The input is not modified. An empty
|
|
556
|
+
``new_raw`` is a no-op: an unmodified copy is returned and the
|
|
557
|
+
derived refresh is skipped.
|
|
556
558
|
"""
|
|
557
559
|
import polars as pl
|
|
558
560
|
|
|
559
|
-
from cellpycore.merge import update_data
|
|
561
|
+
from cellpycore.merge import _frame_is_empty, update_data
|
|
560
562
|
from cellpycore.summarizers import _resolve_nom_cap_abs
|
|
561
563
|
|
|
562
564
|
nom_cap_abs = _resolve_nom_cap_abs(nom_cap_abs, nom_cap)
|
|
563
565
|
|
|
564
|
-
if not isinstance(new_raw, pl.DataFrame):
|
|
566
|
+
if new_raw is not None and not isinstance(new_raw, pl.DataFrame):
|
|
565
567
|
new_raw = pl.from_pandas(new_raw)
|
|
566
568
|
|
|
567
569
|
test_mode = _cycle_mode_to_test_mode(self.cycle_mode)
|
|
@@ -574,6 +576,12 @@ class CellpyCellCore:
|
|
|
574
576
|
**kwargs,
|
|
575
577
|
)
|
|
576
578
|
|
|
579
|
+
if new_raw is None or _frame_is_empty(new_raw):
|
|
580
|
+
# update_data already returned an unmodified copy; the summary still
|
|
581
|
+
# carries the derived columns, so re-joining them would duplicate
|
|
582
|
+
# them as ``*_right`` (issue #147).
|
|
583
|
+
return out
|
|
584
|
+
|
|
577
585
|
if refresh_derived:
|
|
578
586
|
from cellpycore import summarizers
|
|
579
587
|
|
|
@@ -185,7 +185,9 @@ def update_data(
|
|
|
185
185
|
|
|
186
186
|
Trims the overlap at ``source_datapoint_num`` (or ``datapoint_num`` when
|
|
187
187
|
absent), refreshes affected step rows via ``make_step_table(from_data_point=…)``,
|
|
188
|
-
and rebuilds the per-cycle summary on the combined frames.
|
|
188
|
+
and rebuilds the per-cycle summary on the combined frames. Gap-append that
|
|
189
|
+
continues the trailing ``(cycle_num, step_num)`` rebuilds that step from
|
|
190
|
+
``datapoint_num_first`` (same contract as overlap).
|
|
189
191
|
|
|
190
192
|
Args:
|
|
191
193
|
data: Processed ``Data`` with ``raw``, ``steps``, and ``summary``.
|
|
@@ -250,7 +252,12 @@ def update_data(
|
|
|
250
252
|
|
|
251
253
|
if gap_append:
|
|
252
254
|
kept_raw = raw_pl
|
|
253
|
-
kept_steps =
|
|
255
|
+
kept_steps, from_data_point = _trim_steps_for_overlap(
|
|
256
|
+
steps_pl,
|
|
257
|
+
new_pl,
|
|
258
|
+
shdr,
|
|
259
|
+
nhdr,
|
|
260
|
+
)
|
|
254
261
|
else:
|
|
255
262
|
kept_raw = raw_pl.filter(pl.col(partition) < r2_start)
|
|
256
263
|
kept_steps, from_data_point = _trim_steps_for_overlap(
|
|
@@ -260,14 +267,16 @@ def update_data(
|
|
|
260
267
|
nhdr,
|
|
261
268
|
)
|
|
262
269
|
|
|
270
|
+
pre_offset_new_min = int(new_pl[nhdr.datapoint_num].min()) # type: ignore[arg-type]
|
|
263
271
|
dp_max_kept = _max_column(kept_raw, nhdr.datapoint_num)
|
|
264
|
-
|
|
265
|
-
if new_dp_min <= dp_max_kept:
|
|
272
|
+
if pre_offset_new_min <= dp_max_kept:
|
|
266
273
|
new_pl = _offset_int_column(new_pl, nhdr.datapoint_num, dp_max_kept)
|
|
267
274
|
|
|
268
275
|
combined_raw = pl.concat([kept_raw, new_pl], how="vertical")
|
|
269
276
|
|
|
270
|
-
|
|
277
|
+
# Non-continuation gap-append starts at the first new datapoint. If those
|
|
278
|
+
# rows were offset to avoid colliding with kept raw, follow the offset.
|
|
279
|
+
if gap_append and from_data_point == pre_offset_new_min:
|
|
271
280
|
from_data_point = int(new_pl[nhdr.datapoint_num].min()) # type: ignore[arg-type]
|
|
272
281
|
|
|
273
282
|
slice_data = Data()
|
|
@@ -341,13 +350,19 @@ def _trim_steps_for_overlap(
|
|
|
341
350
|
shdr: config.StepCols,
|
|
342
351
|
nhdr: config.RawCols,
|
|
343
352
|
) -> tuple[pl.DataFrame, int]:
|
|
344
|
-
"""Drop the
|
|
353
|
+
"""Drop the spanning step and later ones; return kept steps and ``from_data_point``.
|
|
354
|
+
|
|
355
|
+
The spanning step is the row whose ``[datapoint_num_first, datapoint_num_last]``
|
|
356
|
+
contains the first incoming datapoint. If that datapoint is past every
|
|
357
|
+
existing step (gap-append) but the first incoming row continues the last
|
|
358
|
+
step's ``(cycle_num, step_num)``, drop that trailing step and rebuild from
|
|
359
|
+
its ``datapoint_num_first``.
|
|
360
|
+
"""
|
|
345
361
|
if overlap_raw.is_empty():
|
|
346
362
|
return steps, int(steps[shdr.datapoint_num_first].min()) # type: ignore[arg-type]
|
|
347
363
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
)
|
|
364
|
+
incoming = overlap_raw.sort(nhdr.datapoint_num)
|
|
365
|
+
first_dp = int(incoming[nhdr.datapoint_num][0]) # type: ignore[index]
|
|
351
366
|
steps_sorted = steps.sort(shdr.datapoint_num_first)
|
|
352
367
|
overlap_idx = None
|
|
353
368
|
for idx, row in enumerate(steps_sorted.iter_rows(named=True)):
|
|
@@ -361,6 +376,19 @@ def _trim_steps_for_overlap(
|
|
|
361
376
|
if overlap_idx is None:
|
|
362
377
|
from_data_point = first_dp
|
|
363
378
|
kept = steps_sorted.filter(pl.col(shdr.datapoint_num_last) < first_dp)
|
|
379
|
+
if (
|
|
380
|
+
not kept.is_empty()
|
|
381
|
+
and nhdr.cycle_num in incoming.columns
|
|
382
|
+
and nhdr.step_num in incoming.columns
|
|
383
|
+
):
|
|
384
|
+
last_step = kept.tail(1).row(0, named=True)
|
|
385
|
+
first_new = incoming.row(0, named=True)
|
|
386
|
+
if (
|
|
387
|
+
last_step[shdr.cycle_num] == first_new[nhdr.cycle_num]
|
|
388
|
+
and last_step[shdr.step_num] == first_new[nhdr.step_num]
|
|
389
|
+
):
|
|
390
|
+
from_data_point = int(last_step[shdr.datapoint_num_first])
|
|
391
|
+
kept = kept.head(kept.height - 1)
|
|
364
392
|
return kept, from_data_point
|
|
365
393
|
|
|
366
394
|
kept = steps_sorted.head(overlap_idx)
|
|
@@ -1191,6 +1191,8 @@ def c_rates_to_summary(
|
|
|
1191
1191
|
and passes it in (default 1.0, i.e. no conversion), so this function does no
|
|
1192
1192
|
unit handling itself.
|
|
1193
1193
|
|
|
1194
|
+
Idempotent: C-rate columns already present in the summary are replaced.
|
|
1195
|
+
|
|
1194
1196
|
Args:
|
|
1195
1197
|
data (core.Data): The data object (needs ``summary`` and ``steps``).
|
|
1196
1198
|
schema: The column-header schema to use. Defaults to the native
|
|
@@ -1265,6 +1267,10 @@ def c_rates_to_summary(
|
|
|
1265
1267
|
charge = _first_rate("charge", headers_summary.charge_c_rate)
|
|
1266
1268
|
discharge = _first_rate("discharge", headers_summary.discharge_c_rate)
|
|
1267
1269
|
|
|
1270
|
+
# Idempotent: replace existing C-rate columns instead of joining ``*_right``.
|
|
1271
|
+
summary = _drop_existing(
|
|
1272
|
+
summary, headers_summary.charge_c_rate, headers_summary.discharge_c_rate
|
|
1273
|
+
)
|
|
1268
1274
|
summary = summary.join(charge, left_on=left_on, right_on=right_on, how="left")
|
|
1269
1275
|
summary = summary.join(discharge, left_on=left_on, right_on=right_on, how="left")
|
|
1270
1276
|
data.summary = summary
|
|
@@ -1285,6 +1291,8 @@ def ir_to_summary(
|
|
|
1285
1291
|
#23, fixing the legacy off-by-one attribution). Cycles for which the extractor
|
|
1286
1292
|
yields no value (for example a cycle with no charge step) get ``NaN``.
|
|
1287
1293
|
|
|
1294
|
+
Idempotent: IR columns already present in the summary are replaced.
|
|
1295
|
+
|
|
1288
1296
|
Args:
|
|
1289
1297
|
data (Data): The data object (needs ``summary``, ``raw`` and ``steps``).
|
|
1290
1298
|
schema: The column-header schema to use. Defaults to the native
|
|
@@ -1322,6 +1330,10 @@ def ir_to_summary(
|
|
|
1322
1330
|
and headers_summary.test_id in summary.columns
|
|
1323
1331
|
else headers_summary.cycle_num
|
|
1324
1332
|
)
|
|
1333
|
+
# Idempotent: replace existing IR columns instead of joining ``*_right``.
|
|
1334
|
+
summary = _drop_existing(
|
|
1335
|
+
summary, headers_summary.ir_charge, headers_summary.ir_discharge
|
|
1336
|
+
)
|
|
1325
1337
|
summary = summary.join(per_cycle, on=join_on, how="left")
|
|
1326
1338
|
# Missing IR (e.g. a cycle without a charge/discharge step) stays NaN rather
|
|
1327
1339
|
# than the legacy 0.0, so "no measurement" is distinguishable from a real 0.
|
|
@@ -1333,6 +1345,12 @@ def ir_to_summary(
|
|
|
1333
1345
|
return data
|
|
1334
1346
|
|
|
1335
1347
|
|
|
1348
|
+
def _drop_existing(frame: pl.DataFrame, *columns: str) -> pl.DataFrame:
|
|
1349
|
+
"""Drop ``columns`` that are present in ``frame`` (missing ones are ignored)."""
|
|
1350
|
+
present = [c for c in columns if c in frame.columns]
|
|
1351
|
+
return frame.drop(present) if present else frame
|
|
1352
|
+
|
|
1353
|
+
|
|
1336
1354
|
def _main():
|
|
1337
1355
|
print("summarizers.py - no main function yet")
|
|
1338
1356
|
|
|
@@ -73,7 +73,7 @@ css = [
|
|
|
73
73
|
|
|
74
74
|
[[package]]
|
|
75
75
|
name = "cellpycore"
|
|
76
|
-
version = "0.2.
|
|
76
|
+
version = "0.2.6"
|
|
77
77
|
source = { editable = "." }
|
|
78
78
|
dependencies = [
|
|
79
79
|
{ name = "pandas" },
|
|
@@ -138,7 +138,7 @@ name = "cffi"
|
|
|
138
138
|
version = "2.0.0"
|
|
139
139
|
source = { registry = "https://pypi.org/simple" }
|
|
140
140
|
dependencies = [
|
|
141
|
-
{ name = "pycparser"
|
|
141
|
+
{ name = "pycparser" },
|
|
142
142
|
]
|
|
143
143
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
|
144
144
|
wheels = [
|
|
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
|
|
File without changes
|
|
File without changes
|