Commit cafea1d6 authored by R.W.Majeed's avatar R.W.Majeed

fixed encoding/decoding of visit timestamp accuracy

parent 30456119
package de.sekmi.histream.i2b2;
import java.time.temporal.ChronoUnit;
import de.sekmi.histream.ext.Visit;
/*
* #%L
* histream
......@@ -65,4 +69,200 @@ public class I2b2Visit extends VisitImpl {
public String toString(){
return "I2b2Visit(encounter_um="+encounter_num+")";
}
/**
* Get the i2b2 vital_status_cd for a patient.
* @param visit visit object
* @return vital status code, see CRC_Design doc
*/
public String getActiveStatusCd(){
Visit visit = this;
char end_char=0, start_char=0;
if( visit.getEndTime() != null ){
switch( visit.getEndTime().getAccuracy() ){
case DAYS:
start_char = 0; // same meaning
start_char = 'Y';
break;
case MONTHS:
start_char = 'M';
break;
case YEARS:
start_char = 'X';
break;
case HOURS:
start_char = 'R';
break;
case MINUTES:
start_char = 'T';
break;
case SECONDS:
start_char = 'S';
break;
default:
}
}else{
// null end date
// U: unknown, O: ongoing
// default to unknown
start_char = 'U';
}
// birth date
if( visit.getStartTime() != null ){
switch( visit.getStartTime().getAccuracy() ){
case DAYS:
end_char = 0; // same meaning
end_char = 'D';
break;
case MONTHS:
end_char = 'B';
break;
case YEARS:
end_char = 'F';
break;
case HOURS:
end_char = 'H';
break;
case MINUTES:
end_char = 'I';
break;
case SECONDS:
end_char = 'C';
break;
default:
}
}else{
// null start date
// L: unknown, A: active
// default to unknown
end_char = 'L';
}
if( end_char != 0 && start_char != 0 )
return new String(new char[]{end_char,start_char});
else if( end_char != 0 )
return new String(new char[]{end_char});
else if( start_char != 0 )
return new String(new char[]{start_char});
else return null; // should not happen
}
/**
* For decoding instructions, see the i2b2 documentation CRC_Design.pdf
* The vital cd can be one or two characters.
* This implementation is more failsafe by using the following
* algorithm:
* <ol>
* <li>For {@code null} or {@code ""} use both timestamps accurate to day
* <li>Try to decode first character as end indicator</li>
* <li>If {@code vital_cd.length > 1} use second character as start indicator, otherwise if unable to decode the end indicator, use the first character.</li>
*
* @param vital_cd code to indicate accuracy of start and end date
*/
public void setActiveStatusCd(String vital_cd){
Visit visit = this;
// load accuracy
char endIndicator = 0;
char startIndicator = 0;
if( vital_cd == null || vital_cd.length() == 0 ){
// start and end date accurate to day
// leave indicators at 0/null
}else{
// load first indicator character
endIndicator = vital_cd.charAt(0);
}
ChronoUnit accuracy = null;
// end date indicator
switch( endIndicator ){
case 'U': // unknown, no date
case 'O': // ongoing, no date
// set to null
visit.setEndTime(null);
break;
case 0:
case 'Y': // known, accurate to day
accuracy = ChronoUnit.DAYS;
break;
case 'M': // known, accurate to month
accuracy = ChronoUnit.MONTHS;
break;
case 'X': // known, accurate to year
accuracy = ChronoUnit.YEARS;
break;
case 'R': // known, accurate to hour
accuracy = ChronoUnit.HOURS;
break;
case 'T': // known, accurate to minute
accuracy = ChronoUnit.MINUTES;
break;
case 'S': // known, accurate to second
accuracy = ChronoUnit.SECONDS;
break;
default:
// no end indicator means accurate to day
accuracy = ChronoUnit.DAYS;
// no match for end date -> check for start status in first character
startIndicator = endIndicator;
}
// set accuracy for end time
if( visit.getEndTime() != null && accuracy != null ){
visit.getEndTime().setAccuracy(accuracy);
}
// load start indicator
if( vital_cd != null && vital_cd.length() > 1 ){
// use second character, if available
startIndicator = vital_cd.charAt(1);
}// otherwise, the first character is used if end indicator wasn't used. See default case above
// start date indicator
switch( startIndicator ){
case 'L': // unknown, no date
case 'A': // active, no date
setStartTime(null);
break;
case 0: // same as D
case 'D': // known, accurate to day
accuracy = ChronoUnit.DAYS;
break;
case 'B': // known, accurate to month
accuracy = ChronoUnit.MONTHS;
break;
case 'F': // known, accurate to year
accuracy = ChronoUnit.YEARS;
break;
case 'H': // known, accurate to hour
accuracy = ChronoUnit.HOURS;
break;
case 'I': // known, accurate to minute
accuracy = ChronoUnit.MINUTES;
break;
case 'C': // known, accurate to second
accuracy = ChronoUnit.SECONDS;
break;
default: // default to days if unable to parse
accuracy = ChronoUnit.DAYS;
}
if( visit.getStartTime() != null && accuracy != null ){
visit.getStartTime().setAccuracy(accuracy);
}
}
public String getInOutCd(){
Visit patient = this;
if( patient.getStatus() == null )return null;
else switch( patient.getStatus() ){
case Inpatient:
return "I";
case Outpatient:
case Emergency: // unsupported by i2b2, map to outpatient
return "O";
default:
// XXX should not happen, warning
return null;
}
}
}
......@@ -30,7 +30,6 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
......@@ -302,10 +301,10 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
private void updateStorage(I2b2Visit visit) throws SQLException {
synchronized( update ){
update.setString(1, getActiveStatusCd(visit));
update.setString(1, visit.getActiveStatusCd());
update.setTimestamp(2, inaccurateSqlTimestamp(visit.getStartTime()));
update.setTimestamp(3, inaccurateSqlTimestamp(visit.getEndTime()));
update.setString(4, getInOutCd(visit));
update.setString(4, visit.getInOutCd());
update.setString(5, visit.getLocationId());
update.setTimestamp(6, Timestamp.from(visit.getSourceTimestamp()));
update.setString(7, visit.getSourceId());
......@@ -321,19 +320,6 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
}
}
private static String getInOutCd(Visit patient){
if( patient.getStatus() == null )return null;
else switch( patient.getStatus() ){
case Inpatient:
return "I";
case Outpatient:
case Emergency: // unsupported by i2b2, map to outpatient
return "O";
default:
// XXX should not happen, warning
return null;
}
}
/**
......@@ -368,151 +354,8 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
}
}
/**
* Get the i2b2 vital_status_cd for a patient.
* TODO move method to generic i2b2 support class.
* @param visit visit object
* @return vital status code, see CRC_Design doc
*/
private static String getActiveStatusCd(Visit visit){
char end_char=0, start_char=0;
if( visit.getEndTime() != null ){
switch( visit.getEndTime().getAccuracy() ){
case DAYS:
end_char = 'Y';
break;
case MONTHS:
end_char = 'M';
break;
case YEARS:
end_char = 'X';
break;
case HOURS:
end_char = 'R';
break;
case MINUTES:
end_char = 'T';
break;
case SECONDS:
end_char = 'S';
break;
default:
}
}else{
// null end date
// TODO: U: unknown, O: ongoing
}
// birth date
if( visit.getStartTime() != null ){
switch( visit.getStartTime().getAccuracy() ){
case DAYS:
end_char = 'D';
end_char = 0; // same meaning
break;
case MONTHS:
end_char = 'B';
break;
case YEARS:
end_char = 'F';
break;
case HOURS:
end_char = 'H';
break;
case MINUTES:
end_char = 'I';
break;
case SECONDS:
end_char = 'C';
break;
default:
}
}else{
// null start date
// TODO: L: unknown, A: active
}
if( end_char != 0 && start_char != 0 )
return new String(new char[]{end_char,start_char});
else if( end_char != 0 )
return new String(new char[]{end_char});
else if( start_char != 0 )
return new String(new char[]{start_char});
else return null;
}
private void setActiveStatusCd(Visit patient, String vital_cd){
// load accuracy
if( vital_cd == null )return; // nothing to do
ChronoUnit accuracy = null;
char birthIndicator = 0;
// end date indicator
switch( vital_cd.charAt(0) ){
case 'U': // unknown, no date
case 'O': // ongoing, no date
// TODO
break;
case 0:
case 'Y': // known, accurate to day
accuracy = ChronoUnit.DAYS;
break;
case 'M': // known, accurate to month
accuracy = ChronoUnit.MONTHS;
break;
case 'X': // known, accurate to year
accuracy = ChronoUnit.YEARS;
break;
case 'R': // known, accurate to hour
accuracy = ChronoUnit.HOURS;
break;
case 'T': // known, accurate to minute
accuracy = ChronoUnit.MINUTES;
break;
case 'S': // known, accurate to second
accuracy = ChronoUnit.SECONDS;
break;
default:
// no match for end date -> check for start status in first character
birthIndicator = vital_cd.charAt(0);
}
if( patient.getEndTime() != null && accuracy != null ){
patient.getEndTime().setAccuracy(accuracy);
}
if( birthIndicator == 0 && vital_cd.length() > 1 )
birthIndicator = vital_cd.charAt(1);
// birth date indicator
switch( birthIndicator ){
case 'L': // unknown, no date
case 'A': // active, no date
// TODO
break;
case 0: // same as D
case 'D': // known, accurate to day
accuracy = ChronoUnit.DAYS;
break;
case 'B': // known, accurate to month
accuracy = ChronoUnit.MONTHS;
break;
case 'F': // known, accurate to year
accuracy = ChronoUnit.YEARS;
break;
case 'H': // known, accurate to hour
accuracy = ChronoUnit.HOURS;
break;
case 'I': // known, accurate to minute
accuracy = ChronoUnit.MINUTES;
break;
case 'C': // known, accurate to second
accuracy = ChronoUnit.SECONDS;
break;
}
if( patient.getStartTime() != null && accuracy != null ){
patient.getStartTime().setAccuracy(accuracy);
}
}
private I2b2Visit loadFromResultSet(ResultSet rs) throws SQLException{
int id = rs.getInt(1);
int patid = rs.getInt(2);
......@@ -555,7 +398,7 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
visit.setStartTime(startDate);
visit.setEndTime(endDate);
visit.setStatus(status);
setActiveStatusCd(visit, active_status_cd);
visit.setActiveStatusCd(active_status_cd);
visit.setLocationId(rs.getString(7));
visit.setSourceTimestamp(rs.getTimestamp(8).toInstant());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment