Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,21 @@ For example:
- `run_synthea Massachusetts`
- `run_synthea Alaska Juneau`
- `run_synthea -s 12345`
- `run_synthea -p 1000`
- `run_synthea -s 987 Washington Seattle`
- `run_synthea -s 21 -p 100 Utah "Salt Lake City"`
- `run_synthea -m metabolic*`
- `run_synthea -m *covid19* -p 1000` COVID 19 test data

Some settings can be changed in `./src/main/resources/synthea.properties`.

Synthea<sup>TM</sup> will output patient records in C-CDA and FHIR formats in `./output`.

### TCP functionality
This project has functionality added specifically for TCT which includes:
This project has functionality added specifically for TCP which includes:
- Saving data to local files (now using an argument)
- Sending patients directly to a FHIR server
- Sending files to AWS S3
- creating data linked to gluu inum

### Saving files locally
In order to save files locally (json format) you have to add the following argument on the command line
Expand Down Expand Up @@ -116,16 +117,16 @@ need to follow the steps to configure your programmatic access to AWS.
For the Role to assume you'll need to user the entire ARN and that role has to be able to see the configured
bucket.

### Adding Person generated percentage
If we want to link more than one patient to a single person (empi) we can include
### Adding patients linked to gluu inum
In order to create patients linked to a gluu inum the following flag should be added, this flag is mandatory because
it is handling the population to be created
```
-per <percentage>
-gluu <user's first name>,<patients to be created>,<user's first name>,<patients to be created>
```
this parameter tells synthea to generate a percentage of persons based on the total population parameter (-p), for example
if we are sending 100 patients, and we want to generate 50% of persona, this will link 2 patients per person, the usage
would be as follows
this parameter tells synthea to generate a certain amount of ALIVE patients based on the configuration, the first name
must match with a user name being returned from the Person API
```
run_synthea -p 100 -fhir -per 50
run_synthea -fhir -gluu Test,1,greenday,1
```
### Synthea<sup>TM</sup> GraphViz
Generate graphical visualizations of Synthea<sup>TM</sup> rules and modules.
Expand Down
33 changes: 23 additions & 10 deletions src/main/java/App.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.*;

import org.mitre.synthea.client.IPersonService;
import org.mitre.synthea.engine.Generator;
import org.mitre.synthea.export.FhirR4;
import org.mitre.synthea.export.cp.IPersonFetcher;
import org.mitre.synthea.export.cp.PersonFetcher;
import org.mitre.synthea.helpers.Config;

