conceptql 0.4.0 → 0.5.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +64 -0
  3. data/config/vocabularies.csv +87 -87
  4. data/doc/spec.md +1951 -0
  5. data/lib/conceptql/behaviors/utilizable.rb +0 -2
  6. data/lib/conceptql/cli.rb +1 -1
  7. data/lib/conceptql/data_model/base.rb +1 -167
  8. data/lib/conceptql/data_model.rb +20 -4
  9. data/lib/conceptql/database.rb +2 -0
  10. data/lib/conceptql/lexicon/lexicon_strategy.rb +23 -3
  11. data/lib/conceptql/lexicon.rb +9 -1
  12. data/lib/conceptql/operators/co_reported.rb +4 -31
  13. data/lib/conceptql/operators/hospice.rb +1 -2
  14. data/lib/conceptql/operators/hospitalization.rb +1 -2
  15. data/lib/conceptql/operators/information_periods.rb +1 -3
  16. data/lib/conceptql/operators/one_in_two_out.rb +1 -2
  17. data/lib/conceptql/operators/operator.rb +3 -11
  18. data/lib/conceptql/operators/provenance.rb +1 -2
  19. data/lib/conceptql/operators/race.rb +2 -6
  20. data/lib/conceptql/operators/read.rb +1 -100
  21. data/lib/conceptql/operators/snf.rb +1 -2
  22. data/lib/conceptql/operators/vocabulary.rb +15 -2
  23. data/lib/conceptql/paths.rb +6 -0
  24. data/lib/conceptql/query.rb +5 -2
  25. data/lib/conceptql/rdbms/duckdb.rb +0 -4
  26. data/lib/conceptql/rdbms/generic.rb +0 -4
  27. data/lib/conceptql/rdbms/postgres.rb +0 -4
  28. data/lib/conceptql/rdbms/spark.rb +0 -4
  29. data/lib/conceptql/utils.rb +0 -43
  30. data/lib/conceptql/version.rb +1 -1
  31. data/lib/conceptql/vocabularies/entry.rb +0 -20
  32. data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +2 -15
  33. metadata +16 -13
  34. data/lib/conceptql/data_model/omopv4_plus.rb +0 -12
  35. data/lib/conceptql/query_modifiers/omopv4_plus/admission_date_query_modifier.rb +0 -45
  36. data/lib/conceptql/query_modifiers/omopv4_plus/drug_query_modifier.rb +0 -80
  37. data/lib/conceptql/query_modifiers/omopv4_plus/pos_query_modifier.rb +0 -47
  38. data/lib/conceptql/query_modifiers/omopv4_plus/provenance_query_modifier.rb +0 -45
  39. data/lib/conceptql/query_modifiers/omopv4_plus/provider_query_modifier.rb +0 -26
  40. data/lib/conceptql/sql_formatters/formatter.rb +0 -25
  41. data/lib/conceptql/sql_formatters.rb +0 -22
  42. data/lib/conceptql/vocabularies/behaviors/costish.rb +0 -24
  43. data/lib/conceptql/vocabularies/behaviors/omopish.rb +0 -32
  44. data/schemas/omopv4_plus.yml +0 -729
  45. data/schemas/omopv4_plus_more_ignorables.tsv +0 -13
