conceptql 0.6.0 → 0.7.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +176 -0
- data/README.md +4 -2
- data/assets/diagram/diagram.css +331 -0
- data/assets/diagram/fonts/OFL.txt +93 -0
- data/assets/diagram/fonts/Roboto-400.ttf +0 -0
- data/assets/diagram/fonts/Roboto-700.ttf +0 -0
- data/assets/diagram/fonts/Roboto-Italic-400.ttf +0 -0
- data/assets/diagram/fonts/Roboto-Italic-Latin.woff2 +0 -0
- data/assets/diagram/fonts/Roboto-Normal-Latin.woff2 +0 -0
- data/assets/diagram/operators.svg +224 -0
- data/config/vocabularies.csv +85 -85
- data/docs/spec/README.md +2624 -0
- data/docs/spec/examples/after-operator.json +562 -0
- data/docs/spec/examples/any-overlap-operator.json +106 -0
- data/docs/spec/examples/before-operator.json +437 -0
- data/docs/spec/examples/co-reported-operator.json +168 -0
- data/docs/spec/examples/contains-operator.json +97 -0
- data/docs/spec/examples/date-range-operator.json +275 -0
- data/docs/spec/examples/death-operator.json +103 -0
- data/docs/spec/examples/during-operator.json +178 -0
- data/docs/spec/examples/episode-operator.json +80 -0
- data/docs/spec/examples/equal-operator.json +79 -0
- data/docs/spec/examples/ethnicity-operator.json +86 -0
- data/docs/spec/examples/except-operator.json +536 -0
- data/docs/spec/examples/filter-operator.json +218 -0
- data/docs/spec/examples/first-operator.json +148 -0
- data/docs/spec/examples/gender-operator.json +257 -0
- data/docs/spec/examples/hospice-operator.json +40 -0
- data/docs/spec/examples/hospitalization-operator.json +131 -0
- data/docs/spec/examples/information-periods-operator.json +131 -0
- data/docs/spec/examples/last-operator.json +148 -0
- data/docs/spec/examples/match-operator.json +186 -0
- data/docs/spec/examples/nth-occurrence-operator.json +300 -0
- data/docs/spec/examples/numeric-filter-operator.json +77 -0
- data/docs/spec/examples/one-in-two-out-operator.json +158 -0
- data/docs/spec/examples/person-filter-operator.json +919 -0
- data/docs/spec/examples/person-operator.json +274 -0
- data/docs/spec/examples/place-of-service-filter-operator.json +57 -0
- data/docs/spec/examples/provenance-operator.json +148 -0
- data/docs/spec/examples/provider-filter-operator.json +77 -0
- data/docs/spec/examples/race-operator.json +131 -0
- data/docs/spec/examples/recall-operator.json +289 -0
- data/docs/spec/examples/snf-operator.json +40 -0
- data/docs/spec/examples/time-window-operator.json +736 -0
- data/docs/spec/examples/trim-date-end-operator.json +201 -0
- data/docs/spec/examples/trim-date-start-operator.json +201 -0
- data/docs/spec/examples/union-operator.json +547 -0
- data/docs/spec/examples/vocabulary-operator.json +232 -0
- data/lib/conceptql/behaviors/metadatable.rb +68 -2
- data/lib/conceptql/behaviors/provenanceable.rb +4 -1
- data/lib/conceptql/behaviors/utilizable.rb +3 -0
- data/lib/conceptql/code_case_mismatch_error.rb +50 -0
- data/lib/conceptql/data_model/gdm.rb +23 -2
- data/lib/conceptql/diagram_renderer/app.rb +59 -0
- data/lib/conceptql/diagram_renderer/configuration.rb +87 -0
- data/lib/conceptql/diagram_renderer/diagram_data.rb +80 -0
- data/lib/conceptql/diagram_renderer/drawing.rb +255 -0
- data/lib/conceptql/diagram_renderer/geometry.rb +380 -0
- data/lib/conceptql/diagram_renderer/input_error.rb +11 -0
- data/lib/conceptql/diagram_renderer/js_format.rb +112 -0
- data/lib/conceptql/diagram_renderer/resvg.rb +120 -0
- data/lib/conceptql/diagram_renderer/sprite.rb +73 -0
- data/lib/conceptql/diagram_renderer/static.rb +70 -0
- data/lib/conceptql/diagram_renderer/svg.rb +73 -0
- data/lib/conceptql/diagram_renderer/tree_layout.rb +287 -0
- data/lib/conceptql/diagram_renderer.rb +82 -0
- data/lib/conceptql/lexicon/lexicon_strategy.rb +16 -0
- data/lib/conceptql/lexicon.rb +63 -11
- data/lib/conceptql/nodifier.rb +5 -3
- data/lib/conceptql/operators/after.rb +15 -1
- data/lib/conceptql/operators/any_overlap.rb +6 -1
- data/lib/conceptql/operators/before.rb +11 -1
- data/lib/conceptql/operators/binary_operator_operator.rb +12 -2
- data/lib/conceptql/operators/co_reported.rb +9 -4
- data/lib/conceptql/operators/contains.rb +6 -1
- data/lib/conceptql/operators/count.rb +2 -0
- data/lib/conceptql/operators/date_range.rb +15 -3
- data/lib/conceptql/operators/death.rb +5 -1
- data/lib/conceptql/operators/dedup.rb +2 -0
- data/lib/conceptql/operators/during.rb +6 -1
- data/lib/conceptql/operators/episode.rb +13 -2
- data/lib/conceptql/operators/equal.rb +11 -1
- data/lib/conceptql/operators/ethnicity.rb +19 -7
- data/lib/conceptql/operators/except.rb +10 -1
- data/lib/conceptql/operators/filter.rb +6 -1
- data/lib/conceptql/operators/first.rb +19 -9
- data/lib/conceptql/operators/from.rb +3 -0
- data/lib/conceptql/operators/gender.rb +10 -2
- data/lib/conceptql/operators/hospice.rb +7 -1
- data/lib/conceptql/operators/hospitalization.rb +6 -1
- data/lib/conceptql/operators/information_periods.rb +26 -7
- data/lib/conceptql/operators/invalid.rb +4 -0
- data/lib/conceptql/operators/last.rb +19 -9
- data/lib/conceptql/operators/match.rb +6 -1
- data/lib/conceptql/operators/multiple_vocabularies.rb +17 -3
- data/lib/conceptql/operators/numeric.rb +4 -0
- data/lib/conceptql/operators/numeric_filter.rb +13 -3
- data/lib/conceptql/operators/occurrence.rb +33 -12
- data/lib/conceptql/operators/one_in_two_out.rb +22 -5
- data/lib/conceptql/operators/operator.rb +8 -4
- data/lib/conceptql/operators/person.rb +5 -1
- data/lib/conceptql/operators/person_filter.rb +14 -1
- data/lib/conceptql/operators/place_of_service_filter.rb +14 -3
- data/lib/conceptql/operators/provenance.rb +17 -8
- data/lib/conceptql/operators/provider_filter.rb +18 -6
- data/lib/conceptql/operators/race.rb +14 -9
- data/lib/conceptql/operators/read.rb +11 -1
- data/lib/conceptql/operators/recall.rb +34 -2
- data/lib/conceptql/operators/snf.rb +7 -1
- data/lib/conceptql/operators/sum.rb +4 -0
- data/lib/conceptql/operators/temporal_operator.rb +4 -2
- data/lib/conceptql/operators/time_window.rb +27 -4
- data/lib/conceptql/operators/trim_date.rb +6 -14
- data/lib/conceptql/operators/trim_date_end.rb +9 -3
- data/lib/conceptql/operators/trim_date_start.rb +9 -3
- data/lib/conceptql/operators/union.rb +13 -1
- data/lib/conceptql/operators/vocabulary.rb +80 -6
- data/lib/conceptql/paths.rb +9 -1
- data/lib/conceptql/version.rb +1 -1
- data/lib/conceptql/vocabularies/drg_systems.rb +3 -2
- data/lib/conceptql/vocabularies/entry.rb +14 -11
- data/lib/conceptql/vocabularies/vocabulary_class_factory.rb +2 -1
- data/lib/conceptql.rb +3 -0
- metadata +75 -2
- data/doc/spec.md +0 -2091
|
@@ -12,7 +12,15 @@ module ConceptQL
|
|
|
12
12
|
|
|
13
13
|
register __FILE__
|
|
14
14
|
|
|
15
|
-
desc "
|
|
15
|
+
desc "For each person, passes along the earliest confirmed condition record. " \
|
|
16
|
+
"One inpatient record confirms a condition, and so do two outpatient records whose start dates are a given gap apart."
|
|
17
|
+
|
|
18
|
+
returns "One record per person: the earliest confirmed condition record. " \
|
|
19
|
+
"A returned inpatient record has the admission date as its start_date and the discharge date as its end_date, " \
|
|
20
|
+
"when the claim has them. Only condition records with inpatient, outpatient or carrier_claim provenance can be confirmed."
|
|
21
|
+
|
|
22
|
+
example "First diabetes diagnosis, either found once in an inpatient record, or twice in outpatient records at least 30 days apart",
|
|
23
|
+
[ "one_in_two_out", [ "icd9", "250.00" ], { "outpatient_minimum_gap" => "30d", "outpatient_event_to_return" => "Initial Event" } ]
|
|
16
24
|
|
|
17
25
|
allows_one_upstream
|
|
18
26
|
validate_one_upstream
|
|
@@ -21,13 +29,22 @@ module ConceptQL
|
|
|
21
29
|
basic_type :temporal
|
|
22
30
|
|
|
23
31
|
option :inpatient_length_of_stay, type: :integer, min: 0, default: 0,
|
|
24
|
-
|
|
32
|
+
meaning: "An inpatient stay must be longer than this many days: an inpatient record counts only when its discharge date " \
|
|
33
|
+
"is more than this many days after its admission date. 0 or no value means no length-of-stay check.",
|
|
34
|
+
label: "Inpatient Length of Stay (Days)"
|
|
25
35
|
option :outpatient_minimum_gap, type: :string, default: "30d",
|
|
26
|
-
instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years".'
|
|
36
|
+
instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years".',
|
|
37
|
+
meaning: "A date adjustment, such as 30d. The confirming outpatient record must start at least this long " \
|
|
38
|
+
"after the initial outpatient record. Without this option, the gap is 30 days."
|
|
27
39
|
option :outpatient_maximum_gap, type: :string, default: "365d",
|
|
28
|
-
instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years".'
|
|
40
|
+
instructions: 'Enter a numeric value and specify "d", "m", or "y" for "days", "months", or "years".',
|
|
41
|
+
meaning: "A date adjustment, such as 365d. The confirming outpatient record must start no more than this long " \
|
|
42
|
+
"after the initial outpatient record. Without this option, there is no maximum gap."
|
|
29
43
|
option :outpatient_event_to_return, type: :string, options: [ "Initial Event", "Confirming Event" ],
|
|
30
|
-
default: "Initial Event",
|
|
44
|
+
default: "Initial Event",
|
|
45
|
+
meaning: "Which outpatient record the operator passes on when two outpatient records confirm the condition: " \
|
|
46
|
+
"the initial record or the confirming record. " \
|
|
47
|
+
"Without this option, or with any value other than Initial Event, it passes on the confirming record."
|
|
31
48
|
|
|
32
49
|
validate_option DateAdjuster::VALID_INPUT, :outpatient_minimum_gap, :outpatient_maximum_gap
|
|
33
50
|
|
|
@@ -49,7 +49,8 @@ module ConceptQL
|
|
|
49
49
|
BAD_FORMATS = [ BAD_FORMAT, NDC_BAD_FORMAT ].freeze
|
|
50
50
|
UNKNOWN_CODE = "unknown code(s)"
|
|
51
51
|
|
|
52
|
-
option :label, type: :string
|
|
52
|
+
option :label, type: :string,
|
|
53
|
+
meaning: "A short description of the operator's records. The operator writes it into the label column of each of its records, and a recall operator selects those records by it. Each label in a statement must be different."
|
|
53
54
|
|
|
54
55
|
@validations = []
|
|
55
56
|
|
|
@@ -594,12 +595,15 @@ module ConceptQL
|
|
|
594
595
|
end
|
|
595
596
|
end
|
|
596
597
|
|
|
597
|
-
# A blank value counts as not set, as in Occurrence#at_least_option
|
|
598
|
+
# A blank value counts as not set, as in Occurrence#at_least_option.
|
|
599
|
+
# So does false: the Jigsaw diagram editor writes false when a user
|
|
600
|
+
# clears a checkbox (seed conceptql-ufn).
|
|
598
601
|
def validate_unsupported_options(*opts)
|
|
599
602
|
opts.each do |opt|
|
|
600
|
-
|
|
603
|
+
value = options[opt]
|
|
604
|
+
next if value == false || value.to_s.strip.empty?
|
|
601
605
|
|
|
602
|
-
add_error("unsupported option", opt.to_s,
|
|
606
|
+
add_error("unsupported option", opt.to_s, value)
|
|
603
607
|
end
|
|
604
608
|
end
|
|
605
609
|
|
|
@@ -10,7 +10,11 @@ module ConceptQL
|
|
|
10
10
|
|
|
11
11
|
register __FILE__
|
|
12
12
|
|
|
13
|
-
desc "Selects
|
|
13
|
+
desc "Selects every patient. With one upstream operator, it selects the patients who have a record in the upstream stream."
|
|
14
|
+
returns "One record for each selected patient, from the patients table. The type of the records is person, " \
|
|
15
|
+
"and their start_date and end_date are both the patient's birth date."
|
|
16
|
+
example "All patients", [ "person" ]
|
|
17
|
+
example "The patients with an old myocardial infarction (ICD-9-CM 412)", [ "person", [ "icd9cm", "412" ] ]
|
|
14
18
|
|
|
15
19
|
allows_one_upstream
|
|
16
20
|
domains :person
|
|
@@ -7,7 +7,20 @@ module ConceptQL
|
|
|
7
7
|
class PersonFilter < BinaryOperatorOperator
|
|
8
8
|
register __FILE__
|
|
9
9
|
|
|
10
|
-
desc "
|
|
10
|
+
desc "Keeps each left-hand record whose patient has at least one record in the right-hand stream. The type and the dates of the right-hand records do not matter."
|
|
11
|
+
returns "The left-hand records of every type whose patient has at least one right-hand record. The right-hand records are not returned."
|
|
12
|
+
option_meaning :right, "The right-hand stream. The operator keeps the left-hand records of each patient who has a record in this stream, and does not pass these records on."
|
|
13
|
+
|
|
14
|
+
example "All MI Conditions for people who are male",
|
|
15
|
+
[ "person_filter", { left: [ "icd9", "412" ], right: [ "gender", "Male" ] } ]
|
|
16
|
+
example "All MI Conditions for people who had an office visit at some point in the data",
|
|
17
|
+
[ "person_filter", { left: [ "icd9", "412" ], right: [ "cpt", "99214" ] } ]
|
|
18
|
+
example "The same result as the example above, with the office visits converted to patients first",
|
|
19
|
+
[ "person_filter", { left: [ "icd9", "412" ], right: [ "person", [ "cpt", "99214" ] ] } ]
|
|
20
|
+
example "All MI Conditions for people who are Male OR had an office visit at some point in the data",
|
|
21
|
+
[ "person_filter", { left: [ "icd9", "412" ], right: [ "union", [ "cpt", "99214" ], [ "gender", "Male" ] ] } ]
|
|
22
|
+
example "Yields two types: all MI Conditions for people who are Male, and all office visit Procedures for people who are Male",
|
|
23
|
+
[ "person_filter", { left: [ "union", [ "icd9", "412" ], [ "cpt", "99214" ] ], right: [ "gender", "Male" ] } ]
|
|
11
24
|
default_query_columns
|
|
12
25
|
|
|
13
26
|
# PersonFilter has no `as` option, and a statement that sets one fails
|
|
@@ -31,10 +31,19 @@ module ConceptQL
|
|
|
31
31
|
ANY = "*"
|
|
32
32
|
|
|
33
33
|
desc "Passes along the records whose place of service is one of the given CMS Place of Service codes. " \
|
|
34
|
-
"The code * matches a record that has any place of service. " \
|
|
35
34
|
"A record that has no place of service is not passed along."
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
returns "The upstream records whose place of service is one of the given codes."
|
|
37
|
+
|
|
38
|
+
argument :places_of_service, type: :codelist, vocab: VOCABULARY_ID,
|
|
39
|
+
meaning: "One or more CMS Place of Service codes, such as 21 (inpatient hospital) or 22 (outpatient hospital). " \
|
|
40
|
+
"The code * matches a record whose place of service is any code of that vocabulary. " \
|
|
41
|
+
"An unknown code makes the statement invalid."
|
|
42
|
+
|
|
43
|
+
# The test data has no place of service, so the example returns no rows
|
|
44
|
+
example "Diabetes diagnoses (ICD-9 250.00) with the place of service outpatient hospital (22)",
|
|
45
|
+
[ "place_of_service_filter", 22, [ "icd9", "250.00" ] ], help: false
|
|
46
|
+
|
|
38
47
|
category "Filter Single Stream"
|
|
39
48
|
basic_type :temporal
|
|
40
49
|
allows_one_upstream
|
|
@@ -51,9 +60,11 @@ module ConceptQL
|
|
|
51
60
|
private
|
|
52
61
|
|
|
53
62
|
# With no codes, the lookup returns every concept of the vocabulary.
|
|
63
|
+
# The codes come from the statement, whose validation reports an
|
|
64
|
+
# unknown code, so the lookup does not raise CodeCaseMismatchError.
|
|
54
65
|
def place_of_service_concept_ids(db)
|
|
55
66
|
codes = arguments.map(&:to_s)
|
|
56
|
-
dm.concept_ids(db, VOCABULARY_ID, codes.include?(ANY) ? [] : codes)
|
|
67
|
+
dm.concept_ids(db, VOCABULARY_ID, codes.include?(ANY) ? [] : codes, check_case: false)
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
# With no lexicon there are no codes to check against, and every code
|
|
@@ -20,15 +20,24 @@ module ConceptQL
|
|
|
20
20
|
register __FILE__
|
|
21
21
|
|
|
22
22
|
desc "Passes along the records whose provenance matches at least one of the given keywords. " \
|
|
23
|
-
"
|
|
24
|
-
"and
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
"The provenance of a record says which kind of file it came from, such as inpatient, " \
|
|
24
|
+
"and where its code was on the claim, such as primary."
|
|
25
|
+
|
|
26
|
+
returns "The upstream records whose provenance matches at least one of the keywords."
|
|
27
|
+
|
|
28
|
+
argument :provenance_types, label: "Provenance Types", type: :codelist,
|
|
29
|
+
meaning: "One or more provenance keywords, in lower case. " \
|
|
30
|
+
"A file keyword, such as inpatient, outpatient or carrier_claim, says which kind of file a record came from. " \
|
|
31
|
+
"A code keyword, such as primary or first, says where the code was on the claim. " \
|
|
32
|
+
"A plain keyword matches a record when its file provenance or its code provenance matches it. " \
|
|
33
|
+
"A keyword also matches the provenances under it, so inpatient includes snf. " \
|
|
34
|
+
"A keyword of the form file:code, such as inpatient:primary, needs both the file and the code provenance to match. " \
|
|
35
|
+
"The keyword * matches a record that has any provenance. " \
|
|
36
|
+
"A statement with no keyword, or with an unknown keyword, is invalid."
|
|
37
|
+
|
|
38
|
+
example "Diabetes diagnoses (ICD-9 250.00) from inpatient files",
|
|
39
|
+
[ "provenance", "inpatient", [ "icd9", "250.00" ] ]
|
|
30
40
|
|
|
31
|
-
argument :provenance_types, label: "Provenance Types", type: :codelist
|
|
32
41
|
category "Filter Single Stream"
|
|
33
42
|
basic_type :temporal
|
|
34
43
|
allows_one_upstream
|
|
@@ -10,12 +10,24 @@ module ConceptQL
|
|
|
10
10
|
register __FILE__
|
|
11
11
|
|
|
12
12
|
desc "Passes along the records whose provider matches the given specialties and roles. " \
|
|
13
|
-
"Each option is a comma-separated list of concept IDs, and * matches any value.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
"Each option is a comma-separated list of concept IDs, and * matches any value."
|
|
14
|
+
|
|
15
|
+
returns "The upstream records that have a provider who matches both the specialties and the roles. " \
|
|
16
|
+
"A record can have more than one provider, so a record comes out once for each provider who matches. " \
|
|
17
|
+
"Each record gets the columns provider_id, context_specialty_concept_id, practitioner_specialty_concept_id " \
|
|
18
|
+
"and role_type_concept_id."
|
|
19
|
+
|
|
20
|
+
option :specialties, type: :string,
|
|
21
|
+
meaning: "A comma-separated list of specialty concept IDs, such as 38003868 (Endocrinology, Diabetes & Metabolism Physician), " \
|
|
22
|
+
"or * for any specialty. A provider matches when the specialty recorded for the provider on the claim, " \
|
|
23
|
+
"or the provider's own specialty, is in the list."
|
|
24
|
+
option :roles, type: :string,
|
|
25
|
+
meaning: "A comma-separated list of role concept IDs of the GDM_ROLE vocabulary, " \
|
|
26
|
+
"such as 100000067 (Attending physician) or 100176860 (Billing provider), or * for any role."
|
|
27
|
+
|
|
28
|
+
example "Diabetes diagnoses with an endocrinologist as the attending physician",
|
|
29
|
+
[ "provider_filter", [ "icd9", "250.00" ], { "specialties" => "38003868", "roles" => "100000067" } ]
|
|
30
|
+
|
|
19
31
|
category "Filter Single Stream"
|
|
20
32
|
basic_type :temporal
|
|
21
33
|
allows_one_upstream
|
|
@@ -17,8 +17,16 @@ module ConceptQL
|
|
|
17
17
|
include ConceptQL::Behaviors::Windowable
|
|
18
18
|
include ConceptQL::Behaviors::Timeless
|
|
19
19
|
|
|
20
|
-
desc "Selects
|
|
21
|
-
argument :races, type: :codelist, vocab: "Race"
|
|
20
|
+
desc "Selects the patients with the given races."
|
|
21
|
+
argument :races, type: :codelist, vocab: "Race",
|
|
22
|
+
meaning: "One or more race names, such as White, or race concept IDs. " \
|
|
23
|
+
"A name matches the concepts with that name in any vocabulary, and case does not matter. " \
|
|
24
|
+
"The operator also selects the more detailed races under each given race, " \
|
|
25
|
+
"and each concept that is a kind of one of the race concepts through an is-a relationship. " \
|
|
26
|
+
"A value that contains Unknown also selects the patients who have no race or race concept ID 0."
|
|
27
|
+
returns "One person record for each patient whose race is one of the given races. " \
|
|
28
|
+
"The start_date and the end_date of each record are the patient's birth date."
|
|
29
|
+
example "All patients recorded as white", [ "race", "White" ]
|
|
22
30
|
domains :person
|
|
23
31
|
category "Select by Property"
|
|
24
32
|
basic_type :selection
|
|
@@ -42,17 +50,14 @@ module ConceptQL
|
|
|
42
50
|
|
|
43
51
|
concept_ids = dm.concept_ids_by_name(db, words)
|
|
44
52
|
|
|
45
|
-
c_ids = (concept_ids + actual_ids).map { |i| [ i, race_descendents[i] ] }.flatten.compact.uniq.sort
|
|
53
|
+
c_ids = (concept_ids + actual_ids.map(&:to_i)).map { |i| [ i, race_descendents[i] ] }.flatten.compact.uniq.sort
|
|
46
54
|
|
|
47
55
|
c_ids = dm.related_concept_ids(db, c_ids)
|
|
48
56
|
|
|
49
|
-
|
|
50
|
-
if words.any? { |w| w.match(/unknown/i) }
|
|
51
|
-
c_ids += [ 0 ]
|
|
52
|
-
q = q.where(race_concept_id: nil)
|
|
53
|
-
end
|
|
57
|
+
condition = Sequel.expr(race_concept_id: c_ids)
|
|
58
|
+
condition = Sequel.|(condition, { race_concept_id: 0 }, { race_concept_id: nil }) if words.any? { |w| w.match(/unknown/i) }
|
|
54
59
|
|
|
55
|
-
|
|
60
|
+
db.from(source_table).where(condition)
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
def actual_ids
|
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
require "sequel"
|
|
4
4
|
require "sequel/adapters/mock"
|
|
5
5
|
require_relative "../behaviors/labish"
|
|
6
|
+
require_relative "vocabulary"
|
|
6
7
|
|
|
7
8
|
module ConceptQL
|
|
8
9
|
module Operators
|
|
9
10
|
class Read < Operator
|
|
10
11
|
include ConceptQL::Behaviors::CodeLister
|
|
12
|
+
extend Vocabulary::SharedDocumentation
|
|
11
13
|
register __FILE__
|
|
12
14
|
|
|
13
15
|
preferred_name "READ"
|
|
16
|
+
# The vocabulary_name of Read in the OHDSI vocabulary table
|
|
17
|
+
long_name "NHS UK Read Codes Version 2 (HSCIC)"
|
|
18
|
+
# Metadata only: the records come from ReadGDM, which selects condition_occurrence
|
|
19
|
+
predominant_domains :condition_occurrence
|
|
14
20
|
desc "Selects records based on the Read vocabulary."
|
|
15
|
-
argument :read_codes, type: :codelist, vocab: "Read"
|
|
21
|
+
argument :read_codes, type: :codelist, vocab: "Read",
|
|
22
|
+
meaning: "One or more Read codes. Write each code as the Read vocabulary writes it. " \
|
|
23
|
+
"A code matches exactly, including case, and it does not select its child codes. " \
|
|
24
|
+
"A code alone of * selects every Read record."
|
|
16
25
|
basic_type :selection
|
|
26
|
+
validate_no_upstreams
|
|
17
27
|
category "Select by Clinical Codes"
|
|
18
28
|
conceptql_spec_id "vocabulary"
|
|
19
29
|
|
|
@@ -14,9 +14,41 @@ module ConceptQL
|
|
|
14
14
|
class Recall < Operator
|
|
15
15
|
register __FILE__
|
|
16
16
|
|
|
17
|
-
desc "Passes
|
|
17
|
+
desc "Passes on the records of the operator in the statement that has the given label. " \
|
|
18
|
+
"Use it to break a complex statement into parts, or to use a stream of records more than once."
|
|
19
|
+
returns "The records of the operator that has the given label."
|
|
20
|
+
example 'Label neurological diagnoses as "neuro dxs". Recall them to remove those that were co-reported ' \
|
|
21
|
+
"with a nervous-system MS-DRG. Then apply the one inpatient, two outpatient rule. " \
|
|
22
|
+
"(The code lists are shortened.)",
|
|
23
|
+
[
|
|
24
|
+
"one_in_two_out",
|
|
25
|
+
[
|
|
26
|
+
"except",
|
|
27
|
+
{
|
|
28
|
+
"left" => [
|
|
29
|
+
"union",
|
|
30
|
+
[ "icd9", "331.0", "332.0", "340", "345.90" ],
|
|
31
|
+
[ "icd10cm", "G20", "G30.9", "G35", "G40.909" ],
|
|
32
|
+
{ "label" => "neuro dxs" }
|
|
33
|
+
],
|
|
34
|
+
"right" => [
|
|
35
|
+
"co_reported",
|
|
36
|
+
[ "recall", "neuro dxs" ],
|
|
37
|
+
[ "ms_drg", "056", "057", "058", "059", "060", "100", "101" ]
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
{
|
|
42
|
+
"outpatient_minimum_gap" => "30d",
|
|
43
|
+
"outpatient_maximum_gap" => "365d",
|
|
44
|
+
"outpatient_event_to_return" => "Confirming Event"
|
|
45
|
+
}
|
|
46
|
+
]
|
|
18
47
|
|
|
19
|
-
argument :name, type: :string
|
|
48
|
+
argument :name, type: :string,
|
|
49
|
+
meaning: "The label of the operator whose records to pass on. It must match the label exactly, " \
|
|
50
|
+
"including case. The labeled operator can come before or after the recall in the " \
|
|
51
|
+
"statement, but labeled streams that recall each other in a loop are invalid."
|
|
20
52
|
category "Get Related Data"
|
|
21
53
|
basic_type :selection
|
|
22
54
|
validate_no_upstreams
|
|
@@ -8,7 +8,13 @@ module ConceptQL
|
|
|
8
8
|
register __FILE__
|
|
9
9
|
include ConceptQL::Behaviors::Utilizable
|
|
10
10
|
|
|
11
|
-
desc "Selects skilled nursing
|
|
11
|
+
desc "Selects admissions to skilled nursing facilities (SNF): one record for each admission whose claim came from an SNF file."
|
|
12
|
+
returns "One record for each SNF admission. The type of the records is condition_occurrence, " \
|
|
13
|
+
"and their criterion_table is collections, the table of claims. Their start_date and end_date " \
|
|
14
|
+
"are the dates of the claim, and source_value holds the primary code of the admission when it has one. " \
|
|
15
|
+
"Each record also has admission and discharge columns, such as admission_date, discharge_date and length_of_stay."
|
|
16
|
+
# The test data has no SNF claims
|
|
17
|
+
example "All SNF admissions", [ "snf" ], help: false
|
|
12
18
|
|
|
13
19
|
def collection_type
|
|
14
20
|
"snf"
|
|
@@ -6,6 +6,10 @@ module ConceptQL
|
|
|
6
6
|
module Operators
|
|
7
7
|
class Sum < PassThru
|
|
8
8
|
register __FILE__
|
|
9
|
+
# "We do not yet support numeric or sum in any meaningful way"
|
|
10
|
+
# (Ryan, 2026-11-03, seed conceptql-mdo.4)
|
|
11
|
+
hidden
|
|
12
|
+
category "Modify Data"
|
|
9
13
|
|
|
10
14
|
def events_per_patient
|
|
11
15
|
group_by_person? ? :single : :multiple
|
|
@@ -15,7 +15,8 @@ module ConceptQL
|
|
|
15
15
|
default_query_columns
|
|
16
16
|
|
|
17
17
|
option :within, type: :string,
|
|
18
|
-
instructions: 'Enter a
|
|
18
|
+
instructions: 'Enter a positive number followed by "d", "w", "m" or "y" for days, weeks, months or years. Example: 30d.',
|
|
19
|
+
meaning: "A date adjustment, such as 30d, 2m or 1y-3d, that lets the compared dates be up to this amount apart. Each temporal operator applies it to its own comparison. Use a positive amount."
|
|
19
20
|
|
|
20
21
|
# The temporal operators read join_columns through
|
|
21
22
|
# BinaryOperatorOperator#join_columns_option, but do not declare it with
|
|
@@ -28,7 +29,8 @@ module ConceptQL
|
|
|
28
29
|
class << self
|
|
29
30
|
def allows_at_least_option
|
|
30
31
|
option :at_least, type: :string,
|
|
31
|
-
instructions: 'Enter a
|
|
32
|
+
instructions: 'Enter a positive number followed by "d", "w", "m" or "y" for days, weeks, months or years. Example: 30d.',
|
|
33
|
+
meaning: "A date adjustment, such as 7d or 1w, that sets the smallest gap between the compared dates. The gap must be larger than this amount. Use a positive amount."
|
|
32
34
|
end
|
|
33
35
|
end
|
|
34
36
|
|
|
@@ -8,8 +8,9 @@ module ConceptQL
|
|
|
8
8
|
# A TimeWindow adjusts the start_date and end_date the incoming stream by the values specified in
|
|
9
9
|
# the start and end arguments.
|
|
10
10
|
#
|
|
11
|
-
# Start and end take the format of ([+-]\d*[
|
|
11
|
+
# Start and end take the format of ([+-]\d*[dwmy])+. For example:
|
|
12
12
|
# 'd' => adjust date by one day. 2012-10-07 => 2012-10-08
|
|
13
|
+
# 'w' => adjust date by one week. 2012-10-07 => 2012-10-14
|
|
13
14
|
# 'm' => adjust date by one month. 2012-10-07 => 2012-11-07
|
|
14
15
|
# 'y' => adjust date by one year. 2012-10-07 => 2013-10-07
|
|
15
16
|
# '1y' => adjust date by one year. 2012-10-07 => 2013-10-07
|
|
@@ -24,11 +25,33 @@ module ConceptQL
|
|
|
24
25
|
class TimeWindow < PassThru
|
|
25
26
|
register __FILE__
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
# The values that the start and end options take (DateAdjuster)
|
|
29
|
+
DATE_ADJUSTMENT_TEXT = "The value is an amount of time, made of one or more numbers, each with a unit: " \
|
|
30
|
+
"d (days), w (weeks), m (months) or y (years). Each part can have a - or + sign, " \
|
|
31
|
+
"a bare number means days, and a unit without a number means 1 of it. " \
|
|
32
|
+
"For example, -1m is 1 month back, and 10d3m is 3 months and 10 days. " \
|
|
33
|
+
"An empty value or 0 leaves the date unchanged. The value start or end uses the " \
|
|
34
|
+
"record's start_date or end_date, and a date in YYYY-MM-DD format sets the date to that date."
|
|
35
|
+
|
|
36
|
+
desc "Moves the start_date and the end_date of each record. " \
|
|
37
|
+
"Use it to shift or widen a record's dates before you compare them with other records."
|
|
28
38
|
option :start, type: :string,
|
|
29
|
-
instructions: 'Enter a
|
|
39
|
+
instructions: 'Enter a number followed by "d", "w", "m" or "y" for days, weeks, months or years, such as 30d. A negative number moves the date earlier. You can also enter start, end or a date in YYYY-MM-DD format.',
|
|
40
|
+
meaning: "How to change the start_date. If you omit it, the start_date does not change. #{DATE_ADJUSTMENT_TEXT}"
|
|
30
41
|
option :end, type: :string,
|
|
31
|
-
instructions: 'Enter a
|
|
42
|
+
instructions: 'Enter a number followed by "d", "w", "m" or "y" for days, weeks, months or years, such as 30d. A negative number moves the date earlier. You can also enter start, end or a date in YYYY-MM-DD format.',
|
|
43
|
+
meaning: "How to change the end_date. If you omit it, the end_date does not change. #{DATE_ADJUSTMENT_TEXT}"
|
|
44
|
+
returns "One record for each upstream record, with the start_date and the end_date that the options give. " \
|
|
45
|
+
"The other columns do not change."
|
|
46
|
+
example "All Diagnoses of Diabetes (ICD-9 250.01) within 30 days of an MI",
|
|
47
|
+
[ "during", { "left" => [ "icd9", "250.01" ], "right" => [ "time_window", [ "icd9", "412" ], { "start" => "-30d", "end" => "30d" } ] } ]
|
|
48
|
+
example "Shift the dates of all MIs back by 200 years", [ "time_window", [ "icd9", "412" ], { "start" => "-200y", "end" => "-200y" } ]
|
|
49
|
+
example "Widen the dates of all MIs to run from 2 months and 2 days before the MI to 1 year and 3 days after it",
|
|
50
|
+
[ "time_window", [ "icd9", "412" ], { "start" => "-2m-2d", "end" => "3d1y" } ]
|
|
51
|
+
example "Collapse the date range of all MIs to the start date: leave start_date unchanged and set end_date to start_date",
|
|
52
|
+
[ "time_window", [ "icd9", "412" ], { "start" => "", "end" => "start" } ]
|
|
53
|
+
example "Nonsensical, but allowed: swap the start_date and end_date",
|
|
54
|
+
[ "time_window", [ "icd9", "412" ], { "start" => "end", "end" => "start" } ]
|
|
32
55
|
allows_one_upstream
|
|
33
56
|
validate_one_upstream
|
|
34
57
|
validate_no_arguments
|
|
@@ -4,6 +4,11 @@ require_relative "temporal_operator"
|
|
|
4
4
|
|
|
5
5
|
module ConceptQL
|
|
6
6
|
module Operators
|
|
7
|
+
# The base class of trim_date_end and trim_date_start. It does not
|
|
8
|
+
# register, so trim_date is not an operator name, and its subclasses set
|
|
9
|
+
# their own desc. The rest of this comment describes trim_date_end.
|
|
10
|
+
# trim_date_start does the same with the dates reversed.
|
|
11
|
+
#
|
|
7
12
|
# Trims the end_date of the LHS set of records by the RHS's earliest
|
|
8
13
|
# start_date (per person)
|
|
9
14
|
# If a the RHS contains a start_date that comes before the LHS's start_date
|
|
@@ -27,20 +32,7 @@ module ConceptQL
|
|
|
27
32
|
# (trim_date_start). An LHS record is discarded only when it has RHS
|
|
28
33
|
# records and none of them passes.
|
|
29
34
|
class TrimDate < TemporalOperator
|
|
30
|
-
|
|
31
|
-
Trims the end_date of the left hand records (LHR) by the earliest
|
|
32
|
-
start_date (per person) in the right hand records (RHR)
|
|
33
|
-
If the RHR contains a start_date that comes before the start_date in the LHR
|
|
34
|
-
that record in the LHR is completely discarded.
|
|
35
|
-
|
|
36
|
-
If there is no record in the RHR for a record in the LHR, the record in the LHR is passed
|
|
37
|
-
through unaffected.
|
|
38
|
-
|
|
39
|
-
If the start_date of the record in the RHR is later than the end_date of the record in the LHR, the record in the LHR
|
|
40
|
-
is passed through unaffected.
|
|
41
|
-
EOF
|
|
42
|
-
|
|
43
|
-
allows_one_upstream
|
|
35
|
+
no_desc
|
|
44
36
|
|
|
45
37
|
def query(db)
|
|
46
38
|
# If the RHS's min start date is less than the LHS start date,
|
|
@@ -17,9 +17,15 @@ module ConceptQL
|
|
|
17
17
|
class TrimDateEnd < TrimDate
|
|
18
18
|
register __FILE__
|
|
19
19
|
|
|
20
|
-
desc "For each
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
desc "For each patient, moves the end_date of each left-hand record back to the start_date of the right-hand record with the earliest start_date. A left-hand record that starts after that start_date is removed, and the left-hand records of a patient without right-hand records pass through unchanged."
|
|
21
|
+
returns "The left-hand records, each with its end_date set to the earlier of its own end_date and the start_date of the patient's right-hand record. Left-hand records that start after that start_date are not returned. The right-hand records are not returned."
|
|
22
|
+
option_meaning :left, "The left-hand stream. The operator moves the end_date of these records and passes them on."
|
|
23
|
+
option_meaning :right, "The right-hand stream. For each patient, the earliest start_date of these records sets the latest end_date of the left-hand records. These records are not passed on."
|
|
24
|
+
option_meaning :within, "A date adjustment, such as 30d, 2m or 1y-3d, that is subtracted from the start_date of the right-hand record before the comparison. The adjusted date is the one that moves the end_date of the left-hand record."
|
|
25
|
+
|
|
26
|
+
example "Each patient's span from birth to age 50 or to 1970-01-01, whichever is earlier (patients born after 1970 are dropped)",
|
|
27
|
+
[ "trim_date_end", { left: [ "time_window", [ "person" ], { end: "+50y" } ],
|
|
28
|
+
right: [ "date_range", { start: "1970-01-01", end: "1970-01-01" } ] } ]
|
|
23
29
|
|
|
24
30
|
private
|
|
25
31
|
|
|
@@ -17,9 +17,15 @@ module ConceptQL
|
|
|
17
17
|
class TrimDateStart < TrimDate
|
|
18
18
|
register __FILE__
|
|
19
19
|
|
|
20
|
-
desc "For each
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
desc "For each patient, moves the start_date of each left-hand record forward to the end_date of the right-hand record with the latest start_date. A left-hand record that ends before that end_date is removed, and the left-hand records of a patient without right-hand records pass through unchanged."
|
|
21
|
+
returns "The left-hand records, each with its start_date set to the later of its own start_date and the end_date of the patient's right-hand record. Left-hand records that end before that end_date are not returned. The right-hand records are not returned."
|
|
22
|
+
option_meaning :left, "The left-hand stream. The operator moves the start_date of these records and passes them on."
|
|
23
|
+
option_meaning :right, "The right-hand stream. For each patient, the end_date of the record with the latest start_date sets the earliest start_date of the left-hand records. These records are not passed on."
|
|
24
|
+
option_meaning :within, "A date adjustment, such as 30d, 2m or 1y-3d, that is added to the end_date of the right-hand record before the comparison. The adjusted date is the one that moves the start_date of the left-hand record."
|
|
25
|
+
|
|
26
|
+
example "Each patient's span from birth to age 50, trimmed to start no earlier than 1980-01-01 (patients who were 50 before 1980 are dropped)",
|
|
27
|
+
[ "trim_date_start", { left: [ "time_window", [ "person" ], { end: "+50y" } ],
|
|
28
|
+
right: [ "date_range", { start: "1980-01-01", end: "1980-01-01" } ] } ]
|
|
23
29
|
|
|
24
30
|
private
|
|
25
31
|
|
|
@@ -54,7 +54,19 @@ module ConceptQL
|
|
|
54
54
|
vocabularies.length > 1
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
desc "Combines
|
|
57
|
+
desc "Combines the records of all its upstream operators into one stream. " \
|
|
58
|
+
"Each record keeps its own type, so one stream can hold several types."
|
|
59
|
+
|
|
60
|
+
returns "Each record of each upstream. A record that comes from more than one upstream comes out once. " \
|
|
61
|
+
"Two records are the same only when every column is equal, including label."
|
|
62
|
+
|
|
63
|
+
example "Two streams of the same type (condition_occurrence) combined into one stream",
|
|
64
|
+
[ "union", [ "icd9", "412" ], [ "icd9", "250.01" ] ]
|
|
65
|
+
example "Two condition_occurrence streams combined, then combined with a procedure_occurrence stream",
|
|
66
|
+
[ "union", [ "union", [ "icd9", "412" ], [ "icd9", "250.01" ] ], [ "cpt", "99214" ] ]
|
|
67
|
+
example "Two condition_occurrence streams and a procedure_occurrence stream combined by a single union",
|
|
68
|
+
[ "union", [ "icd9", "412" ], [ "icd9", "250.01" ], [ "cpt", "99214" ] ]
|
|
69
|
+
|
|
58
70
|
allows_many_upstreams
|
|
59
71
|
category "Combine Streams"
|
|
60
72
|
default_query_columns
|