@kenyaemr/esm-patient-clinical-view-app 5.4.2-pre.2592 → 5.4.2-pre.2598

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 (39) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/dist/127.js +1 -1
  3. package/dist/152.js +3 -3
  4. package/dist/152.js.map +1 -1
  5. package/dist/481.js +66 -0
  6. package/dist/481.js.map +1 -0
  7. package/dist/671.js +1 -1
  8. package/dist/671.js.map +1 -1
  9. package/dist/941.js +1 -0
  10. package/dist/941.js.map +1 -0
  11. package/dist/kenyaemr-esm-patient-clinical-view-app.js +2 -2
  12. package/dist/kenyaemr-esm-patient-clinical-view-app.js.buildmanifest.json +55 -55
  13. package/dist/kenyaemr-esm-patient-clinical-view-app.js.map +1 -1
  14. package/dist/main.js +87 -14
  15. package/dist/main.js.map +1 -1
  16. package/dist/routes.json +1 -1
  17. package/package.json +1 -1
  18. package/src/config-schema.ts +144 -0
  19. package/src/index.ts +2 -2
  20. package/src/maternal-and-child-health/partography/labour-delivery.scss +6 -7
  21. package/src/maternal-and-child-health/partography/partograph.component.tsx +487 -151
  22. package/src/maternal-and-child-health/partography/partography-data-form.component.tsx +434 -0
  23. package/src/maternal-and-child-health/partography/partography-data-form.scss +50 -0
  24. package/src/maternal-and-child-health/partography/partography-link.component.tsx +21 -0
  25. package/src/maternal-and-child-health/partography/partography.resource.ts +1024 -0
  26. package/src/maternal-and-child-health/partography/partography.scss +378 -0
  27. package/src/maternal-and-child-health/partography/types/index.ts +980 -0
  28. package/translations/en.json +11 -1
  29. package/dist/287.js +0 -1
  30. package/dist/287.js.map +0 -1
  31. package/dist/98.js +0 -1
  32. package/dist/98.js.map +0 -1
  33. package/src/maternal-and-child-health/partography/cervical-dilation.component.tsx +0 -16
  34. package/src/maternal-and-child-health/partography/contraction-level.component.tsx +0 -16
  35. package/src/maternal-and-child-health/partography/descent-of-head.component.tsx +0 -16
  36. package/src/maternal-and-child-health/partography/foetal-heart-rate.component.tsx +0 -17
  37. package/src/maternal-and-child-health/partography/membrane-amniotic-fluid-moulding.component.tsx +0 -17
  38. package/src/maternal-and-child-health/partography/partograph-chart.scss +0 -94
  39. package/src/maternal-and-child-health/partography/partograph-chart.tsx +0 -176
