@@ -939,161 +939,171 @@ EHR.ext.HematologyExcelWin = Ext.extend(Ext.Panel, {
939939 this . processData ( data ) ;
940940 } ,
941941 processData : function ( data ) {
942- var skippedRows = [ ] ;
943942 var runsStore = Ext . StoreMgr . get ( "study||Clinpath Runs||||" ) ;
944943 var unitStore = Ext . StoreMgr . get ( "ehr_lookups||hematology_tests||testid||testid" ) ;
945944
946- var result ;
947- var tests ;
948- var row1 ;
949- var row2 ;
950- var toAdd = [ ] ;
951-
952- if ( ! data . length || ! data [ 0 ] . length ) {
945+ if ( ! Array . isArray ( data ) || ! data . length || ! data [ 0 ] ?. length ) {
953946 alert ( 'Something went wrong processing the file' ) ;
954947 console . log ( data )
955948 return ;
956949 }
957950
958- data = data [ 0 ] [ 0 ] . split ( / D 1 U / i ) ;
951+ const cleanData = data [ 0 ] [ 0 ] . replace ( / \s + / g , ' ' ) . trim ( ) ; // remove extra whitespace
959952
960- Ext . each ( data , function ( row , idx ) {
961- if ( ! row . match ( / D 2 U / i) )
962- return ;
963-
964- row = row . split ( / D 2 U / i) ;
965-
966- row1 = row [ 0 ] ;
967- row2 = row [ 1 ] ;
968- row1 = row1 . replace ( / \s + / g, '' ) ;
969- row2 = row2 . split ( / \s + / ) ;
970- row2 = row2 . slice ( 2 , row2 . length - 1 ) ;
971- row2 = row2 . join ( '' ) ;
972-
973- result = { } ;
974- tests = { } ;
953+ const formattedObjData = { } ;
954+ const regex = / ( \. D [ A - Z 0 - 9 ] + U ) \s + ( .* ?) (? = \. \D [ A - Z 0 - 9 ] + U | $ ) / gs;
955+ let match ;
956+ while ( ( match = regex . exec ( cleanData ) ) !== null ) {
957+ const key = match [ 1 ] ;
958+ const value = match [ 2 ] . trim ( ) ;
959+ const animalId = value . substring ( 25 , 31 ) . toLowerCase ( ) ;
960+ if ( ! formattedObjData [ animalId ] ) {
961+ formattedObjData [ animalId ] = { } ;
962+ }
963+ formattedObjData [ animalId ] [ key ] = value ;
964+ }
965+ const skippedRows = [ ] ;
966+ const toAdd = [ ] ;
975967
976- //result.animalId = row1[2].substr(0,6);
977- result . animalId = row1 . substring ( 27 , 33 ) ;
978- result . animalId = result . animalId . toLowerCase ( ) ;
968+ // CONFIGURABLE: Define where to start extracting substrings from D2U and D6U
969+ const headerConfig = {
970+ ".D2U" : 31 , // "XN-10^854650000000017000 cj2795" (0-30) (31 length)
971+ ".D6U" : 137 , // remove header plus 106 "reserved"
972+ // Example: D2U starts with "XN-10^... cj2795", skip header, then parse fixed fields
973+ } ;
979974
980- var requestNumber = runsStore . find ( 'Id' , result . animalId )
981- var record = runsStore . getAt ( requestNumber ) ;
975+ Object . entries ( formattedObjData ) . forEach ( ( [ animalId , rawTests ] ) => {
976+ if ( ! rawTests [ ".D2U" ] || ! rawTests [ ".D6U" ] ) {
977+ alert ( "Failed to parse formatted data (missing .D1U/.D2U...) for " , animalId ) ;
978+ return ;
979+ }
980+ const d2uRaw = rawTests [ ".D2U" ] ;
981+ const d6uRaw = rawTests [ ".D6U" ] ;
982+ // Remove headers
983+
984+ const d2u = d2uRaw . slice ( headerConfig [ ".D2U" ] ) ;
985+ const d6u = d6uRaw . slice ( headerConfig [ ".D6U" ] ) ;
986+
987+ /* These tests were based on the standard definitions revision 7 and 12. The ones initially provided (revision 12) seems to be incorrect given
988+ * our current output. To best understand this data refer to revision 7.
989+ */
990+
991+ /*
992+ "Reserved" Definition: “0”s or " "(space) are set to this parameter to fill the specified number of characters.
993+ Reserved should be "0" characters but this doesn't seem to be the actual case, it looks like it is a mix
994+ of "0" and " " (space). Because of this it can cause issues with CoolTerm because CoolTerm trims whitespace,
995+ which is needed to correctly guess some values.
996+ */
997+
998+ const tests = {
999+ WBC : d2u . substring ( 0 , 6 ) ,
1000+ RBC : d2u . substring ( 6 , 11 ) ,
1001+ HGB : d2u . substring ( 11 , 16 ) ,
1002+ HCT : d2u . substring ( 16 , 21 ) ,
1003+ MCV : d2u . substring ( 21 , 26 ) ,
1004+ MCH : d2u . substring ( 26 , 31 ) ,
1005+ MCHC : d2u . substring ( 31 , 36 ) ,
1006+ PLT : d2u . substring ( 36 , 41 ) ,
1007+ LY : d2u . substring ( 41 , 46 ) ,
1008+ MN : d2u . substring ( 46 , 51 ) ,
1009+ NE : d2u . substring ( 51 , 56 ) ,
1010+ EO : d2u . substring ( 56 , 61 ) ,
1011+ BS : d2u . substring ( 61 , 66 ) ,
1012+ "RDW-CV" : d2u . substring ( 95 , 101 ) ,
1013+ "RDW-SD" : d2u . substring ( 101 , 106 ) ,
1014+ MPV : d2u . substring ( 106 , 111 ) ,
1015+ RETICULO : d2u . substring ( 121 , 126 ) ,
1016+ IRF : d2u . substring ( 131 , 136 ) ,
1017+ NRBC : d2u . substring ( 157 , 162 ) ,
1018+ 'RETIC-AB' : d2u . substring ( 179 , 185 ) ,
1019+ 'RETIC HGB' : d2u . substring ( 185 , 190 ) ,
1020+ IPF : d2u . substring ( 190 , 195 ) ,
1021+ // Start D6U Tests
1022+ PDW : d6u . substring ( 0 , 5 ) ,
1023+ "P-LCR" : d6u . substring ( 10 , 15 ) ,
1024+ LFR : d6u . substring ( 30 , 35 ) ,
1025+ MFR : d6u . substring ( 35 , 40 ) ,
1026+ HFR : d6u . substring ( 40 , 45 ) ,
1027+ PCT : d6u . substring ( 45 , 50 ) ,
1028+ } ;
9821029
1030+ // Lookup run record
1031+ const requestNumber = runsStore . find ( 'Id' , animalId ) ;
1032+ const record = runsStore . getAt ( requestNumber ) ;
1033+ let collectionDate ;
9831034 //Getting the collection time from the request itself, if it matches animalId
984- if ( requestNumber != - 1 && result . animalId == record . get ( 'Id' ) ) {
1035+ if ( requestNumber !== - 1 && animalId = == record . get ( 'Id' ) ) {
9851036
986- var collectionDate = record . get ( 'date' ) ;
1037+ collectionDate = record . get ( 'date' ) ;
9871038 }
9881039
989- //result.sequenceNo = row1[1].substr(20,4);
990- //result.date = new Date(row1[2].substr(6,4), row1[2].substr(10,2)-1, row1[2].substr(12,2));
991- //result.date = new Date(row1.substr(33,4), row1.substr(37,2)-1, row1.substr(39,2));
992- result . date = new Date ( collectionDate ) ;
993-
994- if ( ! result . animalId || runsStore . find ( 'Id' , result . animalId ) == - 1 ) {
1040+ if ( ! animalId || runsStore . find ( 'Id' , animalId ) === - 1 ) {
9951041 //alert('ID: '+result.animalId+' not found in Clinpath Runs section. Records will not be added');
996- skippedRows . push ( 'Not found in Clinpath Runs: ' + result . animalId ) ;
1042+ skippedRows . push ( 'Not found in Clinpath Runs: ' + animalId ) ;
1043+ console . log ( "Skip Row" , animalId ) ;
9971044 return ;
9981045 }
9991046
1000- tests [ 'WBC' ] = row2 . substring ( 6 , 12 ) ;
1001- tests [ 'RBC' ] = row2 . substring ( 12 , 17 ) ;
1002- tests [ 'HGB' ] = row2 . substring ( 17 , 22 ) ;
1003- tests [ 'HCT' ] = row2 . substring ( 22 , 27 ) ;
1004- tests [ 'MCV' ] = row2 . substring ( 27 , 32 ) ;
1005- tests [ 'MCH' ] = row2 . substring ( 32 , 37 ) ;
1006- tests [ 'MCHC' ] = row2 . substring ( 37 , 42 ) ;
1007- tests [ 'PLT' ] = row2 . substring ( 42 , 47 ) ;
1008- //tests['LYMPH%'] = row2.substring(47, 52);
1009- tests [ 'LY' ] = row2 . substring ( 47 , 52 ) ;
1010-
1011- //tests['MONO%'] = row2.substring(52, 57);
1012- tests [ 'MN' ] = row2 . substring ( 52 , 57 ) ;
1013-
1014- //tests['SEG%'] = row2.substring(57, 62);
1015- tests [ 'NE' ] = row2 . substring ( 57 , 62 ) ;
1016-
1017- //tests['EOSIN%'] = row2.substring(62, 67);
1018- tests [ 'EO' ] = row2 . substring ( 62 , 67 ) ;
1019-
1020- //tests['BASO%'] = row2.substring(67, 72);
1021- tests [ 'BS' ] = row2 . substring ( 67 , 72 ) ;
1022-
1023- //tests['LYMPH#'] = row2.substring(72, 78);
1024- //tests['MONO#'] = row2.substring(78, 84);
1025- //tests['SEG#'] = row2.substring(84, 90);
1026- //tests['EOSIN#'] = row2.substring(90, 96);
1027- //tests['BASO#'] = row2.substring(96, 102);
1028- //tests['RDW'] = row2.substring(102, 107);
1029- tests [ 'RDW-CV' ] = row2 . substring ( 102 , 107 ) ;
1030- tests [ 'RDW-SD' ] = row2 . substring ( 107 , 112 ) ;
1031- //tests['PDW'] = row2.substring(112, 117);
1032- tests [ 'MPV' ] = row2 . substring ( 117 , 122 ) ;
1033- //tests['P-LCR'] = row2.substring(122, 127);
1034-
1035-
1036- var value ;
1037- for ( var test in tests ) {
1038- var origVal = tests [ test ] ;
1039- value = tests [ test ] ;
1047+ const result = {
1048+ animalId,
1049+ date : new Date ( collectionDate ) ,
1050+ } ;
10401051
1041- if ( value . match ( / ^ 0 0 ( \d ) { 4 } $ / ) ) {
1042- tests [ test ] = value . substring ( 2 , 5 ) / 100 ;
1043- }
1044- //note: at the moment WBC is the only test with 6 chars, so this test is possibly redundant
1045- else if ( value . match ( / ^ 0 ( \d ) { 4 , } $ / ) && test == 'WBC' ) {
1046- tests [ test ] = value . substring ( 1 , 5 ) / 100 ;
1047- }
1048- else if ( value . match ( / ^ 0 \d { 4 } $ / ) ) {
1049- if ( test == 'RBC' ) {
1050- tests [ test ] = value . substring ( 1 , 4 ) / 100 ;
1051- }
1052- else if ( test == 'PLT' ) {
1053- tests [ test ] = value . substring ( 1 , 4 ) / 1 ; //convert to number
1054- }
1055- else {
1056- tests [ test ] = value . substring ( 1 , 4 ) / 10 ;
1052+
1053+ /*
1054+ Rules: Define the test and how many digits from the right the decimal should appear in the string.
1055+ It does not matter how many initial digits are in the string (WBC has 6 while RBC has 5).
1056+ The majority of tests are 1 decimal, so that is the default. If the test does not exist within rules
1057+ specifically it will use the default.
1058+ Ex.
1059+ Rule: 2
1060+ RBC: 000.00
1061+ WBRC: 0000.00
1062+ */
1063+ const rules = {
1064+ WBC : 2 ,
1065+ 'NRBC#' : 2 ,
1066+ RBC : 2 ,
1067+ RETICULO : 2 ,
1068+ 'RETIC-AB' : 4 ,
1069+ PLT : 0 ,
1070+ PCT : 2 ,
1071+ _default : 1
1072+ } ;
1073+
1074+ for ( var test in tests ) {
1075+ const decimal = ( test in rules ) ? rules [ test ] : rules [ '_default' ] ;
1076+ const value = tests [ test ] ;
1077+
1078+ const data = value . slice ( 0 , - 1 ) ;
1079+
1080+ const number = parseInt ( data ) ;
1081+
1082+ // Convert to string to easily manipulate decimal placement
1083+ let numberStr = number . toString ( ) ;
1084+
1085+ // If decimal is 0, no decimal point needed
1086+ if ( decimal === 0 ) {
1087+ tests [ test ] = number ;
1088+ } else {
1089+ // If we need to add a decimal point
1090+ if ( numberStr . length <= decimal ) {
1091+ // If number is shorter than decimal places, pad with zeros
1092+ numberStr = '0' . repeat ( decimal - numberStr . length ) + numberStr ;
1093+ tests [ test ] = parseFloat ( '0.' + numberStr ) ;
1094+ } else {
1095+ // Insert decimal point at the correct position from the right
1096+ const decimalIndex = numberStr . length - decimal ;
1097+ const formatted = numberStr . slice ( 0 , decimalIndex ) + '.' + numberStr . slice ( decimalIndex ) ;
1098+ tests [ test ] = parseFloat ( formatted ) ;
10571099 }
10581100 }
1059- else if ( test == 'PLT' ) {
1060- tests [ test ] = value . substring ( 0 , 4 ) ;
1061- }
1062-
1063- //NOTE: the following is a possible replacement for the logic above
1064- //it attempts to more clearly define how the parsing works
1065- //so far as i can tell, specific tests return different sets of decimals
1066- //and there is no clear way to determine decimal number without knowing the test name
1067- // if(value.match(/^(\d){5,6}$/)){
1068- // //we drop the last digit in all cases
1069- // value = value.substr(0, value.length-1);
1070- //
1071- // var decimals = 1;
1072- // //WBC is output as 10^1/uL, but reported at 10^3/ul
1073- // //RBC is output as 10^4/ul, but reported at 10^6/ul
1074- // if(test=='WBC' || test=='RBC')
1075- // decimals = 2;
1076- // if(test=='PLT')
1077- // decimals = 0;
1078- //
1079- // value = value / Math.pow(10, decimals);
1080- //
1081- // value = Ext4.util.Format.round(value, decimals);
1082- // }
1083- // else {
1084- // //alert('Value: '+value+' is not a number');
1085- // return;
1086- // }
1087- //
1088- // if(value != tests[test]){
1089- // console.log('error: '+test+'/'+tests[test]+'/'+value+'/'+decimals);
1090- // }
10911101
10921102 //find units
10931103 var idx = unitStore . find ( 'testid' , test ) ;
10941104 var units = null ;
10951105 var sortOrder = null ;
1096- if ( idx != - 1 ) {
1106+ if ( idx !== - 1 ) {
10971107 units = unitStore . getAt ( idx ) . get ( 'units' ) ;
10981108 sortOrder = unitStore . getAt ( idx ) . get ( 'sort_order' ) ;
10991109 }
@@ -1112,8 +1122,7 @@ EHR.ext.HematologyExcelWin = Ext.extend(Ext.Panel, {
11121122 sortOrder : sortOrder
11131123 } ) ;
11141124 }
1115-
1116- } , this ) ;
1125+ } ) ;
11171126
11181127 if ( toAdd . length ) {
11191128 toAdd . sort ( function ( a , b ) {
0 commit comments