Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jhlee-mitre committed Nov 29, 2024
1 parent e3b2d3a commit 2120d40
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 53 deletions.
6 changes: 2 additions & 4 deletions lib/inferno/dsl/fhir_evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,21 @@ def initialize(ig_path, data_path)
require_relative file
end

Config.new

# To-Do: these paths will be permanently resolved after
# the evaluator is fully integrated with Inferno.
validate_args(ig_path, data_path)

ig = FhirEvaluator::IG.new(File.join(__dir__, 'fhir_evaluator', 'ig', ig_path))

if data_path
data = if data_path
DatasetLoader.from_path(File.join(__dir__, 'fhir_evaluator', data_path))
else
ig.examples
end

# Rule execution and result output will be later integrated at phase 2 and 3.

# results = FhirEvaluator::Evaluator.new(ig).evaluate(data, config)
# results = FhirEvaluator::Evaluator.new(ig).evaluate(data, Config.new)

# output_results(results, options[:output])
end
Expand Down
54 changes: 27 additions & 27 deletions lib/inferno/dsl/fhir_evaluator/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,38 @@ def initialize(config_file = nil)

raise(TypeError, 'Malformed configuration') unless @data.is_a?(Hash)

def method_missing(name, *args, &)
section = @data[name.to_s]
if section
Section.new(section)
else
super
end
end
# def method_missing(name, *args, &)
# section = @data[name.to_s]
# if section
# Section.new(section)
# else
# super
# end
# end

def respond_to_missing?(name, include_private = false)
@data.key?(name.to_s) || super
end
# def respond_to_missing?(name, include_private = false)
# @data.key?(name.to_s) || super
# end
end

class Section
def initialize(details)
@details = details
end
# class Section
# def initialize(details)
# @details = details
# end

def method_missing(name, *_args)
attribute = @details[name.to_s]
if attribute.is_a?(Hash)
Section.new(attribute)
else
attribute
end
end
# def method_missing(name, *_args)
# attribute = @details[name.to_s]
# if attribute.is_a?(Hash)
# Section.new(attribute)
# else
# attribute
# end
# end

def respond_to_missing?(name, include_private = false)
@details.key?(name.to_s) || super
end
end
# def respond_to_missing?(name, include_private = false)
# @details.key?(name.to_s) || super
# end
# end
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/inferno/dsl/fhir_evaluator/data_summary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ def validate(data)
resource_ids = data.map { |r| extract_resources_ids(r) }.flatten

if resource_ids.uniq == resource_ids
puts 'No duplicate Ids found. Proceed to evaluate..'

# puts 'No duplicate Ids found. Proceed to evaluate..'
else
dup = resource_ids.detect { |r| resource_ids.count(r) > 1 }
puts "Warning: Found duplicate resource Ids: #{dup}. Please validate Examples before running FHIR Evaluator."
# puts "Warning: Found duplicate resource Ids: #{dup}. Please validate Examples before running FHIR Evaluator."
end
end

Expand Down
40 changes: 20 additions & 20 deletions lib/inferno/dsl/fhir_evaluator/evaluator_util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,26 @@ def extract_ids_references(resources)
end

def extract_references(resources)
# resources.each do |resource|
# resource.each_element do |value, metadata, path|
# if metadata['type'] == 'Reference' && !value.reference.nil?
# if value.reference.start_with?('#')
# # skip contained references (not separate resources)
# next
# elsif value.reference.include? '/'
# add_parsed_reference(resource, value, path)
# # if type is not specified in the reference, get type from the
# elsif path.include? 'Reference'
# add_reference_typed_path(resource, value, path)
# else
# # assumes this is a unique uuid
# reference = value.reference
# reference = reference[9..] if reference.start_with? 'urn:uuid:'
# @references[resource.id] << [path, '', reference]
# end
# end
# end
# end
resources.each do |resource|
resource.each_element do |value, metadata, path|
if metadata['type'] == 'Reference' && !value.reference.nil?
if value.reference.start_with?('#')
# skip contained references (not separate resources)
next
elsif value.reference.include? '/'
add_parsed_reference(resource, value, path)
# if type is not specified in the reference, get type from the
elsif path.include? 'Reference'
add_reference_typed_path(resource, value, path)
else
# assumes this is a unique uuid
reference = value.reference
reference = reference[9..] if reference.start_with? 'urn:uuid:'
@references[resource.id] << [path, '', reference]
end
end
end
end
end

def add_parsed_reference(resource, value, path)
Expand Down
27 changes: 27 additions & 0 deletions lib/inferno/dsl/fhir_evaluator/rules/abstract_has_examples_rule.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require_relative '../evaluator_util'

module FhirEvaluator
module Rules
class HasExamples < Rule
def check(_context)
''
end

def unused_resource_urls
@unused_resource_urls ||= []
end

def used_resources
@used_resources ||= []
end

def get_unused_resource_urls(ig_data, &resource_filter)
ig_data.each do |resource|
unused_resource_urls.push resource.url unless resource_filter.call(resource)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module FhirEvaluator
module Rules
class AllIGExtensionsHaveExamples < HasExamples
def check(context)
@used_resources = context.data.map { |e| extension_urls(e) }.flatten.uniq
get_unused_resource_urls(context.ig.extensions) do |extension|
next true if extension.context.any? do |ctx|
# Skip extensions that are defined for definitional artifacts.
# For example, US Core's uscdi-requirement extension is applied to
# the profiles and extensions of the IG, not data that conforms to the IG.
# There may eventually be cases where SD/ED are data, so this may become configurable.
ctx.expression == 'StructureDefinition' || ctx.expression == 'ElementDefinition'
end

versioned_url = "#{extension.url}|#{extension.version}"
used_resources.include?(extension.url) || used_resources.include?(versioned_url)
end

if unused_resource_urls.any?
message = "Found unused extensions defined in the IG: #{unused_resource_urls.join(', ')}"
result = EvaluationResult.new(message, rule: self)
else
message = 'All defined extensions are represented in examples'
result = EvaluationResult.new(message, severity: 'success', rule: self)
end

context.add_result result
end

def extension_urls(resource)
urls = []
resource.each_element do |value, _metadata, path|
path_elements = path.split('.')
next unless path_elements.length > 1

urls << value if path_elements[-2].include?('extension') && path_elements[-1] == 'url'
end
urls.uniq
end
end
end
end

0 comments on commit 2120d40

Please sign in to comment.