cqm-models 1.0.3 → 1.1.1.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 (188) hide show
  1. checksums.yaml +5 -5
  2. data/.eslintrc.json +10 -2
  3. data/.github/PULL_REQUEST_TEMPLATE.md +0 -4
  4. data/.rubocop.yml +3 -0
  5. data/.travis.yml +31 -1
  6. data/README.md +59 -2
  7. data/app/assets/javascripts/AdverseEvent.js +16 -7
  8. data/app/assets/javascripts/AllDataElements.js +6 -6
  9. data/app/assets/javascripts/AllergyIntolerance.js +16 -7
  10. data/app/assets/javascripts/AssessmentOrder.js +15 -6
  11. data/app/assets/javascripts/AssessmentPerformed.js +16 -7
  12. data/app/assets/javascripts/AssessmentRecommended.js +16 -7
  13. data/app/assets/javascripts/CareGoal.js +16 -7
  14. data/app/assets/javascripts/CommunicationPerformed.js +15 -6
  15. data/app/assets/javascripts/DeviceApplied.js +16 -7
  16. data/app/assets/javascripts/DeviceOrder.js +16 -7
  17. data/app/assets/javascripts/DeviceRecommended.js +16 -7
  18. data/app/assets/javascripts/Diagnosis.js +16 -7
  19. data/app/assets/javascripts/DiagnosticStudyOrder.js +16 -7
  20. data/app/assets/javascripts/DiagnosticStudyPerformed.js +16 -7
  21. data/app/assets/javascripts/DiagnosticStudyRecommended.js +16 -7
  22. data/app/assets/javascripts/EncounterOrder.js +16 -7
  23. data/app/assets/javascripts/EncounterPerformed.js +16 -7
  24. data/app/assets/javascripts/EncounterRecommended.js +16 -7
  25. data/app/assets/javascripts/FamilyHistory.js +16 -7
  26. data/app/assets/javascripts/Id.js +7 -3
  27. data/app/assets/javascripts/ImmunizationAdministered.js +16 -7
  28. data/app/assets/javascripts/ImmunizationOrder.js +16 -7
  29. data/app/assets/javascripts/IndividualResult.js +7 -2
  30. data/app/assets/javascripts/InterventionOrder.js +16 -7
  31. data/app/assets/javascripts/InterventionPerformed.js +16 -7
  32. data/app/assets/javascripts/InterventionRecommended.js +16 -7
  33. data/app/assets/javascripts/LaboratoryTestOrder.js +16 -7
  34. data/app/assets/javascripts/LaboratoryTestPerformed.js +16 -7
  35. data/app/assets/javascripts/LaboratoryTestRecommended.js +16 -7
  36. data/app/assets/javascripts/MedicationActive.js +16 -7
  37. data/app/assets/javascripts/MedicationAdministered.js +16 -7
  38. data/app/assets/javascripts/MedicationDischarge.js +16 -7
  39. data/app/assets/javascripts/MedicationDispensed.js +16 -7
  40. data/app/assets/javascripts/MedicationOrder.js +16 -7
  41. data/app/assets/javascripts/Participation.js +15 -6
  42. data/app/assets/javascripts/PatientCareExperience.js +16 -7
  43. data/app/assets/javascripts/PatientCharacteristic.js +16 -7
  44. data/app/assets/javascripts/PatientCharacteristicBirthdate.js +16 -7
  45. data/app/assets/javascripts/PatientCharacteristicClinicalTrialParticipant.js +16 -7
  46. data/app/assets/javascripts/PatientCharacteristicEthnicity.js +16 -7
  47. data/app/assets/javascripts/PatientCharacteristicExpired.js +16 -7
  48. data/app/assets/javascripts/PatientCharacteristicPayer.js +16 -7
  49. data/app/assets/javascripts/PatientCharacteristicRace.js +16 -7
  50. data/app/assets/javascripts/PatientCharacteristicSex.js +16 -7
  51. data/app/assets/javascripts/PhysicalExamOrder.js +16 -7
  52. data/app/assets/javascripts/PhysicalExamPerformed.js +16 -7
  53. data/app/assets/javascripts/PhysicalExamRecommended.js +16 -7
  54. data/app/assets/javascripts/ProcedureOrder.js +16 -7
  55. data/app/assets/javascripts/ProcedurePerformed.js +16 -7
  56. data/app/assets/javascripts/ProcedureRecommended.js +16 -7
  57. data/app/assets/javascripts/ProviderCareExperience.js +16 -7
  58. data/app/assets/javascripts/ProviderCharacteristic.js +16 -7
  59. data/app/assets/javascripts/QDMPatient.js +262 -0
  60. data/app/assets/javascripts/Result.js +7 -2
  61. data/app/assets/javascripts/ResultComponent.js +14 -6
  62. data/app/assets/javascripts/SubstanceAdministered.js +16 -7
  63. data/app/assets/javascripts/SubstanceOrder.js +16 -7
  64. data/app/assets/javascripts/SubstanceRecommended.js +16 -7
  65. data/app/assets/javascripts/Symptom.js +16 -7
  66. data/app/assets/javascripts/attributes/Component.js +30 -0
  67. data/app/assets/javascripts/attributes/FacilityLocation.js +30 -0
  68. data/app/assets/javascripts/basetypes/Any.js +1 -1
  69. data/app/assets/javascripts/basetypes/Code.js +1 -1
  70. data/app/assets/javascripts/basetypes/DataElement.js +2 -1
  71. data/app/assets/javascripts/basetypes/DateTime.js +5 -1
  72. data/app/assets/javascripts/basetypes/Interval.js +1 -1
  73. data/app/assets/javascripts/basetypes/Quantity.js +1 -1
  74. data/app/assets/javascripts/basetypes/Ratio.js +1 -1
  75. data/app/assets/javascripts/browser.js +3 -0
  76. data/app/assets/javascripts/cqm/AllCQMModels.js +24 -0
  77. data/app/assets/javascripts/cqm/CQLLibrary.js +32 -0
  78. data/app/assets/javascripts/cqm/CQLStatementDependency.js +28 -0
  79. data/app/assets/javascripts/cqm/Concept.js +17 -0
  80. data/app/assets/javascripts/cqm/Measure.js +108 -0
  81. data/app/assets/javascripts/{MeasurePackage.js → cqm/MeasurePackage.js} +9 -3
  82. data/app/assets/javascripts/cqm/Patient.js +38 -0
  83. data/app/assets/javascripts/cqm/PopulationSet.js +88 -0
  84. data/app/assets/javascripts/cqm/Provider.js +44 -0
  85. data/app/assets/javascripts/cqm/ValueSet.js +26 -0
  86. data/app/assets/javascripts/index.js +18 -8
  87. data/app/models/{qdm/tacoma → cqm}/concept.rb +4 -4
  88. data/app/models/cqm/cql_library.rb +22 -0
  89. data/app/models/cqm/cql_statement_dependency.rb +24 -0
  90. data/app/models/cqm/measure.rb +105 -0
  91. data/app/models/{qdm/tacoma → cqm}/measure_package.rb +2 -2
  92. data/app/models/cqm/patient.rb +20 -0
  93. data/app/models/cqm/population_set.rb +86 -0
  94. data/app/models/cqm/provider.rb +110 -0
  95. data/app/models/cqm/valueset.rb +15 -0
  96. data/app/models/hqmfOid_to_datatype_map.json +55 -0
  97. data/app/models/model_finder.rb +12 -0
  98. data/app/models/models.rb +18 -6
  99. data/app/models/qdm/adverse_event.rb +3 -2
  100. data/app/models/qdm/allergy_intolerance.rb +2 -1
  101. data/app/models/qdm/assessment_order.rb +1 -0
  102. data/app/models/qdm/assessment_performed.rb +2 -1
  103. data/app/models/qdm/assessment_recommended.rb +2 -1
  104. data/app/models/qdm/attributes/attribute.rb +63 -0
  105. data/app/models/qdm/{component.rb → attributes/component.rb} +1 -1
  106. data/app/models/qdm/{facility_location.rb → attributes/facility_location.rb} +1 -1
  107. data/app/models/qdm/basetypes/data_element.rb +21 -6
  108. data/app/models/qdm/basetypes/interval.rb +2 -1
  109. data/app/models/qdm/care_goal.rb +2 -1
  110. data/app/models/qdm/communication_performed.rb +1 -0
  111. data/app/models/qdm/device_applied.rb +2 -1
  112. data/app/models/qdm/device_order.rb +2 -1
  113. data/app/models/qdm/device_recommended.rb +2 -1
  114. data/app/models/qdm/diagnosis.rb +2 -1
  115. data/app/models/qdm/diagnostic_study_order.rb +2 -1
  116. data/app/models/qdm/diagnostic_study_performed.rb +3 -2
  117. data/app/models/qdm/diagnostic_study_recommended.rb +2 -1
  118. data/app/models/qdm/encounter_order.rb +3 -2
  119. data/app/models/qdm/encounter_performed.rb +2 -1
  120. data/app/models/qdm/encounter_recommended.rb +3 -2
  121. data/app/models/qdm/family_history.rb +2 -1
  122. data/app/models/qdm/immunization_administered.rb +2 -1
  123. data/app/models/qdm/immunization_order.rb +2 -1
  124. data/app/models/qdm/intervention_order.rb +2 -1
  125. data/app/models/qdm/intervention_performed.rb +2 -1
  126. data/app/models/qdm/intervention_recommended.rb +2 -1
  127. data/app/models/qdm/laboratory_test_order.rb +2 -1
  128. data/app/models/qdm/laboratory_test_performed.rb +2 -1
  129. data/app/models/qdm/laboratory_test_recommended.rb +2 -1
  130. data/app/models/qdm/medication_active.rb +2 -1
  131. data/app/models/qdm/medication_administered.rb +2 -1
  132. data/app/models/qdm/medication_discharge.rb +2 -1
  133. data/app/models/qdm/medication_dispensed.rb +4 -3
  134. data/app/models/qdm/medication_order.rb +3 -2
  135. data/app/models/qdm/participation.rb +1 -0
  136. data/app/models/qdm/patient.rb +7 -4
  137. data/app/models/qdm/patient_care_experience.rb +2 -1
  138. data/app/models/qdm/patient_characteristic.rb +2 -1
  139. data/app/models/qdm/patient_characteristic_birthdate.rb +2 -1
  140. data/app/models/qdm/patient_characteristic_clinical_trial_participant.rb +2 -1
  141. data/app/models/qdm/patient_characteristic_ethnicity.rb +2 -1
  142. data/app/models/qdm/patient_characteristic_expired.rb +2 -1
  143. data/app/models/qdm/patient_characteristic_payer.rb +2 -1
  144. data/app/models/qdm/patient_characteristic_race.rb +2 -1
  145. data/app/models/qdm/patient_characteristic_sex.rb +2 -1
  146. data/app/models/qdm/physical_exam_order.rb +2 -1
  147. data/app/models/qdm/physical_exam_performed.rb +2 -1
  148. data/app/models/qdm/physical_exam_recommended.rb +2 -1
  149. data/app/models/qdm/procedure_order.rb +2 -1
  150. data/app/models/qdm/procedure_performed.rb +2 -1
  151. data/app/models/qdm/procedure_recommended.rb +2 -1
  152. data/app/models/qdm/provider_care_experience.rb +2 -1
  153. data/app/models/qdm/provider_characteristic.rb +2 -1
  154. data/app/models/qdm/substance_administered.rb +2 -1
  155. data/app/models/qdm/substance_order.rb +2 -1
  156. data/app/models/qdm/substance_recommended.rb +2 -1
  157. data/app/models/qdm/symptom.rb +2 -1
  158. data/bin/build_cql_execution.sh +8 -0
  159. data/bin/validate_browser.sh +17 -0
  160. data/bin/validate_generator.sh +12 -0
  161. data/cqm-models.gemspec +5 -2
  162. data/data/oids_qdm_5.4.json +321 -0
  163. data/data/{oids.json → oids_unversioned.json} +0 -0
  164. data/dist/browser.js +102937 -0
  165. data/dist/index.js +36637 -36540
  166. data/lib/generate_models.rb +89 -93
  167. data/lib/generate_patients.rb +125 -0
  168. data/lib/generate_types.rb +64 -0
  169. data/lib/generators/custom_mongo/model.rb.tt +20 -0
  170. data/lib/generators/custom_mongo/model_generator.rb +34 -0
  171. data/notice.md +9 -0
  172. data/package.json +12 -6
  173. data/templates/id_template.js.erb +8 -3
  174. data/templates/index_template.js.erb +18 -8
  175. data/templates/models_template.rb.erb +20 -4
  176. data/templates/mongoose_template.js.erb +23 -15
  177. data/templates/patient_extension.rb.erb +7 -4
  178. data/templates/patient_template.js.erb +88 -87
  179. data/yarn.lock +552 -347
  180. metadata +86 -19
  181. data/app/assets/javascripts/Component.js +0 -26
  182. data/app/assets/javascripts/Concept.js +0 -14
  183. data/app/assets/javascripts/FacilityLocation.js +0 -26
  184. data/app/assets/javascripts/Measure.js +0 -78
  185. data/app/assets/javascripts/Patient.js +0 -262
  186. data/app/assets/javascripts/ValueSet.js +0 -30
  187. data/app/models/qdm/tacoma/measure.rb +0 -174
  188. data/app/models/qdm/tacoma/valueset.rb +0 -68
@@ -1,30 +0,0 @@
1
- const mongoose = require('mongoose');
2
- const Concept = require('./Concept.js');
3
-
4
- const [String, Mixed, ObjectId] = [
5
- mongoose.Schema.Types.String,
6
- mongoose.Schema.Types.Mixed,
7
- mongoose.Schema.Types.ObjectId,
8
- ];
9
-
10
- const ValueSetSchema = mongoose.Schema(
11
- {
12
- oid: String,
13
- display_name: String,
14
- version: String,
15
- categories: Mixed,
16
-
17
- concepts: [Concept.ConceptSchema],
18
-
19
- user: { type: ObjectId, ref: 'User', index: true }, // Bonnie-specific
20
- bundle: { type: ObjectId, ref: 'Bundle' }, // Cypress-specific
21
- measures: [{ type: ObjectId, ref: 'Measure' }],
22
- },
23
- // Options
24
- {
25
- timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' },
26
- }
27
- );
28
-
29
- module.exports.ValueSetSchema = ValueSetSchema;
30
- module.exports.ValueSet = mongoose.model('value_set', ValueSetSchema);
@@ -1,174 +0,0 @@
1
- module QDM
2
- # Measure contains the information necessary to represent the CQL version of a Clinical Quality Measure,
3
- # As needed by the Bonnie & Cypress applications
4
- class Measure
5
- include Mongoid::Document
6
- include Mongoid::Timestamps
7
-
8
- DEFAULT_EFFECTIVE_DATE = Time.gm(2012, 12, 31, 23, 59).to_i
9
- MP_START_DATE = Time.gm(2012, 1, 1, 0, 0).to_i
10
- TYPES = %w[ep eh].freeze
11
-
12
- IPP = 'IPP'.freeze
13
- DENOM = 'DENOM'.freeze
14
- NUMER = 'NUMER'.freeze
15
- NUMEX = 'NUMEX'.freeze
16
- DENEXCEP = 'DENEXCEP'.freeze
17
- DENEX = 'DENEX'.freeze
18
- MSRPOPL = 'MSRPOPL'.freeze
19
- OBSERV = 'OBSERV'.freeze
20
- MSRPOPLEX = 'MSRPOPLEX'.freeze
21
-
22
- STRAT = 'STRAT'.freeze
23
-
24
- ALL_POPULATION_CODES = [STRAT, IPP, DENOM, DENEX, NUMER, NUMEX, DENEXCEP, MSRPOPL, OBSERV, MSRPOPLEX].freeze
25
-
26
- CQL_SKIP_STATEMENTS = ['SDE Ethnicity', 'SDE Payer', 'SDE Race', 'SDE Sex'].freeze
27
-
28
- # ID/other measure information
29
- field :id, type: String
30
- field :measure_id, type: String
31
- field :hqmf_id, type: String
32
- field :hqmf_set_id, type: String
33
- field :hqmf_version_number, type: Integer
34
- field :cms_id, type: String
35
- field :title, type: String, default: ''
36
- field :description, type: String, default: ''
37
- field :type, type: String
38
- field :category, type: String, default: 'uncategorized'
39
-
40
- # Measure type variables
41
- field :episode_of_care, type: Boolean
42
- field :continuous_variable, type: Boolean
43
- field :episode_ids, type: Array
44
-
45
- # Publishing data (used by Bonnie)
46
- field :published, type: Boolean
47
- field :publish_date, type: Date
48
- field :version, type: Integer
49
-
50
- # ELM/CQL Measure-logic related data
51
- field :elm_annotations, type: Hash
52
- field :cql, type: Array
53
- field :elm, type: Array
54
- field :main_cql_library, type: String
55
- field :cql_statement_dependencies, type: Hash
56
-
57
- # HQMF/Tacoma-specific Measure-logic related data
58
- field :population_criteria, type: Hash
59
- field :data_criteria, type: Hash
60
- field :source_data_criteria, type: Hash
61
- field :measure_period, type: Hash
62
- field :measure_attributes, type: Array
63
- field :populations, type: Array
64
- field :populations_cql_map, type: Hash
65
- field :observations, type: Array
66
- # TODO: Depending on how we restructure the Measure/Population object, may be deleted in the future
67
- field :population_ids, type: Hash
68
-
69
- field :value_set_oids, type: Array, default: []
70
- field :value_set_oid_version_objects, type: Array, default: []
71
-
72
- field :complexity, type: Hash
73
-
74
- # Relations to other model classes
75
- belongs_to :user
76
- belongs_to :bundle, class_name: 'HealthDataStandards::CQM::Bundle'
77
- has_and_belongs_to_many :patients, inverse_of: nil
78
- has_one :package, class_name: 'CqlMeasurePackage', inverse_of: :measure, dependent: :destroy
79
-
80
- scope :by_measure_id, ->(id) { where('measure_id' => id) }
81
- scope :by_type, ->(type) { where('type' => type) }
82
- scope :by_user, ->(user) { where user_id: user.id }
83
-
84
- index 'user_id' => 1
85
- # Find the measures matching a patient
86
- def self.for_patient(record)
87
- where user_id: record.user_id, hqmf_set_id: { '$in' => record.measure_ids }
88
- end
89
-
90
- def value_sets
91
- options = { oid: value_set_oids }
92
- options[:user_id] = user.id if user?
93
- @value_sets ||= HealthDataStandards::SVS::ValueSet.in(options)
94
- @value_sets
95
- end
96
-
97
- # Returns the hqmf-parser's ruby implementation of an HQMF document.
98
- # Rebuild from population_criteria, data_criteria, and measure_period JSON
99
- def as_hqmf_model
100
- json = {
101
- 'id' => measure_id,
102
- 'title' => title,
103
- 'description' => description,
104
- 'population_criteria' => population_criteria,
105
- 'data_criteria' => data_criteria,
106
- 'source_data_criteria' => source_data_criteria,
107
- 'measure_period' => measure_period,
108
- 'attributes' => measure_attributes,
109
- 'populations' => populations,
110
- 'hqmf_id' => hqmf_id,
111
- 'hqmf_set_id' => hqmf_set_id,
112
- 'hqmf_version_number' => hqmf_version_number,
113
- 'cms_id' => cms_id
114
- }
115
- HQMF::Document.from_json(json)
116
- end
117
-
118
- def all_data_criteria
119
- as_hqmf_model.all_data_criteria
120
- end
121
-
122
- # Note whether or not the measure is a continuous variable measure.
123
- before_save :set_continuous_variable
124
- def set_continuous_variable
125
- # The return value of this function is not related to whether or not this
126
- # measure is a CV measure. The true return value ensures false is not
127
- # accidentally returned here, which would cause the chain of 'before_*' to
128
- # stop executing.
129
- self.continuous_variable = populations.map(&:keys).flatten.uniq.include? HQMF::PopulationCriteria::MSRPOPL
130
- true
131
- end
132
-
133
- # When saving calculate the cyclomatic complexity of the measure
134
- # TODO: Do we want to consider a measure other than "cyclomatic complexity" for CQL?
135
- # TODO: THIS IS NOT CYCLOMATIC COMPLEXITY, ALL MULTIPLE ELEMENT EXPRESSIONS GET COUNTED AS HIGHER COMPLEXITY, NOT JUST LOGICAL
136
- before_save :calculate_complexity
137
- def calculate_complexity
138
- # We calculate the complexity for each statement, and (at least for now) store the result in the same way
139
- # we store the complexity for QDM variables
140
- # TODO: consider whether this is too much of a force fit
141
- self.complexity = { variables: [] }
142
- # Recursively look through an expression to count the logical branches
143
- def count_expression_logical_branches(expression)
144
- case expression
145
- when nil
146
- 0
147
- when Array
148
- expression.map { |exp| count_expression_logical_branches(exp) }.sum
149
- when Hash
150
- case expression['type']
151
- when 'And', 'Or', 'Not'
152
- count_expression_logical_branches(expression['operand'])
153
- when 'Query'
154
- # TODO: Do we need to look into the source side of the query? Can there be logical operators there?
155
- count_expression_logical_branches(expression['where']) + count_expression_logical_branches(expression['relationship'])
156
- else
157
- 1
158
- end
159
- else
160
- 0
161
- end
162
- end
163
-
164
- # Determine the complexity of each statement
165
- elm.each do |elm|
166
- next unless (statements = elm.try(:[], 'library').try(:[], 'statements').try(:[], 'def'))
167
- statements.each do |statement|
168
- complexity[:variables] << { name: statement['name'], complexity: count_expression_logical_branches(statement['expression']) }
169
- end
170
- end
171
- complexity
172
- end
173
- end
174
- end
@@ -1,68 +0,0 @@
1
- module QDM
2
- # ValueSet represents a collection of Concepts, used by the Measures to specify a set of codes for a particular topic
3
- class ValueSet
4
- include Mongoid::Document
5
- field :oid, type: String
6
- field :display_name, type: String
7
- field :version, type: String
8
- field :user_id, type: String # Eventually we need to delete this from bundles when exporting
9
- field :categories, type: Hash
10
-
11
- index(oid: 1)
12
- embeds_many :concepts
13
- index 'concepts.code' => 1
14
- index 'concepts.code_system' => 1
15
- index 'concepts.code_system_name' => 1
16
- index 'bundle_id' => 1
17
- scope :by_oid, ->(oid) { where(oid: oid) }
18
-
19
- # Provides an Array of Hashes. Each code system gets its own Hash
20
- # The hash has a key of "set" for the code system name and "values"
21
- # for the actual code list
22
- def code_set_map
23
- codes = []
24
- concept_pairs = concepts.each_with_object({}) do |concept, memo|
25
- memo[concept.code_system_name] ||= []
26
- memo[concept.code_system_name] << concept.code
27
- end
28
- concept_pairs.each_pair do |code_set, code_list|
29
- codes << { 'set' => code_set, 'values' => code_list }
30
- end
31
-
32
- codes
33
- end
34
-
35
- def self.load_from_xml(doc)
36
- doc.root.add_namespace_definition('vs', 'urn:ihe:iti:svs:2008')
37
- vs_element = doc.at_xpath('/vs:RetrieveValueSetResponse/vs:ValueSet|/vs:RetrieveMultipleValueSetsResponse/vs:DescribedValueSet')
38
- return unless vs_element
39
- vs = ValueSet.new(oid: vs_element['ID'], display_name: vs_element['displayName'], version: vs_element['version'])
40
- vs.concepts = extract_concepts(vs_element)
41
- vs.categories = extract_categories(vs_element)
42
- vs
43
- end
44
-
45
- def self.extract_concepts(vs_element)
46
- vs_element.xpath('//vs:Concept').collect do |con|
47
- code_system_name = HealthDataStandards::Util::CodeSystemHelper::CODE_SYSTEMS[con['codeSystem']] || con['codeSystemName']
48
- Concept.new(code: con['code'],
49
- code_system_name: code_system_name,
50
- code_system_version: con['codeSystemVersion'],
51
- display_name: con['displayName'], code_system: con['codeSystem'])
52
- end
53
- end
54
-
55
- def self.extract_categories(vs_element)
56
- category_hash = Hash.new { |h, k| h[k] = [] }
57
- groups_with_categories = vs_element.xpath("//vs:Group/@ID[../@displayName='CATEGORY']")
58
- groups_with_categories.each do |group_number|
59
- measure = vs_element.xpath("//vs:Group[@displayName='CMS eMeasure ID' and @ID='#{group_number}']/vs:Keyword").text
60
- categories_for_group = vs_element.xpath("//vs:Group[@displayName='CATEGORY' and @ID='#{group_number}']/vs:Keyword")
61
- categories_for_group.each do |category|
62
- category_hash[measure] << category.text
63
- end
64
- end
65
- !category_hash.empty? ? category_hash : nil
66
- end
67
- end
68
- end