cqm-models 1.0.3 → 1.1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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