diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java b/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java
index ac847ab1656..e6de9520f71 100644
--- a/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java
+++ b/sormas-api/src/main/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapper.java
@@ -62,12 +62,15 @@ public static String mapSampleMaterialToEpipulseCode(SampleMaterial sampleMateri
return "NASALSWAB";
case THROAT_SWAB:
case RECTAL_SWAB:
+ case CLINICAL_SAMPLE:
case OTHER:
return "OTH";
case SALIVA:
return "SALOR"; // Saliva/oral fluid
case EDTA_WHOLE_BLOOD:
return "EDTA"; // EDTA whole blood
+ case DRY_BLOOD:
+ return "DRYBLOSP"; // Dry blood spot
default:
return null;
}
@@ -234,10 +237,10 @@ public static String mapClusterTypeToEpipulseCode(ClusterType clusterType) {
* Maps SORMAS CaseImportedStatus enum to EpiPulse imported status codes.
*
* EpiPulse Reference Values:
- * - AUTOCH = Autochthonous (not imported case)
+ * - NOTIMP = Not imported case
* - IMP = Imported case
- * - IMPR = Import-related case
- * - UNK = Unknown importation status
+ * - IMPREL = Import-related case
+ * - IMPUNK = Unknown importation status
*
* @param importedStatus
* SORMAS case imported status enum
@@ -252,11 +255,11 @@ public static String mapCaseImportedStatusToEpipulseCode(CaseImportedStatus impo
case IMPORTED_CASE:
return "IMP";
case IMPORT_RELATED_CASE:
- return "IMPR";
+ return "IMPREL";
case UNKNOWN_IMPORTATION_STATUS:
- return "UNK";
+ return "IMPUNK";
case NOT_IMPORTED_CASE:
- return "AUTOCH";
+ return "NOTIMP";
default:
return null;
}
@@ -279,6 +282,8 @@ public static String mapCaseImportedStatusToEpipulseCode(CaseImportedStatus impo
* Diarrhea symptom state
* @param otitisMedia
* Otitis media symptom state
+ * @param pneumonia
+ * Pneumonia (clinical or radiologic) symptom state
* @param otherComplications
* Other complications symptom state
* @return List of EpiPulse complication codes (empty list returns "NONE" in CSV)
@@ -287,6 +292,7 @@ public static List mapSymptomsToComplicationCodes(
SymptomState acuteEncephalitis,
SymptomState diarrhea,
SymptomState otitisMedia,
+ SymptomState pneumonia,
SymptomState otherComplications) {
List complications = new ArrayList<>();
@@ -300,11 +306,13 @@ public static List mapSymptomsToComplicationCodes(
if (otitisMedia == SymptomState.YES) {
complications.add("OME");
}
+ if (pneumonia == SymptomState.YES) {
+ complications.add("PNEU");
+ }
if (otherComplications == SymptomState.YES) {
complications.add("OTH");
}
- // Note: PNEU (pneumonia) not currently available in SORMAS Symptoms for MEAS
// If no complications found, empty list will result in "NONE" in CSV
return complications;
diff --git a/sormas-api/src/test/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapperTest.java b/sormas-api/src/test/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapperTest.java
new file mode 100644
index 00000000000..dfa2bd6cc4a
--- /dev/null
+++ b/sormas-api/src/test/java/de/symeda/sormas/api/epipulse/EpipulseLaboratoryMapperTest.java
@@ -0,0 +1,77 @@
+package de.symeda.sormas.api.epipulse;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+import de.symeda.sormas.api.sample.SampleMaterial;
+
+public class EpipulseLaboratoryMapperTest {
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_DryBlood() {
+ assertEquals("DRYBLOSP", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.DRY_BLOOD));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_Blood() {
+ assertEquals("SER", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.BLOOD));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_Sera() {
+ assertEquals("SER", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.SERA));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_Urine() {
+ assertEquals("URINE", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.URINE));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_NasalSwab() {
+ assertEquals("NASALSWAB", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.NASAL_SWAB));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_ThroatSwab() {
+ assertEquals("OTH", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.THROAT_SWAB));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_RectalSwab() {
+ assertEquals("OTH", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.RECTAL_SWAB));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_ClinicalSample() {
+ assertEquals("OTH", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.CLINICAL_SAMPLE));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_Other() {
+ assertEquals("OTH", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.OTHER));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_Saliva() {
+ assertEquals("SALOR", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.SALIVA));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_EdtaWholeBlood() {
+ assertEquals("EDTA", EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.EDTA_WHOLE_BLOOD));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_UnmappedMaterial() {
+ assertNull(EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.CEREBROSPINAL_FLUID));
+ assertNull(EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(SampleMaterial.STOOL));
+ }
+
+ @Test
+ public void testMapSampleMaterialToEpipulseCode_Null() {
+ assertNull(EpipulseLaboratoryMapper.mapSampleMaterialToEpipulseCode(null));
+ }
+}
diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/EpipulseSqlCteBuilder.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/EpipulseSqlCteBuilder.java
index 675facf615d..18268c32c56 100644
--- a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/EpipulseSqlCteBuilder.java
+++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/EpipulseSqlCteBuilder.java
@@ -182,7 +182,7 @@ public String buildPathogenTestsCte() {
" pathogentest.testtype," +
" pathogentest.testresult" +
" ), '#'" +
- " ORDER BY pathogentest.testdatetime DESC) AS all_pathogen_tests_from_latest" +
+ " ORDER BY COALESCE(pathogentest.testdatetime, pathogentest.reportdate, pathogentest.creationdate) DESC) AS all_pathogen_tests_from_latest" +
" FROM pathogentest" +
" INNER JOIN case_all_samples_from_latest" +
" ON pathogentest.sample_id = ANY" +
diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/IpiExportStrategy.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/IpiExportStrategy.java
index 4691209a838..c4821b6e84a 100644
--- a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/IpiExportStrategy.java
+++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/IpiExportStrategy.java
@@ -222,7 +222,7 @@ private String buildPneuSerotypingDataCte() {
" AND s.deleted = false " +
" AND pt.testtype IN ('SEROGROUPING', 'GENOTYPING', 'WHOLE_GENOME_SEQUENCING') " +
" AND pt.typingid IS NOT NULL " +
- " ORDER BY pt.testdatetime DESC " +
+ " ORDER BY COALESCE(pt.testdatetime, pt.reportdate, pt.creationdate) DESC NULLS LAST, pt.id DESC " +
" LIMIT 1) as serotype " +
" FROM filtered_cases c)";
//@formatter:on
@@ -240,7 +240,7 @@ private String buildPneuPathogenDetectionMethodCte() {
" WHERE s.associatedcase_id = c.id " +
" AND s.deleted = false " +
" AND pt.testresultverified = true " +
- " ORDER BY pt.testdatetime DESC " +
+ " ORDER BY COALESCE(pt.testdatetime, pt.reportdate, pt.creationdate) DESC NULLS LAST, pt.id DESC " +
" LIMIT 1) as detection_method " +
" FROM filtered_cases c)";
//@formatter:on
@@ -341,7 +341,7 @@ private String buildPneuAstDataCte() {
" LEFT JOIN pathogentest pt ON pt.sample_id = s.id " +
" AND pt.testtype = 'ANTIBIOTIC_SUSCEPTIBILITY' " +
" LEFT JOIN drugsusceptibility ds ON pt.drugsusceptibility_id = ds.id " +
- " ORDER BY c.id, pt.testdatetime DESC" +
+ " ORDER BY c.id, COALESCE(pt.testdatetime, pt.reportdate, pt.creationdate) DESC NULLS LAST, pt.id DESC" +
")";
//@formatter:on
}
diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java
index d78d5e262b7..b1a846524e2 100644
--- a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java
+++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeaslesExportStrategy.java
@@ -136,19 +136,21 @@ protected void mapDiseaseSpecificFields(EpipulseDiseaseExportEntryDto dto, Objec
}
}
- // Complications mapping (4 fields)
+ // Complications mapping (5 fields)
String acuteEncephalitisRaw = (String) row[++index];
String diarrheaRaw = (String) row[++index];
String otitisMediaRaw = (String) row[++index];
+ String pneumoniaRaw = (String) row[++index];
String otherComplicationsRaw = (String) row[++index];
SymptomState acuteEncephalitis = parseSymptomState(acuteEncephalitisRaw);
SymptomState diarrhea = parseSymptomState(diarrheaRaw);
SymptomState otitisMedia = parseSymptomState(otitisMediaRaw);
+ SymptomState pneumonia = parseSymptomState(pneumoniaRaw);
SymptomState otherComplications = parseSymptomState(otherComplicationsRaw);
dto.setComplicationDiagnosis(
- EpipulseLaboratoryMapper.mapSymptomsToComplicationCodes(acuteEncephalitis, diarrhea, otitisMedia, otherComplications));
+ EpipulseLaboratoryMapper.mapSymptomsToComplicationCodes(acuteEncephalitis, diarrhea, otitisMedia, pneumonia, otherComplications));
// Clinical criteria status
String clinicalConfirmationRaw = (String) row[++index];
@@ -218,18 +220,22 @@ private String buildSampleDataCte() {
" STRING_AGG(DISTINCT CAST(s3.samplematerial AS text), ',' ORDER BY CAST(s3.samplematerial AS text)) as specimen_types_serology " +
" FROM filtered_cases c " +
" LEFT JOIN samples s ON s.associatedcase_id = c.id AND s.deleted = false " +
- " LEFT JOIN (SELECT DISTINCT s_vir.id, s_vir.associatedcase_id, s_vir.samplematerial " +
+ " LEFT JOIN (SELECT DISTINCT s_vir.associatedcase_id, s_vir.samplematerial " +
" FROM samples s_vir " +
- " JOIN pathogentest pt_vir ON pt_vir.sample_id = s_vir.id " +
" WHERE s_vir.deleted = false " +
" AND s_vir.samplematerial IS NOT NULL " +
- " AND pt_vir.testtype IN ('PCR_RT_PCR', 'CULTURE', 'ISOLATION', 'DIRECT_FLUORESCENT_ANTIBODY', 'INDIRECT_FLUORESCENT_ANTIBODY')) s2 " +
+ " AND s_vir.associatedcase_id IN (SELECT id FROM filtered_cases)) s2 " +
" ON s2.associatedcase_id = c.id " +
- " LEFT JOIN (SELECT DISTINCT s_sero.id, s_sero.associatedcase_id, s_sero.samplematerial " +
+ " LEFT JOIN (SELECT DISTINCT s_sero.associatedcase_id, s_sero.samplematerial " +
" FROM samples s_sero " +
- " JOIN pathogentest pt_sero ON pt_sero.sample_id = s_sero.id " +
+ " LEFT JOIN pathogentest pt_sero ON pt_sero.sample_id = s_sero.id " +
+ " AND pt_sero.testtype IN ('IGG_SERUM_ANTIBODY', 'IGM_SERUM_ANTIBODY') " +
" WHERE s_sero.deleted = false " +
- " AND pt_sero.testtype IN ('IGG_SERUM_ANTIBODY', 'IGM_SERUM_ANTIBODY', 'SEROLOGY')) s3 " +
+ " AND s_sero.samplematerial IS NOT NULL " +
+ " AND s_sero.associatedcase_id IN (SELECT id FROM filtered_cases) " +
+ " AND (pt_sero.id IS NOT NULL OR NOT EXISTS ( " +
+ " SELECT 1 FROM pathogentest pt_any WHERE pt_any.sample_id = s_sero.id " +
+ " ))) s3 " +
" ON s3.associatedcase_id = c.id " +
" GROUP BY c.id)";
//@formatter:on
@@ -238,15 +244,14 @@ private String buildSampleDataCte() {
private String buildVirusDetectionDataCte() {
//@formatter:off
return "virus_detection_data AS (SELECT c.id as case_id," +
- " MIN(pt.testdatetime) as lab_result_date," +
+ " MIN(COALESCE(pt.testdatetime, pt.reportdate, pt.creationdate)) as lab_result_date," +
" (SELECT pt2.testresult " +
" FROM samples s2 " +
" JOIN pathogentest pt2 ON pt2.sample_id = s2.id " +
" WHERE s2.associatedcase_id = c.id " +
" AND s2.deleted = false " +
- " AND pt2.testtype IN ('PCR_RT_PCR', 'CULTURE', 'ISOLATION', 'DIRECT_FLUORESCENT_ANTIBODY', 'INDIRECT_FLUORESCENT_ANTIBODY') " +
- " AND pt2.testresultverified = true " +
- " ORDER BY pt2.testdatetime ASC " +
+ " AND pt2.testtype IN ('PCR_RT_PCR', 'CULTURE', 'ISOLATION', 'DIRECT_FLUORESCENT_ANTIBODY', 'INDIRECT_FLUORESCENT_ANTIBODY', 'SEQUENCING', 'GENOTYPING') " +
+ " ORDER BY pt2.testresultverified DESC, COALESCE(pt2.testdatetime, pt2.reportdate, pt2.creationdate) ASC " +
" LIMIT 1) as virus_detection_result," +
" (SELECT COALESCE(pt3.typingid, pt3.genotyperesult) " +
" FROM samples s3 " +
@@ -254,13 +259,12 @@ private String buildVirusDetectionDataCte() {
" WHERE s3.associatedcase_id = c.id " +
" AND s3.deleted = false " +
" AND (pt3.typingid IS NOT NULL OR pt3.genotyperesult IS NOT NULL) " +
- " ORDER BY pt3.testdatetime ASC " +
+ " ORDER BY COALESCE(pt3.testdatetime, pt3.reportdate, pt3.creationdate) ASC " +
" LIMIT 1) as genotype_raw " +
" FROM filtered_cases c " +
" LEFT JOIN samples s ON s.associatedcase_id = c.id AND s.deleted = false " +
" LEFT JOIN pathogentest pt ON pt.sample_id = s.id " +
- " AND pt.testtype IN ('PCR_RT_PCR', 'CULTURE', 'ISOLATION', 'DIRECT_FLUORESCENT_ANTIBODY', 'INDIRECT_FLUORESCENT_ANTIBODY') " +
- " AND pt.testresultverified = true " +
+ " AND pt.testtype IN ('PCR_RT_PCR', 'CULTURE', 'ISOLATION', 'DIRECT_FLUORESCENT_ANTIBODY', 'INDIRECT_FLUORESCENT_ANTIBODY', 'SEQUENCING', 'GENOTYPING') " +
" GROUP BY c.id)";
//@formatter:on
}
@@ -277,7 +281,7 @@ private String buildIggSerologyDataCte() {
" WHERE s_igg.associatedcase_id = c.id " +
" AND s_igg.deleted = false " +
" AND pt_igg.testtype = 'IGG_SERUM_ANTIBODY' " +
- " ORDER BY pt_igg.testdatetime ASC " +
+ " ORDER BY COALESCE(pt_igg.testdatetime, pt_igg.reportdate, pt_igg.creationdate) ASC " +
" LIMIT 1) as igg_result " +
" FROM filtered_cases c)";
//@formatter:on
@@ -292,7 +296,7 @@ private String buildIgmSerologyDataCte() {
" WHERE s_igm.associatedcase_id = c.id " +
" AND s_igm.deleted = false " +
" AND pt_igm.testtype = 'IGM_SERUM_ANTIBODY' " +
- " ORDER BY pt_igm.testdatetime ASC " +
+ " ORDER BY COALESCE(pt_igm.testdatetime, pt_igm.reportdate, pt_igm.creationdate) ASC " +
" LIMIT 1) as igm_result " +
" FROM filtered_cases c)";
//@formatter:on
@@ -337,6 +341,7 @@ private String buildComplicationsDataCte() {
" s.acuteencephalitis," +
" s.diarrhea," +
" s.otitismedia," +
+ " s.pneumoniaclinicalorradiologic," +
" s.othercomplications " +
" FROM filtered_cases c " +
" LEFT JOIN symptoms s ON c.symptoms_id = s.id)";
@@ -366,6 +371,7 @@ private String buildMeaslesSelectClause() {
.append(" comp.acuteencephalitis,")
.append(" comp.diarrhea,")
.append(" comp.otitismedia,")
+ .append(" comp.pneumoniaclinicalorradiologic,")
.append(" comp.othercomplications,")
.append(" c.clinicalconfirmation,")
.append(" el.infection_locations,")
diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeniExportStrategy.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeniExportStrategy.java
index d82281e9c2b..e1038dccb1f 100644
--- a/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeniExportStrategy.java
+++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/epipulse/strategy/MeniExportStrategy.java
@@ -253,7 +253,7 @@ private String buildMeniSerogroupCte() {
" AND s.deleted = false " +
" AND pt.testtype IN ('SEROGROUPING', 'SLIDE_AGGLUTINATION') " +
" AND pt.typingid IS NOT NULL " +
- " ORDER BY pt.testdatetime DESC " +
+ " ORDER BY COALESCE(pt.testdatetime, pt.reportdate, pt.creationdate) DESC NULLS LAST, pt.id DESC " +
" LIMIT 1) as serogroup " +
" FROM filtered_cases c" +
")";
@@ -419,7 +419,7 @@ private String buildMeniAstDataCte() {
" LEFT JOIN pathogentest pt ON pt.sample_id = s.id " +
" AND pt.testtype = 'ANTIBIOTIC_SUSCEPTIBILITY' " +
" LEFT JOIN drugsusceptibility ds ON pt.drugsusceptibility_id = ds.id " +
- " ORDER BY c.id, pt.testdatetime DESC" +
+ " ORDER BY c.id, COALESCE(pt.testdatetime, pt.reportdate, pt.creationdate) DESC NULLS LAST, pt.id DESC" +
")";
//@formatter:on
}