Skip to content
Snippets Groups Projects
Commit 315fcbe1 authored by Christopher Bohn's avatar Christopher Bohn :thinking:
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 1344 additions and 0 deletions
# Mac file finder metadata
.DS_Store
# Windows file metadata
._*
# Thumbnail image caches
Thumbs.db
ethumbs.db
# MS Office temporary file
~*
# Emacs backup file
*~
# Common
[Bb]in/
[Bb]uild/
[Oo]bj/
[Oo]ut/
[Tt]mp/
[Xx]86/
[Ii][Aa]32/
[Xx]64/
[Xx]86_64/
[Xx]86-64/
[Aa]rm
[Aa]32
[Tt]32
[Aa]64
*.tmp
*.bak
*.bk
*.swp
# Java files
*.class
javadoc/
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# JetBrains (IntelliJ IDEA, PyCharm, etc) files
.idea/
cmake-build-*/
*.iml
*.iws
*.ipr
# Eclipse files
.settings/
.project
.classpath
.buildpath
.loadpath
.factorypath
local.properties
pom.xml 0 → 100644
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>edu.unl.cse.soft160.instructors.sepsis</groupId>
<artifactId>sepsis</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>sepsis</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package edu.unl.cse.soft161.example.cli;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;
import edu.unl.cse.soft161.example.sepsis_screening.SepsisDetermination;
import edu.unl.cse.soft161.example.sepsis_screening.SepsisScreening;
public abstract class App {
private static String askWithDefault(CLI cli, ResourceBundle bundle, String questionKey, String defaultKey) {
String defaultAnswer = bundle.getString(defaultKey);
String question = bundle.getString(questionKey);
question = question.replaceAll("\\{default\\}", defaultAnswer);
return cli.askForString(question, defaultAnswer);
}
private static void screenPatient(SepsisScreening screening, CLI cli, ResourceBundle bundle) throws IOException {
String patientID = askWithDefault(cli, bundle, "query.prompts.patientID", "query.defaults.patientID");
cli.display(bundle.getString("status.downloading.patient"));
cli.display("");
LocalDateTime now = LocalDateTime.now();
SepsisDetermination results = null;
try {
results = screening.screen(patientID, new MedicationQuestioner(cli, bundle, now), now);
} catch (Exception ignored) {
cli.display(bundle.getString("general.errors.tryAgain"));
}
cli.display("");
cli.display(new Report(bundle, results).toString());
}
private static void screenPatients(SepsisScreening screening, CLI cli, ResourceBundle bundle) throws IOException {
do {
cli.display("");
screenPatient(screening, cli, bundle);
cli.display("");
} while (cli.askYesOrNo(bundle.getString("query.prompts.another")));
}
public static void main(String... arguments) {
ResourceBundle bundle = ResourceBundle.getBundle("SepsisScreening");
CLI cli = new CLI(System.in, System.out, bundle.getString("inputs.boolean.yesNoRegex"),
bundle.getString("inputs.boolean.yesRegex"), bundle.getString("inputs.boolean.reask"));
for (;;) {
try {
String hostname = askWithDefault(cli, bundle, "connection.prompts.hostname",
"connection.defaults.hostname");
String username = askWithDefault(cli, bundle, "connection.prompts.username",
"connection.defaults.username");
String password = askWithDefault(cli, bundle, "connection.prompts.password",
"connection.defaults.password");
cli.display(bundle.getString("status.downloading.visits"));
SepsisScreening screening = new SepsisScreening(hostname, username, password);
screenPatients(screening, cli, bundle);
break;
} catch (NoSuchElementException exception) {
cli.display("");
cli.display(bundle.getString("status.exit.endOfInput"));
System.exit(1);
} catch (IOException exception) {
cli.display("");
if (!cli.askYesOrNo(bundle.getString("connection.prompts.another"))) {
cli.display(bundle.getString("status.exit.connectionDropped"));
System.exit(2);
}
cli.display("");
}
}
cli.display("");
cli.display(bundle.getString("status.exit.clean"));
}
}
package edu.unl.cse.soft161.example.cli;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Scanner;
public class CLI {
protected final Scanner input;
protected final PrintStream output;
protected final String yesNoRegex;
protected final String yesRegex;
protected final String reaskMessage;
public CLI(InputStream input, PrintStream output, String yesNoRegex, String yesRegex, String reaskMessage) {
this.input = new Scanner(input);
this.output = output;
this.yesNoRegex = yesNoRegex;
this.yesRegex = yesRegex;
this.reaskMessage = reaskMessage;
}
public void display(String line) {
output.println(line);
}
public String askForString(String question) {
output.print(question);
output.print(" ");
return input.nextLine();
}
public String askForString(String question, String pattern, String mismatchMessage) {
for (;;) {
String line = askForString(question);
if (line.matches(pattern)) {
return line;
}
output.println(mismatchMessage);
}
}
public String askForString(String question, String defaultAnswer) {
String result = askForString(question);
return result.equals("") ? defaultAnswer : result;
}
public String askForString(String question, String pattern, String mismatchMessage, String defaultAnswer) {
String result = askForString(question, pattern, mismatchMessage);
return result.equals("") ? defaultAnswer : result;
}
public boolean askYesOrNo(String question) {
return askForString(question, yesNoRegex, reaskMessage).matches(yesRegex);
}
}
package edu.unl.cse.soft161.example.cli;
import static edu.unl.cse.soft161.example.rest_backend.Medication.*;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import edu.unl.cse.soft161.example.rest_backend.Medication;
import edu.unl.cse.soft161.example.rest_backend.Patient;
import edu.unl.cse.soft161.example.sepsis_screening.MedicationDataSource;
public class MedicationQuestioner implements MedicationDataSource {
protected static final Duration NOW_TOLERANCE = Duration.ofMinutes(2);
protected static final Map<Medication, String> MEDICATION_KEYS;
static {
MEDICATION_KEYS = new HashMap<Medication, String>();
MEDICATION_KEYS.put(COLONY_STIMULATING_FACTORS, "medications.colonyStimulatingFactors");
MEDICATION_KEYS.put(HEPARIN, "medications.heparin");
MEDICATION_KEYS.put(RECOMBINANT_HUMAN_ERYTHROPOIETINS, "medications.recombinantHumanErythropoietins");
}
protected final CLI cli;
protected final ResourceBundle bundle;
protected final LocalDateTime now;
public MedicationQuestioner(CLI cli, ResourceBundle bundle, LocalDateTime now) {
this.cli = cli;
this.bundle = bundle;
this.now = now;
}
protected String format(Patient patient) {
String result = bundle.getString("formats.patient");
result = result.replaceAll("\\{id\\}", patient.getId());
result = result.replaceAll("\\{familyName\\}", patient.getFamilyName());
result = result.replaceAll("\\{givenName\\}", patient.getGivenName());
return result;
}
protected String format(Medication medication) {
return bundle.getString(MEDICATION_KEYS.get(medication));
}
public boolean isTaking(Patient patient, Medication medication) {
String question = bundle.getString("questions.prompts.isTaking");
question = question.replaceAll("\\{patient\\}", format(patient));
question = question.replaceAll("\\{medication\\}", format(medication));
return cli.askYesOrNo(question);
}
public boolean hasTaken(Patient patient, Medication medication, LocalDateTime begin, LocalDateTime end) {
String questionKey = Duration.between(end, now).compareTo(NOW_TOLERANCE) < 0 ? "questions.prompts.hasTakenSince" : "questions.prompts.hasTakenBetween";
String question = bundle.getString(questionKey);
question = question.replaceAll("\\{patient\\}", format(patient));
question = question.replaceAll("\\{medication\\}", format(medication));
question = question.replaceAll("\\{begin\\}", begin.format(DateTimeFormatter.ofPattern(bundle.getString("formats.time"))));
question = question.replaceAll("\\{end\\}", end.format(DateTimeFormatter.ofPattern(bundle.getString("formats.time"))));
return cli.askYesOrNo(question);
}
}
package edu.unl.cse.soft161.example.cli;
import static edu.unl.cse.soft161.example.sepsis_screening.SepsisDetermination.Determination.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import edu.unl.cse.soft161.example.rest_backend.Concept;
import edu.unl.cse.soft161.example.rest_backend.Observation;
import edu.unl.cse.soft161.example.rest_backend.Patient;
import edu.unl.cse.soft161.example.sepsis_screening.Interval;
import edu.unl.cse.soft161.example.sepsis_screening.ObservationInInterval;
import edu.unl.cse.soft161.example.sepsis_screening.SepsisDetermination;
public class Report {
protected static final Map<SepsisDetermination.Determination, String> REPORT_KEYS;
static {
REPORT_KEYS = new HashMap<SepsisDetermination.Determination, String>();
REPORT_KEYS.put(PATIENT_DECEASED, "reports.deceased");
REPORT_KEYS.put(PATIENT_NOT_IN_VISIT, "reports.notInVisit");
REPORT_KEYS.put(PATIENT_NOT_ADULT, "reports.notAdult");
REPORT_KEYS.put(CONTINUE_MONITORING, "reports.negative");
REPORT_KEYS.put(SIRS_ALERT, "reports.sirs");
REPORT_KEYS.put(SEPSIS_ALERT, "reports.sepsis");
}
protected static String formatIntervalPredicate(ResourceBundle bundle, Interval interval, String units) {
String lower = bundle.getString(interval.isLowerBoundInclusive() ? "formats.atLeast" : "formats.above");
lower = lower.replaceAll("\\{value\\}", String.valueOf(interval.getLowerBound()));
lower = lower.replaceAll("\\{units\\}", units);
String upper = bundle.getString(interval.isUpperBoundInclusive() ? "formats.atMost" : "formats.below");
upper = upper.replaceAll("\\{value\\}", String.valueOf(interval.getUpperBound()));
upper = upper.replaceAll("\\{units\\}", units);
if (interval.getUpperBound() == Double.POSITIVE_INFINITY) {
return lower;
}
if (interval.getLowerBound() == Double.NEGATIVE_INFINITY) {
return upper;
}
return bundle.getString("formats.double_bounded").replaceAll("\\{lower\\}", lower).replaceAll("\\{upper\\}",
upper);
}
protected static String formattedEvidence;
protected static void formatSIRSEvidence(ResourceBundle bundle, List<ObservationInInterval> evidence) {
String indentation = bundle.getString("reports.indentation.sirsEvidence");
if (evidence.isEmpty()) {
formattedEvidence = indentation + bundle.getString("reports.evidence.none");
} else {
StringBuilder builder = new StringBuilder();
for (ObservationInInterval symptom : evidence) {
String item = bundle.getString("reports.evidence.item");
item = item.replaceAll("\\{observation\\}",
Observation.formatObservation(bundle, symptom.getObservation()));
item = item.replaceAll("\\{intervalPredicate\\}",
formatIntervalPredicate(bundle, symptom.getInterval(), symptom.getUnits()));
builder.append(indentation).append(item).append('\n');
}
formattedEvidence = builder.deleteCharAt(builder.length() - 1).toString();
}
}
protected static void formatOrganDysfunctionEvidence(ResourceBundle bundle,
List<ObservationInInterval> evidence) {
String indentation = bundle.getString("reports.indentation.organDysfunctionEvidence");
if (evidence.isEmpty()) {
formattedEvidence = indentation + bundle.getString("reports.evidence.none");
} else {
StringBuilder builder = new StringBuilder();
for (ObservationInInterval symptom : evidence) {
String item = bundle.getString("reports.evidence.item");
item = item.replaceAll("\\{observation\\}",
Observation.formatObservation(bundle, symptom.getObservation()));
item = item.replaceAll("\\{intervalPredicate\\}",
formatIntervalPredicate(bundle, symptom.getInterval(), symptom.getUnits()));
builder.append(indentation).append(item).append('\n');
}
formattedEvidence = builder.deleteCharAt(builder.length() - 1).toString();
}
}
protected static String formatLabs(ResourceBundle bundle, List<Concept> concepts, String indentationKey) {
String indentation = bundle.getString(indentationKey);
if (concepts.isEmpty()) {
return indentation + bundle.getString("reports.labs.none");
}
StringBuilder builder = new StringBuilder();
for (Concept concept : concepts) {
String item = bundle.getString("reports.labs.item");
item = item.replaceAll("\\{concept\\}", Observation.formatConcept(bundle, concept));
builder.append(indentation).append(item).append('\n');
}
return builder.deleteCharAt(builder.length() - 1).toString();
}
protected final String report;
public Report(ResourceBundle bundle, SepsisDetermination results) {
Patient patient = results.getPatient();
LocalDateTime now = results.getTimestamp();
String report = bundle.getString(REPORT_KEYS.get(results.getDetermination()));
report = report.replaceAll("\\{now\\}",
now.format(DateTimeFormatter.ofPattern(bundle.getString("formats.time"))));
report = report.replaceAll("\\{patientID\\}", patient.getId());
report = report.replaceAll("\\{familyName\\}", patient.getFamilyName());
report = report.replaceAll("\\{givenName\\}", patient.getGivenName());
report = report.replaceAll("\\{birthDate\\}",
patient.getBirthDate().format(DateTimeFormatter.ofPattern(bundle.getString("formats.date"))));
report = report.replaceAll("\\{age\\}", String.valueOf(patient.getAge(now)));
report = report.replaceAll("\\{location\\}", patient.getLocation());
if (results.getMissingLabsDetermination() != null) {
report = report.replaceAll("\\{missingLabs\\}", formatLabs(bundle,
results.getMissingLabsDetermination().getMissingLabs(), "reports.indentation.missingLabs"));
}
if (results.getSirsDetermination() != null) {
formatSIRSEvidence(bundle, results.getSirsDetermination().getEvidence());
report = report.replaceAll("\\{sirsEvidence\\}", formattedEvidence);
}
if (results.getOrganDysfunctionDetermination() != null) {
formatOrganDysfunctionEvidence(bundle, results.getOrganDysfunctionDetermination().getEvidence());
report = report.replaceAll("\\{organDysfunctionEvidence\\}", formattedEvidence);
}
this.report = report;
}
@Override
public String toString() {
return report;
}
}
package edu.unl.cse.soft161.example.rest_backend;
import static edu.unl.cse.soft161.example.rest_backend.Concept.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import edu.unl.cse.soft160.rest_connector.connector.ObservationRecord;
import edu.unl.cse.soft160.rest_connector.connector.OpenMRSConnection;
import edu.unl.cse.soft160.rest_connector.connector.PatientRecord;
public class Clinic {
protected static final Map<String, Concept> CONCEPT_DICTIONARY = new HashMap<String, Concept>();
static {
CONCEPT_DICTIONARY.put("Temperature (C)", TEMPERATURE);
CONCEPT_DICTIONARY.put("Pulse", HEART_RATE);
CONCEPT_DICTIONARY.put("Systolic blood pressure", SYSTOLIC_BLOOD_PRESSURE);
CONCEPT_DICTIONARY.put("Diastolic blood pressure", DIASTOLIC_BLOOD_PRESSURE);
CONCEPT_DICTIONARY.put("Respiratory rate", RESPIRATORY_RATE);
CONCEPT_DICTIONARY.put("Leukocytes (#/mL)", LEUKOCYTE_COUNT);
CONCEPT_DICTIONARY.put("Blasts per 100 Leukocytes (%)", BLAST_PERCENTAGE);
CONCEPT_DICTIONARY.put("Platelets (#/mL)", PLATELET_COUNT);
CONCEPT_DICTIONARY.put("Partial Thromboplastin Time (s)", PTT);
CONCEPT_DICTIONARY.put("Glucose in Blood (mg/dL)", GLUCOSE);
CONCEPT_DICTIONARY.put("Lactate in Blood (mmol/L)", LACTATE);
CONCEPT_DICTIONARY.put("Creatinine in Blood (mg/dL)", CREATININE);
CONCEPT_DICTIONARY.put("Bilirubin Total (mg/dL)", BILIRUBIN);
CONCEPT_DICTIONARY.put("Blood Cultures, Bacteria", BLOOD_CULTURES);
CONCEPT_DICTIONARY.put("Blood Cultures, Fungus", BLOOD_CULTURES);
CONCEPT_DICTIONARY.put("Blood Cultures, Viruses", BLOOD_CULTURES);
CONCEPT_DICTIONARY.put("Urinalysis", URINALYSIS);
CONCEPT_DICTIONARY.put("Diabetes Mellitus", DIABETES);
CONCEPT_DICTIONARY.put("Diabetes Mellitus, Type II", DIABETES);
CONCEPT_DICTIONARY.put("End-Stage Renal Disease", ESRD);
}
protected final OpenMRSConnection server;
public Clinic(String authority, String username, String password) throws IOException {
server = new OpenMRSConnection(authority, username, password);
}
public Patient getPatient(String patientID) throws IOException {
PatientRecord patientRecord = server.getPatientRecord(patientID);
if (patientRecord == null) {
throw new NoSuchElementException("No patient has the ID \"" + patientID + "\".");
}
boolean inVisit = false;
Set<ObservationRecord> observationRecords = server.getObservationRecords(patientRecord.getUUID());
Set<Observation> observations = new HashSet<Observation>();
for (ObservationRecord record : observationRecords) {
if (record.getMeasurement() != null) {
inVisit = true;
}
Concept concept = CONCEPT_DICTIONARY.get(record.getConcept());
if (concept != null) {
switch (concept.getType()) {
case UNVALUED:
observations.add(new Observation(record.getTimestamp(), concept));
break;
case BOOLEAN:
if (record.getMeasurement() != null) {
observations.add(new Observation(record.getTimestamp(), concept, record.getMeasurement() > 0));
}
break;
case NUMERIC:
if (record.getMeasurement() != null) {
observations.add(new Observation(record.getTimestamp(), concept, record.getMeasurement()));
}
break;
default:
;
}
}
}
return new Patient(patientID, patientRecord.getFamilyName(), patientRecord.getGivenName(),
patientRecord.getBirthDate(), patientRecord.getLocation(), inVisit, patientRecord.getDeceased(),
observations);
}
}
package edu.unl.cse.soft161.example.rest_backend;
public enum Concept {
TEMPERATURE("℃"), //
HEART_RATE("bpm"), //
SYSTOLIC_BLOOD_PRESSURE("mm Hg"), //
DIASTOLIC_BLOOD_PRESSURE("mm Hg"), //
MEAN_ARTERIAL_PRESSURE("mm Hg"), //
RESPIRATORY_RATE("min⁻¹"), //
LEUKOCYTE_COUNT("/mL"), //
BLAST_PERCENTAGE("%"), //
PLATELET_COUNT("/mL"), //
PTT("s"), //
GLUCOSE("mg/dL"), //
LACTATE("mg/dL"), //
CREATININE("mg/dL"), //
BILIRUBIN("mg/dL"), //
BLOOD_CULTURES(ConceptType.BOOLEAN), //
URINALYSIS(ConceptType.UNVALUED), //
DIABETES(ConceptType.UNVALUED), //
ESRD(ConceptType.UNVALUED);
public enum ConceptType {
UNVALUED, BOOLEAN, NUMERIC;
}
private final ConceptType type;
private final String units; // null if the concept is non-numeric
private Concept(ConceptType type) {
if (type == ConceptType.NUMERIC) {
throw new IllegalArgumentException("Numeric concepts cannot be constructed without units.");
}
this.type = type;
this.units = null;
}
private Concept(String units) {
this.type = ConceptType.NUMERIC;
this.units = units;
}
public ConceptType getType() {
return type;
}
public String getUnits() {
return units;
}
}
package edu.unl.cse.soft161.example.rest_backend;
public enum Medication {
COLONY_STIMULATING_FACTORS, //
HEPARIN, //
RECOMBINANT_HUMAN_ERYTHROPOIETINS;
}
package edu.unl.cse.soft161.example.rest_backend;
import static edu.unl.cse.soft161.example.rest_backend.Concept.*;
import static edu.unl.cse.soft161.example.rest_backend.Concept.ConceptType.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import edu.unl.cse.soft161.example.rest_backend.Concept.ConceptType;
public class Observation extends TimestampedObject {
protected static final Map<Concept, String> CONCEPT_KEYS;
static {
CONCEPT_KEYS = new HashMap<Concept, String>();
CONCEPT_KEYS.put(TEMPERATURE, "concepts.temperature");
CONCEPT_KEYS.put(HEART_RATE, "concepts.heartRate");
CONCEPT_KEYS.put(SYSTOLIC_BLOOD_PRESSURE, "concepts.systolicBloodPressure");
CONCEPT_KEYS.put(DIASTOLIC_BLOOD_PRESSURE, "concepts.diastolicBloodPressure");
CONCEPT_KEYS.put(MEAN_ARTERIAL_PRESSURE, "concepts.meanArterialPressure");
CONCEPT_KEYS.put(RESPIRATORY_RATE, "concepts.respiratoryRate");
CONCEPT_KEYS.put(LEUKOCYTE_COUNT, "concepts.leukocyteCount");
CONCEPT_KEYS.put(BLAST_PERCENTAGE, "concepts.blastPercentage");
CONCEPT_KEYS.put(PLATELET_COUNT, "concepts.plateletCount");
CONCEPT_KEYS.put(PTT, "concepts.ptt");
CONCEPT_KEYS.put(GLUCOSE, "concepts.glucose");
CONCEPT_KEYS.put(LACTATE, "concepts.lactate");
CONCEPT_KEYS.put(CREATININE, "concepts.creatinine");
CONCEPT_KEYS.put(BILIRUBIN, "concepts.bilirubin");
CONCEPT_KEYS.put(BLOOD_CULTURES, "concepts.bloodCultures");
CONCEPT_KEYS.put(URINALYSIS, "concepts.urinalysis");
CONCEPT_KEYS.put(DIABETES, "concepts.diabetes");
CONCEPT_KEYS.put(ESRD, "concepts.esrd");
}
public static String formatConcept(ResourceBundle bundle, Concept concept) {
return bundle.getString(CONCEPT_KEYS.get(concept));
}
public static String formatObservation(ResourceBundle bundle, Observation observation) {
String result = bundle.getString("reports.evidence.numericObservation");
result = result.replaceAll("\\{concept\\}", formatConcept(bundle, observation.getConcept()));
result = result.replaceAll("\\{timestamp\\}",
observation.getTimestamp().format(DateTimeFormatter.ofPattern(bundle.getString("formats.time"))));
if (observation.getConcept().getType() == ConceptType.NUMERIC) {
result = result.replaceAll("\\{measurement\\}", String.valueOf(observation.getMeasurement()));
result = result.replaceAll("\\{units\\}", observation.getConcept().getUnits());
}
return result;
}
protected final Concept concept;
protected final Boolean positive;
protected final Double measurement;
public Observation(LocalDateTime timestamp, Concept concept) {
super(timestamp);
if (concept.getType() != UNVALUED) {
throw new IllegalArgumentException("No value given for valued concept \"" + concept + "\".");
}
this.concept = concept;
this.positive = null;
this.measurement = null;
}
public Observation(LocalDateTime timestamp, Concept concept, boolean positive) {
super(timestamp);
if (concept.getType() != BOOLEAN) {
throw new IllegalArgumentException(
"Boolean value \"" + positive + "\" given for non-boolean concept \"" + concept + "\".");
}
this.concept = concept;
this.positive = positive;
this.measurement = null;
}
public Observation(LocalDateTime timestamp, Concept concept, double measurement) {
super(timestamp);
if (concept.getType() != NUMERIC) {
throw new IllegalArgumentException(
"Numeric value \"" + measurement + "\" given for non-numeric concept \"" + concept + "\".");
}
this.concept = concept;
this.positive = null;
this.measurement = measurement;
}
public Concept getConcept() {
return concept;
}
public boolean isPositive() {
if (concept.getType() != BOOLEAN) {
throw new IllegalStateException(
"Cannot get positive/negative status for non-boolean concept \"" + concept + "\".");
}
return positive;
}
public double getMeasurement() {
if (concept.getType() != NUMERIC) {
throw new IllegalStateException("Cannot get measurement for non-numeric concept \"" + concept + "\".");
}
return measurement;
}
@Override
public String toString() {
switch (concept.getType()) {
case UNVALUED:
return concept + " noted at " + timestamp;
case BOOLEAN:
return concept + ": " + (positive ? "positive" : "negative") + " at " + timestamp;
case NUMERIC:
return concept + ": " + measurement + concept.getUnits() + " at " + timestamp;
default:
return concept + " of unknown type " + concept.getType() + " at " + timestamp;
}
}
}
package edu.unl.cse.soft161.example.rest_backend;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Patient {
protected final String id;
protected final String familyName;
protected final String givenName;
protected final LocalDate birthDate;
protected final String location;
protected final boolean inVisit;
protected final boolean deceased;
protected final Set<Observation> observations;
public Patient(String id, String familyName, String givenName, LocalDate birthDate, String location,
boolean inVisit, boolean deceased, Set<Observation> observations) {
this.id = id;
this.familyName = familyName;
this.givenName = givenName;
this.birthDate = birthDate;
this.location = location;
this.inVisit = inVisit;
this.deceased = deceased;
this.observations = new HashSet<Observation>(observations);
}
public String getId() {
return id;
}
public String getFamilyName() {
return familyName;
}
public String getGivenName() {
return givenName;
}
public LocalDate getBirthDate() {
return birthDate;
}
public int getAge(LocalDateTime now) {
return Period.between(birthDate, now.toLocalDate()).getYears();
}
public String getLocation() {
return location;
}
public boolean isInVisit() {
return inVisit;
}
public boolean isDeceased() {
return deceased;
}
public boolean hasObservation(Concept concept) {
for (Observation observation : observations) {
if (observation.getConcept() == concept) {
return true;
}
}
return false;
}
public Observation getOldestObservation(Concept concept) {
Observation result = null;
for (Observation observation : observations) {
if (observation.getConcept() == concept
&& (result == null || observation.getTimestamp().isBefore(result.getTimestamp()))) {
result = observation;
}
}
return result;
}
public Observation getNewestObservation(Concept concept) {
Observation result = null;
for (Observation observation : observations) {
if (observation.getConcept() == concept
&& (result == null || observation.getTimestamp().isAfter(result.getTimestamp()))) {
result = observation;
}
}
return result;
}
protected Collection<SimultaneousObservations> getSimultaneousObservations(Collection<Concept> concepts) {
Map<LocalDateTime, SimultaneousObservations> candidates = new HashMap<LocalDateTime, SimultaneousObservations>();
for (Observation observation : observations) {
if (concepts.contains(observation.getConcept())) {
LocalDateTime timestamp = observation.getTimestamp();
SimultaneousObservations candidate = candidates.get(timestamp);
if (candidate == null) {
candidate = new SimultaneousObservations(timestamp);
candidates.put(timestamp, candidate);
}
candidate.add(observation);
}
}
return candidates.values();
}
public SimultaneousObservations getOldestSimultaneousObservations(Collection<Concept> concepts) {
SimultaneousObservations result = null;
for (SimultaneousObservations candidate : getSimultaneousObservations(concepts)) {
if (candidate.size() == concepts.size()
&& (result == null || candidate.getTimestamp().isBefore(result.getTimestamp()))) {
result = candidate;
}
}
return result;
}
public SimultaneousObservations getNewestSimultaneousObservations(Collection<Concept> concepts) {
SimultaneousObservations result = null;
for (SimultaneousObservations candidate : getSimultaneousObservations(concepts)) {
if (candidate.size() == concepts.size()
&& (result == null || candidate.getTimestamp().isAfter(result.getTimestamp()))) {
result = candidate;
}
}
return result;
}
@Override
public String toString() {
return "Patient " + id;
}
}
package edu.unl.cse.soft161.example.rest_backend;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class SimultaneousObservations extends TimestampedObject {
protected final Map<Concept, Observation> observations;
public SimultaneousObservations(LocalDateTime timestamp) {
super(timestamp);
observations = new HashMap<Concept, Observation>();
}
public int size() {
return observations.size();
}
public Observation get(Concept concept) {
return observations.get(concept);
}
public Collection<Observation> values() {
return observations.values();
}
public void clear() {
observations.clear();
}
public Observation add(Observation observation) {
if (!observation.getTimestamp().equals(timestamp)) {
throw new IllegalArgumentException("Observation `" + observation
+ "' cannot be added to a set of simultaneous observations from time " + timestamp + ".");
}
return observations.put(observation.getConcept(), observation);
}
public Observation remove(Concept concept) {
return observations.remove(concept);
}
}
package edu.unl.cse.soft161.example.rest_backend;
import java.time.Duration;
import java.time.LocalDateTime;
public abstract class TimestampedObject {
protected final LocalDateTime timestamp;
public TimestampedObject(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
public Duration getAge(LocalDateTime now) {
return Duration.between(timestamp, now);
}
}
package edu.unl.cse.soft161.example.sepsis_screening;
import java.time.LocalDateTime;
import edu.unl.cse.soft161.example.rest_backend.Patient;
import edu.unl.cse.soft161.example.rest_backend.TimestampedObject;
public abstract class Determination {
protected final Patient patient;
protected final LocalDateTime timestamp;
protected boolean withinHours(TimestampedObject timestamped, int hours) {
return timestamped != null && timestamped.getAge(timestamp).toHours() < hours;
}
public Determination(Patient patient, LocalDateTime now) {
this.patient = patient;
this.timestamp = now;
}
public Patient getPatient() {
return patient;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
}
package edu.unl.cse.soft161.example.sepsis_screening;
import static edu.unl.cse.soft161.example.sepsis_screening.Interval.LowerBoundRelation.*;
import static edu.unl.cse.soft161.example.sepsis_screening.Interval.UpperBoundRelation.*;
import edu.unl.cse.soft161.example.rest_backend.Observation;
public class Interval {
public static enum LowerBoundRelation {
ABOVE, AT_LEAST;
}
public static enum UpperBoundRelation {
BELOW, AT_MOST;
}
protected final boolean lowerBoundIsInclusive;
protected final double lowerBound;
protected final double upperBound;
protected final boolean upperBoundIsInclusive;
public Interval(LowerBoundRelation lowerBoundRelation, double lowerBound, UpperBoundRelation upperBoundRelation,
double upperBound) {
if (lowerBound > upperBound
|| lowerBound == upperBound && (lowerBoundRelation == ABOVE || upperBoundRelation == BELOW)) {
throw new IllegalArgumentException("No non-empty interval can be constructed from the arguments "
+ lowerBoundRelation + ", " + lowerBound + ", " + upperBoundRelation + ", and " + upperBound + ".");
}
this.lowerBoundIsInclusive = lowerBoundRelation == AT_LEAST;
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.upperBoundIsInclusive = upperBoundRelation == AT_MOST;
}
public Interval(LowerBoundRelation lowerBoundRelation, double lowerBound) {
this(lowerBoundRelation, lowerBound, BELOW, Double.POSITIVE_INFINITY);
}
public Interval(UpperBoundRelation upperBoundRelation, double upperBound) {
this(ABOVE, Double.NEGATIVE_INFINITY, upperBoundRelation, upperBound);
}
public Interval(Interval unshifted, double shift) {
this.lowerBoundIsInclusive = unshifted.lowerBoundIsInclusive;
this.lowerBound = unshifted.lowerBound + shift;
this.upperBound = unshifted.upperBound + shift;
this.upperBoundIsInclusive = unshifted.upperBoundIsInclusive;
}
public boolean isLowerBoundInclusive() {
return lowerBoundIsInclusive;
}
public double getLowerBound() {
return lowerBound;
}
public double getUpperBound() {
return upperBound;
}
public boolean isUpperBoundInclusive() {
return upperBoundIsInclusive;
}
public boolean contains(double point) {
return point > lowerBound && point < upperBound || point == lowerBound && lowerBoundIsInclusive
|| point == upperBound && upperBoundIsInclusive;
}
public boolean contains(Observation observation) {
return observation != null && contains(observation.getMeasurement());
}
@Override
public String toString() {
return (lowerBoundIsInclusive ? "[" : "(") + lowerBound + "…" + upperBound
+ (upperBoundIsInclusive ? "]" : ")");
}
}
package edu.unl.cse.soft161.example.sepsis_screening;
import java.time.LocalDateTime;
import edu.unl.cse.soft161.example.rest_backend.Medication;
import edu.unl.cse.soft161.example.rest_backend.Patient;
public interface MedicationDataSource {
public boolean isTaking(Patient patient, Medication medication);
public boolean hasTaken(Patient patient, Medication medication, LocalDateTime begin, LocalDateTime end);
}
package edu.unl.cse.soft161.example.sepsis_screening;
import static edu.unl.cse.soft161.example.rest_backend.Concept.*;
import static edu.unl.cse.soft161.example.rest_backend.Medication.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import edu.unl.cse.soft161.example.rest_backend.Concept;
import edu.unl.cse.soft161.example.rest_backend.Patient;
public final class MissingLabsDetermination extends Determination {
private static final int LACTATE_LOOKBACK_HOURS = 12;
private static final int CREATININE_LOOKBACK_HOURS = 30;
private static final int BILIRUBIN_LOOKBACK_HOURS = 30;
private static final int PLATELET_COUNT_LOOKBACK_HOURS = 30;
private static final int PTT_LOOKBACK_HOURS = 30;
private static final int BLOOD_CULTURES_LOOKBACK_HOURS = 30;
private static final int URINALYSIS_LOOKBACK_HOURS = 30;
private static final int HEPARIN_LOOKBACK_HOURS = 24;
private final List<Concept> missingLabs;
private void considerConcept(Concept concept, int hours) {
if (!withinHours(patient.getNewestObservation(concept), hours)) {
missingLabs.add(concept);
}
}
private void considerPTT(MedicationDataSource medicationDataSource) {
if (!withinHours(patient.getNewestObservation(PTT), PTT_LOOKBACK_HOURS) && !medicationDataSource
.hasTaken(patient, HEPARIN, timestamp.minusHours(HEPARIN_LOOKBACK_HOURS), timestamp)) {
missingLabs.add(PTT);
}
}
public MissingLabsDetermination(Patient patient, MedicationDataSource medicationDataSource, LocalDateTime now) {
super(patient, now);
missingLabs = new ArrayList<Concept>();
considerConcept(LACTATE, LACTATE_LOOKBACK_HOURS);
considerConcept(CREATININE, CREATININE_LOOKBACK_HOURS);
considerConcept(BILIRUBIN, BILIRUBIN_LOOKBACK_HOURS);
considerConcept(PLATELET_COUNT, PLATELET_COUNT_LOOKBACK_HOURS);
considerPTT(medicationDataSource);
considerConcept(BLOOD_CULTURES, BLOOD_CULTURES_LOOKBACK_HOURS);
considerConcept(URINALYSIS, URINALYSIS_LOOKBACK_HOURS);
}
public Patient getPatient() {
return patient;
}
public List<Concept> getMissingLabs() {
return missingLabs;
}
}
package edu.unl.cse.soft161.example.sepsis_screening;
import edu.unl.cse.soft161.example.rest_backend.Observation;
public class ObservationInInterval {
protected final Observation observation;
protected final Interval interval;
public ObservationInInterval(Observation observation, Interval interval) {
this.observation = observation;
this.interval = interval;
}
public Observation getObservation() {
return observation;
}
public Interval getInterval() {
return interval;
}
public String getUnits() {
return observation.getConcept().getUnits();
}
}
package edu.unl.cse.soft161.example.sepsis_screening;
import static edu.unl.cse.soft161.example.rest_backend.Concept.*;
import static edu.unl.cse.soft161.example.sepsis_screening.Interval.LowerBoundRelation.*;
import static edu.unl.cse.soft161.example.sepsis_screening.Interval.UpperBoundRelation.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import edu.unl.cse.soft161.example.rest_backend.Concept;
import edu.unl.cse.soft161.example.rest_backend.Observation;
import edu.unl.cse.soft161.example.rest_backend.Patient;
import edu.unl.cse.soft161.example.rest_backend.SimultaneousObservations;
public final class OrganDysfunctionDetermination extends Determination {
private static final int LACTATE_LOOKBACK_HOURS = 12;
private static final int BLOOD_PRESSURE_LOOKBACK_HOURS = 30;
private static final int CREATININE_LOOKBACK_HOURS = 30;
private static final int BILIRUBIN_LOOKBACK_HOURS = 30;
private static final Interval LACTATE_HIGH_INTERVAL = new Interval(ABOVE, 2.0);
private static final Interval MAP_LOW_INTERVAL = new Interval(BELOW, 65.0);
private static final Interval SBP_LOW_INTERVAL = new Interval(BELOW, 90.0);
private static final Interval CREATININE_INCREASE_HIGH_INTERVAL = new Interval(AT_LEAST, 0.5);
private static final Interval BILIRUBIN_HIGH_INTERVAL = new Interval(AT_LEAST, 2.0, BELOW, 10.0);
private int matchedCriteriaCount;
private final List<ObservationInInterval> evidence;
private void considerLactate() {
Observation lactate = patient.getNewestObservation(LACTATE);
if (withinHours(lactate, LACTATE_LOOKBACK_HOURS)) {
if (LACTATE_HIGH_INTERVAL.contains(lactate)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(lactate, LACTATE_HIGH_INTERVAL));
}
}
}
private void considerBloodPressure() {
SimultaneousObservations bloodPressure = patient
.getNewestSimultaneousObservations(Arrays.asList(SYSTOLIC_BLOOD_PRESSURE, DIASTOLIC_BLOOD_PRESSURE));
if (withinHours(bloodPressure, BLOOD_PRESSURE_LOOKBACK_HOURS)) {
double systolicBloodPressure = bloodPressure.get(SYSTOLIC_BLOOD_PRESSURE).getMeasurement();
double diastolicBloodPressure = bloodPressure.get(DIASTOLIC_BLOOD_PRESSURE).getMeasurement();
Observation meanArterialPressure = new Observation(bloodPressure.getTimestamp(), MEAN_ARTERIAL_PRESSURE,
(systolicBloodPressure + 2 * diastolicBloodPressure) / 3);
if (MAP_LOW_INTERVAL.contains(meanArterialPressure)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(meanArterialPressure, MAP_LOW_INTERVAL));
return;
}
}
Observation systolicBloodPressure = patient.getNewestObservation(SYSTOLIC_BLOOD_PRESSURE);
if (withinHours(systolicBloodPressure, BLOOD_PRESSURE_LOOKBACK_HOURS)) {
if (SBP_LOW_INTERVAL.contains(systolicBloodPressure)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(systolicBloodPressure, SBP_LOW_INTERVAL));
}
}
}
private void considerCreatinine() {
Observation creatinineBaseline = patient.getOldestObservation(CREATININE);
Observation creatinine = patient.getNewestObservation(CREATININE);
if (withinHours(creatinine, CREATININE_LOOKBACK_HOURS)) {
Interval shiftedInterval = new Interval(CREATININE_INCREASE_HIGH_INTERVAL,
creatinineBaseline.getMeasurement());
if (shiftedInterval.contains(creatinine)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(creatinineBaseline, null));
evidence.add(new ObservationInInterval(creatinine, shiftedInterval));
}
}
}
private void considerBilirubin() {
Observation bilirubin = patient.getNewestObservation(BILIRUBIN);
if (withinHours(bilirubin, BILIRUBIN_LOOKBACK_HOURS)) {
if (BILIRUBIN_HIGH_INTERVAL.contains(bilirubin)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(bilirubin, BILIRUBIN_HIGH_INTERVAL));
}
}
}
public OrganDysfunctionDetermination(Patient patient, MedicationDataSource medicationDataSource,
LocalDateTime now) {
super(patient, now);
matchedCriteriaCount = 0;
evidence = new ArrayList<ObservationInInterval>();
considerLactate();
considerBloodPressure();
considerCreatinine();
considerBilirubin();
}
public Patient getPatient() {
return patient;
}
public int getMatchedCriteriaCount() {
return matchedCriteriaCount;
}
public boolean contains(Concept concept) {
for (ObservationInInterval candidate : evidence) {
if (candidate.getObservation().getConcept() == concept) {
return true;
}
}
return false;
}
public List<ObservationInInterval> getEvidence() {
return this.evidence;
}
}
package edu.unl.cse.soft161.example.sepsis_screening;
import static edu.unl.cse.soft161.example.rest_backend.Concept.*;
import static edu.unl.cse.soft161.example.rest_backend.Medication.*;
import static edu.unl.cse.soft161.example.sepsis_screening.Interval.LowerBoundRelation.*;
import static edu.unl.cse.soft161.example.sepsis_screening.Interval.UpperBoundRelation.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import edu.unl.cse.soft161.example.rest_backend.Concept;
import edu.unl.cse.soft161.example.rest_backend.Observation;
import edu.unl.cse.soft161.example.rest_backend.Patient;
public final class SIRSDetermination extends Determination {
private static final Interval TEMPERATURE_LOW_INTERVAL = new Interval(BELOW, 36.0);
private static final Interval TEMPERATURE_HIGH_INTERVAL = new Interval(ABOVE, 38.3);
private static final Interval HEART_RATE_HIGH_INTERVAL = new Interval(ABOVE, 95.0);
private static final Interval RESPIRATORY_RATE_HIGH_INTERVAL = new Interval(AT_LEAST, 21.0);
private static final Interval GLUCOSE_HIGH_INTERVAL = new Interval(AT_LEAST, 140.0, BELOW, 200.0);
private static final Interval LEUKOCYTE_COUNT_LOW_INTERVAL = new Interval(BELOW, 4000.0);
private static final Interval LEUKOCYTE_COUNT_HIGH_INTERVAL = new Interval(AT_LEAST, 12000.0);
private static final Interval BLAST_PERCENTAGE_HIGH_INTERVAL = new Interval(ABOVE, 10.0);
private static final int COLONY_STIMULATION_LOOKBACK_DAYS = 60;
private int matchedCriteriaCount;
private final List<ObservationInInterval> evidence;
private int considerTemperature() {
Observation temperature = patient.getNewestObservation(TEMPERATURE);
if (TEMPERATURE_LOW_INTERVAL.contains(temperature)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(temperature, TEMPERATURE_LOW_INTERVAL));
} else if (TEMPERATURE_HIGH_INTERVAL.contains(temperature)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(temperature, TEMPERATURE_HIGH_INTERVAL));
}
return matchedCriteriaCount;
}
private int considerHeartRate() {
Observation heartRate = patient.getNewestObservation(HEART_RATE);
if (HEART_RATE_HIGH_INTERVAL.contains(heartRate)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(heartRate, HEART_RATE_HIGH_INTERVAL));
}
return matchedCriteriaCount;
}
private int considerRepiratoryRate() {
Observation respitoryRate = patient.getNewestObservation(RESPIRATORY_RATE);
if (RESPIRATORY_RATE_HIGH_INTERVAL.contains(respitoryRate)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(respitoryRate, RESPIRATORY_RATE_HIGH_INTERVAL));
}
return matchedCriteriaCount;
}
private int considerGlucose() {
Observation glucose = patient.getNewestObservation(GLUCOSE);
if (GLUCOSE_HIGH_INTERVAL.contains(glucose) && !patient.hasObservation(DIABETES)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(glucose, GLUCOSE_HIGH_INTERVAL));
}
return matchedCriteriaCount;
}
private int considerLeukocytes(MedicationDataSource medicationDataSource) {
Observation leukocyteCount = patient.getNewestObservation(LEUKOCYTE_COUNT);
Observation blastPrecentage = patient.getNewestObservation(BLAST_PERCENTAGE);
if (LEUKOCYTE_COUNT_LOW_INTERVAL.contains(leukocyteCount)) {
if (!medicationDataSource.hasTaken(patient, COLONY_STIMULATING_FACTORS,
timestamp.minusDays(COLONY_STIMULATION_LOOKBACK_DAYS), timestamp)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(leukocyteCount, LEUKOCYTE_COUNT_LOW_INTERVAL));
}
} else if (LEUKOCYTE_COUNT_HIGH_INTERVAL.contains(leukocyteCount)) {
if (!medicationDataSource.hasTaken(patient, COLONY_STIMULATING_FACTORS,
timestamp.minusDays(COLONY_STIMULATION_LOOKBACK_DAYS), timestamp)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(leukocyteCount, LEUKOCYTE_COUNT_HIGH_INTERVAL));
}
} else if (BLAST_PERCENTAGE_HIGH_INTERVAL.contains(blastPrecentage)) {
if (!medicationDataSource.hasTaken(patient, COLONY_STIMULATING_FACTORS,
timestamp.minusDays(COLONY_STIMULATION_LOOKBACK_DAYS), timestamp)) {
++matchedCriteriaCount;
evidence.add(new ObservationInInterval(blastPrecentage, BLAST_PERCENTAGE_HIGH_INTERVAL));
}
}
return matchedCriteriaCount;
}
public SIRSDetermination(Patient patient, MedicationDataSource medicationDataSource, LocalDateTime now) {
super(patient, now);
matchedCriteriaCount = 0;
evidence = new ArrayList<ObservationInInterval>();
matchedCriteriaCount = considerTemperature();
matchedCriteriaCount = considerHeartRate();
matchedCriteriaCount = considerRepiratoryRate();
matchedCriteriaCount = considerGlucose();
matchedCriteriaCount = considerLeukocytes(medicationDataSource);
}
public Patient getPatient() {
return patient;
}
public int getMatchedCriteriaCount() {
return matchedCriteriaCount;
}
public boolean contains(Concept concept) {
for (ObservationInInterval candidate : evidence) {
if (candidate.getObservation().getConcept() == concept) {
return true;
}
}
return false;
}
public List<ObservationInInterval> getEvidence() {
return this.evidence;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment