Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specialities on encounters #1047

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
9 changes: 5 additions & 4 deletions src/main/java/org/mitre/synthea/engine/State.java
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,7 @@ public static class Encounter extends State {
private List<Code> codes;
private String reason;
private String telemedicinePossibility;
private String specialty;

@Override
public Encounter clone() {
Expand Down Expand Up @@ -1008,7 +1009,7 @@ private void renewChronicMedicationsAtWellness(Person person, long time) {
Provider medicationProvider = person.getCurrentProvider(module.name);
if (medicationProvider == null) {
// no provider associated with encounter or medication order
medicationProvider = person.getProvider(EncounterType.WELLNESS, time);
medicationProvider = person.getProvider(EncounterType.WELLNESS, null, time);
}
int year = Utilities.getYear(time);
medicationProvider.incrementPrescriptions(year);
Expand Down Expand Up @@ -1386,7 +1387,7 @@ public boolean process(Person person, long time) {
Provider medicationProvider = person.getCurrentProvider(module.name);
if (medicationProvider == null) {
// no provider associated with encounter or medication order
medicationProvider = person.getProvider(EncounterType.WELLNESS, time);
medicationProvider = person.getProvider(EncounterType.WELLNESS, null, time);
}

int year = Utilities.getYear(time);
Expand Down Expand Up @@ -1641,7 +1642,7 @@ public void processOnce(Person person, long time) {
if (person.getCurrentProvider(module.name) != null) {
provider = person.getCurrentProvider(module.name);
} else { // no provider associated with encounter or procedure
provider = person.getProvider(EncounterType.WELLNESS, time);
provider = person.getProvider(EncounterType.WELLNESS, null, time);
}
int year = Utilities.getYear(time);
provider.incrementProcedures(year);
Expand Down Expand Up @@ -1957,7 +1958,7 @@ public boolean process(Person person, long time) {
if (person.getCurrentProvider(module.name) != null) {
provider = person.getCurrentProvider(module.name);
} else { // no provider associated with encounter or procedure
provider = person.getProvider(EncounterType.WELLNESS, time);
provider = person.getProvider(EncounterType.WELLNESS, null, time);
}
int year = Utilities.getYear(time);
provider.incrementLabs(year);
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/org/mitre/synthea/export/CCDAExporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.mitre.synthea.helpers.RandomNumberGenerator;

import org.mitre.synthea.world.agents.Person;
import org.mitre.synthea.world.concepts.HealthRecord;
import org.mitre.synthea.world.concepts.HealthRecord.Encounter;
import org.mitre.synthea.world.concepts.HealthRecord.Observation;
import org.mitre.synthea.world.concepts.RaceAndEthnicity;
Expand Down Expand Up @@ -138,7 +139,7 @@ public static String export(Person person, long time) {
person.attributes.put("ethnicity_display_lookup",
RaceAndEthnicity.LOOK_UP_CDC_ETHNICITY_DISPLAY);

if (person.attributes.get(Person.PREFERREDYPROVIDER + "wellness") == null) {
if (person.preferredProviders.getWellnessProvider() == null) {
// This person does not have a preferred provider. This happens for veterans at age 20 due to
// the provider reset and they don't have a provider until their next wellness visit. There
// may be other cases. This ensures the preferred provider is there for the CCDA template
Expand All @@ -148,7 +149,8 @@ public static String export(Person person, long time) {
encounter = person.record.encounters.get(person.record.encounters.size() - 1);
}
if (encounter != null) {
person.attributes.put(Person.PREFERREDYPROVIDER + "wellness", encounter.provider);
person.preferredProviders.forceRelationship(HealthRecord.EncounterType.WELLNESS, null,
encounter.provider);
} else {
throw new IllegalStateException(String.format("Unable to export to CCDA because "
+ "person %s %s has no preferred provider.",
Expand All @@ -157,6 +159,8 @@ public static String export(Person person, long time) {
}
}

person.attributes.put("wellness_provider", person.preferredProviders.getWellnessProvider());
jawalonoski marked this conversation as resolved.
Show resolved Hide resolved

StringWriter writer = new StringWriter();
try {
Template template = TEMPLATES.getTemplate("ccda.ftl");
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/mitre/synthea/export/CDWExporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ public void export(Person person, long time) throws IOException {
return;
}
int primarySta3n = -1;
Provider provider = person.getProvider(EncounterType.WELLNESS, time);
Provider provider = person.getProvider(EncounterType.WELLNESS, null, time);
if (provider != null) {
String state = Location.getStateName(provider.state);
String tz = Location.getTimezoneByState(state);
Expand Down Expand Up @@ -1647,4 +1647,4 @@ private static void write(String line, OutputStreamWriter writer) throws IOExcep
writer.flush();
}
}
}
}
4 changes: 2 additions & 2 deletions src/main/java/org/mitre/synthea/export/FhirDstu2.java
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ private static Entry encounter(Person person, Entry personEntry,
Provider provider = encounter.provider;
if (provider == null) {
// no associated provider, patient goes to wellness provider
provider = person.getProvider(EncounterType.WELLNESS, encounter.start);
provider = person.getProvider(EncounterType.WELLNESS, null, encounter.start);
}
if (TRANSACTION_BUNDLE) {
encounterResource.setServiceProvider(new ResourceReferenceDt(
Expand Down Expand Up @@ -1866,4 +1866,4 @@ private static Entry newEntry(Bundle bundle, BaseResource resource,

return entry;
}
}
}
6 changes: 3 additions & 3 deletions src/main/java/org/mitre/synthea/export/FhirR4.java
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ private static BundleEntryComponent encounter(Person person, BundleEntryComponen
Provider provider = encounter.provider;
if (provider == null) {
// no associated provider, patient goes to wellness provider
provider = person.getProvider(EncounterType.WELLNESS, encounter.start);
provider = person.getProvider(EncounterType.WELLNESS, null, encounter.start);
}

if (TRANSACTION_BUNDLE) {
Expand Down Expand Up @@ -2176,7 +2176,7 @@ private static BundleEntryComponent provenance(Bundle bundle, Person person, lon
}

if (clinician == null && providerOrganization == null) {
providerOrganization = person.getProvider(EncounterType.WELLNESS, stopTime);
providerOrganization = person.getProvider(EncounterType.WELLNESS, null, stopTime);
clinician =
providerOrganization.chooseClinicianList(ClinicianSpecialty.GENERAL_PRACTICE, person);
} else if (clinician == null || providerOrganization == null) {
Expand All @@ -2186,7 +2186,7 @@ private static BundleEntryComponent provenance(Bundle bundle, Person person, lon
} else if (clinician != null && providerOrganization == null) {
providerOrganization = clinician.getOrganization();
if (providerOrganization == null) {
providerOrganization = person.getProvider(EncounterType.WELLNESS, stopTime);
providerOrganization = person.getProvider(EncounterType.WELLNESS, null, stopTime);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/mitre/synthea/export/FhirStu3.java
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ private static BundleEntryComponent encounter(Person person, BundleEntryComponen
Provider provider = encounter.provider;
if (provider == null) {
// no associated provider, patient goes to wellness provider
provider = person.getProvider(EncounterType.WELLNESS, encounter.start);
provider = person.getProvider(EncounterType.WELLNESS, null, encounter.start);
}

if (TRANSACTION_BUNDLE) {
Expand Down Expand Up @@ -2707,4 +2707,4 @@ private static BundleEntryComponent newEntry(Bundle bundle, Resource resource,

return entry;
}
}
}
8 changes: 1 addition & 7 deletions src/main/java/org/mitre/synthea/modules/EncounterModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,7 @@ public static Encounter createEncounter(Person person, long time, EncounterType
encounter.codes.add(code);
}
// assign a provider organization
Provider prov = null;
if (specialty.equalsIgnoreCase(ClinicianSpecialty.CARDIOLOGY)) {
// Get the first provider in the list that was loaded
prov = Provider.getProviderList().get(0);
} else {
prov = person.getProvider(type, time);
}
Provider prov = person.getProvider(type, specialty, time);
prov.incrementEncounters(type, year);
encounter.provider = prov;
// assign a clinician
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/org/mitre/synthea/modules/LifecycleModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,7 @@ private static boolean age(Person person, long time) {
if (person.attributes.get("veteran_provider_reset") == null) {
// reset providers for veterans, they'll switch to VA facilities
person.attributes.remove(Person.CURRENTPROVIDER);
for (EncounterType type : EncounterType.values()) {
person.attributes.remove(Person.PREFERREDYPROVIDER + type);
}

person.attributes.put("veteran_provider_reset", true);
}
}
Expand Down
53 changes: 12 additions & 41 deletions src/main/java/org/mitre/synthea/world/agents/Person.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.mitre.synthea.world.concepts.HealthRecord.Code;
import org.mitre.synthea.world.concepts.HealthRecord.Encounter;
import org.mitre.synthea.world.concepts.HealthRecord.EncounterType;
import org.mitre.synthea.world.concepts.PreferredProviders;
import org.mitre.synthea.world.concepts.VitalSign;
import org.mitre.synthea.world.concepts.healthinsurance.CoverageRecord;
import org.mitre.synthea.world.concepts.healthinsurance.InsurancePlan;
Expand Down Expand Up @@ -158,7 +159,9 @@ public class Person implements Serializable, RandomNumberGenerator, QuadTreeElem
/** History of the currently active module. */
public List<State> history;
/** Record of insurance coverage. */
public final CoverageRecord coverage;
public CoverageRecord coverage;
/** A place to maintain preferred providers by encounter type and specialty. */
public PreferredProviders preferredProviders;

/**
* Person constructor.
Expand All @@ -178,6 +181,7 @@ public Person(long seed) {
}
this.initializeDefaultHealthRecords();
coverage = new CoverageRecord(this);
preferredProviders = new PreferredProviders();
}

/**
Expand Down Expand Up @@ -553,7 +557,7 @@ public boolean hadPriorState(String name, String since, Long within) {
*/
public Encounter encounterStart(long time, EncounterType type) {
// Set the record for the current provider as active
Provider provider = getProvider(type, time);
Provider provider = getProvider(type, null, time);
record = getHealthRecord(provider, time);
// Start the encounter
return record.encounterStart(time, type);
Expand Down Expand Up @@ -643,57 +647,24 @@ public boolean reserveCurrentEncounter(long time, String module) {

// Providers API -----------------------------------------------------------
public static final String CURRENTPROVIDER = "currentProvider";
public static final String PREFERREDYPROVIDER = "preferredProvider";

/**
* Get the preferred provider for the specified encounter type. If none is set the
* provider at the specified time as the preferred provider for this encounter type.
*/
public Provider getProvider(EncounterType type, long time) {
String key = PREFERREDYPROVIDER + type;
if (!attributes.containsKey(key)) {
setProvider(type, time);
} else {
Entity entity = (Entity) attributes.get(ENTITY);
// check to see if this is a fixed identity
if (entity != null) {
Provider provider = (Provider) attributes.get(key);
HealthRecord healthRecord = getHealthRecord(provider, time);
long lastEncounterTime = healthRecord.lastEncounterTime();
// check to see if the provider is valid for this see range
if (lastEncounterTime != Long.MIN_VALUE
&& !entity.seedAt(time).getPeriod().contains(lastEncounterTime)) {
// The provider is not in the seed range. Force finding a new provider.
System.out.println("Move reset for " + type);
setProvider(type, time);
}
}
public Provider getProvider(EncounterType type, String speciality, long time) {
if (!preferredProviders.doesRelationshipExist(type, speciality)) {
setProvider(type, speciality, time);
}
return (Provider) attributes.get(key);
}

/**
* Set the preferred provider for the specified encounter type.
*/
public void setProvider(EncounterType type, Provider provider) {
if (provider == null) {
throw new RuntimeException("Unable to find provider: " + type);
}
String key = PREFERREDYPROVIDER + type;
attributes.put(key, provider);
return preferredProviders.get(type, speciality);
}

/**
* Set the preferred provider for the specified encounter type to be the provider
* at the specified time.
*/
public void setProvider(EncounterType type, long time) {
Provider provider = Provider.findService(this, type, time);
if (provider == null && Provider.USE_HOSPITAL_AS_DEFAULT) {
// Default to Hospital
provider = Provider.findService(this, EncounterType.INPATIENT, time);
}
setProvider(type, provider);
public void setProvider(EncounterType type, String specialty, long time) {
preferredProviders.startNewRelationship(this, type, specialty, time);
}

/**
Expand Down
21 changes: 16 additions & 5 deletions src/main/java/org/mitre/synthea/world/agents/Provider.java
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ public boolean hasService(EncounterType service) {
return servicesProvided.contains(service);
}

public boolean hasSpecialty(String specialty) {
return clinicianMap.get(specialty) != null;
}

public boolean canOffer(EncounterType type, String specialty) {
return (type == null || hasService(type)) && (specialty == null || hasSpecialty(specialty));
}

public void incrementEncounters(EncounterType service, int year) {
increment(year, ENCOUNTERS);
increment(year, ENCOUNTERS + "-" + service);
Expand Down Expand Up @@ -271,17 +279,19 @@ public double getRevenue() {
* Find specific service provider for the given person.
* @param person The patient who requires the service.
* @param service The service required. For example, EncounterType.AMBULATORY.
* @param specialty the ClinicianSpecialty required. May be null.
* @param time The date/time within the simulated world, in milliseconds.
* @return Service provider or null if none is available.
*/
public static Provider findService(Person person, EncounterType service, long time) {
public static Provider findService(Person person, EncounterType service, String specialty,
long time) {
double maxDistance = MAX_PROVIDER_SEARCH_DISTANCE;
double degrees = 0.125;
List<Provider> options = null;
Provider provider = null;
while (degrees <= maxDistance) {
options = findProvidersByLocation(person, degrees);
provider = providerFinder.find(options, person, service, time);
provider = providerFinder.find(options, person, service, specialty, time);
if (provider != null) {
return provider;
}
Expand All @@ -301,7 +311,7 @@ public static Provider findServiceNewProvider(Person person, EncounterType servi
Provider provider = null;
while (degrees <= maxDistance) {
options = findNewProvidersByLocation(person, degrees, takenIds);
provider = providerFinder.find(options, person, service, time);
provider = providerFinder.find(options, person, service, null, time);
if (provider != null && !takenIds.contains(provider.uuid)) {
return provider;
}
Expand Down Expand Up @@ -484,8 +494,9 @@ public static void loadProviders(Location location, String filename,

parsed.location = location;

if (row.get("hasSpecialties") == null
|| row.get("hasSpecialties").equalsIgnoreCase("false")) {
String aSpecialty = ClinicianSpecialty.getSpecialties()[0];

if (row.get(aSpecialty) == null) {
parsed.clinicianMap.put(ClinicianSpecialty.GENERAL_PRACTICE,
parsed.generateClinicianList(1, ClinicianSpecialty.GENERAL_PRACTICE,
random));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ public interface IProviderFinder {
* @param providers The list of eligible providers.
* @param person The patient who requires the service.
* @param service The service required. For example, EncounterType.AMBULATORY.
* @param specialty The specialty required. For example, ClinicianSpecialty.INFECTIOUS_DISEASE
* @param time The date/time within the simulated world, in milliseconds.
* @return Service provider or null if none is available.
*/
public Provider find(List<Provider> providers, Person person, EncounterType service, long time);
}
Provider find(List<Provider> providers, Person person, EncounterType service, String specialty,
long time);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
public class ProviderFinderNearest implements IProviderFinder {

@Override
public Provider find(List<Provider> providers, Person person, EncounterType service, long time) {
public Provider find(List<Provider> providers, Person person, EncounterType service,
String specialty, long time) {
Stream<Provider> options = providers.stream()
// Find providers that accept the person
.filter(p -> p.accepts(person, time));

// Find providers with the requested service, if one is given
if (service != null) {
options = options.filter(p -> p.hasService(service));
}
// Find providers with the requested service and specialties, if given
options = options.filter(p -> p.canOffer(service, specialty));

// If it's not an emergency
if (service == null
Expand Down Expand Up @@ -53,4 +52,4 @@ public Provider find(List<Provider> providers, Person person, EncounterType serv
return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
public class ProviderFinderNearestMedicare implements IProviderFinder {

@Override
public Provider find(List<Provider> providers, Person person, EncounterType service, long time) {
public Provider find(List<Provider> providers, Person person, EncounterType service,
String specialty, long time) {
Stream<Provider> options = providers.stream()
// Find providers that accept the person
.filter(p -> p.accepts(person, time));

// Find providers with the requested service, if one is given
if (service != null) {
options = options.filter(p -> p.hasService(service));
options = options.filter(p -> p.canOffer(service, specialty));
}

// Filter to only Medicare providers...
Expand Down
Loading
Loading