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.
Files changed (265) hide show
  1. cellpycore-0.2.6/.issueflows/03-solved-issues/issue147_original.md +15 -0
  2. cellpycore-0.2.6/.issueflows/03-solved-issues/issue147_plan.md +99 -0
  3. cellpycore-0.2.6/.issueflows/03-solved-issues/issue147_status.md +24 -0
  4. cellpycore-0.2.6/.issueflows/03-solved-issues/issue148_original.md +15 -0
  5. cellpycore-0.2.6/.issueflows/03-solved-issues/issue148_plan.md +144 -0
  6. cellpycore-0.2.6/.issueflows/03-solved-issues/issue148_status.md +27 -0
  7. {cellpycore-0.2.5 → cellpycore-0.2.6}/HISTORY.md +11 -0
  8. {cellpycore-0.2.5 → cellpycore-0.2.6}/PKG-INFO +1 -1
  9. {cellpycore-0.2.5 → cellpycore-0.2.6}/pyproject.toml +1 -1
  10. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/cell_core.py +11 -3
  11. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/merge.py +37 -9
  12. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/summarizers.py +18 -0
  13. {cellpycore-0.2.5 → cellpycore-0.2.6}/uv.lock +2 -2
  14. {cellpycore-0.2.5 → cellpycore-0.2.6}/.aliases +0 -0
  15. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/cellpy-core-migration.mdc +0 -0
  16. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/graphify.mdc +0 -0
  17. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/issueflow-rules.mdc +0 -0
  18. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/kiss.mdc +0 -0
  19. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/rules/this-project.mdc +0 -0
  20. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/caveman/SKILL.md +0 -0
  21. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/grill-me/SKILL.md +0 -0
  22. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow/SKILL.md +0 -0
  23. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-archive/SKILL.md +0 -0
  24. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-cleanup/SKILL.md +0 -0
  25. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-close/SKILL.md +0 -0
  26. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-comments/SKILL.md +0 -0
  27. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-cycle/SKILL.md +0 -0
  28. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-epic/SKILL.md +0 -0
  29. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-fix/SKILL.md +0 -0
  30. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-graphify/SKILL.md +0 -0
  31. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-history-update/SKILL.md +0 -0
  32. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-init/SKILL.md +0 -0
  33. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-pause/SKILL.md +0 -0
  34. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-pick/SKILL.md +0 -0
  35. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-plan/SKILL.md +0 -0
  36. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-start/SKILL.md +0 -0
  37. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-status/SKILL.md +0 -0
  38. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-version-bump/SKILL.md +0 -0
  39. {cellpycore-0.2.5 → cellpycore-0.2.6}/.cursor/skills/iflow-yolo/SKILL.md +0 -0
  40. {cellpycore-0.2.5 → cellpycore-0.2.6}/.gitattributes +0 -0
  41. {cellpycore-0.2.5 → cellpycore-0.2.6}/.gitignore +0 -0
  42. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/00-tools/.gitkeep +0 -0
  43. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/00-tools/README.md +0 -0
  44. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/01-current-issues/.gitkeep +0 -0
  45. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/02-partly-solved-issues/.gitkeep +0 -0
  46. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/.gitkeep +0 -0
  47. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue100_original.md +0 -0
  48. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue100_plan.md +0 -0
  49. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue100_status.md +0 -0
  50. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue10_original.md +0 -0
  51. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue10_plan.md +0 -0
  52. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue10_status.md +0 -0
  53. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue110_original.md +0 -0
  54. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue110_plan.md +0 -0
  55. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue110_status.md +0 -0
  56. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue112_original.md +0 -0
  57. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue112_plan.md +0 -0
  58. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue112_status.md +0 -0
  59. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue114_original.md +0 -0
  60. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue114_plan.md +0 -0
  61. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue114_status.md +0 -0
  62. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue115_original.md +0 -0
  63. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue115_plan.md +0 -0
  64. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue115_status.md +0 -0
  65. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue116_original.md +0 -0
  66. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue116_plan.md +0 -0
  67. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue116_status.md +0 -0
  68. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue117_original.md +0 -0
  69. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue117_plan.md +0 -0
  70. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue117_status.md +0 -0
  71. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue118_original.md +0 -0
  72. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue118_plan.md +0 -0
  73. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue118_status.md +0 -0
  74. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue121_original.md +0 -0
  75. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue121_plan.md +0 -0
  76. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue121_status.md +0 -0
  77. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue12_original.md +0 -0
  78. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue12_plan.md +0 -0
  79. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue12_status.md +0 -0
  80. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue136_original.md +0 -0
  81. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue136_plan.md +0 -0
  82. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue136_status.md +0 -0
  83. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue138_original.md +0 -0
  84. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue138_plan.md +0 -0
  85. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue138_status.md +0 -0
  86. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue139_original.md +0 -0
  87. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue139_plan.md +0 -0
  88. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue139_status.md +0 -0
  89. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue13_original.md +0 -0
  90. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue13_plan.md +0 -0
  91. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue13_status.md +0 -0
  92. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue142_original.md +0 -0
  93. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue142_plan.md +0 -0
  94. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue142_status.md +0 -0
  95. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue21_original.md +0 -0
  96. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue21_plan.md +0 -0
  97. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue21_status.md +0 -0
  98. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue22_original.md +0 -0
  99. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue22_plan.md +0 -0
  100. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue22_status.md +0 -0
  101. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue23_original.md +0 -0
  102. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue23_plan.md +0 -0
  103. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue23_status.md +0 -0
  104. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue24_original.md +0 -0
  105. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue24_status.md +0 -0
  106. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue29_original.md +0 -0
  107. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue29_plan.md +0 -0
  108. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue29_status.md +0 -0
  109. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue30_original.md +0 -0
  110. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue30_plan.md +0 -0
  111. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue30_status.md +0 -0
  112. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue32_original.md +0 -0
  113. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue32_plan.md +0 -0
  114. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue32_status.md +0 -0
  115. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue34_original.md +0 -0
  116. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue34_plan.md +0 -0
  117. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue36_original.md +0 -0
  118. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue36_status.md +0 -0
  119. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue39_original.md +0 -0
  120. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue39_plan.md +0 -0
  121. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue39_status.md +0 -0
  122. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue40_original.md +0 -0
  123. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue40_plan.md +0 -0
  124. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue40_status.md +0 -0
  125. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue41_original.md +0 -0
  126. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue41_plan.md +0 -0
  127. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue41_status.md +0 -0
  128. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue42_original.md +0 -0
  129. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue42_plan.md +0 -0
  130. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue42_status.md +0 -0
  131. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue43_original.md +0 -0
  132. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue43_plan.md +0 -0
  133. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue43_status.md +0 -0
  134. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue44_original.md +0 -0
  135. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue44_status.md +0 -0
  136. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue45_original.md +0 -0
  137. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue45_plan.md +0 -0
  138. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue45_status.md +0 -0
  139. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue50_original.md +0 -0
  140. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue50_plan.md +0 -0
  141. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue50_status.md +0 -0
  142. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue54_original.md +0 -0
  143. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue54_plan.md +0 -0
  144. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue54_status.md +0 -0
  145. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue55_original.md +0 -0
  146. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue55_plan.md +0 -0
  147. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue55_status.md +0 -0
  148. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue56_original.md +0 -0
  149. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue56_plan.md +0 -0
  150. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue56_status.md +0 -0
  151. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue62_original.md +0 -0
  152. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue62_plan.md +0 -0
  153. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue62_status.md +0 -0
  154. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue64_original.md +0 -0
  155. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue64_plan.md +0 -0
  156. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue64_status.md +0 -0
  157. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue66_original.md +0 -0
  158. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue66_plan.md +0 -0
  159. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue66_status.md +0 -0
  160. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue67_original.md +0 -0
  161. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue67_plan.md +0 -0
  162. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue67_status.md +0 -0
  163. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue68_original.md +0 -0
  164. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue68_plan.md +0 -0
  165. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue68_status.md +0 -0
  166. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue69_original.md +0 -0
  167. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue69_plan.md +0 -0
  168. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue69_status.md +0 -0
  169. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue70_original.md +0 -0
  170. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue70_plan.md +0 -0
  171. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue70_status.md +0 -0
  172. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue72_original.md +0 -0
  173. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue72_status.md +0 -0
  174. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue77_original.md +0 -0
  175. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue77_plan.md +0 -0
  176. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue77_status.md +0 -0
  177. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue82_original.md +0 -0
  178. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue82_status.md +0 -0
  179. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue84_original.md +0 -0
  180. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue84_plan.md +0 -0
  181. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue84_status.md +0 -0
  182. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue86_original.md +0 -0
  183. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue86_plan.md +0 -0
  184. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue86_status.md +0 -0
  185. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue91_original.md +0 -0
  186. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue91_plan.md +0 -0
  187. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue91_status.md +0 -0
  188. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue95_original.md +0 -0
  189. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue95_plan.md +0 -0
  190. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue95_status.md +0 -0
  191. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue96_original.md +0 -0
  192. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue96_plan.md +0 -0
  193. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue96_status.md +0 -0
  194. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue97_original.md +0 -0
  195. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue97_plan.md +0 -0
  196. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue97_status.md +0 -0
  197. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue98_original.md +0 -0
  198. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue98_plan.md +0 -0
  199. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue98_status.md +0 -0
  200. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue99_original.md +0 -0
  201. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue99_plan.md +0 -0
  202. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/03-solved-issues/issue99_status.md +0 -0
  203. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/.gitkeep +0 -0
  204. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/bdf-io-placement.md +0 -0
  205. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/cellpy-core-integration-into-cellpy.md +0 -0
  206. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/cellpy-core-integration-roadmap.md +0 -0
  207. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/cellpy-core-migration.md +0 -0
  208. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/code-review-2026-07.md +0 -0
  209. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/column-headers-review.md +0 -0
  210. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/legacy-bridge-test-id.md +0 -0
  211. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/legacy-raw-energy-mapping.md +0 -0
  212. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/metadata-scaffolding.md +0 -0
  213. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/nom-cap-naming.md +0 -0
  214. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/rawcols-dtype-map.md +0 -0
  215. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/release-procedure.md +0 -0
  216. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/selector-dead-code-deferral.md +0 -0
  217. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/step-c-rate-split.md +0 -0
  218. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/step-table-polars-migration.md +0 -0
  219. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/summary-extractors.md +0 -0
  220. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/test-data-and-fixtures.md +0 -0
  221. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/test-metadata-and-merging.md +0 -0
  222. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/this-project.md +0 -0
  223. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/04-designs-and-guides/zensical-docs.md +0 -0
  224. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/05-epics/.gitkeep +0 -0
  225. {cellpycore-0.2.5 → cellpycore-0.2.6}/.issueflows/config.toml +0 -0
  226. {cellpycore-0.2.5 → cellpycore-0.2.6}/.pre-commit-config.yaml +0 -0
  227. {cellpycore-0.2.5 → cellpycore-0.2.6}/.python-version +0 -0
  228. {cellpycore-0.2.5 → cellpycore-0.2.6}/.readthedocs.yaml +0 -0
  229. {cellpycore-0.2.5 → cellpycore-0.2.6}/.vscode/settings.json +0 -0
  230. {cellpycore-0.2.5 → cellpycore-0.2.6}/AGENTS.md +0 -0
  231. {cellpycore-0.2.5 → cellpycore-0.2.6}/LICENSE +0 -0
  232. {cellpycore-0.2.5 → cellpycore-0.2.6}/README.md +0 -0
  233. {cellpycore-0.2.5 → cellpycore-0.2.6}/ROADMAP.md +0 -0
  234. {cellpycore-0.2.5 → cellpycore-0.2.6}/SCRATCHPAD.md +0 -0
  235. {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/.graphify_labels.json +0 -0
  236. {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/.graphify_root +0 -0
  237. {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/GRAPH_REPORT.md +0 -0
  238. {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/graph.html +0 -0
  239. {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/graph.json +0 -0
  240. {cellpycore-0.2.5 → cellpycore-0.2.6}/graphify-out/manifest.json +0 -0
  241. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/__init__.py +0 -0
  242. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/config.py +0 -0
  243. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/curves.py +0 -0
  244. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/exceptions.py +0 -0
  245. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/extractors.py +0 -0
  246. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/__init__.py +0 -0
  247. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/headers.py +0 -0
  248. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/limits.py +0 -0
  249. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/mapping.py +0 -0
  250. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/meta.py +0 -0
  251. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/meta_mapping.py +0 -0
  252. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/mock_core.py +0 -0
  253. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/legacy/selectors.py +0 -0
  254. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/metadata/__init__.py +0 -0
  255. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/metadata/io.py +0 -0
  256. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/metadata/models.py +0 -0
  257. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/py.typed +0 -0
  258. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/settings_base.py +0 -0
  259. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/testing/__init__.py +0 -0
  260. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/testing/mock_data.py +0 -0
  261. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/timestamps.py +0 -0
  262. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/units/__init__.py +0 -0
  263. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/units/converters.py +0 -0
  264. {cellpycore-0.2.5 → cellpycore-0.2.6}/src/cellpycore/units/spec.py +0 -0
  265. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cellpycore
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Core engine for cellpy: fast, thread-safe step and cycle summarization of battery-cycling raw data
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cellpycore"
3
- version = "0.2.5"
3
+ version = "0.2.6"
4
4
  license = "MIT"
5
5
  license-files = ["LICEN[CS]E*"]
6
6
  description = "Core engine for cellpy: fast, thread-safe step and cycle summarization of battery-cycling raw data"
@@ -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 = steps_pl
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
- new_dp_min = int(new_pl[nhdr.datapoint_num].min()) # type: ignore[arg-type]
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
- if gap_append:
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 overlap step and all later steps; return kept steps and ``from_data_point``."""
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
- first_dp = int(
349
- overlap_raw.sort(nhdr.datapoint_num)[nhdr.datapoint_num][0] # type: ignore[index]
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.5"
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", marker = "implementation_name != 'PyPy'" },
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