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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/README.adoc +221 -0
  3. data/lib/openphar/cli/main.rb +137 -0
  4. data/lib/openphar/cli.rb +11 -0
  5. data/lib/openphar/core/slug_generator.rb +107 -0
  6. data/lib/openphar/core.rb +11 -0
  7. data/lib/openphar/errors/configuration_error.rb +8 -0
  8. data/lib/openphar/errors/monograph_not_found_error.rb +15 -0
  9. data/lib/openphar/errors/parse_error.rb +18 -0
  10. data/lib/openphar/errors/unknown_format_error.rb +8 -0
  11. data/lib/openphar/errors/unknown_publisher_error.rb +8 -0
  12. data/lib/openphar/errors/unknown_type_error.rb +8 -0
  13. data/lib/openphar/errors/validation_error.rb +15 -0
  14. data/lib/openphar/errors.rb +17 -0
  15. data/lib/openphar/exporters/coverage_validator.rb +265 -0
  16. data/lib/openphar/exporters/neo4j/model_registry.rb +130 -0
  17. data/lib/openphar/exporters/neo4j/node_builder.rb +84 -0
  18. data/lib/openphar/exporters/neo4j/property_mapper.rb +154 -0
  19. data/lib/openphar/exporters/neo4j/relationship_builder.rb +87 -0
  20. data/lib/openphar/exporters/neo4j.rb +16 -0
  21. data/lib/openphar/exporters/neo4j_exporter.rb +199 -0
  22. data/lib/openphar/exporters.rb +15 -0
  23. data/lib/openphar/linkers/chp/cross_edition_linker.rb +101 -0
  24. data/lib/openphar/linkers/chp.rb +11 -0
  25. data/lib/openphar/linkers/cross_publisher_linker.rb +295 -0
  26. data/lib/openphar/linkers/herbapedia_linker.rb +125 -0
  27. data/lib/openphar/linkers.rb +13 -0
  28. data/lib/openphar/migrators/chp/classifier.rb +86 -0
  29. data/lib/openphar/migrators/chp.rb +15 -0
  30. data/lib/openphar/migrators/chp_migrator.rb +130 -0
  31. data/lib/openphar/migrators/jp_migrator.rb +345 -0
  32. data/lib/openphar/migrators/phint_migrator.rb +426 -0
  33. data/lib/openphar/migrators.rb +15 -0
  34. data/lib/openphar/models/ahp.rb +25 -0
  35. data/lib/openphar/models/amino_acid_monograph.rb +28 -0
  36. data/lib/openphar/models/api.rb +29 -0
  37. data/lib/openphar/models/assay_specification.rb +19 -0
  38. data/lib/openphar/models/base_entity.rb +221 -0
  39. data/lib/openphar/models/biological_substance_monograph.rb +31 -0
  40. data/lib/openphar/models/chemical_drug_monograph.rb +39 -0
  41. data/lib/openphar/models/chp/biologic.rb +28 -0
  42. data/lib/openphar/models/chp/chemical_preparation.rb +28 -0
  43. data/lib/openphar/models/chp/chemical_substance.rb +28 -0
  44. data/lib/openphar/models/chp/general_chapter.rb +30 -0
  45. data/lib/openphar/models/chp/monograph.rb +116 -0
  46. data/lib/openphar/models/chp/section.rb +37 -0
  47. data/lib/openphar/models/chp/tcm_crude_drug.rb +28 -0
  48. data/lib/openphar/models/chp/tcm_extract.rb +29 -0
  49. data/lib/openphar/models/chp/tcm_formulation.rb +28 -0
  50. data/lib/openphar/models/chp.rb +33 -0
  51. data/lib/openphar/models/crude_drug_monograph.rb +31 -0
  52. data/lib/openphar/models/edition.rb +41 -0
  53. data/lib/openphar/models/formulation_monograph.rb +31 -0
  54. data/lib/openphar/models/hkcmms.rb +26 -0
  55. data/lib/openphar/models/identification_specification.rb +19 -0
  56. data/lib/openphar/models/jp/kampo_formula.rb +94 -0
  57. data/lib/openphar/models/jp.rb +21 -0
  58. data/lib/openphar/models/limit.rb +54 -0
  59. data/lib/openphar/models/mineral_substance_monograph.rb +28 -0
  60. data/lib/openphar/models/monograph.rb +58 -0
  61. data/lib/openphar/models/ph_int/buffer_solution.rb +38 -0
  62. data/lib/openphar/models/ph_int/dosage_form_monograph.rb +43 -0
  63. data/lib/openphar/models/ph_int/radiopharmaceutical_monograph.rb +52 -0
  64. data/lib/openphar/models/ph_int/reagent.rb +50 -0
  65. data/lib/openphar/models/ph_int/reference_substance.rb +50 -0
  66. data/lib/openphar/models/ph_int/test_method.rb +50 -0
  67. data/lib/openphar/models/ph_int/test_solution.rb +39 -0
  68. data/lib/openphar/models/ph_int/volumetric_solution.rb +38 -0
  69. data/lib/openphar/models/ph_int.rb +37 -0
  70. data/lib/openphar/models/physical_specification.rb +17 -0
  71. data/lib/openphar/models/publisher.rb +98 -0
  72. data/lib/openphar/models/purity_specification.rb +16 -0
  73. data/lib/openphar/models/supplement.rb +31 -0
  74. data/lib/openphar/models/test_specification.rb +29 -0
  75. data/lib/openphar/models/thp.rb +24 -0
  76. data/lib/openphar/models/vitamin_monograph.rb +28 -0
  77. data/lib/openphar/models.rb +58 -0
  78. data/lib/openphar/monograph_merger.rb +374 -0
  79. data/lib/openphar/parsers/base_monograph_parser.rb +108 -0
  80. data/lib/openphar/parsers/jp_html_parser.rb +92 -0
  81. data/lib/openphar/parsers/jp_html_parser_base.rb +113 -0
  82. data/lib/openphar/parsers/jp_ja_html_parser.rb +220 -0
  83. data/lib/openphar/parsers/phint_json_parser.rb +332 -0
  84. data/lib/openphar/parsers.rb +21 -0
  85. data/lib/openphar/registry/publisher_registry.rb +46 -0
  86. data/lib/openphar/registry/type_registry.rb +147 -0
  87. data/lib/openphar/registry.rb +24 -0
  88. data/lib/openphar/repositories/monograph_repository.rb +350 -0
  89. data/lib/openphar/repositories.rb +11 -0
  90. data/lib/openphar/transformers/jsonld_transformer.rb +106 -0
  91. data/lib/openphar/transformers.rb +11 -0
  92. data/lib/openphar/version.rb +5 -0
  93. data/lib/openphar.rb +119 -0
  94. data/ontology/bibliographic/edition.ttl +267 -0
  95. data/ontology/context/chp.jsonld +69 -0
  96. data/ontology/context/crude-drugs.jsonld +56 -0
  97. data/ontology/context/pharmacopoeia.jsonld +631 -0
  98. data/ontology/core/identification.ttl +456 -0
  99. data/ontology/core/pharmacopoeia.ttl +3427 -0
  100. data/ontology/core/substance-form.ttl +302 -0
  101. data/ontology/core/unit.ttl +493 -0
  102. data/ontology/publisher/chp/extensions.ttl +186 -0
  103. data/ontology/publisher/phint/extensions.ttl +327 -0
  104. data/ontology/quality/test-method.ttl +610 -0
  105. data/shapes/chp/monograph.ttl +153 -0
  106. data/shapes/edition-shapes.ttl +230 -0
  107. data/shapes/monograph-shapes.ttl +1324 -0
  108. metadata +319 -0
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ # Base entity class for all pharmacopoeia models.
6
+ #
7
+ # Provides model-centric serialization methods where the model IS the schema.
8
+ # Each model knows how to serialize itself to:
9
+ # - Neo4j (to_neo4j, to_cypher)
10
+ # - JSON-LD (to_jsonld)
11
+ #
12
+ # Subclasses should override:
13
+ # - neo4j_labels: Array of Neo4j labels
14
+ # - jsonld_type: The @type value for JSON-LD
15
+ #
16
+ # @example
17
+ # class MyMonograph < BaseEntity
18
+ # attribute :name, :string
19
+ #
20
+ # def self.neo4j_labels
21
+ # ['Monograph', 'MyType']
22
+ # end
23
+ #
24
+ # def self.jsonld_type
25
+ # 'MyMonograph'
26
+ # end
27
+ # end
28
+ class BaseEntity < Lutaml::Model::Serializable
29
+ # Common attributes for all entities
30
+ attribute :id, :string
31
+
32
+ json do
33
+ map '@id', to: :id
34
+ end
35
+
36
+ class << self
37
+ # Returns Neo4j labels for this entity type.
38
+ # Override in subclasses to specify custom labels.
39
+ #
40
+ # @return [Array<String>] Neo4j node labels
41
+ def neo4j_labels
42
+ ['Entity']
43
+ end
44
+
45
+ # Returns the primary Neo4j label (first in the list).
46
+ #
47
+ # @return [String] Primary Neo4j label
48
+ def neo4j_label
49
+ neo4j_labels.first
50
+ end
51
+
52
+ # Returns Neo4j label string for Cypher (e.g., "Monograph:CrudeDrug").
53
+ #
54
+ # @return [String] Colon-separated labels
55
+ def neo4j_labels_string
56
+ neo4j_labels.join(':')
57
+ end
58
+
59
+ # Returns the JSON-LD @type for this entity.
60
+ # Override in subclasses.
61
+ #
62
+ # @return [String] JSON-LD type
63
+ def jsonld_type
64
+ name&.split('::')&.last || 'Entity'
65
+ end
66
+
67
+ # Returns the JSON-LD context for this entity.
68
+ #
69
+ # @return [String] Context URL
70
+ def jsonld_context
71
+ 'https://www.openphar.org/ontology/context/pharmacopoeia.jsonld'
72
+ end
73
+ end
74
+
75
+ # Returns a hash representation for Neo4j node.
76
+ #
77
+ # @return [Hash] Node data with id, labels, and properties
78
+ def to_neo4j_node
79
+ {
80
+ id: id,
81
+ labels: self.class.neo4j_labels,
82
+ properties: to_neo4j_properties
83
+ }
84
+ end
85
+
86
+ # Converts model attributes to Neo4j-compatible properties.
87
+ #
88
+ # @return [Hash] Neo4j properties
89
+ def to_neo4j_properties
90
+ properties = {}
91
+
92
+ self.class.attributes.each_key do |attr_name|
93
+ value = send(attr_name)
94
+ next if value.nil?
95
+
96
+ prop_name = neo4j_property_name(attr_name)
97
+ properties[prop_name] = neo4j_property_value(value)
98
+ end
99
+
100
+ properties
101
+ end
102
+
103
+ # Returns a JSON-LD representation of this entity.
104
+ #
105
+ # @return [Hash] JSON-LD data
106
+ def to_jsonld
107
+ data = {
108
+ '@id' => id,
109
+ '@type' => self.class.jsonld_type,
110
+ '@context' => self.class.jsonld_context
111
+ }
112
+
113
+ # Add all attributes except id (already included)
114
+ self.class.attributes.each_key do |attr_name|
115
+ next if attr_name == :id
116
+
117
+ value = send(attr_name)
118
+ next if value.nil?
119
+
120
+ json_key = jsonld_property_name(attr_name)
121
+ data[json_key] = value
122
+ end
123
+
124
+ data
125
+ end
126
+
127
+ # Generates a Cypher CREATE or MERGE statement for this entity.
128
+ #
129
+ # @param use_merge [Boolean] Use MERGE instead of CREATE
130
+ # @param variable [String] Cypher variable name (default: 'n')
131
+ # @return [String] Cypher statement
132
+ def to_cypher(use_merge: false, variable: 'n')
133
+ keyword = use_merge ? 'MERGE' : 'CREATE'
134
+ node = to_neo4j_node
135
+ labels = node[:labels].join(':')
136
+ props = cypher_properties_string(node[:properties])
137
+
138
+ "#{keyword} (#{variable}:#{labels} {#{props}})"
139
+ end
140
+
141
+ # Generates a Cypher MATCH statement for finding this entity.
142
+ #
143
+ # @param variable [String] Cypher variable name
144
+ # @return [String] Cypher MATCH statement
145
+ def to_cypher_match(variable: 'n')
146
+ labels = self.class.neo4j_labels_string
147
+ "MATCH (#{variable}:#{labels} {id: '#{id}'})"
148
+ end
149
+
150
+ private
151
+
152
+ # Converts attribute name to Neo4j property name.
153
+ #
154
+ # @param name [Symbol] Attribute name
155
+ # @return [String] Neo4j property name
156
+ def neo4j_property_name(name)
157
+ # Convert snake_case to camelCase for Neo4j
158
+ name.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
159
+ end
160
+
161
+ # Converts attribute name to JSON-LD property name.
162
+ #
163
+ # @param name [Symbol] Attribute name
164
+ # @return [String] JSON-LD property name
165
+ def jsonld_property_name(name)
166
+ # Use camelCase for JSON-LD properties
167
+ neo4j_property_name(name)
168
+ end
169
+
170
+ # Converts a value to Neo4j-compatible format.
171
+ #
172
+ # @param value [Object] The value to convert
173
+ # @return [Object] Neo4j-compatible value
174
+ def neo4j_property_value(value)
175
+ case value
176
+ when Hash
177
+ value.to_json
178
+ when Array
179
+ value.map { |v| neo4j_property_value(v) }
180
+ when Lutaml::Model::Serializable
181
+ value.id || value.to_s
182
+ else
183
+ value
184
+ end
185
+ end
186
+
187
+ # Formats properties as Cypher property string.
188
+ #
189
+ # @param properties [Hash] Properties hash
190
+ # @return [String] Cypher property string
191
+ def cypher_properties_string(properties)
192
+ properties.map do |key, value|
193
+ "#{key}: #{cypher_escape_value(value)}"
194
+ end.join(', ')
195
+ end
196
+
197
+ # Escapes a value for Cypher.
198
+ #
199
+ # @param value [Object] The value to escape
200
+ # @return [String] Escaped Cypher value
201
+ def cypher_escape_value(value)
202
+ case value
203
+ when String
204
+ "'#{value.gsub("'", "\\'")}'"
205
+ when NilClass
206
+ 'null'
207
+ when TrueClass, FalseClass
208
+ value.to_s
209
+ when Numeric
210
+ value.to_s
211
+ when Array
212
+ "[#{value.map { |v| cypher_escape_value(v) }.join(', ')}]"
213
+ when Hash
214
+ "'#{value.to_json.gsub("'", "\\'")}'"
215
+ else
216
+ "'#{value.to_s.gsub("'", "\\'")}'"
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ # Represents a biological substance monograph
6
+ #
7
+ # Biological substances are derived from living organisms or
8
+ # produced by biotechnology methods.
9
+ class BiologicalSubstanceMonograph < Monograph
10
+ attribute :biological_source, :hash, default: {}
11
+ attribute :production_method, :string
12
+ attribute :potency, :string
13
+
14
+ json do
15
+ map 'biologicalSource', to: :biological_source
16
+ map 'productionMethod', to: :production_method
17
+ map 'potency', to: :potency
18
+ end
19
+
20
+ class << self
21
+ def neo4j_labels
22
+ %w[Monograph Biological]
23
+ end
24
+
25
+ def jsonld_type
26
+ 'BiologicalSubstanceMonograph'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ # Represents a chemical drug monograph
6
+ #
7
+ # Chemical drugs are synthesized or purified chemical compounds
8
+ # with defined molecular structures.
9
+ class ChemicalDrugMonograph < Monograph
10
+ attribute :molecular_formula, :string
11
+ attribute :molecular_weight, :decimal
12
+ attribute :cas_number, :string
13
+ attribute :systematic_name, :hash, default: {}
14
+ attribute :inchi, :string
15
+ attribute :inchi_key, :string
16
+ attribute :smiles, :string
17
+
18
+ json do
19
+ map 'molecularFormula', to: :molecular_formula
20
+ map 'molecularWeight', to: :molecular_weight
21
+ map 'casNumber', to: :cas_number
22
+ map 'systematicName', to: :systematic_name
23
+ map 'inchi', to: :inchi
24
+ map 'inchiKey', to: :inchi_key
25
+ map 'smiles', to: :smiles
26
+ end
27
+
28
+ class << self
29
+ def neo4j_labels
30
+ %w[Monograph ChemicalDrug]
31
+ end
32
+
33
+ def jsonld_type
34
+ 'ChemicalDrugMonograph'
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # Biologic — 生物制品 (Vol III).
7
+ #
8
+ # Vaccines, blood products, recombinant proteins, cell therapies, etc.
9
+ # Maps to the cross-publisher BiologicalSubstance concept.
10
+ #
11
+ class Biologic < Monograph
12
+ class << self
13
+ def wire_key
14
+ 'biologic'
15
+ end
16
+
17
+ def harmonized_type
18
+ 'BiologicalSubstance'
19
+ end
20
+
21
+ def jsonld_type
22
+ 'ChpBiologic'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # Chemical preparation — 品种正文 第二部分 (Vol II).
7
+ #
8
+ # Finished chemical drug products (tablets, injections, etc.). Maps
9
+ # to the cross-publisher Formulation concept.
10
+ #
11
+ class ChemicalPreparation < Monograph
12
+ class << self
13
+ def wire_key
14
+ 'chemical_preparation'
15
+ end
16
+
17
+ def harmonized_type
18
+ 'Formulation'
19
+ end
20
+
21
+ def jsonld_type
22
+ 'ChpChemicalPreparation'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # Chemical substance — 品种正文 第一部分 (Vol II).
7
+ #
8
+ # Drug substances (APIs) of chemical origin. Maps to the cross-publisher
9
+ # ChemicalDrug concept.
10
+ #
11
+ class ChemicalSubstance < Monograph
12
+ class << self
13
+ def wire_key
14
+ 'chemical_substance'
15
+ end
16
+
17
+ def harmonized_type
18
+ 'ChemicalDrug'
19
+ end
20
+
21
+ def jsonld_type
22
+ 'ChpChemicalSubstance'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # General chapter — 通则/制剂通则/凡例/指导原则 (Vol IV).
7
+ #
8
+ # General notices, analytical methods (通则 0100–9900), guiding
9
+ # principles (9000 series), reagent/test-solution definitions, etc.
10
+ # No harmonized equivalent in other pharmacopoeias (publisher-specific
11
+ # structural unit).
12
+ #
13
+ class GeneralChapter < Monograph
14
+ class << self
15
+ def wire_key
16
+ 'general_chapter'
17
+ end
18
+
19
+ def harmonized_type
20
+ nil
21
+ end
22
+
23
+ def jsonld_type
24
+ 'ChpGeneralChapter'
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # Base class for all Chinese Pharmacopoeia monographs.
7
+ #
8
+ # Adds ChP-specific identity fields (entryId from the network-edition
9
+ # API, bookId 1..4 for volumes I–IV, directoryTitle for the TOC node,
10
+ # pageNum for the printed-edition cross-reference, pinyin title) on
11
+ # top of the harmonized Openphar::Models::Monograph.
12
+ #
13
+ # Concrete subclasses (TcmCrudeDrug, ChemicalSubstance, Biologic,
14
+ # GeneralChapter, etc.) declare their semantic @type via class methods.
15
+ # The classifier at Openphar::Migrators::Chp::Classifier is the
16
+ # single dispatch point — never use `case/when` on type elsewhere.
17
+ #
18
+ class Monograph < Openphar::Models::Monograph
19
+ attribute :entry_id, :integer
20
+ attribute :book_id, :integer
21
+ attribute :book_name, :string
22
+ attribute :directory_id, :integer
23
+ attribute :directory_title, :string
24
+ attribute :title, :string
25
+ attribute :page_num, :integer
26
+ attribute :pinyin_title, :string
27
+ attribute :e_title, :string
28
+ attribute :html_content, :string
29
+ attribute :part_titles, :string, collection: true, default: []
30
+ attribute :sections, Section, collection: true, default: []
31
+ attribute :visit_count, :integer
32
+ attribute :edition, :string
33
+ attribute :fetched_at, :string
34
+
35
+ json do
36
+ map 'entryId', to: :entry_id
37
+ map 'bookId', to: :book_id
38
+ map 'bookName', to: :book_name
39
+ map 'directoryId', to: :directory_id
40
+ map 'directoryTitle', to: :directory_title
41
+ map 'title', to: :title
42
+ map 'pageNum', to: :page_num
43
+ map 'pinyinTitle', to: :pinyin_title
44
+ map 'eTitle', to: :e_title
45
+ map 'htmlContent', to: :html_content
46
+ map 'partTitle', to: :part_titles
47
+ map 'section', to: :sections
48
+ map 'visitCount', to: :visit_count
49
+ map 'edition', to: :edition
50
+ map 'fetchedAt', to: :fetched_at
51
+ end
52
+
53
+ class << self
54
+ # Snake_case identifier used as the JSON-LD node-directory name
55
+ # and as the wire-key for type routing. Override in subclasses.
56
+ #
57
+ # @return [String]
58
+ def wire_key
59
+ 'monograph'
60
+ end
61
+
62
+ # Harmonized RDF type this ChP type maps to. Override in subclasses
63
+ # to declare the cross-publisher semantic type (e.g., 'CrudeDrug').
64
+ #
65
+ # @return [String, nil]
66
+ def harmonized_type
67
+ nil
68
+ end
69
+
70
+ # Array of types to emit in JSON-LD @type. Computed from
71
+ # jsonld_type + harmonized_type + the base 'Monograph'.
72
+ #
73
+ # @return [Array<String>]
74
+ def jsonld_types
75
+ types = ['Monograph']
76
+ types << harmonized_type if harmonized_type
77
+ types << jsonld_type unless jsonld_type == 'Monograph'
78
+ types.uniq
79
+ end
80
+
81
+ def neo4j_labels
82
+ labels = %w[Monograph Chp]
83
+ labels << harmonized_type if harmonized_type
84
+ labels << name&.split('::')&.last
85
+ labels.compact.uniq
86
+ end
87
+
88
+ def jsonld_type
89
+ 'ChpMonograph'
90
+ end
91
+ end
92
+
93
+ def slug
94
+ base = (pinyin_title || title || "entry-#{entry_id}")
95
+ .to_s
96
+ .downcase
97
+ .gsub(/\s+/, '-')
98
+ .gsub(/[^\p{L}\p{N}\-]/, '')
99
+ .gsub(/\-+/, '-')
100
+ .gsub(/^\-|\-$/, '')
101
+ base = "entry-#{entry_id}" if base.empty?
102
+ "#{base}-#{entry_id}"
103
+ end
104
+
105
+ def iri
106
+ edition_tag = (edition || 'unknown').to_s.downcase.gsub(/[^a-z0-9]/, '-')
107
+ "#{Openphar.base_iri}/data/chp/#{edition_tag}/entry/#{entry_id}"
108
+ end
109
+
110
+ def jsonld_filename
111
+ "#{slug}.jsonld"
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # Section within a ChP monograph.
7
+ #
8
+ # ChP monographs are structured by 【X】 headings (e.g., 【性状】,
9
+ # 【鉴别】, 【检查】). Each monograph has an ordered list of sections
10
+ # carrying the heading name + the HTML body + a plain-text rendering.
11
+ #
12
+ class Section < Openphar::Models::BaseEntity
13
+ attribute :name, :string
14
+ attribute :sequence, :integer
15
+ attribute :html_content, :string
16
+ attribute :text_content, :string
17
+
18
+ json do
19
+ map 'sectionName', to: :name
20
+ map 'sequence', to: :sequence
21
+ map 'htmlContent', to: :html_content
22
+ map 'textContent', to: :text_content
23
+ end
24
+
25
+ class << self
26
+ def neo4j_labels
27
+ %w[Section ChpSection]
28
+ end
29
+
30
+ def jsonld_type
31
+ 'ChpSection'
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # TCM crude drug — 药材和饮片 (Vol I).
7
+ #
8
+ # Crude drugs and their decoction pieces. Maps harmonically to the
9
+ # cross-publisher CrudeDrug concept.
10
+ #
11
+ class TcmCrudeDrug < Monograph
12
+ class << self
13
+ def wire_key
14
+ 'tcm_crude_drug'
15
+ end
16
+
17
+ def harmonized_type
18
+ 'CrudeDrug'
19
+ end
20
+
21
+ def jsonld_type
22
+ 'TcmCrudeDrug'
23
+ end
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
+ module Chp
6
+ # TCM extract — 植物油脂和提取物 (Vol I).
7
+ #
8
+ # Processed plant extracts and fixed oils. Semantically a sub-type of
9
+ # crude drug (processed form) but kept distinct at the ChP layer for
10
+ # routing fidelity.
11
+ #
12
+ class TcmExtract < Monograph
13
+ class << self
14
+ def wire_key
15
+ 'tcm_extract'
16
+ end
17
+
18
+ def harmonized_type
19
+ 'CrudeDrug'
20
+ end
21
+
22
+ def jsonld_type
23
+ 'TcmExtract'
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ module Chp
6
+ # TCM formulation — 成方制剂和单味制剂 (Vol I).
7
+ #
8
+ # Classical TCM formulas and single-herb preparations. Maps to the
9
+ # cross-publisher Formulation concept.
10
+ #
11
+ class TcmFormulation < Monograph
12
+ class << self
13
+ def wire_key
14
+ 'tcm_formulation'
15
+ end
16
+
17
+ def harmonized_type
18
+ 'Formulation'
19
+ end
20
+
21
+ def jsonld_type
22
+ 'TcmFormulation'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Openphar
4
+ module Models
5
+ # Chinese Pharmacopoeia (ChP) specific models.
6
+ #
7
+ # Contains models unique to the Chinese Pharmacopoeia:
8
+ # - TCM crude drugs (药材和饮片)
9
+ # - TCM extracts (植物油脂和提取物)
10
+ # - TCM formulations (成方制剂和单味制剂)
11
+ # - Chemical substances (品种正文 第一部分)
12
+ # - Chemical preparations (品种正文 第二部分)
13
+ # - Biologics (生物制品)
14
+ # - General chapters (通则/制剂通则/凡例 etc.)
15
+ #
16
+ # @example
17
+ # Openphar::Models::Chp::TcmCrudeDrug
18
+ # Openphar::Models::Chp::ChemicalSubstance
19
+ #
20
+ # @see https://2025.chp.org.cn
21
+ module Chp
22
+ autoload :Monograph, 'openphar/models/chp/monograph'
23
+ autoload :Section, 'openphar/models/chp/section'
24
+ autoload :TcmCrudeDrug, 'openphar/models/chp/tcm_crude_drug'
25
+ autoload :TcmExtract, 'openphar/models/chp/tcm_extract'
26
+ autoload :TcmFormulation, 'openphar/models/chp/tcm_formulation'
27
+ autoload :ChemicalSubstance, 'openphar/models/chp/chemical_substance'
28
+ autoload :ChemicalPreparation, 'openphar/models/chp/chemical_preparation'
29
+ autoload :Biologic, 'openphar/models/chp/biologic'
30
+ autoload :GeneralChapter, 'openphar/models/chp/general_chapter'
31
+ end
32
+ end
33
+ end