Class: Openphar::Exporters::CoverageValidator
- Inherits:
-
Object
- Object
- Openphar::Exporters::CoverageValidator
- Defined in:
- lib/openphar/exporters/coverage_validator.rb
Overview
Validates that model coverage is 100% complete
Compares:
- All fields in JSON-LD files have corresponding model attributes
- All model attributes can be exported to JSON-LD
- All model attributes can be exported to Neo4j
Constant Summary collapse
- STANDARD_KEYS =
Standard JSON-LD keys that are framework-related, not domain data
%w[ @context @graph @id @type @vocab @base ].freeze
- FIELD_MAPPING =
Mapping from JSON-LD camelCase to model snake_case
{ "monographId" => "monograph_id", "prefLabel" => "pref_label", "altLabel" => "alt_label", "belongsToEdition" => "belongs_to_edition", "effectiveDate" => "effective_date", "referencesPreparation" => "references_preparation", "referencesTCMProfile" => "references_tcm_profile", "referencesAyurvedaProfile" => "references_ayurveda_profile", "referencesWesternProfile" => "references_western_profile", "referencesPlantSpecies" => "references_plant_species", "referencesPlantPart" => "references_plant_part", "testSpecification" => "test_specifications", "sameSubstanceAs" => "same_substance_as", "hasEquivalentIn" => "has_equivalent_in", "similarTo" => "similar_to", "storageContainer" => "storage_container", "storageConditions" => "storage_conditions", "botanicalSource" => "botanical_source", "macroscopicDescription" => "macroscopic_description", "microscopicDescription" => "microscopic_description", "molecularFormula" => "molecular_formula", "molecularWeight" => "molecular_weight", "casNumber" => "cas_number", "systematicName" => "systematic_name", "inchiKey" => "inchi_key", "smiles" => "smiles", "chemicalStructure" => "chemical_structure", "appearance" => "appearance", "solubility" => "solubility", "testName" => "test_name", "testType" => "test_type", "harmonizedMethod" => "harmonized_method", "publisherMethod" => "publisher_method", "testConditions" => "test_conditions", "limitType" => "limit_type", "limitValue" => "limit_value", "lowerLimit" => "lower_limit", "upperLimit" => "upper_limit", "limitUnit" => "limit_unit", "assayTarget" => "assay_target", "assayExpression" => "assay_expression", "impurityType" => "impurity_type", "physicalProperty" => "physical_property" }.freeze
- REVERSE_MAPPING =
Reverse mapping: snake_case to camelCase
FIELD_MAPPING.invert
Instance Attribute Summary collapse
-
#extra_fields ⇒ Object
readonly
Returns the value of attribute extra_fields.
-
#missing_fields ⇒ Object
readonly
Returns the value of attribute missing_fields.
-
#results ⇒ Object
readonly
Returns the value of attribute results.
Instance Method Summary collapse
-
#initialize ⇒ CoverageValidator
constructor
A new instance of CoverageValidator.
-
#normalize_key(key) ⇒ Object
Normalize a key from JSON-LD to snake_case.
-
#print_report ⇒ Object
Print detailed report.
-
#summary ⇒ Hash
Generate report of all unique missing fields across all files.
-
#validate_directory(directory, expected_fields = nil) ⇒ Object
Validate all JSON-LD files in a directory.
-
#validate_file(jsonld_file, expected_fields = nil) ⇒ Object
Analyze a JSON-LD file and compare to expected model attributes.
Constructor Details
#initialize ⇒ CoverageValidator
Returns a new instance of CoverageValidator.
72 73 74 75 76 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 72 def initialize @results = {} @missing_fields = [] @extra_fields = [] end |
Instance Attribute Details
#extra_fields ⇒ Object (readonly)
Returns the value of attribute extra_fields.
70 71 72 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 70 def extra_fields @extra_fields end |
#missing_fields ⇒ Object (readonly)
Returns the value of attribute missing_fields.
70 71 72 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 70 def missing_fields @missing_fields end |
#results ⇒ Object (readonly)
Returns the value of attribute results.
70 71 72 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 70 def results @results end |
Instance Method Details
#normalize_key(key) ⇒ Object
Normalize a key from JSON-LD to snake_case
123 124 125 126 127 128 129 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 123 def normalize_key(key) # Remove nested path (e.g., "prefLabel.en" -> "prefLabel") base_key = key.split(".").first # Map to snake_case if known FIELD_MAPPING[base_key] || base_key end |
#print_report ⇒ Object
Print detailed report
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 172 def print_report puts "=" * 60 puts "COVERAGE VALIDATION REPORT" puts "=" * 60 summary = self.summary puts "\nFiles Validated: #{summary[:files_validated]}" puts "Total JSON-LD Fields: #{summary[:total_jsonld_fields]}" puts "Total Expected Fields: #{summary[:total_expected_fields]}" puts "Average Coverage: #{summary[:average_coverage].round(2)}%" if summary[:unique_missing_fields].any? puts "\n" + "!" * 60 puts "MISSING FIELDS (in JSON-LD but not in model):" puts "!" * 60 summary[:unique_missing_fields].each do |field| puts " - #{field}" end end if summary[:unique_extra_fields].any? puts "\n" + "+" * 60 puts "EXTRA FIELDS (in model but not in JSON-LD):" puts "+" * 60 summary[:unique_extra_fields].each do |field| puts " + #{field}" end end puts "\n" + "=" * 60 puts "COVERAGE BY FILE:" puts "=" * 60 @results.each do |file, result| status = result[:coverage] >= 100 ? "✓" : "✗" puts "#{status} #{File.basename(file)}: #{result[:coverage].round(2)}%" end end |
#summary ⇒ Hash
Generate report of all unique missing fields across all files
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 147 def summary unique_missing = @missing_fields.uniq unique_extra = @extra_fields.uniq total_jsonld_keys = @results.values.sum { |r| r[:jsonld_keys] } total_expected = @results.values.sum { |r| r[:expected] } avg_coverage = if @results.any? @results.values.map { |r| r[:coverage] }.sum / @results.size else 100 end { files_validated: @results.size, total_jsonld_fields: total_jsonld_keys, total_expected_fields: total_expected, unique_missing_fields: unique_missing, unique_extra_fields: unique_extra, average_coverage: avg_coverage, details: @results } end |
#validate_directory(directory, expected_fields = nil) ⇒ Object
Validate all JSON-LD files in a directory
135 136 137 138 139 140 141 142 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 135 def validate_directory(directory, expected_fields = nil) Dir.glob(File.join(directory, "**", "*.jsonld")).each do |file| puts "Validating: #{file}" validate_file(file, expected_fields) end summary end |
#validate_file(jsonld_file, expected_fields = nil) ⇒ Object
Analyze a JSON-LD file and compare to expected model attributes
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/openphar/exporters/coverage_validator.rb', line 82 def validate_file(jsonld_file, expected_fields = nil) data = JSON.parse(File.read(jsonld_file)) # Extract all keys from JSON-LD jsonld_keys = extract_all_keys(data).to_a domain_keys = jsonld_keys - STANDARD_KEYS # Normalize keys to snake_case for comparison normalized_jsonld_keys = domain_keys.map { |k| normalize_key(k) } # Get expected fields from model if not provided expected ||= expected_fields || model_expected_fields expected_normalized = expected.map(&:to_s) # Find missing fields (in JSON-LD but not in model) missing = normalized_jsonld_keys - expected_normalized # Find extra fields (in model but not in JSON-LD) extra = expected_normalized - normalized_jsonld_keys @missing_fields.concat(missing) @extra_fields.concat(extra) coverage = if normalized_jsonld_keys.any? (1 - missing.size.to_f / normalized_jsonld_keys.size) * 100 else 100 end @results[jsonld_file] = { jsonld_keys: domain_keys.size, expected: expected.size, missing: missing, extra: extra, coverage: coverage } { missing: missing, extra: extra, coverage: coverage } end |