inferno_core 0.6.7 → 0.6.8
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/lib/inferno/apps/cli/evaluate.rb +6 -1
- data/lib/inferno/dsl/fhir_evaluation/rules/all_defined_extensions_have_examples.rb +58 -0
- data/lib/inferno/dsl/fhir_evaluation/rules/all_profiles_have_examples.rb +49 -0
- data/lib/inferno/dsl/fhir_evaluation/rules/all_search_parameters_have_examples.rb +79 -0
- data/lib/inferno/public/bundle.js +3 -3
- data/lib/inferno/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6daa4c1864a7e7004ca9a166f3e516fcddc157c3f6b6e24722965e44330cc450
|
4
|
+
data.tar.gz: b158c90f63eaa8a157a27b92af68b7a3be7798e18aca2243ecbd1071c9d9bfdf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6298ba6c1f61a6ee3f5e50045ac58e1ebaa63a2ebc863755d1c610d3eed4b0e7ca76ea74ee12c506298624248c5f3b874582eaccc1c41600f731b33a601d72ce
|
7
|
+
data.tar.gz: 61f9b65616cf01dd42c838ab0b9be5c38490dea22bb2a3481a82c30a25c687e2aada97321498920f6219a62afe88445ad95e71b2300859fce5ed19af7cef1f78
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require_relative '../../dsl/fhir_evaluation/evaluator'
|
2
2
|
require_relative '../../dsl/fhir_evaluation/config'
|
3
3
|
require_relative '../../entities'
|
4
|
-
require_relative '../../repositories'
|
5
4
|
require_relative '../../utils/ig_downloader'
|
6
5
|
|
7
6
|
require 'tempfile'
|
@@ -10,6 +9,12 @@ module Inferno
|
|
10
9
|
module CLI
|
11
10
|
class Evaluate < Thor::Group
|
12
11
|
def evaluate(ig_path, data_path, _log_level)
|
12
|
+
# NOTE: repositories is required here rather than at the top of the file because
|
13
|
+
# the tree of requires means that this file and its requires get required by every CLI app.
|
14
|
+
# Sequel::Model, used in some repositories, fetches the table schema at instantiation.
|
15
|
+
# This breaks the `migrate` task by fetching a table before the task runs/creates it.
|
16
|
+
require_relative '../../repositories'
|
17
|
+
|
13
18
|
validate_args(ig_path, data_path)
|
14
19
|
ig = Inferno::Repositories::IGs.new.find_or_load(ig_path)
|
15
20
|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Inferno
|
4
|
+
module DSL
|
5
|
+
module FHIREvaluation
|
6
|
+
module Rules
|
7
|
+
class AllDefinedExtensionsHaveExamples < Rule
|
8
|
+
attr_accessor :used_extensions, :unused_extensions
|
9
|
+
|
10
|
+
def check(context)
|
11
|
+
@used_extensions = context.data.map { |e| extension_urls(e) }.flatten.uniq
|
12
|
+
@unused_extensions = []
|
13
|
+
|
14
|
+
get_unused_extensions(context.ig.extensions) do |extension|
|
15
|
+
next true if extension.context.any? do |ctx|
|
16
|
+
# Skip extensions that are defined for definitional artifacts.
|
17
|
+
# For example, US Core's uscdi-requirement extension is applied to
|
18
|
+
# the profiles and extensions of the IG, not data that conforms to the IG.
|
19
|
+
# There may eventually be cases where SD/ED are data, so this may become configurable.
|
20
|
+
ctx.expression == 'StructureDefinition' || ctx.expression == 'ElementDefinition'
|
21
|
+
end
|
22
|
+
|
23
|
+
versioned_url = "#{extension.url}|#{extension.version}"
|
24
|
+
used_extensions.include?(extension.url) || used_extensions.include?(versioned_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
if unused_extensions.any?
|
28
|
+
message = "Found unused extensions defined in the IG: \n\t #{unused_extensions.join(', ')}"
|
29
|
+
result = EvaluationResult.new(message, rule: self)
|
30
|
+
else
|
31
|
+
message = 'All defined extensions are represented in examples'
|
32
|
+
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
33
|
+
end
|
34
|
+
|
35
|
+
context.add_result result
|
36
|
+
end
|
37
|
+
|
38
|
+
def extension_urls(resource)
|
39
|
+
urls = []
|
40
|
+
resource.each_element do |value, _metadata, path|
|
41
|
+
path_elements = path.split('.')
|
42
|
+
next unless path_elements.length > 1
|
43
|
+
|
44
|
+
urls << value if path_elements[-2].include?('extension') && path_elements[-1] == 'url'
|
45
|
+
end
|
46
|
+
urls.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_unused_extensions(extensions, &extension_filter)
|
50
|
+
extensions.each do |extension|
|
51
|
+
unused_extensions.push extension.url unless extension_filter.call(extension)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Inferno
|
2
|
+
module DSL
|
3
|
+
module FHIREvaluation
|
4
|
+
module Rules
|
5
|
+
class AllProfilesHaveExamples < Rule
|
6
|
+
include ProfileConformanceHelper
|
7
|
+
|
8
|
+
attr_accessor :context, :unused_profile_urls, :all_resources
|
9
|
+
|
10
|
+
def check(context)
|
11
|
+
@context = context
|
12
|
+
@unused_profile_urls = []
|
13
|
+
@all_resources = []
|
14
|
+
options = context.config.data['Rule']['AllProfilesHaveExamples']['ConformanceOptions'].to_options
|
15
|
+
|
16
|
+
context.data.map { |entry| extract_resources(entry) }
|
17
|
+
all_resources.uniq!
|
18
|
+
|
19
|
+
context.ig.profiles.each do |profile|
|
20
|
+
profile_used = all_resources.any? do |resource|
|
21
|
+
conforms_to_profile?(resource, profile, options, context.validator)
|
22
|
+
end
|
23
|
+
unused_profile_urls << profile.url unless profile_used
|
24
|
+
end
|
25
|
+
|
26
|
+
unused_profile_urls.uniq!
|
27
|
+
|
28
|
+
if unused_profile_urls.any?
|
29
|
+
message = "Found profiles without examples: \n\t #{unused_profile_urls.join(', ')}"
|
30
|
+
result = EvaluationResult.new(message, rule: self)
|
31
|
+
else
|
32
|
+
message = 'All profiles have example instances.'
|
33
|
+
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
34
|
+
end
|
35
|
+
|
36
|
+
context.add_result result
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_resources(resource)
|
40
|
+
all_resources << resource
|
41
|
+
return unless resource.resourceType == 'Bundle'
|
42
|
+
|
43
|
+
resource.entry.map { |entry| extract_resources(entry.resource) }.flatten
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require_relative '../../fhirpath_evaluation'
|
2
|
+
|
3
|
+
module Inferno
|
4
|
+
module DSL
|
5
|
+
module FHIREvaluation
|
6
|
+
module Rules
|
7
|
+
class AllSearchParametersHaveExamples < Rule
|
8
|
+
include FhirpathEvaluation
|
9
|
+
|
10
|
+
def check(context)
|
11
|
+
unless ENV['FHIRPATH_URL']
|
12
|
+
message = 'FHIRPATH_URL is not found. Skipping rule AllSearchParametersHaveExamples.'
|
13
|
+
result = EvaluationResult.new(message, severity: 'warning', rule: self)
|
14
|
+
context.add_result result
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
unused_resource_urls = []
|
19
|
+
search_params = context.ig.resources_by_type['SearchParameter']
|
20
|
+
|
21
|
+
search_params.each do |search_param|
|
22
|
+
unused_resource_urls.push search_param.url unless param_is_used?(search_param, context)
|
23
|
+
end
|
24
|
+
|
25
|
+
if unused_resource_urls.any?
|
26
|
+
message = "Found SearchParameters with no searchable data: \n\t#{unused_resource_urls.join(' ,')}"
|
27
|
+
result = EvaluationResult.new(message, rule: self)
|
28
|
+
elsif !search_params.empty?
|
29
|
+
message = 'All SearchParameters have examples'
|
30
|
+
result = EvaluationResult.new(message, severity: 'success', rule: self)
|
31
|
+
else
|
32
|
+
message = 'IG contains no SearchParameter'
|
33
|
+
result = EvaluationResult.new(message, severity: 'information', rule: self)
|
34
|
+
end
|
35
|
+
|
36
|
+
context.add_result result
|
37
|
+
end
|
38
|
+
|
39
|
+
def param_is_used?(param, context)
|
40
|
+
# Assume that all params have an expression (fhirpath)
|
41
|
+
# This is not guaranteed since the field is 0..1
|
42
|
+
# but without it there's no other way to select a value
|
43
|
+
# Return warning if params don't include expression
|
44
|
+
unless param.expression
|
45
|
+
message = "Search parameter #{param.url} doesn't have an expression."
|
46
|
+
result = EvaluationResult.new(message, severity: 'warning', rule: self)
|
47
|
+
context.add_result result
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
used = false
|
52
|
+
|
53
|
+
context.data.each do |resource|
|
54
|
+
next unless param.base.include? resource.resourceType
|
55
|
+
|
56
|
+
begin
|
57
|
+
result = evaluate_fhirpath(resource: resource, path: param.expression)
|
58
|
+
rescue StandardError => e
|
59
|
+
message = "SearchParameter #{param.url} failed to evaluate due to an error. " \
|
60
|
+
"Expression: #{param.expression}. #{e}"
|
61
|
+
result = EvaluationResult.new(message)
|
62
|
+
context.add_result result
|
63
|
+
|
64
|
+
used = true
|
65
|
+
break
|
66
|
+
end
|
67
|
+
|
68
|
+
if result.present?
|
69
|
+
used = true
|
70
|
+
break
|
71
|
+
end
|
72
|
+
end
|
73
|
+
used
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|