@toxplanet/pegasus-sdk 1.0.1 → 1.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.
package/lib/chemicals.js CHANGED
@@ -1,229 +1,293 @@
1
- const { logError } = require('@toxplanet/tphelper/logging');
2
- const { getDrizzle, schema } = require('./db');
3
- const { eq } = require('drizzle-orm');
4
-
5
- class ChemicalsService {
6
- constructor(connection) {
7
- this.connection = connection;
8
- this.db = null;
9
- }
10
-
11
- getDb() {
12
- if (!this.db) {
13
- this.db = getDrizzle(this.connection.pgPool);
14
- }
15
- return this.db;
16
- }
17
-
18
- async bulkIndexFielded(documents) {}
19
-
20
- async bulkIndexFulltext(documents) {}
21
-
22
- async bulkIndexSubstances(substances) {}
23
-
24
- async createChemical(chemical) {
25
- try {
26
- const db = this.getDb();
27
-
28
- const [result] = await db
29
- .insert(schema.chemicals)
30
- .values({
31
- sourceId: chemical.source_id,
32
- chemicalName: chemical.chemical_name,
33
- chemicalMeta: chemical.chemical_meta,
34
- chemicalIdentifiers: chemical.chemical_identifiers,
35
- chemicalSynonyms: chemical.chemical_synonyms,
36
- chemicalCategories: chemical.chemical_categories,
37
- createdAt: chemical.created_at || new Date(),
38
- updatedAt: chemical.updated_at || new Date(),
39
- ...(chemical.imported_at && { importedAt: chemical.imported_at }),
40
- ...(chemical.chemical_id && { chemicalId: chemical.chemical_id })
41
- })
42
- .returning();
43
-
44
- return result;
45
- } catch (error) {
46
- logError('pegasus-sdk', 'ChemicalsService', 'createChemical', error);
47
- throw error;
48
- }
49
- }
50
-
51
- async updateChemical(chemicalId, updates) {}
52
-
53
- async deleteChemical(chemicalId) {
54
- try {
55
- const db = this.getDb();
56
-
57
- const [deleted] = await db
58
- .delete(schema.chemicals)
59
- .where(eq(schema.chemicals.chemicalId, chemicalId))
60
- .returning();
61
-
62
- return deleted || null;
63
- } catch (error) {
64
- logError('pegasus-sdk', 'ChemicalsService', 'deleteChemical', error);
65
- throw error;
66
- }
67
- }
68
-
69
- async deleteBySourceId(sourceId) {}
70
-
71
- async deleteCollection(collectionName) {}
72
-
73
- async updateCollectionProperty(collectionName, propertyPath, newValue) {}
74
-
75
- async bulkUpdateProperty(filter, propertyPath, newValue) {}
76
-
77
- async getChemicalById(chemicalId) {
78
- try {
79
- const db = this.getDb();
80
-
81
- const [result] = await db
82
- .select()
83
- .from(schema.chemicals)
84
- .where(eq(schema.chemicals.chemicalId, chemicalId))
85
- .limit(1);
86
-
87
- return result || null;
88
- } catch (error) {
89
- logError('pegasus-sdk', 'ChemicalsService', 'getChemicalById', error);
90
- throw error;
91
- }
92
- }
93
-
94
- async getChemicalBySourceId(sourceId) {}
95
-
96
- async getChemicalsByCAS(casNumber) {}
97
-
98
- async getChemicalsByIdentifier(identifierType, identifierValue) {}
99
-
100
- async countAll() {}
101
-
102
- async countByCollection(collectionName) {}
103
-
104
- async countByIdentifier(identifierValue) {}
105
-
106
- async countByCAS(casNumber) {}
107
-
108
- async getTotalSynonymCount() {}
109
-
110
- async getSynonymCount(synonymTerm) {}
111
-
112
- async convertIdentifier(fromIdentifier, toIdentifierType) {}
113
-
114
- async convertIdentifiersBatch(fromIdentifiers, toIdentifierType) {}
115
-
116
- /**
117
- * Search for chemicals by name using OpenSearch
118
- * @param {string} searchTerm - Name to search for
119
- * @param {number} limit - Maximum number of results (default: 10)
120
- * @returns {Promise<Object>} Search results
121
- */
122
- async searchByName(searchTerm, limit = 10) {
123
- if (!searchTerm) {
124
- return { results: [] };
125
- }
126
-
127
- try {
128
- const opensearchClient = this.connection.getOpenSearchClient();
129
- const indexName = this.connection.getOpenSearchIndex();
130
-
131
- const response = await opensearchClient.search({
132
- index: indexName,
133
- body: {
134
- size: limit,
135
- query: {
136
- bool: {
137
- should: [
138
- // Prioritize exact name matches
139
- { term: { 'chemical_name.keyword': { value: searchTerm, boost: 100, case_insensitive: true } } },
140
- // Then prefix matches
141
- { prefix: { 'chemical_name.keyword': { value: searchTerm, boost: 50, case_insensitive: true } } },
142
- // Include synonym matches as secondary
143
- { term: { 'synonyms.keyword': { value: searchTerm, boost: 30, case_insensitive: true } } },
144
- { prefix: { 'synonyms.keyword': { value: searchTerm, boost: 10, case_insensitive: true } } }
145
- ],
146
- minimum_should_match: 1
147
- }
148
- },
149
- _source: ['postgres_id', 'chemical_name', 'cas_numbers', 'identifier_values', 'synonyms']
150
- }
151
- });
152
-
153
- const hits = response.body?.hits?.hits || [];
154
- const results = hits.map((hit) => ({
155
- id: hit._source.postgres_id,
156
- name: hit._source.chemical_name,
157
- cas: hit._source.cas_numbers || [],
158
- identifiers: hit._source.identifier_values || [],
159
- synonyms: hit._source.synonyms || [],
160
- score: hit._score
161
- }));
162
-
163
- return { results };
164
- } catch (error) {
165
- logError('pegasus-sdk', 'ChemicalsService', 'searchByName', error);
166
- throw error;
167
- }
168
- }
169
-
170
- /**
171
- * Search for chemicals by synonym using OpenSearch
172
- * @param {string} synonymTerm - Synonym to search for
173
- * @param {number} limit - Maximum number of results (default: 10)
174
- * @returns {Promise<Object>} Search results
175
- */
176
- async searchBySynonym(synonymTerm, limit = 10) {
177
- if (!synonymTerm) {
178
- return { results: [] };
179
- }
180
-
181
- try {
182
- const opensearchClient = this.connection.getOpenSearchClient();
183
- const indexName = this.connection.getOpenSearchIndex();
184
-
185
- const response = await opensearchClient.search({
186
- index: indexName,
187
- body: {
188
- size: limit,
189
- query: {
190
- bool: {
191
- should: [
192
- // Prioritize exact synonym matches
193
- { term: { 'synonyms.keyword': { value: synonymTerm, boost: 100, case_insensitive: true } } },
194
- // Then prefix matches
195
- { prefix: { 'synonyms.keyword': { value: synonymTerm, boost: 50, case_insensitive: true } } },
196
- // Include name matches as secondary
197
- { term: { 'chemical_name.keyword': { value: synonymTerm, boost: 30, case_insensitive: true } } },
198
- { prefix: { 'chemical_name.keyword': { value: synonymTerm, boost: 10, case_insensitive: true } } }
199
- ],
200
- minimum_should_match: 1
201
- }
202
- },
203
- _source: ['postgres_id', 'chemical_name', 'cas_numbers', 'identifier_values', 'synonyms']
204
- }
205
- });
206
-
207
- const hits = response.body?.hits?.hits || [];
208
- const results = hits.map((hit) => ({
209
- id: hit._source.postgres_id,
210
- name: hit._source.chemical_name,
211
- cas: hit._source.cas_numbers || [],
212
- identifiers: hit._source.identifier_values || [],
213
- synonyms: hit._source.synonyms || [],
214
- score: hit._score
215
- }));
216
-
217
- return { results };
218
- } catch (error) {
219
- logError('pegasus-sdk', 'ChemicalsService', 'searchBySynonym', error);
220
- throw error;
221
- }
222
- }
223
-
224
- async findChemicalsWithoutDocuments(collectionName, searchTerm, pageSize) {}
225
-
226
- async countChemicalsWithoutDocuments(collectionName) {}
227
- }
228
-
229
- module.exports = ChemicalsService;
1
+ const { logError } = require('@toxplanet/tphelper/logging');
2
+ const { getDrizzle, schema } = require('./db');
3
+ const { eq } = require('drizzle-orm');
4
+
5
+ class ChemicalsService {
6
+ constructor(connection) {
7
+ this.connection = connection;
8
+ this.db = null;
9
+ }
10
+
11
+ getDb() {
12
+ if (!this.db) {
13
+ this.db = getDrizzle(this.connection.pgPool);
14
+ }
15
+ return this.db;
16
+ }
17
+
18
+ async bulkIndexFielded(documents) {}
19
+
20
+ async bulkIndexFulltext(documents) {}
21
+
22
+ async bulkIndexSubstances(substances) {}
23
+
24
+ async createChemical(chemical) {
25
+ try {
26
+ const db = this.getDb();
27
+
28
+ const [result] = await db
29
+ .insert(schema.chemicals)
30
+ .values({
31
+ sourceId: chemical.source_id,
32
+ chemicalName: chemical.chemical_name,
33
+ chemicalMeta: chemical.chemical_meta,
34
+ chemicalIdentifiers: chemical.chemical_identifiers,
35
+ chemicalSynonyms: chemical.chemical_synonyms,
36
+ chemicalCategories: chemical.chemical_categories,
37
+ createdAt: chemical.created_at || new Date(),
38
+ updatedAt: chemical.updated_at || new Date(),
39
+ ...(chemical.imported_at && { importedAt: chemical.imported_at }),
40
+ ...(chemical.chemical_id && { chemicalId: chemical.chemical_id })
41
+ })
42
+ .returning();
43
+
44
+ return result;
45
+ } catch (error) {
46
+ logError('pegasus-sdk', 'ChemicalsService', 'createChemical', error);
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ async updateChemical(chemicalId, updates) {}
52
+
53
+ async deleteChemical(chemicalId) {
54
+ try {
55
+ const db = this.getDb();
56
+
57
+ const [deleted] = await db
58
+ .delete(schema.chemicals)
59
+ .where(eq(schema.chemicals.chemicalId, chemicalId))
60
+ .returning();
61
+
62
+ return deleted || null;
63
+ } catch (error) {
64
+ logError('pegasus-sdk', 'ChemicalsService', 'deleteChemical', error);
65
+ throw error;
66
+ }
67
+ }
68
+
69
+ async deleteBySourceId(sourceId) {}
70
+
71
+ async deleteCollection(collectionName) {}
72
+
73
+ async updateCollectionProperty(collectionName, propertyPath, newValue) {}
74
+
75
+ async bulkUpdateProperty(filter, propertyPath, newValue) {}
76
+
77
+ async getChemicalById(chemicalId) {
78
+ try {
79
+ const db = this.getDb();
80
+
81
+ const [result] = await db
82
+ .select()
83
+ .from(schema.chemicals)
84
+ .where(eq(schema.chemicals.chemicalId, chemicalId))
85
+ .limit(1);
86
+
87
+ return result || null;
88
+ } catch (error) {
89
+ logError('pegasus-sdk', 'ChemicalsService', 'getChemicalById', error);
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ async getChemicalBySourceId(sourceId) {}
95
+
96
+ async getChemicalsByCAS(casNumber) {}
97
+
98
+ async getChemicalsByIdentifier(identifierType, identifierValue) {}
99
+
100
+ async countByCollection(collectionName) {}
101
+
102
+ async countByIdentifier(identifierValue) {}
103
+
104
+ async countByCAS(casNumber) {}
105
+
106
+ async getTotalSynonymCount() {}
107
+
108
+ async getSynonymCount(synonymTerm) {}
109
+
110
+ async convertIdentifier(fromIdentifier, toIdentifierType) {}
111
+
112
+ async convertIdentifiersBatch(fromIdentifiers, toIdentifierType) {}
113
+
114
+ /**
115
+ * Search for chemicals by name using OpenSearch
116
+ * @param {string} searchTerm - Name to search for
117
+ * @param {number} limit - Maximum number of results (default: 10)
118
+ * @returns {Promise<Object>} Search results
119
+ */
120
+ async searchByName(searchTerm, limit = 10) {
121
+ if (!searchTerm) {
122
+ return { results: [] };
123
+ }
124
+
125
+ try {
126
+ const opensearchClient = this.connection.getOpenSearchClient();
127
+ const indexName = this.connection.getOpenSearchIndex();
128
+
129
+ const response = await opensearchClient.search({
130
+ index: indexName,
131
+ body: {
132
+ size: limit,
133
+ query: {
134
+ bool: {
135
+ should: [
136
+ // Prioritize exact name matches
137
+ { term: { 'chemical_name.keyword': { value: searchTerm, boost: 100, case_insensitive: true } } },
138
+ // Then prefix matches
139
+ { prefix: { 'chemical_name.keyword': { value: searchTerm, boost: 50, case_insensitive: true } } },
140
+ // Include synonym matches as secondary
141
+ { term: { 'synonyms.keyword': { value: searchTerm, boost: 30, case_insensitive: true } } },
142
+ { prefix: { 'synonyms.keyword': { value: searchTerm, boost: 10, case_insensitive: true } } }
143
+ ],
144
+ minimum_should_match: 1
145
+ }
146
+ },
147
+ _source: ['postgres_id', 'chemical_name', 'cas_numbers', 'identifier_values', 'synonyms']
148
+ }
149
+ });
150
+
151
+ const hits = response.body?.hits?.hits || [];
152
+ const results = hits.map((hit) => ({
153
+ id: hit._source.postgres_id,
154
+ name: hit._source.chemical_name,
155
+ cas: hit._source.cas_numbers || [],
156
+ identifiers: hit._source.identifier_values || [],
157
+ synonyms: hit._source.synonyms || [],
158
+ score: hit._score
159
+ }));
160
+
161
+ return { results };
162
+ } catch (error) {
163
+ logError('pegasus-sdk', 'ChemicalsService', 'searchByName', error);
164
+ throw error;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Search for chemicals by synonym using OpenSearch
170
+ * @param {string} synonymTerm - Synonym to search for
171
+ * @param {number} limit - Maximum number of results (default: 10)
172
+ * @returns {Promise<Object>} Search results
173
+ */
174
+ async searchBySynonym(synonymTerm, limit = 10) {
175
+ if (!synonymTerm) {
176
+ return { results: [] };
177
+ }
178
+
179
+ try {
180
+ const opensearchClient = this.connection.getOpenSearchClient();
181
+ const indexName = this.connection.getOpenSearchIndex();
182
+
183
+ const response = await opensearchClient.search({
184
+ index: indexName,
185
+ body: {
186
+ size: limit,
187
+ query: {
188
+ bool: {
189
+ should: [
190
+ // Prioritize exact synonym matches
191
+ { term: { 'synonyms.keyword': { value: synonymTerm, boost: 100, case_insensitive: true } } },
192
+ // Then prefix matches
193
+ { prefix: { 'synonyms.keyword': { value: synonymTerm, boost: 50, case_insensitive: true } } },
194
+ // Include name matches as secondary
195
+ { term: { 'chemical_name.keyword': { value: synonymTerm, boost: 30, case_insensitive: true } } },
196
+ { prefix: { 'chemical_name.keyword': { value: synonymTerm, boost: 10, case_insensitive: true } } }
197
+ ],
198
+ minimum_should_match: 1
199
+ }
200
+ },
201
+ _source: ['postgres_id', 'chemical_name', 'cas_numbers', 'identifier_values', 'synonyms']
202
+ }
203
+ });
204
+
205
+ const hits = response.body?.hits?.hits || [];
206
+ const results = hits.map((hit) => ({
207
+ id: hit._source.postgres_id,
208
+ name: hit._source.chemical_name,
209
+ cas: hit._source.cas_numbers || [],
210
+ identifiers: hit._source.identifier_values || [],
211
+ synonyms: hit._source.synonyms || [],
212
+ score: hit._score
213
+ }));
214
+
215
+ return { results };
216
+ } catch (error) {
217
+ logError('pegasus-sdk', 'ChemicalsService', 'searchBySynonym', error);
218
+ throw error;
219
+ }
220
+ }
221
+
222
+ async countAll() {
223
+ try {
224
+ const db = this.getDb();
225
+ const result = await db.select({ count: sql`count(*)::int` }).from(schema.chemicals);
226
+ return { count: result[0].count };
227
+ } catch (error) {
228
+ logError('pegasus-sdk', 'ChemicalsService', 'countAll', error);
229
+ throw error;
230
+ }
231
+ }
232
+
233
+ async findChemicalsWithoutDocuments(collectionName, searchTerm, pageSize) {}
234
+
235
+ async countChemicalsWithoutDocuments(collectionName) {}
236
+
237
+ registerElasticsearchHandlers(elasticsearchService) {
238
+ const indexPatterns = this.connection.config.indexRoutes?.chemicals || ['chemicals*'];
239
+
240
+ indexPatterns.forEach(pattern => {
241
+ elasticsearchService.registerIndexRoute(pattern, {
242
+ index: async (params) => {
243
+ const chemical = params.body;
244
+ return await this.createChemical(chemical);
245
+ },
246
+
247
+ bulk: async (params) => {
248
+ const operations = params.body || params.operations;
249
+ const documents = [];
250
+
251
+ for (let i = 0; i < operations.length; i += 2) {
252
+ const action = operations[i];
253
+ const document = operations[i + 1];
254
+
255
+ if (action.index || action.create) {
256
+ documents.push(document);
257
+ }
258
+ }
259
+
260
+ return await this.bulkIndexFielded(documents);
261
+ },
262
+
263
+ get: async (params) => {
264
+ return await this.getChemicalById(params.id);
265
+ },
266
+
267
+ update: async (params) => {
268
+ return await this.updateChemical(params.id, params.body);
269
+ },
270
+
271
+ delete: async (params) => {
272
+ return await this.deleteChemical(params.id);
273
+ },
274
+
275
+ search: async (params) => {
276
+ const query = params.body?.query;
277
+ const searchTerm = query?.match?.chemical_name ||
278
+ query?.term?.chemical_name ||
279
+ query?.query_string?.query || '';
280
+ const limit = params.body?.size || 10;
281
+
282
+ return await this.searchByName(searchTerm, limit);
283
+ },
284
+
285
+ count: async (params) => {
286
+ return await this.countAll();
287
+ }
288
+ });
289
+ });
290
+ }
291
+ }
292
+
293
+ module.exports = ChemicalsService;