diff --git a/pom.xml b/pom.xml
index 1fc35dc..2ba24f7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,5 +68,11 @@
junit
junit
+
+ org.projectlombok
+ lombok
+ 1.18.38
+ provided
+
diff --git a/src/main/java/org/aktin/dwh/PreferenceKey.java b/src/main/java/org/aktin/dwh/PreferenceKey.java
index a20a90c..8ef897c 100644
--- a/src/main/java/org/aktin/dwh/PreferenceKey.java
+++ b/src/main/java/org/aktin/dwh/PreferenceKey.java
@@ -1,6 +1,6 @@
package org.aktin.dwh;
-import org.aktin.dwh.optinout.PatientReference;
+import org.aktin.dwh.optinout.model.PatientReference;
/**
* Preferences keys for the data warehouse
diff --git a/src/main/java/org/aktin/dwh/optinout/Participation.java b/src/main/java/org/aktin/dwh/optinout/Participation.java
deleted file mode 100644
index 41c45ac..0000000
--- a/src/main/java/org/aktin/dwh/optinout/Participation.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.aktin.dwh.optinout;
-
-public enum Participation {
- /** Patient specifically wants to participate */
- OptIn,
- /** Patient wants to be excluded */
- OptOut
-}
diff --git a/src/main/java/org/aktin/dwh/optinout/PatientEntry.java b/src/main/java/org/aktin/dwh/optinout/PatientEntry.java
deleted file mode 100644
index a74f788..0000000
--- a/src/main/java/org/aktin/dwh/optinout/PatientEntry.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.aktin.dwh.optinout;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.time.Instant;
-import java.util.List;
-
-public interface PatientEntry {
-
- Study getStudy();
- Participation getParticipation();
- PatientReference getReference();
- String getIdRoot();
- String getIdExt();
- String getSIC();
- void setSIC(String sic);
- String getUser();
- void setUser(String user);
- Instant getTimestamp();
- void setTimestamp(Instant timestamp);
- String getComment();
- void setComment(String comment);
-
- /**
- * Get the i2b2 patient_num field. Only available, if the patient was linked
- * to existing data. The link is established automatically and periodically
- * @return patient_num from i2b2, or {@code null} if no link is available.
- */
- Integer getI2b2PatientNum();
- void setI2b2PatientNum(Integer i2b2PatientNum);
-
- /**
- * Delete this entry
- * @param user user name who requested the delete operation
- * @throws FileNotFoundException if the patient was not found
- * @throws IOException any other IO error
- */
- void delete(String user) throws FileNotFoundException, IOException;
-
- /**
- * Determine whether a second patient is equal (study, pat_ref,pat_root,pat_ext)
- * @param other other entry
- * @return true if the ids are equal, false otherwise
- */
- boolean equalsId(PatientEntry other);
-}
diff --git a/src/main/java/org/aktin/dwh/optinout/Study.java b/src/main/java/org/aktin/dwh/optinout/Study.java
deleted file mode 100644
index ad59cf7..0000000
--- a/src/main/java/org/aktin/dwh/optinout/Study.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package org.aktin.dwh.optinout;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.util.List;
-import java.util.Map;
-
-public interface Study {
-
- /**
- * Unique id (e.g. acronym) for the study. The id should consist only of URL-safe characters
- * @return study id
- */
- String getId();
- String getTitle();
- String getDescription();
-
- Instant getCreatedTimestamp();
- Instant getClosedTimestamp();
-
- boolean isOptIn();
- boolean isOptOut();
-
-
- /**
- * Determine whether this study allows the specified participation option.
- * A study may support multiple participation options.
- * @param participation participation option (OptIn, OptOut)
- * @return true if the study allows the specified option
- */
- boolean isParticipationSupported(Participation participation);
-
- /**
- * Get the method of SIC generation
- * @return sic generation enum
- */
- SICGeneration getSicGeneration();
- void setSicGeneration(SICGeneration sic);
-
- String getSicGenerator();
- void setSicGenerator(String sicGenerator);
- String getSicGeneratorState();
- void setSicGeneratorState(String sicGeneratorState);
-
- /**
- * Validate the syndax for a (usually manually entered) subject identification code
- * @param sic code
- * @return validation error message, or {@code null} if valid
- */
- String validateSIC(String sic);
-
- /**
- * Generate a new subject identification code (SIC). This is a transaction which
- * guarantees that two calls never return the same SIC.
- * @return locally unique SIC
- * @throws UnsupportedOperationException if the study does not support generation of SICs
- * @throws IllegalStateException if the generator is in an invalid state and unable to perform the next calculation
- * @throws IOException for IO errors during state loading/saving
- */
- String generateSIC() throws UnsupportedOperationException, IllegalStateException, IOException;
-
- /**
- * Find a patient by his SIC. A patient does not necessarily need a SIC, therefore
- * some entries might exist which cannot be found via this method
- * @param sic subject identification code
- * @return entry or {@code null} if not found
- * @throws IOException IO error
- */
- PatientEntry getPatientBySIC(String sic) throws IOException;
-
- /**
- * Find a patient by their reference type, root id and extension
- * @param ref Patient reference like Encounter id, billing number or patient id
- * @param idRoot Root id, depends on the patient reference
- * @param idExt unique patient extension
- * @return Patient entry or null of not found
- * @throws IOException
- */
- PatientEntry getPatientByID(PatientReference ref, String idRoot, String idExt) throws IOException;
-
- /**
- * Returns all patients registered in this study
- * @return all patients registered in this study
- * @throws IOException
- */
- List extends PatientEntry> allPatients() throws IOException;
-
- /**
- * Persists single patient
- * @param ref Patient reference like Encounter id, billing number or patient id
- * @param idRoot Root id, depends on the patient reference
- * @param idExt unique patient extension
- * @param opt participation, Opt-In or Opt-Out
- * @param sic sic subject identification code
- * @param comment
- * @param user creator
- * @return added patient entry
- * @throws IOException
- */
- PatientEntry addPatient(PatientReference ref, String idRoot, String idExt, Participation opt, String sic, String comment, String user) throws IOException;
-
- /**
- * Persists multiple patients
- * @param ref Patient reference like Encounter id, billing number or patient id
- * @param idRoot Root id, depends on the patient reference
- * @param entries map for unique patient extensions (key) and sics (value) - sic may be {@code null}
- * @param opt participation, Opt-In or Opt-Out
- * @param comment
- * @param user creator
- * @return list of created entries
- * @throws IOException
- */
- List addPatients(PatientReference ref, String idRoot, Map entries, Participation opt, String comment, String user) throws IOException;
-
- /**
- * Update an old entry
- * @param oldEntry entry to be updated
- * @param newEntry new entry
- * @return updated entry
- * @throws IOException
- */
- PatientEntry updatePatient(PatientEntry oldEntry, PatientEntry newEntry) throws IOException;
-}
diff --git a/src/main/java/org/aktin/dwh/optinout/StudyManager.java b/src/main/java/org/aktin/dwh/optinout/StudyManager.java
deleted file mode 100644
index 9ca4cff..0000000
--- a/src/main/java/org/aktin/dwh/optinout/StudyManager.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.aktin.dwh.optinout;
-
-import java.io.IOException;
-import java.util.List;
-
-public interface StudyManager {
-
- List extends Study> getStudies() throws IOException;
-
- PatientMasterData loadMasterData(PatientReference ref, String root, String ext) throws IOException;
- List loadEncounters(PatientReference ref, String root, String ext) throws IOException;
-
- /**
- * Try to link all entries which have not been linked yet to
- * the actual existing data. After a successful link has been established,
- * the {@link PatientEntry#getI2b2PatientNum()} will be not {@code null}.
- * @throws IOException IO error
- */
- void linkPatientEntriesToData() throws IOException;
-}
diff --git a/src/main/java/org/aktin/dwh/optinout/model/ErrorType.java b/src/main/java/org/aktin/dwh/optinout/model/ErrorType.java
new file mode 100644
index 0000000..a680ce3
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/model/ErrorType.java
@@ -0,0 +1,9 @@
+package org.aktin.dwh.optinout.model;
+
+// errors that occur during CRUD operations on opt-in data
+public enum ErrorType {
+ STUDY_NOT_FOUND,
+ PATIENT_NOT_FOUND,
+ PATIENT_ALREADY_EXISTS,
+ SIC_ALREADY_EXISTS,
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/model/Participation.java b/src/main/java/org/aktin/dwh/optinout/model/Participation.java
new file mode 100644
index 0000000..89f50b2
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/model/Participation.java
@@ -0,0 +1,28 @@
+package org.aktin.dwh.optinout.model;
+
+import java.util.Arrays;
+
+public enum Participation {
+ /** Patient specifically wants to participate */
+ OptIn("I"),
+ /** Patient wants to be excluded */
+ OptOut("O");
+
+ /**
+ * helper functions for (de-)serialization
+ */
+ private String code;
+
+ Participation(String code) {
+ this.code = code;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public static Participation fromCode(String code) {
+ return Arrays.stream(values()).filter(a -> a.code.equals(code)).findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unknown database value: " + code));
+ }
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/PatientEncounter.java b/src/main/java/org/aktin/dwh/optinout/model/PatientEncounter.java
similarity index 59%
rename from src/main/java/org/aktin/dwh/optinout/PatientEncounter.java
rename to src/main/java/org/aktin/dwh/optinout/model/PatientEncounter.java
index 6b5ce35..8facae1 100644
--- a/src/main/java/org/aktin/dwh/optinout/PatientEncounter.java
+++ b/src/main/java/org/aktin/dwh/optinout/model/PatientEncounter.java
@@ -1,10 +1,9 @@
-package org.aktin.dwh.optinout;
+package org.aktin.dwh.optinout.model;
import java.time.Instant;
public interface PatientEncounter {
- int getEncounterId();
- int getPatientId();
+ String getPseudonym();
Instant getStartDate();
Instant getEndDate();
}
diff --git a/src/main/java/org/aktin/dwh/optinout/model/PatientEntry.java b/src/main/java/org/aktin/dwh/optinout/model/PatientEntry.java
new file mode 100644
index 0000000..78d6f93
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/model/PatientEntry.java
@@ -0,0 +1,21 @@
+package org.aktin.dwh.optinout.model;
+
+import java.time.Instant;
+
+public interface PatientEntry {
+ Participation getParticipation();
+ PatientReference getReference();
+ String getIdRoot();
+ String getIdExt();
+ String getSIC();
+ String getUser();
+ Instant getTimestamp();
+ String getComment();
+
+ /**
+ * Get the i2b2 patient_num field. Only available, if the patient was linked
+ * to existing data. The link is established automatically and periodically
+ * @return patient_num from i2b2, or {@code null} if no link is available.
+ */
+ Integer getI2b2PatientNum();
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/model/PatientEntryData.java b/src/main/java/org/aktin/dwh/optinout/model/PatientEntryData.java
new file mode 100644
index 0000000..77abf89
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/model/PatientEntryData.java
@@ -0,0 +1,22 @@
+package org.aktin.dwh.optinout.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * This class serves as input POJO for (batch) patient entry write actions (create, update)
+ */
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class PatientEntryData {
+ private String root;
+ private String extension;
+ private String sic;
+ private String comment;
+ private boolean generateSic;
+ private Participation participation;
+ private PatientReference reference;
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/PatientMasterData.java b/src/main/java/org/aktin/dwh/optinout/model/PatientMasterData.java
similarity index 67%
rename from src/main/java/org/aktin/dwh/optinout/PatientMasterData.java
rename to src/main/java/org/aktin/dwh/optinout/model/PatientMasterData.java
index a33b799..aa192ab 100644
--- a/src/main/java/org/aktin/dwh/optinout/PatientMasterData.java
+++ b/src/main/java/org/aktin/dwh/optinout/model/PatientMasterData.java
@@ -1,9 +1,9 @@
-package org.aktin.dwh.optinout;
+package org.aktin.dwh.optinout.model;
import java.time.Instant;
public interface PatientMasterData {
- int getPatientId();
+ String getPseudonym();
String getSex();
String getZip();
Instant getBirthDate();
diff --git a/src/main/java/org/aktin/dwh/optinout/PatientReference.java b/src/main/java/org/aktin/dwh/optinout/model/PatientReference.java
similarity index 61%
rename from src/main/java/org/aktin/dwh/optinout/PatientReference.java
rename to src/main/java/org/aktin/dwh/optinout/model/PatientReference.java
index be91dd1..f36ab15 100644
--- a/src/main/java/org/aktin/dwh/optinout/PatientReference.java
+++ b/src/main/java/org/aktin/dwh/optinout/model/PatientReference.java
@@ -1,23 +1,45 @@
-package org.aktin.dwh.optinout;
+package org.aktin.dwh.optinout.model;
+
+import java.util.Arrays;
public enum PatientReference {
/** patient specific id
* CDA element: /ClinicalDocument/recordTarget/patientRole/id
* hash in i2b2 DB: i2b2crcdata.patient_mapping in column patient_ide [type: character varying(200)
*/
- Patient,
+ Patient("PAT"),
/** visit id, a visit can contain multiple encounters
* Not used in this project.
*/
- Visit,
+ Visit("VIS"),
/** encounter (with a practitioner)
* CDA element: /ClinicalDocument/componentOf/encompassingEncounter/id[1]
* hash in i2b2 DB: i2b2crcdata.encounter_mapping in column encounter_ide [type: character varying(200)
*/
- Encounter,
+ Encounter("ENC"),
/** billing / accounting number of the hospital, used for financial transactions
* CDA element: /ClinicalDocument/componentOf/encompassingEncounter/id[2]
* hash in i2b2 DB: i2b2crcdata.observation_fact in column tval_char where concept_cd=AKTIN:Fallkennzeichen [type: character varying(255)]
*/
- Billing
+ Billing("BIL");
+
+
+ /**
+ * helper functions for (de-)serialization
+ */
+
+ private final String code;
+
+ PatientReference(String code) {
+ this.code = code;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public static PatientReference fromCode(String code) {
+ return Arrays.stream(values()).filter(a -> a.code.equals(code)).findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unknown value: " + code));
+ }
}
diff --git a/src/main/java/org/aktin/dwh/optinout/SICGeneration.java b/src/main/java/org/aktin/dwh/optinout/model/SICGeneration.java
similarity index 84%
rename from src/main/java/org/aktin/dwh/optinout/SICGeneration.java
rename to src/main/java/org/aktin/dwh/optinout/model/SICGeneration.java
index 5e4825e..c3d2354 100644
--- a/src/main/java/org/aktin/dwh/optinout/SICGeneration.java
+++ b/src/main/java/org/aktin/dwh/optinout/model/SICGeneration.java
@@ -1,4 +1,4 @@
-package org.aktin.dwh.optinout;
+package org.aktin.dwh.optinout.model;
/**
* Method of Sic generation
diff --git a/src/main/java/org/aktin/dwh/optinout/model/Study.java b/src/main/java/org/aktin/dwh/optinout/model/Study.java
new file mode 100644
index 0000000..9591e1f
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/model/Study.java
@@ -0,0 +1,45 @@
+package org.aktin.dwh.optinout.model;
+
+import java.time.Instant;
+
+public interface Study {
+
+ /**
+ * Unique id (e.g. acronym) for the study. The id should consist only of URL-safe characters
+ * @return study id
+ */
+ String getId();
+ String getTitle();
+ String getDescription();
+
+ Instant getCreatedTimestamp();
+
+ /**
+ * when the study was closed. {@code null} if still open
+ */
+ Instant getClosedTimestamp();
+
+ /**
+ * Get the participation option for this study.
+ * @return participation option
+ */
+ Participation getParticipation();
+
+ /**
+ * Get the method of SIC generation
+ * @return sic generation enum
+ */
+ SICGeneration getSicGeneration();
+
+ /**
+ * Get the generator of automatic SIC generation.
+ * @return generator name or {@code null} if no generator is used
+ */
+ String getSicGenerator();
+
+ /**
+ * Get the state of the generator.
+ * @return generator state (e.g. last generated SIC (sequence))
+ */
+ String getSicGeneratorState();
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/model/ValidationResult.java b/src/main/java/org/aktin/dwh/optinout/model/ValidationResult.java
new file mode 100644
index 0000000..cd2ada5
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/model/ValidationResult.java
@@ -0,0 +1,19 @@
+package org.aktin.dwh.optinout.model;
+/**
+ * Validation result of a inserted entry data
+ */
+public enum ValidationResult {
+ // User Input States
+ DUPLICATE_PAT_REF, // User entered a patient reference at least twice
+ DUPLICATE_SIC, // User entered a SIC at least twice
+
+ // Database States
+ ENTRY_FOUND, // Patient entry already exists
+ SIC_FOUND, // SIC already exists
+ MASTER_DATA_NOT_FOUND, // No master data in db found
+ ENCOUNTERS_NOT_FOUND, // No encounters in db found
+
+ // Processing States
+ VALID,
+ PENDING, // Validation has not occurred yet
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/service/PatientService.java b/src/main/java/org/aktin/dwh/optinout/service/PatientService.java
new file mode 100644
index 0000000..5541fc0
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/service/PatientService.java
@@ -0,0 +1,74 @@
+package org.aktin.dwh.optinout.service;
+
+import org.aktin.dwh.optinout.model.*;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface PatientService {
+ /**
+ * Get all patients of a study.
+ * @param studyId study id
+ * @return list of patients
+ * @throws IOException
+ */
+ List getAllPatientsOfStudy(String studyId) throws IOException;
+
+ /**
+ * Get a patient by ID.
+ * @param studyId study id
+ * @param ref patient reference
+ * @param extension patient extension
+ * @return patient entry
+ * @throws IOException
+ */
+ PatientEntry getPatientByID(String studyId, PatientReference ref, String extension) throws IOException;
+
+ /**
+ * Add a list of patients to a study.
+ * @param studyId study id
+ * @param patientEntryData patient entries
+ * @param user user who added the patients
+ * @throws IOException
+ */
+ void addPatientsToStudy(String studyId, List patientEntryData, String user) throws IOException;
+
+ /**
+ * Delete a patient from a study.
+ * @param studyId study id
+ * @param ref patient reference
+ * @param extension patient extension
+ * @param user user who deleted the patient
+ * @throws IOException
+ */
+ void deletePatient(String studyId, PatientReference ref, String extension, String user) throws IOException;
+
+ /**
+ * Update a patient entry.
+ * @param studyId study id
+ * @param ref patient reference
+ * @param extension patient extension
+ * @param newData new patient data
+ * @param user user who updated the patient
+ * @throws IOException
+ */
+ void updatePatient(String studyId, PatientReference ref, String extension, PatientEntryData newData, String user) throws IOException;
+
+ /**
+ * Get all encounters for a patient.
+ * @param ref patient reference
+ * @param extension patient extension
+ * @return list of encounters
+ * @throws IOException
+ */
+ List getEncounters(PatientReference ref, String extension) throws IOException;
+
+ /**
+ * Get master data (like sex, zip, etc.) for a patient.
+ * @param ref patient reference
+ * @param extension patient extension
+ * @return master data
+ * @throws IOException
+ */
+ PatientMasterData getMasterData(PatientReference ref, String extension) throws IOException;
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/service/PatientValidator.java b/src/main/java/org/aktin/dwh/optinout/service/PatientValidator.java
new file mode 100644
index 0000000..aeafe35
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/service/PatientValidator.java
@@ -0,0 +1,26 @@
+package org.aktin.dwh.optinout.service;
+
+import org.aktin.dwh.optinout.model.PatientEntryData;
+import org.aktin.dwh.optinout.model.ValidationResult;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Interface for validating patient data in a study context. The purpose of this interface
+ * is to ensure that the provided patient data meets the requirements and validation rules
+ * defined for a specific study.
+ */
+public interface PatientValidator {
+ /**
+ * Validates a list of patient entry data for a specific study and associates each entry with its respective validation results.
+ * This method ensures that the provided patient data complies with the validation rules defined for the given study.
+ *
+ * @param studyId the unique identifier for the study in which the patients are being validated.
+ * @param patients the list of {@link PatientEntryData} objects representing the patient entries to be validated.
+ * @return a map where each {@link PatientEntryData} is associated with a list of {@link ValidationResult} objects
+ * indicating the outcome of the validation process for that entry. An empty list indicates the entry is valid.
+ */
+ Map> validatePatients(String studyId, List patients) throws IOException;
+}
diff --git a/src/main/java/org/aktin/dwh/optinout/service/StudyService.java b/src/main/java/org/aktin/dwh/optinout/service/StudyService.java
new file mode 100644
index 0000000..2fce73e
--- /dev/null
+++ b/src/main/java/org/aktin/dwh/optinout/service/StudyService.java
@@ -0,0 +1,15 @@
+package org.aktin.dwh.optinout.service;
+
+import org.aktin.dwh.optinout.model.Study;
+
+import java.io.IOException;
+import java.util.List;
+
+public interface StudyService {
+ /**
+ * Get all studies.
+ * @return list of studies
+ * @throws IOException
+ */
+ List extends Study> getStudies() throws IOException;
+}