tooluniverse 0.2.0__py3-none-any.whl → 1.0.1__py3-none-any.whl
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.
Potentially problematic release.
This version of tooluniverse might be problematic. Click here for more details.
- tooluniverse/__init__.py +340 -4
- tooluniverse/admetai_tool.py +84 -0
- tooluniverse/agentic_tool.py +563 -0
- tooluniverse/alphafold_tool.py +96 -0
- tooluniverse/base_tool.py +129 -6
- tooluniverse/boltz_tool.py +207 -0
- tooluniverse/chem_tool.py +192 -0
- tooluniverse/compose_scripts/__init__.py +1 -0
- tooluniverse/compose_scripts/biomarker_discovery.py +293 -0
- tooluniverse/compose_scripts/comprehensive_drug_discovery.py +186 -0
- tooluniverse/compose_scripts/drug_safety_analyzer.py +89 -0
- tooluniverse/compose_scripts/literature_tool.py +34 -0
- tooluniverse/compose_scripts/output_summarizer.py +279 -0
- tooluniverse/compose_scripts/tool_description_optimizer.py +681 -0
- tooluniverse/compose_scripts/tool_discover.py +705 -0
- tooluniverse/compose_scripts/tool_graph_composer.py +448 -0
- tooluniverse/compose_tool.py +371 -0
- tooluniverse/ctg_tool.py +1002 -0
- tooluniverse/custom_tool.py +81 -0
- tooluniverse/dailymed_tool.py +108 -0
- tooluniverse/data/admetai_tools.json +155 -0
- tooluniverse/data/adverse_event_tools.json +108 -0
- tooluniverse/data/agentic_tools.json +1156 -0
- tooluniverse/data/alphafold_tools.json +87 -0
- tooluniverse/data/boltz_tools.json +9 -0
- tooluniverse/data/chembl_tools.json +16 -0
- tooluniverse/data/clinicaltrials_gov_tools.json +326 -0
- tooluniverse/data/compose_tools.json +202 -0
- tooluniverse/data/dailymed_tools.json +70 -0
- tooluniverse/data/dataset_tools.json +646 -0
- tooluniverse/data/disease_target_score_tools.json +712 -0
- tooluniverse/data/efo_tools.json +17 -0
- tooluniverse/data/embedding_tools.json +319 -0
- tooluniverse/data/enrichr_tools.json +31 -0
- tooluniverse/data/europe_pmc_tools.json +22 -0
- tooluniverse/data/expert_feedback_tools.json +10 -0
- tooluniverse/data/fda_drug_adverse_event_tools.json +491 -0
- tooluniverse/data/fda_drug_labeling_tools.json +1 -1
- tooluniverse/data/fda_drugs_with_brand_generic_names_for_tool.py +76929 -148860
- tooluniverse/data/finder_tools.json +209 -0
- tooluniverse/data/gene_ontology_tools.json +113 -0
- tooluniverse/data/gwas_tools.json +1082 -0
- tooluniverse/data/hpa_tools.json +333 -0
- tooluniverse/data/humanbase_tools.json +47 -0
- tooluniverse/data/idmap_tools.json +74 -0
- tooluniverse/data/mcp_client_tools_example.json +113 -0
- tooluniverse/data/mcpautoloadertool_defaults.json +28 -0
- tooluniverse/data/medlineplus_tools.json +141 -0
- tooluniverse/data/monarch_tools.json +1 -1
- tooluniverse/data/openalex_tools.json +36 -0
- tooluniverse/data/opentarget_tools.json +1 -1
- tooluniverse/data/output_summarization_tools.json +101 -0
- tooluniverse/data/packages/bioinformatics_core_tools.json +1756 -0
- tooluniverse/data/packages/categorized_tools.txt +206 -0
- tooluniverse/data/packages/cheminformatics_tools.json +347 -0
- tooluniverse/data/packages/earth_sciences_tools.json +74 -0
- tooluniverse/data/packages/genomics_tools.json +776 -0
- tooluniverse/data/packages/image_processing_tools.json +38 -0
- tooluniverse/data/packages/machine_learning_tools.json +789 -0
- tooluniverse/data/packages/neuroscience_tools.json +62 -0
- tooluniverse/data/packages/original_tools.txt +0 -0
- tooluniverse/data/packages/physics_astronomy_tools.json +62 -0
- tooluniverse/data/packages/scientific_computing_tools.json +560 -0
- tooluniverse/data/packages/single_cell_tools.json +453 -0
- tooluniverse/data/packages/structural_biology_tools.json +396 -0
- tooluniverse/data/packages/visualization_tools.json +399 -0
- tooluniverse/data/pubchem_tools.json +215 -0
- tooluniverse/data/pubtator_tools.json +68 -0
- tooluniverse/data/rcsb_pdb_tools.json +1332 -0
- tooluniverse/data/reactome_tools.json +19 -0
- tooluniverse/data/semantic_scholar_tools.json +26 -0
- tooluniverse/data/special_tools.json +2 -25
- tooluniverse/data/tool_composition_tools.json +88 -0
- tooluniverse/data/toolfinderkeyword_defaults.json +34 -0
- tooluniverse/data/txagent_client_tools.json +9 -0
- tooluniverse/data/uniprot_tools.json +211 -0
- tooluniverse/data/url_fetch_tools.json +94 -0
- tooluniverse/data/uspto_downloader_tools.json +9 -0
- tooluniverse/data/uspto_tools.json +811 -0
- tooluniverse/data/xml_tools.json +3275 -0
- tooluniverse/dataset_tool.py +296 -0
- tooluniverse/default_config.py +165 -0
- tooluniverse/efo_tool.py +42 -0
- tooluniverse/embedding_database.py +630 -0
- tooluniverse/embedding_sync.py +396 -0
- tooluniverse/enrichr_tool.py +266 -0
- tooluniverse/europe_pmc_tool.py +52 -0
- tooluniverse/execute_function.py +1775 -95
- tooluniverse/extended_hooks.py +444 -0
- tooluniverse/gene_ontology_tool.py +194 -0
- tooluniverse/graphql_tool.py +158 -36
- tooluniverse/gwas_tool.py +358 -0
- tooluniverse/hpa_tool.py +1645 -0
- tooluniverse/humanbase_tool.py +389 -0
- tooluniverse/logging_config.py +254 -0
- tooluniverse/mcp_client_tool.py +764 -0
- tooluniverse/mcp_integration.py +413 -0
- tooluniverse/mcp_tool_registry.py +925 -0
- tooluniverse/medlineplus_tool.py +337 -0
- tooluniverse/openalex_tool.py +228 -0
- tooluniverse/openfda_adv_tool.py +283 -0
- tooluniverse/openfda_tool.py +393 -160
- tooluniverse/output_hook.py +1122 -0
- tooluniverse/package_tool.py +195 -0
- tooluniverse/pubchem_tool.py +158 -0
- tooluniverse/pubtator_tool.py +168 -0
- tooluniverse/rcsb_pdb_tool.py +38 -0
- tooluniverse/reactome_tool.py +108 -0
- tooluniverse/remote/boltz/boltz_mcp_server.py +50 -0
- tooluniverse/remote/depmap_24q2/depmap_24q2_mcp_tool.py +442 -0
- tooluniverse/remote/expert_feedback/human_expert_mcp_tools.py +2013 -0
- tooluniverse/remote/expert_feedback/simple_test.py +23 -0
- tooluniverse/remote/expert_feedback/start_web_interface.py +188 -0
- tooluniverse/remote/expert_feedback/web_only_interface.py +0 -0
- tooluniverse/remote/immune_compass/compass_tool.py +327 -0
- tooluniverse/remote/pinnacle/pinnacle_tool.py +328 -0
- tooluniverse/remote/transcriptformer/transcriptformer_tool.py +586 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_mcp_server.py +61 -0
- tooluniverse/remote/uspto_downloader/uspto_downloader_tool.py +120 -0
- tooluniverse/remote_tool.py +99 -0
- tooluniverse/restful_tool.py +53 -30
- tooluniverse/scripts/generate_tool_graph.py +408 -0
- tooluniverse/scripts/visualize_tool_graph.py +829 -0
- tooluniverse/semantic_scholar_tool.py +62 -0
- tooluniverse/smcp.py +2452 -0
- tooluniverse/smcp_server.py +975 -0
- tooluniverse/test/mcp_server_test.py +0 -0
- tooluniverse/test/test_admetai_tool.py +370 -0
- tooluniverse/test/test_agentic_tool.py +129 -0
- tooluniverse/test/test_alphafold_tool.py +71 -0
- tooluniverse/test/test_chem_tool.py +37 -0
- tooluniverse/test/test_compose_lieraturereview.py +63 -0
- tooluniverse/test/test_compose_tool.py +448 -0
- tooluniverse/test/test_dailymed.py +69 -0
- tooluniverse/test/test_dataset_tool.py +200 -0
- tooluniverse/test/test_disease_target_score.py +56 -0
- tooluniverse/test/test_drugbank_filter_examples.py +179 -0
- tooluniverse/test/test_efo.py +31 -0
- tooluniverse/test/test_enrichr_tool.py +21 -0
- tooluniverse/test/test_europe_pmc_tool.py +20 -0
- tooluniverse/test/test_fda_adv.py +95 -0
- tooluniverse/test/test_fda_drug_labeling.py +91 -0
- tooluniverse/test/test_gene_ontology_tools.py +66 -0
- tooluniverse/test/test_gwas_tool.py +139 -0
- tooluniverse/test/test_hpa.py +625 -0
- tooluniverse/test/test_humanbase_tool.py +20 -0
- tooluniverse/test/test_idmap_tools.py +61 -0
- tooluniverse/test/test_mcp_server.py +211 -0
- tooluniverse/test/test_mcp_tool.py +247 -0
- tooluniverse/test/test_medlineplus.py +220 -0
- tooluniverse/test/test_openalex_tool.py +32 -0
- tooluniverse/test/test_opentargets.py +28 -0
- tooluniverse/test/test_pubchem_tool.py +116 -0
- tooluniverse/test/test_pubtator_tool.py +37 -0
- tooluniverse/test/test_rcsb_pdb_tool.py +86 -0
- tooluniverse/test/test_reactome.py +54 -0
- tooluniverse/test/test_semantic_scholar_tool.py +24 -0
- tooluniverse/test/test_software_tools.py +147 -0
- tooluniverse/test/test_tool_description_optimizer.py +49 -0
- tooluniverse/test/test_tool_finder.py +26 -0
- tooluniverse/test/test_tool_finder_llm.py +252 -0
- tooluniverse/test/test_tools_find.py +195 -0
- tooluniverse/test/test_uniprot_tools.py +74 -0
- tooluniverse/test/test_uspto_tool.py +72 -0
- tooluniverse/test/test_xml_tool.py +113 -0
- tooluniverse/tool_finder_embedding.py +267 -0
- tooluniverse/tool_finder_keyword.py +693 -0
- tooluniverse/tool_finder_llm.py +699 -0
- tooluniverse/tool_graph_web_ui.py +955 -0
- tooluniverse/tool_registry.py +416 -0
- tooluniverse/uniprot_tool.py +155 -0
- tooluniverse/url_tool.py +253 -0
- tooluniverse/uspto_tool.py +240 -0
- tooluniverse/utils.py +369 -41
- tooluniverse/xml_tool.py +369 -0
- tooluniverse-1.0.1.dist-info/METADATA +387 -0
- tooluniverse-1.0.1.dist-info/RECORD +182 -0
- tooluniverse-1.0.1.dist-info/entry_points.txt +9 -0
- tooluniverse/generate_mcp_tools.py +0 -113
- tooluniverse/mcp_server.py +0 -3340
- tooluniverse-0.2.0.dist-info/METADATA +0 -139
- tooluniverse-0.2.0.dist-info/RECORD +0 -21
- tooluniverse-0.2.0.dist-info/entry_points.txt +0 -4
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/WHEEL +0 -0
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {tooluniverse-0.2.0.dist-info → tooluniverse-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import copy
|
|
3
|
+
import requests
|
|
4
|
+
import urllib.parse
|
|
5
|
+
from .base_tool import BaseTool
|
|
6
|
+
from .tool_registry import register_tool
|
|
7
|
+
|
|
8
|
+
# ---- Helper: human readable -> openFDA code mapping ----
|
|
9
|
+
HUMAN_TO_FDA_MAP = {
|
|
10
|
+
"fulfillexpeditecriteria": {"Yes": "1", "No": "2"},
|
|
11
|
+
"patient.patientsex": {"Unknown": "0", "Male": "1", "Female": "2"},
|
|
12
|
+
"patient.patientagegroup": {
|
|
13
|
+
"Neonate": "1",
|
|
14
|
+
"Infant": "2",
|
|
15
|
+
"Child": "3",
|
|
16
|
+
"Adolescent": "4",
|
|
17
|
+
"Adult": "5",
|
|
18
|
+
"Elderly": "6",
|
|
19
|
+
},
|
|
20
|
+
"patientonsetageunit": {
|
|
21
|
+
"Decade": "800",
|
|
22
|
+
"Year": "801",
|
|
23
|
+
"Month": "802",
|
|
24
|
+
"Week": "803",
|
|
25
|
+
"Day": "804",
|
|
26
|
+
"Hour": "805",
|
|
27
|
+
},
|
|
28
|
+
"patient.reaction.reactionoutcome": {
|
|
29
|
+
"Recovered/resolved": "1",
|
|
30
|
+
"Recovering/resolving": "2",
|
|
31
|
+
"Not recovered/not resolved": "3",
|
|
32
|
+
"Recovered/resolved with sequelae": "4",
|
|
33
|
+
"Fatal": "5",
|
|
34
|
+
"Unknown": "6",
|
|
35
|
+
},
|
|
36
|
+
"serious": {"Yes": "1", "No": "2"},
|
|
37
|
+
"seriousnessdeath": {"Yes": "1"},
|
|
38
|
+
"seriousnesshospitalization": {"Yes": "1"},
|
|
39
|
+
"seriousnessdisabling": {"Yes": "1"},
|
|
40
|
+
"seriousnesslifethreatening": {"Yes": "1"},
|
|
41
|
+
"seriousnessother": {"Yes": "1"},
|
|
42
|
+
"primarysource.qualification": {
|
|
43
|
+
"Physician": "1",
|
|
44
|
+
"Pharmacist": "2",
|
|
45
|
+
"Other health professional": "3",
|
|
46
|
+
"Lawyer": "4",
|
|
47
|
+
"Consumer or non-health professional": "5",
|
|
48
|
+
},
|
|
49
|
+
"patient.drug.drugcharacterization": {
|
|
50
|
+
"Suspect": "1",
|
|
51
|
+
"Concomitant": "2",
|
|
52
|
+
"Interacting": "3",
|
|
53
|
+
},
|
|
54
|
+
"patient.drug.drugadministrationroute": {
|
|
55
|
+
"Oral": "048",
|
|
56
|
+
"Intravenous": "042",
|
|
57
|
+
"Intramuscular": "030",
|
|
58
|
+
"Subcutaneous": "058",
|
|
59
|
+
"Rectal": "054",
|
|
60
|
+
"Topical": "061",
|
|
61
|
+
"Respiratory (inhalation)": "055",
|
|
62
|
+
"Ophthalmic": "047",
|
|
63
|
+
"Unknown": "065",
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---- Base Tool Class ----
|
|
69
|
+
@register_tool("FDADrugAdverseEventTool")
|
|
70
|
+
class FDADrugAdverseEventTool(BaseTool):
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
tool_config,
|
|
74
|
+
endpoint_url="https://api.fda.gov/drug/event.json",
|
|
75
|
+
api_key=None,
|
|
76
|
+
):
|
|
77
|
+
super().__init__(tool_config)
|
|
78
|
+
self.endpoint_url = endpoint_url
|
|
79
|
+
self.api_key = api_key or os.getenv("FDA_API_KEY")
|
|
80
|
+
self.search_fields = tool_config.get("fields", {}).get("search_fields", {})
|
|
81
|
+
self.return_fields = tool_config.get("fields", {}).get("return_fields", [])
|
|
82
|
+
self.count_field = tool_config.get("count_field") or (
|
|
83
|
+
self.return_fields[0] if self.return_fields else None
|
|
84
|
+
)
|
|
85
|
+
self.return_fields_mapping = tool_config.get("fields", {}).get(
|
|
86
|
+
"return_fields_mapping", {}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if not self.count_field:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
"Either 'count_field' or 'return_fields' must be defined in tool_config."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Store allowed enum values
|
|
95
|
+
self.parameter_enums = {}
|
|
96
|
+
if "parameter" in tool_config and "properties" in tool_config["parameter"]:
|
|
97
|
+
for param_name, param_def in tool_config["parameter"]["properties"].items():
|
|
98
|
+
if "enum" in param_def:
|
|
99
|
+
self.parameter_enums[param_name] = param_def["enum"]
|
|
100
|
+
|
|
101
|
+
def run(self, arguments):
|
|
102
|
+
arguments = copy.deepcopy(arguments)
|
|
103
|
+
|
|
104
|
+
# Validate enum parameters
|
|
105
|
+
validation_error = self.validate_enum_arguments(arguments)
|
|
106
|
+
if validation_error:
|
|
107
|
+
return {"error": validation_error}
|
|
108
|
+
|
|
109
|
+
response = self._search(arguments)
|
|
110
|
+
return self._post_process(response)
|
|
111
|
+
|
|
112
|
+
def validate_enum_arguments(self, arguments):
|
|
113
|
+
"""Validate that enum-based arguments match the allowed values"""
|
|
114
|
+
for param_name, value in arguments.items():
|
|
115
|
+
if param_name in self.parameter_enums and value is not None:
|
|
116
|
+
allowed_values = self.parameter_enums[param_name]
|
|
117
|
+
if value not in allowed_values:
|
|
118
|
+
return f"Invalid value '{value}' for parameter '{param_name}'. Allowed values are: {', '.join(allowed_values)}"
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def _post_process(self, response):
|
|
122
|
+
if not response or not isinstance(response, list):
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
if not self.return_fields_mapping:
|
|
126
|
+
return response
|
|
127
|
+
|
|
128
|
+
mapped_results = []
|
|
129
|
+
for item in response:
|
|
130
|
+
try:
|
|
131
|
+
term = item.get("term")
|
|
132
|
+
count = item.get("count", 0)
|
|
133
|
+
mapped_term = self.return_fields_mapping.get(self.count_field, {}).get(
|
|
134
|
+
str(term), term
|
|
135
|
+
)
|
|
136
|
+
mapped_results.append({"term": mapped_term, "count": count})
|
|
137
|
+
except Exception:
|
|
138
|
+
# Keep the original term in case of an exception
|
|
139
|
+
mapped_results.append(item)
|
|
140
|
+
|
|
141
|
+
return mapped_results
|
|
142
|
+
|
|
143
|
+
def _search(self, arguments):
|
|
144
|
+
search_parts = []
|
|
145
|
+
for param_name, value in arguments.items():
|
|
146
|
+
fda_fields = self.search_fields.get(
|
|
147
|
+
param_name, [param_name]
|
|
148
|
+
) # Map param -> FDA field
|
|
149
|
+
|
|
150
|
+
# Apply value mapping if needed
|
|
151
|
+
mapping_error, mapped_value = self._map_value(param_name, value)
|
|
152
|
+
if mapping_error:
|
|
153
|
+
return [{"error": mapping_error}]
|
|
154
|
+
if mapped_value is None:
|
|
155
|
+
continue # Skip this field if instructed
|
|
156
|
+
|
|
157
|
+
# Build search parts
|
|
158
|
+
for fda_field in fda_fields:
|
|
159
|
+
if isinstance(mapped_value, str) and " " in mapped_value:
|
|
160
|
+
search_parts.append(f'{fda_field}:"{mapped_value}"')
|
|
161
|
+
else:
|
|
162
|
+
search_parts.append(f"{fda_field}:{mapped_value}")
|
|
163
|
+
|
|
164
|
+
# Final search query
|
|
165
|
+
search_query = "+AND+".join(search_parts)
|
|
166
|
+
search_encoded = urllib.parse.quote(search_query, safe='+:"')
|
|
167
|
+
|
|
168
|
+
# Build URL
|
|
169
|
+
if self.api_key:
|
|
170
|
+
url = f"{self.endpoint_url}?api_key={self.api_key}&search={search_encoded}&count={self.count_field}"
|
|
171
|
+
else:
|
|
172
|
+
url = (
|
|
173
|
+
f"{self.endpoint_url}?search={search_encoded}&count={self.count_field}"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# API request
|
|
177
|
+
try:
|
|
178
|
+
response = requests.get(url)
|
|
179
|
+
response.raise_for_status()
|
|
180
|
+
response = response.json()
|
|
181
|
+
if "results" in response:
|
|
182
|
+
response = response["results"]
|
|
183
|
+
return response
|
|
184
|
+
except requests.exceptions.RequestException as e:
|
|
185
|
+
return [{"error": f"API request failed: {str(e)}"}]
|
|
186
|
+
|
|
187
|
+
def _map_value(self, param_name, value):
|
|
188
|
+
# Special handling for seriousness fields: if value is "No", skip this field
|
|
189
|
+
seriousness_fields = {
|
|
190
|
+
"seriousnessdeath",
|
|
191
|
+
"seriousnesshospitalization",
|
|
192
|
+
"seriousnessdisabling",
|
|
193
|
+
"seriousnesslifethreatening",
|
|
194
|
+
"seriousnessother",
|
|
195
|
+
}
|
|
196
|
+
if param_name in seriousness_fields:
|
|
197
|
+
if value == "No":
|
|
198
|
+
return None, None # Signal to skip this field
|
|
199
|
+
if value == "Yes":
|
|
200
|
+
return None, "1"
|
|
201
|
+
# If not Yes/No, error
|
|
202
|
+
return (
|
|
203
|
+
f"Invalid value '{value}' for '{param_name}'. Allowed values: ['Yes', 'No']",
|
|
204
|
+
None,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if param_name in HUMAN_TO_FDA_MAP:
|
|
208
|
+
value_map = HUMAN_TO_FDA_MAP[param_name]
|
|
209
|
+
if value not in value_map:
|
|
210
|
+
print("No mapping found for value:", value, "skipping")
|
|
211
|
+
allowed_values = list(value_map.keys())
|
|
212
|
+
return (
|
|
213
|
+
f"Invalid value '{value}' for '{param_name}'. Allowed values: {allowed_values}",
|
|
214
|
+
None,
|
|
215
|
+
)
|
|
216
|
+
return None, value_map[value]
|
|
217
|
+
return None, value
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@register_tool("FDACountAdditiveReactionsTool")
|
|
221
|
+
class FDACountAdditiveReactionsTool(FDADrugAdverseEventTool):
|
|
222
|
+
"""
|
|
223
|
+
Leverage openFDA API to count adverse reaction events across multiple drugs in one request.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
def __init__(
|
|
227
|
+
self,
|
|
228
|
+
tool_config,
|
|
229
|
+
endpoint_url="https://api.fda.gov/drug/event.json",
|
|
230
|
+
api_key=None,
|
|
231
|
+
):
|
|
232
|
+
super().__init__(tool_config)
|
|
233
|
+
|
|
234
|
+
def run(self, arguments):
|
|
235
|
+
# Make a copy to avoid modifying the original
|
|
236
|
+
arguments = copy.deepcopy(arguments)
|
|
237
|
+
|
|
238
|
+
# Validate medicinalproducts list first
|
|
239
|
+
drugs = arguments.pop("medicinalproducts", [])
|
|
240
|
+
if not drugs:
|
|
241
|
+
return {"error": "`medicinalproducts` list is required."}
|
|
242
|
+
if not isinstance(drugs, list):
|
|
243
|
+
return {"error": "`medicinalproducts` must be a list of drug names."}
|
|
244
|
+
|
|
245
|
+
# Validate the remaining enum parameters
|
|
246
|
+
validation_error = self.validate_enum_arguments(arguments)
|
|
247
|
+
if validation_error:
|
|
248
|
+
return {"error": validation_error}
|
|
249
|
+
|
|
250
|
+
# Build OR clause for multiple drugs
|
|
251
|
+
escaped = []
|
|
252
|
+
for d in drugs:
|
|
253
|
+
val = urllib.parse.quote(d, safe="")
|
|
254
|
+
escaped.append(f"patient.drug.medicinalproduct:{val}")
|
|
255
|
+
or_clause = "+OR+".join(escaped)
|
|
256
|
+
|
|
257
|
+
# Combine additional filters
|
|
258
|
+
filters = []
|
|
259
|
+
for k, v in arguments.items():
|
|
260
|
+
mapping_error, mapped = self._map_value(k, v)
|
|
261
|
+
if mapping_error:
|
|
262
|
+
return {"error": mapping_error}
|
|
263
|
+
if mapped is None:
|
|
264
|
+
continue # Skip this field if instructed
|
|
265
|
+
filters.append(f"{k}:{mapped}")
|
|
266
|
+
|
|
267
|
+
filter_str = "+AND+".join(filters) if filters else ""
|
|
268
|
+
search_query = f"({or_clause})" + (f"+AND+{filter_str}" if filter_str else "")
|
|
269
|
+
|
|
270
|
+
# Call API
|
|
271
|
+
if self.api_key:
|
|
272
|
+
url = f"{self.endpoint_url}?api_key={self.api_key}&search={search_query}&count={self.count_field}"
|
|
273
|
+
else:
|
|
274
|
+
url = f"{self.endpoint_url}?search={search_query}&count={self.count_field}"
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
resp = requests.get(url)
|
|
278
|
+
resp.raise_for_status()
|
|
279
|
+
results = resp.json().get("results", [])
|
|
280
|
+
results = self._post_process(results)
|
|
281
|
+
return results
|
|
282
|
+
except requests.exceptions.RequestException as e:
|
|
283
|
+
return {"error": f"API request failed: {str(e)}"}
|