openphar 0.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.
- checksums.yaml +7 -0
- data/README.adoc +221 -0
- data/lib/openphar/cli/main.rb +137 -0
- data/lib/openphar/cli.rb +11 -0
- data/lib/openphar/core/slug_generator.rb +107 -0
- data/lib/openphar/core.rb +11 -0
- data/lib/openphar/errors/configuration_error.rb +8 -0
- data/lib/openphar/errors/monograph_not_found_error.rb +15 -0
- data/lib/openphar/errors/parse_error.rb +18 -0
- data/lib/openphar/errors/unknown_format_error.rb +8 -0
- data/lib/openphar/errors/unknown_publisher_error.rb +8 -0
- data/lib/openphar/errors/unknown_type_error.rb +8 -0
- data/lib/openphar/errors/validation_error.rb +15 -0
- data/lib/openphar/errors.rb +17 -0
- data/lib/openphar/exporters/coverage_validator.rb +265 -0
- data/lib/openphar/exporters/neo4j/model_registry.rb +130 -0
- data/lib/openphar/exporters/neo4j/node_builder.rb +84 -0
- data/lib/openphar/exporters/neo4j/property_mapper.rb +154 -0
- data/lib/openphar/exporters/neo4j/relationship_builder.rb +87 -0
- data/lib/openphar/exporters/neo4j.rb +16 -0
- data/lib/openphar/exporters/neo4j_exporter.rb +199 -0
- data/lib/openphar/exporters.rb +15 -0
- data/lib/openphar/linkers/chp/cross_edition_linker.rb +101 -0
- data/lib/openphar/linkers/chp.rb +11 -0
- data/lib/openphar/linkers/cross_publisher_linker.rb +295 -0
- data/lib/openphar/linkers/herbapedia_linker.rb +125 -0
- data/lib/openphar/linkers.rb +13 -0
- data/lib/openphar/migrators/chp/classifier.rb +86 -0
- data/lib/openphar/migrators/chp.rb +15 -0
- data/lib/openphar/migrators/chp_migrator.rb +130 -0
- data/lib/openphar/migrators/jp_migrator.rb +345 -0
- data/lib/openphar/migrators/phint_migrator.rb +426 -0
- data/lib/openphar/migrators.rb +15 -0
- data/lib/openphar/models/ahp.rb +25 -0
- data/lib/openphar/models/amino_acid_monograph.rb +28 -0
- data/lib/openphar/models/api.rb +29 -0
- data/lib/openphar/models/assay_specification.rb +19 -0
- data/lib/openphar/models/base_entity.rb +221 -0
- data/lib/openphar/models/biological_substance_monograph.rb +31 -0
- data/lib/openphar/models/chemical_drug_monograph.rb +39 -0
- data/lib/openphar/models/chp/biologic.rb +28 -0
- data/lib/openphar/models/chp/chemical_preparation.rb +28 -0
- data/lib/openphar/models/chp/chemical_substance.rb +28 -0
- data/lib/openphar/models/chp/general_chapter.rb +30 -0
- data/lib/openphar/models/chp/monograph.rb +116 -0
- data/lib/openphar/models/chp/section.rb +37 -0
- data/lib/openphar/models/chp/tcm_crude_drug.rb +28 -0
- data/lib/openphar/models/chp/tcm_extract.rb +29 -0
- data/lib/openphar/models/chp/tcm_formulation.rb +28 -0
- data/lib/openphar/models/chp.rb +33 -0
- data/lib/openphar/models/crude_drug_monograph.rb +31 -0
- data/lib/openphar/models/edition.rb +41 -0
- data/lib/openphar/models/formulation_monograph.rb +31 -0
- data/lib/openphar/models/hkcmms.rb +26 -0
- data/lib/openphar/models/identification_specification.rb +19 -0
- data/lib/openphar/models/jp/kampo_formula.rb +94 -0
- data/lib/openphar/models/jp.rb +21 -0
- data/lib/openphar/models/limit.rb +54 -0
- data/lib/openphar/models/mineral_substance_monograph.rb +28 -0
- data/lib/openphar/models/monograph.rb +58 -0
- data/lib/openphar/models/ph_int/buffer_solution.rb +38 -0
- data/lib/openphar/models/ph_int/dosage_form_monograph.rb +43 -0
- data/lib/openphar/models/ph_int/radiopharmaceutical_monograph.rb +52 -0
- data/lib/openphar/models/ph_int/reagent.rb +50 -0
- data/lib/openphar/models/ph_int/reference_substance.rb +50 -0
- data/lib/openphar/models/ph_int/test_method.rb +50 -0
- data/lib/openphar/models/ph_int/test_solution.rb +39 -0
- data/lib/openphar/models/ph_int/volumetric_solution.rb +38 -0
- data/lib/openphar/models/ph_int.rb +37 -0
- data/lib/openphar/models/physical_specification.rb +17 -0
- data/lib/openphar/models/publisher.rb +98 -0
- data/lib/openphar/models/purity_specification.rb +16 -0
- data/lib/openphar/models/supplement.rb +31 -0
- data/lib/openphar/models/test_specification.rb +29 -0
- data/lib/openphar/models/thp.rb +24 -0
- data/lib/openphar/models/vitamin_monograph.rb +28 -0
- data/lib/openphar/models.rb +58 -0
- data/lib/openphar/monograph_merger.rb +374 -0
- data/lib/openphar/parsers/base_monograph_parser.rb +108 -0
- data/lib/openphar/parsers/jp_html_parser.rb +92 -0
- data/lib/openphar/parsers/jp_html_parser_base.rb +113 -0
- data/lib/openphar/parsers/jp_ja_html_parser.rb +220 -0
- data/lib/openphar/parsers/phint_json_parser.rb +332 -0
- data/lib/openphar/parsers.rb +21 -0
- data/lib/openphar/registry/publisher_registry.rb +46 -0
- data/lib/openphar/registry/type_registry.rb +147 -0
- data/lib/openphar/registry.rb +24 -0
- data/lib/openphar/repositories/monograph_repository.rb +350 -0
- data/lib/openphar/repositories.rb +11 -0
- data/lib/openphar/transformers/jsonld_transformer.rb +106 -0
- data/lib/openphar/transformers.rb +11 -0
- data/lib/openphar/version.rb +5 -0
- data/lib/openphar.rb +119 -0
- data/ontology/bibliographic/edition.ttl +267 -0
- data/ontology/context/chp.jsonld +69 -0
- data/ontology/context/crude-drugs.jsonld +56 -0
- data/ontology/context/pharmacopoeia.jsonld +631 -0
- data/ontology/core/identification.ttl +456 -0
- data/ontology/core/pharmacopoeia.ttl +3427 -0
- data/ontology/core/substance-form.ttl +302 -0
- data/ontology/core/unit.ttl +493 -0
- data/ontology/publisher/chp/extensions.ttl +186 -0
- data/ontology/publisher/phint/extensions.ttl +327 -0
- data/ontology/quality/test-method.ttl +610 -0
- data/shapes/chp/monograph.ttl +153 -0
- data/shapes/edition-shapes.ttl +230 -0
- data/shapes/monograph-shapes.ttl +1324 -0
- metadata +319 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Openphar
|
|
7
|
+
module Migrators
|
|
8
|
+
# Migrates International Pharmacopoeia data from data-international-pharmacopoeia
|
|
9
|
+
# to Open Pharmacopoeia ontology format
|
|
10
|
+
#
|
|
11
|
+
# This class reads existing Ph.Int. JSON-LD files and transforms them to
|
|
12
|
+
# the OP ontology structure, preserving radiopharmaceutical data and
|
|
13
|
+
# other Ph.Int.-specific extensions.
|
|
14
|
+
class PhIntMigrator
|
|
15
|
+
attr_reader :source_dir, :output_dir, :edition_id
|
|
16
|
+
|
|
17
|
+
# Category mapping for organizing monographs
|
|
18
|
+
CATEGORIES = {
|
|
19
|
+
"pharmaceutical-substances" => "pharmaceutical-substances",
|
|
20
|
+
"radiopharmaceuticals" => "radiopharmaceuticals",
|
|
21
|
+
"crude-drugs" => "crude-drugs",
|
|
22
|
+
"reagents" => "reagents",
|
|
23
|
+
"methods" => "methods",
|
|
24
|
+
"test-solutions" => "reagents/test-solutions",
|
|
25
|
+
"volumetric-solutions" => "reagents/volumetric-solutions",
|
|
26
|
+
"buffer-solutions" => "reagents/buffer-solutions",
|
|
27
|
+
"reference-substances" => "reagents/reference-substances"
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# @param source_dir [String] Directory containing Ph.Int. JSON-LD files
|
|
31
|
+
# @param output_dir [String] Output directory for migrated files
|
|
32
|
+
# @param edition_id [String] Edition IRI (default: Ph.Int.13)
|
|
33
|
+
def initialize(source_dir:, output_dir:, edition_id: "https://www.openphar.org/data/edition/phint/phint13")
|
|
34
|
+
@source_dir = source_dir
|
|
35
|
+
@output_dir = output_dir
|
|
36
|
+
@edition_id = edition_id
|
|
37
|
+
@monographs = []
|
|
38
|
+
@stats = { total: 0, success: 0, failed: 0, categories: Hash.new(0) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Run the migration
|
|
42
|
+
# @return [Hash] Statistics about the migration
|
|
43
|
+
def run
|
|
44
|
+
puts "Starting Ph.Int. migration..."
|
|
45
|
+
puts " Source: #{source_dir}"
|
|
46
|
+
puts " Output: #{output_dir}"
|
|
47
|
+
|
|
48
|
+
# Ensure output directories exist
|
|
49
|
+
ensure_output_directories
|
|
50
|
+
|
|
51
|
+
# Process JSON-LD files
|
|
52
|
+
process_jsonld_files
|
|
53
|
+
|
|
54
|
+
# Generate aggregate files
|
|
55
|
+
generate_aggregate_files
|
|
56
|
+
|
|
57
|
+
# Print statistics
|
|
58
|
+
print_statistics
|
|
59
|
+
|
|
60
|
+
@stats
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def ensure_output_directories
|
|
66
|
+
CATEGORIES.each_value do |category|
|
|
67
|
+
dir = File.join(output_dir, "monographs", category)
|
|
68
|
+
FileUtils.mkdir_p(dir)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def process_jsonld_files
|
|
73
|
+
# Find all JSON-LD files in source directory
|
|
74
|
+
jsonld_files = Dir.glob(File.join(source_dir, "**/*.jsonld"))
|
|
75
|
+
puts "Found #{jsonld_files.count} JSON-LD files"
|
|
76
|
+
|
|
77
|
+
jsonld_files.each do |jsonld_file|
|
|
78
|
+
process_jsonld_file(jsonld_file)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def process_jsonld_file(file_path)
|
|
83
|
+
puts "Processing #{file_path}..."
|
|
84
|
+
|
|
85
|
+
begin
|
|
86
|
+
content = File.read(file_path, encoding: "UTF-8")
|
|
87
|
+
data = JSON.parse(content)
|
|
88
|
+
|
|
89
|
+
# Handle @graph arrays
|
|
90
|
+
if data["@graph"]
|
|
91
|
+
data["@graph"].each do |node|
|
|
92
|
+
process_node(node)
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
process_node(data)
|
|
96
|
+
end
|
|
97
|
+
rescue JSON::ParserError => e
|
|
98
|
+
puts " ERROR: Invalid JSON - #{e.message}"
|
|
99
|
+
@stats[:failed] += 1
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
puts " ERROR: #{e.message}"
|
|
102
|
+
puts " #{e.backtrace.first(5).join("\n ")}"
|
|
103
|
+
@stats[:failed] += 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def process_node(node)
|
|
108
|
+
return unless node.is_a?(Hash)
|
|
109
|
+
return unless node["@id"] || node["id"]
|
|
110
|
+
|
|
111
|
+
@stats[:total] += 1
|
|
112
|
+
|
|
113
|
+
# Determine the type and category
|
|
114
|
+
type = determine_type(node)
|
|
115
|
+
category = determine_category(node, type)
|
|
116
|
+
@stats[:categories][category] += 1
|
|
117
|
+
|
|
118
|
+
# Transform to OP ontology structure
|
|
119
|
+
transformed = transform_node(node, type, category)
|
|
120
|
+
|
|
121
|
+
# Write individual file
|
|
122
|
+
slug = generate_slug(node)
|
|
123
|
+
output_path = File.join(output_dir, "monographs", category, "#{slug}.jsonld")
|
|
124
|
+
File.write(output_path, JSON.pretty_generate(transformed))
|
|
125
|
+
|
|
126
|
+
@monographs << transformed
|
|
127
|
+
@stats[:success] += 1
|
|
128
|
+
|
|
129
|
+
puts " Created: #{output_path}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def determine_type(node)
|
|
133
|
+
types = node["@type"] || node["type"] || []
|
|
134
|
+
types = [types] unless types.is_a?(Array)
|
|
135
|
+
|
|
136
|
+
# Map Ph.Int. types to OP types
|
|
137
|
+
if types.any? { |t| t.to_s.include?("Radiopharmaceutical") }
|
|
138
|
+
return "RadiopharmaceuticalMonograph"
|
|
139
|
+
end
|
|
140
|
+
if types.any? { |t| t.to_s.include?("CrudeDrug") }
|
|
141
|
+
return "CrudeDrugMonograph"
|
|
142
|
+
end
|
|
143
|
+
if types.any? { |t| t.to_s.include?("TestSolution") }
|
|
144
|
+
return "TestSolution"
|
|
145
|
+
end
|
|
146
|
+
if types.any? { |t| t.to_s.include?("VolumetricSolution") }
|
|
147
|
+
return "VolumetricSolution"
|
|
148
|
+
end
|
|
149
|
+
if types.any? { |t| t.to_s.include?("BufferSolution") }
|
|
150
|
+
return "BufferSolution"
|
|
151
|
+
end
|
|
152
|
+
if types.any? { |t| t.to_s.include?("ReferenceSubstance") }
|
|
153
|
+
return "ReferenceSubstance"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
"ChemicalDrugMonograph"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def determine_category(node, type)
|
|
160
|
+
case type
|
|
161
|
+
when "RadiopharmaceuticalMonograph"
|
|
162
|
+
"radiopharmaceuticals"
|
|
163
|
+
when "CrudeDrugMonograph"
|
|
164
|
+
"crude-drugs"
|
|
165
|
+
when "TestSolution"
|
|
166
|
+
"reagents/test-solutions"
|
|
167
|
+
when "VolumetricSolution"
|
|
168
|
+
"reagents/volumetric-solutions"
|
|
169
|
+
when "BufferSolution"
|
|
170
|
+
"reagents/buffer-solutions"
|
|
171
|
+
when "ReferenceSubstance"
|
|
172
|
+
"reagents/reference-substances"
|
|
173
|
+
else
|
|
174
|
+
# Check for specific characteristics
|
|
175
|
+
name = node["prefLabel"] || node["rdfs:label"] || node["name"]
|
|
176
|
+
if name.is_a?(Hash)
|
|
177
|
+
name = name.values.first
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
if name&.match?(/reagent|solution/i)
|
|
181
|
+
return "reagents"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
"pharmaceutical-substances"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def generate_slug(node)
|
|
189
|
+
# Try to get a name for the slug
|
|
190
|
+
name = node["prefLabel"] || node["rdfs:label"] || node["name"] || node["skos:prefLabel"]
|
|
191
|
+
if name.is_a?(Hash)
|
|
192
|
+
name = name["en"] || name["@value"] || name.values.first
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
return "unknown-#{@stats[:total]}" unless name
|
|
196
|
+
|
|
197
|
+
name.to_s.downcase
|
|
198
|
+
.gsub(/[^a-z0-9\s-]/, "")
|
|
199
|
+
.gsub(/\s+/, "-")
|
|
200
|
+
.gsub(/-+/, "-")
|
|
201
|
+
.strip
|
|
202
|
+
.sub(/^-/, "")
|
|
203
|
+
.sub(/-$/, "")
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def transform_node(node, type, category)
|
|
207
|
+
slug = generate_slug(node)
|
|
208
|
+
base_iri = "https://www.openphar.org/data/phint/monographs/#{category}/#{slug}"
|
|
209
|
+
|
|
210
|
+
# Get original IRI for reference
|
|
211
|
+
original_id = node["@id"] || node["id"]
|
|
212
|
+
|
|
213
|
+
# Build new JSON-LD structure
|
|
214
|
+
transformed = {
|
|
215
|
+
"@context" => "https://www.openphar.org/ontology/context/pharmacopoeia.jsonld",
|
|
216
|
+
"@id" => base_iri,
|
|
217
|
+
"@type" => type,
|
|
218
|
+
"publisher" => "WHO",
|
|
219
|
+
"belongsToEdition" => @edition_id
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# Copy over common properties
|
|
223
|
+
copy_property(transformed, node, "prefLabel", "prefLabel")
|
|
224
|
+
copy_property(transformed, node, "skos:prefLabel", "prefLabel")
|
|
225
|
+
copy_property(transformed, node, "rdfs:label", "prefLabel")
|
|
226
|
+
copy_property(transformed, node, "name", "prefLabel")
|
|
227
|
+
|
|
228
|
+
copy_property(transformed, node, "definition", "definition")
|
|
229
|
+
copy_property(transformed, node, "skos:definition", "definition")
|
|
230
|
+
|
|
231
|
+
copy_property(transformed, node, "molecularFormula", "molecularFormula")
|
|
232
|
+
copy_property(transformed, node, "molecularWeight", "molecularWeight")
|
|
233
|
+
copy_property(transformed, node, "casNumber", "casNumber")
|
|
234
|
+
|
|
235
|
+
# Handle chemical identifiers
|
|
236
|
+
copy_property(transformed, node, "inchi", "inchi")
|
|
237
|
+
copy_property(transformed, node, "inchiKey", "inchiKey")
|
|
238
|
+
copy_property(transformed, node, "smiles", "smiles")
|
|
239
|
+
|
|
240
|
+
# Handle test specifications
|
|
241
|
+
if node["testSpecification"] || node["tests"]
|
|
242
|
+
specs = node["testSpecification"] || node["tests"]
|
|
243
|
+
specs = [specs] unless specs.is_a?(Array)
|
|
244
|
+
transformed["testSpecification"] = specs.map do |spec|
|
|
245
|
+
transform_test_specification(spec)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Handle radiopharmaceutical-specific data
|
|
250
|
+
if type == "RadiopharmaceuticalMonograph"
|
|
251
|
+
transform_radiopharmaceutical(transformed, node)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Store reference to original IRI
|
|
255
|
+
if original_id && original_id != base_iri
|
|
256
|
+
transformed["sameAs"] = original_id
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
transformed.compact
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def copy_property(target, source, source_key, target_key)
|
|
263
|
+
value = source[source_key] || source[source_key.to_sym]
|
|
264
|
+
return unless value
|
|
265
|
+
|
|
266
|
+
# Handle language maps
|
|
267
|
+
if value.is_a?(Hash)
|
|
268
|
+
if value["@value"]
|
|
269
|
+
value = value["@value"]
|
|
270
|
+
elsif value["en"]
|
|
271
|
+
value = { "en" => value["en"] }
|
|
272
|
+
elsif value["@language"]
|
|
273
|
+
lang = value["@language"]
|
|
274
|
+
value = { lang => value["@value"] }
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
target[target_key] = value
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def transform_test_specification(spec)
|
|
282
|
+
return spec unless spec.is_a?(Hash)
|
|
283
|
+
|
|
284
|
+
transformed = {
|
|
285
|
+
"@type" => spec["@type"] || "TestSpecification"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
copy_property(transformed, spec, "testName", "testName")
|
|
289
|
+
copy_property(transformed, spec, "name", "testName")
|
|
290
|
+
copy_property(transformed, spec, "testType", "testType")
|
|
291
|
+
copy_property(transformed, spec, "harmonizedMethod", "harmonizedMethod")
|
|
292
|
+
copy_property(transformed, spec, "testConditions", "testConditions")
|
|
293
|
+
|
|
294
|
+
if spec["limit"] || spec["limitValue"]
|
|
295
|
+
transformed["limit"] = transform_limit(spec["limit"] || spec)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
transformed.compact
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def transform_limit(limit_data)
|
|
302
|
+
return limit_data unless limit_data.is_a?(Hash)
|
|
303
|
+
|
|
304
|
+
limit = { "@type" => "Limit" }
|
|
305
|
+
|
|
306
|
+
copy_property(limit, limit_data, "limitType", "limitType")
|
|
307
|
+
copy_property(limit, limit_data, "limitValue", "limitValue")
|
|
308
|
+
copy_property(limit, limit_data, "lowerLimit", "lowerLimit")
|
|
309
|
+
copy_property(limit, limit_data, "upperLimit", "upperLimit")
|
|
310
|
+
copy_property(limit, limit_data, "limitUnit", "limitUnit")
|
|
311
|
+
|
|
312
|
+
limit.compact
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def transform_radiopharmaceutical(transformed, node)
|
|
316
|
+
# Handle radionuclide data
|
|
317
|
+
if node["radionuclide"] || node["hasRadionuclide"]
|
|
318
|
+
rad_data = node["radionuclide"] || node["hasRadionuclide"]
|
|
319
|
+
rad_data = [rad_data] unless rad_data.is_a?(Array)
|
|
320
|
+
|
|
321
|
+
transformed["hasRadionuclide"] = rad_data.map do |rad|
|
|
322
|
+
if rad.is_a?(Hash)
|
|
323
|
+
{
|
|
324
|
+
"@type" => "Radionuclide",
|
|
325
|
+
"prefLabel" => rad["prefLabel"] || rad["name"],
|
|
326
|
+
"halfLife" => rad["halfLife"],
|
|
327
|
+
"emissionType" => rad["emissionType"]
|
|
328
|
+
}.compact
|
|
329
|
+
else
|
|
330
|
+
rad
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Handle radioactivity-related specifications
|
|
336
|
+
if node["radiochemicalPurity"]
|
|
337
|
+
transformed["radiochemicalPurity"] = node["radiochemicalPurity"]
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
if node["radionuclidicPurity"]
|
|
341
|
+
transformed["radionuclidicPurity"] = node["radionuclidicPurity"]
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
if node["photonFraction"]
|
|
345
|
+
transformed["photonFraction"] = node["photonFraction"]
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def generate_aggregate_files
|
|
350
|
+
puts "\nGenerating aggregate files..."
|
|
351
|
+
|
|
352
|
+
# Generate JSON-LD aggregate
|
|
353
|
+
jsonld_aggregate = {
|
|
354
|
+
"@context" => "https://www.openphar.org/ontology/context/pharmacopoeia.jsonld",
|
|
355
|
+
"@id" => "https://www.openphar.org/data/phint/phint-monographs",
|
|
356
|
+
"@type" => "Collection",
|
|
357
|
+
"prefLabel" => { "en" => "International Pharmacopoeia 13 - All Monographs" },
|
|
358
|
+
"belongsToEdition" => @edition_id,
|
|
359
|
+
"@graph" => @monographs
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
jsonld_path = File.join(output_dir, "phint-monographs.jsonld")
|
|
363
|
+
File.write(jsonld_path, JSON.pretty_generate(jsonld_aggregate))
|
|
364
|
+
puts " Created: #{jsonld_path}"
|
|
365
|
+
|
|
366
|
+
# Generate TTL aggregate
|
|
367
|
+
ttl_content = generate_ttl_aggregate
|
|
368
|
+
ttl_path = File.join(output_dir, "phint-monographs.ttl")
|
|
369
|
+
File.write(ttl_path, ttl_content)
|
|
370
|
+
puts " Created: #{ttl_path}"
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def generate_ttl_aggregate
|
|
374
|
+
ttl = StringIO.new
|
|
375
|
+
ttl.puts "@prefix op: <https://www.openphar.org/ontology/core/> ."
|
|
376
|
+
ttl.puts "@prefix bib: <https://www.openphar.org/ontology/bibliographic/> ."
|
|
377
|
+
ttl.puts "@prefix phint: <https://www.openphar.org/ontology/publisher/phint/> ."
|
|
378
|
+
ttl.puts "@prefix skos: <http://www.w3.org/2004/02/skos/core#> ."
|
|
379
|
+
ttl.puts "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> ."
|
|
380
|
+
ttl.puts ""
|
|
381
|
+
ttl.puts "<https://www.openphar.org/data/phint/phint-monographs>"
|
|
382
|
+
ttl.puts " a op:Collection ;"
|
|
383
|
+
ttl.puts " skos:prefLabel \"International Pharmacopoeia 13 - All Monographs\"@en ;"
|
|
384
|
+
ttl.puts " op:belongsToEdition <#{@edition_id}> ;"
|
|
385
|
+
ttl.puts " op:monographCount #{@monographs.count} ."
|
|
386
|
+
ttl.puts ""
|
|
387
|
+
|
|
388
|
+
@monographs.each do |monograph|
|
|
389
|
+
id = monograph["@id"]
|
|
390
|
+
type = monograph["@type"]
|
|
391
|
+
label = extract_label(monograph)
|
|
392
|
+
|
|
393
|
+
ttl.puts "<#{id}>"
|
|
394
|
+
ttl.puts " a op:#{type} ;"
|
|
395
|
+
ttl.puts " skos:prefLabel #{label.inspect} ;"
|
|
396
|
+
ttl.puts " op:publisher op:WHO ;"
|
|
397
|
+
ttl.puts " op:belongsToEdition <#{@edition_id}> ."
|
|
398
|
+
ttl.puts ""
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
ttl.string
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def extract_label(monograph)
|
|
405
|
+
label = monograph["prefLabel"]
|
|
406
|
+
return "Unknown" unless label
|
|
407
|
+
|
|
408
|
+
label.is_a?(Hash) ? (label["en"] || label.values.first) : label
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def print_statistics
|
|
412
|
+
puts "\n" + "=" * 50
|
|
413
|
+
puts "Migration Statistics"
|
|
414
|
+
puts "=" * 50
|
|
415
|
+
puts "Total monographs: #{@stats[:total]}"
|
|
416
|
+
puts "Successfully migrated: #{@stats[:success]}"
|
|
417
|
+
puts "Failed: #{@stats[:failed]}"
|
|
418
|
+
puts "\nBy category:"
|
|
419
|
+
@stats[:categories].each do |category, count|
|
|
420
|
+
puts " #{category}: #{count}"
|
|
421
|
+
end
|
|
422
|
+
puts "=" * 50
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Openphar
|
|
4
|
+
module Migrators
|
|
5
|
+
# Migrators for converting source data to Openphar format.
|
|
6
|
+
#
|
|
7
|
+
# Handles migration from various pharmacopoeia sources to the
|
|
8
|
+
# unified Openphar ontology.
|
|
9
|
+
|
|
10
|
+
autoload :JpMigrator, 'openphar/migrators/jp_migrator'
|
|
11
|
+
autoload :PhIntMigrator, 'openphar/migrators/phint_migrator'
|
|
12
|
+
autoload :Chp, 'openphar/migrators/chp'
|
|
13
|
+
autoload :ChpMigrator, 'openphar/migrators/chp_migrator'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Openphar
|
|
4
|
+
module Models
|
|
5
|
+
# American Herbal Pharmacopoeia (AHP) specific models.
|
|
6
|
+
#
|
|
7
|
+
# Contains models unique to the American Herbal Pharmacopoeia:
|
|
8
|
+
# - Western herbal monographs with comprehensive therapeutic information
|
|
9
|
+
# - Safety and efficacy profiles
|
|
10
|
+
# - Detailed analytical methods for botanicals
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # Access AHP models
|
|
14
|
+
# Openphar::Models::AHP::HerbalMonograph
|
|
15
|
+
#
|
|
16
|
+
# @see https://www.herbal-ahp.org/
|
|
17
|
+
module AHP
|
|
18
|
+
# Placeholder for AHP-specific models
|
|
19
|
+
# Will include:
|
|
20
|
+
# - HerbalMonograph (Western herbal medicine)
|
|
21
|
+
# - TherapeuticProfile
|
|
22
|
+
# - SafetyProfile
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Openphar
|
|
4
|
+
module Models
|
|
5
|
+
# Represents an amino acid monograph
|
|
6
|
+
#
|
|
7
|
+
# Amino acids are the building blocks of proteins.
|
|
8
|
+
class AminoAcidMonograph < ChemicalDrugMonograph
|
|
9
|
+
attribute :amino_acid_type, :string
|
|
10
|
+
attribute :essential, :boolean
|
|
11
|
+
|
|
12
|
+
json do
|
|
13
|
+
map 'aminoAcidType', to: :amino_acid_type
|
|
14
|
+
map 'essential', to: :essential
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def neo4j_labels
|
|
19
|
+
%w[Monograph ChemicalDrug AminoAcid]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def jsonld_type
|
|
23
|
+
'AminoAcidMonograph'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Openphar
|
|
4
|
+
module Models
|
|
5
|
+
# Ayurvedic Pharmacopoeia of India (API) specific models.
|
|
6
|
+
#
|
|
7
|
+
# Contains models unique to the Ayurvedic Pharmacopoeia of India:
|
|
8
|
+
# - Dravya (Ayurvedic substances) with Rasa/Guna/Virya/Vipaka/Karma properties
|
|
9
|
+
# - Traditional Ayurvedic formulations (Asava, Arishta, Bhasma, etc.)
|
|
10
|
+
# - Classical preparation methods
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # Access API models
|
|
14
|
+
# Openphar::Models::API::DravyaMonograph
|
|
15
|
+
# Openphar::Models::API::AyurvedicFormula
|
|
16
|
+
#
|
|
17
|
+
# @see https://main.ayush.gov.in/
|
|
18
|
+
module API
|
|
19
|
+
# Placeholder for API-specific models
|
|
20
|
+
# Will include:
|
|
21
|
+
# - DravyaMonograph (Ayurvedic substances)
|
|
22
|
+
# - AyurvedicFormula (classical formulations)
|
|
23
|
+
# - Rasa (taste properties)
|
|
24
|
+
# - Guna (quality properties)
|
|
25
|
+
# - Virya (potency)
|
|
26
|
+
# - Vipaka (post-digestive effect)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Openphar
|
|
4
|
+
module Models
|
|
5
|
+
# Represents an assay specification
|
|
6
|
+
#
|
|
7
|
+
# Assay specifications define the quantitative determination of
|
|
8
|
+
# active ingredients or marker compounds.
|
|
9
|
+
class AssaySpecification < TestSpecification
|
|
10
|
+
attribute :assay_target, :hash, default: {}
|
|
11
|
+
attribute :assay_expression, :string
|
|
12
|
+
|
|
13
|
+
json do
|
|
14
|
+
map 'assayTarget', to: :assay_target
|
|
15
|
+
map 'assayExpression', to: :assay_expression
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|