rowan-mcp 1.0.1__py3-none-any.whl → 2.0.0__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 rowan-mcp might be problematic. Click here for more details.

Files changed (70) hide show
  1. rowan_mcp/__init__.py +2 -2
  2. rowan_mcp/__main__.py +3 -5
  3. rowan_mcp/functions_v2/BENCHMARK.md +86 -0
  4. rowan_mcp/functions_v2/molecule_lookup.py +232 -0
  5. rowan_mcp/functions_v2/protein_management.py +141 -0
  6. rowan_mcp/functions_v2/submit_basic_calculation_workflow.py +195 -0
  7. rowan_mcp/functions_v2/submit_conformer_search_workflow.py +158 -0
  8. rowan_mcp/functions_v2/submit_descriptors_workflow.py +52 -0
  9. rowan_mcp/functions_v2/submit_docking_workflow.py +244 -0
  10. rowan_mcp/functions_v2/submit_fukui_workflow.py +114 -0
  11. rowan_mcp/functions_v2/submit_irc_workflow.py +58 -0
  12. rowan_mcp/functions_v2/submit_macropka_workflow.py +99 -0
  13. rowan_mcp/functions_v2/submit_pka_workflow.py +72 -0
  14. rowan_mcp/functions_v2/submit_protein_cofolding_workflow.py +88 -0
  15. rowan_mcp/functions_v2/submit_redox_potential_workflow.py +55 -0
  16. rowan_mcp/functions_v2/submit_scan_workflow.py +82 -0
  17. rowan_mcp/functions_v2/submit_solubility_workflow.py +157 -0
  18. rowan_mcp/functions_v2/submit_tautomer_search_workflow.py +51 -0
  19. rowan_mcp/functions_v2/workflow_management_v2.py +382 -0
  20. rowan_mcp/server.py +109 -144
  21. rowan_mcp/tests/basic_calculation_from_json.py +0 -0
  22. rowan_mcp/tests/basic_calculation_with_constraint.py +33 -0
  23. rowan_mcp/tests/basic_calculation_with_solvent.py +0 -0
  24. rowan_mcp/tests/bde.py +37 -0
  25. rowan_mcp/tests/benchmark_queries.md +120 -0
  26. rowan_mcp/tests/cofolding_screen.py +131 -0
  27. rowan_mcp/tests/conformer_dependent_redox.py +37 -0
  28. rowan_mcp/tests/conformers.py +31 -0
  29. rowan_mcp/tests/data.json +189 -0
  30. rowan_mcp/tests/docking_screen.py +157 -0
  31. rowan_mcp/tests/irc.py +24 -0
  32. rowan_mcp/tests/macropka.py +13 -0
  33. rowan_mcp/tests/multistage_opt.py +13 -0
  34. rowan_mcp/tests/optimization.py +21 -0
  35. rowan_mcp/tests/phenol_pka.py +36 -0
  36. rowan_mcp/tests/pka.py +36 -0
  37. rowan_mcp/tests/protein_cofolding.py +17 -0
  38. rowan_mcp/tests/scan.py +28 -0
  39. {rowan_mcp-1.0.1.dist-info → rowan_mcp-2.0.0.dist-info}/METADATA +49 -33
  40. rowan_mcp-2.0.0.dist-info/RECORD +42 -0
  41. rowan_mcp/functions/admet.py +0 -94
  42. rowan_mcp/functions/bde.py +0 -113
  43. rowan_mcp/functions/calculation_retrieve.py +0 -89
  44. rowan_mcp/functions/conformers.py +0 -80
  45. rowan_mcp/functions/descriptors.py +0 -92
  46. rowan_mcp/functions/docking.py +0 -340
  47. rowan_mcp/functions/docking_enhanced.py +0 -174
  48. rowan_mcp/functions/electronic_properties.py +0 -205
  49. rowan_mcp/functions/folder_management.py +0 -137
  50. rowan_mcp/functions/fukui.py +0 -219
  51. rowan_mcp/functions/hydrogen_bond_basicity.py +0 -94
  52. rowan_mcp/functions/irc.py +0 -125
  53. rowan_mcp/functions/macropka.py +0 -120
  54. rowan_mcp/functions/molecular_converter.py +0 -423
  55. rowan_mcp/functions/molecular_dynamics.py +0 -191
  56. rowan_mcp/functions/molecule_lookup.py +0 -57
  57. rowan_mcp/functions/multistage_opt.py +0 -171
  58. rowan_mcp/functions/pdb_handler.py +0 -200
  59. rowan_mcp/functions/pka.py +0 -137
  60. rowan_mcp/functions/redox_potential.py +0 -352
  61. rowan_mcp/functions/scan.py +0 -536
  62. rowan_mcp/functions/scan_analyzer.py +0 -347
  63. rowan_mcp/functions/solubility.py +0 -277
  64. rowan_mcp/functions/spin_states.py +0 -747
  65. rowan_mcp/functions/system_management.py +0 -368
  66. rowan_mcp/functions/tautomers.py +0 -91
  67. rowan_mcp/functions/workflow_management.py +0 -422
  68. rowan_mcp-1.0.1.dist-info/RECORD +0 -34
  69. {rowan_mcp-1.0.1.dist-info → rowan_mcp-2.0.0.dist-info}/WHEEL +0 -0
  70. {rowan_mcp-1.0.1.dist-info → rowan_mcp-2.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,137 +0,0 @@
1
- """
2
- Calculate pKa values for molecules using Rowan API.
3
- """
4
-
5
- import os
6
- import rowan
7
- from typing import Optional
8
-
9
- # Set up logging
10
- import logging
11
- logger = logging.getLogger(__name__)
12
-
13
- # Configure rowan API key
14
- if not hasattr(rowan, 'api_key') or not rowan.api_key:
15
- api_key = os.getenv("ROWAN_API_KEY")
16
- if api_key:
17
- rowan.api_key = api_key
18
- logger.info("🔑 Rowan API key configured")
19
- else:
20
- logger.error("No ROWAN_API_KEY found in environment")
21
-
22
- def log_rowan_api_call(workflow_type: str, **kwargs):
23
- """Log Rowan API calls and let Rowan handle its own errors."""
24
-
25
- # Simple logging for calculations
26
- logger.info(f" Starting {workflow_type.replace('_', ' ')}...")
27
-
28
- # Let Rowan handle everything - no custom error handling
29
- return rowan.compute(workflow_type=workflow_type, **kwargs)
30
-
31
- def rowan_pka(
32
- name: str,
33
- molecule: str,
34
- folder_uuid: Optional[str] = None,
35
- blocking: bool = True,
36
- ping_interval: int = 5
37
- ) -> str:
38
- """Calculate pKa values for molecules.
39
-
40
- Args:
41
- name: Name for the calculation
42
- molecule: Molecule SMILES string or common name
43
- folder_uuid: UUID of folder to organize calculation in
44
- blocking: Whether to wait for completion (default: True)
45
- ping_interval: How often to check status in seconds (default: 5)
46
-
47
- Returns:
48
- pKa calculation results
49
- """
50
- try:
51
- result = log_rowan_api_call(
52
- workflow_type="pka",
53
- name=name,
54
- molecule=molecule,
55
- folder_uuid=folder_uuid,
56
- blocking=blocking,
57
- ping_interval=ping_interval
58
- )
59
-
60
- # Format results based on whether we waited or not
61
- if blocking:
62
- # We waited for completion - format actual results
63
- status = result.get('status', result.get('object_status', 'Unknown'))
64
-
65
- if status == 2: # Completed successfully
66
- formatted = f" pKa calculation for '{name}' completed successfully!\n\n"
67
- elif status == 3: # Failed
68
- formatted = f" pKa calculation for '{name}' failed!\n\n"
69
- else:
70
- formatted = f" pKa calculation for '{name}' finished with status {status}\n\n"
71
-
72
- formatted += f" Molecule: {molecule}\n"
73
- formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
74
- formatted += f" Status: {status}\n"
75
-
76
- # Try to extract pKa results
77
- if isinstance(result, dict) and 'object_data' in result and result['object_data']:
78
- data = result['object_data']
79
-
80
- # Extract pKa values
81
- if 'strongest_acid' in data:
82
- if data['strongest_acid'] is not None:
83
- formatted += f" Strongest Acid pKa: {data['strongest_acid']:.2f}\n"
84
- else:
85
- formatted += f" Strongest Acid pKa: N/A (no acidic sites found)\n"
86
-
87
- if 'strongest_base' in data:
88
- if data['strongest_base'] is not None:
89
- formatted += f" Strongest Base pKa: {data['strongest_base']:.2f}\n"
90
- else:
91
- formatted += f" Strongest Base pKa: N/A (no basic sites found)\n"
92
- if 'pka_values' in data and isinstance(data['pka_values'], list):
93
- formatted += f" All pKa values: {', '.join([f'{val:.2f}' for val in data['pka_values']])}\n"
94
-
95
- # Additional properties if available
96
- if 'ionizable_sites' in data:
97
- formatted += f" Ionizable sites found: {data['ionizable_sites']}\n"
98
-
99
- # Basic guidance
100
- if status == 2:
101
- formatted += f"\n Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for detailed data\n"
102
- else:
103
- # Non-blocking mode - just submission confirmation
104
- formatted = f" pKa calculation for '{name}' submitted!\n\n"
105
- formatted += f" Molecule: {molecule}\n"
106
- formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
107
- formatted += f" Status: {result.get('status', 'Submitted')}\n"
108
-
109
- return formatted
110
-
111
- except Exception as e:
112
- error_response = {
113
- "error": f"pKa calculation failed: {str(e)}",
114
- "name": name,
115
- "molecule": molecule
116
- }
117
- return str(error_response)
118
-
119
-
120
- def test_rowan_pka():
121
- """Test the rowan_pka function."""
122
- try:
123
- # Test with minimal parameters
124
- result = rowan_pka(
125
- name="test_pka_water",
126
- molecule="O"
127
- )
128
- print("✅ pKa test successful!")
129
- print(f"Result: {result}")
130
- return True
131
- except Exception as e:
132
- print(f"pKa test failed: {e}")
133
- return False
134
-
135
-
136
- if __name__ == "__main__":
137
- test_rowan_pka()
@@ -1,352 +0,0 @@
1
- """
2
- Rowan redox potential function for electrochemistry.
3
- """
4
-
5
- import os
6
- import logging
7
- import time
8
- from typing import Optional
9
-
10
- try:
11
- import rowan
12
- except ImportError:
13
- rowan = None
14
-
15
- # Setup logging
16
- logger = logging.getLogger(__name__)
17
-
18
- # Setup API key
19
- api_key = os.getenv("ROWAN_API_KEY")
20
- if rowan and api_key:
21
- rowan.api_key = api_key
22
-
23
- def lookup_molecule_smiles(molecule_name: str) -> str:
24
- """Look up canonical SMILES for common molecule names."""
25
- # Common molecule SMILES database
26
- MOLECULE_SMILES = {
27
- # Aromatics
28
- "phenol": "Oc1ccccc1",
29
- "benzene": "c1ccccc1",
30
- "toluene": "Cc1ccccc1",
31
- "aniline": "Nc1ccccc1",
32
- "benzoic acid": "O=C(O)c1ccccc1",
33
- "pyridine": "c1ccncc1",
34
- "furan": "c1ccoc1",
35
- "thiophene": "c1ccsc1",
36
- "pyrrole": "c1cc[nH]c1",
37
- "imidazole": "c1c[nH]cn1",
38
- "indole": "c1ccc2c(c1)cc[nH]2",
39
- "quinoline": "c1ccc2ncccc2c1",
40
-
41
- # Aliphatics
42
- "methane": "C",
43
- "ethane": "CC",
44
- "propane": "CCC",
45
- "butane": "CCCC",
46
- "pentane": "CCCCC",
47
- "hexane": "CCCCCC",
48
- "heptane": "CCCCCCC",
49
- "octane": "CCCCCCCC",
50
- "nonane": "CCCCCCCCC",
51
- "decane": "CCCCCCCCCC",
52
-
53
- # Alcohols
54
- "methanol": "CO",
55
- "ethanol": "CCO",
56
- "propanol": "CCCO",
57
- "isopropanol": "CC(C)O",
58
- "butanol": "CCCCO",
59
- "isobutanol": "CC(C)CO",
60
- "tert-butanol": "CC(C)(C)O",
61
-
62
- # Simple molecules
63
- "water": "O",
64
- "hydrogen peroxide": "OO",
65
- "ammonia": "N",
66
- "methyl amine": "CN",
67
- "ethyl amine": "CCN",
68
- "formaldehyde": "C=O",
69
- "acetaldehyde": "CC=O",
70
- "acetone": "CC(=O)C",
71
- "formic acid": "C(=O)O",
72
- "acetic acid": "CC(=O)O",
73
- "acetamide": "CC(=O)N",
74
- "dimethyl sulfoxide": "CS(=O)C",
75
- "hydrogen sulfide": "S",
76
- "carbon dioxide": "O=C=O",
77
- "carbon monoxide": "C#O",
78
-
79
- # Cyclic compounds
80
- "cyclopropane": "C1CC1",
81
- "cyclobutane": "C1CCC1",
82
- "cyclopentane": "C1CCCC1",
83
- "cyclohexane": "C1CCCCC1",
84
- "cycloheptane": "C1CCCCCC1",
85
- "cyclooctane": "C1CCCCCCC1",
86
-
87
- # Ethers
88
- "diethyl ether": "CCOCC",
89
- "tetrahydrofuran": "C1CCOC1",
90
- "dioxane": "C1COCCO1",
91
-
92
- # Halogens
93
- "chloroform": "C(Cl)(Cl)Cl",
94
- "carbon tetrachloride": "C(Cl)(Cl)(Cl)Cl",
95
- "methyl chloride": "CCl",
96
- "dichloromethane": "C(Cl)Cl",
97
- "fluoromethane": "CF",
98
- "bromomethane": "CBr",
99
- "iodomethane": "CI",
100
-
101
- # Sugars
102
- "glucose": "C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O",
103
- "fructose": "C([C@H]([C@H]([C@@H]([C@H](CO)O)O)O)O)O",
104
- "sucrose": "C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O[C@]2([C@H]([C@@H]([C@@H](O2)CO)O)O)CO)O)O)O)O",
105
-
106
- # Nucleotides
107
- "adenine": "c1nc(c2c(n1)ncn2)N",
108
- "guanine": "c1nc2c(n1)c(=O)[nH]c(=n2)N",
109
- "cytosine": "c1c(nc(=O)[nH]c1=O)N",
110
- "thymine": "Cc1c[nH]c(=O)[nH]c1=O",
111
- "uracil": "c1c[nH]c(=O)[nH]c1=O",
112
-
113
- # Amino acids
114
- "glycine": "C(C(=O)O)N",
115
- "alanine": "C[C@@H](C(=O)O)N",
116
- "valine": "CC(C)[C@@H](C(=O)O)N",
117
- "leucine": "CC(C)C[C@@H](C(=O)O)N",
118
- "isoleucine": "CC[C@H](C)[C@@H](C(=O)O)N",
119
- "phenylalanine": "c1ccc(cc1)C[C@@H](C(=O)O)N",
120
- "tyrosine": "c1cc(ccc1C[C@@H](C(=O)O)N)O",
121
- "tryptophan": "c1ccc2c(c1)c(c[nH]2)C[C@@H](C(=O)O)N",
122
- "serine": "C([C@@H](C(=O)O)N)O",
123
- "threonine": "C[C@H]([C@@H](C(=O)O)N)O",
124
- "asparagine": "C([C@@H](C(=O)O)N)C(=O)N",
125
- "glutamine": "C(CC(=O)N)[C@@H](C(=O)O)N",
126
- "aspartic acid": "C([C@@H](C(=O)O)N)C(=O)O",
127
- "glutamic acid": "C(CC(=O)O)[C@@H](C(=O)O)N",
128
- "lysine": "C(CCN)C[C@@H](C(=O)O)N",
129
- "arginine": "C(C[C@@H](C(=O)O)N)CN=C(N)N",
130
- "histidine": "c1c([nH]cn1)C[C@@H](C(=O)O)N",
131
- "cysteine": "C([C@@H](C(=O)O)N)S",
132
- "methionine": "CCSC[C@@H](C(=O)O)N",
133
- "proline": "C1C[C@H](NC1)C(=O)O",
134
-
135
- # Drugs and bioactive molecules
136
- "aspirin": "CC(=O)Oc1ccccc1C(=O)O",
137
- "caffeine": "Cn1c(=O)c2c(ncn2C)n(C)c1=O",
138
- "nicotine": "CN1CCC[C@H]1c2cccnc2",
139
- "morphine": "CN1CC[C@]23c4c5ccc(O)c4O[C@H]2[C@@H](O)C=C[C@H]3[C@H]1C5",
140
- "codeine": "COc1ccc2c3c1O[C@H]1[C@@H](O)C=C[C@H]4[C@@H]1C[C@@](CC3)(C2)N4C",
141
- "glucose": "C([C@@H]1[C@H]([C@@H]([C@H]([C@H](O1)O)O)O)O)O",
142
- "cholesterol": "C[C@H](CCCC(C)C)[C@H]1CC[C@@H]2[C@@H]1CC[C@H]3[C@@H]2CC=C4[C@@]3(CC[C@@H](C4)O)C",
143
- "testosterone": "C[C@]12CC[C@H]3[C@H]([C@@H]1CC[C@@H]2O)CCC4=CC(=O)CC[C@]34C",
144
- "estradiol": "C[C@]12CC[C@H]3[C@H]([C@@H]1CC[C@@H]2O)CCC4=C3C=CC(=C4)O",
145
-
146
- # Solvents
147
- "dichloromethane": "C(Cl)Cl",
148
- "chloroform": "C(Cl)(Cl)Cl",
149
- "carbon tetrachloride": "C(Cl)(Cl)(Cl)Cl",
150
- "acetonitrile": "CC#N",
151
- "dimethylformamide": "CN(C)C=O",
152
- "dimethyl sulfoxide": "CS(=O)C",
153
- "ethyl acetate": "CCOC(=O)C",
154
- "diethyl ether": "CCOCC",
155
- "tetrahydrofuran": "C1CCOC1",
156
- "1,4-dioxane": "C1COCCO1",
157
- "toluene": "Cc1ccccc1",
158
- "xylene": "Cc1ccccc1C",
159
- "mesitylene": "Cc1cc(C)cc(C)c1",
160
- "hexane": "CCCCCC",
161
- "heptane": "CCCCCCC",
162
- "octane": "CCCCCCCC",
163
- "cyclohexane": "C1CCCCC1",
164
- "benzene": "c1ccccc1",
165
- "nitromethane": "C[N+](=O)[O-]",
166
- "acetone": "CC(=O)C",
167
- "2-butanone": "CCC(=O)C",
168
- "2-propanol": "CC(C)O",
169
- "1-butanol": "CCCCO",
170
- "2-butanol": "CC(C)CO",
171
- "tert-butanol": "CC(C)(C)O",
172
- "ethylene glycol": "OCCO",
173
- "propylene glycol": "CC(CO)O",
174
- "glycerol": "C(C(CO)O)O",
175
- }
176
-
177
- # Try exact match first
178
- name_lower = molecule_name.lower().strip()
179
- if name_lower in MOLECULE_SMILES:
180
- return MOLECULE_SMILES[name_lower]
181
-
182
- # If not found, return the original input (assume it's already SMILES)
183
- return molecule_name
184
-
185
- def log_rowan_api_call(workflow_type: str, **kwargs):
186
- """Log Rowan API calls with detailed parameters."""
187
-
188
- try:
189
- start_time = time.time()
190
- result = rowan.compute(workflow_type=workflow_type, **kwargs)
191
- api_time = time.time() - start_time
192
-
193
- if isinstance(result, dict) and 'uuid' in result:
194
- job_status = result.get('status', result.get('object_status', 'Unknown'))
195
- status_names = {0: "Queued", 1: "Running", 2: "Completed", 3: "Failed", 4: "Stopped", 5: "Awaiting Queue"}
196
- status_text = status_names.get(job_status, f"Unknown ({job_status})")
197
-
198
- return result
199
-
200
- except Exception as e:
201
- api_time = time.time() - start_time
202
- raise e
203
-
204
- def rowan_redox_potential(
205
- name: str,
206
- molecule: str,
207
- reduction: bool = True,
208
- oxidation: bool = True,
209
- mode: str = "rapid",
210
- folder_uuid: Optional[str] = None,
211
- blocking: bool = True,
212
- ping_interval: int = 5
213
- ) -> str:
214
- """Predict redox potentials vs. SCE in acetonitrile.
215
-
216
- Calculates oxidation and reduction potentials for:
217
- - Electrochemical reaction design
218
- - Battery and energy storage applications
219
- - Understanding electron transfer processes
220
-
221
- **Important**: Only acetonitrile solvent is supported by Rowan's redox workflow.
222
-
223
- Use this for: Electrochemistry, battery materials, electron transfer studies
224
-
225
- Args:
226
- name: Name for the calculation
227
- molecule: Molecule SMILES string or common name (e.g., "phenol", "benzene")
228
- reduction: Whether to calculate reduction potential (default: True)
229
- oxidation: Whether to calculate oxidation potential (default: True)
230
- mode: Calculation accuracy mode - "reckless", "rapid", "careful", "meticulous" (default: "rapid")
231
- folder_uuid: Optional folder UUID for organization
232
- blocking: Whether to wait for completion (default: True)
233
- ping_interval: Check status interval in seconds (default: 5)
234
-
235
- Returns:
236
- Redox potential results vs. SCE in acetonitrile
237
- """
238
- # Look up SMILES if a common name was provided
239
- canonical_smiles = lookup_molecule_smiles(molecule)
240
-
241
- # Validate mode
242
- valid_modes = ["reckless", "rapid", "careful", "meticulous"]
243
- mode_lower = mode.lower()
244
- if mode_lower not in valid_modes:
245
- return f" Invalid mode '{mode}'. Valid modes: {', '.join(valid_modes)}"
246
-
247
- # At least one type must be selected
248
- if not reduction and not oxidation:
249
- return f" At least one of 'reduction' or 'oxidation' must be True"
250
-
251
- logger.info(f" Name: {name}")
252
- logger.info(f" Input: {molecule}")
253
- logger.info(f" Mode: {mode_lower}")
254
- logger.info(f" Reduction: {reduction}")
255
- logger.info(f" Oxidation: {oxidation}")
256
-
257
- # Build parameters for Rowan API
258
- redox_params = {
259
- "name": name,
260
- "molecule": canonical_smiles,
261
- "reduction": reduction,
262
- "oxidation": oxidation,
263
- "mode": mode_lower,
264
- "solvent": "acetonitrile", # Required by Rowan
265
- "folder_uuid": folder_uuid,
266
- "blocking": blocking,
267
- "ping_interval": ping_interval
268
- }
269
-
270
- result = log_rowan_api_call(
271
- workflow_type="redox_potential",
272
- **redox_params
273
- )
274
-
275
- if blocking:
276
- status = result.get('status', result.get('object_status', 'Unknown'))
277
-
278
- if status == 2: # Completed successfully
279
- formatted = f" Redox potential analysis for '{name}' completed successfully!\n\n"
280
- elif status == 3: # Failed
281
- formatted = f" Redox potential analysis for '{name}' failed!\n\n"
282
- else:
283
- formatted = f" Redox potential analysis for '{name}' finished with status {status}\n\n"
284
-
285
- formatted += f" Molecule: {molecule}\n"
286
- formatted += f" SMILES: {canonical_smiles}\n"
287
- formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
288
- formatted += f" Status: {status}\n"
289
- formatted += f"⚙ Mode: {mode_lower.title()}\n"
290
- formatted += f"💧 Solvent: Acetonitrile\n"
291
-
292
- # Show which potentials were calculated
293
- calc_types = []
294
- if reduction:
295
- calc_types.append("Reduction")
296
- if oxidation:
297
- calc_types.append("Oxidation")
298
- formatted += f" Calculated: {' + '.join(calc_types)} potential(s)\n"
299
-
300
- # Try to extract redox potential results
301
- if isinstance(result, dict) and 'object_data' in result and result['object_data']:
302
- data = result['object_data']
303
-
304
- if reduction and 'reduction_potential' in data and data['reduction_potential'] is not None:
305
- formatted += f" Reduction Potential: {data['reduction_potential']:.3f} V vs. SCE\n"
306
-
307
- if oxidation and 'oxidation_potential' in data and data['oxidation_potential'] is not None:
308
- formatted += f" Oxidation Potential: {data['oxidation_potential']:.3f} V vs. SCE\n"
309
-
310
- # Legacy support for older format
311
- if 'redox_potential' in data and data['redox_potential'] is not None:
312
- redox_type = data.get('redox_type', 'unknown')
313
- formatted += f" {redox_type.title()} Potential: {data['redox_potential']:.3f} V vs. SCE\n"
314
-
315
- if status == 2:
316
- formatted += f"\n **Results Available:**\n"
317
- formatted += f"• Potentials reported vs. SCE (Saturated Calomel Electrode)\n"
318
- formatted += f"• Calculated in acetonitrile solvent\n"
319
- formatted += f"• Use rowan_workflow_management(action='retrieve', workflow_uuid='{result.get('uuid')}') for detailed data\n"
320
-
321
- return formatted
322
- else:
323
- formatted = f" Redox potential analysis for '{name}' submitted!\n\n"
324
- formatted += f" Molecule: {molecule}\n"
325
- formatted += f" SMILES: {canonical_smiles}\n"
326
- formatted += f" Job UUID: {result.get('uuid', 'N/A')}\n"
327
- formatted += f" Status: {result.get('status', 'Submitted')}\n"
328
- formatted += f"⚙ Mode: {mode_lower.title()}\n"
329
-
330
- calc_types = []
331
- if reduction:
332
- calc_types.append("Reduction")
333
- if oxidation:
334
- calc_types.append("Oxidation")
335
- formatted += f" Will calculate: {' + '.join(calc_types)} potential(s)\n"
336
-
337
- return formatted
338
-
339
- def test_rowan_redox_potential():
340
- """Test the rowan_redox_potential function."""
341
- try:
342
- # Test with a simple molecule (non-blocking to avoid long wait)
343
- result = rowan_redox_potential("test_redox", "phenol", blocking=False)
344
- print(" Redox potential test successful!")
345
- print(f"Result length: {len(result)} characters")
346
- return True
347
- except Exception as e:
348
- print(f" Redox potential test failed: {e}")
349
- return False
350
-
351
- if __name__ == "__main__":
352
- test_rowan_redox_potential()