Guest User

Untitled

a guest
Jan 19th, 2018
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.41 KB | None | 0 0
  1. package org.mitre.synthea.engine;
  2.  
  3. import java.util.Collection;
  4. import java.util.List;
  5.  
  6. import org.mitre.synthea.engine.Components.ExactWithUnit;
  7. import org.mitre.synthea.helpers.Utilities;
  8. import org.mitre.synthea.world.agents.Person;
  9. import org.mitre.synthea.world.concepts.HealthRecord;
  10. import org.mitre.synthea.world.concepts.HealthRecord.CarePlan;
  11. import org.mitre.synthea.world.concepts.HealthRecord.Code;
  12. import org.mitre.synthea.world.concepts.HealthRecord.Entry;
  13. import org.mitre.synthea.world.concepts.HealthRecord.Medication;
  14.  
  15. /**
  16. * Logic represents any portion of a generic module that requires a logical
  17. * expression. This class is stateless, and calling 'test' on an instance
  18. * must not modify state as instances of Logic within Modules are shared
  19. * across the population.
  20. */
  21. public abstract class Logic implements Validation {
  22. public List<String> remarks;
  23.  
  24. /**
  25. * Test whether the logic is true for the given person at the given time.
  26. *
  27. * @param person Person to execute logic against
  28. * @param time Timestamp to execute logic against
  29. * @return boolean - whether or not the given condition is true or not
  30. */
  31. public abstract boolean test(Person person, long time);
  32.  
  33. /**
  34. * The Gender condition type tests the patient's gender. (M or F)
  35. */
  36. public static class Gender extends Logic {
  37. @Metadata(required = true, validValues = {"M", "F"})
  38. private String gender;
  39.  
  40. @Override
  41. public boolean test(Person person, long time) {
  42. return gender.equals(person.attributes.get(Person.GENDER));
  43. }
  44. }
  45.  
  46. /**
  47. * The Age condition type tests the patient's age, in a given unit.
  48. * (Ex, years for adults or months for young children)
  49. */
  50. public static class Age extends Logic {
  51. @Metadata(required = true)
  52. private Double quantity;
  53.  
  54. @Metadata(required = true, validValues = {"years", "months"})
  55. private String unit;
  56.  
  57. @Metadata(required = true, validValues = {"==", "!=", "<", ">", "<=", ">="})
  58. private String operator;
  59.  
  60. @Override
  61. public boolean test(Person person, long time) {
  62. double age;
  63.  
  64. switch (unit) {
  65. case "years":
  66. age = person.ageInYears(time);
  67. break;
  68. case "months":
  69. age = person.ageInMonths(time);
  70. break;
  71. default:
  72. // TODO - add more unit types if we determine they are necessary
  73. throw new UnsupportedOperationException("Units '" + unit
  74. + "' not currently supported in Age logic.");
  75. }
  76.  
  77. return Utilities.compare(age, quantity, operator);
  78. }
  79. }
  80.  
  81. /**
  82. * The Date condition type tests the current year being simulated. For example, this may be used
  83. * to drive different logic depending on the suggested medications or procedures of different time
  84. * periods, or model different frequency of conditions.
  85. */
  86. public static class Date extends Logic {
  87. private int year;
  88. @Metadata(required = true, validValues = {"==", "!=", "<", ">", "<=", ">="})
  89. private String operator;
  90.  
  91. @Override
  92. public boolean test(Person person, long time) {
  93. int current = Utilities.getYear(time);
  94. return Utilities.compare(current, year, operator);
  95. }
  96. }
  97.  
  98. /**
  99. * The Socioeconomic Status condition type tests the patient's socioeconomic status. Socioeconomic
  100. * status is based on income, education, and occupation, and is categorized in Synthea as "High",
  101. * "Middle", or "Low".
  102. */
  103. public static class SocioeconomicStatus extends Logic {
  104. @Metadata(required = true, validValues = {"High", "Middle", "Low"})
  105. private String category;
  106.  
  107. @Override
  108. public boolean test(Person person, long time) {
  109. return category.equals(person.attributes.get(Person.SOCIOECONOMIC_CATEGORY));
  110. }
  111. }
  112.  
  113. /**
  114. * The Race condition type tests a patient's race. Synthea supports the following races:
  115. * "White", "Native" (Native American), "Hispanic", "Black", "Asian", and "Other".
  116. */
  117. public static class Race extends Logic {
  118. @Metadata(required = true,
  119. validValues = {"White", "Native", "Hispanic", "Black", "Asian", "Other"})
  120. private String race;
  121.  
  122. @Override
  123. public boolean test(Person person, long time) {
  124. return race.equals(person.attributes.get(Person.RACE));
  125. }
  126. }
  127.  
  128. /**
  129. * The Symptom condition type tests a patient's current symptoms. Synthea tracks symptoms in order
  130. * to drive a patient's encounters, on a scale of 1-100. A symptom may be tracked for multiple
  131. * conditions, in these cases only the highest value is considered. See also the Symptom State.
  132. */
  133. public static class Symptom extends Logic {
  134. private String symptom;
  135. @Metadata(required = true, validValues = {"==", "!=", "<", ">", "<=", ">="})
  136. private String operator;
  137. private double value;
  138.  
  139. @Override
  140. public boolean test(Person person, long time) {
  141. return Utilities.compare((double) person.getSymptom(symptom), value, operator);
  142. }
  143. }
  144.  
  145. /**
  146. * The Observation condition type tests the most recent observation of a given type against a
  147. * given value.
  148. * Implementation Warnings:
  149. * - Synthea does not support conversion between arbitrary units, so all observations of a given
  150. * type are expected to be made in the same units.
  151. * - The given observation must have been recorded prior to performing this logical check,
  152. * unless the operator is is nil or is not nil. Otherwise, the GMF will raise an exception
  153. * that the observation value cannot be compared as there has been no observation made.
  154. */
  155. public static class Observation extends Logic {
  156. @Metadata(required = true,
  157. validValues = {"==", "!=", "<", ">", "<=", ">=", "is nil", "is not nil"})
  158. private String operator;
  159. private List<Code> codes;
  160. private String referencedByAttribute;
  161. private Double value;
  162.  
  163. @Override
  164. public boolean test(Person person, long time) {
  165. HealthRecord.Observation observation = null;
  166. if (this.codes != null) {
  167. for (Code code : this.codes) {
  168. HealthRecord.Observation last = person.record.getLatestObservation(code.code);
  169. if (last != null) {
  170. observation = last;
  171. break;
  172. }
  173. }
  174. } else if (this.referencedByAttribute != null) {
  175. if (person.attributes.containsKey(this.referencedByAttribute)) {
  176. observation =
  177. (HealthRecord.Observation) person.attributes.get(this.referencedByAttribute);
  178. } else {
  179. return false;
  180. }
  181. }
  182.  
  183. return Utilities.compare(observation.value, this.value, operator);
  184. }
  185. }
  186.  
  187. /**
  188. * The Attribute condition type tests a named attribute on the patient entity.
  189. */
  190. public static class Attribute extends Logic {
  191. @Metadata(required = true)
  192. private String attribute;
  193.  
  194. @Metadata(required = true,
  195. validValues = {"==", "!=", "<", ">", "<=", ">=", "is nil", "is not nil"})
  196. private String operator;
  197.  
  198. private Object value;
  199.  
  200. @Override
  201. public boolean test(Person person, long time) {
  202. if (value instanceof String) {
  203. return value.equals(person.attributes.get(attribute));
  204. } else {
  205. return Utilities.compare(person.attributes.get(attribute), value, operator);
  206. }
  207. }
  208. }
  209.  
  210. /**
  211. * GroupedCondition is the parent class for Logic that aggregates multiple conditions.
  212. * It should never be used directly in a JSON file.
  213. */
  214. private abstract static class GroupedCondition extends Logic {
  215. @Metadata(required = true, min = 1)
  216. protected Collection<Logic> conditions;
  217. }
  218.  
  219. /**
  220. * The And condition type tests that a set of sub-conditions are all true.
  221. * If all sub-conditions are true, it will return true,
  222. * but if any are false, it will return false.
  223. */
  224. public static class And extends GroupedCondition {
  225. @Override
  226. public boolean test(Person person, long time) {
  227. return conditions.stream().allMatch(c -> c.test(person, time));
  228. }
  229. }
  230.  
  231. /**
  232. * The Or condition type tests that at least one of its sub-conditions is true.
  233. * If any sub-condition is true, it will return true,
  234. * but if all sub-conditions are false, it will return false.
  235. */
  236. public static class Or extends GroupedCondition {
  237. @Override
  238. public boolean test(Person person, long time) {
  239. return conditions.stream().anyMatch(c -> c.test(person, time));
  240. }
  241. }
  242.  
  243. /**
  244. * The Not condition type negates its sub-condition.
  245. * If the sub-condition is true, it will return false;
  246. * if the sub-condition is false, it will return true.
  247. */
  248. public static class Not extends Logic {
  249. @Metadata(required = true)
  250. private Logic condition;
  251.  
  252. @Override
  253. public boolean test(Person person, long time) {
  254. return !condition.test(person, time);
  255. }
  256. }
  257.  
  258. /**
  259. * The At Least condition type tests that a minimum number of conditions
  260. * from a set of sub-conditions are true.
  261. * If the minimum number or more sub-conditions are true, it will return true,
  262. * but if less than the minimum are true, it will return false.
  263. * (If the minimum is the same as the number of sub-conditions provided,
  264. * this is equivalent to the And condition.
  265. * If the minimum is 1, this is equivalent to the Or condition.)
  266. */
  267. public static class AtLeast extends GroupedCondition {
  268. @Metadata(required = true)
  269. private Integer minimum;
  270.  
  271. @Override
  272. public boolean test(Person person, long time) {
  273. return conditions.stream().filter(c -> c.test(person, time)).count() >= minimum;
  274. }
  275. }
  276.  
  277. /**
  278. * The At Most condition type tests that a maximum number of conditions
  279. * from a set of sub-conditions are true. If the maximum number or fewer sub-conditions are true,
  280. * it will return true, but if more than the maximum are true, it will return false.
  281. */
  282. public static class AtMost extends GroupedCondition {
  283. @Metadata(required = true)
  284. private Integer maximum;
  285.  
  286. @Override
  287. public boolean test(Person person, long time) {
  288. return conditions.stream().filter(c -> c.test(person, time)).count() <= maximum;
  289. }
  290. }
  291.  
  292.  
  293. /**
  294. * The True condition always returns true.
  295. * This condition is mainly used for testing purposes
  296. * and is not expected to be used in any real module.
  297. */
  298. public static class True extends Logic {
  299. @Override
  300. public boolean test(Person person, long time) {
  301. return true;
  302. }
  303. }
  304.  
  305. /**
  306. * The False condition always returns false.
  307. * This condition is mainly used for testing purposes
  308. * and is not expected to be used in any real module.
  309. */
  310. public static class False extends Logic {
  311. @Override
  312. public boolean test(Person person, long time) {
  313. return false;
  314. }
  315. }
  316.  
  317. /**
  318. * The PriorState condition type tests the progression of the patient through the module, and
  319. * checks if a specific state has already been processed (in other words, the state is in the
  320. * module's state history). The search for the state may be limited by time or the name of another
  321. * state.
  322. */
  323. public static class PriorState extends Logic {
  324. @Metadata(required = true)
  325. private String name;
  326. private String since;
  327. private ExactWithUnit<Long> within;
  328. private Long window;
  329.  
  330. @Override
  331. public boolean test(Person person, long time) {
  332. Long sinceTime = null;
  333.  
  334. if (within != null) {
  335. if (window == null) {
  336. // cache the value since it doesn't depend on person or time
  337. window = Utilities.convertTime(within.unit, within.quantity);
  338. }
  339. sinceTime = time - window;
  340. }
  341.  
  342. return person.hadPriorState(name, since, sinceTime);
  343. }
  344. }
  345.  
  346. /**
  347. * Parent class for logics that look up "active" things.
  348. * This class should never be referenced directly.
  349. */
  350. private abstract static class ActiveLogic extends Logic {
  351. protected List<Code> codes;
  352. protected String referencedByAttribute;
  353. }
  354.  
  355. /**
  356. * The Active Condition condition type tests whether a given condition is currently diagnosed and
  357. * active on the patient.
  358. * Future Implementation Considerations:
  359. * Currently to check if a condition has been added but not diagnosed, it is possible to use the
  360. * PriorState condition to check if the state has been processed. In the future it may be
  361. * preferable to add a distinct "Present Condition" logical condition to clearly specify the
  362. * intent of looking for a present but not diagnosed condition.
  363. */
  364. public static class ActiveCondition extends ActiveLogic {
  365. @Override
  366. public boolean test(Person person, long time) {
  367. if (this.codes != null) {
  368. for (Code code : this.codes) {
  369. if (person.record.present.containsKey(code.code)) {
  370. return true;
  371. }
  372. }
  373. return false;
  374. } else if (referencedByAttribute != null) {
  375. if (person.attributes.containsKey(referencedByAttribute)) {
  376. Entry diagnosis = (Entry) person.attributes.get(referencedByAttribute);
  377. return person.record.present.containsKey(diagnosis.type);
  378. } else {
  379. return false;
  380. }
  381. }
  382.  
  383. throw new RuntimeException("Active Condition logic must be specified by code or attribute");
  384. }
  385. }
  386.  
  387. /**
  388. * The Active Medication condition type tests whether a given medication is currently prescribed
  389. * and active for the patient.
  390. */
  391. public static class ActiveMedication extends ActiveLogic {
  392. @Override
  393. public boolean test(Person person, long time) {
  394. if (this.codes != null) {
  395. for (Code code : this.codes) {
  396. if (person.record.medicationActive(code.code)) {
  397. return true;
  398. }
  399. }
  400. return false;
  401. } else if (this.referencedByAttribute != null) {
  402. if (person.attributes.containsKey(this.referencedByAttribute)) {
  403. Medication medication = (Medication) person.attributes.get(this.referencedByAttribute);
  404. return person.record.medicationActive(medication.type);
  405. } else {
  406. return false;
  407. }
  408. }
  409.  
  410. throw new RuntimeException("Active Medication logic must be specified by code or attribute");
  411. }
  412. }
  413.  
  414. /**
  415. * The Active CarePlan condition type tests whether a given care plan is currently prescribed and
  416. * active for the patient.
  417. */
  418. public static class ActiveCarePlan extends ActiveLogic {
  419. @Override
  420. public boolean test(Person person, long time) {
  421. if (this.codes != null) {
  422. for (Code code : this.codes) {
  423. if (person.record.careplanActive(code.code)) {
  424. return true;
  425. }
  426. }
  427. return false;
  428. } else if (this.referencedByAttribute != null) {
  429. if (person.attributes.containsKey(this.referencedByAttribute)) {
  430. CarePlan carePlan = (CarePlan) person.attributes.get(this.referencedByAttribute);
  431. return person.record.careplanActive(carePlan.type);
  432. } else {
  433. return false;
  434. }
  435. }
  436.  
  437. throw new RuntimeException("Active CarePlan logic must be specified by code or attribute");
  438. }
  439. }
  440.  
  441. /**
  442. * The Vital Sign condition type tests a patient's current vital signs. Synthea tracks vital signs
  443. * in order to drive a patient's physical condition, and are recorded in observations. See also
  444. * the Symptom State.
  445. */
  446. public static class VitalSign extends Logic {
  447. @Metadata(required = true)
  448. private org.mitre.synthea.world.concepts.VitalSign vitalSign;
  449.  
  450. @Metadata(required = true, validValues = {"==", "!=", "<", ">", "<=", ">="})
  451. private String operator;
  452.  
  453. private double value;
  454.  
  455. @Override
  456. public boolean test(Person person, long time) {
  457. return Utilities.compare(person.getVitalSign(vitalSign), value, operator);
  458. }
  459. }
  460. }
Add Comment
Please, Sign In to add comment