/*
Expand Down Expand Up @@ -72,16 +73,28 @@ public static void main(String[] args) throws Exception {
options.saveLocally = true;
}else if (currArg.equalsIgnoreCase("-s3")) {
options.saveIntoS3 = true;
}else if (currArg.equalsIgnoreCase("-per")) {
FhirR4.percentage = Integer.parseInt(argsQ.poll());
}else if (currArg.equalsIgnoreCase("-gluu")) {
String gluuData = argsQ.poll();
String[] data = gluuData.split(",");
if((data.length%2)!=0){
throw new Exception("Invalid gluu data.");
}
int population = 0;
Map<String,Integer> config = new HashMap<>();
for(int x=0;x<data.length-1;x+=2){
String username = data[x];
int patients = Integer.parseInt(data[x+1]);
population+=patients;
config.put(username,patients);
}
options.population = population;
FhirR4.population = config;
IPersonFetcher personService = new PersonFetcher();
FhirR4.persons = personService.getTcpPersons();
}else if (currArg.equalsIgnoreCase("-cs")) {
String value = argsQ.poll();
options.clinicianSeed = Long.parseLong(value);
} else if (currArg.equalsIgnoreCase("-p")) {
String value = argsQ.poll();
options.population = Integer.parseInt(value);
FhirR4.total = options.population;
} else if (currArg.equalsIgnoreCase("-o")) {
} else if (currArg.equalsIgnoreCase("-o")) {
String value = argsQ.poll();
options.overflow = Boolean.parseBoolean(value);
} else if (currArg.equalsIgnoreCase("-g")) {
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/mitre/synthea/client/IPersonService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.mitre.synthea.client;

import com.google.gson.JsonObject;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Headers;

import java.util.List;

public interface IPersonService {
@Headers("Content-Type: application/json")
@GET("/Prod/person")
Call<List<TCPPerson>> getPersons();
}
36 changes: 36 additions & 0 deletions src/main/java/org/mitre/synthea/client/TCPPerson.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.mitre.synthea.client;

import com.google.gson.annotations.SerializedName;

public class TCPPerson {
@SerializedName("last_name")
private String lastName;
@SerializedName("gluu_id")
private String gluuId;
@SerializedName("first_name")
private String firstNae;

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getGluuId() {
return gluuId;
}

public void setGluuId(String gluuId) {
this.gluuId = gluuId;
}

public String getFirstNae() {
return firstNae;
}

public void setFirstNae(String firstNae) {
this.firstNae = firstNae;
}
}
38 changes: 19 additions & 19 deletions src/main/java/org/mitre/synthea/export/FhirR4.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,7 @@

import java.awt.geom.Point2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;

import org.hl7.fhir.r4.model.Address;
Expand Down Expand Up @@ -126,6 +119,7 @@
import org.hl7.fhir.r4.model.codesystems.DoseRateType;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.mitre.synthea.client.TCPPerson;
import org.mitre.synthea.engine.Components;
import org.mitre.synthea.engine.Components.Attachment;
import org.mitre.synthea.helpers.Config;
Expand Down Expand Up @@ -164,9 +158,9 @@ public class FhirR4 {
private static final String UNITSOFMEASURE_URI = "http://unitsofmeasure.org";
private static final String DICOM_DCM_URI = "http://dicom.nema.org/resources/ontology/DCM";
private static final String MEDIA_TYPE_URI = "http://terminology.hl7.org/CodeSystem/media-type";
public static int counter = 0;
public static int total = 0;
public static int percentage = 0;

public static Map<String, Integer> population;
public static List<TCPPerson> persons;

@SuppressWarnings("rawtypes")
private static final Map raceEthnicityCodes = loadRaceEthnicityCodes();
Expand Down Expand Up @@ -361,14 +355,20 @@ public static String convertToFHIRJson(Person person, long stopTime) {
@SuppressWarnings("rawtypes")
private static BundleEntryComponent basicInfo(Person person, Bundle bundle, long stopTime) {
String uniqueID = null;
if (FhirR4.percentage == 0) {
uniqueID = UUID.randomUUID().toString();
} else {
int picker = ((FhirR4.total * FhirR4.percentage) / 100);
if (FhirR4.counter == picker) {
FhirR4.counter = 0;
}
uniqueID = UUID.nameUUIDFromBytes(String.valueOf(FhirR4.counter++).getBytes()).toString();
Optional<Map.Entry<String, Integer>> toProcess =
FhirR4.population.entrySet().stream().filter(e -> !e.getValue().equals(0)).findFirst();
if (!toProcess.isPresent()) {
throw new RuntimeException("Bad configuration");
}

Optional<TCPPerson> personConfig = FhirR4.persons.stream().filter(p->p.getFirstNae().equals(toProcess.get().getKey()))
.findFirst();
if(!personConfig.isPresent()){
throw new RuntimeException("Person " + toProcess.get().getKey() + " not found");
}
uniqueID = UUID.fromString(personConfig.get().getGluuId()).toString();
if (person.alive(stopTime)) {
toProcess.get().setValue(toProcess.get().getValue() - 1);
}
Patient patientResource = new Patient();

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/mitre/synthea/export/cp/IPersonFetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.mitre.synthea.export.cp;

import org.mitre.synthea.client.TCPPerson;

import java.io.IOException;
import java.util.List;

public interface IPersonFetcher {
List<TCPPerson> getTcpPersons() throws IOException;
}
44 changes: 44 additions & 0 deletions src/main/java/org/mitre/synthea/export/cp/PersonFetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.mitre.synthea.export.cp;

import com.google.gson.JsonObject;
import okhttp3.OkHttpClient;
import org.mitre.synthea.client.IHapiFhirService;
import org.mitre.synthea.client.IPersonService;
import org.mitre.synthea.client.TCPPerson;
import org.mitre.synthea.helpers.Config;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class PersonFetcher implements IPersonFetcher {
private IPersonService personService;

public PersonFetcher() {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder()
.callTimeout(2, TimeUnit.MINUTES)
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(2, TimeUnit.MINUTES)
.writeTimeout(30, TimeUnit.SECONDS);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Config.get("exporter.person.api"))
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build();
this.personService = retrofit.create(IPersonService.class);
}

public List<TCPPerson> getTcpPersons() throws IOException {
Call<List<TCPPerson>> res = personService.getPersons();
Response<List<TCPPerson>> response = res.execute();
if (!response.isSuccessful()) {
throw new RuntimeException("Error getting persons");
}
return response.body();

}
}
1 change: 1 addition & 0 deletions src/main/resources/synthea.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Starting with a properties file because it requires no additional dependencies

exporter.fhir.fhirServer = http://localhost:8080/
exporter.person.api = https://o5d4mbjac1.execute-api.us-east-1.amazonaws.com/
exporter.aws.role = arn:aws:iam::789379687343:role/DevDevops
exporter.aws.bucket = commonhealth-synthea
exporter.baseDirectory = ./output/
Expand Down