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 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 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 getStudies() throws IOException; +}