conceptql 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +498 -0
  3. data/config/vocabularies.csv +85 -87
  4. data/config/vocabulary_formats.sha +1 -0
  5. data/config/vocabulary_formats.tsv +39 -0
  6. data/doc/spec.md +2091 -0
  7. data/lib/conceptql/behaviors/metadatable.rb +15 -3
  8. data/lib/conceptql/behaviors/provenanceable.rb +82 -8
  9. data/lib/conceptql/behaviors/utilizable.rb +5 -3
  10. data/lib/conceptql/cli.rb +1 -1
  11. data/lib/conceptql/data_model/base.rb +3 -171
  12. data/lib/conceptql/data_model/gdm.rb +3 -1
  13. data/lib/conceptql/data_model.rb +20 -4
  14. data/lib/conceptql/database.rb +9 -2
  15. data/lib/conceptql/date_adjuster.rb +12 -5
  16. data/lib/conceptql/lexicon/lexicon_gdm.rb +4 -1
  17. data/lib/conceptql/lexicon/lexicon_ohdsi.rb +19 -1
  18. data/lib/conceptql/lexicon/lexicon_strategy.rb +58 -4
  19. data/lib/conceptql/lexicon.rb +218 -2
  20. data/lib/conceptql/nodifier.rb +7 -1
  21. data/lib/conceptql/one_query_counts.rb +120 -0
  22. data/lib/conceptql/operators/co_reported.rb +4 -31
  23. data/lib/conceptql/operators/count.rb +4 -0
  24. data/lib/conceptql/operators/death.rb +1 -1
  25. data/lib/conceptql/operators/dedup.rb +1 -1
  26. data/lib/conceptql/operators/episode.rb +10 -2
  27. data/lib/conceptql/operators/ethnicity.rb +0 -4
  28. data/lib/conceptql/operators/except.rb +5 -0
  29. data/lib/conceptql/operators/filter.rb +12 -2
  30. data/lib/conceptql/operators/hospice.rb +2 -3
  31. data/lib/conceptql/operators/hospitalization.rb +2 -3
  32. data/lib/conceptql/operators/information_periods.rb +5 -4
  33. data/lib/conceptql/operators/invalid.rb +7 -1
  34. data/lib/conceptql/operators/multiple_vocabularies.rb +19 -6
  35. data/lib/conceptql/operators/numeric.rb +51 -14
  36. data/lib/conceptql/operators/occurrence.rb +16 -4
  37. data/lib/conceptql/operators/one_in_two_out.rb +1 -2
  38. data/lib/conceptql/operators/operator.rb +66 -30
  39. data/lib/conceptql/operators/person.rb +1 -1
  40. data/lib/conceptql/operators/person_filter.rb +5 -0
  41. data/lib/conceptql/operators/place_of_service_filter.rb +38 -10
  42. data/lib/conceptql/operators/provenance.rb +13 -4
  43. data/lib/conceptql/operators/provider_filter.rb +5 -1
  44. data/lib/conceptql/operators/race.rb +2 -10
  45. data/lib/conceptql/operators/read.rb +1 -100
  46. data/lib/conceptql/operators/recall.rb +1 -1
  47. data/lib/conceptql/operators/snf.rb +3 -4
  48. data/lib/conceptql/operators/sum.rb +46 -5
  49. data/lib/conceptql/operators/temporal_operator.rb +6 -2
  50. data/lib/conceptql/operators/trim_date.rb +52 -6
  51. data/lib/conceptql/operators/trim_date_end.rb +11 -1
  52. data/lib/conceptql/operators/trim_date_start.rb +11 -1
  53. data/lib/conceptql/operators/union.rb +45 -1
  54. data/lib/conceptql/operators/vocabulary.rb +21 -4
  55. data/lib/conceptql/paths.rb +16 -0
  56. data/lib/conceptql/query.rb +78 -24
  57. data/lib/conceptql/query_modifiers/gdm/provider_query_modifier.rb +7 -7
  58. data/lib/conceptql/rdbms/generic.rb +21 -1
  59. data/lib/conceptql/rdbms/spark.rb +12 -0
  60. data/lib/conceptql/scope.rb +187 -24
  61. data/lib/conceptql/version.rb +1 -1
  62. data/lib/conceptql/vocabularies/drg_systems.rb +118 -0
  63. data/lib/conceptql/vocabularies/dynamic_vocabularies.rb +10 -4
  64. data/lib/conceptql/vocabularies/entry.rb +15 -23
  65. data/lib/conceptql/vocabularies/formats.rb +59 -0
  66. data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +8 -16
  67. data/lib/conceptql/window/table.rb +12 -3
  68. metadata +9 -14
  69. data/lib/conceptql/data_model/omopv4_plus.rb +0 -12
  70. data/lib/conceptql/operators/concurrent_within.rb +0 -69
  71. data/lib/conceptql/query_modifiers/omopv4_plus/admission_date_query_modifier.rb +0 -45
  72. data/lib/conceptql/query_modifiers/omopv4_plus/drug_query_modifier.rb +0 -80
  73. data/lib/conceptql/query_modifiers/omopv4_plus/pos_query_modifier.rb +0 -47
  74. data/lib/conceptql/query_modifiers/omopv4_plus/provenance_query_modifier.rb +0 -45
  75. data/lib/conceptql/query_modifiers/omopv4_plus/provider_query_modifier.rb +0 -26
  76. data/lib/conceptql/vocabularies/behaviors/costish.rb +0 -24
  77. data/lib/conceptql/vocabularies/behaviors/omopish.rb +0 -32
  78. data/schemas/omopv4_plus.yml +0 -729
  79. data/schemas/omopv4_plus_more_ignorables.tsv +0 -13
