...
 
Commits (8)
......@@ -15,7 +15,6 @@ import org.junit.Assert;
import static org.junit.Assert.*;
import org.junit.Test;
import de.sekmi.histream.xml.DateTimeAccuracyAdapter;
public class TestDateTimeAccuracy {
......@@ -29,6 +28,16 @@ public class TestDateTimeAccuracy {
Assert.assertEquals(2, ac.get(ChronoField.MONTH_OF_YEAR));
Assert.assertEquals(2003, ac.get(ChronoField.YEAR));
}
@Test
public void testParseTimestampWithoutSeparators(){
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuuMMddHHmm");
String text = "201705190800";
DateTimeAccuracy a = DateTimeAccuracy.parse(formatter, text, ZoneOffset.UTC);
Assert.assertEquals(ChronoUnit.MINUTES,a.getAccuracy());
TemporalAccessor ac = a.toInstantMin().atOffset(ZoneOffset.UTC);
Assert.assertEquals(5, ac.get(ChronoField.MONTH_OF_YEAR));
Assert.assertEquals(2017, ac.get(ChronoField.YEAR));
}
@Test
public void verifyZoneOffset(){
......
README
======
Developing and testing import descriptors
-----------------------------------------
For standalone operation of import descriptor parsing,
put histream-core.jar, histream-import.jar and histream-js.jar
into a single folder and run the following command:
java -cp \*.jar de.sekmi.histream.etl.XMLExport datasource.xml
Schema/XSD for import descriptions
----------------------------------
If you want an XSD file for the import description XML,
......
......@@ -251,7 +251,10 @@ public class FactGroupingQueue implements Supplier<Observation>{
currentRows.remove(tableIndex);
factTables.remove(tableIndex);
continue; // index will now point to next table
}else if( row.getPatientId().equals(currentPatient.getPatientId()) && compareWithNulls(row.getVisitId(), currentVisitId) == 0 ){
}if( (row.getPatientId() == null || row.getPatientId().equals(currentPatient.getPatientId())) && compareWithNulls(row.getVisitId(), currentVisitId) == 0 ){
// if the row does not have a patient id, it is assumed that the visit id is unique and sufficient for matching
// if the row has both patient id and visit id, both are used for matching
// row fits into current group
addFactsToWorkQueue(row);
// prefetch next row
......
package de.sekmi.histream.etl;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Paths;
import javax.xml.stream.XMLStreamException;
import de.sekmi.histream.impl.Meta;
import de.sekmi.histream.io.GroupedXMLWriter;
......@@ -26,11 +30,15 @@ public class XMLExport {
System.err.println("Usage: XMLExport <import-descriptor-file>");
System.exit(-1);
}
ETLObservationSupplier suppl = ETLObservationSupplier.load(Paths.get(args[0]).toUri().toURL());
GroupedXMLWriter writer = new GroupedXMLWriter(System.out);
descriptorToXML(Paths.get(args[0]).toUri().toURL(), System.out);
}
public static void descriptorToXML(URL importDescriptor, OutputStream out) throws IOException, ParseException, XMLStreamException {
ETLObservationSupplier suppl = ETLObservationSupplier.load(importDescriptor);
GroupedXMLWriter writer = new GroupedXMLWriter(out);
Meta.transfer(suppl, writer);
Streams.transfer(suppl, writer);
suppl.close();
writer.close();
writer.close();
}
}
......@@ -150,6 +150,7 @@ public class Concept{
concept = mf.getConceptOverride();
}
// or drop the modifier
// TODO how to specify that a modifier should be dropped (e.g. if the value is NA)???
if( mf.isActionDrop() ){
continue; // ignore this modifier
}
......
......@@ -75,6 +75,10 @@ public class EavTable extends Table<EavRow> {
ColumnMap map = new ColumnMap(headers);
if( idat.patientId == null ){
throw new ParseException("datasource/eav-table/idat/patient-id column not specified");
// TODO allow missing patient id, similar to WideTable.java
// missing patient id is allowed, for cases
// when the visit id is unique and sufficient for matching
}
if( idat.visitId == null ){
throw new ParseException("datasource/eav-table/idat/visit-id column not specified");
......
......@@ -24,13 +24,15 @@ public class WideTable extends Table<WideRow> implements ConceptTable{
ColumnMap map = new ColumnMap(headers);
if( idat.patientId == null ){
throw new ParseException("datasource/wide-table/idat/patient-id column not specified");
// missing patientId column is allowed. In these cases visitId should be unique for identification
// TODO write INFO message that patient-id is not available and visit-id is used for matching
}else {
map.registerColumn(idat.patientId);
}
if( idat.visitId == null ){
throw new ParseException("datasource/wide-table/idat/visit-id column not specified");
}
map.registerColumn(idat.patientId);
map.registerColumn(idat.visitId);
for( Concept c : concepts ){
......@@ -45,8 +47,15 @@ public class WideTable extends Table<WideRow> implements ConceptTable{
@Override
public WideRow fillRecord(ColumnMap map, Object[] row, ObservationFactory factory) throws ParseException {
String patid = idat.patientId.valueOf(map, row);
String visit = idat.visitId.valueOf(map, row);
String patid;
if( idat.patientId == null ) {
// if there is no patient id in this table,
// we can get the patient id by lookup by visit
patid = null;
}else {
patid = idat.patientId.valueOf(map, row);
}
WideRow rec = new WideRow(patid,visit);
for( Concept c : concepts ){
Observation o = c.createObservation(patid, visit, factory, map, row);
......
package de.sekmi.histream.etl.config;
import java.io.IOException;
import javax.xml.stream.XMLStreamException;
import de.sekmi.histream.etl.ParseException;
import de.sekmi.histream.etl.XMLExport;
public class TestP21 {
public static void main(String[] args) throws IOException, ParseException, XMLStreamException {
XMLExport.descriptorToXML(Class.class.getResource("/data/test-p21-datasource.xml"), System.out);
}
}
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;
......@@ -17,6 +13,10 @@ IK;Entlassender-Standort;Entgeltbereich;KH-internes-Kennzeichen;IK-Krankenkasse;
260100012;01;PSY;5;101556856;00000000;4865,05;20170101;20170119;1;2;
260100012;01;PSY;5;101556856;01012900;185,65;20170101;20170119;17;2;
260100012;01;PSY;5;101556856;01000001;100,00;20170101;20170119;17;2;
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;PSY;7;101556856;A1PA02A5;305,80;20170101;20170105;5;0;
260100012;01;PSY;7;101556856;00000000;1529,00;20170101;20170105;1;0;
260100012;01;PIA;11;101556856;31000000;185,65;;;1;0;20170410
......
IK;Entlassender-Standort;Entgeltbereich;KH-internes-Kennzeichen;Diagnoseart;ICD-Version;ICD-Kode;Lokalisation;Diagnosensicherheit;Sekundr-Kode;Lokalisation2;Diagnosensicherheit2
260100012;01;PSY;6;ND;2017;F10.1;;;;;
260100012;01;DRG;1;HD;2017;M16.7;L;;;;
260100012;01;DRG;2;HD;2017;M16.7;L;;;;
260100012;01;DRG;3;HD;2017;R26.8;;;;;
......@@ -11,6 +10,7 @@ IK;Entlassender-Standort;Entgeltbereich;KH-internes-Kennzeichen;Diagnoseart;ICD-
260100012;01;PSY;4;ND;2017;F10.1;;;;;
260100012;01;PSY;6;HD;2017;F20.1;;;;;
260100012;01;PSY;6;ND;2017;F10.1;;;;;
260100012;01;PSY;6;ND;2017;F10.1;;;;;
260100012;01;PSY;7;HD;2017;F20.0;;;;;
260100012;01;PSY;7;ND;2017;F05.8;;;;;
260100012;01;PIA;11;ND;2017;F12.2;;A;;;
IK;Entlassender-Standort;Entgeltbereich;KH-internes-Kennzeichen;OPS-Version;OPS-Kode;Lokalisation;OPS-Datum;Belegoperateur;Belegansthesist;Beleghebamme
260100012;1;DRG;1;2017;5-056.8;L;201705190800;;;
260100012;1;DRG;1;2017;5-820.00;L;201705190800;;;
260100012;1;DRG;2;2017;5-056.8;L;201705190800;;;
260100012;1;DRG;2;2017;5-820.00;L;201705190800;;;
260100012;1;DRG;3;2017;5-056.8;L;201701010800;;;
260100012;1;DRG;3;2017;5-820.00;L;201701010800;;;
260100012;1;PSY;4;2017;9-980.0;;201701010800;;;
......@@ -24,7 +28,3 @@ IK;Entlassender-Standort;Entgeltbereich;KH-internes-Kennzeichen;OPS-Version;OPS-
260100012;1;PSY;6;2017;9-649.50;;201701150800;;;
260100012;1;PSY;7;2017;9-649.50;;201701010900;;;
260100012;1;PSY;7;2017;9-980.0;;201701010900;;;
260100012;1;DRG;1;2017;5-056.8;L;201705190800;;;
260100012;1;DRG;1;2017;5-820.00;L;201705190800;;;
260100012;1;DRG;2;2017;5-056.8;L;201705190800;;;
260100012;1;DRG;2;2017;5-820.00;L;201705190800;;;
Example dataset for data transfer following
the German paragraph 21 KHEntgG.
Data is available at
https://www.g-drg.de/Datenlieferung_gem._21_KHEntgG/Dokumente_zur_Datenlieferung
Slight modifications (until the sorting/filtering
functionality is implemented in code):
- Tables OPS.csv, ICD.csv, Entgelte.csv are sorted
by "KH-internes-Kennzeichen"
- File _PAT.csv is added and contains distinct
patients from FALL.csv (distinct "Patientennummer")
\ No newline at end of file
IK;Entlassender-Standort;Entgeltbereich;KH-internes-Kennzeichen;Versicherten-ID;Vertragskennzeichen-64b-Modellvorhaben;IK-der-Krankenkasse;Geburtsjahr;Geburtsmonat;Geschlecht;PLZ;Wohnort;Aufnahmedatum;Aufnahmeanlass;Aufnahmegrund;Fallzusammenfhrung;Fallzusammenfhrungsgrund;Aufnahmegewicht;Entlassungsdatum;Entlassungsgrund;Alter-in-Tagen-am-Aufnahmetag;Alter-in-Jahren-am-Aufnahmetag;Patientennummer;interkurrente-Dialysen;Beatmungsstunden;Behandlungsbeginn-vorstationr;Behandlungstage-vorstationr;Behandlungsende-nachstationr;Behandlungstage-nachstationr;IK-Verlegungs-KH;Belegungstage-in-anderem-Entgeltbereich;Beurlaubungstage-PSY;Kennung Besonderer Fall Modellvorhaben
260100012;01;DRG;1;9999999999;;111556856;1945;01;w;53121;Bonn;201705190800;E;0101;N;;;201706020800;019;;66;Pat7412;;;;;;;;;;
260100012;01;DRG;2;9999999999;;111556856;1945;01;w;53121;Bonn;201705190800;E;0101;J;OG;;201706020800;019;;66;Pat7413;;;;;;;;2;;
260100012;01;DRG;3;P000000004;;101556856;1945;01;w;53121;Bonn;201701010800;E;0301;N;;;201701050800;019;;66;Pat7414;;;;;;;;;;
260100012;01;PSY;4;P000000004;;101556856;1945;01;w;53121;Bonn;201701010800;E;0101;N;;;201701200800;019;;66;Pat7415;;;;;;;;;;
<?xml version="1.0" encoding="UTF-8"?>
<datasource version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<meta>
<id>test-1</id>
<etl-strategy>replace-source</etl-strategy>
</meta>
<patient-table>
<source xsi:type="csv-file">
<url>p21khg/_PAT.csv</url>
<separator>;</separator>
</source>
<idat>
<patient-id column="Patientennummer"/>
<!-- TODO allow concatenation/splitting of columns to join separate columns for birth year and month -->
<birthdate format="u" na="" column="Geburtsjahr"/>
<gender column="Geschlecht" na="">
<map> <!-- maps a column -->
<case value="w" set-value="female"/>
<case value="m" set-value="male"/>
<otherwise set-value="" log-warning="Unexpected gender value"/>
</map>
</gender>
</idat>
<ignore xsi:type="string" column="*"/>
</patient-table>
<!-- optional -->
<visit-table>
<source xsi:type="csv-file">
<url>p21khg/FALL.csv</url>
<separator>;</separator>
</source>
<idat>
<patient-id column="Patientennummer"/>
<visit-id column="KH-internes-Kennzeichen"/>
<start column="Aufnahmedatum" format="uuuuMMddHHmm" na=""/>
<end column="Entlassungsdatum" format="uuuuMMddHHmm" zone="Europe/Berlin" na=""/>
</idat>
<mdat>
<!-- in/out code -->
<concept id="AG">
<value column="Aufnahmegrund" xsi:type="string"/>
<start column="Aufnahmedatum" format="uuuuMMddHHmm" na=""/>
</concept>
</mdat>
<ignore xsi:type="string" column="*"/>
</visit-table>
<wide-table>
<source xsi:type="csv-file">
<url>p21khg/FAB.csv</url>
<separator>;</separator>
</source>
<idat>
<visit-id column="KH-internes-Kennzeichen"/>
</idat>
<mdat>
<!-- wechsel zwischen fachabteilung -->
<concept id="FAB">
<value column="FAB" xsi:type="string" />
<start column="FAB-Aufnahmedatum" format="uuuuMMddHHmm" na="" />
<end column="FAB-Aufnahmedatum" format="uuuuMMddHHmm" na="" />
<modifier id="ENTG">
<value column="Entgeltbereich" xsi:type="string" na-action="drop-fact" />
</modifier>
</concept>
</mdat>
</wide-table>
<wide-table>
<source xsi:type="csv-file">
<url>p21khg/ICD.csv</url>
<separator>;</separator>
</source>
<idat>
<visit-id column="KH-internes-Kennzeichen"/>
</idat>
<mdat>
<!-- coded diagnoses -->
<concept id="ICD">
<!-- TODO code should be part of the concept -->
<value column="ICD-Kode" xsi:type="string" />
<modifier id="art">
<value column="Diagnoseart" xsi:type="string">
<map>
<case value="HD" set-value="" set-concept="DIAG:H"/>
<case value="ND" set-value="" set-concept="DIAG:N"/>
<otherwise action="drop-fact" />
</map>
</value>
</modifier>
<modifier id="ICDVer">
<value column="ICD-Version" xsi:type="string" />
</modifier>
<modifier id="lokality">
<value column="Lokalisation" xsi:type="string">
<map>
<case value="L" set-value="" set-concept="DIAG:LO:L"/>
<case value="R" set-value="" set-concept="DIAG:LO:R"/>
<otherwise action="drop-fact" /><!-- removes the modifier if none of the listed values is specified -->
</map>
</value>
</modifier>
</concept>
</mdat>
</wide-table>
</datasource>