@@ -0,0 +1,980 @@
1
+ export const PARTOGRAPHY_CONCEPTS = {
2
+ 'fetal-heart-rate': '1440AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
3
+ 'cervical-dilation': '162261AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
4
+ 'descent-of-head': '1810AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
5
+ 'uterine-contractions': '163750AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
6
+ 'uterine-contraction-frequency': '166529AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
7
+ 'uterine-contraction-duration': '159368AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
8
+ 'maternal-pulse': '5087AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
9
+ 'systolic-bp': '5085AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
10
+ 'diastolic-bp': '5086AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
11
+ temperature: '5088AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
12
+ 'protein-level': '161442AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
13
+ 'glucose-level': '887AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
14
+ 'ketone-level': '165438AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
15
+ 'urine-volume': '159660AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
16
+ 'urine-characteristics': '56AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
17
+ medication: '1282AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
18
+ 'medication-name': '164231AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
19
+ 'oxytocin-dose': '166531AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
20
+ 'iv-fluids': '161911AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
21
+ dosage: '1443AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
22
+ 'event-type': '162879AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
23
+ 'event-description': '160632AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
24
+ 'amniotic-fluid': '162653AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
25
+ moulding: '166527AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
26
+ 'blood-group': '300AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
27
+ 'time-slot': '163286AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
28
+ 'labor-pattern': '164135AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
29
+ 'hours-since-rupture': '167149AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
30
+ 'ruptured-membranes': '164900AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
31
+ 'date-of-admission': '1640AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
32
+ 'gestation-weeks': '1789AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
33
+ 'estimated-delivery-date': '5596AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
34
+ 'last-menstrual-period': '1427AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
35
+ } as const;
36
+
37
+ export const MCH_PARTOGRAPHY_ENCOUNTER_UUID = '022d62af-e2a5-4282-953b-52dd5cba3296';
38
+
39
+ export const PARTOGRAPHY_GRAPH_TYPES = [
40
+ 'fetal-heart-rate',
41
+ 'cervical-dilation',
42
+ 'descent-of-head',
43
+ 'uterine-contractions',
44
+ 'maternal-pulse',
45
+ 'blood-pressure',
46
+ 'temperature',
47
+ 'urine-analysis',
48
+ 'drugs-fluids',
49
+ 'progress-events',
50
+ ] as const;
51
+
52
+ export const PARTOGRAPHY_ENCOUNTER_TYPES = Object.fromEntries(
53
+ PARTOGRAPHY_GRAPH_TYPES.map((type) => [type, MCH_PARTOGRAPHY_ENCOUNTER_UUID]),
54
+ ) as Record<(typeof PARTOGRAPHY_GRAPH_TYPES)[number], string>;
55
+
56
+ export interface OpenMRSResponse<T> {
57
+ results: T[];
58
+ }
59
+
60
+ export interface PartographyObservation {
61
+ uuid: string;
62
+ concept: {
63
+ uuid: string;
64
+ display: string;
65
+ };
66
+ value: string | number;
67
+ obsDatetime: string;
68
+ encounter: {
69
+ uuid: string;
70
+ encounterType: {
71
+ uuid: string;
72
+ display: string;
73
+ };
74
+ };
75
+ }
76
+
77
+ export interface PartographyEncounter {
78
+ uuid: string;
79
+ encounterDatetime: string;
80
+ encounterType: {
81
+ uuid: string;
82
+ display: string;
83
+ };
84
+ obs: PartographyObservation[];
85
+ }
86
+
87
+ export type PartographyGraphType = keyof typeof PARTOGRAPHY_ENCOUNTER_TYPES;
88
+
89
+ export interface PartographyGraph {
90
+ id: PartographyGraphType;
91
+ titleKey: string;
92
+ titleDefault: string;
93
+ descriptionKey: string;
94
+ descriptionDefault: string;
95
+ yAxisLabel: string;
96
+ normalRange: string;
97
+ color: string;
98
+ yMin: number;
99
+ yMax: number;
100
+ }
101
+
102
+ export const PARTOGRAPHY_UNITS = {
103
+ 'fetal-heart-rate': 'BPM',
104
+ 'cervical-dilation': 'cm',
105
+ 'descent-of-head': 'Station',
106
+ 'uterine-contractions': 'per 10min',
107
+ 'maternal-pulse': 'BPM',
108
+ 'blood-pressure': 'mmHg',
109
+ temperature: '°C',
110
+ 'urine-analysis': 'Level',
111
+ 'drugs-fluids': 'ml/hr',
112
+ 'progress-events': 'Value',
113
+ } as const;
114
+
115
+ export const PARTOGRAPHY_NORMAL_RANGES = {
116
+ 'fetal-heart-rate': '110-160 BPM',
117
+ 'cervical-dilation': '0-10 cm',
118
+ 'descent-of-head': '-3 to +3',
119
+ 'uterine-contractions': '2-5 per 10min',
120
+ 'maternal-pulse': '60-100 BPM',
121
+ 'blood-pressure': '90/60-140/90',
122
+ temperature: '36-37.5°C',
123
+ 'urine-analysis': 'Negative/Trace',
124
+ 'drugs-fluids': 'As prescribed',
125
+ 'progress-events': 'Timeline based',
126
+ } as const;
127
+
128
+ export const PARTOGRAPHY_COLORS = {
129
+ 'fetal-heart-rate': 'red',
130
+ 'cervical-dilation': 'blue',
131
+ 'descent-of-head': 'green',
132
+ 'uterine-contractions': 'purple',
133
+ 'maternal-pulse': 'orange',
134
+ 'blood-pressure': 'cyan',
135
+ temperature: 'teal',
136
+ 'urine-analysis': 'yellow',
137
+ 'drugs-fluids': 'magenta',
138
+ 'progress-events': 'gray',
139
+ } as const;
140
+
141
+ export const PARTOGRAPHY_Y_RANGES = {
142
+ 'fetal-heart-rate': { min: 80, max: 200 },
143
+ 'cervical-dilation': { min: 0, max: 10 },
144
+ 'descent-of-head': { min: -4, max: 4 },
145
+ 'uterine-contractions': { min: 0, max: 6 },
146
+ 'maternal-pulse': { min: 50, max: 120 },
147
+ 'blood-pressure': { min: 50, max: 180 },
148
+ temperature: { min: 35, max: 39 },
149
+ 'urine-analysis': { min: 0, max: 4 },
150
+ 'drugs-fluids': { min: 0, max: 500 },
151
+ 'progress-events': { min: 0, max: 10 },
152
+ } as const;
153
+
154
+ export const PARTOGRAPHY_COLOR_MAPPINGS = {
155
+ red: '#da1e28',
156
+ blue: '#0f62fe',
157
+ green: '#24a148',
158
+ purple: '#8a3ffc',
159
+ orange: '#ff832b',
160
+ cyan: '#1192e8',
161
+ teal: '#009d9a',
162
+ yellow: '#f1c21b',
163
+ magenta: '#d12771',
164
+ gray: '#525252',
165
+ } as const;
166
+
167
+ export const isValidPartographyGraphType = (type: string): type is PartographyGraphType => {
168
+ return PARTOGRAPHY_GRAPH_TYPES.includes(type as PartographyGraphType);
169
+ };
170
+
171
+ export const getPartographyUnit = (graphType: PartographyGraphType): string => {
172
+ return PARTOGRAPHY_UNITS[graphType];
173
+ };
174
+
175
+ export const getPartographyNormalRange = (graphType: PartographyGraphType): string => {
176
+ return PARTOGRAPHY_NORMAL_RANGES[graphType];
177
+ };
178
+
179
+ export const getPartographyColor = (graphType: PartographyGraphType): string => {
180
+ return PARTOGRAPHY_COLORS[graphType];
181
+ };
182
+
183
+ export const getPartographyYRange = (graphType: PartographyGraphType): { min: number; max: number } => {
184
+ return PARTOGRAPHY_Y_RANGES[graphType];
185
+ };
186
+
187
+ export const PARTOGRAPHY_GRAPH_CONFIGS: PartographyGraph[] = [
188
+ {
189
+ id: 'fetal-heart-rate',
190
+ titleKey: 'fetalHeartRate',
191
+ titleDefault: 'Fetal Heart Rate',
192
+ descriptionKey: 'fetalHeartRateDesc',
193
+ descriptionDefault: 'Monitor fetal well-being during labor',
194
+ yAxisLabel: 'BPM',
195
+ normalRange: '110-160 BPM',
196
+ color: 'red',
197
+ yMin: 80,
198
+ yMax: 200,
199
+ },
200
+ {
201
+ id: 'cervical-dilation',
202
+ titleKey: 'cervicalDilation',
203
+ titleDefault: 'Cervical Dilation',
204
+ descriptionKey: 'cervicalDilationDesc',
205
+ descriptionDefault: 'Progress of cervical opening during labor',
206
+ yAxisLabel: 'cm',
207
+ normalRange: '0-10 cm',
208
+ color: 'blue',
209
+ yMin: 0,
210
+ yMax: 10,
211
+ },
212
+ {
213
+ id: 'descent-of-head',
214
+ titleKey: 'descentOfHead',
215
+ titleDefault: 'Descent of Head',
216
+ descriptionKey: 'descentOfHeadDesc',
217
+ descriptionDefault: 'Fetal head position relative to ischial spines',
218
+ yAxisLabel: 'Station',
219
+ normalRange: '-3 to +3',
220
+ color: 'green',
221
+ yMin: -4,
222
+ yMax: 4,
223
+ },
224
+ {
225
+ id: 'uterine-contractions',
226
+ titleKey: 'uterineContractions',
227
+ titleDefault: 'Uterine Contractions',
228
+ descriptionKey: 'uterineContractionsDesc',
229
+ descriptionDefault: 'Frequency and intensity of contractions',
230
+ yAxisLabel: 'Contractions/10min',
231
+ normalRange: '2-5 per 10min',
232
+ color: 'purple',
233
+ yMin: 0,
234
+ yMax: 6,
235
+ },
236
+ {
237
+ id: 'maternal-pulse',
238
+ titleKey: 'maternalPulse',
239
+ titleDefault: 'Maternal Pulse',
240
+ descriptionKey: 'maternalPulseDesc',
241
+ descriptionDefault: 'Heart rate monitoring during labor',
242
+ yAxisLabel: 'BPM',
243
+ normalRange: '60-100 BPM',
244
+ color: 'orange',
245
+ yMin: 50,
246
+ yMax: 120,
247
+ },
248
+ {
249
+ id: 'blood-pressure',
250
+ titleKey: 'bloodPressure',
251
+ titleDefault: 'Blood Pressure',
252
+ descriptionKey: 'bloodPressureDesc',
253
+ descriptionDefault: 'Systolic and diastolic blood pressure',
254
+ yAxisLabel: 'mmHg',
255
+ normalRange: '90/60-140/90',
256
+ color: 'cyan',
257
+ yMin: 50,
258
+ yMax: 180,
259
+ },
260
+ {
261
+ id: 'temperature',
262
+ titleKey: 'temperature',
263
+ titleDefault: 'Temperature',
264
+ descriptionKey: 'temperatureDesc',
265
+ descriptionDefault: 'Maternal body temperature monitoring',
266
+ yAxisLabel: '°C',
267
+ normalRange: '36-37.5°C',
268
+ color: 'teal',
269
+ yMin: 35,
270
+ yMax: 39,
271
+ },
272
+ {
273
+ id: 'urine-analysis',
274
+ titleKey: 'urineAnalysis',
275
+ titleDefault: 'Urine Analysis',
276
+ descriptionKey: 'urineAnalysisDesc',
277
+ descriptionDefault: 'Protein, glucose, and ketones in urine',
278
+ yAxisLabel: 'Level',
279
+ normalRange: 'Negative/Trace',
280
+ color: 'yellow',
281
+ yMin: 0,
282
+ yMax: 4,
283
+ },
284
+ {
285
+ id: 'drugs-fluids',
286
+ titleKey: 'drugsFluids',
287
+ titleDefault: 'Drugs & Fluids',
288
+ descriptionKey: 'drugsFluidsDesc',
289
+ descriptionDefault: 'Medications and fluid administration',
290
+ yAxisLabel: 'ml/hr',
291
+ normalRange: 'As prescribed',
292
+ color: 'magenta',
293
+ yMin: 0,
294
+ yMax: 500,
295
+ },
296
+ {
297
+ id: 'progress-events',
298
+ titleKey: 'progressEvents',
299
+ titleDefault: 'Progress & Events',
300
+ descriptionKey: 'progressEventsDesc',
301
+ descriptionDefault: 'Labor milestones and significant events',
302
+ yAxisLabel: 'Progress Stage',
303
+ normalRange: 'Timeline based',
304
+ color: 'gray',
305
+ yMin: 0,
306
+ yMax: 10,
307
+ },
308
+ ];
309
+
310
+ export const getTranslatedPartographyGraphs = (t: (key: string, fallback: string) => string) => {
311
+ return PARTOGRAPHY_GRAPH_CONFIGS.map((config) => ({
312
+ id: config.id,
313
+ title: t(config.titleKey, config.titleDefault),
314
+ description: t(config.descriptionKey, config.descriptionDefault),
315
+ yAxisLabel: config.yAxisLabel,
316
+ normalRange: config.normalRange,
317
+ color: config.color,
318
+ yMin: config.yMin,
319
+ yMax: config.yMax,
320
+ }));
321
+ };
322
+
323
+ export const getPartographyTableHeaders = (t: (key: string, fallback: string) => string) => [
324
+ { key: 'dateTime', header: t('dateAndTime', 'Date and time') },
325
+ { key: 'value', header: t('value', 'Value') },
326
+ { key: 'unit', header: t('unit', 'Unit') },
327
+ ];
328
+
329
+ export const getColorForGraph = (colorName: string): string => {
330
+ return PARTOGRAPHY_COLOR_MAPPINGS[colorName as keyof typeof PARTOGRAPHY_COLOR_MAPPINGS] || '#525252';
331
+ };
332
+
333
+ // Concept UUID mappings for form options
334
+ export const FORM_OPTION_CONCEPTS = {
335
+ // Amniotic fluid concepts
336
+ AMNIOTIC_FLUID: {
337
+ MEMBRANE_INTACT: PARTOGRAPHY_CONCEPTS['amniotic-fluid'], // '162653AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
338
+ CLEAR_LIQUOR: '159484AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
339
+ MECONIUM_STAINED: '134488AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
340
+ ABSENT: '163747AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
341
+ BLOOD_STAINED: '1077AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
342
+ },
343
+
344
+ // Moulding concepts
345
+ MOULDING: {
346
+ NONE: '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
347
+ SLIGHT: '1362AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
348
+ MODERATE: '1363AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
349
+ SEVERE: '1364AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
350
+ },
351
+
352
+ // Descent of head station concepts
353
+ DESCENT_STATION: {
354
+ ZERO_FIFTHS: '160769AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
355
+ ONE_FIFTH: '162135AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
356
+ TWO_FIFTHS: '166065AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
357
+ THREE_FIFTHS: '166066AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
358
+ FOUR_FIFTHS: '166067AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
359
+ FIVE_FIFTHS: '163734AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
360
+ },
361
+
362
+ // Contraction intensity concepts
363
+ CONTRACTION_INTENSITY: {
364
+ MILD: PARTOGRAPHY_CONCEPTS['uterine-contractions'],
365
+ MODERATE: PARTOGRAPHY_CONCEPTS['uterine-contractions'],
366
+ STRONG: PARTOGRAPHY_CONCEPTS['uterine-contractions'],
367
+ },
368
+
369
+ // Urine analysis level concepts
370
+ URINE_LEVELS: {
371
+ NEGATIVE: '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
372
+ TRACE: '1362AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
373
+ POSITIVE_PLUS: '1363AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
374
+ POSITIVE_PLUS_PLUS: '1364AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
375
+ },
376
+
377
+ // Blood group concept (single concept, different values)
378
+ BLOOD_GROUP: PARTOGRAPHY_CONCEPTS['blood-group'],
379
+
380
+ // Event type concepts
381
+ EVENT_TYPES: {
382
+ MEMBRANE_RUPTURE: PARTOGRAPHY_CONCEPTS['event-type'],
383
+ LABOR_ONSET: PARTOGRAPHY_CONCEPTS['event-type'],
384
+ POSITION_CHANGE: PARTOGRAPHY_CONCEPTS['event-type'],
385
+ MEDICATION_GIVEN: PARTOGRAPHY_CONCEPTS['event-type'],
386
+ COMPLICATION: PARTOGRAPHY_CONCEPTS['event-type'],
387
+ DELIVERY: PARTOGRAPHY_CONCEPTS['event-type'],
388
+ OTHER: PARTOGRAPHY_CONCEPTS['event-type'],
389
+ },
390
+ } as const;
391
+
392
+ // Form option configurations using concepts
393
+ export const AMNIOTIC_FLUID_OPTIONS = [
394
+ { value: '', text: 'chooseAnOption', conceptUuid: '' },
395
+ {
396
+ value: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.MEMBRANE_INTACT,
397
+ text: 'membraneIntact',
398
+ conceptUuid: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.MEMBRANE_INTACT,
399
+ },
400
+ {
401
+ value: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.CLEAR_LIQUOR,
402
+ text: 'clearLiquor',
403
+ conceptUuid: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.CLEAR_LIQUOR,
404
+ },
405
+ {
406
+ value: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.MECONIUM_STAINED,
407
+ text: 'meconiumStained',
408
+ conceptUuid: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.MECONIUM_STAINED,
409
+ },
410
+ {
411
+ value: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.ABSENT,
412
+ text: 'absent',
413
+ conceptUuid: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.ABSENT,
414
+ },
415
+ {
416
+ value: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.BLOOD_STAINED,
417
+ text: 'bloodStained',
418
+ conceptUuid: FORM_OPTION_CONCEPTS.AMNIOTIC_FLUID.BLOOD_STAINED,
419
+ },
420
+ ] as const;
421
+
422
+ export const MOULDING_OPTIONS = [
423
+ { value: '', text: 'chooseAnOption', display: '', conceptUuid: '' },
424
+ {
425
+ value: FORM_OPTION_CONCEPTS.MOULDING.NONE,
426
+ text: 'none',
427
+ display: '0',
428
+ conceptUuid: FORM_OPTION_CONCEPTS.MOULDING.NONE,
429
+ },
430
+ {
431
+ value: FORM_OPTION_CONCEPTS.MOULDING.SLIGHT,
432
+ text: 'slight',
433
+ display: '+',
434
+ conceptUuid: FORM_OPTION_CONCEPTS.MOULDING.SLIGHT,
435
+ },
436
+ {
437
+ value: FORM_OPTION_CONCEPTS.MOULDING.MODERATE,
438
+ text: 'moderate',
439
+ display: '++',
440
+ conceptUuid: FORM_OPTION_CONCEPTS.MOULDING.MODERATE,
441
+ },
442
+ {
443
+ value: FORM_OPTION_CONCEPTS.MOULDING.SEVERE,
444
+ text: 'severe',
445
+ display: '+++',
446
+ conceptUuid: FORM_OPTION_CONCEPTS.MOULDING.SEVERE,
447
+ },
448
+ ] as const;
449
+
450
+ export const DESCENT_OF_HEAD_OPTIONS = [
451
+ { value: '', text: 'selectStation', stationValue: 0, conceptUuid: '' },
452
+ {
453
+ value: FORM_OPTION_CONCEPTS.DESCENT_STATION.ZERO_FIFTHS,
454
+ text: '0/5',
455
+ stationValue: 0,
456
+ conceptUuid: FORM_OPTION_CONCEPTS.DESCENT_STATION.ZERO_FIFTHS,
457
+ },
458
+ {
459
+ value: FORM_OPTION_CONCEPTS.DESCENT_STATION.ONE_FIFTH,
460
+ text: '1/5',
461
+ stationValue: 1,
462
+ conceptUuid: FORM_OPTION_CONCEPTS.DESCENT_STATION.ONE_FIFTH,
463
+ },
464
+ {
465
+ value: FORM_OPTION_CONCEPTS.DESCENT_STATION.TWO_FIFTHS,
466
+ text: '2/5',
467
+ stationValue: 2,
468
+ conceptUuid: FORM_OPTION_CONCEPTS.DESCENT_STATION.TWO_FIFTHS,
469
+ },
470
+ {
471
+ value: FORM_OPTION_CONCEPTS.DESCENT_STATION.THREE_FIFTHS,
472
+ text: '3/5',
473
+ stationValue: 3,
474
+ conceptUuid: FORM_OPTION_CONCEPTS.DESCENT_STATION.THREE_FIFTHS,
475
+ },
476
+ {
477
+ value: FORM_OPTION_CONCEPTS.DESCENT_STATION.FOUR_FIFTHS,
478
+ text: '4/5',
479
+ stationValue: 4,
480
+ conceptUuid: FORM_OPTION_CONCEPTS.DESCENT_STATION.FOUR_FIFTHS,
481
+ },
482
+ {
483
+ value: FORM_OPTION_CONCEPTS.DESCENT_STATION.FIVE_FIFTHS,
484
+ text: '5/5',
485
+ stationValue: 5,
486
+ conceptUuid: FORM_OPTION_CONCEPTS.DESCENT_STATION.FIVE_FIFTHS,
487
+ },
488
+ ] as const;
489
+
490
+ export const CONTRACTION_INTENSITY_OPTIONS = [
491
+ { value: '', text: 'selectIntensity', conceptUuid: '' },
492
+ { value: 'mild', text: 'mild', conceptUuid: FORM_OPTION_CONCEPTS.CONTRACTION_INTENSITY.MILD },
493
+ { value: 'moderate', text: 'moderate', conceptUuid: FORM_OPTION_CONCEPTS.CONTRACTION_INTENSITY.MODERATE },
494
+ { value: 'strong', text: 'strong', conceptUuid: FORM_OPTION_CONCEPTS.CONTRACTION_INTENSITY.STRONG },
495
+ ] as const;
496
+
497
+ export const URINE_LEVEL_OPTIONS = [
498
+ { value: '', text: 'selectLevel', conceptUuid: '' },
499
+ {
500
+ value: FORM_OPTION_CONCEPTS.URINE_LEVELS.NEGATIVE,
501
+ text: 'negative',
502
+ conceptUuid: FORM_OPTION_CONCEPTS.URINE_LEVELS.NEGATIVE,
503
+ },
504
+ {
505
+ value: FORM_OPTION_CONCEPTS.URINE_LEVELS.TRACE,
506
+ text: 'trace',
507
+ conceptUuid: FORM_OPTION_CONCEPTS.URINE_LEVELS.TRACE,
508
+ },
509
+ {
510
+ value: FORM_OPTION_CONCEPTS.URINE_LEVELS.POSITIVE_PLUS,
511
+ text: 'positivePlus',
512
+ conceptUuid: FORM_OPTION_CONCEPTS.URINE_LEVELS.POSITIVE_PLUS,
513
+ },
514
+ {
515
+ value: FORM_OPTION_CONCEPTS.URINE_LEVELS.POSITIVE_PLUS_PLUS,
516
+ text: 'positivePlusPlus',
517
+ conceptUuid: FORM_OPTION_CONCEPTS.URINE_LEVELS.POSITIVE_PLUS_PLUS,
518
+ },
519
+ ] as const;
520
+
521
+ export const BLOOD_GROUP_OPTIONS = [
522
+ { value: '', text: 'selectBG', conceptUuid: '' },
523
+ { value: 'A+', text: 'A+', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
524
+ { value: 'A-', text: 'A-', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
525
+ { value: 'B+', text: 'B+', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
526
+ { value: 'B-', text: 'B-', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
527
+ { value: 'AB+', text: 'AB+', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
528
+ { value: 'AB-', text: 'AB-', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
529
+ { value: 'O+', text: 'O+', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
530
+ { value: 'O-', text: 'O-', conceptUuid: FORM_OPTION_CONCEPTS.BLOOD_GROUP },
531
+ ] as const;
532
+
533
+ export const ADMISSION_OPTIONS = [
534
+ { value: '', text: 'selectAdmission', conceptUuid: '' },
535
+ { value: 'normal', text: 'normal', conceptUuid: PARTOGRAPHY_CONCEPTS['date-of-admission'] },
536
+ { value: 'emergency', text: 'emergency', conceptUuid: PARTOGRAPHY_CONCEPTS['date-of-admission'] },
537
+ { value: 'elective', text: 'elective', conceptUuid: PARTOGRAPHY_CONCEPTS['date-of-admission'] },
538
+ { value: 'referral', text: 'referral', conceptUuid: PARTOGRAPHY_CONCEPTS['date-of-admission'] },
539
+ ] as const;
540
+
541
+ export const TIME_SLOT_OPTIONS = [
542
+ { value: '', text: 'selectAM', conceptUuid: '' },
543
+ { value: 'morning', text: 'morning', conceptUuid: PARTOGRAPHY_CONCEPTS['time-slot'] },
544
+ { value: 'afternoon', text: 'afternoon', conceptUuid: PARTOGRAPHY_CONCEPTS['time-slot'] },
545
+ { value: 'evening', text: 'evening', conceptUuid: PARTOGRAPHY_CONCEPTS['time-slot'] },
546
+ { value: 'night', text: 'night', conceptUuid: PARTOGRAPHY_CONCEPTS['time-slot'] },
547
+ ] as const;
548
+
549
+ export const EVENT_TYPE_OPTIONS = [
550
+ { value: '', text: 'selectEventType', conceptUuid: '' },
551
+ {
552
+ value: 'membrane-rupture',
553
+ text: 'membraneRupture',
554
+ conceptUuid: FORM_OPTION_CONCEPTS.EVENT_TYPES.MEMBRANE_RUPTURE,
555
+ },
556
+ { value: 'labor-onset', text: 'laborOnset', conceptUuid: FORM_OPTION_CONCEPTS.EVENT_TYPES.LABOR_ONSET },
557
+ { value: 'position-change', text: 'positionChange', conceptUuid: FORM_OPTION_CONCEPTS.EVENT_TYPES.POSITION_CHANGE },
558
+ {
559
+ value: 'medication-given',
560
+ text: 'medicationGiven',
561
+ conceptUuid: FORM_OPTION_CONCEPTS.EVENT_TYPES.MEDICATION_GIVEN,
562
+ },
563
+ { value: 'complication', text: 'complication', conceptUuid: FORM_OPTION_CONCEPTS.EVENT_TYPES.COMPLICATION },
564
+ { value: 'delivery', text: 'delivery', conceptUuid: FORM_OPTION_CONCEPTS.EVENT_TYPES.DELIVERY },
565
+ { value: 'other', text: 'other', conceptUuid: FORM_OPTION_CONCEPTS.EVENT_TYPES.OTHER },
566
+ ] as const;
567
+
568
+ // Mapping functions
569
+ export const getDescentStationMapping = () => {
570
+ const mapping: Record<string, number> = {};
571
+ DESCENT_OF_HEAD_OPTIONS.forEach((option) => {
572
+ if (option.value && option.stationValue !== undefined) {
573
+ mapping[option.value] = option.stationValue;
574
+ }
575
+ });
576
+ return mapping;
577
+ };
578
+
579
+ // Configurable input validation ranges
580
+ export const VALIDATION_RANGES = {
581
+ FETAL_HEART_RATE: { min: 80, max: 200, normal: { min: 110, max: 160 }, step: 1 },
582
+ CERVICAL_DILATION: { min: 0, max: 10, normal: { min: 0, max: 10 }, step: 0.5 },
583
+ MATERNAL_PULSE: { min: 40, max: 140, normal: { min: 60, max: 100 }, step: 1 },
584
+ SYSTOLIC_BP: { min: 60, max: 250, normal: { min: 90, max: 140 }, step: 1 },
585
+ DIASTOLIC_BP: { min: 30, max: 150, normal: { min: 60, max: 90 }, step: 1 },
586
+ TEMPERATURE: { min: 35, max: 42, normal: { min: 36, max: 37.5 }, step: 0.1 },
587
+ UTERINE_CONTRACTIONS: { min: 0, max: 10, normal: { min: 2, max: 5 }, step: 1 },
588
+ } as const;
589
+
590
+ // Input ranges mapped to graph types
591
+ export const INPUT_RANGES = {
592
+ 'fetal-heart-rate': {
593
+ ...VALIDATION_RANGES.FETAL_HEART_RATE,
594
+ placeholderKey: 'fetalHeartRatePlaceholder',
595
+ conceptUuid: PARTOGRAPHY_CONCEPTS['fetal-heart-rate'],
596
+ },
597
+ 'cervical-dilation': {
598
+ ...VALIDATION_RANGES.CERVICAL_DILATION,
599
+ placeholderKey: 'cervicalDilationPlaceholder',
600
+ conceptUuid: PARTOGRAPHY_CONCEPTS['cervical-dilation'],
601
+ },
602
+ 'maternal-pulse': {
603
+ ...VALIDATION_RANGES.MATERNAL_PULSE,
604
+ placeholderKey: 'maternalPulsePlaceholder',
605
+ conceptUuid: PARTOGRAPHY_CONCEPTS['maternal-pulse'],
606
+ },
607
+ 'systolic-bp': {
608
+ ...VALIDATION_RANGES.SYSTOLIC_BP,
609
+ placeholderKey: 'systolicBPPlaceholder',
610
+ conceptUuid: PARTOGRAPHY_CONCEPTS['systolic-bp'],
611
+ },
612
+ 'diastolic-bp': {
613
+ ...VALIDATION_RANGES.DIASTOLIC_BP,
614
+ placeholderKey: 'diastolicBPPlaceholder',
615
+ conceptUuid: PARTOGRAPHY_CONCEPTS['diastolic-bp'],
616
+ },
617
+ temperature: {
618
+ ...VALIDATION_RANGES.TEMPERATURE,
619
+ placeholderKey: 'temperaturePlaceholder',
620
+ conceptUuid: PARTOGRAPHY_CONCEPTS.temperature,
621
+ },
622
+ 'uterine-contractions': {
623
+ ...VALIDATION_RANGES.UTERINE_CONTRACTIONS,
624
+ placeholderKey: 'contractionFrequencyPlaceholder',
625
+ conceptUuid: PARTOGRAPHY_CONCEPTS['uterine-contractions'],
626
+ },
627
+ } as const;
628
+
629
+ // Configurable form validation helpers
630
+ export const createRangeValidator = (rangeName: keyof typeof VALIDATION_RANGES) => {
631
+ const range = VALIDATION_RANGES[rangeName];
632
+ return (value: string | number): boolean => {
633
+ const num = typeof value === 'string' ? parseFloat(value) : value;
634
+ return !isNaN(num) && num >= range.min && num <= range.max;
635
+ };
636
+ };
637
+
638
+ export const isValidCervicalDilation = createRangeValidator('CERVICAL_DILATION');
639
+
640
+ export const isValidFetalHeartRate = createRangeValidator('FETAL_HEART_RATE');
641
+
642
+ export const isValidMaternalPulse = createRangeValidator('MATERNAL_PULSE');
643
+
644
+ export const isValidSystolicBP = createRangeValidator('SYSTOLIC_BP');
645
+
646
+ export const isValidDiastolicBP = createRangeValidator('DIASTOLIC_BP');
647
+
648
+ export const isValidTemperature = createRangeValidator('TEMPERATURE');
649
+
650
+ export const isValidUterineContractions = createRangeValidator('UTERINE_CONTRACTIONS');
651
+
652
+ export const isValidBloodPressure = (systolic: string, diastolic: string): boolean => {
653
+ return (
654
+ systolic.trim() !== '' && diastolic.trim() !== '' && isValidSystolicBP(systolic) && isValidDiastolicBP(diastolic)
655
+ );
656
+ };
657
+
658
+ export const isValidUrineAnalysis = (protein: string, glucose: string, ketone: string): boolean => {
659
+ return protein !== '' || glucose !== '' || ketone !== '';
660
+ };
661
+
662
+ export const isValidDrugsFluids = (medication: string, dosage: string): boolean => {
663
+ return medication.trim() !== '' && dosage.trim() !== '';
664
+ };
665
+
666
+ export const isValidProgressEvents = (eventType: string, eventDescription: string): boolean => {
667
+ return eventType !== '' && eventDescription.trim() !== '';
668
+ };
669
+
670
+ export const isValidDescentOfHead = (value: string): boolean => {
671
+ return value.trim() !== '' && DESCENT_OF_HEAD_OPTIONS.some((option) => option.value === value);
672
+ };
673
+
674
+ export const isValidMeasurement = (graphType: string, value: string): boolean => {
675
+ switch (graphType) {
676
+ case 'fetal-heart-rate':
677
+ return isValidFetalHeartRate(value);
678
+ case 'cervical-dilation':
679
+ return isValidCervicalDilation(value);
680
+ case 'maternal-pulse':
681
+ return isValidMaternalPulse(value);
682
+ case 'temperature':
683
+ return isValidTemperature(value);
684
+ case 'uterine-contractions':
685
+ return isValidUterineContractions(value);
686
+ case 'descent-of-head':
687
+ return isValidDescentOfHead(value);
688
+ default:
689
+ return value.trim() !== '';
690
+ }
691
+ };
692
+
693
+ // Measurement label mappings
694
+ export const MEASUREMENT_LABELS = {
695
+ 'fetal-heart-rate': { key: 'fetalHeartRate', default: 'Foetal Heart Rate' },
696
+ 'cervical-dilation': { key: 'membraneAmnioticFluidMoulding', default: 'Membrane Amniotic Fluid & Moulding' },
697
+ 'descent-of-head': { key: 'descentOfHead', default: 'Descent of Head' },
698
+ 'uterine-contractions': { key: 'uterineContractions', default: 'Uterine Contractions' },
699
+ 'maternal-pulse': { key: 'maternalPulse', default: 'Maternal Pulse' },
700
+ 'blood-pressure': { key: 'bloodPressure', default: 'Blood Pressure' },
701
+ temperature: { key: 'temperature', default: 'Temperature' },
702
+ 'urine-analysis': { key: 'urineAnalysis', default: 'Urine Analysis' },
703
+ 'drugs-fluids': { key: 'drugsFluids', default: 'Drugs & Fluids' },
704
+ 'progress-events': { key: 'progressEvents', default: 'Progress & Events' },
705
+ } as const;
706
+
707
+ // Get measurement label function
708
+ // Field label mappings for all form inputs
709
+ export const FIELD_LABELS = {
710
+ // Number input labels
711
+ fetalHeartRate: { key: 'fetalHeartRate', default: 'Fetal Heart Rate (BPM)' },
712
+ cervicalDilation: { key: 'cervicalDilation', default: 'Cervical Dilation (cm)' },
713
+ maternalPulse: { key: 'maternalPulse', default: 'Maternal Pulse (BPM)' },
714
+ systolic: { key: 'systolic', default: 'Systolic (mmHg)' },
715
+ diastolic: { key: 'diastolic', default: 'Diastolic (mmHg)' },
716
+ temperature: { key: 'temperature', default: 'Temperature (°C)' },
717
+ frequency: { key: 'frequency', default: 'Frequency (per 10min)' },
718
+
719
+ // Select field labels
720
+ amnioticFluid: { key: 'amnioticFluid', default: 'Amniotic Fluid' },
721
+ moulding: { key: 'moulding', default: 'Moulding' },
722
+ descentOfHead: { key: 'descentOfHead', default: 'Descent of Head (Station)' },
723
+ intensity: { key: 'intensity', default: 'Intensity' },
724
+ proteinLevel: { key: 'proteinLevel', default: 'Protein Level' },
725
+ glucoseLevel: { key: 'glucoseLevel', default: 'Glucose Level' },
726
+ ketoneLevel: { key: 'ketoneLevel', default: 'Ketone Level' },
727
+ eventType: { key: 'eventType', default: 'Event Type' },
728
+
729
+ // Text input labels
730
+ medication: { key: 'medication', default: 'Medication/Fluid' },
731
+ dosageRate: { key: 'dosageRate', default: 'Dosage/Rate' },
732
+ eventDescription: { key: 'eventDescription', default: 'Event Description' },
733
+
734
+ // Common labels
735
+ admission: { key: 'admission', default: 'Admission' },
736
+ bg: { key: 'bg', default: 'BG' },
737
+ am: { key: 'am', default: 'AM' },
738
+ } as const;
739
+
740
+ // Placeholder mappings for inputs - all translatable
741
+ export const FIELD_PLACEHOLDERS = {
742
+ // Number input placeholders
743
+ fetalHeartRatePlaceholder: { key: 'enterFetalHeartRate', default: 'Enter fetal heart rate (110-160 BPM)' },
744
+ cervicalDilationPlaceholder: { key: 'enterCervicalDilation', default: 'Enter cervical dilation (0-10 cm)' },
745
+ maternalPulsePlaceholder: { key: 'enterMaternalPulse', default: 'Enter maternal pulse (60-100 BPM)' },
746
+ systolicBPPlaceholder: { key: 'enterSystolicBP', default: 'Enter systolic BP (90-140 mmHg)' },
747
+ diastolicBPPlaceholder: { key: 'enterDiastolicBP', default: 'Enter diastolic BP (60-90 mmHg)' },
748
+ temperaturePlaceholder: { key: 'enterTemperature', default: 'Enter temperature (36-37.5°C)' },
749
+ contractionFrequencyPlaceholder: { key: 'enterContractionFrequency', default: 'Enter contractions per 10 minutes' },
750
+ contractionIntensityPlaceholder: { key: 'selectContractionIntensity', default: 'Select contraction intensity' },
751
+ descentOfHeadPlaceholder: { key: 'selectDescentStation', default: 'Select descent station (0/5 - 5/5)' },
752
+
753
+ // Select placeholders
754
+ amnioticFluidPlaceholder: { key: 'selectAmnioticFluid', default: 'Select amniotic fluid status' },
755
+ mouldingPlaceholder: { key: 'selectMoulding', default: 'Select moulding status' },
756
+ proteinLevelPlaceholder: { key: 'selectProteinLevel', default: 'Select protein level' },
757
+ glucoseLevelPlaceholder: { key: 'selectGlucoseLevel', default: 'Select glucose level' },
758
+ ketoneLevelPlaceholder: { key: 'selectKetoneLevel', default: 'Select ketone level' },
759
+ eventTypePlaceholder: { key: 'selectEventType', default: 'Select event type' },
760
+
761
+ // Text input placeholders
762
+ medicationType: { key: 'enterMedicationOrFluidType', default: 'Enter medication or fluid type' },
763
+ dosageRate: { key: 'enterDosageOrFlowRate', default: 'Enter dosage or flow rate' },
764
+ eventDescription: { key: 'describeEventOrMilestone', default: 'Describe the event or milestone' },
765
+ generalMeasurement: { key: 'enterMeasurementValue', default: 'Enter measurement value' },
766
+ } as const;
767
+
768
+ // Get measurement label function
769
+ export const getMeasurementLabel = (
770
+ graphType: string,
771
+ t: (key: string, fallback: string) => string,
772
+ fallbackTitle?: string,
773
+ ): string => {
774
+ const labelConfig = MEASUREMENT_LABELS[graphType as keyof typeof MEASUREMENT_LABELS];
775
+ if (labelConfig) {
776
+ return t(labelConfig.key, labelConfig.default);
777
+ }
778
+ return fallbackTitle || graphType;
779
+ };
780
+
781
+ // Get field label function
782
+ export const getFieldLabel = (
783
+ fieldKey: keyof typeof FIELD_LABELS,
784
+ t: (key: string, fallback: string) => string,
785
+ ): string => {
786
+ const labelConfig = FIELD_LABELS[fieldKey];
787
+ return t(labelConfig.key, labelConfig.default);
788
+ };
789
+
790
+ // Get field placeholder function
791
+ export const getFieldPlaceholder = (
792
+ fieldKey: keyof typeof FIELD_PLACEHOLDERS,
793
+ t: (key: string, fallback: string) => string,
794
+ ): string => {
795
+ const placeholderConfig = FIELD_PLACEHOLDERS[fieldKey];
796
+ return t(placeholderConfig.key, placeholderConfig.default);
797
+ };
798
+
799
+ // Get dynamic placeholder for input ranges
800
+ export const getInputRangePlaceholder = (
801
+ graphType: keyof typeof INPUT_RANGES,
802
+ t: (key: string, fallback: string) => string,
803
+ ): string => {
804
+ const inputConfig = INPUT_RANGES[graphType];
805
+ if (inputConfig && 'placeholderKey' in inputConfig) {
806
+ return getFieldPlaceholder(inputConfig.placeholderKey as keyof typeof FIELD_PLACEHOLDERS, t);
807
+ }
808
+ return t('enterMeasurementValue', 'Enter measurement value');
809
+ };
810
+
811
+ // Get concept UUID for form option
812
+ export const getConceptUuid = (optionType: string, optionValue: string): string => {
813
+ switch (optionType) {
814
+ case 'amnioticFluid':
815
+ return AMNIOTIC_FLUID_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
816
+ case 'moulding':
817
+ return MOULDING_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
818
+ case 'descentOfHead':
819
+ return DESCENT_OF_HEAD_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
820
+ case 'contractionIntensity':
821
+ return CONTRACTION_INTENSITY_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
822
+ case 'urineLevel':
823
+ return URINE_LEVEL_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
824
+ case 'bloodGroup':
825
+ return BLOOD_GROUP_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
826
+ case 'admission':
827
+ return ADMISSION_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
828
+ case 'timeSlot':
829
+ return TIME_SLOT_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
830
+ case 'eventType':
831
+ return EVENT_TYPE_OPTIONS.find((opt) => opt.value === optionValue)?.conceptUuid || '';
832
+ default:
833
+ return '';
834
+ }
835
+ };
836
+
837
+ // Validate if measurement is within normal range
838
+ export const isWithinNormalRange = (graphType: string, value: number): boolean => {
839
+ const range = Object.values(VALIDATION_RANGES).find((r) => {
840
+ // Match range based on graph type
841
+ switch (graphType) {
842
+ case 'fetal-heart-rate':
843
+ return r === VALIDATION_RANGES.FETAL_HEART_RATE;
844
+ case 'cervical-dilation':
845
+ return r === VALIDATION_RANGES.CERVICAL_DILATION;
846
+ case 'maternal-pulse':
847
+ return r === VALIDATION_RANGES.MATERNAL_PULSE;
848
+ case 'temperature':
849
+ return r === VALIDATION_RANGES.TEMPERATURE;
850
+ case 'uterine-contractions':
851
+ return r === VALIDATION_RANGES.UTERINE_CONTRACTIONS;
852
+ default:
853
+ return false;
854
+ }
855
+ });
856
+
857
+ if (!range || !range.normal) {
858
+ return true;
859
+ }
860
+ return value >= range.normal.min && value <= range.normal.max;
861
+ };
862
+
863
+ // Configurable data processing for different graph types
864
+ export interface GraphDataProcessor {
865
+ validate: (formData: any) => boolean;
866
+ getValue: (formData: any) => number;
867
+ getAdditionalData: (formData: any) => Record<string, any>;
868
+ }
869
+
870
+ export const GRAPH_DATA_PROCESSORS: Record<string, GraphDataProcessor> = {
871
+ 'cervical-dilation': {
872
+ validate: (formData) => isValidCervicalDilation(formData.measurementValue),
873
+ getValue: (formData) => parseFloat(formData.measurementValue) || 0,
874
+ getAdditionalData: (formData) => ({
875
+ amnioticFluid: formData.amnioticFluid,
876
+ moulding: formData.moulding,
877
+ conceptUuid: PARTOGRAPHY_CONCEPTS['cervical-dilation'],
878
+ }),
879
+ },
880
+ 'blood-pressure': {
881
+ validate: (formData) => isValidBloodPressure(formData.systolic, formData.diastolic),
882
+ getValue: (formData) => parseFloat(formData.systolic) || 0,
883
+ getAdditionalData: (formData) => ({
884
+ diastolic: parseFloat(formData.diastolic) || 0,
885
+ conceptUuid: PARTOGRAPHY_CONCEPTS['systolic-bp'],
886
+ diastolicConceptUuid: PARTOGRAPHY_CONCEPTS['diastolic-bp'],
887
+ }),
888
+ },
889
+ 'urine-analysis': {
890
+ validate: (formData) => isValidUrineAnalysis(formData.proteinLevel, formData.glucoseLevel, formData.ketoneLevel),
891
+ getValue: () => 1,
892
+ getAdditionalData: (formData) => ({
893
+ proteinLevel: formData.proteinLevel,
894
+ glucoseLevel: formData.glucoseLevel,
895
+ ketoneLevel: formData.ketoneLevel,
896
+ proteinConceptUuid: PARTOGRAPHY_CONCEPTS['protein-level'],
897
+ glucoseConceptUuid: PARTOGRAPHY_CONCEPTS['glucose-level'],
898
+ ketoneConceptUuid: PARTOGRAPHY_CONCEPTS['ketone-level'],
899
+ }),
900
+ },
901
+ 'drugs-fluids': {
902
+ validate: (formData) => isValidDrugsFluids(formData.medication, formData.dosage),
903
+ getValue: () => 1,
904
+ getAdditionalData: (formData) => ({
905
+ medication: formData.medication,
906
+ dosage: formData.dosage,
907
+ medicationConceptUuid: PARTOGRAPHY_CONCEPTS.medication,
908
+ dosageConceptUuid: PARTOGRAPHY_CONCEPTS.dosage,
909
+ }),
910
+ },
911
+ 'progress-events': {
912
+ validate: (formData) => isValidProgressEvents(formData.eventType, formData.eventDescription),
913
+ getValue: () => 1,
914
+ getAdditionalData: (formData) => ({
915
+ eventType: formData.eventType,
916
+ eventDescription: formData.eventDescription,
917
+ eventTypeConceptUuid: PARTOGRAPHY_CONCEPTS['event-type'],
918
+ eventDescriptionConceptUuid: PARTOGRAPHY_CONCEPTS['event-description'],
919
+ }),
920
+ },
921
+ 'uterine-contractions': {
922
+ validate: (formData) => isValidMeasurement('uterine-contractions', formData.measurementValue),
923
+ getValue: (formData) => parseFloat(formData.measurementValue) || 0,
924
+ getAdditionalData: (formData) => ({
925
+ intensity: formData.eventDescription,
926
+ conceptUuid: PARTOGRAPHY_CONCEPTS['uterine-contractions'],
927
+ intensityConceptUuid: PARTOGRAPHY_CONCEPTS['uterine-contraction-duration'],
928
+ }),
929
+ },
930
+ 'descent-of-head': {
931
+ validate: (formData) => isValidDescentOfHead(formData.measurementValue),
932
+ getValue: (formData) => {
933
+ const stationMapping = getDescentStationMapping();
934
+ return stationMapping[formData.measurementValue] ?? 0;
935
+ },
936
+ getAdditionalData: (formData) => {
937
+ const stationMapping = getDescentStationMapping();
938
+ return {
939
+ conceptUuid: formData.measurementValue,
940
+ stationValue: stationMapping[formData.measurementValue] ?? 0,
941
+ descentConceptUuid: PARTOGRAPHY_CONCEPTS['descent-of-head'],
942
+ };
943
+ },
944
+ },
945
+ 'fetal-heart-rate': {
946
+ validate: (formData) => isValidMeasurement('fetal-heart-rate', formData.measurementValue),
947
+ getValue: (formData) => parseFloat(formData.measurementValue) || 0,
948
+ getAdditionalData: () => ({
949
+ conceptUuid: PARTOGRAPHY_CONCEPTS['fetal-heart-rate'],
950
+ }),
951
+ },
952
+ 'maternal-pulse': {
953
+ validate: (formData) => isValidMeasurement('maternal-pulse', formData.measurementValue),
954
+ getValue: (formData) => parseFloat(formData.measurementValue) || 0,
955
+ getAdditionalData: () => ({
956
+ conceptUuid: PARTOGRAPHY_CONCEPTS['maternal-pulse'],
957
+ }),
958
+ },
959
+ temperature: {
960
+ validate: (formData) => isValidMeasurement('temperature', formData.measurementValue),
961
+ getValue: (formData) => parseFloat(formData.measurementValue) || 0,
962
+ getAdditionalData: () => ({
963
+ conceptUuid: PARTOGRAPHY_CONCEPTS.temperature,
964
+ }),
965
+ },
966
+ };
967
+
968
+ // Generic processor for unmapped graph types
969
+ export const getDefaultProcessor = (graphType: string): GraphDataProcessor => ({
970
+ validate: (formData) => isValidMeasurement(graphType, formData.measurementValue),
971
+ getValue: (formData) => parseFloat(formData.measurementValue) || 0,
972
+ getAdditionalData: () => ({
973
+ conceptUuid: PARTOGRAPHY_CONCEPTS[graphType as keyof typeof PARTOGRAPHY_CONCEPTS] || '',
974
+ }),
975
+ });
976
+
977
+ // Get processor for a specific graph type
978
+ export const getGraphDataProcessor = (graphType: string): GraphDataProcessor => {
979
+ return GRAPH_DATA_PROCESSORS[graphType] || getDefaultProcessor(graphType);
980
+ };