Guest User

Untitled

a guest
Jan 19th, 2018
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 55.64 KB | None | 0 0
  1. package org.mitre.synthea.engine;
  2.  
  3. import static org.mitre.synthea.helpers.ValidationHelper.allOf;
  4. import static org.mitre.synthea.helpers.ValidationHelper.buildMessage;
  5. import static org.mitre.synthea.helpers.ValidationHelper.exactlyOneOf;
  6.  
  7. import com.google.gson.Gson;
  8. import com.google.gson.JsonObject;
  9.  
  10. import java.util.List;
  11.  
  12. import org.mitre.synthea.engine.Components.Exact;
  13. import org.mitre.synthea.engine.Components.ExactWithUnit;
  14. import org.mitre.synthea.engine.Components.Range;
  15. import org.mitre.synthea.engine.Components.RangeWithUnit;
  16. import org.mitre.synthea.engine.Transition.ComplexTransition;
  17. import org.mitre.synthea.engine.Transition.ComplexTransitionOption;
  18. import org.mitre.synthea.engine.Transition.ConditionalTransition;
  19. import org.mitre.synthea.engine.Transition.ConditionalTransitionOption;
  20. import org.mitre.synthea.engine.Transition.DirectTransition;
  21. import org.mitre.synthea.engine.Transition.DistributedTransition;
  22. import org.mitre.synthea.engine.Transition.DistributedTransitionOption;
  23. import org.mitre.synthea.helpers.Utilities;
  24. import org.mitre.synthea.modules.EncounterModule;
  25. import org.mitre.synthea.world.agents.CommunityHealthWorker;
  26. import org.mitre.synthea.world.agents.Person;
  27. import org.mitre.synthea.world.agents.Provider;
  28. import org.mitre.synthea.world.concepts.HealthRecord;
  29. import org.mitre.synthea.world.concepts.HealthRecord.CarePlan;
  30. import org.mitre.synthea.world.concepts.HealthRecord.Code;
  31. import org.mitre.synthea.world.concepts.HealthRecord.EncounterType;
  32. import org.mitre.synthea.world.concepts.HealthRecord.Entry;
  33. import org.mitre.synthea.world.concepts.HealthRecord.Medication;
  34. import org.mitre.synthea.world.concepts.HealthRecord.Report;
  35.  
  36. public abstract class State implements Validation, Cloneable {
  37. public Module module;
  38. public String name;
  39. public Long entered;
  40. public Long exited;
  41.  
  42. private Transition transition;
  43. // note that these are not Transition objects, because they are JSON lists
  44. private String directTransition; // or in this case just a String
  45. private List<ConditionalTransitionOption> conditionalTransition;
  46. private List<DistributedTransitionOption> distributedTransition;
  47. private List<ComplexTransitionOption> complexTransition;
  48. public List<String> remarks;
  49.  
  50. protected void initialize(Module module, String name, JsonObject definition) {
  51. this.module = module;
  52. this.name = name;
  53.  
  54. if (directTransition != null) {
  55. this.transition = new DirectTransition(directTransition);
  56. } else if (distributedTransition != null) {
  57. this.transition = new DistributedTransition(distributedTransition);
  58. } else if (conditionalTransition != null) {
  59. this.transition = new ConditionalTransition(conditionalTransition);
  60. } else if (complexTransition != null) {
  61. this.transition = new ComplexTransition(complexTransition);
  62. } else if (!(this instanceof Terminal)) {
  63. throw new RuntimeException("State `" + name + "` has no transition.\n");
  64. }
  65. }
  66.  
  67. /**
  68. * Construct a state object from the given definitions.
  69. *
  70. * @param module
  71. * The module this state belongs to
  72. * @param name
  73. * The name of the state
  74. * @param definition
  75. * The JSON definition of the state
  76. * @return The constructed State object. The returned object will be of the appropriate subclass
  77. * of State, based on the "type" parameter in the JSON definition.
  78. * @throws Exception
  79. * if the state type does not exist
  80. */
  81. public static State build(Module module, String name, JsonObject definition) throws Exception {
  82. String className = State.class.getName() + "$" + definition.get("type").getAsString();
  83.  
  84. Class<?> stateClass = Class.forName(className);
  85.  
  86. Gson gson = Utilities.getGson();
  87. State state = (State) gson.fromJson(definition, stateClass);
  88.  
  89. state.initialize(module, name, definition);
  90.  
  91. return state;
  92. }
  93.  
  94. /**
  95. * clone() should copy all the necessary variables of this State so that it can be correctly
  96. * executed and modified without altering the original copy. So for example, 'entered' and
  97. * 'exited' times should not be copied so the clone can be cleanly executed.
  98. */
  99. public State clone() {
  100. try {
  101. State clone = (State) super.clone();
  102. clone.module = this.module;
  103. clone.name = this.name;
  104. clone.transition = this.transition;
  105. clone.remarks = this.remarks;
  106. return clone;
  107. } catch (CloneNotSupportedException e) {
  108. // should not happen, and not something we can handle
  109. throw new RuntimeException(e);
  110. }
  111. }
  112.  
  113. public String transition(Person person, long time) {
  114. return transition.follow(person, time);
  115. }
  116.  
  117. public Transition getTransition() {
  118. return transition;
  119. }
  120.  
  121. /**
  122. * Process this State with the given Person at the specified time within the simulation.
  123. *
  124. * @param person
  125. * : the person being simulated
  126. * @param time
  127. * : the date within the simulated world
  128. * @return `true` if processing should continue to the next state, `false` if the processing
  129. * should halt for this time step.
  130. */
  131. public abstract boolean process(Person person, long time);
  132.  
  133. /**
  134. * Run the state. This processes the state, setting entered and exit times.
  135. *
  136. * @param person
  137. * the person being simulated
  138. * @param time
  139. * the date within the simulated world
  140. * @return `true` if processing should continue to the next state, `false` if the processing
  141. * should halt for this time step.
  142. */
  143. public boolean run(Person person, long time) {
  144. // System.out.format("State: %s\n", this.name);
  145. if (this.entered == null) {
  146. this.entered = time;
  147. }
  148. boolean exit = process(person, time);
  149.  
  150. if (exit) {
  151. // Delay state returns a special value for exited,
  152. // to indicate when the delay actually completed.
  153. if (this instanceof Delay) {
  154. this.exited = ((Delay)this).next;
  155. } else {
  156. this.exited = time;
  157. }
  158. }
  159.  
  160. return exit;
  161. }
  162.  
  163. public String toString() {
  164. return this.getClass().getSimpleName() + " '" + name + "'";
  165. }
  166.  
  167. /**
  168. * The Initial state type is the first state that is processed in a generic module. It does not
  169. * provide any specific function except to indicate the starting point, so it has no properties
  170. * except its type. The Initial state requires the specific name "Initial". In addition, it is the
  171. * only state for which there can only be one in the whole module.
  172. */
  173. public static class Initial extends State {
  174. @Override
  175. public boolean process(Person person, long time) {
  176. return true;
  177. }
  178. }
  179.  
  180. /**
  181. * The Simple state type indicates a state that performs no additional actions, adds no additional
  182. * information to the patient entity, and just transitions to the next state. As an example, this
  183. * state may be used to chain conditional or distributed transitions, in order to represent
  184. * complex logic.
  185. */
  186. public static class Simple extends State {
  187. @Override
  188. public boolean process(Person person, long time) {
  189. return true;
  190. }
  191. }
  192.  
  193. /**
  194. * The CallSubmodule state immediately processes a reusable series of states contained in a
  195. * submodule. These states are processes in the same time step, starting with the submodule's
  196. * Initial state. Once the submodule's Terminal state is reached, execution of the calling module
  197. * resumes.
  198. */
  199. public static class CallSubmodule extends State {
  200. @Metadata(required = true)
  201. private String submodule;
  202.  
  203. @Override
  204. public CallSubmodule clone() {
  205. CallSubmodule clone = (CallSubmodule) super.clone();
  206. clone.submodule = submodule;
  207. return clone;
  208. }
  209.  
  210. @Override
  211. public boolean process(Person person, long time) {
  212. // e.g. "submodule": "medications/otc_antihistamine"
  213. List<State> moduleHistory = person.history;
  214. Module submod = Module.getModuleByPath(submodule);
  215. boolean completed = submod.process(person, time);
  216.  
  217. if (completed) {
  218. // add the history from the submodule to this module's history, at the front
  219. moduleHistory.addAll(0, person.history);
  220. // clear the submodule history
  221. person.attributes.remove(submod.name);
  222. // reset person.history to this module's history
  223. person.history = moduleHistory;
  224. // add this state to history to indicate we returned to this module
  225. person.history.add(0, this);
  226.  
  227. return true;
  228. } else {
  229. // reset person.history to this module's history
  230. person.history = moduleHistory;
  231. // the submodule is still processing
  232. // next time we call this state it should pick up where it left off
  233. return false;
  234. }
  235. }
  236. }
  237.  
  238. /**
  239. * The Terminal state type indicates the end of the module progression. Once a Terminal state is
  240. * reached, no further progress will be made. As such, Terminal states cannot have any transition
  241. * properties. If desired, there may be multiple Terminal states with different names to indicate
  242. * different ending points; however, this has no actual effect on the records that are produced.
  243. */
  244. public static class Terminal extends State {
  245. @Override
  246. public boolean process(Person person, long time) {
  247. return false;
  248. }
  249. }
  250.  
  251. /**
  252. * The Delay state type introduces a pre-configured temporal delay in the module's timeline. As a
  253. * simple example, a Delay state may indicate a one-month gap in time between an initial encounter
  254. * and a followup encounter. The module will not pass through the Delay state until the proper
  255. * amount of time has passed. The Delay state may define an exact time to delay (e.g. 4 days) or a
  256. * range of time to delay (e.g. 5 - 7 days).
  257. *
  258. * <p>Implementation Details Synthea generation occurs in time steps; the default time step is 7
  259. * days. This means that if a module is processed on a given date, the next time it is processed
  260. * will be exactly 7 days later. If a delay expiration falls between time steps (e.g. day 3 of a
  261. * 7-day time step), then the first time step after the delay expiration will effectively rewind
  262. * the clock to the delay expiration time and process states using that time. Once it reaches a
  263. * state that it can't pass through, it will process it once more using the original (7-day time
  264. * step) time.
  265. */
  266. public static class Delay extends State {
  267. // next is "transient" in the sense that it represents object state
  268. // as opposed to the other fields which represent object definition
  269. // hence it is not set in clone()
  270. public Long next;
  271.  
  272. private RangeWithUnit<Long> range;
  273. private ExactWithUnit<Long> exact;
  274.  
  275.  
  276. @Override
  277. public Delay clone() {
  278. Delay clone = (Delay) super.clone();
  279. clone.exact = exact;
  280. clone.range = range;
  281. return clone;
  282. }
  283.  
  284. @Override
  285. public void validateSpecial(Module context, List<String> path, List<String> messages) {
  286. if (!exactlyOneOf(range, exact)) {
  287. String message = "Either range or exact is required";
  288. messages.add(buildMessage(message, path));
  289. }
  290. }
  291.  
  292. @Override
  293. public boolean process(Person person, long time) {
  294. if (this.next == null) {
  295. if (exact != null) {
  296. // use an exact quantity
  297. this.next = time + Utilities.convertTime(exact.unit, exact.quantity);
  298. } else if (range != null) {
  299. // use a range
  300. this.next =
  301. time + Utilities.convertTime(range.unit, (long) person.rand(range.low, range.high));
  302. } else {
  303. throw new RuntimeException("Delay state has no exact or range: " + this);
  304. }
  305. }
  306.  
  307. return time >= this.next;
  308. }
  309. }
  310.  
  311. /**
  312. * The Guard state type indicates a point in the module through which a patient can only pass if
  313. * they meet certain logical conditions. For example, a Guard may block a workflow until the
  314. * patient reaches a certain age, after which the Guard allows the module to continue to progress.
  315. * Depending on the condition(s), a patient may be blocked by a Guard until they die - in which
  316. * case they never reach the module's Terminal state.
  317. *
  318. * <p>The Guard state's allow property provides the logical condition(s) which must be met to
  319. * allow the module to continue to the next state. Guard states are similar to conditional
  320. * transitions in some ways, but also have an important difference. A conditional transition
  321. * tests conditions once and uses the result to immediately choose the next state. A Guard
  322. * state will test the same condition on every time-step until the condition passes, at which
  323. * point it progresses to the next state.
  324. */
  325. public static class Guard extends State {
  326. @Metadata(required = true)
  327. private Logic allow;
  328.  
  329. @Override
  330. public Guard clone() {
  331. Guard clone = (Guard) super.clone();
  332. clone.allow = allow;
  333. return clone;
  334. }
  335.  
  336. @Override
  337. public boolean process(Person person, long time) {
  338. boolean exit = allow.test(person, time);
  339. if (exit) {
  340. this.exited = time;
  341. }
  342. return exit;
  343. }
  344. }
  345.  
  346. /**
  347. * The SetAttribute state type sets a specified attribute on the patient entity. In addition to
  348. * the assign_to_attribute property on MedicationOrder/ConditionOnset/etc states, this state
  349. * allows for arbitrary text or values to be set on an attribute, or for the attribute to be
  350. * reset.
  351. */
  352. public static class SetAttribute extends State {
  353. @Metadata(required = true)
  354. private String attribute;
  355. private Object value;
  356.  
  357. @Override
  358. protected void initialize(Module module, String name, JsonObject definition) {
  359. super.initialize(module, name, definition);
  360.  
  361. // special handling for integers
  362. if (value instanceof Double) {
  363. double doubleVal = (double)value;
  364.  
  365. if (doubleVal == Math.rint(doubleVal)) {
  366. value = (int) doubleVal;
  367. }
  368. }
  369. }
  370.  
  371. @Override
  372. public SetAttribute clone() {
  373. SetAttribute clone = (SetAttribute) super.clone();
  374. clone.attribute = attribute;
  375. clone.value = value;
  376. return clone;
  377. }
  378.  
  379. @Override
  380. public boolean process(Person person, long time) {
  381. if (value != null) {
  382. person.attributes.put(attribute, value);
  383. } else if (person.attributes.containsKey(attribute)) {
  384. // intentionally clear out the variable
  385. person.attributes.remove(attribute);
  386. }
  387.  
  388. return true;
  389. }
  390. }
  391.  
  392. /**
  393. * The Counter state type increments or decrements a specified numeric attribute on the patient
  394. * entity. In essence, this state counts the number of times it is processed.
  395. *
  396. * <p>Note: The attribute is initialized with a default value of 0 if not previously set.
  397. */
  398. public static class Counter extends State {
  399. @Metadata(required = true)
  400. private String attribute;
  401. @Metadata(required = true, validValues = {"increment", "decrement"})
  402. private String action;
  403. private boolean increment;
  404.  
  405. @Override
  406. protected void initialize(Module module, String name, JsonObject definition) {
  407. super.initialize(module, name, definition);
  408. increment = action.equals("increment");
  409. }
  410.  
  411. @Override
  412. public Counter clone() {
  413. Counter clone = (Counter) super.clone();
  414. clone.attribute = attribute;
  415. clone.increment = increment;
  416. return clone;
  417. }
  418.  
  419. @Override
  420. public boolean process(Person person, long time) {
  421. int counter = 0;
  422. if (person.attributes.containsKey(attribute)) {
  423. counter = (int) person.attributes.get(attribute);
  424. }
  425.  
  426. if (increment) {
  427. counter++;
  428. } else {
  429. counter--;
  430. }
  431. person.attributes.put(attribute, counter);
  432. return true;
  433. }
  434. }
  435.  
  436. /**
  437. * The Encounter state type indicates a point in the module where an encounter should take place.
  438. * Encounters are important in Synthea because they are generally the mechanism through which the
  439. * actual patient record is updated (a disease is diagnosed, a medication is prescribed, etc). The
  440. * generic module framework supports integration with scheduled wellness encounters from Synthea's
  441. * Encounters module, as well as creation of new stand-alone encounters.
  442. *
  443. * <p>Scheduled Wellness Encounters vs. Standalone Encounters An Encounter state with the wellness
  444. * property set to true will block until the next scheduled wellness encounter occurs. Scheduled
  445. * wellness encounters are managed by the Encounters module in Synthea and, depending on the
  446. * patient's age, typically occur every 1 - 3 years. When a scheduled wellness encounter finally
  447. * does occur, Synthea will search the generic modules for currently blocked Encounter states and
  448. * will immediately process them (and their subsequent states). An example where this might be
  449. * used is for a condition that onsets between encounters, but isn't found and diagnosed until the
  450. * next regularly scheduled wellness encounter.
  451. *
  452. * <p>An Encounter state without the wellness property set will be processed and recorded in the
  453. * patient record immediately. Since this creates an encounter, the encounter_class and one or
  454. * more codes must be specified in the state configuration. This is how generic modules can
  455. * introduce encounters that are not already scheduled by other modules.
  456. *
  457. * <p>Encounters and Related Events Encounters are typically the mechanism through which a
  458. * patient's record will be updated. This makes sense since most recorded events (diagnoses,
  459. * prescriptions, and procedures) should happen in the context of an encounter. When an Encounter
  460. * state is successfully processed, Synthea will look through the previously processed states for
  461. * un-recorded ConditionOnset or AllergyOnset instances that indicate that Encounter (by name) as
  462. * the target_encounter. If Synthea finds any, they will be recorded in the patient's record at
  463. * the time of the encounter. This is the mechanism for onsetting a disease before it is
  464. * discovered and diagnosed.
  465. */
  466. public static class Encounter extends State {
  467. private boolean wellness;
  468. private String encounterClass;
  469. @Metadata(min = 1)
  470. private List<Code> codes;
  471. private String reason;
  472.  
  473. @Override
  474. public Encounter clone() {
  475. Encounter clone = (Encounter) super.clone();
  476. clone.wellness = wellness;
  477. clone.encounterClass = encounterClass;
  478. clone.reason = reason;
  479. clone.codes = codes;
  480. return clone;
  481. }
  482.  
  483. @Override
  484. public void validateSpecial(Module context, List<String> path, List<String> messages) {
  485. if (!(wellness || allOf(codes, encounterClass))) {
  486. String message = "Either wellness or all of (codes and encounter_class) is required.";
  487. messages.add(buildMessage(message, path));
  488. }
  489. }
  490.  
  491. @Override
  492. public boolean process(Person person, long time) {
  493. if (wellness) {
  494. HealthRecord.Encounter encounter = person.record.currentEncounter(time);
  495. String activeKey = EncounterModule.ACTIVE_WELLNESS_ENCOUNTER + " " + this.module.name;
  496. if (person.attributes.containsKey(activeKey)) {
  497. person.attributes.remove(activeKey);
  498.  
  499. person.setCurrentEncounter(module, encounter);
  500.  
  501. // find closest provider and increment encounters count
  502. Provider provider = Provider.findClosestService(person, "wellness");
  503. person.addCurrentProvider(module.name, provider);
  504. int year = Utilities.getYear(time);
  505. provider.incrementEncounters("wellness", year);
  506. encounter.provider = provider;
  507.  
  508. diagnosePastConditions(person, time);
  509.  
  510. return true;
  511. } else {
  512. // Block until we're in a wellness encounter... then proceed.
  513. return false;
  514. }
  515. } else {
  516.  
  517. HealthRecord.Encounter encounter = person.record.encounterStart(time, encounterClass);
  518. if (codes != null) {
  519. encounter.codes.addAll(codes);
  520. }
  521. person.setCurrentEncounter(module, encounter);
  522. if (encounterClass.equals("emergency")) {
  523. // if emergency room encounter and CHW policy is enabled for emergency rooms, add CHW
  524. // interventions
  525. person.chwEncounter(time, CommunityHealthWorker.DEPLOYMENT_EMERGENCY);
  526. }
  527.  
  528. // find closest provider and increment encounters count
  529. Provider provider = Provider.findClosestService(person, encounterClass);
  530. person.addCurrentProvider(module.name, provider);
  531. int year = Utilities.getYear(time);
  532. provider.incrementEncounters(encounterClass, year);
  533. encounter.provider = provider;
  534.  
  535. encounter.name = this.name;
  536.  
  537. diagnosePastConditions(person, time);
  538.  
  539. if (reason != null) {
  540. if (person.attributes.containsKey(reason)) {
  541. Entry condition = (Entry) person.attributes.get(reason);
  542. encounter.reason = condition.codes.get(0);
  543. } else if (person.hadPriorState(reason)) {
  544. // loop through the present conditions, the condition "name" will match
  545. // the name of the ConditionOnset state (aka "reason")
  546. for (Entry entry : person.record.present.values()) {
  547. if (reason.equals(entry.name)) {
  548. encounter.reason = entry.codes.get(0);
  549. break;
  550. }
  551. }
  552. }
  553. }
  554.  
  555. return true;
  556. }
  557. }
  558.  
  559. private void diagnosePastConditions(Person person, long time) {
  560. for (State state : person.history) {
  561. if (state instanceof OnsetState && !((OnsetState) state).diagnosed) {
  562. ((OnsetState) state).diagnose(person, time);
  563. }
  564. }
  565. }
  566.  
  567. public boolean isWellness() {
  568. return wellness;
  569. }
  570. }
  571.  
  572. /**
  573. * The EncounterEnd state type indicates the end of the encounter the patient is currently in, for
  574. * example when the patient leaves a clinician's office, or is discharged from a hospital. The
  575. * time the encounter ended is recorded on the patient's record.
  576. *
  577. * <p>Note on Wellness Encounters Because wellness encounters are scheduled and initiated outside
  578. * the generic modules, and a single wellness encounter may contain observations or medications
  579. * from multiple modules, an EncounterEnd state will not record the end time for a wellness
  580. * encounter. Hence it is not strictly necessary to use an EncounterEnd state to end the wellness
  581. * encounter. Still, it is recommended to use an EncounterEnd state to mark a clear end to the
  582. * encounter.
  583. */
  584. public static class EncounterEnd extends State {
  585. private Code dischargeDisposition;
  586.  
  587. @Override
  588. public EncounterEnd clone() {
  589. EncounterEnd clone = (EncounterEnd) super.clone();
  590. clone.dischargeDisposition = dischargeDisposition;
  591. return clone;
  592. }
  593.  
  594. @Override
  595. public boolean process(Person person, long time) {
  596. HealthRecord.Encounter encounter = person.getCurrentEncounter(module);
  597. if (encounter.type != EncounterType.WELLNESS.toString()) {
  598. encounter.stop = time;
  599. // if CHW policy is enabled for discharge follow up, add CHW interventions for all
  600. // non-wellness encounters
  601. person.chwEncounter(time, CommunityHealthWorker.DEPLOYMENT_POSTDISCHARGE);
  602. }
  603.  
  604. encounter.discharge = dischargeDisposition;
  605.  
  606. // reset current provider hash
  607. person.removeCurrentProvider(module.name);
  608.  
  609. person.setCurrentEncounter(module, null);
  610.  
  611. return true;
  612. }
  613. }
  614.  
  615. /**
  616. * OnsetState is a parent class for ConditionOnset and AllergyOnset, where some common logic can
  617. * be shared. It is an implementation detail and should never be referenced directly in a JSON
  618. * module.
  619. */
  620. private abstract static class OnsetState extends State {
  621. public boolean diagnosed;
  622.  
  623. @Metadata(min = 1)
  624. protected List<Code> codes;
  625. protected String assignToAttribute;
  626.  
  627. @Metadata(referenceToStateType = Encounter.class)
  628. protected String targetEncounter;
  629.  
  630. public OnsetState clone() {
  631. OnsetState clone = (OnsetState) super.clone();
  632. clone.codes = codes;
  633. clone.assignToAttribute = assignToAttribute;
  634. clone.targetEncounter = targetEncounter;
  635. return clone;
  636. }
  637.  
  638. @Override
  639. public boolean process(Person person, long time) {
  640. HealthRecord.Encounter encounter = person.getCurrentEncounter(module);
  641.  
  642. if (targetEncounter == null
  643. || (encounter != null && targetEncounter.equals(encounter.name))) {
  644. diagnose(person, time);
  645. } else if (assignToAttribute != null && codes != null) {
  646. // TODO - this is a hack. can we eventually split Diagnosis & Onset states?
  647.  
  648. // create a temporary coded entry to use for reference in the attribute,
  649. // which will be replaced if the thing is diagnosed
  650. HealthRecord.Entry codedEntry = person.record.new Entry(time, codes.get(0).code);
  651. codedEntry.codes.addAll(codes);
  652.  
  653. person.attributes.put(assignToAttribute, codedEntry);
  654. }
  655. return true;
  656. }
  657.  
  658. public abstract void diagnose(Person person, long time);
  659. }
  660.  
  661. /**
  662. * The ConditionOnset state type indicates a point in the module where the patient acquires a
  663. * condition. This is not necessarily the same as when the condition is diagnosed and recorded in
  664. * the patient's record. In fact, it is possible for a condition to onset but never be discovered.
  665. *
  666. * <p>If the ConditionOnset state's target_encounter is set to the name of a future encounter,
  667. * then the condition will only be diagnosed when that future encounter occurs.
  668. */
  669. public static class ConditionOnset extends OnsetState {
  670. @Override
  671. public void diagnose(Person person, long time) {
  672. String primaryCode = codes.get(0).code;
  673. Entry condition = person.record.conditionStart(time, primaryCode);
  674. condition.name = this.name;
  675. if (codes != null) {
  676. condition.codes.addAll(codes);
  677. }
  678. if (assignToAttribute != null) {
  679. person.attributes.put(assignToAttribute, condition);
  680. }
  681.  
  682. diagnosed = true;
  683. }
  684. }
  685.  
  686. /**
  687. * The ConditionEnd state type indicates a point in the module where a currently active condition
  688. * should be ended, for example if the patient has been cured of a disease.
  689. *
  690. * <p>The ConditionEnd state supports three ways of specifying the condition to end: By `codes[]`,
  691. * specifying the system, code, and display name of the condition to end By `condition_onset`,
  692. * specifying the name of the ConditionOnset state in which the condition was onset By
  693. * `referenced_by_attribute`, specifying the name of the attribute to which a previous
  694. * ConditionOnset state assigned a condition
  695. */
  696. public static class ConditionEnd extends State {
  697. @Metadata(min = 1)
  698. private List<Code> codes;
  699. @Metadata(referenceToStateType = ConditionOnset.class)
  700. private String conditionOnset;
  701. private String referencedByAttribute;
  702.  
  703. @Override
  704. public ConditionEnd clone() {
  705. ConditionEnd clone = (ConditionEnd) super.clone();
  706. clone.codes = codes;
  707. clone.conditionOnset = conditionOnset;
  708. clone.referencedByAttribute = referencedByAttribute;
  709. return clone;
  710. }
  711.  
  712. @Override
  713. public void validateSpecial(Module context, List<String> path, List<String> messages) {
  714. if (!exactlyOneOf(codes, conditionOnset, referencedByAttribute)) {
  715. String msg =
  716. "Exactly one of codes, condition_onset, or referenced_by_attribute is required";
  717. messages.add(buildMessage(msg, path));
  718. }
  719. }
  720.  
  721. @Override
  722. public boolean process(Person person, long time) {
  723. if (conditionOnset != null) {
  724. person.record.conditionEndByState(time, conditionOnset);
  725. } else if (referencedByAttribute != null) {
  726. Entry condition = (Entry) person.attributes.get(referencedByAttribute);
  727. condition.stop = time;
  728. person.record.conditionEnd(time, condition.type);
  729. } else if (codes != null) {
  730. codes.forEach(code -> person.record.conditionEnd(time, code.code));
  731. }
  732. return true;
  733. }
  734. }
  735.  
  736. /**
  737. * The AllergyOnset state type indicates a point in the module where the patient acquires an
  738. * allergy. This is not necessarily the same as when the allergy is diagnosed and recorded in the
  739. * patient's record. In fact, it is possible for an allergy to onset but never be discovered.
  740. *
  741. * <p>If the AllergyOnset state's target_encounter is set to the name of a future encounter,
  742. * then the allergy will only be diagnosed when that future encounter occurs.
  743. */
  744. public static class AllergyOnset extends OnsetState {
  745. @Override
  746. public void diagnose(Person person, long time) {
  747. String primaryCode = codes.get(0).code;
  748. Entry allergy = person.record.allergyStart(time, primaryCode);
  749. allergy.name = this.name;
  750. allergy.codes.addAll(codes);
  751.  
  752. if (assignToAttribute != null) {
  753. person.attributes.put(assignToAttribute, allergy);
  754. }
  755.  
  756. diagnosed = true;
  757. }
  758. }
  759.  
  760. /**
  761. * The AllergyEnd state type indicates a point in the module where a currently active allergy
  762. * should be ended, for example if the patient's allergy subsides with time.
  763. *
  764. * <p>The AllergyEnd state supports three ways of specifying the allergy to end: By `codes[]`,
  765. * specifying the system, code, and display name of the allergy to end By `allergy_onset`,
  766. * specifying the name of the AllergyOnset state in which the allergy was onset By
  767. * `referenced_by_attribute`, specifying the name of the attribute to which a previous
  768. * AllergyOnset state assigned a condition
  769. *
  770. */
  771. public static class AllergyEnd extends State {
  772. @Metadata(min = 1)
  773. private List<Code> codes;
  774.  
  775. @Metadata(referenceToStateType = AllergyOnset.class)
  776. private String allergyOnset;
  777. private String referencedByAttribute;
  778.  
  779. @Override
  780. public AllergyEnd clone() {
  781. AllergyEnd clone = (AllergyEnd) super.clone();
  782. clone.codes = codes;
  783. clone.allergyOnset = allergyOnset;
  784. clone.referencedByAttribute = referencedByAttribute;
  785. return clone;
  786. }
  787.  
  788. @Override
  789. public void validateSpecial(Module context, List<String> path, List<String> messages) {
  790. if (!exactlyOneOf(codes, allergyOnset, referencedByAttribute)) {
  791. String msg =
  792. "Exactly one of codes, allergy_onset, or referenced_by_attribute is required";
  793. messages.add(buildMessage(msg, path));
  794. }
  795. }
  796.  
  797. @Override
  798. public boolean process(Person person, long time) {
  799. if (allergyOnset != null) {
  800. person.record.allergyEndByState(time, allergyOnset);
  801. } else if (referencedByAttribute != null) {
  802. Entry allergy = (Entry) person.attributes.get(referencedByAttribute);
  803. allergy.stop = time;
  804. person.record.allergyEnd(time, allergy.type);
  805. } else if (codes != null) {
  806. codes.forEach(code -> person.record.conditionEnd(time, code.code));
  807. }
  808. return true;
  809. }
  810. }
  811.  
  812. /**
  813. * The MedicationOrder state type indicates a point in the module where a medication is
  814. * prescribed. MedicationOrder states may only be processed during an Encounter, and so must occur
  815. * after the target Encounter state and before the EncounterEnd. See the Encounter section above
  816. * for more details. The MedicationOrder state supports identifying a previous ConditionOnset or
  817. * the name of an attribute as the reason for the prescription.
  818. */
  819. public static class MedicationOrder extends State {
  820. @Metadata(required = true, min = 1)
  821. private List<Code> codes;
  822. private String reason;
  823. private JsonObject prescription; // TODO make this a Component
  824. private String assignToAttribute;
  825.  
  826. @Override
  827. public MedicationOrder clone() {
  828. MedicationOrder clone = (MedicationOrder) super.clone();
  829. clone.codes = codes;
  830. clone.reason = reason;
  831. clone.prescription = prescription;
  832. clone.assignToAttribute = assignToAttribute;
  833. return clone;
  834. }
  835.  
  836. @Override
  837. public boolean process(Person person, long time) {
  838. String primaryCode = codes.get(0).code;
  839. Medication medication = person.record.medicationStart(time, primaryCode);
  840. medication.name = this.name;
  841. medication.codes.addAll(codes);
  842.  
  843. if (reason != null) {
  844. // "reason" is an attribute or stateName referencing a previous conditionOnset state
  845. if (person.attributes.containsKey(reason)) {
  846. Entry condition = (Entry) person.attributes.get(reason);
  847. medication.reasons.addAll(condition.codes);
  848. } else if (person.hadPriorState(reason)) {
  849. // loop through the present conditions, the condition "name" will match
  850. // the name of the ConditionOnset state (aka "reason")
  851. for (Entry entry : person.record.present.values()) {
  852. if (reason.equals(entry.name)) {
  853. medication.reasons.addAll(entry.codes);
  854. }
  855. }
  856. }
  857. }
  858.  
  859. medication.prescriptionDetails = prescription;
  860.  
  861. if (assignToAttribute != null) {
  862. person.attributes.put(assignToAttribute, medication);
  863. }
  864. // increment number of prescriptions prescribed by respective hospital
  865. Provider medicationProvider = person.getCurrentProvider(module.name);
  866. if (medicationProvider == null) {
  867. // no provider associated with encounter or medication order
  868. medicationProvider = person.getAmbulatoryProvider();
  869. }
  870.  
  871. int year = Utilities.getYear(time);
  872. medicationProvider.incrementPrescriptions(year);
  873. return true;
  874. }
  875. }
  876.  
  877. /**
  878. * The MedicationEnd state type indicates a point in the module where a currently prescribed
  879. * medication should be ended.
  880. *
  881. * <p>The MedicationEnd state supports three ways of specifying the medication to end:
  882. * By `codes[]`, specifying the code system, code, and display name of the medication to end By
  883. * `medication_order`, specifying the name of the MedicationOrder state in which the medication
  884. * was prescribed By `referenced_by_attribute`, specifying the name of the attribute to which a
  885. * previous MedicationOrder state assigned a medication
  886. */
  887. public static class MedicationEnd extends State {
  888. @Metadata(min = 1)
  889. private List<Code> codes;
  890. @Metadata(referenceToStateType = MedicationOrder.class)
  891. private String medicationOrder;
  892. private String referencedByAttribute;
  893.  
  894. // note that this code has some child codes for various different reasons,
  895. // ex "medical aim achieved", "ineffective", "avoid interaction", "side effect", etc
  896. private static final Code EXPIRED = new Code("SNOMED-CT", "182840001",
  897. "Drug treatment stopped - medical advice");
  898.  
  899. @Override
  900. public MedicationEnd clone() {
  901. MedicationEnd clone = (MedicationEnd) super.clone();
  902. clone.codes = codes;
  903. clone.medicationOrder = medicationOrder;
  904. clone.referencedByAttribute = referencedByAttribute;
  905. return clone;
  906. }
  907.  
  908. @Override
  909. public boolean process(Person person, long time) {
  910. if (medicationOrder != null) {
  911. person.record.medicationEndByState(time, medicationOrder, EXPIRED);
  912. } else if (referencedByAttribute != null) {
  913. Medication medication = (Medication) person.attributes.get(referencedByAttribute);
  914. medication.stop = time;
  915. person.record.medicationEnd(time, medication.type, EXPIRED);
  916. } else if (codes != null) {
  917. codes.forEach(code -> person.record.medicationEnd(time, code.code, EXPIRED));
  918. }
  919. return true;
  920. }
  921. }
  922.  
  923. /**
  924. * The CarePlanStart state type indicates a point in the module where a care plan should be
  925. * prescribed. CarePlanStart states may only be processed during an Encounter, and so must occur
  926. * after the target Encounter state and before the EncounterEnd. See the Encounter section above
  927. * for more details. One or more codes describes the care plan and a list of activities describes
  928. * what the care plan entails.
  929. */
  930. public static class CarePlanStart extends State {
  931. @Metadata(required = true, min = 1)
  932. private List<Code> codes;
  933. private List<Code> activities;
  934. private List<JsonObject> goals; // TODO: make this a Component
  935. private String reason;
  936. private String assignToAttribute;
  937.  
  938. @Override
  939. public CarePlanStart clone() {
  940. CarePlanStart clone = (CarePlanStart) super.clone();
  941. clone.codes = codes;
  942. clone.activities = activities;
  943. clone.goals = goals;
  944. clone.reason = reason;
  945. clone.assignToAttribute = assignToAttribute;
  946. return clone;
  947. }
  948.  
  949. @Override
  950. public boolean process(Person person, long time) {
  951. String primaryCode = codes.get(0).code;
  952. CarePlan careplan = person.record.careplanStart(time, primaryCode);
  953. careplan.name = this.name;
  954. careplan.codes.addAll(codes);
  955.  
  956. if (activities != null) {
  957. careplan.activities.addAll(activities);
  958. }
  959. if (goals != null) {
  960. careplan.goals.addAll(goals);
  961. }
  962. if (reason != null) {
  963. // "reason" is an attribute or stateName referencing a previous conditionOnset state
  964. if (person.attributes.containsKey(reason)) {
  965. Entry condition = (Entry) person.attributes.get(reason);
  966. careplan.reasons.addAll(condition.codes);
  967. } else if (person.hadPriorState(reason)) {
  968. // loop through the present conditions, the condition "name" will match
  969. // the name of the ConditionOnset state (aka "reason")
  970. for (Entry entry : person.record.present.values()) {
  971. if (reason.equals(entry.name)) {
  972. careplan.reasons.addAll(entry.codes);
  973. }
  974. }
  975. }
  976. }
  977. if (assignToAttribute != null) {
  978. person.attributes.put(assignToAttribute, careplan);
  979. }
  980. return true;
  981. }
  982. }
  983.  
  984. /**
  985. * The CarePlanEnd state type indicates a point in the module where a currently prescribed care
  986. * plan should be ended. The CarePlanEnd state supports three ways of specifying the care plan to
  987. * end: By `codes[]`, specifying the code system, code, and display name of the care plan to end
  988. * By `careplan`, specifying the name of the CarePlanStart state in which the care plan was
  989. * prescribed By `referenced_by_attribute`, specifying the name of the attribute to which a
  990. * previous CarePlanStart state assigned a care plan
  991. */
  992. public static class CarePlanEnd extends State {
  993. @Metadata(min = 1)
  994. private List<Code> codes;
  995. @Metadata(referenceToStateType = CarePlanStart.class)
  996. private String careplan;
  997. private String referencedByAttribute;
  998.  
  999. private static final Code FINISHED = new Code("SNOMED-CT", "385658003", "Done");
  1000.  
  1001. @Override
  1002. public CarePlanEnd clone() {
  1003. CarePlanEnd clone = (CarePlanEnd) super.clone();
  1004. clone.codes = codes;
  1005. clone.careplan = careplan;
  1006. clone.referencedByAttribute = referencedByAttribute;
  1007. return clone;
  1008. }
  1009.  
  1010. @Override
  1011. public boolean process(Person person, long time) {
  1012. if (careplan != null) {
  1013. person.record.careplanEndByState(time, careplan, FINISHED);
  1014. } else if (referencedByAttribute != null) {
  1015. CarePlan careplan = (CarePlan) person.attributes.get(referencedByAttribute);
  1016. careplan.stop = time;
  1017. person.record.careplanEnd(time, careplan.type, FINISHED);
  1018. } else if (codes != null) {
  1019. codes.forEach(code -> person.record.careplanEnd(time, code.code, FINISHED));
  1020. }
  1021. return true;
  1022. }
  1023. }
  1024.  
  1025. /**
  1026. * The Procedure state type indicates a point in the module where a procedure should be performed.
  1027. * Procedure states may only be processed during an Encounter, and so must occur after the target
  1028. * Encounter state and before the EncounterEnd. See the Encounter section above for more details.
  1029. * Optionally, you may define a duration of time that the procedure takes. The Procedure also
  1030. * supports identifying a previous ConditionOnset or an attribute as the reason for the procedure.
  1031. */
  1032. public static class Procedure extends State {
  1033. @Metadata(required = true, min = 1)
  1034. private List<Code> codes;
  1035. private String reason;
  1036. private RangeWithUnit<Long> duration;
  1037. private String assignToAttribute;
  1038.  
  1039. @Override
  1040. public Procedure clone() {
  1041. Procedure clone = (Procedure) super.clone();
  1042. clone.codes = codes;
  1043. clone.reason = reason;
  1044. clone.duration = duration;
  1045. clone.assignToAttribute = assignToAttribute;
  1046. return clone;
  1047. }
  1048.  
  1049. @Override
  1050. public boolean process(Person person, long time) {
  1051. String primaryCode = codes.get(0).code;
  1052. HealthRecord.Procedure procedure = person.record.procedure(time, primaryCode);
  1053. procedure.name = this.name;
  1054. procedure.codes.addAll(codes);
  1055.  
  1056. if (reason != null) {
  1057. // "reason" is an attribute or stateName referencing a previous conditionOnset state
  1058. if (person.attributes.containsKey(reason)) {
  1059. Entry condition = (Entry) person.attributes.get(reason);
  1060. procedure.reasons.addAll(condition.codes);
  1061. } else if (person.hadPriorState(reason)) {
  1062. // loop through the present conditions, the condition "name" will match
  1063. // the name of the ConditionOnset state (aka "reason")
  1064. for (Entry entry : person.record.present.values()) {
  1065. if (reason.equals(entry.name)) {
  1066. procedure.reasons.addAll(entry.codes);
  1067. }
  1068. }
  1069. }
  1070. }
  1071. if (duration != null) {
  1072. double durationVal = person.rand(duration.low, duration.high);
  1073. procedure.stop = procedure.start + Utilities.convertTime(duration.unit, (long) durationVal);
  1074. }
  1075. // increment number of procedures by respective hospital
  1076. Provider provider;
  1077. if (person.getCurrentProvider(module.name) != null) {
  1078. provider = person.getCurrentProvider(module.name);
  1079. } else { // no provider associated with encounter or procedure
  1080. provider = person.getAmbulatoryProvider();
  1081. }
  1082. int year = Utilities.getYear(time);
  1083. provider.incrementProcedures(year);
  1084.  
  1085. if (assignToAttribute != null) {
  1086. person.attributes.put(assignToAttribute, procedure);
  1087. }
  1088.  
  1089. return true;
  1090. }
  1091. }
  1092.  
  1093. /**
  1094. * The VitalSign state type indicates a point in the module where a patient's vital sign is set.
  1095. * Vital Signs represent the actual physical state of the patient, in contrast to Observations
  1096. * which are the recording of that physical state.
  1097. *
  1098. * <p>Usage Notes In general, the Vital Sign should be used if the value directly affects the
  1099. * patient's physical condition. For example, high blood pressure directly increases the risk of
  1100. * heart attack so any conditional logic that would trigger a heart attack should reference a
  1101. * Vital Sign instead of an Observation. ' On the other hand, if the value only affects the
  1102. * patient's care, using just an Observation would be more appropriate. For example, it is the
  1103. * observation of MMSE that can lead to a diagnosis of Alzheimer's; MMSE is an observed value and
  1104. * not a physical metric, so it should not be stored in a VitalSign.
  1105. */
  1106. public static class VitalSign extends State {
  1107. @Metadata(required = true)
  1108. private org.mitre.synthea.world.concepts.VitalSign vitalSign;
  1109. @Metadata(required = true)
  1110. private String unit;
  1111. private Range<Double> range;
  1112. private Exact<Double> exact;
  1113.  
  1114. @Override
  1115. public VitalSign clone() {
  1116. VitalSign clone = (VitalSign) super.clone();
  1117. clone.range = range;
  1118. clone.exact = exact;
  1119. clone.vitalSign = vitalSign;
  1120. clone.unit = unit;
  1121. return clone;
  1122. }
  1123.  
  1124. @Override
  1125. public void validateSpecial(Module context, List<String> path, List<String> messages) {
  1126. if (!exactlyOneOf(range, exact)) {
  1127. String message = "Either range or exact is required";
  1128. messages.add(buildMessage(message, path));
  1129. }
  1130. }
  1131.  
  1132. @Override
  1133. public boolean process(Person person, long time) {
  1134. if (exact != null) {
  1135. person.setVitalSign(vitalSign, exact.quantity);
  1136. } else if (range != null) {
  1137. double value = person.rand(range.low, range.high);
  1138. person.setVitalSign(vitalSign, value);
  1139. } else {
  1140. throw new RuntimeException(
  1141. "VitalSign state has no exact quantity or low/high range: " + this);
  1142. }
  1143.  
  1144. return true;
  1145. }
  1146. }
  1147.  
  1148. /**
  1149. * The Observation state type indicates a point in the module where an observation is recorded.
  1150. * Observations include clinical findings, vital signs, lab tests, etc. Observation states may
  1151. * only be processed during an Encounter, and so must occur after the target Encounter state and
  1152. * before the EncounterEnd. See the Encounter section above for more details.
  1153. *
  1154. * <p>Observation Categories Common observation categories include: "vital-signs" :
  1155. * Clinical observations measure the body's basic functions such as such as blood pressure, heart
  1156. * rate, respiratory rate, height, weight, body mass index, head circumference, pulse oximetry,
  1157. * temperature, and body surface area.
  1158. *
  1159. * <p>"procedure" : Observations generated by other procedures. This category includes
  1160. * observations resulting from interventional and non-interventional procedures excluding lab and
  1161. * imaging (e.g. cardiology catheterization, endoscopy, electrodiagnostics, etc.). Procedure
  1162. * results are typically generated by a clinician to provide more granular information about
  1163. * component observations made during a procedure, such as where a gastroenterologist reports the
  1164. * size of a polyp observed during a colonoscopy.
  1165. *
  1166. * <p>"laboratory" : The results of observations generated by laboratories. Laboratory results are
  1167. * typically generated by laboratories providing analytic services in areas such as chemistry,
  1168. * hematology, serology, histology, cytology, anatomic pathology, microbiology, and/or virology.
  1169. * These observations are based on analysis of specimens obtained from the patient and submitted
  1170. * to the laboratory.
  1171. *
  1172. * <p>"exam" : Observations generated by physical exam findings including direct observations made
  1173. * by a clinician and use of simple instruments and the result of simple maneuvers performed
  1174. * directly on the patient's body.
  1175. *
  1176. * <p>"social-history" : The Social History Observations define the patient's occupational,
  1177. * personal (e.g. lifestyle), social, and environmental history and health risk factors, as well
  1178. * as administrative data such as marital status, race, ethnicity and religious affiliation.
  1179. */
  1180. public static class Observation extends State {
  1181. @Metadata(required = true, min = 1)
  1182. private List<Code> codes;
  1183. private Range<Double> range;
  1184. private Exact<Object> exact;
  1185. private String attribute;
  1186. private org.mitre.synthea.world.concepts.VitalSign vitalSign;
  1187. private String category;
  1188. private String unit;
  1189.  
  1190. @Override
  1191. public Observation clone() {
  1192. Observation clone = (Observation) super.clone();
  1193. clone.codes = codes;
  1194. clone.range = range;
  1195. clone.exact = exact;
  1196. clone.attribute = attribute;
  1197. clone.vitalSign = vitalSign;
  1198. clone.category = category;
  1199. clone.unit = unit;
  1200. return clone;
  1201. }
  1202.  
  1203. @Override
  1204. public void validateSpecial(Module context, List<String> path, List<String> messages) {
  1205. // ruby version is
  1206. // required_field or: [:vital_sign, and: [:unit, or: [:attribute, :range, :exact]]]
  1207. // try to translate that in a way that's readable
  1208.  
  1209. if (vitalSign == null
  1210. && (unit == null || !exactlyOneOf(attribute, range, exact))) {
  1211. String msg =
  1212. "Either vital_sign, or unit and one of (attribute, range, or exact) is required.";
  1213. messages.add(buildMessage(msg, path));
  1214. }
  1215. }
  1216.  
  1217. @Override
  1218. public boolean process(Person person, long time) {
  1219. String primaryCode = codes.get(0).code;
  1220. Object value = null;
  1221. if (exact != null) {
  1222. value = exact.quantity;
  1223. } else if (range != null) {
  1224. value = person.rand(range.low, range.high);
  1225. } else if (attribute != null) {
  1226. value = person.attributes.get(attribute);
  1227. } else if (vitalSign != null) {
  1228. value = person.getVitalSign(vitalSign);
  1229. }
  1230. HealthRecord.Observation observation = person.record.observation(time, primaryCode, value);
  1231. observation.name = this.name;
  1232. observation.codes.addAll(codes);
  1233. observation.category = category;
  1234. observation.unit = unit;
  1235.  
  1236. return true;
  1237. }
  1238. }
  1239.  
  1240. /**
  1241. * ObservationGroup is an internal parent class to provide common logic to state types that
  1242. * package multiple observations into a single entity. It is an implementation detail and should
  1243. * not be referenced by JSON modules directly.
  1244. */
  1245. private abstract static class ObservationGroup extends State {
  1246. @Metadata(required = true, min = 1)
  1247. protected List<Code> codes;
  1248.  
  1249. @Metadata(required = true)
  1250. protected int numberOfObservations;
  1251.  
  1252. public ObservationGroup clone() {
  1253. ObservationGroup clone = (ObservationGroup) super.clone();
  1254. clone.codes = codes;
  1255. clone.numberOfObservations = numberOfObservations;
  1256. return clone;
  1257. }
  1258. }
  1259.  
  1260. /**
  1261. * The MultiObservation state indicates that some number of prior Observation states should be
  1262. * grouped together as a single observation. This can be necessary when one observation records
  1263. * multiple values, for example in the case of Blood Pressure, which is really 2 values, Systolic
  1264. * and Diastolic Blood Pressure. This state must occur directly after the relevant Observation
  1265. * states, otherwise unexpected behavior can occur. MultiObservation states may only be processed
  1266. * during an Encounter, and so must occur after the target Encounter state and before the
  1267. * EncounterEnd. See the Encounter section above for more details.
  1268. */
  1269. public static class MultiObservation extends ObservationGroup {
  1270. @Metadata(required = true)
  1271. private String category;
  1272.  
  1273. @Override
  1274. public MultiObservation clone() {
  1275. MultiObservation clone = (MultiObservation) super.clone();
  1276. clone.category = category;
  1277. return clone;
  1278. }
  1279.  
  1280. @Override
  1281. public boolean process(Person person, long time) {
  1282. String primaryCode = codes.get(0).code;
  1283. HealthRecord.Observation observation = person.record.multiObservation(time, primaryCode,
  1284. numberOfObservations);
  1285. observation.name = this.name;
  1286. observation.codes.addAll(codes);
  1287. observation.category = category;
  1288.  
  1289. return true;
  1290. }
  1291. }
  1292.  
  1293. /**
  1294. * The DiagnosticReport state indicates that some number of prior Observation states should be
  1295. * grouped together within a single Diagnostic Report. This can be used when multiple observations
  1296. * are part of a single panel. DiagnosticReport states may only be processed during an Encounter,
  1297. * and so must occur after the target Encounter state and before the EncounterEnd. See the
  1298. * Encounter section above for more details.
  1299. */
  1300. public static class DiagnosticReport extends ObservationGroup {
  1301. @Override
  1302. public boolean process(Person person, long time) {
  1303. String primaryCode = codes.get(0).code;
  1304. Report report = person.record.report(time, primaryCode, numberOfObservations);
  1305. report.name = this.name;
  1306. report.codes.addAll(codes);
  1307.  
  1308. return true;
  1309. }
  1310. }
  1311.  
  1312. /**
  1313. * The Symptom state type adds or updates a patient's symptom. Synthea tracks symptoms in order to
  1314. * drive a patient's encounters, on a scale of 1-100. A symptom may be tracked for multiple
  1315. * conditions, in these cases only the highest value is considered. See also the Symptom logical
  1316. * condition type.
  1317. */
  1318. public static class Symptom extends State {
  1319. @Metadata(required = true)
  1320. private String symptom;
  1321. private String cause;
  1322. private Range<Integer> range;
  1323. private Exact<Integer> exact;
  1324.  
  1325. @Override
  1326. protected void initialize(Module module, String name, JsonObject definition) {
  1327. super.initialize(module, name, definition);
  1328. if (cause == null) {
  1329. cause = module.name;
  1330. }
  1331. }
  1332.  
  1333. @Override
  1334. public Symptom clone() {
  1335. Symptom clone = (Symptom) super.clone();
  1336. clone.symptom = symptom;
  1337. clone.cause = cause;
  1338. clone.range = range;
  1339. clone.exact = exact;
  1340. return clone;
  1341. }
  1342.  
  1343. @Override
  1344. public boolean process(Person person, long time) {
  1345. if (exact != null) {
  1346. person.setSymptom(cause, symptom, exact.quantity);
  1347. } else if (range != null) {
  1348. person.setSymptom(cause, symptom, (int) person.rand(range.low, range.high));
  1349. } else {
  1350. person.setSymptom(cause, symptom, 0);
  1351. }
  1352. return true;
  1353. }
  1354. }
  1355.  
  1356. /**
  1357. * The Death state type indicates a point in the module at which the patient dies or the patient
  1358. * is given a terminal diagnosis (e.g. "you have 3 months to live"). When the Death state is
  1359. * processed, the patient's death is immediately recorded (the alive? method will return false)
  1360. * unless range or exact attributes are specified, in which case the patient will die sometime in
  1361. * the future. In either case the module will continue to progress to the next state(s) for the
  1362. * current time-step. Typically, the Death state should transition to a Terminal state.
  1363. *
  1364. * <p>The Cause of Death listed on a Death Certificate can be specified in three ways:
  1365. * By `codes[]`, specifying the system, code, and display name of the condition causing death.
  1366. * By `condition_onset`, specifying the name of the ConditionOnset state in which the condition
  1367. * causing death was onset. By `referenced_by_attribute`, specifying the name of the attribute to
  1368. * which a previous ConditionOnset state assigned a condition that caused death.
  1369. *
  1370. * <p>Implementation Warning If a Death state is processed after a Delay, it may cause
  1371. * inconsistencies in the record. This is because the Delay implementation must rewind time to
  1372. * correctly honor the requested delay duration. If it rewinds time, and then the patient dies at
  1373. * the rewinded time, then any modules that were processed before the generic module may have
  1374. * created events and records with a timestamp after the patient's death.
  1375. */
  1376. public static class Death extends State {
  1377. @Metadata(min = 1)
  1378. private List<Code> codes;
  1379. @Metadata(referenceToStateType = ConditionOnset.class)
  1380. private String conditionOnset;
  1381. private String referencedByAttribute;
  1382. private RangeWithUnit<Integer> range;
  1383. private ExactWithUnit<Integer> exact;
  1384.  
  1385. @Override
  1386. public Death clone() {
  1387. Death clone = (Death) super.clone();
  1388. clone.codes = codes;
  1389. clone.conditionOnset = conditionOnset;
  1390. clone.referencedByAttribute = referencedByAttribute;
  1391. clone.range = range;
  1392. clone.exact = exact;
  1393. return clone;
  1394. }
  1395.  
  1396. @Override
  1397. public boolean process(Person person, long time) {
  1398. Code reason = null;
  1399. if (codes != null) {
  1400. reason = codes.get(0);
  1401. } else if (conditionOnset != null) {
  1402. if (person.hadPriorState(conditionOnset)) {
  1403. // loop through the present conditions, the condition "name" will match
  1404. // the name of the ConditionOnset state (aka "reason")
  1405. for (Entry entry : person.record.present.values()) {
  1406. if (entry.name != null && entry.name.equals(conditionOnset)) {
  1407. reason = entry.codes.get(0);
  1408. }
  1409. }
  1410. }
  1411. } else if (referencedByAttribute != null) {
  1412. Entry entry = (Entry) person.attributes.get(referencedByAttribute);
  1413. if (entry == null) {
  1414. // TODO - condition referenced but not yet diagnosed
  1415. throw new RuntimeException("Attribute '" + referencedByAttribute
  1416. + "' was referenced by state '" + name + "' but not set");
  1417. }
  1418. reason = entry.codes.get(0);
  1419. }
  1420. String rule = String.format("%s %s", module, name);
  1421. if (reason != null) {
  1422. rule = String.format("%s %s", rule, reason.display);
  1423. }
  1424. if (exact != null) {
  1425. long timeOfDeath = time + Utilities.convertTime(exact.unit, exact.quantity);
  1426. person.recordDeath(timeOfDeath, reason, rule);
  1427. return true;
  1428. } else if (range != null) {
  1429. double duration = person.rand(range.low, range.high);
  1430. long timeOfDeath = time + Utilities.convertTime(range.unit, (long) duration);
  1431. person.recordDeath(timeOfDeath, reason, rule);
  1432. return true;
  1433. } else {
  1434. person.recordDeath(time, reason, rule);
  1435. return true;
  1436. }
  1437. }
  1438. }
  1439. }
Add Comment
Please, Sign In to add comment