Skip to content

Commit a98a4e3

Browse files
authored
NgSurvey integration (#13885)
Introduces an end-to-end pipeline that lets an external survey tool (ngSurvey) push answer payloads back into SORMAS and have them turned into a CaseDataDto patch. Feature is broken down into following layers: - API layer (sormas-api): new DTOs/facades for external survey messages, a DataPatcher contract, a PartialRetriever contract, plus mapping/alias support. - Backend (sormas-backend.patch) - an in-house mini-framework on top of Apache Commons PropertyUtils: DataPatcherImpl orchestrates per-field patching with a result-collecting pipeline. PartialRetrieverImpl reads back the same paths for display. Pluggable ValuePatchMapper (date / enum / customizable enum / primitive / reference DTO) andFieldCustomMapper (person contact details, person birthdate) registries. EqualityChecker registry used when DataReplacementStrategy.IF_NOT_ALREADY_PRESENT. PathAliasHelper lets external systems address fields by their Field ID (see: SORMAS Data Dictionary) instead of the full DTO path (e.g. Symptoms.fever instead of CaseData.symptoms.fever). Survey integration (sormas-backend.externalmessage.survey) - AutomaticSurveyResponseProcessor consumes ExternalMessageDtos, resolves the SurveyToken, and feeds the DataPatcher. UI: new views/forms for survey/token management and survey-response details. The core abstraction (path-based patcher with pluggable mappers, validators and aliases) is reusable beyond ngSurvey. Fixes #13832
1 parent ab65691 commit a98a4e3

164 files changed

Lines changed: 12467 additions & 230 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

sormas-api/src/main/java/de/symeda/sormas/api/Disease.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
import java.util.Arrays;
1919
import java.util.List;
20+
import java.util.Set;
21+
import java.util.stream.Collectors;
22+
23+
import com.google.common.collect.ImmutableSet;
2024

2125
import de.symeda.sormas.api.i18n.I18nProperties;
2226
import de.symeda.sormas.api.statistics.StatisticsGroupingKey;
@@ -94,6 +98,12 @@ public enum Disease
9498
OTHER(true, true, true, false, true, 21, false, false, false, false, 0, 0),
9599
UNDEFINED(true, true, true, false, true, 0, false, false, false, false, 0, 0);
96100

101+
/**
102+
* Immutable that eager loads all available diseases.
103+
*/
104+
public static final Set<Disease> ALL_DISEASES =
105+
Arrays.stream(Disease.values()).collect(Collectors.collectingAndThen(Collectors.toSet(), ImmutableSet::copyOf));
106+
97107
private final boolean defaultActive;
98108
private final boolean defaultPrimary;
99109
private final boolean defaultCaseSurveillanceEnabled;

sormas-api/src/main/java/de/symeda/sormas/api/FacadeProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
import de.symeda.sormas.api.specialcaseaccess.SpecialCaseAccessFacade;
110110
import de.symeda.sormas.api.survey.SurveyFacade;
111111
import de.symeda.sormas.api.survey.SurveyTokenFacade;
112+
import de.symeda.sormas.api.survey.alias.PathAliasFacade;
112113
import de.symeda.sormas.api.symptoms.SymptomsFacade;
113114
import de.symeda.sormas.api.systemconfiguration.SystemConfigurationCategoryFacade;
114115
import de.symeda.sormas.api.systemconfiguration.SystemConfigurationValueFacade;
@@ -319,6 +320,10 @@ public static PrescriptionFacade getPrescriptionFacade() {
319320
return get().lookupEjbRemote(PrescriptionFacade.class);
320321
}
321322

323+
public static PathAliasFacade getPathAliasFacade() {
324+
return get().lookupEjbRemote(PathAliasFacade.class);
325+
}
326+
322327
public static TreatmentFacade getTreatmentFacade() {
323328
return get().lookupEjbRemote(TreatmentFacade.class);
324329
}

sormas-api/src/main/java/de/symeda/sormas/api/ResourceBundle.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@ public String getString(String key, String defaultValue) {
2626
public String getString(String key) {
2727
return getString(key, null);
2828
}
29+
30+
public java.util.ResourceBundle getResourceBundle() {
31+
return resourceBundle;
32+
}
2933
}

sormas-api/src/main/java/de/symeda/sormas/api/caze/InfectionSetting.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package de.symeda.sormas.api.caze;
1717

1818
import de.symeda.sormas.api.i18n.I18nProperties;
19+
import de.symeda.sormas.api.patch.mapping.ValueMapperDefault;
1920

2021
public enum InfectionSetting {
2122

23+
@ValueMapperDefault
2224
UNKNOWN(null),
2325
AMBULATORY(null),
2426
MEDICAL_PRACTICE(AMBULATORY),

sormas-api/src/main/java/de/symeda/sormas/api/caze/QuarantineReason.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
package de.symeda.sormas.api.caze;
1717

1818
import de.symeda.sormas.api.i18n.I18nProperties;
19+
import de.symeda.sormas.api.patch.mapping.ValueMapperDefault;
1920

2021
public enum QuarantineReason {
2122

2223
IDENTIFIED_BY_CONTACT_TRACING,
2324
ENTRY_FROM_RISK_AREA,
2425
SWISS_COVID_APP_NOTIFICATION,
2526
OUTBREAK_INVESTIGATION,
27+
@ValueMapperDefault
2628
OTHER_REASON;
2729

2830
@Override

sormas-api/src/main/java/de/symeda/sormas/api/caze/Trimester.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package de.symeda.sormas.api.caze;
22

33
import de.symeda.sormas.api.i18n.I18nProperties;
4+
import de.symeda.sormas.api.patch.mapping.ValueMapperDefault;
45

56
public enum Trimester {
67

78
FIRST,
89
SECOND,
910
THIRD,
11+
@ValueMapperDefault
1012
UNKNOWN;
1113

1214
@Override

sormas-api/src/main/java/de/symeda/sormas/api/clinicalcourse/HealthConditionsDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public class HealthConditionsDto extends PseudonymizableDto {
8484
private YesNoUnknown hivArt;
8585
private YesNoUnknown chronicLiverDisease;
8686
private YesNoUnknown malignancyChemotherapy;
87+
88+
//TODO: rename ? general heart issue
8789
@HideForCountries(countries = {
8890
CountryHelper.COUNTRY_CODE_GERMANY,
8991
CountryHelper.COUNTRY_CODE_SWITZERLAND })

sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/ExternalMessageDto.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Date;
2020
import java.util.List;
2121

22+
import javax.annotation.Nullable;
2223
import javax.validation.constraints.Size;
2324

2425
import de.symeda.sormas.api.CountryHelper;
@@ -33,6 +34,7 @@
3334
import de.symeda.sormas.api.disease.DiseaseVariant;
3435
import de.symeda.sormas.api.exposure.ModeOfTransmission;
3536
import de.symeda.sormas.api.externalmessage.labmessage.SampleReportDto;
37+
import de.symeda.sormas.api.externalmessage.survey.ExternalSurveyResponseData;
3638
import de.symeda.sormas.api.feature.FeatureType;
3739
import de.symeda.sormas.api.i18n.Validations;
3840
import de.symeda.sormas.api.infrastructure.country.CountryReferenceDto;
@@ -206,6 +208,10 @@ public class ExternalMessageDto extends SormasToSormasShareableDto {
206208
private String externalMessageDetails;
207209
@Size(max = FieldConstraints.CHARACTER_LIMIT_TEXT, message = Validations.textTooLong)
208210
private String caseComments;
211+
212+
/**
213+
* Used as deduplication key for {@link ExternalMessageType#SURVEY_RESPONSE}.
214+
*/
209215
@Size(max = FieldConstraints.CHARACTER_LIMIT_DEFAULT, message = Validations.textTooLong)
210216
private String reportId;
211217

@@ -279,6 +285,12 @@ public class ExternalMessageDto extends SormasToSormasShareableDto {
279285
private ModeOfTransmission modeOfTransmission;
280286
private String modeOfTransmissionType;
281287

288+
/**
289+
* Will only be present for: {@link ExternalMessageType#SURVEY_RESPONSE} to represent the pair.
290+
*/
291+
@Nullable
292+
private ExternalSurveyResponseData surveyResponseData;
293+
282294
public ExternalMessageType getType() {
283295
return type;
284296
}
@@ -1020,4 +1032,14 @@ public String getModeOfTransmissionType() {
10201032
public void setModeOfTransmissionType(String modeOfTransmissionType) {
10211033
this.modeOfTransmissionType = modeOfTransmissionType;
10221034
}
1035+
1036+
@Nullable
1037+
public ExternalSurveyResponseData getSurveyResponseData() {
1038+
return surveyResponseData;
1039+
}
1040+
1041+
public ExternalMessageDto setSurveyResponseData(@Nullable ExternalSurveyResponseData surveyResponseData) {
1042+
this.surveyResponseData = surveyResponseData;
1043+
return this;
1044+
}
10231045
}

sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/ExternalMessageFacade.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import java.util.Date;
44
import java.util.List;
5+
import java.util.Map;
56

7+
import javax.annotation.Nullable;
68
import javax.ejb.Remote;
79
import javax.naming.NamingException;
810
import javax.validation.Valid;
@@ -11,6 +13,7 @@
1113
import de.symeda.sormas.api.ReferenceDto;
1214
import de.symeda.sormas.api.caze.surveillancereport.SurveillanceReportReferenceDto;
1315
import de.symeda.sormas.api.common.Page;
16+
import de.symeda.sormas.api.patch.partial_retrieval.DisplayablePartialRetrievalResponse;
1417
import de.symeda.sormas.api.sample.SampleReferenceDto;
1518
import de.symeda.sormas.api.user.UserReferenceDto;
1619
import de.symeda.sormas.api.utils.SortProperty;
@@ -22,6 +25,19 @@ public interface ExternalMessageFacade extends PermanentlyDeletableFacade {
2225

2326
ExternalMessageDto saveAndProcessLabmessage(@Valid ExternalMessageDto dto);
2427

28+
/**
29+
* Will attempt to fetch and refresh.
30+
*
31+
* @param since
32+
* if not specified
33+
* @return external messages that have been saved.
34+
*/
35+
List<ExternalMessageDto> saveAndProcessSurveyResponses(@Nullable Date since);
36+
37+
default List<ExternalMessageDto> saveAndProcessSurveyResponses() {
38+
return saveAndProcessSurveyResponses(null);
39+
}
40+
2541
void validate(ExternalMessageDto dto);
2642

2743
// Also returns deleted lab messages
@@ -70,4 +86,25 @@ public interface ExternalMessageFacade extends PermanentlyDeletableFacade {
7086
boolean existsForwardedExternalMessageWith(String reportId);
7187

7288
ExternalMessageDto getForSurveillanceReport(SurveillanceReportReferenceDto surveillanceReport);
89+
90+
/**
91+
* Re-submits a survey response with a corrected patch dictionary after previous processing failures.
92+
*
93+
* @param uuid
94+
* UUID of the external message (must be of type SURVEY_RESPONSE)
95+
* @param correctedDictionary
96+
* the corrected field path -> value map to apply
97+
* @return updated ExternalMessageDto after reprocessing
98+
*/
99+
ExternalMessageDto overwriteSurveyResponse(String uuid, Map<String, Object> correctedDictionary);
100+
101+
/**
102+
* Retrieves display-ready field information (translated names and current case values) for all fields
103+
* in the survey response patch dictionary. Used by the UI to render the detail/editor windows.
104+
*
105+
* @param externalMessageUuid
106+
* UUID of the external message (must be of type SURVEY_RESPONSE with a processed result)
107+
* @return displayable field info keyed by field path
108+
*/
109+
DisplayablePartialRetrievalResponse fetchSurveyResponseFieldsForDisplay(String externalMessageUuid);
73110
}

sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/ExternalMessageType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
public enum ExternalMessageType {
66

77
LAB_MESSAGE,
8-
PHYSICIANS_REPORT;
8+
PHYSICIANS_REPORT,
9+
SURVEY_RESPONSE;
910

1011
@Override
1112
public String toString() {

0 commit comments

Comments
 (0)