data/doc/spec.md ADDED
@@ -0,0 +1,2091 @@
1
+ # ConceptQL Specification
2
+
3
+ ConceptQL (pronounced concept-Q-L) is a high-level language that allows researchers to unambiguously define their research algorithms.
4
+
5
+ ## Motivation for ConceptQL
6
+
7
+ Outcomes Insights intends to build a vast library of research algorithms and apply those algorithms to large databases of claims data. Early into building the library, we realized we had to overcome two major issues:
8
+
9
+ 1. Methods sections of research papers commonly use natural language to specify the criteria used to build cohorts from a claims database.
10
+ - Algorithms defined in natural language are often imprecise, open to multiple interpretations, and generally difficult to reproduce.
11
+ - Researchers could benefit from a language that removes the ambiguity of natural language while increasing the reproducibility of their research algorithms.
12
+ 2. Querying against claims databases is often difficult.
13
+ - Hand-coding algorithms to extract cohorts from datasets is time-consuming, error-prone, and opaque.
14
+ - Researchers could benefit from a language that allows algorithms to be defined at a high-level and then gets translated into the appropriate queries against a database.
15
+
16
+ We developed ConceptQL to address these two issues.
17
+
18
+ We are writing a tool that can read research algorithms defined in ConceptQL. The tool can create a diagram for the algorithm which makes it easy to visualize and understand. The tool can also translate the algorithm into a SQL query which runs against data structured in the Generalized Data Model (GDM). GDM standardizes the format of observational healthcare data, such as claims, so that the same algorithm can run against many data sources.
19
+
20
+ GDM keeps the data in a small number of tables:
21
+
22
+ - patients
23
+ - One row for each patient, with the date of birth, gender, race, and ethnicity
24
+ - clinical_codes
25
+ - One row for each code that a source record reports for a patient, such as a diagnosis, a procedure, a drug, or a lab test, with a start date and an end date
26
+ - contexts
27
+ - One row for each source record, such as a claim or a claim line
28
+ - Each clinical code points at the context that reported it
29
+ - collections
30
+ - One row for each group of contexts, such as a hospital stay
31
+ - information_periods
32
+ - The date ranges for which the data source has information about a patient
33
+ - deaths
34
+ - One row for each recorded death
35
+
36
+ ConceptQL supports two variants of GDM. The data model `gdm` reads clinical codes from clinical_codes. The data model `gdm_wide` reads them from observations, a wide table that holds the same rows as clinical_codes with the columns of their contexts and collections joined on. ConceptQL looks up codes, such as ICD-9 412, in a vocabulary database to find their concept IDs. When the data database is a real database, and neither its search path nor the database at LEXICON_URL has the vocabulary tables, ConceptQL raises an error instead of running, because without a vocabulary every code would match no rows.
37
+
38
+ For instance, using ConceptQL we can take a statement that looks like this:
39
+
40
+ ```YAML
41
+ - icd9
42
+ - '412'
43
+ ```
44
+
45
+ And generate a diagram that looks like this:
46
+
47
+ ```YAML
48
+ ---
49
+ - icd9
50
+ - '412'
51
+
52
+ ```
53
+
54
+ ![](spec/f6b4fc31703cfb6327bbbd4614af8bb72da6d39fa3d53ada63a70157f2fad80e.png)
55
+
56
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
57
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
58
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
59
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
60
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
61
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
62
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
63
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
64
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
65
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
66
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
67
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
68
+
69
+ And generate SQL that looks like this (simplified from the SQL that ConceptQL generates for `gdm_wide`, where 44820864 is the concept ID of ICD-9 412):
70
+
71
+ ```SQL
72
+ SELECT
73
+ "patient_id" AS "person_id",
74
+ "id" AS "criterion_id",
75
+ 'observations' AS "criterion_table",
76
+ 'condition_occurrence' AS "criterion_domain",
77
+ "start_date",
78
+ "end_date",
79
+ "clinical_code_source_value" AS "source_value"
80
+ FROM "observations"
81
+ WHERE "clinical_code_vocabulary_id" = 'ICD9CM'
82
+ AND "clinical_code_concept_id" IN (44820864)
83
+ ```
84
+
85
+ As stated above, one of the goals of ConceptQL is to make it easy to assemble fairly complex queries without having to roll up our sleeves and write raw SQL. To accommodate this complexity, ConceptQL itself has some complexities of its own. That said, we believe ConceptQL will help researchers define, hone, and share their research algorithms.
86
+
87
+ ## ConceptQL Overview
88
+
89
+ ### What ConceptQL Looks Like
90
+
91
+ I find seeing examples to be the quickest way to get a sense of a language. Here is a trivial example to whet your appetite. The example is in YAML, but could just as easily be in JSON or any other markup language capable of representing nested sets of heterogeneous arrays and hashes. In fact, the ConceptQL "language" is a just set of nested hashes and arrays representing search criteria and some set operations and temporal operations to glue those criteria together.
92
+
93
+ ```YAML
94
+ # Example 1: A simple example in YAML
95
+ # This is just a simple array: the name of an operator, icd9, followed by its argument, 412
96
+ # This example will search the clinical codes for all conditions that match the ICD-9 code 412.
97
+ ---
98
+ - icd9
99
+ - '412'
100
+ ```
101
+
102
+ ### ConceptQL Diagrams
103
+
104
+ Reading ConceptQL in YAML or JSON seems hard to me. I prefer to explore ConceptQL using directed graphs. For instance, the diagram for the simple example listed in YAML above is:
105
+
106
+ ```YAML
107
+ ---
108
+ - icd9
109
+ - '412'
110
+
111
+ ```
112
+
113
+ ![All Conditions Matching MI](spec/f6b4fc31703cfb6327bbbd4614af8bb72da6d39fa3d53ada63a70157f2fad80e.png)
114
+
115
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
116
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
117
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
118
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
119
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
120
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
121
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
122
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
123
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
124
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
125
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
126
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
127
+
128
+ Each oval depicts a "operator", or rather, a ConceptQL expression. An arrow between a pair of operators indicates that the results from the operator on the tail of the arrow pass on to the operator at the head of the arrow. A simple example should help here:
129
+
130
+ ```YAML
131
+ ---
132
+ - first
133
+ - - cpt
134
+ - '99214'
135
+
136
+ ```
137
+
138
+ ![First Office Visit Per Patient](spec/39d6a8eb71cae51b1d6937c97134e51f04fd47c54535ff0915fe6a8b4f197fb2.png)
139
+
140
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
141
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
142
+ | 2 | 58127 | observations | procedure_occurrence | 2009-07-03 | 2009-07-03 | 99214 |
143
+ | 5 | 38313 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99214 |
144
+ | 6 | 48992 | observations | procedure_occurrence | 2009-09-12 | 2009-09-12 | 99214 |
145
+ | 7 | 63797 | observations | procedure_occurrence | 2008-08-13 | 2008-08-13 | 99214 |
146
+ | 8 | 55871 | observations | procedure_occurrence | 2008-02-27 | 2008-02-27 | 99214 |
147
+ | 9 | 45498 | observations | procedure_occurrence | 2009-09-11 | 2009-09-11 | 99214 |
148
+ | 10 | 48752 | observations | procedure_occurrence | 2009-05-09 | 2009-05-09 | 99214 |
149
+ | 11 | 73107 | observations | procedure_occurrence | 2008-06-17 | 2008-06-17 | 99214 |
150
+ | 12 | 71635 | observations | procedure_occurrence | 2008-02-11 | 2008-02-11 | 99214 |
151
+ | 13 | 74458 | observations | procedure_occurrence | 2008-01-24 | 2008-01-24 | 99214 |
152
+
153
+ The diagram above reads "get all procedures that match the CPT 99214 (Office Visit) and then filter them down to the first occurrence for each person". The diagram is much more terse than that and to accurately read the diagram, you need a lot of implicit knowledge about how each operator operates. Fortunately, this document will (hopefully) impart that knowledge to you.
154
+
155
+ Please note that all of my diagrams end with an arrow pointing at nothing. You'll see why soon.
156
+
157
+ ### Think of Results as a Stream
158
+
159
+ I draw my ConceptQL diagrams with leaf operators at the top and the "trunk" operators at the bottom. I like to think of the results of a ConceptQL statement as a flowing stream of data. The leaf operators, or operators that gather results out of the database, act like tributaries. The results flow downwards and either join with other results, or filter out other results until the streams emerge at the bottom of the diagram. Think of each arrow as a stream of results, flowing down through one operator to the next.
160
+
161
+ The trailing arrow in the diagrams serves as a reminder that ConceptQL yields a stream of results.
162
+
163
+ ### Streams have Types
164
+
165
+ You might have noticed that the operators and edges in the diagrams often have a color. That color represents what "type" of stream the operator or edge represents. A stream's type is the domain of its results. Each result reports its type in the criterion_domain column. These are the most common types, with their colors in the diagrams:
166
+
167
+ - condition_occurrence
168
+ - red
169
+ - death
170
+ - brown
171
+ - drug_exposure
172
+ - purple
173
+ - observation
174
+ - magenta
175
+ - observation_period
176
+ - gray
177
+ - person
178
+ - blue
179
+ - procedure_occurrence
180
+ - green
181
+
182
+ A stream that carries more than one type is black.
183
+
184
+ Each stream has a point of origin: the selection operator that pulled its results out of the database. Based on that origin, each stream will have a particular type. The type does not always match the table. Most results come from the clinical codes table, and a vocabulary operator gives its results the domain of its vocabulary. For example, `icd9` yields condition_occurrence results and `cpt` yields procedure_occurrence results, but both read the same table. Results from patients are person results, results from deaths are death results, and results from information_periods are observation_period results. The stream carries this type information as it moves through each operator. When certain operators, particularly set and temporal operators, need to perform filtering, they can use this type information to determine how to best filter a stream. There will be much more discussion about types woven throughout this document. For now, it is sufficient to know that each stream has a type.
185
+
186
+ You'll also notice that the trailing arrow(s) at the end of the diagrams indicate which types of streams are ultimately passed on at the end of a ConceptQL statement.
187
+
188
+ ### What *are* Streams Really?
189
+
190
+ Though I think that a "stream" is a helpful abstraction when thinking in ConceptQL, on a few occasions we need to know what's going on under the hood.
191
+
192
+ Every table in GDM has a surrogate key column, id. When we execute a ConceptQL statement, the "streams" that are generated by the statement are just sets of these IDs for rows that matched the ConceptQL criteria. Each result carries the ID of its row in the criterion_id column and the name of its table of origin in the criterion_table column. So each stream is just a set of IDs that point back to some rows in the GDM tables. The "type" of a stream is the criterion_domain of its results.
193
+
194
+ So when we execute this ConceptQL statement, the resulting "stream" is all the patient IDs for all male patients in the database:
195
+
196
+ ```YAML
197
+ ---
198
+ - gender
199
+ - Male
200
+
201
+ ```
202
+
203
+ ![All Male Patients](spec/c82077b9455d0f9abc2c45ee1a298e38b99c9ce9cd685f65d87b376d7718d7ad.png)
204
+
205
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
206
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
207
+ | 1 | 1 | patients | person | 1923-05-01 | 1923-05-01 | 00013D2EFD8E45D1 |
208
+ | 2 | 2 | patients | person | 1943-01-01 | 1943-01-01 | 00016F745862898F |
209
+ | 4 | 4 | patients | person | 1941-06-01 | 1941-06-01 | 00021CA6FF03E670 |
210
+ | 5 | 5 | patients | person | 1936-08-01 | 1936-08-01 | 00024B3D2352D2D0 |
211
+ | 6 | 6 | patients | person | 1943-10-01 | 1943-10-01 | 0002DAE1C81CC70D |
212
+ | 7 | 7 | patients | person | 1922-07-01 | 1922-07-01 | 0002F28CE057345B |
213
+ | 8 | 8 | patients | person | 1935-09-01 | 1935-09-01 | 000308435E3E5B76 |
214
+ | 12 | 12 | patients | person | 1929-06-01 | 1929-06-01 | 00048EF1F4791C68 |
215
+ | 14 | 14 | patients | person | 1934-05-01 | 1934-05-01 | 00052705243EA128 |
216
+ | 16 | 16 | patients | person | 1934-01-01 | 1934-01-01 | 0007E57CC13CE880 |
217
+
218
+ When we execute this ConceptQL statement, the resulting "stream" is the IDs of all clinical codes that match ICD-9 486 (pneumonia), with the type condition_occurrence:
219
+
220
+ ```YAML
221
+ ---
222
+ - icd9
223
+ - '486'
224
+
225
+ ```
226
+
227
+ ![All Condition Occurrences that match ICD-9 486](spec/346fab7e13a927b7e8b42795ef1a9da5af9ab9140d3415a7a9dfeab4af44c1d9.png)
228
+
229
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
230
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
231
+ | 14 | 1727 | observations | condition_occurrence | 2008-09-12 | 2008-09-12 | 486 |
232
+ | 14 | 38866 | observations | condition_occurrence | 2009-04-05 | 2009-04-05 | 486 |
233
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
234
+ | 19 | 57559 | observations | condition_occurrence | 2008-02-02 | 2008-02-03 | 486 |
235
+ | 23 | 28373 | observations | condition_occurrence | 2009-08-18 | 2009-08-18 | 486 |
236
+ | 23 | 40349 | observations | condition_occurrence | 2009-12-20 | 2009-12-20 | 486 |
237
+ | 23 | 35166 | observations | condition_occurrence | 2010-02-17 | 2010-02-17 | 486 |
238
+ | 25 | 79304 | observations | condition_occurrence | 2009-07-08 | 2009-07-08 | 486 |
239
+ | 27 | 86462 | observations | condition_occurrence | 2010-09-30 | 2010-09-30 | 486 |
240
+ | 28 | 42706 | observations | condition_occurrence | 2009-09-12 | 2009-09-12 | 486 |
241
+
242
+ Generally, I find it helpful to just think of those queries generating a "stream of people" or a "stream of conditions" and not worry about the table of origin or the fact that they are just IDs.
243
+
244
+ When a ConceptQL statement is executed, it yields a final set of streams that are just all the IDs that passed through all the criteria. What is done with that set of IDs is up to the user who assembled the ConceptQL statement. If a user gathers all 486 Conditions, they will end up with a set of clinical code IDs. They could take those IDs and do all sorts of things like:
245
+
246
+ - Gather the first and last date of occurrence per person
247
+ - Count the number of occurrences per person
248
+ - Count number of persons with the condition
249
+ - Count the total number of occurrences for the entire population
250
+
251
+ This kind of aggregation and analysis is beyond the scope of ConceptQL. ConceptQL will get you the IDs of the rows you're interested in, its up to other parts of the calling system to determine what you do with them.
252
+
253
+ ## Selection Operators
254
+
255
+ Selection operators are the parts of a ConceptQL query that search for specific values within the GDM data, e.g. searching the clinical codes for a diagnosis of an old myocardial infarction (ICD-9 412) is a selection. Selection operators are always leaf operators.
256
+
257
+ There are _many_ selection operators. A list of currently implemented operators is available in Appendix A.
258
+
259
+ ### DRG Codes
260
+
261
+ CMS renumbered the Diagnosis-Related Group (DRG) codes on 2007-10-01, when the MS-DRG system replaced the CMS-DRG system. Most codes got a new meaning: DRG 205 meant a liver disorder in CMS-DRG and means other respiratory system diagnoses in MS-DRG. One vocabulary, DRG, holds the codes of both systems, and a clinical code does not record which meaning applied. So a statement names the DRG system, and ConceptQL selects by the code and by the start_date of the clinical code:
262
+
263
+ - `ms_drg` selects the DRG codes whose start_date is on or after 2007-10-01.
264
+ - `cms_drg` selects the DRG codes whose start_date is before 2007-10-01.
265
+
266
+ ```YAML
267
+ ---
268
+ - ms_drg
269
+ - '205'
270
+
271
+ ```
272
+
273
+ ![All MS-DRG 205 events: other respiratory system diagnoses with a major complication or comorbidity](spec/2f8919588f782c227a2d45b6579a2bf51059bf3a38e5a5524b3edb06e0be9900.png)
274
+
275
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
276
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
277
+ | 17 | 1365 | observations | procedure_occurrence | 2010-06-02 | 2010-06-06 | 205 |
278
+ | 68 | 18 | observations | procedure_occurrence | 2008-04-19 | 2008-04-23 | 205 |
279
+ | 95 | 603 | observations | procedure_occurrence | 2008-08-27 | 2008-09-01 | 205 |
280
+
281
+ ```YAML
282
+ ---
283
+ - cms_drg
284
+ - '205'
285
+
286
+ ```
287
+
288
+ ![All CMS-DRG 205 events (a liver disorder): none, because the test data starts in December 2007](spec/ba330009cc3750e3d781ff6f4a52b1a4e7c6e440b1f950b8e43419f10b1e56dc.png)
289
+
290
+ `No Results found.`
291
+
292
+ Both operators check codes as other vocabulary operators do: a DRG code has 3 digits, and a code that the DRG vocabulary does not have gets a warning. When the vocabulary records the system of each meaning (the OHDSI concept class MS-DRG or DRG), the code list of a statement names each code by its meaning in the operator's system. Otherwise it names the code by all of its meanings.
293
+
294
+ The `drg` operator is deprecated. It selects the DRG codes of both systems, whatever the date. It still runs, but annotation shows a warning on it, and a later release will make it an error.
295
+
296
+ ## All Other Operators i.e. Mutation Operators
297
+
298
+ Virtually all other operators add, remove, filter, or otherwise alter streams of results. They are discussed in this section.
299
+
300
+ ## Set Operators
301
+
302
+ Because streams represent sets of results, its makes sense to include a operators that operate on sets. ConceptQL has three of them: union, filter, and except. It has no intersect operator and no complement operator.
303
+
304
+ ### Union
305
+
306
+ - Takes any number of upstream operators and aggregates their streams
307
+ - Unions together streams with identical types
308
+ - Think of streams with the same type flowing together into a single stream
309
+ - Union removes duplicates: a result that arrives from more than one upstream operator appears once
310
+ - Every stream is a set, so no operator passes along two identical results
311
+ - Two results are identical when they have the same value in every column, label included
312
+ - Streams with the different types flow along together concurrently without interacting
313
+ - It does not make sense to union, say, condition_occurrence results with procedure_occurrence results, so streams with different types won't mingle together, but will continue to flow downstream in parallel
314
+
315
+ ```YAML
316
+ ---
317
+ - union
318
+ - - icd9
319
+ - '412'
320
+ - - icd9
321
+ - '486'
322
+
323
+ ```
324
+
325
+ ![Two streams of the same type (condition_occurrence) joined into a single stream](spec/79939bbb392a2152f20243f018abe44b681393ea0412e06b282dcaa4647476e1.png)
326
+
327
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
328
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
329
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
330
+ | 14 | 1727 | observations | condition_occurrence | 2008-09-12 | 2008-09-12 | 486 |
331
+ | 14 | 38866 | observations | condition_occurrence | 2009-04-05 | 2009-04-05 | 486 |
332
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
333
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
334
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
335
+ | 19 | 57559 | observations | condition_occurrence | 2008-02-02 | 2008-02-03 | 486 |
336
+ | 23 | 28373 | observations | condition_occurrence | 2009-08-18 | 2009-08-18 | 486 |
337
+ | 23 | 40349 | observations | condition_occurrence | 2009-12-20 | 2009-12-20 | 486 |
338
+ | 23 | 35166 | observations | condition_occurrence | 2010-02-17 | 2010-02-17 | 486 |
339
+
340
+ ```YAML
341
+ ---
342
+ - union
343
+ - - union
344
+ - - icd9
345
+ - '412'
346
+ - - icd9
347
+ - '486'
348
+ - - cpt
349
+ - '99231'
350
+
351
+ ```
352
+
353
+ ![Two streams of the same type (condition_occurrence) joined into a single stream, then a different stream (procedure_occurrence) flows concurrently](spec/4e59dcff780ca34a7e99631e5ad063c11dc7bfe19b14050523cb4f58387b5b81.png)
354
+
355
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
356
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
357
+ | 2 | 92691 | observations | procedure_occurrence | 2008-10-20 | 2008-10-20 | 99231 |
358
+ | 7 | 72498 | observations | procedure_occurrence | 2009-12-24 | 2010-01-02 | 99231 |
359
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
360
+ | 13 | 65589 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99231 |
361
+ | 14 | 1727 | observations | condition_occurrence | 2008-09-12 | 2008-09-12 | 486 |
362
+ | 14 | 38866 | observations | condition_occurrence | 2009-04-05 | 2009-04-05 | 486 |
363
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
364
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
365
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
366
+ | 17 | 53417 | observations | procedure_occurrence | 2008-04-05 | 2008-04-05 | 99231 |
367
+
368
+ ```YAML
369
+ ---
370
+ - union
371
+ - - icd9
372
+ - '412'
373
+ - - icd9
374
+ - '486'
375
+ - - cpt
376
+ - '99231'
377
+
378
+ ```
379
+
380
+ ![Two streams of the same type (condition_occurrence) joined into a single stream, along with a different stream (procedure_occurrence) flows concurrently (same as above example)](spec/5f87dd446147760f348e0e72e1abb72b8be1966e147951cff87e846b87f2d7f7.png)
381
+
382
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
383
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
384
+ | 2 | 92691 | observations | procedure_occurrence | 2008-10-20 | 2008-10-20 | 99231 |
385
+ | 7 | 72498 | observations | procedure_occurrence | 2009-12-24 | 2010-01-02 | 99231 |
386
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
387
+ | 13 | 65589 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99231 |
388
+ | 14 | 1727 | observations | condition_occurrence | 2008-09-12 | 2008-09-12 | 486 |
389
+ | 14 | 38866 | observations | condition_occurrence | 2009-04-05 | 2009-04-05 | 486 |
390
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
391
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
392
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
393
+ | 17 | 53417 | observations | procedure_occurrence | 2008-04-05 | 2008-04-05 | 99231 |
394
+
395
+ ```YAML
396
+ ---
397
+ - union
398
+ - - first
399
+ - - icd9
400
+ - '412'
401
+ - - icd9
402
+ - '412'
403
+
404
+ ```
405
+
406
+ ![The first MI of each patient is also one of the patient's MIs, so it appears once: the union holds the same results as icd9 412 alone](spec/4e6081d437226f76213819b0626b91ce3ba4e340da55c7366bf61ffa276a9b2c.png)
407
+
408
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
409
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
410
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
411
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
412
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
413
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
414
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
415
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
416
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
417
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
418
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
419
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
420
+
421
+ ### Filter
422
+
423
+ This operator takes two sets of incoming streams, a left-hand stream and a right-hand stream. It passes along each left-hand result that also appears in the right-hand stream. Two results are the same when they have the same person_id, criterion_id, criterion_table, criterion_domain, start_date, and end_date. A criterion_id identifies a result only within its criterion_table, so results from two tables never match, even when their ids are equal. The operator discards all results in the right-hand stream. Use filter to keep only the results that two streams have in common, as an intersection would.
424
+
425
+ ```YAML
426
+ ---
427
+ - filter
428
+ - :left:
429
+ - provenance
430
+ - - icd9
431
+ - '412'
432
+ - inpatient
433
+ :right:
434
+ - during
435
+ - :left:
436
+ - icd9
437
+ - '412'
438
+ :right:
439
+ - date_range
440
+ - :start: '2009-01-01'
441
+ :end: '2009-12-31'
442
+
443
+ ```
444
+
445
+ ![All MI Conditions from inpatient records that also occurred in 2009](spec/5180287351cc692b438dc0b13d0fc5d7ef068c716429ecea6a81f18443fe63e0.png)
446
+
447
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
448
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
449
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
450
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
451
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
452
+ | 161 | 963 | observations | condition_occurrence | 2009-10-25 | 2009-10-29 | 412 |
453
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
454
+
455
+ The `provenance` operator in this example passes along the results that come from a given kind of source record, here inpatient records.
456
+
457
+ ### Except
458
+
459
+ This operator takes two sets of incoming streams, a left-hand stream and a right-hand stream. The operator removes any results in the left-hand stream if they appear in the right-hand stream. Two results are the same when they have the same criterion_id, criterion_table, start_date, and end_date. The operator passes only results for the left-hand stream downstream. The operator discards all results in the right-hand stream. For example:
460
+
461
+ ```YAML
462
+ ---
463
+ - except
464
+ - :left:
465
+ - icd9
466
+ - '412'
467
+ :right:
468
+ - provenance
469
+ - - icd9
470
+ - '412'
471
+ - inpatient
472
+
473
+ ```
474
+
475
+ ![All Conditions that are MI unless they come from inpatient records](spec/4daefcd16621b00dc32e8e9e14bca04025f2e37539b319b22175bb8d80452a8a.png)
476
+
477
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
478
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
479
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
480
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
481
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
482
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
483
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
484
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
485
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
486
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
487
+ | 88 | 73803 | observations | condition_occurrence | 2009-05-30 | 2009-05-30 | 412 |
488
+ | 90 | 33940 | observations | condition_occurrence | 2010-05-26 | 2010-05-26 | 412 |
489
+
490
+ A result in one stream cannot match a result of a different type, so if the left-hand stream has no types that match the right-hand stream, the left-hand stream passes through unaffected:
491
+
492
+ ```YAML
493
+ ---
494
+ - except
495
+ - :left:
496
+ - icd9
497
+ - '412'
498
+ :right:
499
+ - cpt
500
+ - '99214'
501
+
502
+ ```
503
+
504
+ ![All Conditions that are MI](spec/253845fe6162621af407ebd110296ff4f6d8a3f23ec75dfb4ea8cda30be71262.png)
505
+
506
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
507
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
508
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
509
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
510
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
511
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
512
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
513
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
514
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
515
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
516
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
517
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
518
+
519
+ And just to show how multiple streams behave:
520
+
521
+ ```YAML
522
+ ---
523
+ - except
524
+ - :left:
525
+ - union
526
+ - - icd9
527
+ - '412'
528
+ - - gender
529
+ - Male
530
+ - - cpt
531
+ - '99214'
532
+ :right:
533
+ - union
534
+ - - provenance
535
+ - - icd9
536
+ - '412'
537
+ - inpatient
538
+ - - race
539
+ - White
540
+
541
+ ```
542
+
543
+ ![Passes three streams downstream: a stream of Conditions that are MI but not from inpatient records, a stream of People that are Male but not White, and a stream of Procedures that are office visits (this stream is completely unaffected by the right hand stream)](spec/0e33b527af36923e0d4f9ccca73bcffcc5274cf5bf652a9bed71427b4e616bf5.png)
544
+
545
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
546
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ---------------- |
547
+ | 2 | 58127 | observations | procedure_occurrence | 2009-07-03 | 2009-07-03 | 99214 |
548
+ | 2 | 83339 | observations | procedure_occurrence | 2009-12-02 | 2009-12-02 | 99214 |
549
+ | 4 | 4 | patients | person | 1941-06-01 | 1941-06-01 | 00021CA6FF03E670 |
550
+ | 5 | 38313 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99214 |
551
+ | 5 | 76788 | observations | procedure_occurrence | 2009-07-04 | 2009-07-04 | 99214 |
552
+ | 5 | 63847 | observations | procedure_occurrence | 2009-12-21 | 2009-12-21 | 99214 |
553
+ | 6 | 6 | patients | person | 1943-10-01 | 1943-10-01 | 0002DAE1C81CC70D |
554
+ | 6 | 48992 | observations | procedure_occurrence | 2009-09-12 | 2009-09-12 | 99214 |
555
+ | 6 | 76651 | observations | procedure_occurrence | 2010-02-23 | 2010-02-23 | 99214 |
556
+ | 7 | 63797 | observations | procedure_occurrence | 2008-08-13 | 2008-08-13 | 99214 |
557
+
558
+ ### Discussion about Set Operators
559
+
560
+ #### Union Operators
561
+
562
+ ##### Q. Why should we allow two different types of streams to continue downstream concurrently?
563
+
564
+ - This feature lets us do interesting things, like find the first occurrence of either an MI or Death as in the example below
565
+ - Throw in a few more criteria and you could find the first occurrence of all censor events for each patient
566
+
567
+ ```YAML
568
+ ---
569
+ - first
570
+ - - union
571
+ - - icd9
572
+ - '412'
573
+ - - death
574
+
575
+ ```
576
+
577
+ ![First occurrence of either MI or Death for each patient](spec/3bf7022e9a06d0b25d7e66f30508c7ac68453b51bbede666c94d7988f681bcad.png)
578
+
579
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
580
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
581
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
582
+ | 16 | 1 | deaths | death | 2010-12-01 | 2010-12-01 | |
583
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
584
+ | 26 | 2 | deaths | death | 2009-07-01 | 2009-07-01 | |
585
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
586
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
587
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
588
+ | 65 | 3 | deaths | death | 2009-01-01 | 2009-01-01 | |
589
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
590
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
591
+
592
+ ## Filtering by Provenance
593
+
594
+ ### provenance
595
+
596
+ `provenance` passes along the events of its upstream stream that come from a given kind of source record. Each event has two provenances. Its file provenance tells which kind of file the record came from, such as `inpatient`, `outpatient` or `carrier_claim`. Its code provenance tells the position of the code in the record, such as `primary`, `first` or `admitting`. Each argument of `provenance` is a keyword from the file or the code provenance vocabulary, and the operator keeps an event when its provenance is that keyword's concept or one of its descendants.
597
+
598
+ A plain keyword, such as `inpatient` or `primary_or_first`, keeps an event when either provenance matches it. A keyword of the form `<file keyword>:<code keyword>` keeps an event when its file provenance matches the file keyword AND its code provenance matches the code keyword. The left half must be a file provenance keyword and the right half a code provenance keyword. Any other keyword with a `:` is an error.
599
+
600
+ When `provenance` has several keywords, it keeps the events that match any one of them. So `["inpatient:primary", "outpatient:first"]` keeps the inpatient events with a primary code and the outpatient events with a first code.
601
+
602
+ ```YAML
603
+ ---
604
+ - provenance
605
+ - - icd9
606
+ - '250.00'
607
+ - inpatient:primary_or_first
608
+
609
+ ```
610
+
611
+ ![Diabetes diagnoses that are the primary or first code of an inpatient record](spec/14942bd4e7713897ad9aa646c71cc348d5690cccafadd50e4a7dcd65b9a81061.png)
612
+
613
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
614
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
615
+ | 37 | 310 | observations | condition_occurrence | 2009-01-19 | 2009-01-20 | 25000 |
616
+ | 68 | 19 | observations | condition_occurrence | 2008-04-19 | 2008-04-23 | 25000 |
617
+ | 68 | 1596 | observations | condition_occurrence | 2008-11-10 | 2008-11-11 | 25000 |
618
+ | 131 | 676 | observations | condition_occurrence | 2008-03-21 | 2008-03-24 | 25000 |
619
+ | 207 | 106 | observations | condition_occurrence | 2010-04-28 | 2010-05-02 | 25000 |
620
+ | 237 | 918 | observations | condition_occurrence | 2010-07-16 | 2010-07-19 | 25000 |
621
+
622
+ The keyword `*` keeps an event that has any file or code provenance. It drops only the events whose two provenances are both empty. Several keywords are ORed, so `*` with other keywords keeps the same events as `*` alone. A `*` in a `<file keyword>:<code keyword>` keyword is an error. `provenance` needs at least one keyword: with none, it is the error `no provenance keywords; use "*" for any provenance`.
623
+
624
+ ```YAML
625
+ ---
626
+ - provenance
627
+ - - icd9
628
+ - '250.00'
629
+ - "*"
630
+
631
+ ```
632
+
633
+ ![Diabetes diagnoses with any provenance](spec/8614aacee51d8264bc6c61b5e202aa9c0c9318fd66c2ff254cdb45456bdda9fc.png)
634
+
635
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
636
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
637
+ | 2 | 89752 | observations | condition_occurrence | 2009-05-11 | 2009-05-11 | 25000 |
638
+ | 2 | 57897 | observations | condition_occurrence | 2009-05-16 | 2009-05-16 | 25000 |
639
+ | 2 | 76985 | observations | condition_occurrence | 2009-07-31 | 2009-07-31 | 25000 |
640
+ | 3 | 93400 | observations | condition_occurrence | 2009-11-17 | 2009-11-17 | 25000 |
641
+ | 5 | 89717 | observations | condition_occurrence | 2009-06-28 | 2009-06-28 | 25000 |
642
+ | 7 | 15942 | observations | condition_occurrence | 2008-04-23 | 2008-04-23 | 25000 |
643
+ | 7 | 69706 | observations | condition_occurrence | 2008-05-12 | 2008-05-12 | 25000 |
644
+ | 7 | 38732 | observations | condition_occurrence | 2008-06-09 | 2008-06-09 | 25000 |
645
+ | 7 | 38733 | observations | condition_occurrence | 2008-06-09 | 2008-06-09 | 25000 |
646
+ | 7 | 70454 | observations | condition_occurrence | 2008-10-02 | 2008-10-02 | 25000 |
647
+
648
+ ## Co-reported Events
649
+
650
+ ### co_reported
651
+
652
+ `co_reported` passes along the events of its upstream streams that were reported in the same source record, for example on the same claim. The source record is the event's context (`context_id`).
653
+
654
+ A source record is kept only if every upstream stream has at least one event in it. If any one upstream has no event in a record, `co_reported` drops the events of **all** streams in that record. The drop is silent: the operator gives no warning and does not report how many records it removed.
655
+
656
+ For example, a claim with a diabetes diagnosis and no office-visit procedure contributes nothing to the statement below, not even its diagnosis. In the test data, ICD-9 250.00 has 1,036 events and CPT 99214 has 1,073 events, but only 49 records have both, so `co_reported` returns 98 events.
657
+
658
+ ```YAML
659
+ ---
660
+ - co_reported
661
+ - - icd9
662
+ - '250.00'
663
+ - - cpt
664
+ - '99214'
665
+
666
+ ```
667
+
668
+ ![Yields the diabetes diagnoses and the office-visit procedures that were reported in the same source record](spec/dfc63bee1d9f447450c764cc505def315209de4c752ce49f4cb3779dd28b1001.png)
669
+
670
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
671
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
672
+ | 25 | 93217 | observations | condition_occurrence | 2008-09-18 | 2008-09-18 | 25000 |
673
+ | 25 | 93220 | observations | procedure_occurrence | 2008-09-18 | 2008-09-18 | 99214 |
674
+ | 31 | 87695 | observations | condition_occurrence | 2008-06-17 | 2008-06-17 | 25000 |
675
+ | 31 | 87696 | observations | procedure_occurrence | 2008-06-17 | 2008-06-17 | 99214 |
676
+ | 40 | 81973 | observations | condition_occurrence | 2009-05-01 | 2009-05-01 | 25000 |
677
+ | 40 | 81993 | observations | procedure_occurrence | 2009-05-01 | 2009-05-01 | 99214 |
678
+ | 44 | 44702 | observations | condition_occurrence | 2008-01-10 | 2008-01-10 | 25000 |
679
+ | 44 | 44715 | observations | procedure_occurrence | 2008-01-10 | 2008-01-10 | 99214 |
680
+ | 58 | 35093 | observations | condition_occurrence | 2009-06-15 | 2009-06-15 | 25000 |
681
+ | 58 | 35095 | observations | procedure_occurrence | 2009-06-15 | 2009-06-15 | 99214 |
682
+
683
+ ## Time-oriented Operators
684
+
685
+ All results in a stream carry a start_date and end_date with them. All temporal comparisons of streams use these two date columns. Each result in a stream derives its start and end date from its corresponding row in its table of origin.
686
+
687
+ For instance, a condition_occurrence result derives its start_date and end_date from the start_date and end_date columns of its row in the clinical codes table.
688
+
689
+ If a result comes from a table that only has a single date value, the result derives both its start_date and end_date from that single date, e.g. a death result derives both its start_date and end_date from the date column of its row in deaths.
690
+
691
+ The person stream is a special case. Person results use the person's date of birth as the start_date and end_date. This may sound strange, but we will explain below why this makes sense.
692
+
693
+ ### Relative Temporal Operators
694
+
695
+ When looking at a set of results for a person, perhaps we want to select just the chronologically first or last result. Or maybe we want to select the 2nd result or 2nd to last result. Relative temporal operators provide this type of filtering. Relative temporal operators use a result's start_date to do chronological ordering.
696
+
697
+ #### occurrence
698
+
699
+ - Takes a two arguments: the stream to select from and an integer argument
700
+ - For the integer argument
701
+ - Positive numbers mean 1st, 2nd, 3rd occurrence in chronological order
702
+ - e.g. 1 => first
703
+ - e.g. 4 => fourth
704
+ - Negative numbers mean 1st, 2nd, 3rd occurrence in reverse chronological order
705
+ - e.g. -1 => last
706
+ - e.g. -4 => fourth from last
707
+ - 0 is undefined?
708
+
709
+ ```YAML
710
+ ---
711
+ - occurrence
712
+ - 2
713
+ - - icd9
714
+ - '412'
715
+
716
+ ```
717
+
718
+ ![For each patient, select the Condition that represents the second occurrence of an MI](spec/267385650ec29b77f30e5da3d7103c5ce6982947722d939dfc3ea8e5d030ce59.png)
719
+
720
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
721
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
722
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
723
+ | 88 | 73803 | observations | condition_occurrence | 2009-05-30 | 2009-05-30 | 412 |
724
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
725
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
726
+ | 187 | 87583 | observations | condition_occurrence | 2010-12-26 | 2010-12-26 | 412 |
727
+ | 212 | 34052 | observations | condition_occurrence | 2009-10-07 | 2009-10-07 | 412 |
728
+ | 213 | 15005 | observations | condition_occurrence | 2010-02-07 | 2010-02-07 | 412 |
729
+ | 214 | 83257 | observations | condition_occurrence | 2009-11-12 | 2009-11-12 | 412 |
730
+ | 220 | 20660 | observations | condition_occurrence | 2009-10-31 | 2009-10-31 | 412 |
731
+ | 223 | 39893 | observations | condition_occurrence | 2008-10-31 | 2008-10-31 | 412 |
732
+
733
+ #### first
734
+
735
+ - Operator that is shorthand for writing "occurrence: 1"
736
+
737
+ ```YAML
738
+ ---
739
+ - first
740
+ - - icd9
741
+ - '412'
742
+
743
+ ```
744
+
745
+ ![For each patient, select the Condition that represents the first occurrence of an MI](spec/04491942fcbd741982514f9eb12aeecf3d54b5b69a2b50c8331f7700169d5521.png)
746
+
747
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
748
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
749
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
750
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
751
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
752
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
753
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
754
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
755
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
756
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
757
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
758
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
759
+
760
+ #### last
761
+
762
+ - Operator that is just shorthand for writing "occurrence: -1"
763
+
764
+ ```YAML
765
+ ---
766
+ - last
767
+ - - icd9
768
+ - '412'
769
+
770
+ ```
771
+
772
+ ![For each patient, select the Condition that represents the last occurrence of an MI](spec/ebacbd092e3d1a3c7b745a381e51e8ff9d63a21db23a16940193e18e57bc866f.png)
773
+
774
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
775
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
776
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
777
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
778
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
779
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
780
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
781
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
782
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
783
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
784
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
785
+ | 88 | 73803 | observations | condition_occurrence | 2009-05-30 | 2009-05-30 | 412 |
786
+
787
+ ### Date Literals
788
+
789
+ For situations where we need to represent pre-defined date ranges, we can use "date literal" operators.
790
+
791
+ #### date_range
792
+
793
+ - Takes a hash with two elements: { start: \<date-format>, end: \<date-format> }
794
+ - Creates an inclusive, continuous range of dates defined by a start and end date
795
+
796
+ #### day
797
+
798
+ - Takes a single argument: \<date-format>
799
+ - Represents a single day
800
+ - Shorthand for creating a date range that starts and ends on the same date
801
+ - *Not yet implemented*
802
+
803
+ #### What is \<date-format>?
804
+
805
+ Dates follow these formats:
806
+
807
+ - "YYYY-MM-DD"
808
+ - Four-digit year, two-digit month with leading 0s, two-digit day with leading 0s
809
+ - "START"
810
+ - Represents the first date of information available from the data source
811
+ - "END"
812
+ - Represents the last date of information available from the data source.
813
+
814
+ ### Temporal Comparison Operators
815
+
816
+ As described above, each result carries a start and end date, defining its own date range. It is through these date ranges that we are able to do temporal filtering of streams via temporal operators.
817
+
818
+ Temporal operators work by comparing a left-hand stream (L) against a right-hand stream (R). R can be either a set of streams or a pre-defined date range. Each temporal operator has a comparison operator which defines how it compares dates between L and R. A temporal operator passes results only from L downstream. A temporal operator discards all results in the R stream after it makes all comparisons.
819
+
820
+ The available set of temporal operators comes from the work of Allen's Interval Algebra[^AIA]. Interval Algebra defines 13 distinct temporal relationships, as shown in this handy chart [borrowed from this website](http://people.kmi.open.ac.uk/carlos/174): ![](http://people.kmi.open.ac.uk/carlos/wp-content/uploads/2011/02/Allens-Algebra.png)
821
+
822
+ Our implementation of this algebra is originally going to be as strict as listed here, meaning that:
823
+
824
+ - Before/After
825
+ - There must be a minimum 1-day gap between date ranges
826
+ - Meets/Met-by
827
+ - Only if the first date range starts/ends a day before the next date range ends/starts
828
+ - Started-by/Starts
829
+ - The start dates of the two ranges must be equal and the end dates must not be
830
+ - Finished-by/Finishes
831
+ - The end dates of the two ranges must be equal and the start dates must not be
832
+ - Contains/During
833
+ - The start/end dates of the two ranges must be different from each other
834
+ - Overlaps/Overlapped-by
835
+ - The start date of one range and the end date of the other range must be outside the overlapping range
836
+ - Temporally coincides
837
+ - Start dates must be equal, end dates must be equal
838
+
839
+ Ryan's Sidebar on These Definitions:
840
+
841
+ > These strict definitions may not be particularly handy or even intuitive. It seems like contains, starts, finishes, and coincides are all examples of overlapping ranges. Starts/finishes seem to be examples of one range containing another. Meets/met-by seem to be special cases of before/after. But these definitions, if used in their strict sense, are all mutually exclusive.
842
+
843
+ > We may want to adopt a less strict set of definitions, though their meaning may not be as easily defined as the one provided by Allen's Interval Algebra
844
+
845
+ When comparing results in L against a date range, results in L continue downstream only if they pass the comparison.
846
+
847
+ ```YAML
848
+ ---
849
+ - during
850
+ - :left:
851
+ - icd9
852
+ - '412'
853
+ :right:
854
+ - date_range
855
+ - :start: '2010-01-01'
856
+ :end: '2010-12-31'
857
+
858
+ ```
859
+
860
+ ![All MIs for the year 2010](spec/ba90ef705f7be91c53c5eb4a81a439fa0f7c48532214bd3db3cc5c069160543e.png)
861
+
862
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
863
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
864
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
865
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
866
+ | 90 | 33940 | observations | condition_occurrence | 2010-05-26 | 2010-05-26 | 412 |
867
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
868
+ | 110 | 62592 | observations | condition_occurrence | 2010-06-27 | 2010-06-27 | 412 |
869
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
870
+ | 187 | 87583 | observations | condition_occurrence | 2010-12-26 | 2010-12-26 | 412 |
871
+ | 213 | 15005 | observations | condition_occurrence | 2010-02-07 | 2010-02-07 | 412 |
872
+ | 217 | 56467 | observations | condition_occurrence | 2010-06-22 | 2010-06-23 | 412 |
873
+ | 239 | 49718 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
874
+
875
+ When comparing results in L against a set of results in R, the temporal operator compares results in stream L against results in stream R on a person-by-person basis.
876
+
877
+ - If a person has results in L or R stream, but not in both, none of their results continue downstream
878
+ - On a per person basis, the temporal operator joins all results in the L stream to all results in the R stream
879
+ - Any results in the L stream that meet the temporal comparison against any results in the R stream continue downstream
880
+
881
+ ```YAML
882
+ ---
883
+ - during
884
+ - :left:
885
+ - icd9
886
+ - '412'
887
+ :right:
888
+ - hospitalization
889
+
890
+ ```
891
+
892
+ ![All MIs that occurred during a hospitalization](spec/9524ac806e9ac1dd97132bc6b66a793536b9d8fa31d7ded9f54fba018e6288aa.png)
893
+
894
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
895
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
896
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
897
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
898
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
899
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
900
+ | 161 | 963 | observations | condition_occurrence | 2009-10-25 | 2009-10-29 | 412 |
901
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
902
+ | 230 | 523 | observations | condition_occurrence | 2008-03-14 | 2008-03-21 | 412 |
903
+
904
+ #### Edge behaviors
905
+
906
+ For 11 of the 13 temporal operators, comparison of results is straight-forward. However, the before/after operators have a slight twist.
907
+
908
+ Imagine events 1-1-2-1-2-1. In my mind, three 1's come before a 2 and two 1's come after a 2. Accordingly:
909
+
910
+ - When comparing L **before** R, the temporal operator compares L against the **LAST** occurrence of R per person
911
+ - When comparing L **after** R, the temporal operator compares L against the **FIRST** occurrence of R per person
912
+
913
+ If we're looking for events in L that occur before events in R, then any event in L that occurs before the last event in R technically meet the comparison of "before". The reverse is true for after: all events in L that occur after the first event in R technically occur after R.
914
+
915
+ ```YAML
916
+ ---
917
+ - before
918
+ - :left:
919
+ - icd9
920
+ - '412'
921
+ :right:
922
+ - icd9
923
+ - '486'
924
+
925
+ ```
926
+
927
+ ![All MIs that occurred before a patient's last case of pneumonia (486)](spec/fc3b7c03816cc84fd92931b2cd0eb279e3fba5851e1ce15d4f084a1a3f04feba.png)
928
+
929
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
930
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
931
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
932
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
933
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
934
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
935
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
936
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
937
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
938
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
939
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
940
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
941
+
942
+ If this is not the behavior you desire, use one of the sequence operators to select which event in R should be the one used to do comparison
943
+
944
+ ```YAML
945
+ ---
946
+ - before
947
+ - :left:
948
+ - icd9
949
+ - '412'
950
+ :right:
951
+ - first
952
+ - - icd9
953
+ - '486'
954
+
955
+ ```
956
+
957
+ ![All MIs that occurred before a patient's first case of pneumonia (486)](spec/bd129d7a0fc5b3ee6610f990e4d8a6c604825e11ed04123c202595a08f917438.png)
958
+
959
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
960
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
961
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
962
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
963
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
964
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
965
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
966
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
967
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
968
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
969
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
970
+ | 213 | 55557 | observations | condition_occurrence | 2009-06-07 | 2009-06-07 | 412 |
971
+
972
+ ## Time Windows
973
+
974
+ There are situations when the date columns associated with a result should have their values shifted forward or backward in time to make a comparison with another set of dates.
975
+
976
+ ### time_window
977
+
978
+ - Takes 2 arguments
979
+ - First argument is the stream on which to operate
980
+ - Second argument is a hash with two keys: [:start, :end] each with a value in the following format: "[res]\*([-+]?\\d\*[dwmy])+" or "[res]\*[-+]?\\d+"
981
+ - d is days, w is weeks (7 days), m is months and y is years
982
+ - A unit without a number means 1 of that unit
983
+ - Both start and end must be defined, even if you are only adjusting one of the dates
984
+ - Some examples
985
+ - 30d => 30 days
986
+ - 20 => 20 days
987
+ - d => 1 day
988
+ - 2w => 2 weeks (14 days)
989
+ - 1y => 1 year
990
+ - -1m => -1 month
991
+ - 10d3m => 3 months and 10 days
992
+ - -2y10m-3d => -2 years, +10 months, -3 days
993
+ - An optional prefix of the letters r, e and s comes before the amounts
994
+ - r reverses the adjustment: each amount counts back instead of forward, so R365d means 365 days before the date and R-1m means 1 month after it
995
+ - e adjusts from the result's end_date, so E30d means end_date + 30 days
996
+ - s adjusts from the result's start_date, so S7d means start_date + 7 days
997
+ - Without e or s, the start value adjusts start_date and the end value adjusts end_date
998
+ - A bare number means days, also after an r, e or s prefix: R365, E30 and S7 mean the same as R365d, E30d and S7d
999
+ - The start or end value can also be '', '0', or nil
1000
+ - This will leave the date unaffected
1001
+ - The start or end value can also be the string 'start' or 'end'
1002
+ - 'start' represents the start_date for each result
1003
+ - 'end' represents the end_date for each result
1004
+ - See the example below
1005
+
1006
+ ```YAML
1007
+ ---
1008
+ - during
1009
+ - :left:
1010
+ - icd9
1011
+ - '486'
1012
+ :right:
1013
+ - time_window
1014
+ - - icd9
1015
+ - '412'
1016
+ - :start: "-30d"
1017
+ :end: 30d
1018
+
1019
+ ```
1020
+
1021
+ ![All Diagnoses of Pneumonia (ICD-9 486) within 30 days of an MI](spec/c01589dc9f64d7ad8ef808d7471d19abec29f06771b06da7f3f884f3ea2fdc43.png)
1022
+
1023
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1024
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1025
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
1026
+ | 38 | 47462 | observations | condition_occurrence | 2010-02-24 | 2010-02-24 | 486 |
1027
+ | 38 | 47463 | observations | condition_occurrence | 2010-02-24 | 2010-02-24 | 486 |
1028
+ | 38 | 47464 | observations | condition_occurrence | 2010-02-24 | 2010-02-24 | 486 |
1029
+ | 60 | 988 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 486 |
1030
+ | 77 | 62783 | observations | condition_occurrence | 2010-10-17 | 2010-10-17 | 486 |
1031
+ | 110 | 81700 | observations | condition_occurrence | 2010-06-18 | 2010-06-19 | 486 |
1032
+ | 172 | 87537 | observations | condition_occurrence | 2009-01-25 | 2009-01-25 | 486 |
1033
+ | 214 | 59068 | observations | condition_occurrence | 2009-10-23 | 2009-10-23 | 486 |
1034
+ | 220 | 47310 | observations | condition_occurrence | 2008-11-26 | 2008-11-26 | 486 |
1035
+
1036
+ ```YAML
1037
+ ---
1038
+ - time_window
1039
+ - - icd9
1040
+ - '412'
1041
+ - :start: "-2y"
1042
+ :end: "-2y"
1043
+
1044
+ ```
1045
+
1046
+ ![Shift the window for all MIs back by 2 years](spec/15268bc45993d3f57ccf915877f91e48bdee684f24faa614249915833cac4af9.png)
1047
+
1048
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1049
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1050
+ | 11 | 82614 | observations | condition_occurrence | 2006-10-29 | 2006-10-29 | 412 |
1051
+ | 17 | 27269 | observations | condition_occurrence | 2006-08-25 | 2006-08-25 | 412 |
1052
+ | 17 | 46165 | observations | condition_occurrence | 2007-04-30 | 2007-04-30 | 412 |
1053
+ | 38 | 40556 | observations | condition_occurrence | 2008-02-12 | 2008-02-12 | 412 |
1054
+ | 54 | 74606 | observations | condition_occurrence | 2006-06-05 | 2006-06-05 | 412 |
1055
+ | 60 | 986 | observations | condition_occurrence | 2007-07-19 | 2007-07-22 | 412 |
1056
+ | 66 | 16171 | observations | condition_occurrence | 2007-07-25 | 2007-07-25 | 412 |
1057
+ | 73 | 52843 | observations | condition_occurrence | 2006-11-16 | 2006-11-16 | 412 |
1058
+ | 77 | 58165 | observations | condition_occurrence | 2008-10-06 | 2008-10-06 | 412 |
1059
+ | 81 | 1405 | observations | condition_occurrence | 2007-01-28 | 2007-01-30 | 412 |
1060
+
1061
+ ```YAML
1062
+ ---
1063
+ - time_window
1064
+ - - icd9
1065
+ - '412'
1066
+ - :start: "-2m-2d"
1067
+ :end: 3d1y
1068
+
1069
+ ```
1070
+
1071
+ ![Expand the dates for all MIs to a window ranging from 2 months and 2 days prior to 1 year and 3 days after the MI](spec/cc960a268d51ccf2ebde657d36848f780213834844900018bce3d111843f7b0f.png)
1072
+
1073
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1074
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1075
+ | 11 | 82614 | observations | condition_occurrence | 2008-08-27 | 2009-11-01 | 412 |
1076
+ | 17 | 27269 | observations | condition_occurrence | 2008-06-23 | 2009-08-28 | 412 |
1077
+ | 17 | 46165 | observations | condition_occurrence | 2009-02-26 | 2010-05-03 | 412 |
1078
+ | 38 | 40556 | observations | condition_occurrence | 2009-12-10 | 2011-02-15 | 412 |
1079
+ | 54 | 74606 | observations | condition_occurrence | 2008-04-03 | 2009-06-08 | 412 |
1080
+ | 60 | 986 | observations | condition_occurrence | 2009-05-17 | 2010-07-25 | 412 |
1081
+ | 66 | 16171 | observations | condition_occurrence | 2009-05-23 | 2010-07-28 | 412 |
1082
+ | 73 | 52843 | observations | condition_occurrence | 2008-09-14 | 2009-11-19 | 412 |
1083
+ | 77 | 58165 | observations | condition_occurrence | 2010-08-04 | 2011-10-09 | 412 |
1084
+ | 81 | 1405 | observations | condition_occurrence | 2008-11-26 | 2010-02-02 | 412 |
1085
+
1086
+ ```YAML
1087
+ ---
1088
+ - time_window
1089
+ - - hospitalization
1090
+ - :start: ''
1091
+ :end: start
1092
+
1093
+ ```
1094
+
1095
+ ![Collapse all hospitalizations' date ranges down to just the date of admission by leaving start_date unaffected and setting end_date to start_date](spec/aff055a65bcf745c075dc3f80b567ab40f1ce74cb9bcb4976d8044761b8aed32.png)
1096
+
1097
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1098
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1099
+ | 1 | 95 | collections | condition_occurrence | 2010-03-12 | 2010-03-12 | V4501 |
1100
+ | 2 | 37 | collections | condition_occurrence | 2009-04-12 | 2009-04-12 | 2724 |
1101
+ | 2 | 96 | collections | condition_occurrence | 2009-08-31 | 2009-08-31 | 7092 |
1102
+ | 2 | 12 | collections | condition_occurrence | 2009-09-17 | 2009-09-17 | 32723 |
1103
+ | 2 | 43 | collections | condition_occurrence | 2010-06-26 | 2010-06-26 | 7856 |
1104
+ | 14 | 151 | collections | condition_occurrence | 2008-09-12 | 2008-09-12 | 2724 |
1105
+ | 17 | 93 | collections | condition_occurrence | 2008-09-19 | 2008-09-19 | 33811 |
1106
+ | 17 | 83 | collections | condition_occurrence | 2010-05-22 | 2010-05-22 | 5845 |
1107
+ | 17 | 122 | collections | condition_occurrence | 2010-06-02 | 2010-06-02 | 4280 |
1108
+ | 17 | 124 | collections | condition_occurrence | 2010-06-16 | 2010-06-16 | V4365 |
1109
+
1110
+ ```YAML
1111
+ ---
1112
+ - time_window
1113
+ - - icd9
1114
+ - '412'
1115
+ - :start: end
1116
+ :end: start
1117
+
1118
+ ```
1119
+
1120
+ ![Nonsensical, but allowed: swap the start_date and end_date for a range](spec/6705af65d728c0d5c50d6c4d46253017a91eb459dbb1f40f92449f05d5562f4a.png)
1121
+
1122
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1123
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1124
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1125
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1126
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1127
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1128
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1129
+ | 60 | 986 | observations | condition_occurrence | 2009-07-22 | 2009-07-19 | 412 |
1130
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1131
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1132
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1133
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-30 | 2009-01-28 | 412 |
1134
+
1135
+ ### Temporal Operators and Person Streams
1136
+
1137
+ Person streams carry a patient's date of birth in their date columns. This makes them almost useless when they are part of the L stream of a temporal operator. But person streams are useful as the R stream. By `time_window`ing the patient's date of birth, we can filter based on the patient's age like so:
1138
+
1139
+ ```YAML
1140
+ ---
1141
+ - after
1142
+ - :left:
1143
+ - icd9
1144
+ - '412'
1145
+ :right:
1146
+ - time_window
1147
+ - - gender
1148
+ - Male
1149
+ - :start: 50y
1150
+ :end: 50y
1151
+
1152
+ ```
1153
+
1154
+ ![All MIs that occurred after a male patient's 50th birthday](spec/68fac940a32c7e40caacf8e560c61da552d57633d015ba98f2e98ec040a00c5b.png)
1155
+
1156
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1157
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1158
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1159
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1160
+ | 93 | 82295 | observations | condition_occurrence | 2009-11-08 | 2009-11-08 | 412 |
1161
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
1162
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
1163
+ | 110 | 62592 | observations | condition_occurrence | 2010-06-27 | 2010-06-27 | 412 |
1164
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
1165
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
1166
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
1167
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
1168
+
1169
+ ### trim_date_end and trim_date_start
1170
+
1171
+ - trim_date_end takes a left stream and a right stream. For each person, it trims the end_date of each left record to the earliest start_date of the person's right records.
1172
+ - A left record whose start_date comes after that right start_date is discarded.
1173
+ - A left record passes through unchanged when its person has no right records, or when that right start_date comes after its end_date.
1174
+ - trim_date_start trims the other end. For each person, it trims the start_date of each left record to the latest end_date of the person's right records.
1175
+ - A left record whose end_date comes before that right end_date is discarded.
1176
+ - A left record passes through unchanged when its person has no right records, or when that right end_date comes before its start_date.
1177
+ - With join_columns, the person's right records are the right records of the person with the same join_columns values as the left record.
1178
+ - With compare_all: true, trim_date_end and trim_date_start compare each left record with every right record of the person (and of the join_columns), not only with the person's first or last right record, and return each left record once.
1179
+ - trim_date_end trims its end_date to the earliest right start_date on or after its start_date.
1180
+ - trim_date_start trims its start_date to the latest right end_date on or before its end_date.
1181
+ - A left record is discarded only when it has right records and none of them qualifies.
1182
+
1183
+ ### Window tables
1184
+
1185
+ - The `window` statement runs a statement inside windows: `["window", <statement>, {"window_table": <window table>}]`.
1186
+ - The window table has the columns person_id, start_date and end_date. It is the name of a table or a ConceptQL statement.
1187
+ - Each selection operator keeps only the results whose person has a window and whose start_date falls in that window. The column window_id of a result identifies its window.
1188
+ - If the window table is a ConceptQL statement with errors, annotate reports them under window_table and counts nothing. Running the statement raises InvalidStatementError.
1189
+
1190
+ ## Type Conversion
1191
+
1192
+ There are situations where it is appropriate to convert the type of a stream of results into a different type. In programmer parlance, we say "typecasting" or "casting", which is the terminology we'll use here. A good analogy and mnemonic for casting is to think of taking a piece of metal, say a candle holder, melting it down, and recasting it into, say, a lamp. We'll do something similar with streams. We'll take, for example, a condition_occurrence stream and recast it into a stream of person.
1193
+
1194
+ ConceptQL has two casting operators: person and death. GDM has no visit table to cast to. To find conditions and procedures that were reported in the same source record, such as the same claim, use the co_reported operator (see Co-reported Events).
1195
+
1196
+ ### Casting to person
1197
+
1198
+ - Useful if we're just checking for the presence of a condition for a person
1199
+ - E.g. We want to know *if* a person has an old MI, not when an MI or how many MIs occurred
1200
+
1201
+ ```YAML
1202
+ ---
1203
+ - person
1204
+ - - icd9
1205
+ - '412'
1206
+
1207
+ ```
1208
+
1209
+ ![All People Who Had an MI](spec/8ed478d8c81a58a202d0c51348fca246df206fc24a0567b163d4e0bdab56ca46.png)
1210
+
1211
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1212
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1213
+ | 11 | 11 | patients | person | 1934-02-01 | 1934-02-01 | 000489E7EAAD463F |
1214
+ | 17 | 17 | patients | person | 1919-09-01 | 1919-09-01 | 0007F12A492FD25D |
1215
+ | 38 | 38 | patients | person | 1922-12-01 | 1922-12-01 | 001731EB127233DA |
1216
+ | 54 | 54 | patients | person | 1931-02-01 | 1931-02-01 | 001CAFF084B21E14 |
1217
+ | 60 | 60 | patients | person | 1925-07-01 | 1925-07-01 | 001EA2F4DB30F105 |
1218
+ | 66 | 66 | patients | person | 1920-04-01 | 1920-04-01 | 0021B3C854C968C8 |
1219
+ | 73 | 73 | patients | person | 1925-09-01 | 1925-09-01 | 00237322613CFC3C |
1220
+ | 77 | 77 | patients | person | 1929-03-01 | 1929-03-01 | 00244B6D9AB50F9B |
1221
+ | 81 | 81 | patients | person | 1940-06-01 | 1940-06-01 | 0024E5A7B7272E75 |
1222
+ | 88 | 88 | patients | person | 1922-05-01 | 1922-05-01 | 00291F39917544B1 |
1223
+
1224
+ ### Casting to death
1225
+
1226
+ - Replaces each result with the death record of the result's person
1227
+ - A person with no death record yields no results
1228
+
1229
+ ```YAML
1230
+ ---
1231
+ - death
1232
+ - - icd9
1233
+ - '412'
1234
+
1235
+ ```
1236
+
1237
+ ![The Deaths of All People Who Had an MI](spec/8b20c1a598c4d0f7cae613f707f5eaa90da3293d7ac24bb5b12df278a8e69d57.png)
1238
+
1239
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1240
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ------------ |
1241
+ | 190 | 7 | deaths | death | 2009-09-01 | 2009-09-01 | |
1242
+
1243
+ ### Casting Loses All Original Information
1244
+
1245
+ After a result undergoes casting, it loses its original information. E.g. casting a condition_occurrence to a person loses the condition information and resets the start_date and end_date columns to the person's date of birth. As a side note, this is actually handy if a person stream's dates have been altered by a time_window operator and you want the original dates later on. Just cast the stream to person again and it will regain the date of birth.
1246
+
1247
+ ### Casting as a way to fetch all rows
1248
+
1249
+ The casting operator doubles as a way to fetch all rows for a single type. Provide the casting operator with no upstream operator to get all rows as results:
1250
+
1251
+ ```YAML
1252
+ ---
1253
+ - death
1254
+
1255
+ ```
1256
+
1257
+ ![All death results in the database](spec/e281558c677a04df02e53dcc7fd6497a0d831e4037560cd13942b67ab10f98f4.png)
1258
+
1259
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1260
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ------------ |
1261
+ | 16 | 1 | deaths | death | 2010-12-01 | 2010-12-01 | |
1262
+ | 26 | 2 | deaths | death | 2009-07-01 | 2009-07-01 | |
1263
+ | 65 | 3 | deaths | death | 2009-01-01 | 2009-01-01 | |
1264
+ | 119 | 4 | deaths | death | 2008-09-01 | 2008-09-01 | |
1265
+ | 166 | 5 | deaths | death | 2008-07-01 | 2008-07-01 | |
1266
+ | 181 | 6 | deaths | death | 2010-07-01 | 2010-07-01 | |
1267
+ | 190 | 7 | deaths | death | 2009-09-01 | 2009-09-01 | |
1268
+ | 191 | 8 | deaths | death | 2009-09-01 | 2009-09-01 | |
1269
+ | 201 | 9 | deaths | death | 2008-05-01 | 2008-05-01 | |
1270
+ | 215 | 10 | deaths | death | 2008-01-01 | 2008-01-01 | |
1271
+
1272
+ This comes in handy for situations like these:
1273
+
1274
+ ```YAML
1275
+ ---
1276
+ - person_filter
1277
+ - :left:
1278
+ - gender
1279
+ - Male
1280
+ :right:
1281
+ - death
1282
+
1283
+ ```
1284
+
1285
+ ![All Male patients who died](spec/bb369188911f61bfd851f575d6e8dcc96f1328f4b58721e3fb32932985db1025.png)
1286
+
1287
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1288
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1289
+ | 16 | 16 | patients | person | 1934-01-01 | 1934-01-01 | 0007E57CC13CE880 |
1290
+ | 65 | 65 | patients | person | 1936-01-01 | 1936-01-01 | 0021940CF994C374 |
1291
+ | 181 | 181 | patients | person | 1939-11-01 | 1939-11-01 | 005E0AB5172E715F |
1292
+ | 190 | 190 | patients | person | 1939-07-01 | 1939-07-01 | 0060C263DA4EBBAF |
1293
+ | 191 | 191 | patients | person | 1928-12-01 | 1928-12-01 | 0061141AB18FFA96 |
1294
+ | 201 | 201 | patients | person | 1949-06-01 | 1949-06-01 | 00653DC86A0E30D8 |
1295
+
1296
+ ## Filtering by People
1297
+
1298
+ Often we want to filter out a set of results by people. For instance, say we wanted to find all MIs for all males. We'd use the person_filter operator for that. Like the Except operator, it takes a left-hand stream and a right-hand stream.
1299
+
1300
+ Unlike the `except` operator, the person_filter operator will use all types of all streams in the right-hand side to filter out results in all types of all streams on the left hand side.
1301
+
1302
+ ```YAML
1303
+ ---
1304
+ - person_filter
1305
+ - :left:
1306
+ - icd9
1307
+ - '412'
1308
+ :right:
1309
+ - gender
1310
+ - Male
1311
+
1312
+ ```
1313
+
1314
+ ![All MI Conditions for people who are male](spec/0360fc0c2b7a88fd82ffc4d13387b76766649d5d42aa8f9349bbf60a81ac6119.png)
1315
+
1316
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1317
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1318
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1319
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1320
+ | 93 | 82295 | observations | condition_occurrence | 2009-11-08 | 2009-11-08 | 412 |
1321
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
1322
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
1323
+ | 110 | 62592 | observations | condition_occurrence | 2010-06-27 | 2010-06-27 | 412 |
1324
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
1325
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
1326
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
1327
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
1328
+
1329
+ But we can get crazier. The right-hand side doesn't have to be a person stream. If a non-person stream is used in the right-hand side, the person_filter will cast all right-hand streams to person first and use the union of those streams:
1330
+
1331
+ ```YAML
1332
+ ---
1333
+ - person_filter
1334
+ - :left:
1335
+ - icd9
1336
+ - '412'
1337
+ :right:
1338
+ - cpt
1339
+ - '99214'
1340
+
1341
+ ```
1342
+
1343
+ ![All MI Conditions for people who had an office visit at some point in the data](spec/26fd52b5ac55438dd9f81b4a3f3913f1058c9f225c8a5e129007c1e9413d5881.png)
1344
+
1345
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1346
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1347
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1348
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1349
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1350
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1351
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1352
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1353
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1354
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1355
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1356
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1357
+
1358
+ ```YAML
1359
+ ---
1360
+ - person_filter
1361
+ - :left:
1362
+ - icd9
1363
+ - '412'
1364
+ :right:
1365
+ - person
1366
+ - - cpt
1367
+ - '99214'
1368
+
1369
+ ```
1370
+
1371
+ ![All MI Conditions for people who had an office visit at some point in the data (an explicit representation of what's happening in the diagram above)](spec/e1cb0863b21e14256d61a54200316791f6547c78ba2f0156c110f4500f9bbd49.png)
1372
+
1373
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1374
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1375
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1376
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1377
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1378
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1379
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1380
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1381
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1382
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1383
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1384
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1385
+
1386
+ ```YAML
1387
+ ---
1388
+ - person_filter
1389
+ - :left:
1390
+ - icd9
1391
+ - '412'
1392
+ :right:
1393
+ - union
1394
+ - - cpt
1395
+ - '99214'
1396
+ - - gender
1397
+ - Male
1398
+
1399
+ ```
1400
+
1401
+ ![All MI Conditions for people who are Male OR had an office visit at some point in the data](spec/5d331d74c460d75814b2d3138a9b7d90b5ddb2dcd85e1f5f260d183745fc3a1e.png)
1402
+
1403
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1404
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1405
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1406
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1407
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1408
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1409
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1410
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1411
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1412
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1413
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1414
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1415
+
1416
+ And don't forget the left-hand side can have multiple types of streams:
1417
+
1418
+ ```YAML
1419
+ ---
1420
+ - person_filter
1421
+ - :left:
1422
+ - union
1423
+ - - icd9
1424
+ - '412'
1425
+ - - cpt
1426
+ - '99214'
1427
+ :right:
1428
+ - gender
1429
+ - Male
1430
+
1431
+ ```
1432
+
1433
+ ![Yields two streams: a stream of all MI Conditions for people who are Male and a stream of all office visit Procedures for people who are Male](spec/c93dd18894a245a5647f99e1867d3779e4cd34c9c8f8860600ff0c837a5ffa53.png)
1434
+
1435
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1436
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1437
+ | 2 | 58127 | observations | procedure_occurrence | 2009-07-03 | 2009-07-03 | 99214 |
1438
+ | 2 | 83339 | observations | procedure_occurrence | 2009-12-02 | 2009-12-02 | 99214 |
1439
+ | 5 | 38313 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99214 |
1440
+ | 5 | 76788 | observations | procedure_occurrence | 2009-07-04 | 2009-07-04 | 99214 |
1441
+ | 5 | 63847 | observations | procedure_occurrence | 2009-12-21 | 2009-12-21 | 99214 |
1442
+ | 6 | 48992 | observations | procedure_occurrence | 2009-09-12 | 2009-09-12 | 99214 |
1443
+ | 6 | 76651 | observations | procedure_occurrence | 2010-02-23 | 2010-02-23 | 99214 |
1444
+ | 7 | 63797 | observations | procedure_occurrence | 2008-08-13 | 2008-08-13 | 99214 |
1445
+ | 7 | 59555 | observations | procedure_occurrence | 2008-09-21 | 2008-09-21 | 99214 |
1446
+ | 7 | 71597 | observations | procedure_occurrence | 2008-09-22 | 2008-09-22 | 99214 |
1447
+
1448
+ ## Sub-algorithms within a Larger Algorithm
1449
+
1450
+ If a algorithm is particularly complex, or has a stream of results that are used more than once, it can be helpful to break the algorithm into a set of sub-algorithms. This can be done using the `label` options and the `recall` operator.
1451
+
1452
+ ### `label` option
1453
+
1454
+ Any ConceptQL operator can be assigned a label. The label simply provides a way to apply a brief description to an operator, generally, what kind of results the operator is producing. Any operator that has a label can be accessed via the `recall` operator.
1455
+
1456
+ ### `recall` operator
1457
+
1458
+ - Takes 1 argument
1459
+ - The "label" of an operator from which you'd like to pull the exact same set of results
1460
+
1461
+ A stream must have a `label` before `recall` can use it.
1462
+
1463
+ ```YAML
1464
+ ---
1465
+ - first
1466
+ - - union
1467
+ - - provenance
1468
+ - - icd9
1469
+ - '412'
1470
+ - :label: Heart Attack
1471
+ - inpatient
1472
+ - - before
1473
+ - :left:
1474
+ - provenance
1475
+ - - recall
1476
+ - Heart Attack
1477
+ - outpatient
1478
+ - :label: Outpatient Heart Attack
1479
+ :right:
1480
+ - time_window
1481
+ - - recall
1482
+ - Outpatient Heart Attack
1483
+ - :start: "-30d"
1484
+ :end: '0'
1485
+ :label: Earliest of Two Outpatient Heart Attacks
1486
+
1487
+ ```
1488
+
1489
+ ![Save away a stream of results to build the 1 inpatient, 2 outpatient pattern used in claims data algorithms](spec/30e432f256d528ee828a7d97e550e110277841bce042da718ee253134dbeaf3f.png)
1490
+
1491
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1492
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1493
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1494
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1495
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
1496
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
1497
+ | 161 | 963 | observations | condition_occurrence | 2009-10-25 | 2009-10-29 | 412 |
1498
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
1499
+ | 230 | 523 | observations | condition_occurrence | 2008-03-14 | 2008-03-21 | 412 |
1500
+
1501
+ ConceptQL also has a `one_in_two_out` operator that implements this pattern in a single operator.
1502
+
1503
+ ## Algorithms within Algorithms
1504
+
1505
+ One of the main motivations behind keeping ConceptQL so flexible is to allow users to build ConceptQL statements from other ConceptQL statements. This section describes how this feature works.
1506
+
1507
+ Say a ConceptQL statement gathers all pneumonia diagnoses (ICD-9 486) and hospital visits (CPT 99232, subsequent hospital care) that were reported in the same source record:
1508
+
1509
+ ```YAML
1510
+ ---
1511
+ - co_reported
1512
+ - - icd9
1513
+ - '486'
1514
+ - - cpt
1515
+ - '99232'
1516
+
1517
+ ```
1518
+
1519
+ ![All Pneumonia Diagnoses and Hospital Visits reported in the same source record](spec/814fa803d0ba09d73b7bb33177d043c26b681f5c621e9d76dc5c2e18dc852770.png)
1520
+
1521
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1522
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1523
+ | 63 | 61990 | observations | condition_occurrence | 2009-07-02 | 2009-07-04 | 486 |
1524
+ | 63 | 61991 | observations | condition_occurrence | 2009-07-02 | 2009-07-04 | 486 |
1525
+ | 63 | 61994 | observations | procedure_occurrence | 2009-07-02 | 2009-07-04 | 99232 |
1526
+ | 63 | 61995 | observations | procedure_occurrence | 2009-07-02 | 2009-07-04 | 99232 |
1527
+ | 83 | 38139 | observations | condition_occurrence | 2008-03-02 | 2008-03-02 | 486 |
1528
+ | 83 | 38140 | observations | procedure_occurrence | 2008-03-02 | 2008-03-02 | 99232 |
1529
+ | 85 | 80214 | observations | condition_occurrence | 2008-08-24 | 2008-08-30 | 486 |
1530
+ | 85 | 80215 | observations | condition_occurrence | 2008-08-24 | 2008-08-30 | 486 |
1531
+ | 85 | 80216 | observations | procedure_occurrence | 2008-08-24 | 2008-08-30 | 99232 |
1532
+ | 85 | 80217 | observations | procedure_occurrence | 2008-08-24 | 2008-08-30 | 99232 |
1533
+
1534
+ The "algorithm" operator represents a stored ConceptQL statement in a new statement. It takes the ID of the stored statement. ConceptQL reads the stored statement from the `statement` column of the `concepts` table, in the row whose `concept_id` is that ID. So, assuming that the ID 2031 represents the algorithm defined above, we could gather all the people with those records like this:
1535
+
1536
+ ```YAML
1537
+ ---
1538
+ - person
1539
+ - - algorithm
1540
+ - 2031
1541
+
1542
+ ```
1543
+
1544
+ ![All People with a Pneumonia Diagnosis and a Hospital Visit reported in the same source record](spec/eaa8e3fcbe133f6962f97ae3de3714e943d5e38c3e1cecfe62c5a1972866228f.png)
1545
+
1546
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1547
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1548
+ | 63 | 63 | patients | person | 1964-05-01 | 1964-05-01 | 00205821D74AA7D8 |
1549
+ | 83 | 83 | patients | person | 1963-05-01 | 1963-05-01 | 002578091E1953AF |
1550
+ | 85 | 85 | patients | person | 1937-01-01 | 1937-01-01 | 0025ACAB19F5DEC0 |
1551
+ | 141 | 141 | patients | person | 1933-04-01 | 1933-04-01 | 0040CF7315D51D01 |
1552
+ | 143 | 143 | patients | person | 1945-05-01 | 1945-05-01 | 00422125F788CC20 |
1553
+ | 149 | 149 | patients | person | 1943-02-01 | 1943-02-01 | 0045A5AFF076CBE6 |
1554
+ | 150 | 150 | patients | person | 1936-01-01 | 1936-01-01 | 0046020E72E1CBFD |
1555
+ | 176 | 176 | patients | person | 1939-03-01 | 1939-03-01 | 0057DAC8F6C528CB |
1556
+ | 178 | 178 | patients | person | 1928-06-01 | 1928-06-01 | 0058B2A556D74DC2 |
1557
+ | 214 | 214 | patients | person | 1914-07-01 | 1914-07-01 | 006D1BD234E5C844 |
1558
+
1559
+ ConceptQL replaces the algorithm operator with the stored statement when it reads the statement, so the diagram above shows the stored statement. The result of nesting one algorithm within another is exactly the same had we taken algorithm operator and replaced it with the ConceptQL statement for the algorithm it represents.
1560
+
1561
+ ```YAML
1562
+ ---
1563
+ - person
1564
+ - - co_reported
1565
+ - - icd9
1566
+ - '486'
1567
+ - - cpt
1568
+ - '99232'
1569
+
1570
+ ```
1571
+
1572
+ ![All People with a Pneumonia Diagnosis and a Hospital Visit reported in the same source record (same as above)](spec/2b0e2bd4016c9888be0d4f3afe2e71d57a05796cbb396a786e447b33f5428897.png)
1573
+
1574
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1575
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1576
+ | 63 | 63 | patients | person | 1964-05-01 | 1964-05-01 | 00205821D74AA7D8 |
1577
+ | 83 | 83 | patients | person | 1963-05-01 | 1963-05-01 | 002578091E1953AF |
1578
+ | 85 | 85 | patients | person | 1937-01-01 | 1937-01-01 | 0025ACAB19F5DEC0 |
1579
+ | 141 | 141 | patients | person | 1933-04-01 | 1933-04-01 | 0040CF7315D51D01 |
1580
+ | 143 | 143 | patients | person | 1945-05-01 | 1945-05-01 | 00422125F788CC20 |
1581
+ | 149 | 149 | patients | person | 1943-02-01 | 1943-02-01 | 0045A5AFF076CBE6 |
1582
+ | 150 | 150 | patients | person | 1936-01-01 | 1936-01-01 | 0046020E72E1CBFD |
1583
+ | 176 | 176 | patients | person | 1939-03-01 | 1939-03-01 | 0057DAC8F6C528CB |
1584
+ | 178 | 178 | patients | person | 1928-06-01 | 1928-06-01 | 0058B2A556D74DC2 |
1585
+ | 214 | 214 | patients | person | 1914-07-01 | 1914-07-01 | 006D1BD234E5C844 |
1586
+
1587
+ ## Values
1588
+
1589
+ A result can carry forward three different types of values:
1590
+
1591
+ - value_as_number
1592
+ - For values like lab values, counts of occurrence of results, or sums
1593
+ - value_as_string
1594
+ - For values that are text, such as a lab result that is text
1595
+ - value_as_concept_id
1596
+ - For values that are concepts, such as a coded lab result
1597
+
1598
+ By default, all value fields are set to NULL, unless an operator is explicitly written to populate one or more of those fields.
1599
+
1600
+ There are many operations that can be performed on the value_as\_\* columns and as those operations are implemented, this section will grow.
1601
+
1602
+ For now we'll cover some of the general behavior of the value_as_number column and it's associated operators.
1603
+
1604
+ ### numeric
1605
+
1606
+ - Takes 2 arguments
1607
+ - A stream
1608
+ - And a numeric value
1609
+
1610
+ Passing streams through a `numeric` operator changes the number stored in the value column:
1611
+
1612
+ ```YAML
1613
+ ---
1614
+ - numeric
1615
+ - 2
1616
+ - - icd9
1617
+ - '412'
1618
+
1619
+ ```
1620
+
1621
+ ![All MIs, setting value_as_number to 2](spec/d39452b58257c95a7cba07afed4877417b0c227f3690e2bff22c9eca89eac845.png)
1622
+
1623
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1624
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ | --------------- |
1625
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 | 2.0 |
1626
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 | 2.0 |
1627
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 | 2.0 |
1628
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 | 2.0 |
1629
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 | 2.0 |
1630
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 | 2.0 |
1631
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 | 2.0 |
1632
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 | 2.0 |
1633
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 | 2.0 |
1634
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 | 2.0 |
1635
+
1636
+ With a `numeric` operator defined, we can use the `sum` operator to sum the values for each patient and type. This allows us to implement the Charlson comorbidity algorithm:
1637
+
1638
+ ```YAML
1639
+ ---
1640
+ - sum
1641
+ - - union
1642
+ - - numeric
1643
+ - 1
1644
+ - - person
1645
+ - - icd9
1646
+ - '412'
1647
+ - - numeric
1648
+ - 2
1649
+ - - person
1650
+ - - icd9
1651
+ - '278.02'
1652
+
1653
+ ```
1654
+
1655
+ ![](spec/ba572c4f4dbade65be55f141df16cf7b3e7d09e0aec4e4e5debc4f2075277371.png)
1656
+
1657
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1658
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- | --------------- |
1659
+ | 11 | 0 | patients | person | 1934-02-01 | 1934-02-01 | 000489E7EAAD463F | 1.0 |
1660
+ | 17 | 0 | patients | person | 1919-09-01 | 1919-09-01 | 0007F12A492FD25D | 3.0 |
1661
+ | 34 | 0 | patients | person | 1919-10-01 | 1919-10-01 | 00151A878F9A2C0D | 2.0 |
1662
+ | 38 | 0 | patients | person | 1922-12-01 | 1922-12-01 | 001731EB127233DA | 1.0 |
1663
+ | 54 | 0 | patients | person | 1931-02-01 | 1931-02-01 | 001CAFF084B21E14 | 1.0 |
1664
+ | 55 | 0 | patients | person | 1924-12-01 | 1924-12-01 | 001D0E59C94130D3 | 2.0 |
1665
+ | 60 | 0 | patients | person | 1925-07-01 | 1925-07-01 | 001EA2F4DB30F105 | 1.0 |
1666
+ | 66 | 0 | patients | person | 1920-04-01 | 1920-04-01 | 0021B3C854C968C8 | 1.0 |
1667
+ | 73 | 0 | patients | person | 1925-09-01 | 1925-09-01 | 00237322613CFC3C | 1.0 |
1668
+ | 77 | 0 | patients | person | 1929-03-01 | 1929-03-01 | 00244B6D9AB50F9B | 1.0 |
1669
+
1670
+ ### Counting
1671
+
1672
+ The `count` operator is deprecated. It still runs, but annotation shows a warning on it, and a later release will make it an error. `count` groups identical results and stores the number of results in each group in the value_as_number column. Two results are identical only when all of their columns match, including criterion_id. Every stream is a set, so `count` returns 1 for nearly every result. It does not count the events of each person.
1673
+
1674
+ To count the events of each person, set value_as_number to 1 with `numeric`, and then use `sum` with its `group_by_person` option. `sum` gives one person result for each person, and value_as_number holds the number of events. Without `group_by_person`, `sum` puts the results of one person in several groups when they have different source_values, so the count of the person is split over several results.
1675
+
1676
+ ```YAML
1677
+ ---
1678
+ - sum
1679
+ - - numeric
1680
+ - 1
1681
+ - - icd9
1682
+ - '412'
1683
+ - '414.01'
1684
+ - '250.00'
1685
+ - :group_by_person: true
1686
+
1687
+ ```
1688
+
1689
+ ![Count the number of times each person had a heart attack or diabetes](spec/8324a33ed37a82d29fb36ba9ed6fe84b445b7589af1c0c016813d2d98b2c6d75.png)
1690
+
1691
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1692
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ------------ | --------------- |
1693
+ | 2 | 0 | patients | person | 2009-05-11 | 2009-07-31 | | 3.0 |
1694
+ | 3 | 0 | patients | person | 2009-11-17 | 2009-11-17 | | 1.0 |
1695
+ | 5 | 0 | patients | person | 2009-06-28 | 2009-06-28 | | 1.0 |
1696
+ | 6 | 0 | patients | person | 2010-09-26 | 2010-09-26 | | 1.0 |
1697
+ | 7 | 0 | patients | person | 2008-04-23 | 2010-07-24 | | 30.0 |
1698
+ | 8 | 0 | patients | person | 2008-10-17 | 2009-07-16 | | 3.0 |
1699
+ | 9 | 0 | patients | person | 2010-08-01 | 2010-09-12 | | 2.0 |
1700
+ | 11 | 0 | patients | person | 2008-06-17 | 2009-11-12 | | 4.0 |
1701
+ | 12 | 0 | patients | person | 2009-04-20 | 2009-05-21 | | 3.0 |
1702
+ | 13 | 0 | patients | person | 2008-03-05 | 2010-09-08 | | 9.0 |
1703
+
1704
+ #### Numeric Value Comparison
1705
+
1706
+ The `numeric_filter` operator takes one stream. It passes along the results whose value_as_number is in a range, and it drops results whose value_as_number is NULL. It takes two options, and both are inclusive:
1707
+
1708
+ - greater_than_or_equal_to
1709
+ - less_than_or_equal_to
1710
+
1711
+ ```YAML
1712
+ ---
1713
+ - person
1714
+ - - numeric_filter
1715
+ - - sum
1716
+ - - numeric
1717
+ - 1
1718
+ - - icd9
1719
+ - '412'
1720
+ - :group_by_person: true
1721
+ - :greater_than_or_equal_to: 2
1722
+
1723
+ ```
1724
+
1725
+ ![People with more than 1 MI](spec/6613c788b33483210fbc0c40fdcab5942bd3fca204c8a188ae176d51435041b0.png)
1726
+
1727
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1728
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- | --------------- |
1729
+ | 17 | 17 | patients | person | 1919-09-01 | 1919-09-01 | 0007F12A492FD25D | |
1730
+ | 88 | 88 | patients | person | 1922-05-01 | 1922-05-01 | 00291F39917544B1 | |
1731
+ | 152 | 152 | patients | person | 1927-07-01 | 1927-07-01 | 00490754761BC1C2 | |
1732
+ | 177 | 177 | patients | person | 1939-02-01 | 1939-02-01 | 0057EDCE5B633B19 | |
1733
+ | 187 | 187 | patients | person | 1936-01-01 | 1936-01-01 | 006084B3FA2A151C | |
1734
+ | 212 | 212 | patients | person | 1940-01-01 | 1940-01-01 | 006B004741EB5AE9 | |
1735
+ | 213 | 213 | patients | person | 1940-02-01 | 1940-02-01 | 006C7FC30305A339 | |
1736
+ | 214 | 214 | patients | person | 1914-07-01 | 1914-07-01 | 006D1BD234E5C844 | |
1737
+ | 220 | 220 | patients | person | 1927-11-01 | 1927-11-01 | 0070946DE04C2014 | |
1738
+ | 223 | 223 | patients | person | 1926-05-01 | 1926-05-01 | 0071E2E641B73233 | |
1739
+
1740
+ ### numeric as selection operator
1741
+
1742
+ Numeric doesn't have to take a stream. If it doesn't have a stream as an argument, it acts like a selection operator much like date_range. It yields one person result for each patient, with value_as_number set to its value.
1743
+
1744
+ ```YAML
1745
+ ---
1746
+ - numeric
1747
+ - 1
1748
+
1749
+ ```
1750
+
1751
+ ![All patients, each with value_as_number set to 1](spec/3d6b85dbd283c27fa65bc58746c7fe1e58ab76962e72460dbcf85e336bf47e51.png)
1752
+
1753
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1754
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- | --------------- |
1755
+ | 1 | 1 | patients | person | 1923-05-01 | 1923-05-01 | 00013D2EFD8E45D1 | 1.0 |
1756
+ | 2 | 2 | patients | person | 1943-01-01 | 1943-01-01 | 00016F745862898F | 1.0 |
1757
+ | 3 | 3 | patients | person | 1936-09-01 | 1936-09-01 | 0001FDD721E223DC | 1.0 |
1758
+ | 4 | 4 | patients | person | 1941-06-01 | 1941-06-01 | 00021CA6FF03E670 | 1.0 |
1759
+ | 5 | 5 | patients | person | 1936-08-01 | 1936-08-01 | 00024B3D2352D2D0 | 1.0 |
1760
+ | 6 | 6 | patients | person | 1943-10-01 | 1943-10-01 | 0002DAE1C81CC70D | 1.0 |
1761
+ | 7 | 7 | patients | person | 1922-07-01 | 1922-07-01 | 0002F28CE057345B | 1.0 |
1762
+ | 8 | 8 | patients | person | 1935-09-01 | 1935-09-01 | 000308435E3E5B76 | 1.0 |
1763
+ | 9 | 9 | patients | person | 1976-09-01 | 1976-09-01 | 000345A39D4157C9 | 1.0 |
1764
+ | 10 | 10 | patients | person | 1938-10-01 | 1938-10-01 | 00036A21B65B0206 | 1.0 |
1765
+
1766
+ #### sum
1767
+
1768
+ - Takes a stream of results and does some wild things
1769
+ - Groups the results by all of their columns except criterion_id, start_date, end_date, and value_as_number, which puts the results of each person and type in one group when they share a source_value
1770
+ - Sums the value_as_number column within that grouping
1771
+ - Sets start_date to the earliest start_date in the group
1772
+ - Sets the end_date to the most recent end_date in the group
1773
+ - Sets criterion_id to 0 since there is no particular single row that the result refers to anymore
1774
+ - Takes one option, `group_by_person`, which is true or false. When it is true, `sum` puts all of the results of each person in one group, whatever their type and source_value. It gives one person result for each person:
1775
+ - person_id is the person
1776
+ - criterion_domain is person and criterion_table is the person table, as a cast to `person` gives them
1777
+ - criterion_id is 0
1778
+ - start_date is the earliest start_date and end_date is the most recent end_date of the person's results
1779
+ - value_as_number is the sum of the value_as_number column of the person's results
1780
+ - The other columns, such as source_value, are NULL
1781
+ - In a window table, `sum` gives one result for each person and window
1782
+
1783
+ ## Appendix A - Selection Operators
1784
+
1785
+ ConceptQL creates a vocabulary operator for each vocabulary in its vocabulary list (`config/vocabularies.csv`) and in its vocabulary database. This table lists the most common selection operators.
1786
+
1787
+ | Operator Name | Stream Type | Arguments | Returns |
1788
+ | ------------------- | -------------------- | ---------------------------------------- | -------------------------------------------------------------------- |
1789
+ | cpt | procedure_occurrence | 1 or more CPT codes | All clinical codes that match any of the CPT codes |
1790
+ | icd9 | condition_occurrence | 1 or more ICD-9-CM diagnosis codes | All clinical codes that match any of the ICD-9 codes |
1791
+ | icd9_procedure | procedure_occurrence | 1 or more ICD-9-CM procedure codes | All clinical codes that match any of the ICD-9 procedure codes |
1792
+ | icd10cm | condition_occurrence | 1 or more ICD-10-CM codes | All clinical codes that match any of the ICD-10-CM codes |
1793
+ | hcpcs | procedure_occurrence | 1 or more HCPCS codes | All clinical codes that match any of the HCPCS codes |
1794
+ | ms_drg | procedure_occurrence | 1 or more DRG codes | All clinical codes from 2007-10-01 that match any of the DRG codes |
1795
+ | cms_drg | procedure_occurrence | 1 or more DRG codes | All clinical codes before 2007-10-01 that match any of the DRG codes |
1796
+ | loinc | observation | 1 or more LOINC codes | All clinical codes that match any of the LOINC codes |
1797
+ | ndc | drug_exposure | 1 or more NDC codes | All clinical codes that match any of the NDC codes |
1798
+ | rxnorm | drug_exposure | 1 or more RxNorm codes | All clinical codes that match any of the RxNorm codes |
1799
+ | snomed | condition_occurrence | 1 or more SNOMED codes | All clinical codes that match any of the SNOMED codes |
1800
+ | gender | person | 1 or more of Male, Female, and Unknown | All patients whose gender matches any of the arguments |
1801
+ | race | person | 1 or more race names or race concept IDs | All patients whose race matches any of the arguments |
1802
+ | death | death | None | All death records |
1803
+ | information_periods | observation_period | None | All information periods |
1804
+ | date_range | person | A start date and an end date | One result for each patient, with the given dates |
1805
+ | hospitalization | condition_occurrence | None | All inpatient admissions |
1806
+ | hospice | condition_occurrence | None | All hospice admissions |
1807
+ | snf | condition_occurrence | None | All skilled nursing facility admissions |
1808
+
1809
+ hospitalization, hospice and snf select admissions. On gdm, they read collections and admission_details. On gdm_wide, they read observations, but their results still have criterion_table collections. Their results have criterion_domain condition_occurrence, because GDM has no visit or admission domain. primary_concept_id, source_value and source_vocabulary_id hold one code of the admission that has primary provenance. That code is usually a diagnosis, but it can be a procedure or a DRG. When the admission has no code with primary provenance, these three columns are NULL. Because the results have the condition_occurrence type, union combines them with the results of condition operators such as icd9 into one stream.
1810
+
1811
+ ## Appendix B - Algorithm Showcase
1812
+
1813
+ Here I take some algorithms from [OMOP's Health Outcomes of Interest](http://omop.org/HOI) and turn them into ConceptQL statements to give more examples. I truncated some of the sets of codes to help ensure the diagrams didn't get too large.
1814
+
1815
+ ### Acute Kidney Injury - Narrow Definition and diagnositc procedure
1816
+
1817
+ - ICD-9 of 584 (584.5 through 584.9)
1818
+ - AND
1819
+ - ICD-9 procedure codes of 39.95 or 54.98 within 60 days after diagnosis
1820
+ - AND NOT
1821
+ - A diagnostic code of chronic dialysis any time before initial diagnosis
1822
+ - V45.1, V56.0, V56.31, V56.32, V56.8
1823
+
1824
+ ```YAML
1825
+ ---
1826
+ - during
1827
+ - :left:
1828
+ - except
1829
+ - :left:
1830
+ - icd9
1831
+ - '584.5'
1832
+ - '584.6'
1833
+ - '584.7'
1834
+ - '584.8'
1835
+ - '584.9'
1836
+ :right:
1837
+ - after
1838
+ - :left:
1839
+ - icd9
1840
+ - '584.5'
1841
+ - '584.6'
1842
+ - '584.7'
1843
+ - '584.8'
1844
+ - '584.9'
1845
+ :right:
1846
+ - icd9
1847
+ - V45.1
1848
+ - V56.0
1849
+ - V56.31
1850
+ - V56.32
1851
+ - V56.8
1852
+ :right:
1853
+ - time_window
1854
+ - - icd9_procedure
1855
+ - '39.95'
1856
+ - '54.98'
1857
+ - :start: '0'
1858
+ :end: 60d
1859
+
1860
+ ```
1861
+
1862
+ ![](spec/58b1555ee5f9e9b2d2adfca6683c2c1e29498347e207e7406b1fe3265ba619b4.png)
1863
+
1864
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1865
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1866
+ | 153 | 102 | observations | condition_occurrence | 2010-01-29 | 2010-02-08 | 5849 |
1867
+
1868
+ ### Mortality after Myocardial Infarction #3
1869
+
1870
+ - Person Died
1871
+ - And Occurrence of an acute MI (ICD-9 410.x1, initial episode of care) prior to death
1872
+ - And either
1873
+ - MI diagnosis within 30 days prior to 410
1874
+ - MI therapy within 60 days after 410
1875
+
1876
+ ```YAML
1877
+ ---
1878
+ - during
1879
+ - :left:
1880
+ - before
1881
+ - :left:
1882
+ - icd9
1883
+ - '410.01'
1884
+ - '410.11'
1885
+ - '410.21'
1886
+ - '410.31'
1887
+ - '410.41'
1888
+ - '410.51'
1889
+ - '410.61'
1890
+ - '410.71'
1891
+ - '410.81'
1892
+ - '410.91'
1893
+ :right:
1894
+ - death
1895
+ :right:
1896
+ - union
1897
+ - - time_window
1898
+ - - union
1899
+ - - cpt
1900
+ - 0146T
1901
+ - '75898'
1902
+ - '82554'
1903
+ - '92980'
1904
+ - '93010'
1905
+ - '93233'
1906
+ - '93508'
1907
+ - '93540'
1908
+ - '93545'
1909
+ - - icd9_procedure
1910
+ - '00.24'
1911
+ - '36.02'
1912
+ - '89.53'
1913
+ - '89.57'
1914
+ - '89.69'
1915
+ - - loinc
1916
+ - 10839-9
1917
+ - 13969-1
1918
+ - 18843-3
1919
+ - 2154-3
1920
+ - 33204-9
1921
+ - 48425-3
1922
+ - 49259-5
1923
+ - 6597-9
1924
+ - 8634-8
1925
+ - :start: "-30d"
1926
+ :end: '0'
1927
+ - - time_window
1928
+ - - union
1929
+ - - cpt
1930
+ - 0146T
1931
+ - '75898'
1932
+ - '82554'
1933
+ - '92980'
1934
+ - '93010'
1935
+ - '93233'
1936
+ - - icd9_procedure
1937
+ - '00.24'
1938
+ - '36.02'
1939
+ - '89.53'
1940
+ - '89.57'
1941
+ - '89.69'
1942
+ - :start: ''
1943
+ :end: 60d
1944
+
1945
+ ```
1946
+
1947
+ ![](spec/f6b358f3006c217809558dc8b9ebe7060623ac72068a96fdf5b72240d8aed13a.png)
1948
+
1949
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1950
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1951
+ | 191 | 64752 | observations | condition_occurrence | 2008-03-11 | 2008-03-11 | 41071 |
1952
+ | 191 | 89727 | observations | condition_occurrence | 2008-12-27 | 2008-12-27 | 41071 |
1953
+
1954
+ ### GI Ulcer Hospitalization 2 (5000001002)
1955
+
1956
+ - Occurrence of GI Ulcer diagnostic code
1957
+ - Hospitalization at time of diagnostic code
1958
+ - At least one diagnostic procedure during same hospitalization
1959
+
1960
+ GDM has no visit table. In this statement, co_reported keeps the ulcer diagnoses and the diagnostic procedures that were reported in the same source record. Then during keeps those that occurred during a hospitalization. A shared source record is stricter than a shared hospitalization.
1961
+
1962
+ ```YAML
1963
+ ---
1964
+ - during
1965
+ - :left:
1966
+ - co_reported
1967
+ - - icd9
1968
+ - '531.00'
1969
+ - '531.40'
1970
+ - '531.90'
1971
+ - '532.40'
1972
+ - '532.90'
1973
+ - '533.40'
1974
+ - '533.90'
1975
+ - '534.40'
1976
+ - '534.90'
1977
+ - - union
1978
+ - - cpt
1979
+ - '0008T'
1980
+ - 3142F
1981
+ - '43205'
1982
+ - '43236'
1983
+ - '76975'
1984
+ - '91110'
1985
+ - '91111'
1986
+ - - hcpcs
1987
+ - B4081
1988
+ - B4082
1989
+ - - icd9_procedure
1990
+ - '42.22'
1991
+ - '42.23'
1992
+ - '44.13'
1993
+ - '45.13'
1994
+ - '52.21'
1995
+ - '97.01'
1996
+ - - loinc
1997
+ - 16125-7
1998
+ - 17780-8
1999
+ - 40820-3
2000
+ - 50320-1
2001
+ - 5177-1
2002
+ - 7901-2
2003
+ :right:
2004
+ - hospitalization
2005
+
2006
+ ```
2007
+
2008
+ ![GI Ulcer diagnoses and diagnostic procedures reported in the same source record during a hospitalization](spec/1f7c9cec3ac4993fb25c83a889d5a2ddf1739e8cf58fb922a59b141e2b4a7cf4.png)
2009
+
2010
+ `No Results found.`
2011
+
2012
+ ## Appendix C - Under Development
2013
+
2014
+ ConceptQL is not yet fully specified. These are modifications/enhancements that are under consideration. These ideas are most likely not completely refined and might actually represent changes that would fundamentally break ConceptQL.
2015
+
2016
+ ### Todo List
2017
+
2018
+ 1. Handle costs
2019
+ - How do we aggregate?
2020
+ 2. How do we count?
2021
+ 3. How do we handle missing values in streams?
2022
+ - For instance, missing DoB on patient?
2023
+ 4. What does it mean to pass a date range as an L stream?
2024
+ - I'm thinking we pass through no results
2025
+ - Turns out that, as implemented, a date_range is really a person_stream where the start and end dates represent the range (instead of the date of birth) so we're probably OK
2026
+ 5. How do we want to look up standard vocab concepts?
2027
+ - I think Marc’s approach is a bit heavy-handed
2028
+
2029
+ Some statements maybe very useful and it would be handy to reuse the bulk of the statement, but perhaps vary just a few things about it. ConceptQL could use variables to represent sub-expressions. A variable operator would be a place holder to say "some criteria set belongs here". That variable could be defined in another part of the criteria set and would be used in all places the variable operator appears. ConceptQL does not have a variable operator. The `label` option and the `recall` operator reuse a stream within one statement.
2030
+
2031
+ ### Future Work for Define and Recall
2032
+
2033
+ I'd like to make it so if a variable operator is used, but not defined, the algorithm is still valid, but will fail to run until a definition for all missing variables is provided.
2034
+
2035
+ But I don't have a good feel for:
2036
+
2037
+ - Whether we should have users name the variables, or auto-assign a name?
2038
+ - We risk name collisions if a algorithm includes a sub-algorithm with the same variable name
2039
+ - Probably need to name space all variables
2040
+ - How to prompt users to enter values for variables in a concept
2041
+ - If we have name-spaced variables and sub-algorithms needing values, how do we show this in a coherent manner to a user?
2042
+ - We'll need to do a pass through a algorithm to find all variables and prompt a user, then do another pass through the algorithm before attempting to execute it to ensure all variables have values
2043
+ - Do we throw an exception if not?
2044
+ - Do we require calling programs to invoke a check on the algorithm before generating the query?
2045
+ - Perhaps slot is a different operator from "define"
2046
+
2047
+ ### Considerations for Values
2048
+
2049
+ I'm considering defaulting each value_as\_\* column to some value.
2050
+
2051
+ - numeric => 1
2052
+ - concept_id => 0
2053
+ - Or maybe the concept_id of the main concept_id value from the row?
2054
+ - This would be confusing when pulling from the observation table
2055
+ - What's the "main" concept_id of a person?
2056
+ - Hm. This feels a bit less like a good idea now
2057
+ - string
2058
+ - source_value?
2059
+ - Boy, this one is even harder to default
2060
+
2061
+ ```YAML
2062
+ ---
2063
+ - icd9
2064
+ - '412'
2065
+
2066
+ ```
2067
+
2068
+ ![All MIs, defaulting value_as_number to 1, value_as_concept_id to the concept ID for 412, value_as_string to the source code](spec/f6b4fc31703cfb6327bbbd4614af8bb72da6d39fa3d53ada63a70157f2fad80e.png)
2069
+
2070
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
2071
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
2072
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
2073
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
2074
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
2075
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
2076
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
2077
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
2078
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
2079
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
2080
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
2081
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
2082
+
2083
+ ### `as` Option for Filter
2084
+
2085
+ The filter operator (see Set Operators) keeps left-hand results that also appear in the right-hand stream. An `as` option could temporarily cast the left-hand and right-hand streams to the type it names, and then keep the left-hand results whose cast results appear on both sides. That would be handy for keeping procedures that coincide with conditions without fully casting the streams. With `"as": "person"`, filter would behave like person_filter. ConceptQL does not have this option.
2086
+
2087
+ ### AS option for Except
2088
+
2089
+ Like the proposed `as` option for filter, add an `as` option to the Except operator. This would simplify some of the algorithms I've developed.
2090
+
2091
+ \[^AIA\]: J. Allen. Maintaining knowledge about temporal intervals. Communications of the ACM (1983) vol. 26 (11) pp. 832-843