...
 
Commits (16)
...@@ -37,13 +37,14 @@ ...@@ -37,13 +37,14 @@
<birthdate>2001-01-01</birthdate> <birthdate>2001-01-01</birthdate>
<deathdate>2020</deathdate> <deathdate>2020</deathdate>
<sex>F</sex> <sex>F</sex>
<encounter start="2014-01-01T10:30:00" end="2014-01-05T10:30:00">XXE12345</encounter> <encounter start="2014-01-01T10:30:00" end="2014-01-05T10:30:00.123">XXE12345</encounter>
<location>Zuhause</location> <location>Zuhause</location>
<!-- TODO inpatient/outpatient --> <!-- TODO inpatient/outpatient -->
<provider>xxxa</provider> <provider>xxxa</provider>
<facts> <facts>
<!-- test parsing of partial time stamps --> <!-- test parsing of partial time stamps -->
<eav-item concept="T:date:secs" start="2014-09-07T10:40:03"/> <eav-item concept="T:date:secs" start="2014-09-07T10:40:03"/>
<eav-item concept="T:date:msec" start="2014-09-07T10:40:03.123"/>
<eav-item concept="T:date:mins" start="2014-09-07T10:40"/> <eav-item concept="T:date:mins" start="2014-09-07T10:40"/>
<eav-item concept="T:date:hours" start="2014-09-07T10"/> <eav-item concept="T:date:hours" start="2014-09-07T10"/>
<eav-item concept="T:date:day" start="2014-09-07"/> <eav-item concept="T:date:day" start="2014-09-07"/>
......
...@@ -23,9 +23,10 @@ XX12345 XXE12345 gest dat 2020 ...@@ -23,9 +23,10 @@ XX12345 XXE12345 gest dat 2020
XX12345 XXE12345 sex str F XX12345 XXE12345 sex str F
XX12345 XXE12345 psurname str Dampf XX12345 XXE12345 psurname str Dampf
XX12345 XXE12345 pnames str A B XX12345 XXE12345 pnames str A B
XX12345 XXE12345 visit @ @ @ 2014-01-01T10:30:00 2014-01-05T10:30:00 xxxa Zuhause XX12345 XXE12345 visit @ @ @ 2014-01-01T10:30:00 2014-01-05T10:30:00.123 xxxa Zuhause
# normale werte # normale werte
XX12345 XXE12345 T:date:secs @ @ @ 2014-09-07T10:40:03 XX12345 XXE12345 T:date:secs @ @ @ 2014-09-07T10:40:03
XX12345 XXE12345 T:date:msec @ @ @ 2014-09-07T10:40:03.123
XX12345 XXE12345 T:date:mins @ @ @ 2014-09-07T10:40 XX12345 XXE12345 T:date:mins @ @ @ 2014-09-07T10:40
XX12345 XXE12345 T:date:hours @ @ @ 2014-09-07T10 XX12345 XXE12345 T:date:hours @ @ @ 2014-09-07T10
XX12345 XXE12345 T:date:day @ @ @ 2014-09-07 XX12345 XXE12345 T:date:day @ @ @ 2014-09-07
......
...@@ -51,7 +51,7 @@ import de.sekmi.histream.xml.DateTimeAccuracyAdapter; ...@@ -51,7 +51,7 @@ import de.sekmi.histream.xml.DateTimeAccuracyAdapter;
*/ */
@XmlJavaTypeAdapter(DateTimeAccuracyAdapter.class) @XmlJavaTypeAdapter(DateTimeAccuracyAdapter.class)
public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> { public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> {
static final String PARTIAL_FORMATTER_PATTERN = "u[-M[-d['T'H[:m[:s[.S]]][X]]]]"; static final String PARTIAL_FORMATTER_PATTERN = "u[-M[-d['T'H[:m[:s[.SSS]]][X]]]]";
static final DateTimeFormatter PARTIAL_FORMATTER = DateTimeFormatter.ofPattern(PARTIAL_FORMATTER_PATTERN); static final DateTimeFormatter PARTIAL_FORMATTER = DateTimeFormatter.ofPattern(PARTIAL_FORMATTER_PATTERN);
// TODO why not use instant, since we always calculate UTC? or Offset/ZonedDateTime? // TODO why not use instant, since we always calculate UTC? or Offset/ZonedDateTime?
...@@ -67,33 +67,33 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> { ...@@ -67,33 +67,33 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> {
this.accuracy = ChronoUnit.SECONDS; this.accuracy = ChronoUnit.SECONDS;
} }
@Deprecated @Deprecated
public DateTimeAccuracy(int year) { public DateTimeAccuracy(ZoneId zone, int year) {
instant = LocalDateTime.of(year, 1, 1, 0, 0).toInstant(ZoneOffset.UTC); instant = LocalDateTime.of(year, 1, 1, 0, 0).atZone(zone).toInstant();
accuracy = ChronoUnit.YEARS; accuracy = ChronoUnit.YEARS;
} }
@Deprecated @Deprecated
public DateTimeAccuracy(int year, int month) { public DateTimeAccuracy(ZoneId zone, int year, int month) {
instant = LocalDateTime.of(year, month, 1, 0, 0).toInstant(ZoneOffset.UTC); instant = LocalDateTime.of(year, month, 1, 0, 0).atZone(zone).toInstant();
accuracy = ChronoUnit.MONTHS; accuracy = ChronoUnit.MONTHS;
} }
@Deprecated @Deprecated
public DateTimeAccuracy(int year, int month, int day) { public DateTimeAccuracy(ZoneId zone, int year, int month, int day) {
instant = LocalDateTime.of(year, month, day, 0, 0).toInstant(ZoneOffset.UTC); instant = LocalDateTime.of(year, month, day, 0, 0).atZone(zone).toInstant();
accuracy = ChronoUnit.DAYS; accuracy = ChronoUnit.DAYS;
} }
@Deprecated @Deprecated
public DateTimeAccuracy(int year, int month, int day, int hours) { public DateTimeAccuracy(ZoneId zone, int year, int month, int day, int hours) {
instant = LocalDateTime.of(year, month, day, hours, 0).toInstant(ZoneOffset.UTC); instant = LocalDateTime.of(year, month, day, hours, 0).atZone(zone).toInstant();
accuracy = ChronoUnit.HOURS; accuracy = ChronoUnit.HOURS;
} }
@Deprecated @Deprecated
public DateTimeAccuracy(int year, int month, int day, int hours, int mins) { public DateTimeAccuracy(ZoneId zone, int year, int month, int day, int hours, int mins) {
instant = LocalDateTime.of(year, month, day, hours, mins).toInstant(ZoneOffset.UTC); instant = LocalDateTime.of(year, month, day, hours, mins).atZone(zone).toInstant();
accuracy = ChronoUnit.MINUTES; accuracy = ChronoUnit.MINUTES;
} }
@Deprecated @Deprecated
public DateTimeAccuracy(int year, int month, int day, int hours, int mins, int secs) { public DateTimeAccuracy(ZoneId zone, int year, int month, int day, int hours, int mins, int secs) {
instant = LocalDateTime.of(year, month, day, hours, mins, secs).toInstant(ZoneOffset.UTC); instant = LocalDateTime.of(year, month, day, hours, mins, secs).atZone(zone).toInstant();
accuracy = ChronoUnit.SECONDS; accuracy = ChronoUnit.SECONDS;
} }
...@@ -201,9 +201,9 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> { ...@@ -201,9 +201,9 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> {
dt = instant.atOffset(ZoneOffset.UTC).toLocalDateTime(); dt = instant.atOffset(ZoneOffset.UTC).toLocalDateTime();
} }
char[] prefixes = {0,'-','-','T',':',':'}; char[] prefixes = {0,'-','-','T',':',':','.'};
ChronoField[] fields = {ChronoField.YEAR, ChronoField.MONTH_OF_YEAR, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE}; ChronoField[] fields = {ChronoField.YEAR, ChronoField.MONTH_OF_YEAR, ChronoField.DAY_OF_MONTH, ChronoField.HOUR_OF_DAY, ChronoField.MINUTE_OF_HOUR, ChronoField.SECOND_OF_MINUTE,ChronoField.MILLI_OF_SECOND};
int[] digits = {4,2,2,2,2,2}; int[] digits = {4,2,2,2,2,2,3};
int i; int i;
for( i=0; i<fields.length; i++ ){ for( i=0; i<fields.length; i++ ){
if( prefixes[i] != 0 )b.append(prefixes[i]); if( prefixes[i] != 0 )b.append(prefixes[i]);
...@@ -285,8 +285,9 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> { ...@@ -285,8 +285,9 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> {
if( a.isSupported(ChronoField.NANO_OF_SECOND) ){ if( a.isSupported(ChronoField.NANO_OF_SECOND) ){
// maximum accuracy of nanoseconds // maximum accuracy of nanoseconds
// not supported yet, truncate to seconds // not supported yet, truncate to seconds
accuracy = ChronoUnit.NANOS; accuracy = ChronoUnit.MILLIS;
dateTime = LocalDateTime.from(a); // dateTime = LocalDateTime.from(a);
dateTime = LocalDateTime.of(a.get(ChronoField.YEAR), a.get(ChronoField.MONTH_OF_YEAR), a.get(ChronoField.DAY_OF_MONTH), a.get(ChronoField.HOUR_OF_DAY), a.get(ChronoField.MINUTE_OF_HOUR), a.get(ChronoField.SECOND_OF_MINUTE), a.get(ChronoField.NANO_OF_SECOND));
}else if( a.isSupported(ChronoField.SECOND_OF_MINUTE) ){ }else if( a.isSupported(ChronoField.SECOND_OF_MINUTE) ){
accuracy = ChronoUnit.SECONDS; accuracy = ChronoUnit.SECONDS;
dateTime = LocalDateTime.of(a.get(ChronoField.YEAR), a.get(ChronoField.MONTH_OF_YEAR), a.get(ChronoField.DAY_OF_MONTH), a.get(ChronoField.HOUR_OF_DAY), a.get(ChronoField.MINUTE_OF_HOUR), a.get(ChronoField.SECOND_OF_MINUTE)); dateTime = LocalDateTime.of(a.get(ChronoField.YEAR), a.get(ChronoField.MONTH_OF_YEAR), a.get(ChronoField.DAY_OF_MONTH), a.get(ChronoField.HOUR_OF_DAY), a.get(ChronoField.MINUTE_OF_HOUR), a.get(ChronoField.SECOND_OF_MINUTE));
...@@ -334,54 +335,64 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> { ...@@ -334,54 +335,64 @@ public class DateTimeAccuracy implements Comparable<DateTimeAccuracy> {
* *
* @param formatter formatter * @param formatter formatter
* @param text input text * @param text input text
* @param zoneId time zone to use, if the parser doesn't supply a time zone or offset
* @return date time with accuracy * @return date time with accuracy
*/ */
public static DateTimeAccuracy parse(DateTimeFormatter formatter, CharSequence text){ public static DateTimeAccuracy parse(DateTimeFormatter formatter, CharSequence text, ZoneId zoneId){
ParsePosition pos = new ParsePosition(0); ParsePosition pos = new ParsePosition(0);
TemporalAccessor a = formatter.parseUnresolved(text, pos); TemporalAccessor a = formatter.parseUnresolved(text, pos);
if( pos.getErrorIndex() != -1 ){ if( pos.getErrorIndex() != -1 ){
throw new DateTimeParseException("Text '"+String.valueOf(text.charAt(pos.getErrorIndex()))+"' could not be parsed at index "+pos.getErrorIndex(), text, pos.getErrorIndex()); throw new DateTimeParseException("Text '"+text+"' could not be parsed at index "+pos.getErrorIndex(), text, pos.getErrorIndex());
}else if( pos.getIndex() != text.length() ){ }else if( pos.getIndex() != text.length() ){
throw new DateTimeParseException("Unparsed text found at index "+pos.getIndex(), text, pos.getIndex()); throw new DateTimeParseException("Unparsed text found at index "+pos.getIndex(), text, pos.getIndex());
} }
try{
int offset = a.get(ChronoField.OFFSET_SECONDS);
// explicit offset specified, use that information
zoneId = ZoneOffset.ofTotalSeconds(offset);
}catch( DateTimeException e ){
// no offset available
// use default specified in zoneId param
}
int year = a.get(ChronoField.YEAR); int year = a.get(ChronoField.YEAR);
// month // month
int month; int month;
try{ try{
month = a.get(ChronoField.MONTH_OF_YEAR); month = a.get(ChronoField.MONTH_OF_YEAR);
}catch( DateTimeException e ){ }catch( DateTimeException e ){
return new DateTimeAccuracy(year); return new DateTimeAccuracy(zoneId, year);
} }
int day; int day;
try{ try{
day = a.get(ChronoField.DAY_OF_MONTH); day = a.get(ChronoField.DAY_OF_MONTH);
}catch( DateTimeException e ){ }catch( DateTimeException e ){
return new DateTimeAccuracy(year,month); return new DateTimeAccuracy(zoneId, year,month);
} }
int hour; int hour;
try{ try{
hour = a.get(ChronoField.HOUR_OF_DAY); hour = a.get(ChronoField.HOUR_OF_DAY);
}catch( DateTimeException e ){ }catch( DateTimeException e ){
return new DateTimeAccuracy(year,month,day); return new DateTimeAccuracy(zoneId, year,month,day);
} }
int minute; int minute;
try{ try{
minute = a.get(ChronoField.MINUTE_OF_HOUR); minute = a.get(ChronoField.MINUTE_OF_HOUR);
}catch( DateTimeException e ){ }catch( DateTimeException e ){
return new DateTimeAccuracy(year,month,day, hour); return new DateTimeAccuracy(zoneId, year,month,day, hour);
} }
int seconds; int seconds;
try{ try{
seconds = a.get(ChronoField.SECOND_OF_MINUTE); seconds = a.get(ChronoField.SECOND_OF_MINUTE);
}catch( DateTimeException e ){ }catch( DateTimeException e ){
return new DateTimeAccuracy(year,month,day, hour, minute); return new DateTimeAccuracy(zoneId, year,month,day, hour, minute);
} }
return new DateTimeAccuracy(year,month,day, hour, minute, seconds); return new DateTimeAccuracy(zoneId, year,month,day, hour, minute, seconds);
// milliseconds not supported for now // milliseconds not supported for now
} }
@Override @Override
......
...@@ -47,7 +47,7 @@ public class SimpleVisitExtension implements Extension<VisitImpl>{ ...@@ -47,7 +47,7 @@ public class SimpleVisitExtension implements Extension<VisitImpl>{
} }
VisitImpl visit = new VisitImpl(); VisitImpl visit = new VisitImpl();
visit.setId((String)args[0]); visit.setId((String)args[0]);
visit.setPatientId(((Patient)args[1]).getId()); visit.setPatient(((Patient)args[1]));
ExternalSourceType source = (ExternalSourceType)args[2]; ExternalSourceType source = (ExternalSourceType)args[2];
visit.setSourceId(source.getSourceId()); visit.setSourceId(source.getSourceId());
visit.setSourceTimestamp(source.getSourceTimestamp()); visit.setSourceTimestamp(source.getSourceTimestamp());
......
package de.sekmi.histream.impl; package de.sekmi.histream.impl;
import java.util.Objects;
/* /*
* #%L * #%L
* histream * histream
...@@ -24,6 +26,7 @@ package de.sekmi.histream.impl; ...@@ -24,6 +26,7 @@ package de.sekmi.histream.impl;
import de.sekmi.histream.DateTimeAccuracy; import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.ext.StoredExtensionType; import de.sekmi.histream.ext.StoredExtensionType;
import de.sekmi.histream.ext.Visit; import de.sekmi.histream.ext.Visit;
...@@ -34,28 +37,36 @@ public class VisitImpl extends StoredExtensionType implements Visit { ...@@ -34,28 +37,36 @@ public class VisitImpl extends StoredExtensionType implements Visit {
private String patientId; private String patientId;
private String locationId; private String locationId;
public VisitImpl(){ /**
* Empty constructor protected, only
* available to overriding classes.
*/
protected VisitImpl() {
} }
public VisitImpl(String id, String patientId, DateTimeAccuracy startTime){
public VisitImpl(String id, String patientId, DateTimeAccuracy startTime, DateTimeAccuracy endTime, Status status){
setId(id); setId(id);
this.patientId = patientId; this.patientId = patientId;
this.startTime = startTime;
markDirty(true);
}
public VisitImpl(String id, String patientId, DateTimeAccuracy startTime, DateTimeAccuracy endTime, Status status){
this(id, patientId, startTime);
this.status = status; this.status = status;
this.startTime = startTime;
this.endTime = endTime; this.endTime = endTime;
markDirty(true);
} }
public String getPatientId(){return patientId;} public String getPatientId(){return patientId;}
public void setPatientId(String patientId){ public void setPatient(Patient patient){
Objects.requireNonNull(patient);
// patient id should not be changed normally. // patient id should not be changed normally.
this.patientId = patientId; this.patientId = patient.getId();
// TODO need to update dirty flag? // TODO need to update dirty flag?
markDirty(true); markDirty(true);
} }
@Override @Override
public DateTimeAccuracy getStartTime() { public DateTimeAccuracy getStartTime() {
return startTime; return startTime;
......
...@@ -13,6 +13,8 @@ import javax.xml.stream.XMLStreamException; ...@@ -13,6 +13,8 @@ import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter; import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result; import javax.xml.transform.Result;
import org.w3c.dom.DOMException;
import de.sekmi.histream.Observation; import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationException; import de.sekmi.histream.ObservationException;
import de.sekmi.histream.ext.ExternalSourceType; import de.sekmi.histream.ext.ExternalSourceType;
...@@ -394,7 +396,7 @@ public class GroupedXMLWriter extends GroupedObservationHandler{ ...@@ -394,7 +396,7 @@ public class GroupedXMLWriter extends GroupedObservationHandler{
formatIndent(); formatIndent();
marshalFactWithContext(observation, observation.getExtension(Visit.class), meta.source); marshalFactWithContext(observation, observation.getExtension(Visit.class), meta.source);
formatNewline(); formatNewline();
} catch (JAXBException | XMLStreamException e) { } catch (JAXBException | XMLStreamException | DOMException e) {
throw new ObservationException(e); throw new ObservationException(e);
} }
this.observationCount ++; this.observationCount ++;
......
...@@ -36,6 +36,7 @@ import de.sekmi.histream.Observation; ...@@ -36,6 +36,7 @@ import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationFactory; import de.sekmi.histream.ObservationFactory;
import de.sekmi.histream.ObservationSupplier; import de.sekmi.histream.ObservationSupplier;
// TODO remove this class
@Deprecated @Deprecated
public class XMLObservationSupplier extends XMLObservationParser implements ObservationSupplier{ public class XMLObservationSupplier extends XMLObservationParser implements ObservationSupplier{
//private static final String namespaceURI = "http://sekmi.de/histream/dwh-eav"; //private static final String namespaceURI = "http://sekmi.de/histream/dwh-eav";
......
...@@ -23,7 +23,7 @@ public class TestDateTimeAccuracy { ...@@ -23,7 +23,7 @@ public class TestDateTimeAccuracy {
public void testParseYYYYDD(){ public void testParseYYYYDD(){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M.u"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M.u");
String text = "02.2003"; String text = "02.2003";
DateTimeAccuracy a = DateTimeAccuracy.parse(formatter, text); DateTimeAccuracy a = DateTimeAccuracy.parse(formatter, text, ZoneOffset.UTC);
Assert.assertEquals(ChronoUnit.MONTHS,a.getAccuracy()); Assert.assertEquals(ChronoUnit.MONTHS,a.getAccuracy());
TemporalAccessor ac = a.toInstantMin().atOffset(ZoneOffset.UTC); TemporalAccessor ac = a.toInstantMin().atOffset(ZoneOffset.UTC);
Assert.assertEquals(2, ac.get(ChronoField.MONTH_OF_YEAR)); Assert.assertEquals(2, ac.get(ChronoField.MONTH_OF_YEAR));
...@@ -35,10 +35,10 @@ public class TestDateTimeAccuracy { ...@@ -35,10 +35,10 @@ public class TestDateTimeAccuracy {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d.M.u[ H[:m[:s]]]"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d.M.u[ H[:m[:s]]]");
DateTimeAccuracy a; DateTimeAccuracy a;
ZoneId tz = ZoneId.of("Asia/Shanghai"); // China standard time ZoneId tz = ZoneId.of("Asia/Shanghai"); // China standard time
a = DateTimeAccuracy.parse(formatter, "01.02.2003"); a = DateTimeAccuracy.parse(formatter, "01.02.2003", ZoneOffset.UTC);
Assert.assertEquals("2003-02-01", a.toPartialIso8601(null)); Assert.assertEquals("2003-02-01", a.toPartialIso8601(null));
Assert.assertEquals("2003-02-01", a.toPartialIso8601(tz)); Assert.assertEquals("2003-02-01", a.toPartialIso8601(tz));
a = DateTimeAccuracy.parse(formatter, "01.02.2003 13"); a = DateTimeAccuracy.parse(formatter, "01.02.2003 13", ZoneOffset.UTC);
Assert.assertEquals(ChronoUnit.HOURS, a.getAccuracy()); Assert.assertEquals(ChronoUnit.HOURS, a.getAccuracy());
Assert.assertEquals("2003-02-01T13", a.toPartialIso8601(null)); Assert.assertEquals("2003-02-01T13", a.toPartialIso8601(null));
Assert.assertEquals("2003-02-01T21+0800", a.toPartialIso8601(tz)); Assert.assertEquals("2003-02-01T21+0800", a.toPartialIso8601(tz));
...@@ -82,6 +82,8 @@ public class TestDateTimeAccuracy { ...@@ -82,6 +82,8 @@ public class TestDateTimeAccuracy {
assertEquals(ChronoUnit.MINUTES, a.getAccuracy()); assertEquals(ChronoUnit.MINUTES, a.getAccuracy());
a = DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06"); a = DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06");
assertEquals(ChronoUnit.SECONDS, a.getAccuracy()); assertEquals(ChronoUnit.SECONDS, a.getAccuracy());
a = DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06.789");
assertEquals(ChronoUnit.MILLIS, a.getAccuracy());
// verify zone offset // verify zone offset
// for second accuracy // for second accuracy
a = DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06+0800"); a = DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06+0800");
...@@ -103,13 +105,13 @@ public class TestDateTimeAccuracy { ...@@ -103,13 +105,13 @@ public class TestDateTimeAccuracy {
formatter.withResolverStyle(ResolverStyle.STRICT); formatter.withResolverStyle(ResolverStyle.STRICT);
DateTimeAccuracy a; DateTimeAccuracy a;
a = DateTimeAccuracy.parse(formatter, "01.02.2003"); a = DateTimeAccuracy.parse(formatter, "01.02.2003", ZoneOffset.UTC);
Assert.assertEquals(ChronoUnit.DAYS,a.getAccuracy()); Assert.assertEquals(ChronoUnit.DAYS,a.getAccuracy());
a = DateTimeAccuracy.parse(formatter, "01.02.2003 13"); a = DateTimeAccuracy.parse(formatter, "01.02.2003 13", ZoneOffset.UTC);
Assert.assertEquals(ChronoUnit.HOURS,a.getAccuracy()); Assert.assertEquals(ChronoUnit.HOURS,a.getAccuracy());
a = DateTimeAccuracy.parse(formatter, "01.02.2003 13:14"); a = DateTimeAccuracy.parse(formatter, "01.02.2003 13:14", ZoneOffset.UTC);
Assert.assertEquals(ChronoUnit.MINUTES,a.getAccuracy()); Assert.assertEquals(ChronoUnit.MINUTES,a.getAccuracy());
} }
...@@ -144,16 +146,16 @@ public class TestDateTimeAccuracy { ...@@ -144,16 +146,16 @@ public class TestDateTimeAccuracy {
} }
// verify same behavior for DateTimeAccurecy // verify same behavior for DateTimeAccurecy
// should not fail below // should not fail below
DateTimeAccuracy a = DateTimeAccuracy.parse(formatter, "2003"); DateTimeAccuracy a = DateTimeAccuracy.parse(formatter, "2003" ,ZoneOffset.UTC);
Assert.assertEquals(ChronoUnit.YEARS, a.getAccuracy()); Assert.assertEquals(ChronoUnit.YEARS, a.getAccuracy());
// next two calls should throw exceptions // next two calls should throw exceptions
try{ try{
DateTimeAccuracy.parse(formatter, "+"); DateTimeAccuracy.parse(formatter, "+", ZoneOffset.UTC);
Assert.fail("Exception unexpected input"); Assert.fail("Exception unexpected input");
}catch( DateTimeParseException e ){ }catch( DateTimeParseException e ){
} }
try{ try{
DateTimeAccuracy.parse(formatter, "2003+"); DateTimeAccuracy.parse(formatter, "2003+", ZoneOffset.UTC);
Assert.fail("Exception expected for unparsed text at end of input"); Assert.fail("Exception expected for unparsed text at end of input");
}catch( DateTimeParseException e ){ }catch( DateTimeParseException e ){
} }
...@@ -165,6 +167,13 @@ public class TestDateTimeAccuracy { ...@@ -165,6 +167,13 @@ public class TestDateTimeAccuracy {
assertEquals("2001-02-02T20Z", a.toPartialIso8601(ZoneId.of("UTC"))); assertEquals("2001-02-02T20Z", a.toPartialIso8601(ZoneId.of("UTC")));
} }
@Test
public void verifyMillisecondParseAndToString() throws ParseException{
DateTimeAccuracy a = DateTimeAccuracy.parsePartialIso8601("2001-02-03T04:05:06.789", ZoneId.of("UTC"));
// date should be treated as if it had a +08:00 offset
assertEquals("2001-02-03T04:05:06.789Z", a.toPartialIso8601(ZoneId.of("UTC")));
}
@Test @Test
public void verifyComparison() throws ParseException{ public void verifyComparison() throws ParseException{
ZoneId zone = ZoneOffset.UTC.normalized(); ZoneId zone = ZoneOffset.UTC.normalized();
......
...@@ -35,9 +35,10 @@ public class ECMAEvaluatorTest { ...@@ -35,9 +35,10 @@ public class ECMAEvaluatorTest {
} }
// skip to string value // skip to string value
for( int i=0; i<6; i++ ){ for( int i=0; i<7; i++ ){
o = s.get(); o = s.get();
} }
Assert.assertEquals("T:type:str", o.getConceptId());
// compare string value // compare string value
Assert.assertEquals(true, eval.test("fact.value != null", o)); Assert.assertEquals(true, eval.test("fact.value != null", o));
......
...@@ -8,6 +8,7 @@ import java.io.StringWriter; ...@@ -8,6 +8,7 @@ import java.io.StringWriter;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.URL; import java.net.URL;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneOffset;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
import javax.xml.bind.JAXB; import javax.xml.bind.JAXB;
...@@ -61,7 +62,7 @@ public class ObservationImplJAXBTest { ...@@ -61,7 +62,7 @@ public class ObservationImplJAXBTest {
o.conceptId = "C"+index; o.conceptId = "C"+index;
o.patientId = "P"+index; o.patientId = "P"+index;
o.encounterId = "E"+index; o.encounterId = "E"+index;
o.startTime = new DateTimeAccuracy(2015,1,1,index); o.startTime = new DateTimeAccuracy(ZoneOffset.UTC, 2015,1,1,index);
switch( index ){ switch( index ){
case 0: case 0:
// string value // string value
......
...@@ -38,9 +38,10 @@ public class XPathEvaluatorTest { ...@@ -38,9 +38,10 @@ public class XPathEvaluatorTest {
Assert.assertEquals(expr, true, eval.test(expr, o)); Assert.assertEquals(expr, true, eval.test(expr, o));
} }
// skip to string value // skip to string value
for( int i=0; i<6; i++ ){ for( int i=0; i<7; i++ ){
o = s.get(); o = s.get();
} }
Assert.assertEquals("T:type:str", o.getConceptId());
// compare string value // compare string value
Assert.assertEquals(true, eval.test("f:fact/f:value='abc123'", o)); Assert.assertEquals(true, eval.test("f:fact/f:value='abc123'", o));
......
...@@ -101,7 +101,7 @@ public class FileObservationProviderTest { ...@@ -101,7 +101,7 @@ public class FileObservationProviderTest {
Assert.assertEquals("Zuhause", v.getLocationId()); Assert.assertEquals("Zuhause", v.getLocationId());
try{ try{
Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2014-01-01T10:30:00"), v.getStartTime()); Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2014-01-01T10:30:00"), v.getStartTime());
Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2014-01-05T10:30:00"), v.getEndTime()); Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2014-01-05T10:30:00.123"), v.getEndTime());
} catch (ParseException e) { } catch (ParseException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
...@@ -111,6 +111,12 @@ public class FileObservationProviderTest { ...@@ -111,6 +111,12 @@ public class FileObservationProviderTest {
ExternalSourceType s = o.getSource(); ExternalSourceType s = o.getSource();
Assert.assertNotNull(s); Assert.assertNotNull(s);
}, },
(Observation o) -> {
Assert.assertEquals("T:date:msec", o.getConceptId());
// TODO store and calculate time in nanos
Assert.assertEquals(ChronoUnit.MILLIS, o.getStartTime().getAccuracy());
Assert.assertEquals(123, o.getStartTime().toInstantMin().atOffset(ZoneOffset.UTC).getLong(ChronoField.MILLI_OF_SECOND));
},
(Observation o) -> { (Observation o) -> {
Assert.assertEquals("T:date:mins", o.getConceptId()); Assert.assertEquals("T:date:mins", o.getConceptId());
Assert.assertEquals(ChronoUnit.MINUTES, o.getStartTime().getAccuracy()); Assert.assertEquals(ChronoUnit.MINUTES, o.getStartTime().getAccuracy());
...@@ -232,6 +238,7 @@ public class FileObservationProviderTest { ...@@ -232,6 +238,7 @@ public class FileObservationProviderTest {
@Test @Test
public void testStAXReader() throws FileNotFoundException, XMLStreamException, FactoryConfigurationError { public void testStAXReader() throws FileNotFoundException, XMLStreamException, FactoryConfigurationError {
// TODO delete XMLObservationSupplier
XMLObservationSupplier xos = new XMLObservationSupplier(factory, new FileInputStream("examples/dwh-eav.xml")); XMLObservationSupplier xos = new XMLObservationSupplier(factory, new FileInputStream("examples/dwh-eav.xml"));
validateExample(xos); validateExample(xos);
xos.close(); xos.close();
......
...@@ -7,11 +7,17 @@ import java.io.InputStream; ...@@ -7,11 +7,17 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.text.ParseException;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset;
import javax.xml.XMLConstants; import javax.xml.XMLConstants;
import javax.xml.bind.JAXB; import javax.xml.bind.JAXB;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
...@@ -33,10 +39,19 @@ import org.w3c.dom.Node; ...@@ -33,10 +39,19 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import de.sekmi.histream.DateTimeAccuracy; import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationException; import de.sekmi.histream.ObservationException;
import de.sekmi.histream.ObservationFactory;
import de.sekmi.histream.ObservationSupplier; import de.sekmi.histream.ObservationSupplier;
import de.sekmi.histream.ext.ExternalSourceType;
import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.ext.Visit;
import de.sekmi.histream.impl.ExternalSourceImpl; import de.sekmi.histream.impl.ExternalSourceImpl;
import de.sekmi.histream.impl.Meta; import de.sekmi.histream.impl.Meta;
import de.sekmi.histream.impl.ObservationFactoryImpl;
import de.sekmi.histream.impl.SimplePatientExtension;
import de.sekmi.histream.impl.SimpleVisitExtension;
import de.sekmi.histream.impl.StringValue;
import de.sekmi.histream.xml.XMLUtils; import de.sekmi.histream.xml.XMLUtils;
public class TestXMLWriter { public class TestXMLWriter {
...@@ -66,6 +81,7 @@ public class TestXMLWriter { ...@@ -66,6 +81,7 @@ public class TestXMLWriter {
Document doc = builder.newDocument(); Document doc = builder.newDocument();
doc.getDomConfig().setParameter("namespaces", true); doc.getDomConfig().setParameter("namespaces", true);
doc.getDomConfig().setParameter("namespace-declarations", true); doc.getDomConfig().setParameter("namespace-declarations", true);
// TODO check if DOM can be configured to allow newline characters in values
// not suppoted by default implementation // not suppoted by default implementation
// doc.getDomConfig().setParameter("canonical-form", true); // doc.getDomConfig().setParameter("canonical-form", true);
...@@ -102,6 +118,19 @@ public class TestXMLWriter { ...@@ -102,6 +118,19 @@ public class TestXMLWriter {
w.writeEndDocument(); w.writeEndDocument();
} }
@Test
public void testMarshallNewlineInValues() throws ParserConfigurationException, ParseException {
DOMResult result = new DOMResult(createDocument());
ObservationFactory factory = new ObservationFactoryImpl();
factory.registerExtension(new SimplePatientExtension());
factory.registerExtension(new SimpleVisitExtension());
Observation o = factory.createObservation("A", "B", DateTimeAccuracy.parsePartialIso8601("2018", ZoneOffset.UTC.normalized()));
o.setValue(new StringValue("1\n2"));
JAXB.marshal(o, result);
}
@Test @Test
public void testStreamWriterNamespaces() throws XMLStreamException, ParserConfigurationException{ public void testStreamWriterNamespaces() throws XMLStreamException, ParserConfigurationException{
XMLOutputFactory factory = XMLOutputFactory.newInstance(); XMLOutputFactory factory = XMLOutputFactory.newInstance();
...@@ -164,6 +193,42 @@ public class TestXMLWriter { ...@@ -164,6 +193,42 @@ public class TestXMLWriter {
reader.close(); reader.close();
} }
} }
@XmlRootElement
private static class ValType{
public ValType() {}
public ValType(String s) { this.value = s;}
@XmlValue
String value;
}
@Test
public void testWriteDOM2() throws Exception{
// create document
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
// f.setNamespaceAware(true);
// f.setCoalescing(true);
// f.setIgnoringComments(true);
DocumentBuilder builder = f.newDocumentBuilder();
Document doc = builder.newDocument();
// doc.getDomConfig().setParameter("namespaces", true);
// doc.getDomConfig().setParameter("namespace-declarations", true);
DOMResult dr = new DOMResult(doc);
XMLOutputFactory factory = XMLOutputFactory.newInstance();
// enable repairing namespaces to remove duplicate namespace declarations by JAXB marshal
// this does not work with the DOM stream writer
// factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
XMLStreamWriter writer = factory.createXMLStreamWriter(dr);
Marshaller marshaller = JAXBContext.newInstance(ValType.class).createMarshaller();
// marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.marshal(new ValType("1\n2"), writer);
doc.normalizeDocument();
XMLUtils.printDOM(doc, debugLog);
}
@Test @Test
public void testWriteDOM() throws Exception{ public void testWriteDOM() throws Exception{
...@@ -175,6 +240,17 @@ public class TestXMLWriter { ...@@ -175,6 +240,17 @@ public class TestXMLWriter {
w.setZoneId(ZoneId.of("Asia/Shanghai")); w.setZoneId(ZoneId.of("Asia/Shanghai"));
Meta.transfer(s, w); Meta.transfer(s, w);
Streams.transfer(s, w); Streams.transfer(s, w);
// manually create observation
Observation o = t.getFactory().createObservation("A", "B", DateTimeAccuracy.parsePartialIso8601("2018", ZoneOffset.UTC.normalized()));
o.setValue(new StringValue("1\n2"));
ExternalSourceType es = new ExternalSourceImpl("manual", Instant.now());
o.setSource(es);
Patient pat = t.getFactory().getExtension(Patient.class).createInstance("A",es);
o.setExtension(Patient.class, pat);
o.setExtension(Visit.class, t.getFactory().getExtension(Visit.class).createInstance("V",pat,es));
w.accept(o);
w.close(); w.close();
s.close(); s.close();
......
...@@ -40,13 +40,14 @@ ...@@ -40,13 +40,14 @@
<source timestamp="2015-09-28T09:41:10Z"/> <source timestamp="2015-09-28T09:41:10Z"/>
<encounter id="XXE12345"> <encounter id="XXE12345">
<start>2014-01-01T10:30:00</start> <start>2014-01-01T10:30:00</start>
<end>2014-01-05T10:30:00</end> <end>2014-01-05T10:30:00.123</end>
<location>Zuhause</location> <location>Zuhause</location>
<!-- TODO inpatient/outpatient <!-- TODO inpatient/outpatient
<provider>xxxa</provider>--> <provider>xxxa</provider>-->
<source timestamp="2015-09-28T08:41:10Z"/> <source timestamp="2015-09-28T08:41:10Z"/>
<!-- no more <facts> group --> <!-- no more <facts> group -->
<fact concept="T:date:secs" start="2014-09-07T10:40:03"/> <fact concept="T:date:secs" start="2014-09-07T10:40:03"/>
<fact concept="T:date:msec" start="2014-09-07T10:40:03.123"/>
<fact concept="T:date:mins" start="2014-09-07T10:40"/> <fact concept="T:date:mins" start="2014-09-07T10:40"/>
<fact concept="T:date:hours" start="2014-09-07T10"/> <fact concept="T:date:hours" start="2014-09-07T10"/>
<fact concept="T:date:day" start="2014-09-07"/> <fact concept="T:date:day" start="2014-09-07"/>
......
...@@ -140,7 +140,7 @@ abstract class VisitFragmentParser extends GroupedXMLWriter { ...@@ -140,7 +140,7 @@ abstract class VisitFragmentParser extends GroupedXMLWriter {
/** /**
* Called after each patient fragment was parsed. * Called after each patient fragment was parsed.
* The patient fragment does not contain any encounters, * The patient fragment does not contain any encounters,
* these are provided to {@link #visitFragment(Node)}. * these are provided to {@link #visitFragment(Element)}.
* @param patient patient node * @param patient patient node
* @throws ObservationException error * @throws ObservationException error
*/ */
......
...@@ -14,8 +14,8 @@ class Table implements TableWriter{ ...@@ -14,8 +14,8 @@ class Table implements TableWriter{
private final CSVWriter export; private final CSVWriter export;
private PrintWriter out; private PrintWriter out;
/** /**
* @param csvWriter * @param csvWriter CSV writer
* @throws IOException * @throws IOException IO error
*/ */
Table(CSVWriter csvWriter, String filename) throws IOException { Table(CSVWriter csvWriter, String filename) throws IOException {
export = csvWriter; export = csvWriter;
......
...@@ -44,8 +44,8 @@ public class TestExport { ...@@ -44,8 +44,8 @@ public class TestExport {
Assert.assertEquals("T:type:str", m.get(MemoryExportWriter.VISIT_TABLE, "byclass", 0)); Assert.assertEquals("T:type:str", m.get(MemoryExportWriter.VISIT_TABLE, "byclass", 0));
m.dump(); m.dump();
// verify eav table // verify eav table
Assert.assertEquals(6, m.rowCount("eavtabletest")); Assert.assertEquals(7, m.rowCount("eavtabletest"));
Assert.assertEquals("T:date:month", m.get("eavtabletest", "code", 4)); Assert.assertEquals("T:date:month", m.get("eavtabletest", "code", 5));
// TODO something wrong with namespaces in xpath/dom // TODO something wrong with namespaces in xpath/dom
} }
......
...@@ -46,7 +46,7 @@ public class TestVisitFragmentParser { ...@@ -46,7 +46,7 @@ public class TestVisitFragmentParser {
// System.out.println("XPath="+ret); // System.out.println("XPath="+ret);
ret = (String)xp.evaluate("count(eav:fact)", visit, XPathConstants.STRING); ret = (String)xp.evaluate("count(eav:fact)", visit, XPathConstants.STRING);
// System.out.println("Facts:"+ret); // System.out.println("Facts:"+ret);
Assert.assertEquals("12", ret); Assert.assertEquals("13", ret);
} }
} }
...@@ -58,8 +58,8 @@ public abstract class I2b2Extractor implements ObservationSupplier { ...@@ -58,8 +58,8 @@ public abstract class I2b2Extractor implements ObservationSupplier {
* <p> * <p>
* *
* </p> * </p>
* @param factory * @param factory extractor factory
* @param dbc * @param dbc database connection
* @throws SQLException error * @throws SQLException error
*/ */
I2b2Extractor(I2b2ExtractorFactory factory, Connection dbc) throws SQLException { I2b2Extractor(I2b2ExtractorFactory factory, Connection dbc) throws SQLException {
......
...@@ -2,6 +2,7 @@ package de.sekmi.histream.i2b2; ...@@ -2,6 +2,7 @@ package de.sekmi.histream.i2b2;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.ext.Visit; import de.sekmi.histream.ext.Visit;
/* /*
...@@ -64,7 +65,17 @@ public class I2b2Visit extends VisitImpl { ...@@ -64,7 +65,17 @@ public class I2b2Visit extends VisitImpl {
public int getNum(){return encounter_num;} public int getNum(){return encounter_num;}
public int getPatientNum(){return patient_num;} public int getPatientNum(){return patient_num;}
@Override
public void setPatient(Patient patient) {
super.setPatient(patient);
if( patient instanceof I2b2Patient ) {
// also set the patient_num
int patient_num = ((I2b2Patient)patient).getNum();
this.patient_num = patient_num;
}
}
@Override @Override
public String toString(){ public String toString(){
return "I2b2Visit(encounter_um="+encounter_num+")"; return "I2b2Visit(encounter_um="+encounter_num+")";
......
...@@ -53,6 +53,7 @@ import de.sekmi.histream.ext.Visit.Status; ...@@ -53,6 +53,7 @@ import de.sekmi.histream.ext.Visit.Status;
* <p> * <p>
* Some optional columns are used: active_status_cd, start_date, end_date, inout_cd, location_cd, sourcesystem_cd * Some optional columns are used: active_status_cd, start_date, end_date, inout_cd, location_cd, sourcesystem_cd
* <p> * <p>
* XXX after loading encounters, the String patientId not set anymore and always null. To determine the patientId, the patientStore is required for lookup of the patientNum
* TODO use encounter_mapping table to map actual (source) patient_ide to internal patient_num for facts. * TODO use encounter_mapping table to map actual (source) patient_ide to internal patient_num for facts.
* <p> * <p>
* The variable argument list for {@link #createInstance(Object...)} requires the following arguments: * The variable argument list for {@link #createInstance(Object...)} requires the following arguments:
...@@ -137,14 +138,14 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements ...@@ -137,14 +138,14 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
db.setAutoCommit(true); db.setAutoCommit(true);
prepareStatements(); prepareStatements();
loadMaxEncounterNum(); loadMaxEncounterNum();
batchLoad(); batchLoad(); /// XXX loading visits does not set the String patientId, for that, the patientStore would be needed
} }
@Override @Override
protected void prepareStatements() throws SQLException { protected void prepareStatements() throws SQLException {
// TODO: use prefix from configuration to specify tablespace // TODO: use prefix from configuration to specify tablespace
insert = db.prepareStatement("INSERT INTO visit_dimension(encounter_num, patient_num, import_date, download_date, sourcesystem_cd) VALUES(?,?,current_timestamp,?,?)"); insert = db.prepareStatement("INSERT INTO visit_dimension(encounter_num, patient_num, import_date, download_date, sourcesystem_cd) VALUES(?,?,current_timestamp,?,?)");
insertMapping = db.prepareStatement("INSERT INTO encounter_mapping(encounter_num, encounter_ide, encounter_ide_source, patient_ide, patient_ide_source, encounter_ide_status, project_id, import_date, download_date, sourcesystem_cd) VALUES(?,?,?,?,?,'A','"+projectId+"',current_timestamp,?,?)"); insertMapping = db.prepareStatement("INSERT INTO encounter_mapping(encounter_num, encounter_ide, encounter_ide_source, patient_ide, patient_ide_source, encounter_ide_status, project_id, import_date, download_date, sourcesystem_cd) VALUES(?,?,?,?,?,'A','"+projectId+"',current_timestamp,?,?)");
update = db.prepareStatement("UPDATE visit_dimension SET active_status_cd=?, start_date=?, end_date=?, inout_cd=?, location_cd=?, update_date=current_timestamp, download_date=?, sourcesystem_cd=? WHERE encounter_num=?"); update = db.prepareStatement("UPDATE visit_dimension SET patient_num=?, active_status_cd=?, start_date=?, end_date=?, inout_cd=?, location_cd=?, update_date=current_timestamp, download_date=?, sourcesystem_cd=? WHERE encounter_num=?");
//select = db.prepareStatement("SELECT encounter_num, patient_num, active_status_cd, start_date, end_date, inout_cd, location_cd, update_date, sourcesystem_cd FROM visit_dimension WHERE patient_num=?"); //select = db.prepareStatement("SELECT encounter_num, patient_num, active_status_cd, start_date, end_date, inout_cd, location_cd, update_date, sourcesystem_cd FROM visit_dimension WHERE patient_num=?");
selectAll = db.prepareStatement("SELECT encounter_num, patient_num, active_status_cd, start_date, end_date, inout_cd, location_cd, download_date, sourcesystem_cd FROM visit_dimension", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); selectAll = db.prepareStatement("SELECT encounter_num, patient_num, active_status_cd, start_date, end_date, inout_cd, location_cd, download_date, sourcesystem_cd FROM visit_dimension", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
selectAll.setFetchSize(fetchSize); selectAll.setFetchSize(fetchSize);
...@@ -310,16 +311,17 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements ...@@ -310,16 +311,17 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
private void updateStorage(I2b2Visit visit) throws SQLException { private void updateStorage(I2b2Visit visit) throws SQLException {
synchronized( update ){ synchronized( update ){
update.setString(1, visit.getActiveStatusCd()); update.setInt(1, visit.getPatientNum());
update.setTimestamp(2, dialect.encodeInstantPartial(visit.getStartTime())); update.setString(2, visit.getActiveStatusCd());
update.setTimestamp(3, dialect.encodeInstantPartial(visit.getEndTime())); update.setTimestamp(3, dialect.encodeInstantPartial(visit.getStartTime()));
update.setString(4, visit.getInOutCd()); update.setTimestamp(4, dialect.encodeInstantPartial(visit.getEndTime()));
update.setString(5, dialect.encodeLocationCd(visit.getLocationId())); update.setString(5, visit.getInOutCd());
update.setTimestamp(6, dialect.encodeInstant(visit.getSourceTimestamp())); update.setString(6, dialect.encodeLocationCd(visit.getLocationId()));
update.setString(7, visit.getSourceId()); update.setTimestamp(7, dialect.encodeInstant(visit.getSourceTimestamp()));
update.setString(8, visit.getSourceId());
// where encounter_num=visit.getNum() // where encounter_num=visit.getNum()
update.setInt(8, visit.getNum()); update.setInt(9, visit.getNum());
int rows = update.executeUpdate(); int rows = update.executeUpdate();
if( rows == 0 ){ if( rows == 0 ){
log.warning("UPDATE executed for visit_dimension.encounter_num="+visit.getNum()+", but no rows changed."); log.warning("UPDATE executed for visit_dimension.encounter_num="+visit.getNum()+", but no rows changed.");
...@@ -368,7 +370,8 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements ...@@ -368,7 +370,8 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
private I2b2Visit loadFromResultSet(ResultSet rs) throws SQLException{ private I2b2Visit loadFromResultSet(ResultSet rs) throws SQLException{
int id = rs.getInt(1); int id = rs.getInt(1);
int patid = rs.getInt(2); int patid = rs.getInt(2);
// XXX String patientId is always null after loading from the database.
// load vital status code, which contains information about // load vital status code, which contains information about
// accuracy of birth and death dates. // accuracy of birth and death dates.
String active_status_cd = rs.getString(3); String active_status_cd = rs.getString(3);
...@@ -426,10 +429,12 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements ...@@ -426,10 +429,12 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
I2b2Visit visit = idCache.get(encounterId); I2b2Visit visit = idCache.get(encounterId);
if( visit == null ){ if( visit == null ){
// visit does not exist, create a new one
maxEncounterNum ++; maxEncounterNum ++;
int encounter_num = maxEncounterNum; int encounter_num = maxEncounterNum;
visit = new I2b2Visit(encounter_num, patient.getNum()); visit = new I2b2Visit(encounter_num, patient.getNum());
visit.setPatientId(patient.getId()); visit.setPatient(patient);
// created from observation, use source metadata // created from observation, use source metadata
visit.setSourceId(source.getSourceId()); visit.setSourceId(source.getSourceId());
...@@ -451,12 +456,17 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements ...@@ -451,12 +456,17 @@ public class PostgresVisitStore extends PostgresExtension<I2b2Visit> implements
// commonly, the item is modified after a call to this method, // commonly, the item is modified after a call to this method,
// but changes are written later via a call to update. // but changes are written later via a call to update.
// (otherwise, the instance would need to know whether to perform INSERT or UPDATE) // (otherwise, the instance would need to know whether to perform INSERT or UPDATE)
}else if( rejectPatientChange ){ }else {
// visit already existing // visit already existing
// verify that the patient number from the visit matches with the observation // verify that the patient number from the visit matches with the observation
if( visit.getPatientNum() != patient.getNum() ){ if( visit.getPatientNum() != patient.getNum() ){
// throw exception to abort processing // throw exception to abort processing
throw new AssertionError("Patient_num mismatch between observation and visit", null); if( rejectPatientChange ){
throw new AssertionError("Patient_num mismatch for visit "+encounterId+": history says "+visit.getPatientNum()+" while data says "+patient.getNum(), null);
}else {
log.info("Updating visit #"+visit.getNum()+" for patient change from #"+visit.getPatientNum()+" to #"+patient.getNum());
visit.setPatient(patient);
}
} }
} }
return visit; return visit;
......
...@@ -6,6 +6,7 @@ import java.io.InputStreamReader; ...@@ -6,6 +6,7 @@ import java.io.InputStreamReader;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.charset.Charset;
import java.time.Instant; import java.time.Instant;
import com.opencsv.CSVParser; import com.opencsv.CSVParser;
...@@ -19,7 +20,7 @@ public class FileRowSupplier extends RowSupplier { ...@@ -19,7 +20,7 @@ public class FileRowSupplier extends RowSupplier {
private Instant timestamp; private Instant timestamp;
public FileRowSupplier(URL location, String fieldSeparator) throws IOException{ public FileRowSupplier(URL location, String fieldSeparator, Charset charset) throws IOException{
if( fieldSeparator.length() > 1 ){ if( fieldSeparator.length() > 1 ){
if( fieldSeparator.equals("\\t") ){ if( fieldSeparator.equals("\\t") ){
fieldSeparator = "\t"; fieldSeparator = "\t";
...@@ -28,7 +29,7 @@ public class FileRowSupplier extends RowSupplier { ...@@ -28,7 +29,7 @@ public class FileRowSupplier extends RowSupplier {
} }
} }
this.url = location; this.url = location;
this.in = new CSVReader(new InputStreamReader(location.openStream()),fieldSeparator.charAt(0), CSVParser.DEFAULT_QUOTE_CHARACTER, (char)0); this.in = new CSVReader(new InputStreamReader(location.openStream(), charset),fieldSeparator.charAt(0), CSVParser.DEFAULT_QUOTE_CHARACTER, (char)0);
// TODO: check whether needed to close underlying InputStream // TODO: check whether needed to close underlying InputStream
......
...@@ -3,13 +3,15 @@ package de.sekmi.histream.etl; ...@@ -3,13 +3,15 @@ package de.sekmi.histream.etl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.Observation; import de.sekmi.histream.Observation;
import de.sekmi.histream.impl.VisitImpl; import de.sekmi.histream.impl.VisitImpl;
public class VisitRow extends VisitImpl implements FactRow{ public class VisitRow extends VisitImpl implements FactRow{
List<Observation> facts; List<Observation> facts;
public VisitRow(){ public VisitRow(String visitId, String patientId, DateTimeAccuracy startTime){
super(visitId, patientId, startTime);
facts = new ArrayList<>(); facts = new ArrayList<>();
} }
@Override @Override
...@@ -21,6 +23,4 @@ public class VisitRow extends VisitImpl implements FactRow{ ...@@ -21,6 +23,4 @@ public class VisitRow extends VisitImpl implements FactRow{
public String getVisitId() { public String getVisitId() {
return this.getId(); return this.getId();
} }
} }
...@@ -3,6 +3,7 @@ package de.sekmi.histream.etl.config; ...@@ -3,6 +3,7 @@ package de.sekmi.histream.etl.config;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.charset.Charset;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
...@@ -32,7 +33,7 @@ public class CsvFile extends TableSource{ ...@@ -32,7 +33,7 @@ public class CsvFile extends TableSource{
String url; String url;
/** /**
* File encoding is not used yet. * Encoding to use for reading text files
*/ */
@XmlElement @XmlElement
String encoding; String encoding;
...@@ -44,11 +45,11 @@ public class CsvFile extends TableSource{ ...@@ -44,11 +45,11 @@ public class CsvFile extends TableSource{
@XmlElement @XmlElement
String separator; String separator;
@XmlElement // @XmlElement
String quote; // String quote;
//
@XmlElement // @XmlElement
char escape; // char escape;
private CsvFile(){ private CsvFile(){
} }
...@@ -59,9 +60,18 @@ public class CsvFile extends TableSource{ ...@@ -59,9 +60,18 @@ public class CsvFile extends TableSource{
} }
@Override @Override
public RowSupplier rows(Meta meta) throws IOException { public RowSupplier rows(Meta meta) throws IOException {
// resolve url relative to base url from metadata
URL base = meta.getLocation(); URL base = meta.getLocation();
URL source = (base == null)?new URL(url):new URL(base, url); URL source = (base == null)?new URL(url):new URL(base, url);
return new FileRowSupplier(source, separator); // determine charset
Charset charset;
if( encoding != null ) {
charset = Charset.forName(encoding);
}else{
// if not defined, use system charset
charset = Charset.defaultCharset();
}
return new FileRowSupplier(source, separator, charset);
} }
} }
package de.sekmi.histream.etl.config; package de.sekmi.histream.etl.config;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
...@@ -26,7 +27,9 @@ public class DateTimeColumn extends Column<DateTimeAccuracy>{ ...@@ -26,7 +27,9 @@ public class DateTimeColumn extends Column<DateTimeAccuracy>{
*/ */
@XmlAttribute @XmlAttribute
String format; String format;
@XmlAttribute
String zone;
public DateTimeColumn(String name, String format){ public DateTimeColumn(String name, String format){
super(name); super(name);
...@@ -56,9 +59,15 @@ public class DateTimeColumn extends Column<DateTimeAccuracy>{ ...@@ -56,9 +59,15 @@ public class DateTimeColumn extends Column<DateTimeAccuracy>{
if( formatter == null ){ if( formatter == null ){
throw new ParseException("format must be specified for DateTime fields if strings are parsed"); throw new ParseException("format must be specified for DateTime fields if strings are parsed");
} }
ZoneId zoneId;
if( zone != null ){
zoneId = ZoneId.of(zone);
}else{
zoneId = ZoneId.systemDefault();
}
// parse // parse
try{ try{
return DateTimeAccuracy.parse(formatter,input); return DateTimeAccuracy.parse(formatter,input, zoneId);
}catch( DateTimeParseException e ){ }catch( DateTimeParseException e ){
throw new ParseException("Unable to parse date '"+input+"' in column '"+this.column+"'", e); throw new ParseException("Unable to parse date '"+input+"' in column '"+this.column+"'", e);
} }
......
...@@ -84,19 +84,18 @@ public class VisitTable extends Table<VisitRow> implements ConceptTable{ ...@@ -84,19 +84,18 @@ public class VisitTable extends Table<VisitRow> implements ConceptTable{
@Override @Override
public VisitRow fillRecord(ColumnMap map, Object[] row, ObservationFactory factory) throws ParseException { public VisitRow fillRecord(ColumnMap map, Object[] row, ObservationFactory factory) throws ParseException {
VisitRow visit = new VisitRow(); String vid = idat.visitId.valueOf(map, row);
visit.setId(idat.visitId.valueOf(map, row)); String pid = idat.patientId.valueOf(map, row);
visit.setPatientId(idat.patientId.valueOf(map, row));
DateTimeAccuracy start = idat.start.valueOf(map, row); DateTimeAccuracy start = idat.start.valueOf(map, row);
if( start != null ){ if( start == null ){
visit.setStartTime(start);
}else{
// no start time specified for visit row // no start time specified for visit row
// any other way to retrieve a timestamp?? // any other way to retrieve a timestamp??
// ignore row // ignore row
// TODO issue warning // TODO issue warning
return null; return null;
} }
VisitRow visit = new VisitRow(vid, pid, start);
if( idat.end != null ){ if( idat.end != null ){
visit.setEndTime(idat.end.valueOf(map, row)); visit.setEndTime(idat.end.valueOf(map, row));
} }
......
...@@ -18,21 +18,29 @@ public class DuplicateFactFilter extends PostProcessingFilter{ ...@@ -18,21 +18,29 @@ public class DuplicateFactFilter extends PostProcessingFilter{
@XmlElement @XmlElement
public String[] concept; public String[] concept;
private static class FactComparator implements Comparator<Fact>{ static class FactComparator implements Comparator<Fact>{
@Override @Override
public int compare(Fact o1, Fact o2) { public int compare(Fact o1, Fact o2) {
int cmp = o1.getObservation().getStartTime().compareTo( return DuplicateFactFilter.compare(o1, o2);
o2.getObservation().getStartTime() ); }
if( cmp == 0 ){ }
// if times are equal, sort by concept public static int compare(Fact o1, Fact o2) {
cmp = o1.getConcept().compareTo(o2.getConcept()); int cmp = o1.getObservation().getStartTime().compareTo(
} o2.getObservation().getStartTime() );
return cmp; if( true )return cmp;
if( cmp == 0 ){
// if times are equal, sort by concept
cmp = o1.getConcept().compareTo(o2.getConcept());
} }
return cmp;
} }
private void removeAllDuplicates(AbstractFacts facts){ private void removeAllDuplicates(AbstractFacts facts){
// order by start and concept // order by start and concept
facts.sort( new FactComparator() ); if( true ) {
throw new UnsupportedOperationException("Not yet implemented");
}
//facts.sort( new FactComparator() );
ArrayList<Integer> duplicates = new ArrayList<>(); ArrayList<Integer> duplicates = new ArrayList<>();
......
package de.sekmi.histream.etl; package de.sekmi.histream.etl;
import java.io.IOException; import java.io.IOException;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -11,13 +13,11 @@ import org.junit.Test; ...@@ -11,13 +13,11 @@ import org.junit.Test;
import de.sekmi.histream.DateTimeAccuracy; import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.Observation; import de.sekmi.histream.Observation;
import de.sekmi.histream.ObservationFactory;
import de.sekmi.histream.ObservationSupplier; import de.sekmi.histream.ObservationSupplier;
import de.sekmi.histream.ext.ExternalSourceType; import de.sekmi.histream.ext.ExternalSourceType;
import de.sekmi.histream.ext.Patient; import de.sekmi.histream.ext.Patient;
import de.sekmi.histream.ext.Visit; import de.sekmi.histream.ext.Visit;
import de.sekmi.histream.impl.Meta; import de.sekmi.histream.impl.Meta;
import de.sekmi.histream.impl.ObservationFactoryImpl;
import de.sekmi.histream.io.GroupedXMLWriter; import de.sekmi.histream.io.GroupedXMLWriter;
import de.sekmi.histream.io.Streams; import de.sekmi.histream.io.Streams;
...@@ -103,8 +103,9 @@ public class TestETLSupplier { ...@@ -103,8 +103,9 @@ public class TestETLSupplier {
Patient p = fact.getExtension(Patient.class); Patient p = fact.getExtension(Patient.class);
Assert.assertNotNull(p); Assert.assertNotNull(p);
Assert.assertEquals("p1", p.getId()); Assert.assertEquals("p1", p.getId());
Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2003-02-01"), p.getBirthDate()); ZoneId zone = ZoneId.systemDefault();
Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2003-02-11"), p.getDeathDate()); Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2003-02-01",zone), p.getBirthDate());
Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2003-02-11",zone), p.getDeathDate());
// TODO verify other patient information // TODO verify other patient information
Assert.assertEquals("v1", p.getGivenName()); Assert.assertEquals("v1", p.getGivenName());
...@@ -128,7 +129,7 @@ public class TestETLSupplier { ...@@ -128,7 +129,7 @@ public class TestETLSupplier {
Assert.assertEquals("v1", v.getId()); Assert.assertEquals("v1", v.getId());
// TODO make sure custom partial date format is parsed correctly for missing seconds // TODO make sure custom partial date format is parsed correctly for missing seconds
//Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2013-03-20T09:00"), v.getStartTime()); //Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2013-03-20T09:00"), v.getStartTime());
Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2013-03-21T13:00:21"), v.getEndTime()); Assert.assertEquals(DateTimeAccuracy.parsePartialIso8601("2013-03-21T13:00:21", ZoneId.systemDefault()), v.getEndTime());
Assert.assertEquals("v1", v.getId()); Assert.assertEquals("v1", v.getId());
Assert.assertEquals(null, v.getLocationId()); Assert.assertEquals(null, v.getLocationId());
......
package de.sekmi.histream.etl; package de.sekmi.histream.etl;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
...@@ -9,7 +10,7 @@ public class TestRowSupplier { ...@@ -9,7 +10,7 @@ public class TestRowSupplier {
@Test @Test
public void testLoadRows() throws IOException{ public void testLoadRows() throws IOException{
try( FileRowSupplier r = new FileRowSupplier(getClass().getResource("/data/test-1-patients.txt"), "\t") ){ try( FileRowSupplier r = new FileRowSupplier(getClass().getResource("/data/test-1-patients.txt"), "\t", StandardCharsets.ISO_8859_1) ){
String[] h = r.getHeaders(); String[] h = r.getHeaders();
Assert.assertEquals("patid", h[0]); Assert.assertEquals("patid", h[0]);
Assert.assertEquals("nachname", h[2]); Assert.assertEquals("nachname", h[2]);
......
...@@ -69,10 +69,13 @@ public class TestMarshall { ...@@ -69,10 +69,13 @@ public class TestMarshall {
// check post processing // check post processing
Assert.assertNotNull(ds.postProcessing); Assert.assertNotNull(ds.postProcessing);
Assert.assertEquals(3, ds.postProcessing.filter.length); Assert.assertEquals(2, ds.postProcessing.filter.length);
Assert.assertEquals(DuplicateFactFilter.class, ds.postProcessing.filter[0].getClass()); // duplicate fact filter removed for now
DuplicateFactFilter f = (DuplicateFactFilter)ds.postProcessing.filter[0]; // Assert.assertEquals(DuplicateFactFilter.class, ds.postProcessing.filter[0].getClass());
// DuplicateFactFilter f = (DuplicateFactFilter)ds.postProcessing.filter[0];
// Assert.assertEquals(1, f.concept.length);
ScriptFilter sf = (ScriptFilter)ds.postProcessing.filter[1]; ScriptFilter sf = (ScriptFilter)ds.postProcessing.filter[1];
Assert.assertNotNull(sf);
// check script // check script
/* Assert.assertEquals(2, ds.scripts.length); /* Assert.assertEquals(2, ds.scripts.length);
......
package de.sekmi.histream.etl.filter;
import java.text.ParseException;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import javax.script.ScriptException;
import org.junit.Test;
import de.sekmi.histream.DateTimeAccuracy;
import de.sekmi.histream.ObservationFactory;
import de.sekmi.histream.impl.ObservationFactoryImpl;
import de.sekmi.histream.impl.SimplePatientExtension;
import de.sekmi.histream.impl.SimpleVisitExtension;
import de.sekmi.histream.scripting.AbstractFacts;
import de.sekmi.histream.scripting.EncounterScriptEngine;
import de.sekmi.histream.scripting.Fact;
import static org.junit.Assert.*;
public class TestDuplicateFactFilter {
// XXX implement filter first
//@Test
public void verifyComparator() throws ScriptException, ParseException{
ObservationFactory of = new ObservationFactoryImpl(new SimplePatientExtension(), new SimpleVisitExtension());
DuplicateFactFilter filter = new DuplicateFactFilter();
EncounterScriptEngine e = new EncounterScriptEngine();
e.setObservationFactory(of);
AbstractFacts facts = e.wrapEncounterFacts("P1", "E1", DateTimeAccuracy.parsePartialIso8601("2001-02-03", ZoneId.systemDefault()), new ArrayList<>());
Fact a = facts.add("lala");
Fact b = facts.add("xx");
// assertTrue( DuplicateFactFilter.compare(a, b) < 0 );
b.start("2000-01-02T03:04:05Z");
// assertTrue( DuplicateFactFilter.compare(a, b) > 0 );
Fact c = facts.add("xx").start("2000-01-02T03:05Z");
Fact d = facts.add("lala"); // add duplicate
// assertTrue( DuplicateFactFilter.compare(c, b) > 0 );
// assertTrue( DuplicateFactFilter.compare(a, d) == 0 );
// assertTrue( DuplicateFactFilter.compare(a, c) > 0 );
// assertTrue( DuplicateFactFilter.compare(d, c) > 0 );
filter.processVisit(facts);
assertEquals(3, facts.size());
// TODO compare with timestamps
}
}
...@@ -5,7 +5,6 @@ import org.junit.Test; ...@@ -5,7 +5,6 @@ import org.junit.Test;
import de.sekmi.histream.ObservationSupplier; import de.sekmi.histream.ObservationSupplier;
import de.sekmi.histream.etl.ETLObservationSupplier; import de.sekmi.histream.etl.ETLObservationSupplier;
import de.sekmi.histream.etl.config.DataSource;
import de.sekmi.histream.io.Streams; import de.sekmi.histream.io.Streams;
public class TestValidator { public class TestValidator {
...@@ -70,7 +69,8 @@ public class TestValidator { ...@@ -70,7 +69,8 @@ public class TestValidator {
} }
Assert.fail("Exception expected"); Assert.fail("Exception expected");
} }
@Test // XXX implement duplicate fact filter first
//@Test
public void validateData4WithDuplicateFilter() throws Exception{ public void validateData4WithDuplicateFilter() throws Exception{
// duplicate concepts // duplicate concepts
try( ObservationSupplier os = ETLObservationSupplier.load(getClass().getResource("/data/test-4-datasource2.xml")) ){ try( ObservationSupplier os = ETLObservationSupplier.load(getClass().getResource("/data/test-4-datasource2.xml")) ){
......
IK;Ausbildungsstätte;Ausbildungsstätten-Typ;Ausbildungsplätze-insgesamt;Ausbildungsplätze-des-KH;Ausbildungsplätze-für-andere-KH;Ausbildende;Auszubildende-im-eigenen-KH;Azubis-J1;Azubis-J2;Azubis-J3;Auszubildende-an-anderen-KH;Ausbildungsvergütungen;Personalkosten-je-VK;Kosten-Unterricht;Kosten-praktische-Ausbildung;Sachaufwand-Ausbildungsstätte;Gemeinkosten-Ausbildungsstätte;Vereinbarte-Gesamtkosten-Ausbildungsstätte
260100012;A05;1;30;30;0;2,00;29,00;12,00;9,00;8,00;0,00;290000,00;45532,78;60555,21;30672,10;10321,11;22081,20;20000,00
260100012;A06;1;20;20;0;2,00;20,00;7,00;6,00;7,00;0,00;220000,00;45556,18;60000,01;20214,20;4567,69;17238,08;22000,00
IK;DRG-Flle-vereinbart;DRG-Flle-abgerechnet;Bewertungsrelationen-vereinbart;Bewertungsrelationen-abgerechnet;Erlsausgleich-4-Abs-3
260100012;12000;11970;11876,32;10957,45;475,22
IK;Entlassender-Standort;Entgeltbereich;KH-internes-Kennzeichen;IK-Krankenkasse;Entgeltart;Entgeltbetrag;Abrechnung-von;Abrechnung-bis;Entgeltanzahl;Tage-ohne-Berechnung-Behandlung;Tag-der-Behandlung
260100012;01;PSY;6;101556856;01032900;185,65;20170113;20170120;8;0;
260100012;01;PSY;6;101556856;01032900;185,65;20170108;20170110;3;0;
260100012;01;PSY;6;101556856;01032900;185,65;20170101;20170105;5;0;
260100012;01;PSY;6;101556856;00000000;2970,40;20170101;20170120;1;4;
260100012;01;DRG;1;111556856;7010I47B;6803,08;20170519;20170601;1;0;
260100012;01;DRG;1;111556856;00000000;6803,08;20170519;20170601;1;0;
260100012;01;DRG;2;111556856;7010I47B;6803,08;20170519;20170601;1;2;
260100012;01;DRG;2;111556856;00000000;6803,08;20170519;20170601;1;2;
260100012;01;DRG;3;101556856;8500A90A;178,50;20170101;20170101;1;0;