Skip to content

Commit 096148c

Browse files
committed
Review feedback - Fixup
1 parent aae10ec commit 096148c

File tree

2 files changed

+83
-130
lines changed

2 files changed

+83
-130
lines changed

src/utils/common-expression-helpers.test.ts

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -130,38 +130,6 @@ describe('CommonExpressionHelpers', () => {
130130
});
131131
});
132132

133-
describe('isDateAfterOffset', () => {
134-
it('should return true if selected date is after base date plus offset in months', () => {
135-
const selectedDate = new Date('2022-01-01');
136-
const baseDate = new Date('2021-01-01');
137-
expect(helpers.isDateAfterOffset(selectedDate, baseDate, 6, 'months')).toBe(true);
138-
});
139-
140-
it('should return true if selected date is after base date plus offset in weeks', () => {
141-
const selectedDate = new Date('2021-02-08');
142-
const baseDate = new Date('2021-01-01');
143-
expect(helpers.isDateAfterOffset(selectedDate, baseDate, 5, 'weeks')).toBe(true);
144-
});
145-
146-
it('should return true if selected date is after base date plus offset in days', () => {
147-
const selectedDate = new Date('2021-01-31');
148-
const baseDate = new Date('2021-01-01');
149-
expect(helpers.isDateAfterOffset(selectedDate, baseDate, 30, 'days')).toBe(true);
150-
});
151-
152-
it('should return true if selected date is after base date plus offset in years', () => {
153-
const selectedDate = new Date('2022-01-01');
154-
const baseDate = new Date('2021-01-01');
155-
expect(helpers.isDateAfterOffset(selectedDate, baseDate, 1, 'years')).toBe(true);
156-
});
157-
158-
it('should return false if selected date is before base date plus offset', () => {
159-
const selectedDate = new Date('2021-06-01');
160-
const baseDate = new Date('2021-01-01');
161-
expect(helpers.isDateAfterOffset(selectedDate, baseDate, 1, 'years')).toBe(false);
162-
});
163-
});
164-
165133
describe('addWeeksToDate', () => {
166134
it('should add weeks to a date correctly', () => {
167135
const date = new Date('2021-01-01');
@@ -177,6 +145,27 @@ describe('CommonExpressionHelpers', () => {
177145
});
178146
});
179147

148+
describe('addDaysToDate', () => {
149+
it('should add days to a date correctly', () => {
150+
const date = new Date('2021-01-01');
151+
const result = helpers.addDaysToDate(date, 10);
152+
expect(result).toEqual(new Date('2021-01-11'));
153+
});
154+
155+
it('should not mutate the original date', () => {
156+
const date = new Date('2021-01-01');
157+
const originalTime = date.getTime();
158+
helpers.addDaysToDate(date, 10);
159+
expect(date.getTime()).toBe(originalTime);
160+
});
161+
162+
it('should handle negative days', () => {
163+
const date = new Date('2021-01-15');
164+
const result = helpers.addDaysToDate(date, -5);
165+
expect(result).toEqual(new Date('2021-01-10'));
166+
});
167+
});
168+
180169
describe('useFieldValue', () => {
181170
it('should return the field value if the key exists', () => {
182171
helpers.allFieldValues = { question1: 'value1' };
@@ -228,6 +217,23 @@ describe('CommonExpressionHelpers', () => {
228217
});
229218
});
230219

220+
describe('calcBSA', () => {
221+
it('should return the correct BSA value using Mosteller formula', () => {
222+
// BSA = sqrt((height * weight) / 3600)
223+
// For height=180cm, weight=75kg: sqrt((180 * 75) / 3600) = sqrt(3.75) ≈ 1.94
224+
const height = 180;
225+
const weight = 75;
226+
expect(helpers.calcBSA(height, weight)).toBeCloseTo(1.94, 2);
227+
});
228+
229+
it('should return null if height or weight is not provided', () => {
230+
expect(helpers.calcBSA(null, 75)).toBe(null);
231+
expect(helpers.calcBSA(180, null)).toBe(null);
232+
expect(helpers.calcBSA(0, 75)).toBe(null);
233+
expect(helpers.calcBSA(180, 0)).toBe(null);
234+
});
235+
});
236+
231237
describe('calcEDD', () => {
232238
it('should return the expected date of delivery', () => {
233239
const lmp = new Date('2021-01-01');
@@ -422,8 +428,8 @@ describe('CommonExpressionHelpers', () => {
422428
expect(helpers.calcTimeDifference(obsDate, 'y')).toBe(1);
423429
});
424430

425-
it('should return "0" if obsDate is not provided', () => {
426-
expect(helpers.calcTimeDifference(null, 'd')).toBe('0');
431+
it('should return 0 if obsDate is not provided', () => {
432+
expect(helpers.calcTimeDifference(null, 'd')).toBe(0);
427433
});
428434
});
429435

src/utils/common-expression-helpers.ts

Lines changed: 43 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import dayjs from 'dayjs';
2-
import duration from 'dayjs/plugin/duration';
32
import customParseFormat from 'dayjs/plugin/customParseFormat';
4-
dayjs.extend(duration);
53
dayjs.extend(customParseFormat);
64
import findIndex from 'lodash/findIndex';
75
import filter from 'lodash/filter';
@@ -42,11 +40,12 @@ export class CommonExpressionHelpers {
4240
measurementValue: number,
4341
): string | null => {
4442
if (!refSectionObject) {
43+
console.warn('Z-score calculation: No reference data object provided');
4544
return null;
4645
}
4746

48-
const refObjectValues = Object.keys(refSectionObject).map((key) => refSectionObject[key]);
4947
const refObjectKeys = Object.keys(refSectionObject);
48+
const refObjectValues = refObjectKeys.map((key) => refSectionObject[key]);
5049
const minimumValue = refObjectValues[1];
5150
const minReferencePoint: number[] = [];
5251

@@ -101,11 +100,8 @@ export class CommonExpressionHelpers {
101100
* @param format - Optional format string for parsing right date (defaults to 'YYYY-MM-DD')
102101
* @returns true if left is before right
103102
*/
104-
isDateBefore = (left: Date, right: string | Date, format?: string) => {
105-
let otherDate: any = right;
106-
if (typeof right == 'string') {
107-
otherDate = format ? dayjs(right, format, true).toDate() : dayjs(right, 'YYYY-MM-DD', true).toDate();
108-
}
103+
isDateBefore = (left: Date, right: string | Date, format?: string): boolean => {
104+
const otherDate: Date = right instanceof Date ? right : (format ? dayjs(right, format, true).toDate() : dayjs(right, 'YYYY-MM-DD', true).toDate());
109105
return left?.getTime() < otherDate.getTime();
110106
};
111107

@@ -117,7 +113,7 @@ export class CommonExpressionHelpers {
117113
* @param timePeriod - The time unit: 'days', 'weeks', 'months', or 'years'
118114
* @returns true if selectedDate >= (baseDate + duration)
119115
*/
120-
isDateAfter = (selectedDate: Date, baseDate: Date, duration: number, timePeriod: string) => {
116+
isDateAfter = (selectedDate: Date, baseDate: Date, duration: number, timePeriod: 'days' | 'weeks' | 'months' | 'years'): boolean => {
121117
const parsedBaseDate = dayjs(baseDate);
122118

123119
let calculatedDate: Date;
@@ -169,43 +165,10 @@ export class CommonExpressionHelpers {
169165
* @returns true if left is after right
170166
*/
171167
isDateAfterSimple = (left: Date, right: string | Date, format?: string): boolean => {
172-
let otherDate: Date = right instanceof Date ? right : (format ? dayjs(right, format, true).toDate() : dayjs(right, 'YYYY-MM-DD', true).toDate());
168+
const otherDate: Date = right instanceof Date ? right : (format ? dayjs(right, format, true).toDate() : dayjs(right, 'YYYY-MM-DD', true).toDate());
173169
return left?.getTime() > otherDate.getTime();
174170
};
175171

176-
/**
177-
* Checks if selectedDate is after baseDate plus an offset duration.
178-
* This is a more clearly named version of isDateAfter with the same functionality.
179-
* @param selectedDate - The date to check
180-
* @param baseDate - The base date to add the offset to
181-
* @param duration - The number of time units to add
182-
* @param timePeriod - The time unit ('days', 'weeks', 'months', or 'years')
183-
* @returns true if selectedDate is on or after the calculated date
184-
*/
185-
isDateAfterOffset = (selectedDate: Date, baseDate: Date, duration: number, timePeriod: 'days' | 'weeks' | 'months' | 'years'): boolean => {
186-
const parsedBaseDate = dayjs(baseDate);
187-
188-
let calculatedDate: Date;
189-
switch (timePeriod) {
190-
case 'months':
191-
calculatedDate = parsedBaseDate.add(duration, 'month').toDate();
192-
break;
193-
case 'weeks':
194-
calculatedDate = parsedBaseDate.add(duration, 'week').toDate();
195-
break;
196-
case 'days':
197-
calculatedDate = parsedBaseDate.add(duration, 'day').toDate();
198-
break;
199-
case 'years':
200-
calculatedDate = parsedBaseDate.add(duration, 'year').toDate();
201-
break;
202-
default:
203-
calculatedDate = new Date(0);
204-
}
205-
206-
return selectedDate.getTime() >= calculatedDate.getTime();
207-
};
208-
209172
/**
210173
* Retrieves the current value of another form field and registers a dependency.
211174
* When the referenced field changes, expressions using this helper will be re-evaluated.
@@ -320,12 +283,11 @@ export class CommonExpressionHelpers {
320283
* @param arvDispensedInDays - Number of days of ARV medication dispensed
321284
* @returns The next visit date (followupDate + arvDispensedInDays), or null if inputs are missing
322285
*/
323-
calcNextVisitDate = (followupDate, arvDispensedInDays) => {
324-
let resultNextVisitDate: Date;
286+
calcNextVisitDate = (followupDate: Date, arvDispensedInDays: number): Date | null => {
325287
if (followupDate && arvDispensedInDays) {
326-
resultNextVisitDate = new Date(followupDate.getTime() + arvDispensedInDays * 24 * 60 * 60 * 1000);
288+
return new Date(followupDate.getTime() + arvDispensedInDays * 24 * 60 * 60 * 1000);
327289
}
328-
return resultNextVisitDate ?? null;
290+
return null;
329291
};
330292

331293
/**
@@ -358,16 +320,10 @@ export class CommonExpressionHelpers {
358320
* @param dateValue - The reference date to calculate age at (defaults to today if not provided)
359321
* @returns Age in years (year difference only, not precise age)
360322
*/
361-
calcAgeBasedOnDate = (dateValue?: ConstructorParameters<typeof Date>[0] | null) => {
362-
let targetYear = null;
363-
if (dateValue) {
364-
targetYear = new Date(dateValue).getFullYear();
365-
} else {
366-
targetYear = new Date().getFullYear();
367-
}
368-
let birthDate = new Date(this.patient.birthDate).getFullYear();
369-
let calculatedYear = targetYear - birthDate;
370-
return calculatedYear;
323+
calcAgeBasedOnDate = (dateValue?: ConstructorParameters<typeof Date>[0] | null): number => {
324+
const targetYear = dateValue ? new Date(dateValue).getFullYear() : new Date().getFullYear();
325+
const birthYear = new Date(this.patient.birthDate).getFullYear();
326+
return targetYear - birthYear;
371327
};
372328

373329
/**
@@ -377,12 +333,11 @@ export class CommonExpressionHelpers {
377333
* @param weight - Weight in kilograms
378334
* @returns BSA in m² rounded to 2 decimal places, or null if inputs are missing
379335
*/
380-
calcBSA = (height: number, weight: number) => {
381-
let result: string;
382-
if (height && weight) {
383-
result = Math.sqrt((height * weight) / 3600).toFixed(2);
336+
calcBSA = (height: number, weight: number): number | null => {
337+
if (!height || !weight) {
338+
return null;
384339
}
385-
return result ? parseFloat(result) : null;
340+
return parseFloat(Math.sqrt((height * weight) / 3600).toFixed(2));
386341
};
387342

388343
/**
@@ -496,20 +451,14 @@ export class CommonExpressionHelpers {
496451

497452
/**
498453
* Calculates the gravida (total number of pregnancies) based on term pregnancies and abortions/miscarriages.
499-
*
500-
* @param {number|string} parityTerm - The number of term pregnancies.
501-
* @param {number|string} parityAbortion - The number of abortions (including miscarriages).
502-
* @returns {number} The total number of pregnancies (gravida).
503-
* @throws {Error} If either input is not a valid number.
504-
*
505-
* @example
506-
* const gravida = calcGravida(2, 1);
507-
* console.log(gravida); // Output: 3
454+
* @param parityTerm - The number of term pregnancies (can be number or numeric string)
455+
* @param parityAbortion - The number of abortions including miscarriages (can be number or numeric string)
456+
* @returns The total number of pregnancies (gravida)
457+
* @throws Error if either input is not a valid number
508458
*/
509-
510-
calcGravida = (parityTerm, parityAbortion) => {
511-
const term = parseInt(parityTerm, 10);
512-
const abortion = parseInt(parityAbortion, 10);
459+
calcGravida = (parityTerm: number | string, parityAbortion: number | string): number => {
460+
const term = typeof parityTerm === 'number' ? parityTerm : parseInt(parityTerm, 10);
461+
const abortion = typeof parityAbortion === 'number' ? parityAbortion : parseInt(parityAbortion, 10);
513462

514463
if (!Number.isInteger(term) || !Number.isInteger(abortion)) {
515464
throw new Error('Both inputs must be valid numbers.');
@@ -525,15 +474,15 @@ export class CommonExpressionHelpers {
525474
* @param weight - Patient's weight in kilograms
526475
* @returns Z-score as a string (e.g., '-2', '0', '1'), '-4' if out of range, or null if inputs missing
527476
*/
528-
calcWeightForHeightZscore = (height, weight) => {
477+
calcWeightForHeightZscore = (height: number, weight: number): string | null => {
529478
if (!height || !weight) {
530479
return null;
531480
}
532481

533482
const birthDate = new Date(this.patient.birthDate);
534483
const weightForHeightRef = getZRefByGenderAndAge(this.patient.sex, birthDate, new Date()).weightForHeightRef;
535484

536-
const formattedHeight = parseFloat(height).toFixed(1);
485+
const formattedHeight = height.toFixed(1);
537486
const standardHeightMin = 45;
538487
const standardMaxHeight = 110;
539488

@@ -556,7 +505,7 @@ export class CommonExpressionHelpers {
556505
* @param weight - Patient's weight in kilograms
557506
* @returns Z-score as a string (e.g., '-2', '0', '1'), or null if inputs missing
558507
*/
559-
calcBMIForAgeZscore = (height, weight) => {
508+
calcBMIForAgeZscore = (height: number, weight: number): string | null => {
560509
if (!height || !weight) {
561510
return null;
562511
}
@@ -578,7 +527,7 @@ export class CommonExpressionHelpers {
578527
* @param _weight - Unused parameter kept for backward compatibility
579528
* @returns Z-score as a string (e.g., '-2', '0', '1'), or null if height is missing
580529
*/
581-
calcHeightForAgeZscore = (height, _weight?) => {
530+
calcHeightForAgeZscore = (height: number, _weight?: number): string | null => {
582531
if (!height) {
583532
return null;
584533
}
@@ -594,26 +543,24 @@ export class CommonExpressionHelpers {
594543
* Calculates the time difference between an observation date and today.
595544
* @param obsDate - The observation/reference date to compare against today
596545
* @param timeFrame - The unit of time: 'd' (days), 'w' (weeks), 'm' (months), or 'y' (years)
597-
* @returns The absolute time difference as a number, or '0' (string) if obsDate is not provided
546+
* @returns The absolute time difference as a number, or 0 if obsDate is not provided
598547
*/
599-
calcTimeDifference = (obsDate: Date | dayjs.Dayjs, timeFrame: 'd' | 'w' | 'm' | 'y') => {
600-
let daySinceLastObs: number | string = '';
548+
calcTimeDifference = (obsDate: Date | dayjs.Dayjs, timeFrame: 'd' | 'w' | 'm' | 'y'): number => {
549+
if (!obsDate) {
550+
return 0;
551+
}
552+
601553
const endDate = dayjs();
602-
if (obsDate) {
603-
if (timeFrame == 'd') {
604-
daySinceLastObs = Math.abs(Math.round(endDate.diff(obsDate, 'day', true)));
605-
}
606-
if (timeFrame == 'w') {
607-
daySinceLastObs = Math.abs(Math.round(endDate.diff(obsDate, 'week', true)));
608-
}
609-
if (timeFrame == 'm') {
610-
daySinceLastObs = Math.abs(Math.round(endDate.diff(obsDate, 'month', true)));
611-
}
612-
if (timeFrame == 'y') {
613-
daySinceLastObs = Math.abs(Math.round(endDate.diff(obsDate, 'year', true)));
614-
}
554+
switch (timeFrame) {
555+
case 'd':
556+
return Math.abs(Math.round(endDate.diff(obsDate, 'day', true)));
557+
case 'w':
558+
return Math.abs(Math.round(endDate.diff(obsDate, 'week', true)));
559+
case 'm':
560+
return Math.abs(Math.round(endDate.diff(obsDate, 'month', true)));
561+
case 'y':
562+
return Math.abs(Math.round(endDate.diff(obsDate, 'year', true)));
615563
}
616-
return daySinceLastObs === '' ? '0' : daySinceLastObs;
617564
};
618565

619566
/**

0 commit comments

Comments
 (0)