data/doc/spec.md ADDED
@@ -0,0 +1,1951 @@
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.
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
+ ## All Other Operators i.e. Mutation Operators
260
+
261
+ Virtually all other operators add, remove, filter, or otherwise alter streams of results. They are discussed in this section.
262
+
263
+ ## Set Operators
264
+
265
+ 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.
266
+
267
+ ### Union
268
+
269
+ - Takes any number of upstream operators and aggregates their streams
270
+ - Unions together streams with identical types
271
+ - Think of streams with the same type flowing together into a single stream
272
+ - Union does not remove duplicates: a result that arrives from two upstream operators can appear twice
273
+ - Streams with the different types flow along together concurrently without interacting
274
+ - 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
275
+
276
+ ```YAML
277
+ ---
278
+ - union
279
+ - - icd9
280
+ - '412'
281
+ - - icd9
282
+ - '486'
283
+
284
+ ```
285
+
286
+ ![Two streams of the same type (condition_occurrence) joined into a single stream](spec/79939bbb392a2152f20243f018abe44b681393ea0412e06b282dcaa4647476e1.png)
287
+
288
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
289
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
290
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
291
+ | 14 | 1727 | observations | condition_occurrence | 2008-09-12 | 2008-09-12 | 486 |
292
+ | 14 | 38866 | observations | condition_occurrence | 2009-04-05 | 2009-04-05 | 486 |
293
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
294
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
295
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
296
+ | 19 | 57559 | observations | condition_occurrence | 2008-02-02 | 2008-02-03 | 486 |
297
+ | 23 | 28373 | observations | condition_occurrence | 2009-08-18 | 2009-08-18 | 486 |
298
+ | 23 | 40349 | observations | condition_occurrence | 2009-12-20 | 2009-12-20 | 486 |
299
+ | 23 | 35166 | observations | condition_occurrence | 2010-02-17 | 2010-02-17 | 486 |
300
+
301
+ ```YAML
302
+ ---
303
+ - union
304
+ - - union
305
+ - - icd9
306
+ - '412'
307
+ - - icd9
308
+ - '486'
309
+ - - cpt
310
+ - '99231'
311
+
312
+ ```
313
+
314
+ ![Two streams of the same type (condition_occurrence) joined into a single stream, then a different stream (procedure_occurrence) flows concurrently](spec/4e59dcff780ca34a7e99631e5ad063c11dc7bfe19b14050523cb4f58387b5b81.png)
315
+
316
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
317
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
318
+ | 2 | 92691 | observations | procedure_occurrence | 2008-10-20 | 2008-10-20 | 99231 |
319
+ | 7 | 72498 | observations | procedure_occurrence | 2009-12-24 | 2010-01-02 | 99231 |
320
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
321
+ | 13 | 65589 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99231 |
322
+ | 14 | 1727 | observations | condition_occurrence | 2008-09-12 | 2008-09-12 | 486 |
323
+ | 14 | 38866 | observations | condition_occurrence | 2009-04-05 | 2009-04-05 | 486 |
324
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
325
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
326
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
327
+ | 17 | 53417 | observations | procedure_occurrence | 2008-04-05 | 2008-04-05 | 99231 |
328
+
329
+ ```YAML
330
+ ---
331
+ - union
332
+ - - icd9
333
+ - '412'
334
+ - - icd9
335
+ - '486'
336
+ - - cpt
337
+ - '99231'
338
+
339
+ ```
340
+
341
+ ![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)
342
+
343
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
344
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
345
+ | 2 | 92691 | observations | procedure_occurrence | 2008-10-20 | 2008-10-20 | 99231 |
346
+ | 7 | 72498 | observations | procedure_occurrence | 2009-12-24 | 2010-01-02 | 99231 |
347
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
348
+ | 13 | 65589 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99231 |
349
+ | 14 | 1727 | observations | condition_occurrence | 2008-09-12 | 2008-09-12 | 486 |
350
+ | 14 | 38866 | observations | condition_occurrence | 2009-04-05 | 2009-04-05 | 486 |
351
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
352
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
353
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
354
+ | 17 | 53417 | observations | procedure_occurrence | 2008-04-05 | 2008-04-05 | 99231 |
355
+
356
+ ### Filter
357
+
358
+ 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_domain, start_date, and end_date. 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.
359
+
360
+ ```YAML
361
+ ---
362
+ - filter
363
+ - :left:
364
+ - provenance
365
+ - - icd9
366
+ - '412'
367
+ - inpatient
368
+ :right:
369
+ - during
370
+ - :left:
371
+ - icd9
372
+ - '412'
373
+ :right:
374
+ - date_range
375
+ - :start: '2009-01-01'
376
+ :end: '2009-12-31'
377
+
378
+ ```
379
+
380
+ ![All MI Conditions from inpatient records that also occurred in 2009](spec/5180287351cc692b438dc0b13d0fc5d7ef068c716429ecea6a81f18443fe63e0.png)
381
+
382
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
383
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
384
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
385
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
386
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
387
+ | 161 | 963 | observations | condition_occurrence | 2009-10-25 | 2009-10-29 | 412 |
388
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
389
+
390
+ The `provenance` operator in this example passes along the results that come from a given kind of source record, here inpatient records.
391
+
392
+ ### Except
393
+
394
+ 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:
395
+
396
+ ```YAML
397
+ ---
398
+ - except
399
+ - :left:
400
+ - icd9
401
+ - '412'
402
+ :right:
403
+ - provenance
404
+ - - icd9
405
+ - '412'
406
+ - inpatient
407
+
408
+ ```
409
+
410
+ ![All Conditions that are MI unless they come from inpatient records](spec/4daefcd16621b00dc32e8e9e14bca04025f2e37539b319b22175bb8d80452a8a.png)
411
+
412
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
413
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
414
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
415
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
416
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
417
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
418
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
419
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
420
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
421
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
422
+ | 88 | 73803 | observations | condition_occurrence | 2009-05-30 | 2009-05-30 | 412 |
423
+ | 90 | 33940 | observations | condition_occurrence | 2010-05-26 | 2010-05-26 | 412 |
424
+
425
+ 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:
426
+
427
+ ```YAML
428
+ ---
429
+ - except
430
+ - :left:
431
+ - icd9
432
+ - '412'
433
+ :right:
434
+ - cpt
435
+ - '99214'
436
+
437
+ ```
438
+
439
+ ![All Conditions that are MI](spec/253845fe6162621af407ebd110296ff4f6d8a3f23ec75dfb4ea8cda30be71262.png)
440
+
441
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
442
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
443
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
444
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
445
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
446
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
447
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
448
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
449
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
450
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
451
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
452
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
453
+
454
+ And just to show how multiple streams behave:
455
+
456
+ ```YAML
457
+ ---
458
+ - except
459
+ - :left:
460
+ - union
461
+ - - icd9
462
+ - '412'
463
+ - - gender
464
+ - Male
465
+ - - cpt
466
+ - '99214'
467
+ :right:
468
+ - union
469
+ - - provenance
470
+ - - icd9
471
+ - '412'
472
+ - inpatient
473
+ - - race
474
+ - White
475
+
476
+ ```
477
+
478
+ ![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)
479
+
480
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
481
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ---------------- |
482
+ | 2 | 58127 | observations | procedure_occurrence | 2009-07-03 | 2009-07-03 | 99214 |
483
+ | 2 | 83339 | observations | procedure_occurrence | 2009-12-02 | 2009-12-02 | 99214 |
484
+ | 4 | 4 | patients | person | 1941-06-01 | 1941-06-01 | 00021CA6FF03E670 |
485
+ | 5 | 38313 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99214 |
486
+ | 5 | 76788 | observations | procedure_occurrence | 2009-07-04 | 2009-07-04 | 99214 |
487
+ | 5 | 63847 | observations | procedure_occurrence | 2009-12-21 | 2009-12-21 | 99214 |
488
+ | 6 | 6 | patients | person | 1943-10-01 | 1943-10-01 | 0002DAE1C81CC70D |
489
+ | 6 | 48992 | observations | procedure_occurrence | 2009-09-12 | 2009-09-12 | 99214 |
490
+ | 6 | 76651 | observations | procedure_occurrence | 2010-02-23 | 2010-02-23 | 99214 |
491
+ | 7 | 63797 | observations | procedure_occurrence | 2008-08-13 | 2008-08-13 | 99214 |
492
+
493
+ ### Discussion about Set Operators
494
+
495
+ #### Union Operators
496
+
497
+ ##### Q. Why should we allow two different types of streams to continue downstream concurrently?
498
+
499
+ - This feature lets us do interesting things, like find the first occurrence of either an MI or Death as in the example below
500
+ - Throw in a few more criteria and you could find the first occurrence of all censor events for each patient
501
+
502
+ ```YAML
503
+ ---
504
+ - first
505
+ - - union
506
+ - - icd9
507
+ - '412'
508
+ - - death
509
+
510
+ ```
511
+
512
+ ![First occurrence of either MI or Death for each patient](spec/3bf7022e9a06d0b25d7e66f30508c7ac68453b51bbede666c94d7988f681bcad.png)
513
+
514
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
515
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
516
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
517
+ | 16 | 1 | deaths | death | 2010-12-01 | 2010-12-01 | |
518
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
519
+ | 26 | 2 | deaths | death | 2009-07-01 | 2009-07-01 | |
520
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
521
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
522
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
523
+ | 65 | 3 | deaths | death | 2009-01-01 | 2009-01-01 | |
524
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
525
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
526
+
527
+ ## Co-reported Events
528
+
529
+ ### co_reported
530
+
531
+ `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`).
532
+
533
+ 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.
534
+
535
+ 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.
536
+
537
+ ```YAML
538
+ ---
539
+ - co_reported
540
+ - - icd9
541
+ - '250.00'
542
+ - - cpt
543
+ - '99214'
544
+
545
+ ```
546
+
547
+ ![Yields the diabetes diagnoses and the office-visit procedures that were reported in the same source record](spec/dfc63bee1d9f447450c764cc505def315209de4c752ce49f4cb3779dd28b1001.png)
548
+
549
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
550
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
551
+ | 25 | 93217 | observations | condition_occurrence | 2008-09-18 | 2008-09-18 | 25000 |
552
+ | 25 | 93220 | observations | procedure_occurrence | 2008-09-18 | 2008-09-18 | 99214 |
553
+ | 31 | 87695 | observations | condition_occurrence | 2008-06-17 | 2008-06-17 | 25000 |
554
+ | 31 | 87696 | observations | procedure_occurrence | 2008-06-17 | 2008-06-17 | 99214 |
555
+ | 40 | 81973 | observations | condition_occurrence | 2009-05-01 | 2009-05-01 | 25000 |
556
+ | 40 | 81993 | observations | procedure_occurrence | 2009-05-01 | 2009-05-01 | 99214 |
557
+ | 44 | 44702 | observations | condition_occurrence | 2008-01-10 | 2008-01-10 | 25000 |
558
+ | 44 | 44715 | observations | procedure_occurrence | 2008-01-10 | 2008-01-10 | 99214 |
559
+ | 58 | 35093 | observations | condition_occurrence | 2009-06-15 | 2009-06-15 | 25000 |
560
+ | 58 | 35095 | observations | procedure_occurrence | 2009-06-15 | 2009-06-15 | 99214 |
561
+
562
+ ## Time-oriented Operators
563
+
564
+ 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.
565
+
566
+ 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.
567
+
568
+ 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.
569
+
570
+ 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.
571
+
572
+ ### Relative Temporal Operators
573
+
574
+ 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.
575
+
576
+ #### occurrence
577
+
578
+ - Takes a two arguments: the stream to select from and an integer argument
579
+ - For the integer argument
580
+ - Positive numbers mean 1st, 2nd, 3rd occurrence in chronological order
581
+ - e.g. 1 => first
582
+ - e.g. 4 => fourth
583
+ - Negative numbers mean 1st, 2nd, 3rd occurrence in reverse chronological order
584
+ - e.g. -1 => last
585
+ - e.g. -4 => fourth from last
586
+ - 0 is undefined?
587
+
588
+ ```YAML
589
+ ---
590
+ - occurrence
591
+ - 2
592
+ - - icd9
593
+ - '412'
594
+
595
+ ```
596
+
597
+ ![For each patient, select the Condition that represents the second occurrence of an MI](spec/267385650ec29b77f30e5da3d7103c5ce6982947722d939dfc3ea8e5d030ce59.png)
598
+
599
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
600
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
601
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
602
+ | 88 | 73803 | observations | condition_occurrence | 2009-05-30 | 2009-05-30 | 412 |
603
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
604
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
605
+ | 187 | 87583 | observations | condition_occurrence | 2010-12-26 | 2010-12-26 | 412 |
606
+ | 212 | 34052 | observations | condition_occurrence | 2009-10-07 | 2009-10-07 | 412 |
607
+ | 213 | 15005 | observations | condition_occurrence | 2010-02-07 | 2010-02-07 | 412 |
608
+ | 214 | 83257 | observations | condition_occurrence | 2009-11-12 | 2009-11-12 | 412 |
609
+ | 220 | 20660 | observations | condition_occurrence | 2009-10-31 | 2009-10-31 | 412 |
610
+ | 223 | 39893 | observations | condition_occurrence | 2008-10-31 | 2008-10-31 | 412 |
611
+
612
+ #### first
613
+
614
+ - Operator that is shorthand for writing "occurrence: 1"
615
+
616
+ ```YAML
617
+ ---
618
+ - first
619
+ - - icd9
620
+ - '412'
621
+
622
+ ```
623
+
624
+ ![For each patient, select the Condition that represents the first occurrence of an MI](spec/04491942fcbd741982514f9eb12aeecf3d54b5b69a2b50c8331f7700169d5521.png)
625
+
626
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
627
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
628
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
629
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
630
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
631
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
632
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
633
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
634
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
635
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
636
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
637
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
638
+
639
+ #### last
640
+
641
+ - Operator that is just shorthand for writing "occurrence: -1"
642
+
643
+ ```YAML
644
+ ---
645
+ - last
646
+ - - icd9
647
+ - '412'
648
+
649
+ ```
650
+
651
+ ![For each patient, select the Condition that represents the last occurrence of an MI](spec/ebacbd092e3d1a3c7b745a381e51e8ff9d63a21db23a16940193e18e57bc866f.png)
652
+
653
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
654
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
655
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
656
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
657
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
658
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
659
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
660
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
661
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
662
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
663
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
664
+ | 88 | 73803 | observations | condition_occurrence | 2009-05-30 | 2009-05-30 | 412 |
665
+
666
+ ### Date Literals
667
+
668
+ For situations where we need to represent pre-defined date ranges, we can use "date literal" operators.
669
+
670
+ #### date_range
671
+
672
+ - Takes a hash with two elements: { start: \<date-format>, end: \<date-format> }
673
+ - Creates an inclusive, continuous range of dates defined by a start and end date
674
+
675
+ #### day
676
+
677
+ - Takes a single argument: \<date-format>
678
+ - Represents a single day
679
+ - Shorthand for creating a date range that starts and ends on the same date
680
+ - *Not yet implemented*
681
+
682
+ #### What is \<date-format>?
683
+
684
+ Dates follow these formats:
685
+
686
+ - "YYYY-MM-DD"
687
+ - Four-digit year, two-digit month with leading 0s, two-digit day with leading 0s
688
+ - "START"
689
+ - Represents the first date of information available from the data source
690
+ - "END"
691
+ - Represents the last date of information available from the data source.
692
+
693
+ ### Temporal Comparison Operators
694
+
695
+ 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.
696
+
697
+ 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.
698
+
699
+ 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)
700
+
701
+ Our implementation of this algebra is originally going to be as strict as listed here, meaning that:
702
+
703
+ - Before/After
704
+ - There must be a minimum 1-day gap between date ranges
705
+ - Meets/Met-by
706
+ - Only if the first date range starts/ends a day before the next date range ends/starts
707
+ - Started-by/Starts
708
+ - The start dates of the two ranges must be equal and the end dates must not be
709
+ - Finished-by/Finishes
710
+ - The end dates of the two ranges must be equal and the start dates must not be
711
+ - Contains/During
712
+ - The start/end dates of the two ranges must be different from each other
713
+ - Overlaps/Overlapped-by
714
+ - The start date of one range and the end date of the other range must be outside the overlapping range
715
+ - Temporally coincides
716
+ - Start dates must be equal, end dates must be equal
717
+
718
+ Ryan's Sidebar on These Definitions:
719
+
720
+ > 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.
721
+
722
+ > 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
723
+
724
+ When comparing results in L against a date range, results in L continue downstream only if they pass the comparison.
725
+
726
+ ```YAML
727
+ ---
728
+ - during
729
+ - :left:
730
+ - icd9
731
+ - '412'
732
+ :right:
733
+ - date_range
734
+ - :start: '2010-01-01'
735
+ :end: '2010-12-31'
736
+
737
+ ```
738
+
739
+ ![All MIs for the year 2010](spec/ba90ef705f7be91c53c5eb4a81a439fa0f7c48532214bd3db3cc5c069160543e.png)
740
+
741
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
742
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
743
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
744
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
745
+ | 90 | 33940 | observations | condition_occurrence | 2010-05-26 | 2010-05-26 | 412 |
746
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
747
+ | 110 | 62592 | observations | condition_occurrence | 2010-06-27 | 2010-06-27 | 412 |
748
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
749
+ | 187 | 87583 | observations | condition_occurrence | 2010-12-26 | 2010-12-26 | 412 |
750
+ | 213 | 15005 | observations | condition_occurrence | 2010-02-07 | 2010-02-07 | 412 |
751
+ | 217 | 56467 | observations | condition_occurrence | 2010-06-22 | 2010-06-23 | 412 |
752
+ | 239 | 49718 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
753
+
754
+ 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.
755
+
756
+ - If a person has results in L or R stream, but not in both, none of their results continue downstream
757
+ - On a per person basis, the temporal operator joins all results in the L stream to all results in the R stream
758
+ - Any results in the L stream that meet the temporal comparison against any results in the R stream continue downstream
759
+
760
+ ```YAML
761
+ ---
762
+ - during
763
+ - :left:
764
+ - icd9
765
+ - '412'
766
+ :right:
767
+ - hospitalization
768
+
769
+ ```
770
+
771
+ ![All MIs that occurred during a hospitalization](spec/9524ac806e9ac1dd97132bc6b66a793536b9d8fa31d7ded9f54fba018e6288aa.png)
772
+
773
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
774
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
775
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
776
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
777
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
778
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
779
+ | 161 | 963 | observations | condition_occurrence | 2009-10-25 | 2009-10-29 | 412 |
780
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
781
+ | 230 | 523 | observations | condition_occurrence | 2008-03-14 | 2008-03-21 | 412 |
782
+
783
+ #### Edge behaviors
784
+
785
+ For 11 of the 13 temporal operators, comparison of results is straight-forward. However, the before/after operators have a slight twist.
786
+
787
+ 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:
788
+
789
+ - When comparing L **before** R, the temporal operator compares L against the **LAST** occurrence of R per person
790
+ - When comparing L **after** R, the temporal operator compares L against the **FIRST** occurrence of R per person
791
+
792
+ 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.
793
+
794
+ ```YAML
795
+ ---
796
+ - before
797
+ - :left:
798
+ - icd9
799
+ - '412'
800
+ :right:
801
+ - icd9
802
+ - '486'
803
+
804
+ ```
805
+
806
+ ![All MIs that occurred before a patient's last case of pneumonia (486)](spec/fc3b7c03816cc84fd92931b2cd0eb279e3fba5851e1ce15d4f084a1a3f04feba.png)
807
+
808
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
809
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
810
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
811
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
812
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
813
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
814
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
815
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
816
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
817
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
818
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
819
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
820
+
821
+ 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
822
+
823
+ ```YAML
824
+ ---
825
+ - before
826
+ - :left:
827
+ - icd9
828
+ - '412'
829
+ :right:
830
+ - first
831
+ - - icd9
832
+ - '486'
833
+
834
+ ```
835
+
836
+ ![All MIs that occurred before a patient's first case of pneumonia (486)](spec/bd129d7a0fc5b3ee6610f990e4d8a6c604825e11ed04123c202595a08f917438.png)
837
+
838
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
839
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
840
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
841
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
842
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
843
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
844
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
845
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
846
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
847
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
848
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
849
+ | 213 | 55557 | observations | condition_occurrence | 2009-06-07 | 2009-06-07 | 412 |
850
+
851
+ ## Time Windows
852
+
853
+ 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.
854
+
855
+ ### time_window
856
+
857
+ - Takes 2 arguments
858
+ - First argument is the stream on which to operate
859
+ - Second argument is a hash with two keys: [:start, :end] each with a value in the following format: "(-?\\d+[dmy])+"
860
+ - Both start and end must be defined, even if you are only adjusting one of the dates
861
+ - Some examples
862
+ - 30d => 30 days
863
+ - 20 => 20 days
864
+ - d => 1 day
865
+ - 1y => 1 year
866
+ - -1m => -1 month
867
+ - 10d3m => 3 months and 10 days
868
+ - -2y10m-3d => -2 years, +10 months, -3 days
869
+ - The start or end value can also be '', '0', or nil
870
+ - This will leave the date unaffected
871
+ - The start or end value can also be the string 'start' or 'end'
872
+ - 'start' represents the start_date for each result
873
+ - 'end' represents the end_date for each result
874
+ - See the example below
875
+
876
+ ```YAML
877
+ ---
878
+ - during
879
+ - :left:
880
+ - icd9
881
+ - '486'
882
+ :right:
883
+ - time_window
884
+ - - icd9
885
+ - '412'
886
+ - :start: "-30d"
887
+ :end: 30d
888
+
889
+ ```
890
+
891
+ ![All Diagnoses of Pneumonia (ICD-9 486) within 30 days of an MI](spec/c01589dc9f64d7ad8ef808d7471d19abec29f06771b06da7f3f884f3ea2fdc43.png)
892
+
893
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
894
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
895
+ | 17 | 31480 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 |
896
+ | 38 | 47462 | observations | condition_occurrence | 2010-02-24 | 2010-02-24 | 486 |
897
+ | 38 | 47463 | observations | condition_occurrence | 2010-02-24 | 2010-02-24 | 486 |
898
+ | 38 | 47464 | observations | condition_occurrence | 2010-02-24 | 2010-02-24 | 486 |
899
+ | 60 | 988 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 486 |
900
+ | 77 | 62783 | observations | condition_occurrence | 2010-10-17 | 2010-10-17 | 486 |
901
+ | 110 | 81700 | observations | condition_occurrence | 2010-06-18 | 2010-06-19 | 486 |
902
+ | 172 | 87537 | observations | condition_occurrence | 2009-01-25 | 2009-01-25 | 486 |
903
+ | 214 | 59068 | observations | condition_occurrence | 2009-10-23 | 2009-10-23 | 486 |
904
+ | 220 | 47310 | observations | condition_occurrence | 2008-11-26 | 2008-11-26 | 486 |
905
+
906
+ ```YAML
907
+ ---
908
+ - time_window
909
+ - - icd9
910
+ - '412'
911
+ - :start: "-2y"
912
+ :end: "-2y"
913
+
914
+ ```
915
+
916
+ ![Shift the window for all MIs back by 2 years](spec/15268bc45993d3f57ccf915877f91e48bdee684f24faa614249915833cac4af9.png)
917
+
918
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
919
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
920
+ | 11 | 82614 | observations | condition_occurrence | 2006-10-29 | 2006-10-29 | 412 |
921
+ | 17 | 27269 | observations | condition_occurrence | 2006-08-25 | 2006-08-25 | 412 |
922
+ | 17 | 46165 | observations | condition_occurrence | 2007-04-30 | 2007-04-30 | 412 |
923
+ | 38 | 40556 | observations | condition_occurrence | 2008-02-12 | 2008-02-12 | 412 |
924
+ | 54 | 74606 | observations | condition_occurrence | 2006-06-05 | 2006-06-05 | 412 |
925
+ | 60 | 986 | observations | condition_occurrence | 2007-07-19 | 2007-07-22 | 412 |
926
+ | 66 | 16171 | observations | condition_occurrence | 2007-07-25 | 2007-07-25 | 412 |
927
+ | 73 | 52843 | observations | condition_occurrence | 2006-11-16 | 2006-11-16 | 412 |
928
+ | 77 | 58165 | observations | condition_occurrence | 2008-10-06 | 2008-10-06 | 412 |
929
+ | 81 | 1405 | observations | condition_occurrence | 2007-01-28 | 2007-01-30 | 412 |
930
+
931
+ ```YAML
932
+ ---
933
+ - time_window
934
+ - - icd9
935
+ - '412'
936
+ - :start: "-2m-2d"
937
+ :end: 3d1y
938
+
939
+ ```
940
+
941
+ ![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)
942
+
943
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
944
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
945
+ | 11 | 82614 | observations | condition_occurrence | 2008-08-27 | 2009-11-01 | 412 |
946
+ | 17 | 27269 | observations | condition_occurrence | 2008-06-23 | 2009-08-28 | 412 |
947
+ | 17 | 46165 | observations | condition_occurrence | 2009-02-26 | 2010-05-03 | 412 |
948
+ | 38 | 40556 | observations | condition_occurrence | 2009-12-10 | 2011-02-15 | 412 |
949
+ | 54 | 74606 | observations | condition_occurrence | 2008-04-03 | 2009-06-08 | 412 |
950
+ | 60 | 986 | observations | condition_occurrence | 2009-05-17 | 2010-07-25 | 412 |
951
+ | 66 | 16171 | observations | condition_occurrence | 2009-05-23 | 2010-07-28 | 412 |
952
+ | 73 | 52843 | observations | condition_occurrence | 2008-09-14 | 2009-11-19 | 412 |
953
+ | 77 | 58165 | observations | condition_occurrence | 2010-08-04 | 2011-10-09 | 412 |
954
+ | 81 | 1405 | observations | condition_occurrence | 2008-11-26 | 2010-02-02 | 412 |
955
+
956
+ ```YAML
957
+ ---
958
+ - time_window
959
+ - - hospitalization
960
+ - :start: ''
961
+ :end: start
962
+
963
+ ```
964
+
965
+ ![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)
966
+
967
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
968
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
969
+ | 1 | 95 | collections | condition_occurrence | 2010-03-12 | 2010-03-12 | V4501 |
970
+ | 2 | 37 | collections | condition_occurrence | 2009-04-12 | 2009-04-12 | 2724 |
971
+ | 2 | 96 | collections | condition_occurrence | 2009-08-31 | 2009-08-31 | 7092 |
972
+ | 2 | 12 | collections | condition_occurrence | 2009-09-17 | 2009-09-17 | 32723 |
973
+ | 2 | 43 | collections | condition_occurrence | 2010-06-26 | 2010-06-26 | 7856 |
974
+ | 14 | 151 | collections | condition_occurrence | 2008-09-12 | 2008-09-12 | 2724 |
975
+ | 17 | 93 | collections | condition_occurrence | 2008-09-19 | 2008-09-19 | 33811 |
976
+ | 17 | 83 | collections | condition_occurrence | 2010-05-22 | 2010-05-22 | 5845 |
977
+ | 17 | 122 | collections | condition_occurrence | 2010-06-02 | 2010-06-02 | 4280 |
978
+ | 17 | 124 | collections | condition_occurrence | 2010-06-16 | 2010-06-16 | V4365 |
979
+
980
+ ```YAML
981
+ ---
982
+ - time_window
983
+ - - icd9
984
+ - '412'
985
+ - :start: end
986
+ :end: start
987
+
988
+ ```
989
+
990
+ ![Nonsensical, but allowed: swap the start_date and end_date for a range](spec/6705af65d728c0d5c50d6c4d46253017a91eb459dbb1f40f92449f05d5562f4a.png)
991
+
992
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
993
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
994
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
995
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
996
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
997
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
998
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
999
+ | 60 | 986 | observations | condition_occurrence | 2009-07-22 | 2009-07-19 | 412 |
1000
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1001
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1002
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1003
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-30 | 2009-01-28 | 412 |
1004
+
1005
+ ### Temporal Operators and Person Streams
1006
+
1007
+ 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:
1008
+
1009
+ ```YAML
1010
+ ---
1011
+ - after
1012
+ - :left:
1013
+ - icd9
1014
+ - '412'
1015
+ :right:
1016
+ - time_window
1017
+ - - gender
1018
+ - Male
1019
+ - :start: 50y
1020
+ :end: 50y
1021
+
1022
+ ```
1023
+
1024
+ ![All MIs that occurred after a male patient's 50th birthday](spec/68fac940a32c7e40caacf8e560c61da552d57633d015ba98f2e98ec040a00c5b.png)
1025
+
1026
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1027
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1028
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1029
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1030
+ | 93 | 82295 | observations | condition_occurrence | 2009-11-08 | 2009-11-08 | 412 |
1031
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
1032
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
1033
+ | 110 | 62592 | observations | condition_occurrence | 2010-06-27 | 2010-06-27 | 412 |
1034
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
1035
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
1036
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
1037
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
1038
+
1039
+ ## Type Conversion
1040
+
1041
+ 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.
1042
+
1043
+ 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).
1044
+
1045
+ ### Casting to person
1046
+
1047
+ - Useful if we're just checking for the presence of a condition for a person
1048
+ - E.g. We want to know *if* a person has an old MI, not when an MI or how many MIs occurred
1049
+
1050
+ ```YAML
1051
+ ---
1052
+ - person
1053
+ - - icd9
1054
+ - '412'
1055
+
1056
+ ```
1057
+
1058
+ ![All People Who Had an MI](spec/8ed478d8c81a58a202d0c51348fca246df206fc24a0567b163d4e0bdab56ca46.png)
1059
+
1060
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1061
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1062
+ | 11 | 11 | patients | person | 1934-02-01 | 1934-02-01 | 000489E7EAAD463F |
1063
+ | 17 | 17 | patients | person | 1919-09-01 | 1919-09-01 | 0007F12A492FD25D |
1064
+ | 38 | 38 | patients | person | 1922-12-01 | 1922-12-01 | 001731EB127233DA |
1065
+ | 54 | 54 | patients | person | 1931-02-01 | 1931-02-01 | 001CAFF084B21E14 |
1066
+ | 60 | 60 | patients | person | 1925-07-01 | 1925-07-01 | 001EA2F4DB30F105 |
1067
+ | 66 | 66 | patients | person | 1920-04-01 | 1920-04-01 | 0021B3C854C968C8 |
1068
+ | 73 | 73 | patients | person | 1925-09-01 | 1925-09-01 | 00237322613CFC3C |
1069
+ | 77 | 77 | patients | person | 1929-03-01 | 1929-03-01 | 00244B6D9AB50F9B |
1070
+ | 81 | 81 | patients | person | 1940-06-01 | 1940-06-01 | 0024E5A7B7272E75 |
1071
+ | 88 | 88 | patients | person | 1922-05-01 | 1922-05-01 | 00291F39917544B1 |
1072
+
1073
+ ### Casting to death
1074
+
1075
+ - Replaces each result with the death record of the result's person
1076
+ - A person with no death record yields no results
1077
+
1078
+ ```YAML
1079
+ ---
1080
+ - death
1081
+ - - icd9
1082
+ - '412'
1083
+
1084
+ ```
1085
+
1086
+ ![The Deaths of All People Who Had an MI](spec/8b20c1a598c4d0f7cae613f707f5eaa90da3293d7ac24bb5b12df278a8e69d57.png)
1087
+
1088
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1089
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ------------ |
1090
+ | 190 | 7 | deaths | death | 2009-09-01 | 2009-09-01 | |
1091
+
1092
+ ### Casting Loses All Original Information
1093
+
1094
+ 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.
1095
+
1096
+ ### Casting as a way to fetch all rows
1097
+
1098
+ 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:
1099
+
1100
+ ```YAML
1101
+ ---
1102
+ - death
1103
+
1104
+ ```
1105
+
1106
+ ![All death results in the database](spec/e281558c677a04df02e53dcc7fd6497a0d831e4037560cd13942b67ab10f98f4.png)
1107
+
1108
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1109
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ------------ |
1110
+ | 16 | 1 | deaths | death | 2010-12-01 | 2010-12-01 | |
1111
+ | 26 | 2 | deaths | death | 2009-07-01 | 2009-07-01 | |
1112
+ | 65 | 3 | deaths | death | 2009-01-01 | 2009-01-01 | |
1113
+ | 119 | 4 | deaths | death | 2008-09-01 | 2008-09-01 | |
1114
+ | 166 | 5 | deaths | death | 2008-07-01 | 2008-07-01 | |
1115
+ | 181 | 6 | deaths | death | 2010-07-01 | 2010-07-01 | |
1116
+ | 190 | 7 | deaths | death | 2009-09-01 | 2009-09-01 | |
1117
+ | 191 | 8 | deaths | death | 2009-09-01 | 2009-09-01 | |
1118
+ | 201 | 9 | deaths | death | 2008-05-01 | 2008-05-01 | |
1119
+ | 215 | 10 | deaths | death | 2008-01-01 | 2008-01-01 | |
1120
+
1121
+ This comes in handy for situations like these:
1122
+
1123
+ ```YAML
1124
+ ---
1125
+ - person_filter
1126
+ - :left:
1127
+ - gender
1128
+ - Male
1129
+ :right:
1130
+ - death
1131
+
1132
+ ```
1133
+
1134
+ ![All Male patients who died](spec/bb369188911f61bfd851f575d6e8dcc96f1328f4b58721e3fb32932985db1025.png)
1135
+
1136
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1137
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1138
+ | 16 | 16 | patients | person | 1934-01-01 | 1934-01-01 | 0007E57CC13CE880 |
1139
+ | 65 | 65 | patients | person | 1936-01-01 | 1936-01-01 | 0021940CF994C374 |
1140
+ | 181 | 181 | patients | person | 1939-11-01 | 1939-11-01 | 005E0AB5172E715F |
1141
+ | 190 | 190 | patients | person | 1939-07-01 | 1939-07-01 | 0060C263DA4EBBAF |
1142
+ | 191 | 191 | patients | person | 1928-12-01 | 1928-12-01 | 0061141AB18FFA96 |
1143
+ | 201 | 201 | patients | person | 1949-06-01 | 1949-06-01 | 00653DC86A0E30D8 |
1144
+
1145
+ ## Filtering by People
1146
+
1147
+ 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.
1148
+
1149
+ 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.
1150
+
1151
+ ```YAML
1152
+ ---
1153
+ - person_filter
1154
+ - :left:
1155
+ - icd9
1156
+ - '412'
1157
+ :right:
1158
+ - gender
1159
+ - Male
1160
+
1161
+ ```
1162
+
1163
+ ![All MI Conditions for people who are male](spec/0360fc0c2b7a88fd82ffc4d13387b76766649d5d42aa8f9349bbf60a81ac6119.png)
1164
+
1165
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1166
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1167
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1168
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1169
+ | 93 | 82295 | observations | condition_occurrence | 2009-11-08 | 2009-11-08 | 412 |
1170
+ | 96 | 20748 | observations | condition_occurrence | 2008-09-21 | 2008-09-24 | 412 |
1171
+ | 109 | 89334 | observations | condition_occurrence | 2010-07-06 | 2010-07-06 | 412 |
1172
+ | 110 | 62592 | observations | condition_occurrence | 2010-06-27 | 2010-06-27 | 412 |
1173
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
1174
+ | 149 | 28518 | observations | condition_occurrence | 2008-04-07 | 2008-04-07 | 412 |
1175
+ | 152 | 82988 | observations | condition_occurrence | 2008-08-16 | 2008-08-16 | 412 |
1176
+ | 152 | 84298 | observations | condition_occurrence | 2010-11-22 | 2010-11-22 | 412 |
1177
+
1178
+ 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:
1179
+
1180
+ ```YAML
1181
+ ---
1182
+ - person_filter
1183
+ - :left:
1184
+ - icd9
1185
+ - '412'
1186
+ :right:
1187
+ - cpt
1188
+ - '99214'
1189
+
1190
+ ```
1191
+
1192
+ ![All MI Conditions for people who had an office visit at some point in the data](spec/26fd52b5ac55438dd9f81b4a3f3913f1058c9f225c8a5e129007c1e9413d5881.png)
1193
+
1194
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1195
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1196
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1197
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1198
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1199
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1200
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1201
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1202
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1203
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1204
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1205
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1206
+
1207
+ ```YAML
1208
+ ---
1209
+ - person_filter
1210
+ - :left:
1211
+ - icd9
1212
+ - '412'
1213
+ :right:
1214
+ - person
1215
+ - - cpt
1216
+ - '99214'
1217
+
1218
+ ```
1219
+
1220
+ ![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)
1221
+
1222
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1223
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1224
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1225
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1226
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1227
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1228
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1229
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1230
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1231
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1232
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1233
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1234
+
1235
+ ```YAML
1236
+ ---
1237
+ - person_filter
1238
+ - :left:
1239
+ - icd9
1240
+ - '412'
1241
+ :right:
1242
+ - union
1243
+ - - cpt
1244
+ - '99214'
1245
+ - - gender
1246
+ - Male
1247
+
1248
+ ```
1249
+
1250
+ ![All MI Conditions for people who are Male OR had an office visit at some point in the data](spec/5d331d74c460d75814b2d3138a9b7d90b5ddb2dcd85e1f5f260d183745fc3a1e.png)
1251
+
1252
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1253
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1254
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1255
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1256
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1257
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1258
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1259
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1260
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1261
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1262
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1263
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1264
+
1265
+ And don't forget the left-hand side can have multiple types of streams:
1266
+
1267
+ ```YAML
1268
+ ---
1269
+ - person_filter
1270
+ - :left:
1271
+ - union
1272
+ - - icd9
1273
+ - '412'
1274
+ - - cpt
1275
+ - '99214'
1276
+ :right:
1277
+ - gender
1278
+ - Male
1279
+
1280
+ ```
1281
+
1282
+ ![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)
1283
+
1284
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1285
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1286
+ | 2 | 58127 | observations | procedure_occurrence | 2009-07-03 | 2009-07-03 | 99214 |
1287
+ | 2 | 83339 | observations | procedure_occurrence | 2009-12-02 | 2009-12-02 | 99214 |
1288
+ | 5 | 38313 | observations | procedure_occurrence | 2008-12-14 | 2008-12-14 | 99214 |
1289
+ | 5 | 76788 | observations | procedure_occurrence | 2009-07-04 | 2009-07-04 | 99214 |
1290
+ | 5 | 63847 | observations | procedure_occurrence | 2009-12-21 | 2009-12-21 | 99214 |
1291
+ | 6 | 48992 | observations | procedure_occurrence | 2009-09-12 | 2009-09-12 | 99214 |
1292
+ | 6 | 76651 | observations | procedure_occurrence | 2010-02-23 | 2010-02-23 | 99214 |
1293
+ | 7 | 63797 | observations | procedure_occurrence | 2008-08-13 | 2008-08-13 | 99214 |
1294
+ | 7 | 59555 | observations | procedure_occurrence | 2008-09-21 | 2008-09-21 | 99214 |
1295
+ | 7 | 71597 | observations | procedure_occurrence | 2008-09-22 | 2008-09-22 | 99214 |
1296
+
1297
+ ## Sub-algorithms within a Larger Algorithm
1298
+
1299
+ 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.
1300
+
1301
+ ### `label` option
1302
+
1303
+ 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.
1304
+
1305
+ ### `recall` operator
1306
+
1307
+ - Takes 1 argument
1308
+ - The "label" of an operator from which you'd like to pull the exact same set of results
1309
+
1310
+ A stream must have a `label` before `recall` can use it.
1311
+
1312
+ ```YAML
1313
+ ---
1314
+ - first
1315
+ - - union
1316
+ - - provenance
1317
+ - - icd9
1318
+ - '412'
1319
+ - :label: Heart Attack
1320
+ - inpatient
1321
+ - - before
1322
+ - :left:
1323
+ - provenance
1324
+ - - recall
1325
+ - Heart Attack
1326
+ - outpatient
1327
+ - :label: Outpatient Heart Attack
1328
+ :right:
1329
+ - time_window
1330
+ - - recall
1331
+ - Outpatient Heart Attack
1332
+ - :start: "-30d"
1333
+ :end: '0'
1334
+ :label: Earliest of Two Outpatient Heart Attacks
1335
+
1336
+ ```
1337
+
1338
+ ![Save away a stream of results to build the 1 inpatient, 2 outpatient pattern used in claims data algorithms](spec/30e432f256d528ee828a7d97e550e110277841bce042da718ee253134dbeaf3f.png)
1339
+
1340
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1341
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1342
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1343
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1344
+ | 88 | 1572 | observations | condition_occurrence | 2009-01-03 | 2009-01-09 | 412 |
1345
+ | 131 | 172 | observations | condition_occurrence | 2008-03-22 | 2008-03-23 | 412 |
1346
+ | 161 | 963 | observations | condition_occurrence | 2009-10-25 | 2009-10-29 | 412 |
1347
+ | 177 | 507 | observations | condition_occurrence | 2009-06-13 | 2009-06-16 | 412 |
1348
+ | 230 | 523 | observations | condition_occurrence | 2008-03-14 | 2008-03-21 | 412 |
1349
+
1350
+ ConceptQL also has a `one_in_two_out` operator that implements this pattern in a single operator.
1351
+
1352
+ ## Algorithms within Algorithms
1353
+
1354
+ 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.
1355
+
1356
+ 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:
1357
+
1358
+ ```YAML
1359
+ ---
1360
+ - co_reported
1361
+ - - icd9
1362
+ - '486'
1363
+ - - cpt
1364
+ - '99232'
1365
+
1366
+ ```
1367
+
1368
+ ![All Pneumonia Diagnoses and Hospital Visits reported in the same source record](spec/814fa803d0ba09d73b7bb33177d043c26b681f5c621e9d76dc5c2e18dc852770.png)
1369
+
1370
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1371
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1372
+ | 63 | 61990 | observations | condition_occurrence | 2009-07-02 | 2009-07-04 | 486 |
1373
+ | 63 | 61991 | observations | condition_occurrence | 2009-07-02 | 2009-07-04 | 486 |
1374
+ | 63 | 61994 | observations | procedure_occurrence | 2009-07-02 | 2009-07-04 | 99232 |
1375
+ | 63 | 61995 | observations | procedure_occurrence | 2009-07-02 | 2009-07-04 | 99232 |
1376
+ | 83 | 38139 | observations | condition_occurrence | 2008-03-02 | 2008-03-02 | 486 |
1377
+ | 83 | 38140 | observations | procedure_occurrence | 2008-03-02 | 2008-03-02 | 99232 |
1378
+ | 85 | 80214 | observations | condition_occurrence | 2008-08-24 | 2008-08-30 | 486 |
1379
+ | 85 | 80215 | observations | condition_occurrence | 2008-08-24 | 2008-08-30 | 486 |
1380
+ | 85 | 80216 | observations | procedure_occurrence | 2008-08-24 | 2008-08-30 | 99232 |
1381
+ | 85 | 80217 | observations | procedure_occurrence | 2008-08-24 | 2008-08-30 | 99232 |
1382
+
1383
+ 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:
1384
+
1385
+ ```YAML
1386
+ ---
1387
+ - person
1388
+ - - algorithm
1389
+ - 2031
1390
+
1391
+ ```
1392
+
1393
+ ![All People with a Pneumonia Diagnosis and a Hospital Visit reported in the same source record](spec/eaa8e3fcbe133f6962f97ae3de3714e943d5e38c3e1cecfe62c5a1972866228f.png)
1394
+
1395
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1396
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1397
+ | 63 | 63 | patients | person | 1964-05-01 | 1964-05-01 | 00205821D74AA7D8 |
1398
+ | 83 | 83 | patients | person | 1963-05-01 | 1963-05-01 | 002578091E1953AF |
1399
+ | 85 | 85 | patients | person | 1937-01-01 | 1937-01-01 | 0025ACAB19F5DEC0 |
1400
+ | 141 | 141 | patients | person | 1933-04-01 | 1933-04-01 | 0040CF7315D51D01 |
1401
+ | 143 | 143 | patients | person | 1945-05-01 | 1945-05-01 | 00422125F788CC20 |
1402
+ | 149 | 149 | patients | person | 1943-02-01 | 1943-02-01 | 0045A5AFF076CBE6 |
1403
+ | 150 | 150 | patients | person | 1936-01-01 | 1936-01-01 | 0046020E72E1CBFD |
1404
+ | 176 | 176 | patients | person | 1939-03-01 | 1939-03-01 | 0057DAC8F6C528CB |
1405
+ | 178 | 178 | patients | person | 1928-06-01 | 1928-06-01 | 0058B2A556D74DC2 |
1406
+ | 214 | 214 | patients | person | 1914-07-01 | 1914-07-01 | 006D1BD234E5C844 |
1407
+
1408
+ 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.
1409
+
1410
+ ```YAML
1411
+ ---
1412
+ - person
1413
+ - - co_reported
1414
+ - - icd9
1415
+ - '486'
1416
+ - - cpt
1417
+ - '99232'
1418
+
1419
+ ```
1420
+
1421
+ ![All People with a Pneumonia Diagnosis and a Hospital Visit reported in the same source record (same as above)](spec/2b0e2bd4016c9888be0d4f3afe2e71d57a05796cbb396a786e447b33f5428897.png)
1422
+
1423
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1424
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- |
1425
+ | 63 | 63 | patients | person | 1964-05-01 | 1964-05-01 | 00205821D74AA7D8 |
1426
+ | 83 | 83 | patients | person | 1963-05-01 | 1963-05-01 | 002578091E1953AF |
1427
+ | 85 | 85 | patients | person | 1937-01-01 | 1937-01-01 | 0025ACAB19F5DEC0 |
1428
+ | 141 | 141 | patients | person | 1933-04-01 | 1933-04-01 | 0040CF7315D51D01 |
1429
+ | 143 | 143 | patients | person | 1945-05-01 | 1945-05-01 | 00422125F788CC20 |
1430
+ | 149 | 149 | patients | person | 1943-02-01 | 1943-02-01 | 0045A5AFF076CBE6 |
1431
+ | 150 | 150 | patients | person | 1936-01-01 | 1936-01-01 | 0046020E72E1CBFD |
1432
+ | 176 | 176 | patients | person | 1939-03-01 | 1939-03-01 | 0057DAC8F6C528CB |
1433
+ | 178 | 178 | patients | person | 1928-06-01 | 1928-06-01 | 0058B2A556D74DC2 |
1434
+ | 214 | 214 | patients | person | 1914-07-01 | 1914-07-01 | 006D1BD234E5C844 |
1435
+
1436
+ ## Values
1437
+
1438
+ A result can carry forward three different types of values:
1439
+
1440
+ - value_as_number
1441
+ - For values like lab values, counts of occurrence of results, or sums
1442
+ - value_as_string
1443
+ - For values that are text, such as a lab result that is text
1444
+ - value_as_concept_id
1445
+ - For values that are concepts, such as a coded lab result
1446
+
1447
+ By default, all value fields are set to NULL, unless an operator is explicitly written to populate one or more of those fields.
1448
+
1449
+ There are many operations that can be performed on the value_as\_\* columns and as those operations are implemented, this section will grow.
1450
+
1451
+ For now we'll cover some of the general behavior of the value_as_number column and it's associated operators.
1452
+
1453
+ ### numeric
1454
+
1455
+ - Takes 2 arguments
1456
+ - A stream
1457
+ - And a numeric value
1458
+
1459
+ Passing streams through a `numeric` operator changes the number stored in the value column:
1460
+
1461
+ ```YAML
1462
+ ---
1463
+ - numeric
1464
+ - 2
1465
+ - - icd9
1466
+ - '412'
1467
+
1468
+ ```
1469
+
1470
+ ![All MIs, setting value_as_number to 2](spec/d39452b58257c95a7cba07afed4877417b0c227f3690e2bff22c9eca89eac845.png)
1471
+
1472
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1473
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ | --------------- |
1474
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 | 2.0 |
1475
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 | 2.0 |
1476
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 | 2.0 |
1477
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 | 2.0 |
1478
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 | 2.0 |
1479
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 | 2.0 |
1480
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 | 2.0 |
1481
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 | 2.0 |
1482
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 | 2.0 |
1483
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 | 2.0 |
1484
+
1485
+ 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:
1486
+
1487
+ ```YAML
1488
+ ---
1489
+ - sum
1490
+ - - union
1491
+ - - numeric
1492
+ - 1
1493
+ - - person
1494
+ - - icd9
1495
+ - '412'
1496
+ - - numeric
1497
+ - 2
1498
+ - - person
1499
+ - - icd9
1500
+ - '278.02'
1501
+
1502
+ ```
1503
+
1504
+ ![](spec/ba572c4f4dbade65be55f141df16cf7b3e7d09e0aec4e4e5debc4f2075277371.png)
1505
+
1506
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1507
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- | --------------- |
1508
+ | 11 | 0 | patients | person | 1934-02-01 | 1934-02-01 | 000489E7EAAD463F | 1.0 |
1509
+ | 17 | 0 | patients | person | 1919-09-01 | 1919-09-01 | 0007F12A492FD25D | 3.0 |
1510
+ | 34 | 0 | patients | person | 1919-10-01 | 1919-10-01 | 00151A878F9A2C0D | 2.0 |
1511
+ | 38 | 0 | patients | person | 1922-12-01 | 1922-12-01 | 001731EB127233DA | 1.0 |
1512
+ | 54 | 0 | patients | person | 1931-02-01 | 1931-02-01 | 001CAFF084B21E14 | 1.0 |
1513
+ | 55 | 0 | patients | person | 1924-12-01 | 1924-12-01 | 001D0E59C94130D3 | 2.0 |
1514
+ | 60 | 0 | patients | person | 1925-07-01 | 1925-07-01 | 001EA2F4DB30F105 | 1.0 |
1515
+ | 66 | 0 | patients | person | 1920-04-01 | 1920-04-01 | 0021B3C854C968C8 | 1.0 |
1516
+ | 73 | 0 | patients | person | 1925-09-01 | 1925-09-01 | 00237322613CFC3C | 1.0 |
1517
+ | 77 | 0 | patients | person | 1929-03-01 | 1929-03-01 | 00244B6D9AB50F9B | 1.0 |
1518
+
1519
+ ### Counting
1520
+
1521
+ The `count` operator 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. So `count` does not count the events of each person. Casting a stream to person first does not help, because the cast yields one result for each person.
1522
+
1523
+ To count the events of each person, set value_as_number to 1 with `numeric`, and then use `sum`:
1524
+
1525
+ ```YAML
1526
+ ---
1527
+ - sum
1528
+ - - numeric
1529
+ - 1
1530
+ - - icd9
1531
+ - '486'
1532
+
1533
+ ```
1534
+
1535
+ ![Count the number of times each person had pneumonia](spec/385ede8312e323fa1ae2337c303c8e89f252ebcb8ba229961fa3af1a3caec886.png)
1536
+
1537
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1538
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ | --------------- |
1539
+ | 14 | 0 | observations | condition_occurrence | 2008-09-12 | 2009-04-05 | 486 | 2.0 |
1540
+ | 17 | 0 | observations | condition_occurrence | 2009-05-21 | 2009-05-21 | 486 | 1.0 |
1541
+ | 19 | 0 | observations | condition_occurrence | 2008-02-02 | 2008-02-03 | 486 | 1.0 |
1542
+ | 23 | 0 | observations | condition_occurrence | 2009-08-18 | 2010-02-17 | 486 | 3.0 |
1543
+ | 25 | 0 | observations | condition_occurrence | 2009-07-08 | 2009-07-08 | 486 | 1.0 |
1544
+ | 27 | 0 | observations | condition_occurrence | 2010-09-30 | 2010-09-30 | 486 | 1.0 |
1545
+ | 28 | 0 | observations | condition_occurrence | 2009-09-12 | 2009-09-12 | 486 | 1.0 |
1546
+ | 38 | 0 | observations | condition_occurrence | 2010-02-24 | 2010-02-24 | 486 | 3.0 |
1547
+ | 44 | 0 | observations | condition_occurrence | 2009-09-07 | 2010-08-30 | 486 | 6.0 |
1548
+ | 51 | 0 | observations | condition_occurrence | 2008-03-08 | 2008-10-18 | 486 | 2.0 |
1549
+
1550
+ We could do dumb things like count the number of times a row shows up in a union:
1551
+
1552
+ ```YAML
1553
+ ---
1554
+ - count
1555
+ - - union
1556
+ - - icd9
1557
+ - '412'
1558
+ - - provenance
1559
+ - - icd9
1560
+ - '412'
1561
+ - inpatient
1562
+
1563
+ ```
1564
+
1565
+ ![All rows with a value of 2 would be rows that were both MI and from an inpatient record](spec/ecede6b94c97cf0b036a664e4df704d119be7c31607a1defe6eb77f294fd1393.png)
1566
+
1567
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1568
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ | --------------- |
1569
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 | 1 |
1570
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 | 1 |
1571
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 | 1 |
1572
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 | 1 |
1573
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 | 1 |
1574
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 | 2 |
1575
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 | 1 |
1576
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 | 1 |
1577
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 | 1 |
1578
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 | 2 |
1579
+
1580
+ #### Numeric Value Comparison
1581
+
1582
+ 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:
1583
+
1584
+ - greater_than_or_equal_to
1585
+ - less_than_or_equal_to
1586
+
1587
+ ```YAML
1588
+ ---
1589
+ - person
1590
+ - - numeric_filter
1591
+ - - sum
1592
+ - - numeric
1593
+ - 1
1594
+ - - icd9
1595
+ - '412'
1596
+ - :greater_than_or_equal_to: 2
1597
+
1598
+ ```
1599
+
1600
+ ![People with more than 1 MI](spec/0d8546d33c77d25b522ae23b74c87bf9ee77f5564c6de688af8a2b270b615cfb.png)
1601
+
1602
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1603
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- | --------------- |
1604
+ | 17 | 17 | patients | person | 1919-09-01 | 1919-09-01 | 0007F12A492FD25D | |
1605
+ | 88 | 88 | patients | person | 1922-05-01 | 1922-05-01 | 00291F39917544B1 | |
1606
+ | 152 | 152 | patients | person | 1927-07-01 | 1927-07-01 | 00490754761BC1C2 | |
1607
+ | 177 | 177 | patients | person | 1939-02-01 | 1939-02-01 | 0057EDCE5B633B19 | |
1608
+ | 187 | 187 | patients | person | 1936-01-01 | 1936-01-01 | 006084B3FA2A151C | |
1609
+ | 212 | 212 | patients | person | 1940-01-01 | 1940-01-01 | 006B004741EB5AE9 | |
1610
+ | 213 | 213 | patients | person | 1940-02-01 | 1940-02-01 | 006C7FC30305A339 | |
1611
+ | 214 | 214 | patients | person | 1914-07-01 | 1914-07-01 | 006D1BD234E5C844 | |
1612
+ | 220 | 220 | patients | person | 1927-11-01 | 1927-11-01 | 0070946DE04C2014 | |
1613
+ | 223 | 223 | patients | person | 1926-05-01 | 1926-05-01 | 0071E2E641B73233 | |
1614
+
1615
+ ### numeric as selection operator
1616
+
1617
+ 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.
1618
+
1619
+ ```YAML
1620
+ ---
1621
+ - numeric
1622
+ - 1
1623
+
1624
+ ```
1625
+
1626
+ ![All patients, each with value_as_number set to 1](spec/3d6b85dbd283c27fa65bc58746c7fe1e58ab76962e72460dbcf85e336bf47e51.png)
1627
+
1628
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value | value_as_number |
1629
+ | --------- | ------------ | --------------- | ---------------- | ---------- | ---------- | ---------------- | --------------- |
1630
+ | 1 | 1 | patients | person | 1923-05-01 | 1923-05-01 | 00013D2EFD8E45D1 | 1.0 |
1631
+ | 2 | 2 | patients | person | 1943-01-01 | 1943-01-01 | 00016F745862898F | 1.0 |
1632
+ | 3 | 3 | patients | person | 1936-09-01 | 1936-09-01 | 0001FDD721E223DC | 1.0 |
1633
+ | 4 | 4 | patients | person | 1941-06-01 | 1941-06-01 | 00021CA6FF03E670 | 1.0 |
1634
+ | 5 | 5 | patients | person | 1936-08-01 | 1936-08-01 | 00024B3D2352D2D0 | 1.0 |
1635
+ | 6 | 6 | patients | person | 1943-10-01 | 1943-10-01 | 0002DAE1C81CC70D | 1.0 |
1636
+ | 7 | 7 | patients | person | 1922-07-01 | 1922-07-01 | 0002F28CE057345B | 1.0 |
1637
+ | 8 | 8 | patients | person | 1935-09-01 | 1935-09-01 | 000308435E3E5B76 | 1.0 |
1638
+ | 9 | 9 | patients | person | 1976-09-01 | 1976-09-01 | 000345A39D4157C9 | 1.0 |
1639
+ | 10 | 10 | patients | person | 1938-10-01 | 1938-10-01 | 00036A21B65B0206 | 1.0 |
1640
+
1641
+ #### sum
1642
+
1643
+ - Takes a stream of results and does some wild things
1644
+ - 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
1645
+ - Sums the value_as_number column within that grouping
1646
+ - Sets start_date to the earliest start_date in the group
1647
+ - Sets the end_date to the most recent end_date in the group
1648
+ - Sets criterion_id to 0 since there is no particular single row that the result refers to anymore
1649
+
1650
+ ## Appendix A - Selection Operators
1651
+
1652
+ 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.
1653
+
1654
+ | Operator Name | Stream Type | Arguments | Returns |
1655
+ | ------------------- | -------------------- | ---------------------------------------- | -------------------------------------------------------------- |
1656
+ | cpt | procedure_occurrence | 1 or more CPT codes | All clinical codes that match any of the CPT codes |
1657
+ | icd9 | condition_occurrence | 1 or more ICD-9-CM diagnosis codes | All clinical codes that match any of the ICD-9 codes |
1658
+ | icd9_procedure | procedure_occurrence | 1 or more ICD-9-CM procedure codes | All clinical codes that match any of the ICD-9 procedure codes |
1659
+ | icd10cm | condition_occurrence | 1 or more ICD-10-CM codes | All clinical codes that match any of the ICD-10-CM codes |
1660
+ | hcpcs | procedure_occurrence | 1 or more HCPCS codes | All clinical codes that match any of the HCPCS codes |
1661
+ | loinc | observation | 1 or more LOINC codes | All clinical codes that match any of the LOINC codes |
1662
+ | ndc | drug_exposure | 1 or more NDC codes | All clinical codes that match any of the NDC codes |
1663
+ | rxnorm | drug_exposure | 1 or more RxNorm codes | All clinical codes that match any of the RxNorm codes |
1664
+ | snomed | condition_occurrence | 1 or more SNOMED codes | All clinical codes that match any of the SNOMED codes |
1665
+ | gender | person | 1 or more of Male, Female, and Unknown | All patients whose gender matches any of the arguments |
1666
+ | race | person | 1 or more race names or race concept IDs | All patients whose race matches any of the arguments |
1667
+ | death | death | None | All death records |
1668
+ | information_periods | observation_period | None | All information periods |
1669
+ | date_range | person | A start date and an end date | One result for each patient, with the given dates |
1670
+
1671
+ ## Appendix B - Algorithm Showcase
1672
+
1673
+ 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.
1674
+
1675
+ ### Acute Kidney Injury - Narrow Definition and diagnositc procedure
1676
+
1677
+ - ICD-9 of 584 (584.5 through 584.9)
1678
+ - AND
1679
+ - ICD-9 procedure codes of 39.95 or 54.98 within 60 days after diagnosis
1680
+ - AND NOT
1681
+ - A diagnostic code of chronic dialysis any time before initial diagnosis
1682
+ - V45.1, V56.0, V56.31, V56.32, V56.8
1683
+
1684
+ ```YAML
1685
+ ---
1686
+ - during
1687
+ - :left:
1688
+ - except
1689
+ - :left:
1690
+ - icd9
1691
+ - '584.5'
1692
+ - '584.6'
1693
+ - '584.7'
1694
+ - '584.8'
1695
+ - '584.9'
1696
+ :right:
1697
+ - after
1698
+ - :left:
1699
+ - icd9
1700
+ - '584.5'
1701
+ - '584.6'
1702
+ - '584.7'
1703
+ - '584.8'
1704
+ - '584.9'
1705
+ :right:
1706
+ - icd9
1707
+ - V45.1
1708
+ - V56.0
1709
+ - V56.31
1710
+ - V56.32
1711
+ - V56.8
1712
+ :right:
1713
+ - time_window
1714
+ - - icd9_procedure
1715
+ - '39.95'
1716
+ - '54.98'
1717
+ - :start: '0'
1718
+ :end: 60d
1719
+
1720
+ ```
1721
+
1722
+ ![](spec/58b1555ee5f9e9b2d2adfca6683c2c1e29498347e207e7406b1fe3265ba619b4.png)
1723
+
1724
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1725
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1726
+ | 153 | 102 | observations | condition_occurrence | 2010-01-29 | 2010-02-08 | 5849 |
1727
+
1728
+ ### Mortality after Myocardial Infarction #3
1729
+
1730
+ - Person Died
1731
+ - And Occurrence of an acute MI (ICD-9 410.x1, initial episode of care) prior to death
1732
+ - And either
1733
+ - MI diagnosis within 30 days prior to 410
1734
+ - MI therapy within 60 days after 410
1735
+
1736
+ ```YAML
1737
+ ---
1738
+ - during
1739
+ - :left:
1740
+ - before
1741
+ - :left:
1742
+ - icd9
1743
+ - '410.01'
1744
+ - '410.11'
1745
+ - '410.21'
1746
+ - '410.31'
1747
+ - '410.41'
1748
+ - '410.51'
1749
+ - '410.61'
1750
+ - '410.71'
1751
+ - '410.81'
1752
+ - '410.91'
1753
+ :right:
1754
+ - death
1755
+ :right:
1756
+ - union
1757
+ - - time_window
1758
+ - - union
1759
+ - - cpt
1760
+ - 0146T
1761
+ - '75898'
1762
+ - '82554'
1763
+ - '92980'
1764
+ - '93010'
1765
+ - '93233'
1766
+ - '93508'
1767
+ - '93540'
1768
+ - '93545'
1769
+ - - icd9_procedure
1770
+ - '00.24'
1771
+ - '36.02'
1772
+ - '89.53'
1773
+ - '89.57'
1774
+ - '89.69'
1775
+ - - loinc
1776
+ - 10839-9
1777
+ - 13969-1
1778
+ - 18843-3
1779
+ - 2154-3
1780
+ - 33204-9
1781
+ - 48425-3
1782
+ - 49259-5
1783
+ - 6597-9
1784
+ - 8634-8
1785
+ - :start: "-30d"
1786
+ :end: '0'
1787
+ - - time_window
1788
+ - - union
1789
+ - - cpt
1790
+ - 0146T
1791
+ - '75898'
1792
+ - '82554'
1793
+ - '92980'
1794
+ - '93010'
1795
+ - '93233'
1796
+ - - icd9_procedure
1797
+ - '00.24'
1798
+ - '36.02'
1799
+ - '89.53'
1800
+ - '89.57'
1801
+ - '89.69'
1802
+ - :start: ''
1803
+ :end: 60d
1804
+
1805
+ ```
1806
+
1807
+ ![](spec/f6b358f3006c217809558dc8b9ebe7060623ac72068a96fdf5b72240d8aed13a.png)
1808
+
1809
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1810
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1811
+ | 191 | 64752 | observations | condition_occurrence | 2008-03-11 | 2008-03-11 | 41071 |
1812
+ | 191 | 89727 | observations | condition_occurrence | 2008-12-27 | 2008-12-27 | 41071 |
1813
+
1814
+ ### GI Ulcer Hospitalization 2 (5000001002)
1815
+
1816
+ - Occurrence of GI Ulcer diagnostic code
1817
+ - Hospitalization at time of diagnostic code
1818
+ - At least one diagnostic procedure during same hospitalization
1819
+
1820
+ 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.
1821
+
1822
+ ```YAML
1823
+ ---
1824
+ - during
1825
+ - :left:
1826
+ - co_reported
1827
+ - - icd9
1828
+ - '531.00'
1829
+ - '531.40'
1830
+ - '531.90'
1831
+ - '532.40'
1832
+ - '532.90'
1833
+ - '533.40'
1834
+ - '533.90'
1835
+ - '534.40'
1836
+ - '534.90'
1837
+ - - union
1838
+ - - cpt
1839
+ - '0008T'
1840
+ - 3142F
1841
+ - '43205'
1842
+ - '43236'
1843
+ - '76975'
1844
+ - '91110'
1845
+ - '91111'
1846
+ - - hcpcs
1847
+ - B4081
1848
+ - B4082
1849
+ - - icd9_procedure
1850
+ - '42.22'
1851
+ - '42.23'
1852
+ - '44.13'
1853
+ - '45.13'
1854
+ - '52.21'
1855
+ - '97.01'
1856
+ - - loinc
1857
+ - 16125-7
1858
+ - 17780-8
1859
+ - 40820-3
1860
+ - 50320-1
1861
+ - 5177-1
1862
+ - 7901-2
1863
+ :right:
1864
+ - hospitalization
1865
+
1866
+ ```
1867
+
1868
+ ![GI Ulcer diagnoses and diagnostic procedures reported in the same source record during a hospitalization](spec/1f7c9cec3ac4993fb25c83a889d5a2ddf1739e8cf58fb922a59b141e2b4a7cf4.png)
1869
+
1870
+ `No Results found.`
1871
+
1872
+ ## Appendix C - Under Development
1873
+
1874
+ 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.
1875
+
1876
+ ### Todo List
1877
+
1878
+ 1. Handle costs
1879
+ - How do we aggregate?
1880
+ 2. How do we count?
1881
+ 3. How do we handle missing values in streams?
1882
+ - For instance, missing DoB on patient?
1883
+ 4. What does it mean to pass a date range as an L stream?
1884
+ - I'm thinking we pass through no results
1885
+ - 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
1886
+ 5. How do we want to look up standard vocab concepts?
1887
+ - I think Marc’s approach is a bit heavy-handed
1888
+
1889
+ 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.
1890
+
1891
+ ### Future Work for Define and Recall
1892
+
1893
+ 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.
1894
+
1895
+ But I don't have a good feel for:
1896
+
1897
+ - Whether we should have users name the variables, or auto-assign a name?
1898
+ - We risk name collisions if a algorithm includes a sub-algorithm with the same variable name
1899
+ - Probably need to name space all variables
1900
+ - How to prompt users to enter values for variables in a concept
1901
+ - If we have name-spaced variables and sub-algorithms needing values, how do we show this in a coherent manner to a user?
1902
+ - 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
1903
+ - Do we throw an exception if not?
1904
+ - Do we require calling programs to invoke a check on the algorithm before generating the query?
1905
+ - Perhaps slot is a different operator from "define"
1906
+
1907
+ ### Considerations for Values
1908
+
1909
+ I'm considering defaulting each value_as\_\* column to some value.
1910
+
1911
+ - numeric => 1
1912
+ - concept_id => 0
1913
+ - Or maybe the concept_id of the main concept_id value from the row?
1914
+ - This would be confusing when pulling from the observation table
1915
+ - What's the "main" concept_id of a person?
1916
+ - Hm. This feels a bit less like a good idea now
1917
+ - string
1918
+ - source_value?
1919
+ - Boy, this one is even harder to default
1920
+
1921
+ ```YAML
1922
+ ---
1923
+ - icd9
1924
+ - '412'
1925
+
1926
+ ```
1927
+
1928
+ ![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)
1929
+
1930
+ | person_id | criterion_id | criterion_table | criterion_domain | start_date | end_date | source_value |
1931
+ | --------- | ------------ | --------------- | -------------------- | ---------- | ---------- | ------------ |
1932
+ | 11 | 82614 | observations | condition_occurrence | 2008-10-29 | 2008-10-29 | 412 |
1933
+ | 17 | 27269 | observations | condition_occurrence | 2008-08-25 | 2008-08-25 | 412 |
1934
+ | 17 | 46165 | observations | condition_occurrence | 2009-04-30 | 2009-04-30 | 412 |
1935
+ | 38 | 40556 | observations | condition_occurrence | 2010-02-12 | 2010-02-12 | 412 |
1936
+ | 54 | 74606 | observations | condition_occurrence | 2008-06-05 | 2008-06-05 | 412 |
1937
+ | 60 | 986 | observations | condition_occurrence | 2009-07-19 | 2009-07-22 | 412 |
1938
+ | 66 | 16171 | observations | condition_occurrence | 2009-07-25 | 2009-07-25 | 412 |
1939
+ | 73 | 52843 | observations | condition_occurrence | 2008-11-16 | 2008-11-16 | 412 |
1940
+ | 77 | 58165 | observations | condition_occurrence | 2010-10-06 | 2010-10-06 | 412 |
1941
+ | 81 | 1405 | observations | condition_occurrence | 2009-01-28 | 2009-01-30 | 412 |
1942
+
1943
+ ### `as` Option for Filter
1944
+
1945
+ 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.
1946
+
1947
+ ### AS option for Except
1948
+
1949
+ 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.
1950
+
1951
+ \[^AIA\]: J. Allen. Maintaining knowledge about temporal intervals. Communications of the ACM (1983) vol. 26 (11) pp. 832-843