#41 Motor ZPP refactor - #42
Conversation
…IDE and follow the prettier rules for linting
There was a problem hiding this comment.
Apologies for so many changes. Most of them are formatting as I've been letting my IDE auto-format on save based on the prettier rules.
| const zpp = state.zpp.sort((a, b) => { | ||
| const aIndex = a === 'NA' ? -1 : SensoryLevels.indexOf(a.replace(/\*/, '') as SensoryLevel); | ||
| const bIndex = b === 'NA' ? -1 : SensoryLevels.indexOf(b.replace(/\*/, '') as SensoryLevel); | ||
| const zpp = [...state.zpp].sort((a, b) => { |
There was a problem hiding this comment.
Bug: mutation in sortMotorZPP
Array.prototype.sort() mutates the array. This breaks immutability and can cause subtle bugs.
Fix: Sort a copy of the array:
const zpp = [...state.zpp].sort((a, b) => {
| return /\d\*/.test(currentLevel.lightTouch) | ||
| || /\d\*/.test(currentLevel.pinPrick) | ||
| || (currentLevel.index <= lastLevelWithConsecutiveNormalValues.index && currentLevel.index >= firstLevelWithStar.index); | ||
| const isInStarRange = |
There was a problem hiding this comment.
The /\d\*/.test(...) checks were unreachable because the same condition was being returned earlier.
… improve redability and testability
…irst real step and simplify the loop
There was a problem hiding this comment.
Pull request overview
This pull request implements a comprehensive refactoring of the Motor ZPP (Zone of Partial Preservation) calculation module, addressing all 7 objectives outlined in issue #41. The refactoring improves code maintainability, readability, and testability while preserving all existing functionality.
Changes:
- Introduced custom error handling with
MotorZPPErrorclass and centralized error messages - Extracted regex patterns and magic values into a well-documented
PATTERNSconstant object - Decomposed large functions (
getLevelsRange,checkForSensoryFunction,checkForMotorFunction) into smaller, focused helper functions - Added
motorZPPStepsgenerator function for step-by-step execution visibility - Fixed mutation bug in
sortMotorZPPby using array spread operator - Added type aliases (
StepHandler) and helper functions (createStep,buildMotorZPPLevelName) to reduce code duplication - Updated development tooling (prettier, eslint) and removed unused test code
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/classification/zoneOfPartialPreservation/motorZPPErrors.ts |
New file introducing custom error class with typed error codes for better error handling |
src/classification/zoneOfPartialPreservation/motorZPP.ts |
Major refactoring: extracted constants, decomposed functions, added generator, improved maintainability |
src/classification/zoneOfPartialPreservation/motorZPP.spec.ts |
Updated tests for new error types and added comprehensive tests for motorZPPSteps generator |
src/classification/commonSpec.ts |
Added explicit void return types to helper functions |
src/classification/asiaImpairmentScale/asiaImpairmentScale.spec.ts |
Removed unused imports and variables |
package.json |
Updated eslint version and added prettier; modified test script for explicit file patterns |
package-lock.json |
Dependency updates reflecting package.json changes |
.prettierrc |
New prettier configuration file with singleQuote preference |
.eslintrc.json |
Reformatted for consistency and added output directories to ignorePatterns |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Closes #41
Issues and suggestions
1. Bug: mutation in sortMotorZPP
Array.prototype.sort() mutates the array. This breaks immutability and can cause subtle bugs.
Fix: Sort a copy of the array:
2.
hasStarOnCurrentOrAboveLevel– redundant logicSuggested refactor:
3. Extract regex and magic values
Patterns like
/(^2$)|(\*\*$)/, /(^5$)|(\*\*$)/, /^[1-5]/, /^(NT|[0-4])\*\*$/are hard to read and reuse.Suggestion: Move them to named constants or small helpers:
Then use
MOTOR_PATTERNS.NORMAL_MOTOR.test(...)etc. This improves readability and makes changes easier.4. Large step functions
checkForMotorFunctionandcheckForSensoryFunctionare long and branchy. Consider:Example for checkForMotorFunction:
5.
getLevelsRangecomplexitygetLevelsRangedoes several things in one loop:Suggestion: Split into smaller functions, e.g.:
buildLevelChain(side, top, bottom)findFirstLevelWithStar(levels)findLastLevelWithConsecutiveNormalValues(levels, bottom)Then compose them in getLevelsRange. This makes the logic easier to follow and test.
6. determineMotorZPP entry point
The initial step is synthetic and only used to bootstrap the loop. A clearer approach is to start from the first real step:
This removes the synthetic step and makes the flow more obvious.
7. Step iterator for step-by-step execution
To support stepping through the calculation, expose a generator or iterator:
Consumers can then iterate step by step or collect all steps.
8. Error handling
Errors like
'state.currentLevel is null. A SideLevel value is required.'are clear. Consider:9. Type for next
next: ((state: State) => Step) | nullis correct but a bit opaque. A type alias can make the API clearer: