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 }