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

fixed timestamp accuracy limitation for patient and visit via active_status_cd and vital_status_cd

parent 17f02000
package de.sekmi.histream.i2b2;
import java.time.temporal.ChronoUnit;
/*
* #%L
* histream
......@@ -22,6 +24,7 @@ package de.sekmi.histream.i2b2;
import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.impl.PatientImpl;
/**
......@@ -54,5 +57,190 @@ public class I2b2Patient extends PatientImpl {
public int getNum(){return patient_num;}
public void setNum(int patient_num){this.patient_num = patient_num;}
/**
* Get the i2b2 vital_status_cd for a patient.
* Values Y,M,X,R,T,S can be returned.
* @param patient patient object
* @return vital status code, see CRC_Design doc
*/
public String getVitalStatusCd(){
Patient patient = this;
char death_char=0, birth_char=0;
if( patient.getDeathDate() != null ){
switch( patient.getDeathDate().getAccuracy() ){
case DAYS:
death_char = 'Y';
break;
case MONTHS:
death_char = 'M';
break;
case YEARS:
death_char = 'X';
break;
case HOURS:
death_char = 'R';
break;
case MINUTES:
death_char = 'T';
break;
case SECONDS:
death_char = 'S';
break;
default:
}
}else{
// no death date available
Boolean deceased = patient.getDeceased();
if( deceased == null ){
death_char = 'U'; // unknown
}else if( deceased.booleanValue() == true ){
death_char = 'Z'; // deceased
}else if( deceased.booleanValue() == false ){
// living
death_char = 0; // null death char
death_char = 'N'; // same meaning
}
}
// birth date
if( patient.getBirthDate() != null ){
switch( patient.getBirthDate().getAccuracy() ){
case DAYS:
birth_char = 'D';
break;
case MONTHS:
birth_char = 'B';
break;
case YEARS:
birth_char = 'F';
break;
case HOURS:
birth_char = 'H';
break;
case MINUTES:
birth_char = 'I';
break;
case SECONDS:
birth_char = 'C';
break;
default:
}
}else{
// birth date unknown
birth_char = 'L';
}
if( death_char != 0 && birth_char != 0 )
return new String(new char[]{death_char,birth_char});
else if( death_char != 0 )
return new String(new char[]{death_char});
else if( birth_char != 0 )
return new String(new char[]{birth_char});
else return null;
}
/**
* Set the vital status code, which also determines the accuracy
* of birth date and deceased date. This method should be called AFTER
* setting the birth and death date.
*
* @param vital_cd vital code
*/
public void setVitalStatusCd(String vital_cd){
Patient patient = this;
ChronoUnit accuracy = null;
char birthIndicator = 0;
char deathIndicator = 0;
// load accuracy
if( vital_cd == null || vital_cd.length() == 0 ){
// living patient, birth date accurate to day
birthIndicator = 0;
deathIndicator = 0;
}else{
deathIndicator = vital_cd.charAt(0);
}
// death date indicator
switch( deathIndicator ){
case 'U': // unknown, no date
setDeathDate(null);
setDeceased(null);
break;
case 'Z': // deceased, no date
setDeathDate(null);
setDeceased(true);
break;
case 'Y': // deceased, accurate to day
accuracy = ChronoUnit.DAYS;
break;
case 'M': // deceased, accurate to month
accuracy = ChronoUnit.MONTHS;
break;
case 'X': // deceased, accurate to year
accuracy = ChronoUnit.YEARS;
break;
case 'R': // deceased, accurate to hour
accuracy = ChronoUnit.HOURS;
break;
case 'T': // deceased, accurate to minute
accuracy = ChronoUnit.MINUTES;
break;
case 'S': // deceased, accurate to second
accuracy = ChronoUnit.SECONDS;
break;
default:
// no match for death status -> check for birth status in first character
birthIndicator = deathIndicator;
case 0:
case 'N': // living, no date
setDeathDate(null);
setDeceased(false);
break;
}
if( patient.getDeathDate() != null && accuracy != null ){
patient.getDeathDate().setAccuracy(accuracy);
patient.setDeceased(true);
}
accuracy = null;
if( vital_cd != null && vital_cd.length() > 1 ){
// use second character if available
birthIndicator = vital_cd.charAt(1);
}
// birth date indicator
switch( birthIndicator ){
case 'L': // unknown, no date
setBirthDate(null);
break;
case 0:
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.getBirthDate() != null && accuracy != null ){
patient.getBirthDate().setAccuracy(accuracy);
}
}
}
......@@ -81,23 +81,23 @@ public class I2b2Visit extends VisitImpl {
if( visit.getEndTime() != null ){
switch( visit.getEndTime().getAccuracy() ){
case DAYS:
start_char = 0; // same meaning
start_char = 'Y';
end_char = 0; // same meaning
end_char = 'Y';
break;
case MONTHS:
start_char = 'M';
end_char = 'M';
break;
case YEARS:
start_char = 'X';
end_char = 'X';
break;
case HOURS:
start_char = 'R';
end_char = 'R';
break;
case MINUTES:
start_char = 'T';
end_char = 'T';
break;
case SECONDS:
start_char = 'S';
end_char = 'S';
break;
default:
}
......@@ -105,30 +105,30 @@ public class I2b2Visit extends VisitImpl {
// null end date
// U: unknown, O: ongoing
// default to unknown
start_char = 'U';
end_char = 'U';
}
// birth date
// start date
if( visit.getStartTime() != null ){
switch( visit.getStartTime().getAccuracy() ){
case DAYS:
end_char = 0; // same meaning
end_char = 'D';
start_char = 0; // same meaning
start_char = 'D';
break;
case MONTHS:
end_char = 'B';
start_char = 'B';
break;
case YEARS:
end_char = 'F';
start_char = 'F';
break;
case HOURS:
end_char = 'H';
start_char = 'H';
break;
case MINUTES:
end_char = 'I';
start_char = 'I';
break;
case SECONDS:
end_char = 'C';
start_char = 'C';
break;
default:
}
......@@ -136,7 +136,7 @@ public class I2b2Visit extends VisitImpl {
// null start date
// L: unknown, A: active
// default to unknown
end_char = 'L';
start_char = 'L';
}
if( end_char != 0 && start_char != 0 )
......@@ -217,6 +217,7 @@ public class I2b2Visit extends VisitImpl {
startIndicator = vital_cd.charAt(1);
}// otherwise, the first character is used if end indicator wasn't used. See default case above
accuracy = null;
// start date indicator
switch( startIndicator ){
case 'L': // unknown, no date
......
......@@ -82,6 +82,7 @@ public class PostgresPatientStore extends PostgresExtension<I2b2Patient> impleme
private char idSourceSeparator;
private Connection db;
private int fetchSize;
// TODO read only flag!!!!!! XXX
// private String autoInsertSourceId;
// maximum patient number, used to generate new patient_num for new patients
......@@ -353,7 +354,7 @@ public class PostgresPatientStore extends PostgresExtension<I2b2Patient> impleme
private void updateStorage(I2b2Patient patient) throws SQLException {
synchronized( update ){
update.setString(1, getVitalStatusCd(patient));
update.setString(1, patient.getVitalStatusCd());
update.setTimestamp(2, inaccurateSqlTimestamp(patient.getBirthDate()));
update.setTimestamp(3, inaccurateSqlTimestamp(patient.getDeathDate()));
update.setString(4, getSexCd(patient));
......@@ -403,140 +404,6 @@ public class PostgresPatientStore extends PostgresExtension<I2b2Patient> impleme
return null;
}
}
/**
* Get the i2b2 vital_status_cd for a patient.
* Values Y,M,X,R,T,S can be returned.
* @param patient patient object
* @return vital status code, see CRC_Design doc
*/
private static String getVitalStatusCd(Patient patient){
char death_char=0, birth_char=0;
if( patient.getDeathDate() != null ){
switch( patient.getDeathDate().getAccuracy() ){
case DAYS:
death_char = 'Y';
break;
case MONTHS:
death_char = 'M';
break;
case YEARS:
death_char = 'X';
break;
case HOURS:
death_char = 'R';
break;
case MINUTES:
death_char = 'T';
break;
case SECONDS:
death_char = 'S';
break;
default:
}
}
// birth date
if( patient.getBirthDate() != null ){
switch( patient.getBirthDate().getAccuracy() ){
case DAYS:
death_char = 'D';
break;
case MONTHS:
death_char = 'B';
break;
case YEARS:
death_char = 'F';
break;
case HOURS:
death_char = 'H';
break;
case MINUTES:
death_char = 'I';
break;
case SECONDS:
death_char = 'C';
break;
default:
}
}
if( death_char != 0 && birth_char != 0 )
return new String(new char[]{death_char,birth_char});
else if( death_char != 0 )
return new String(new char[]{death_char});
else if( birth_char != 0 )
return new String(new char[]{birth_char});
else return null;
}
private static void setVitalStatusCd(Patient patient, String vital_cd){
// load accuracy
if( vital_cd == null )return; // nothing to do
ChronoUnit accuracy = null;
char birthIndicator = 0;
// death date indicator
switch( vital_cd.charAt(0) ){
case 'N': // living, no date
case 'U': // unknown, no date
case 'Z': // deceased, no date
break;
case 'Y': // deceased, accurate to day
accuracy = ChronoUnit.DAYS;
break;
case 'M': // deceased, accurate to month
accuracy = ChronoUnit.MONTHS;
break;
case 'X': // deceased, accurate to year
accuracy = ChronoUnit.YEARS;
break;
case 'R': // deceased, accurate to hour
accuracy = ChronoUnit.HOURS;
break;
case 'T': // deceased, accurate to minute
accuracy = ChronoUnit.MINUTES;
break;
case 'S': // deceased, accurate to second
accuracy = ChronoUnit.SECONDS;
break;
default:
// no match for death status -> check for birth status in first character
birthIndicator = vital_cd.charAt(0);
}
if( patient.getBirthDate() != null && accuracy != null ){
patient.getBirthDate().setAccuracy(accuracy);
}
if( birthIndicator == 0 && vital_cd.length() > 1 )
birthIndicator = vital_cd.charAt(1);
// birth date indicator
switch( birthIndicator ){
case 'L': // unknown, no date
break;
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.getBirthDate() != null && accuracy != null ){
patient.getBirthDate().setAccuracy(accuracy);
}
}
private I2b2Patient loadFromResultSet(ResultSet rs) throws SQLException{
int id = rs.getInt(1);
......@@ -585,7 +452,7 @@ public class PostgresPatientStore extends PostgresExtension<I2b2Patient> impleme
patient.setSourceId(rs.getString(7));
setVitalStatusCd(patient, vital_cd);
patient.setVitalStatusCd(vital_cd);
patient.markDirty(false);
......
package de.sekmi.histream.i2b2;
import java.text.ParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import static org.junit.Assert.*;
import org.junit.Test;
import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.ext.Patient.Sex;
public class TestI2b2Patient {
private DateTimeAccuracy createAccurateTimestamp(){
try {
return DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06");
} catch (ParseException e) {
throw new AssertionError();
}
}
@Test
public void verifyAccurateTimestampParsing(){
assertEquals(ChronoUnit.SECONDS, createAccurateTimestamp().getAccuracy());
// make sure visit has full accuracy by default
I2b2Patient v = createPatientWithTimestamps();
assertEquals(ChronoUnit.SECONDS, v.getBirthDate().getAccuracy());
assertEquals(ChronoUnit.SECONDS, v.getDeathDate().getAccuracy());
}
private I2b2Patient createPatientWithTimestamps(){
I2b2Patient v = new I2b2Patient(0, Sex.male, createAccurateTimestamp(), createAccurateTimestamp());
return v;
}
@Test
public void applyNullVitalStatus(){
I2b2Patient v = createPatientWithTimestamps();
// check if the timestamps are both accurate to day
v.setVitalStatusCd(null);
assertEquals(ChronoUnit.DAYS, v.getBirthDate().getAccuracy());
assertNull(v.getDeathDate());
assertFalse(v.getDeceased());
v = createPatientWithTimestamps();
v.setVitalStatusCd("");
assertEquals(ChronoUnit.DAYS, v.getBirthDate().getAccuracy());
assertNull(v.getDeathDate());
assertFalse(v.getDeceased());
}
@Test
public void verifyMinuteAccuracy(){
I2b2Patient v = createPatientWithTimestamps();
v.setVitalStatusCd("I");
assertEquals(ChronoUnit.MINUTES, v.getBirthDate().getAccuracy());
assertNull(v.getDeathDate());
assertFalse(v.getDeceased());
v = createPatientWithTimestamps();
v.setVitalStatusCd("T");
assertEquals(ChronoUnit.DAYS, v.getBirthDate().getAccuracy());
assertEquals(ChronoUnit.MINUTES, v.getDeathDate().getAccuracy());
}
@Test
public void verifyHourAndNullAccuracy(){
I2b2Patient v = createPatientWithTimestamps();
v.setVitalStatusCd("UH");
assertEquals(ChronoUnit.HOURS, v.getBirthDate().getAccuracy());
assertEquals(4, v.getBirthDate().get(ChronoField.HOUR_OF_DAY));
assertNull(v.getDeathDate());
v = createPatientWithTimestamps();
v.setVitalStatusCd("RL");
assertNull(v.getBirthDate());
assertEquals(ChronoUnit.HOURS, v.getDeathDate().getAccuracy());
assertEquals(4, v.getDeathDate().get(ChronoField.HOUR_OF_DAY));
}
@Test
public void verifyMonthAndYearAccuracy(){
I2b2Patient v = createPatientWithTimestamps();
v.setVitalStatusCd("XB");
assertEquals(ChronoUnit.MONTHS, v.getBirthDate().getAccuracy());
assertEquals(ChronoUnit.YEARS, v.getDeathDate().getAccuracy());
v = createPatientWithTimestamps();
v.setVitalStatusCd("MF");
assertEquals(ChronoUnit.YEARS, v.getBirthDate().getAccuracy());
assertEquals(ChronoUnit.MONTHS, v.getDeathDate().getAccuracy());
}
}
package de.sekmi.histream.i2b2;
import java.text.ParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import static org.junit.Assert.*;
......@@ -12,7 +13,7 @@ public class TestI2b2Visit {
private DateTimeAccuracy createAccurateTimestamp(){
try {
return DateTimeAccuracy.parsePartialIso8601("20010203T04:05:06");
return DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06");
} catch (ParseException e) {
throw new AssertionError();
}
......@@ -20,13 +21,75 @@ public class TestI2b2Visit {
@Test
public void verifyAccurateTimestampParsing(){
assertEquals(ChronoUnit.SECONDS, createAccurateTimestamp().getAccuracy());
// make sure visit has full accuracy by default
I2b2Visit v = createVisitWithTimestamps();
assertEquals(ChronoUnit.SECONDS, v.getEndTime().getAccuracy());
assertEquals(ChronoUnit.SECONDS, v.getStartTime().getAccuracy());
}
public void verifyVisitTimestampSerialisation() throws ParseException{
private I2b2Visit createVisitWithTimestamps(){
I2b2Visit v = new I2b2Visit(0, 0);
v.setStartTime(createAccurateTimestamp());
v.setEndTime(createAccurateTimestamp());
return v;
}
@Test
public void verifyDaysAccuracy(){
I2b2Visit v = createVisitWithTimestamps();
// check if the timestamps are both accurate to day
v.setActiveStatusCd(null);
// TODO check if the timestamps are both accurate to day
// TODO more tests
assertEquals(ChronoUnit.DAYS, v.getEndTime().getAccuracy());
assertEquals(ChronoUnit.DAYS, v.getStartTime().getAccuracy());
v = createVisitWithTimestamps();
v.setActiveStatusCd("");
assertEquals(ChronoUnit.DAYS, v.getEndTime().getAccuracy());
assertEquals(ChronoUnit.DAYS, v.getStartTime().getAccuracy());
v = createVisitWithTimestamps();
v.setActiveStatusCd("YD");
assertEquals(ChronoUnit.DAYS, v.getEndTime().getAccuracy());
assertEquals(ChronoUnit.DAYS, v.getStartTime().getAccuracy());
}
@Test
public void verifyMinuteAccuracy(){
I2b2Visit v = createVisitWithTimestamps();
v.setActiveStatusCd("I");
assertEquals(ChronoUnit.MINUTES, v.getStartTime().getAccuracy());
assertEquals(ChronoUnit.DAYS, v.getEndTime().getAccuracy());
v = createVisitWithTimestamps();
v.setActiveStatusCd("T");
assertEquals(ChronoUnit.DAYS, v.getStartTime().getAccuracy());
assertEquals(ChronoUnit.MINUTES, v.getEndTime().getAccuracy());
}
@Test
public void verifyHourAndNullAccuracy(){
I2b2Visit v = createVisitWithTimestamps();
v.setActiveStatusCd("UH");
assertEquals(ChronoUnit.HOURS, v.getStartTime().getAccuracy());
assertEquals(4, v.getStartTime().get(ChronoField.HOUR_OF_DAY));
assertNull(v.getEndTime());
v = createVisitWithTimestamps();
v.setActiveStatusCd("RL");
assertNull(v.getStartTime());
assertEquals(ChronoUnit.HOURS, v.getEndTime().getAccuracy());
assertEquals(4, v.getEndTime().get(ChronoField.HOUR_OF_DAY));
}
@Test
public void verifyMonthAndYearAccuracy(){
I2b2Visit v = createVisitWithTimestamps();
v.setActiveStatusCd("XB");
assertEquals(ChronoUnit.MONTHS, v.getStartTime().getAccuracy());
assertEquals(ChronoUnit.YEARS, v.getEndTime().getAccuracy());
v = createVisitWithTimestamps();
v.setActiveStatusCd("MF");
assertEquals(ChronoUnit.YEARS, v.getStartTime().getAccuracy());
assertEquals(ChronoUnit.MONTHS, v.getEndTime().getAccuracy());
}
}
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