Skip to content

Commit

Permalink
Merge pull request #955 from synthetichealth/fix-condition-onset
Browse files Browse the repository at this point in the history
Prevent duplicate codes in HealthRecord Entries
  • Loading branch information
jawalonoski authored Dec 6, 2021
2 parents 8a087a2 + dfb8c57 commit 484c3f9
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 21 deletions.
24 changes: 12 additions & 12 deletions src/main/java/org/mitre/synthea/engine/State.java
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ public boolean process(Person person, long time) {
ClinicianSpecialty.GENERAL_PRACTICE, null);
entry = encounter;
if (codes != null) {
encounter.codes.addAll(codes);
encounter.mergeCodeList(codes);
}
person.setCurrentEncounter(module, encounter);
encounter.name = this.name;
Expand Down Expand Up @@ -906,7 +906,7 @@ private void renewChronicMedicationsAtWellness(Person person, long time) {

// Copy over the characteristics from old medication to new medication
medication.name = chronicMedication.name;
medication.codes.addAll(chronicMedication.codes);
medication.mergeCodeList(chronicMedication.codes);
medication.reasons.addAll(chronicMedication.reasons);
medication.prescriptionDetails = chronicMedication.prescriptionDetails;
medication.administration = chronicMedication.administration;
Expand Down Expand Up @@ -1010,7 +1010,7 @@ public boolean process(Person person, long time) {
// create a temporary coded entry to use for reference in the attribute,
// which will be replaced if the thing is diagnosed
HealthRecord.Entry codedEntry = person.record.new Entry(time, codes.get(0).code);
codedEntry.codes.addAll(codes);
codedEntry.mergeCodeList(codes);

person.attributes.put(assignToAttribute, codedEntry);
}
Expand Down Expand Up @@ -1047,7 +1047,7 @@ public void diagnose(Person person, long time) {
entry = person.record.conditionStart(time, primaryCode);
entry.name = this.name;
if (codes != null) {
entry.codes.addAll(codes);
entry.mergeCodeList(codes);
}
if (assignToAttribute != null) {
person.attributes.put(assignToAttribute, entry);
Expand Down Expand Up @@ -1123,7 +1123,7 @@ public void diagnose(Person person, long time) {
String primaryCode = codes.get(0).code;
entry = person.record.allergyStart(time, primaryCode);
entry.name = this.name;
entry.codes.addAll(codes);
entry.mergeCodeList(codes);
HealthRecord.Allergy allergy = (HealthRecord.Allergy) entry;
allergy.allergyType = allergyType;
allergy.category = category;
Expand Down Expand Up @@ -1250,7 +1250,7 @@ public boolean process(Person person, long time) {
Medication medication = person.record.medicationStart(time, primaryCode, chronic);
entry = medication;
medication.name = this.name;
medication.codes.addAll(codes);
medication.mergeCodeList(codes);

if (reason != null) {
// "reason" is an attribute or stateName referencing a previous conditionOnset state
Expand Down Expand Up @@ -1354,7 +1354,7 @@ public boolean process(Person person, long time) {
CarePlan careplan = person.record.careplanStart(time, primaryCode);
entry = careplan;
careplan.name = this.name;
careplan.codes.addAll(codes);
careplan.mergeCodeList(codes);

if (activities != null) {
careplan.activities.addAll(activities);
Expand All @@ -1372,7 +1372,7 @@ public boolean process(Person person, long time) {
// the name of the ConditionOnset state (aka "reason")
for (Entry entry : person.record.present.values()) {
if (reason.equals(entry.name)) {
careplan.reasons.addAll(entry.codes);
careplan.mergeReasonList(entry.codes);
}
}
}
Expand Down Expand Up @@ -1468,7 +1468,7 @@ public void processOnce(Person person, long time) {
HealthRecord.Procedure procedure = person.record.procedure(time, primaryCode);
entry = procedure;
procedure.name = this.name;
procedure.codes.addAll(codes);
procedure.mergeCodeList(codes);

if (reason != null) {
// "reason" is an attribute or stateName referencing a previous conditionOnset state
Expand Down Expand Up @@ -1719,7 +1719,7 @@ public boolean process(Person person, long time) {
HealthRecord.Observation observation = person.record.observation(time, primaryCode, value);
entry = observation;
observation.name = this.name;
observation.codes.addAll(codes);
observation.mergeCodeList(codes);
observation.category = category;
observation.unit = unit;

Expand Down Expand Up @@ -1788,7 +1788,7 @@ public boolean process(Person person, long time) {
person.record.multiObservation(time, primaryCode, observations.size());
entry = observation;
observation.name = this.name;
observation.codes.addAll(codes);
observation.mergeCodeList(codes);
observation.category = category;

return true;
Expand All @@ -1812,7 +1812,7 @@ public boolean process(Person person, long time) {
Report report = person.record.report(time, primaryCode, observations.size());
entry = report;
report.name = this.name;
report.codes.addAll(codes);
report.mergeCodeList(codes);

// increment number of labs by respective provider
Provider provider;
Expand Down
61 changes: 52 additions & 9 deletions src/main/java/org/mitre/synthea/world/concepts/HealthRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,19 @@ public boolean containsCode(String code, String system) {
return this.codes.stream().anyMatch(c -> code.equals(c.code) && system.equals(c.system));
}

/**
* Merges the passed in code list into the existing list of codes for this entry. If a code in
* otherCodes already exists in this.codes, it is skipped, since it already exists in the Entry.
* @param otherCodes codes to add to this entry
*/
public void mergeCodeList(List<Code> otherCodes) {
otherCodes.forEach(oc -> {
if (! this.containsCode(oc.code, oc.system)) {
this.codes.add(oc);
}
});
}

/**
* Converts the entry to a String.
*/
Expand All @@ -177,6 +190,42 @@ public String toString() {
}
}

public abstract class EntryWithReasons extends Entry {
public List<Code> reasons;

/**
* Constructor for HealthRecord EntryWithReasons.
*/
public EntryWithReasons(long time, String type) {
super(time, type);
this.reasons = new ArrayList<Code>();
}


/**
* Merges the passed in code list into the existing list of codes for this entry. If a code in
* otherCodes already exists in this.codes, it is skipped, since it already exists in the Entry.
* @param otherCodes codes to add to this entry
*/
public void mergeReasonList(List<Code> otherCodes) {
otherCodes.forEach(oc -> {
if (! this.containsReason(oc.code, oc.system)) {
this.codes.add(oc);
}
});
}

/**
* Determines if the given entry contains the provided reason code in its list of reason codes.
* @param code clinical term
* @param system system for the code
* @return true if the code is there
*/
public boolean containsReason(String code, String system) {
return this.reasons.stream().anyMatch(c -> code.equals(c.code) && system.equals(c.system));
}
}

public class Observation extends Entry {
public Object value;
public String category;
Expand Down Expand Up @@ -206,8 +255,7 @@ public Report(long time, String type, List<Observation> observations) {
}
}

public class Medication extends Entry {
public List<Code> reasons;
public class Medication extends EntryWithReasons {
public Code stopReason;
public transient JsonObject prescriptionDetails;
public Claim claim;
Expand All @@ -219,7 +267,6 @@ public class Medication extends Entry {
*/
public Medication(long time, String type) {
super(time, type);
this.reasons = new ArrayList<Code>();
// Create a medication claim.
this.claim = new Claim(this, person);
}
Expand Down Expand Up @@ -262,8 +309,7 @@ public Immunization(long start, String type) {
}
}

public class Procedure extends Entry {
public List<Code> reasons;
public class Procedure extends EntryWithReasons {
public Provider provider;
public Clinician clinician;

Expand All @@ -272,14 +318,12 @@ public class Procedure extends Entry {
*/
public Procedure(long time, String type) {
super(time, type);
this.reasons = new ArrayList<Code>();
this.stop = this.start + TimeUnit.MINUTES.toMillis(15);
}
}

public class CarePlan extends Entry {
public class CarePlan extends EntryWithReasons {
public Set<Code> activities;
public List<Code> reasons;
public transient Set<JsonObject> goals;
public Code stopReason;

Expand All @@ -289,7 +333,6 @@ public class CarePlan extends Entry {
public CarePlan(long time, String type) {
super(time, type);
this.activities = new LinkedHashSet<Code>();
this.reasons = new ArrayList<Code>();
this.goals = new LinkedHashSet<JsonObject>();
}

Expand Down
33 changes: 33 additions & 0 deletions src/test/java/org/mitre/synthea/engine/StateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,39 @@ public void condition_onset_during_encounter() throws Exception {
assertEquals(time, onsetTime.longValue());
}

/**
* Previously, if there were multiple calls to ConditionOnset, the condition Entry would contain
* multiple Codes, one for each time the ConditionOnset was invoked. This test checks to make
* sure that the same code is only added once.
* @throws Exception when bad things happen
*/
@Test
public void condition_onset_during_encounter_prevent_multiple_coding() throws Exception {
Module module = TestHelper.getFixture("condition_onset.json");
// The encounter comes first (and add it to history);
State encounter = module.getState("ED_Visit");

assertTrue(encounter.process(person, time));
person.history.add(0, encounter);

// Then appendicitis is diagnosed
State appendicitis = module.getState("Appendicitis");
assertTrue(appendicitis.process(person, time));
// Call the same ConditionOnset
assertTrue(appendicitis.process(person, time));

Encounter enc = person.record.encounters.get(0);
assertEquals(1, enc.conditions.get(0).codes.size());
Code code = enc.conditions.get(0).codes.get(0);
assertEquals("47693006", code.code);
assertEquals("Rupture of appendix", code.display);
Long onsetTime = person.getOnsetConditionRecord().getConditionLastOnsetTimeFromModule(
module.name, code.display
);
assertTrue(onsetTime != null);
assertEquals(time, onsetTime.longValue());
}

@Test
public void allergy_onset() throws Exception {
// Setup a mock to track calls to the patient record
Expand Down

0 comments on commit 484c3f9

Please sign in to comment.