react-cheminfo 0.76.0 → 0.77.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 (316) hide show
  1. package/README.md +141 -31
  2. package/lib/card/core/cardAddress.d.ts +19 -2
  3. package/lib/card/core/cardAddress.d.ts.map +1 -1
  4. package/lib/card/core/cardAddress.js +35 -2
  5. package/lib/card/core/cardAddress.js.map +1 -1
  6. package/lib/card/core/cardPageUrl.d.ts +7 -1
  7. package/lib/card/core/cardPageUrl.d.ts.map +1 -1
  8. package/lib/card/core/cardPageUrl.js +4 -2
  9. package/lib/card/core/cardPageUrl.js.map +1 -1
  10. package/lib/card/core/index.d.ts +1 -1
  11. package/lib/card/core/index.d.ts.map +1 -1
  12. package/lib/card/core/index.js +1 -1
  13. package/lib/card/core/index.js.map +1 -1
  14. package/lib/credits/core/credits.d.ts +30 -0
  15. package/lib/credits/core/credits.d.ts.map +1 -1
  16. package/lib/credits/core/credits.js +35 -0
  17. package/lib/credits/core/credits.js.map +1 -1
  18. package/lib/download/core/downloadFigure.d.ts +12 -1
  19. package/lib/download/core/downloadFigure.d.ts.map +1 -1
  20. package/lib/download/core/downloadFigure.js +21 -0
  21. package/lib/download/core/downloadFigure.js.map +1 -1
  22. package/lib/download/core/figureDpi.d.ts +59 -0
  23. package/lib/download/core/figureDpi.d.ts.map +1 -0
  24. package/lib/download/core/figureDpi.js +75 -0
  25. package/lib/download/core/figureDpi.js.map +1 -0
  26. package/lib/download/core/figurePng.d.ts +4 -1
  27. package/lib/download/core/figurePng.d.ts.map +1 -1
  28. package/lib/download/core/figurePng.js +11 -2
  29. package/lib/download/core/figurePng.js.map +1 -1
  30. package/lib/download/core/index.d.ts +4 -1
  31. package/lib/download/core/index.d.ts.map +1 -1
  32. package/lib/download/core/index.js +3 -1
  33. package/lib/download/core/index.js.map +1 -1
  34. package/lib/download/core/pngDpi.d.ts +22 -0
  35. package/lib/download/core/pngDpi.d.ts.map +1 -0
  36. package/lib/download/core/pngDpi.js +79 -0
  37. package/lib/download/core/pngDpi.js.map +1 -0
  38. package/lib/download/ui/FigureDownloadPanel.d.ts +13 -0
  39. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  40. package/lib/download/ui/FigureDownloadPanel.js +18 -34
  41. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  42. package/lib/download/ui/figureDownloadHint.d.ts +5 -0
  43. package/lib/download/ui/figureDownloadHint.d.ts.map +1 -1
  44. package/lib/download/ui/figureDownloadHint.js +8 -0
  45. package/lib/download/ui/figureDownloadHint.js.map +1 -1
  46. package/lib/download/ui/figureDownloadOptions.d.ts +84 -0
  47. package/lib/download/ui/figureDownloadOptions.d.ts.map +1 -0
  48. package/lib/download/ui/figureDownloadOptions.js +2 -0
  49. package/lib/download/ui/figureDownloadOptions.js.map +1 -0
  50. package/lib/download/ui/resolutionChoices.d.ts +27 -0
  51. package/lib/download/ui/resolutionChoices.d.ts.map +1 -0
  52. package/lib/download/ui/resolutionChoices.js +48 -0
  53. package/lib/download/ui/resolutionChoices.js.map +1 -0
  54. package/lib/download/ui/useFigureActions.d.ts +4 -1
  55. package/lib/download/ui/useFigureActions.d.ts.map +1 -1
  56. package/lib/download/ui/useFigureActions.js +17 -8
  57. package/lib/download/ui/useFigureActions.js.map +1 -1
  58. package/lib/download/ui/useFigureDownload.d.ts +2 -55
  59. package/lib/download/ui/useFigureDownload.d.ts.map +1 -1
  60. package/lib/download/ui/useFigureDownload.js +45 -13
  61. package/lib/download/ui/useFigureDownload.js.map +1 -1
  62. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  63. package/lib/ecosystem/core/sites.js +14 -0
  64. package/lib/ecosystem/core/sites.js.map +1 -1
  65. package/lib/ecosystem/core/types.d.ts +1 -1
  66. package/lib/ecosystem/core/types.d.ts.map +1 -1
  67. package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
  68. package/lib/ecosystem/ui/glyphs.js +3 -0
  69. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  70. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  71. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  72. package/lib/locales/de.json +30 -1
  73. package/lib/locales/en.json +30 -1
  74. package/lib/locales/es.json +30 -1
  75. package/lib/locales/fr.json +30 -1
  76. package/lib/locales/it.json +30 -1
  77. package/lib/molecule3d/ui/Molecule3DStage.d.ts +4 -33
  78. package/lib/molecule3d/ui/Molecule3DStage.d.ts.map +1 -1
  79. package/lib/molecule3d/ui/Molecule3DStage.js +3 -37
  80. package/lib/molecule3d/ui/Molecule3DStage.js.map +1 -1
  81. package/lib/osiris/core/barGeometry.d.ts +42 -0
  82. package/lib/osiris/core/barGeometry.d.ts.map +1 -0
  83. package/lib/osiris/core/barGeometry.js +46 -0
  84. package/lib/osiris/core/barGeometry.js.map +1 -0
  85. package/lib/osiris/core/computeProperties.d.ts +42 -0
  86. package/lib/osiris/core/computeProperties.d.ts.map +1 -0
  87. package/lib/osiris/core/computeProperties.js +115 -0
  88. package/lib/osiris/core/computeProperties.js.map +1 -0
  89. package/lib/osiris/core/constants.d.ts +70 -0
  90. package/lib/osiris/core/constants.d.ts.map +1 -0
  91. package/lib/osiris/core/constants.js +99 -0
  92. package/lib/osiris/core/constants.js.map +1 -0
  93. package/lib/osiris/core/fitness.d.ts +32 -0
  94. package/lib/osiris/core/fitness.d.ts.map +1 -0
  95. package/lib/osiris/core/fitness.js +48 -0
  96. package/lib/osiris/core/fitness.js.map +1 -0
  97. package/lib/osiris/core/index.d.ts +29 -0
  98. package/lib/osiris/core/index.d.ts.map +1 -0
  99. package/lib/osiris/core/index.js +19 -0
  100. package/lib/osiris/core/index.js.map +1 -0
  101. package/lib/osiris/core/poolSize.d.ts +17 -0
  102. package/lib/osiris/core/poolSize.d.ts.map +1 -0
  103. package/lib/osiris/core/poolSize.js +22 -0
  104. package/lib/osiris/core/poolSize.js.map +1 -0
  105. package/lib/osiris/core/poolTypes.d.ts +140 -0
  106. package/lib/osiris/core/poolTypes.d.ts.map +1 -0
  107. package/lib/osiris/core/poolTypes.js +12 -0
  108. package/lib/osiris/core/poolTypes.js.map +1 -0
  109. package/lib/osiris/core/predictIdCode.d.ts +39 -0
  110. package/lib/osiris/core/predictIdCode.d.ts.map +1 -0
  111. package/lib/osiris/core/predictIdCode.js +54 -0
  112. package/lib/osiris/core/predictIdCode.js.map +1 -0
  113. package/lib/osiris/core/predictProtocol.d.ts +67 -0
  114. package/lib/osiris/core/predictProtocol.d.ts.map +1 -0
  115. package/lib/osiris/core/predictProtocol.js +29 -0
  116. package/lib/osiris/core/predictProtocol.js.map +1 -0
  117. package/lib/osiris/core/predictionPool.d.ts +23 -0
  118. package/lib/osiris/core/predictionPool.d.ts.map +1 -0
  119. package/lib/osiris/core/predictionPool.js +186 -0
  120. package/lib/osiris/core/predictionPool.js.map +1 -0
  121. package/lib/osiris/core/predictionQueue.d.ts +46 -0
  122. package/lib/osiris/core/predictionQueue.d.ts.map +1 -0
  123. package/lib/osiris/core/predictionQueue.js +57 -0
  124. package/lib/osiris/core/predictionQueue.js.map +1 -0
  125. package/lib/osiris/core/predictors.d.ts +62 -0
  126. package/lib/osiris/core/predictors.d.ts.map +1 -0
  127. package/lib/osiris/core/predictors.js +100 -0
  128. package/lib/osiris/core/predictors.js.map +1 -0
  129. package/lib/osiris/core/propertyKeys.d.ts +32 -0
  130. package/lib/osiris/core/propertyKeys.d.ts.map +1 -0
  131. package/lib/osiris/core/propertyKeys.js +44 -0
  132. package/lib/osiris/core/propertyKeys.js.map +1 -0
  133. package/lib/osiris/core/riskAppearance.d.ts +60 -0
  134. package/lib/osiris/core/riskAppearance.d.ts.map +1 -0
  135. package/lib/osiris/core/riskAppearance.js +59 -0
  136. package/lib/osiris/core/riskAppearance.js.map +1 -0
  137. package/lib/osiris/core/riskDetail.d.ts +22 -0
  138. package/lib/osiris/core/riskDetail.d.ts.map +1 -0
  139. package/lib/osiris/core/riskDetail.js +86 -0
  140. package/lib/osiris/core/riskDetail.js.map +1 -0
  141. package/lib/osiris/core/riskDetailRequest.d.ts +31 -0
  142. package/lib/osiris/core/riskDetailRequest.d.ts.map +1 -0
  143. package/lib/osiris/core/riskDetailRequest.js +49 -0
  144. package/lib/osiris/core/riskDetailRequest.js.map +1 -0
  145. package/lib/osiris/core/riskFindings.d.ts +26 -0
  146. package/lib/osiris/core/riskFindings.d.ts.map +1 -0
  147. package/lib/osiris/core/riskFindings.js +79 -0
  148. package/lib/osiris/core/riskFindings.js.map +1 -0
  149. package/lib/osiris/core/runPredictionJob.d.ts +20 -0
  150. package/lib/osiris/core/runPredictionJob.d.ts.map +1 -0
  151. package/lib/osiris/core/runPredictionJob.js +43 -0
  152. package/lib/osiris/core/runPredictionJob.js.map +1 -0
  153. package/lib/osiris/core/scales.d.ts +70 -0
  154. package/lib/osiris/core/scales.d.ts.map +1 -0
  155. package/lib/osiris/core/scales.js +185 -0
  156. package/lib/osiris/core/scales.js.map +1 -0
  157. package/lib/osiris/core/types.d.ts +91 -0
  158. package/lib/osiris/core/types.d.ts.map +1 -0
  159. package/lib/osiris/core/types.js +19 -0
  160. package/lib/osiris/core/types.js.map +1 -0
  161. package/lib/osiris/core/valuation.d.ts +28 -0
  162. package/lib/osiris/core/valuation.d.ts.map +1 -0
  163. package/lib/osiris/core/valuation.js +39 -0
  164. package/lib/osiris/core/valuation.js.map +1 -0
  165. package/lib/osiris/ui/PropertyBar.d.ts +27 -0
  166. package/lib/osiris/ui/PropertyBar.d.ts.map +1 -0
  167. package/lib/osiris/ui/PropertyBar.js +27 -0
  168. package/lib/osiris/ui/PropertyBar.js.map +1 -0
  169. package/lib/osiris/ui/PropertyPanel.d.ts +91 -0
  170. package/lib/osiris/ui/PropertyPanel.d.ts.map +1 -0
  171. package/lib/osiris/ui/PropertyPanel.js +77 -0
  172. package/lib/osiris/ui/PropertyPanel.js.map +1 -0
  173. package/lib/osiris/ui/PropertyRows.d.ts +40 -0
  174. package/lib/osiris/ui/PropertyRows.d.ts.map +1 -0
  175. package/lib/osiris/ui/PropertyRows.js +23 -0
  176. package/lib/osiris/ui/PropertyRows.js.map +1 -0
  177. package/lib/osiris/ui/RiskFindings.d.ts +43 -0
  178. package/lib/osiris/ui/RiskFindings.d.ts.map +1 -0
  179. package/lib/osiris/ui/RiskFindings.js +51 -0
  180. package/lib/osiris/ui/RiskFindings.js.map +1 -0
  181. package/lib/osiris/ui/RiskPanel.d.ts +46 -0
  182. package/lib/osiris/ui/RiskPanel.d.ts.map +1 -0
  183. package/lib/osiris/ui/RiskPanel.js +32 -0
  184. package/lib/osiris/ui/RiskPanel.js.map +1 -0
  185. package/lib/osiris/ui/RiskSquare.d.ts +51 -0
  186. package/lib/osiris/ui/RiskSquare.d.ts.map +1 -0
  187. package/lib/osiris/ui/RiskSquare.js +23 -0
  188. package/lib/osiris/ui/RiskSquare.js.map +1 -0
  189. package/lib/osiris/ui/index.d.ts +15 -0
  190. package/lib/osiris/ui/index.d.ts.map +1 -0
  191. package/lib/osiris/ui/index.js +8 -0
  192. package/lib/osiris/ui/index.js.map +1 -0
  193. package/lib/osiris/ui/usePredictions.d.ts +81 -0
  194. package/lib/osiris/ui/usePredictions.d.ts.map +1 -0
  195. package/lib/osiris/ui/usePredictions.js +102 -0
  196. package/lib/osiris/ui/usePredictions.js.map +1 -0
  197. package/lib/osiris-ui.d.ts +11 -0
  198. package/lib/osiris-ui.d.ts.map +1 -0
  199. package/lib/osiris-ui.js +11 -0
  200. package/lib/osiris-ui.js.map +1 -0
  201. package/lib/osiris.d.ts +17 -0
  202. package/lib/osiris.d.ts.map +1 -0
  203. package/lib/osiris.js +17 -0
  204. package/lib/osiris.js.map +1 -0
  205. package/lib/panel/ui/FilterInput.d.ts.map +1 -1
  206. package/lib/panel/ui/FilterInput.js +3 -1
  207. package/lib/panel/ui/FilterInput.js.map +1 -1
  208. package/lib/panel/ui/SidePanelItem.d.ts +39 -0
  209. package/lib/panel/ui/SidePanelItem.d.ts.map +1 -0
  210. package/lib/panel/ui/SidePanelItem.js +63 -0
  211. package/lib/panel/ui/SidePanelItem.js.map +1 -0
  212. package/lib/panel/ui/SidePanelStack.d.ts +11 -10
  213. package/lib/panel/ui/SidePanelStack.d.ts.map +1 -1
  214. package/lib/panel/ui/SidePanelStack.js +22 -15
  215. package/lib/panel/ui/SidePanelStack.js.map +1 -1
  216. package/lib/panel/ui/SpectrumColorSwatch.d.ts.map +1 -1
  217. package/lib/panel/ui/SpectrumColorSwatch.js +4 -2
  218. package/lib/panel/ui/SpectrumColorSwatch.js.map +1 -1
  219. package/lib/panel/ui/usePanelFolds.d.ts +35 -0
  220. package/lib/panel/ui/usePanelFolds.d.ts.map +1 -0
  221. package/lib/panel/ui/usePanelFolds.js +73 -0
  222. package/lib/panel/ui/usePanelFolds.js.map +1 -0
  223. package/lib/shared/ui/FullscreenStage.d.ts +43 -0
  224. package/lib/shared/ui/FullscreenStage.d.ts.map +1 -0
  225. package/lib/shared/ui/FullscreenStage.js +38 -0
  226. package/lib/shared/ui/FullscreenStage.js.map +1 -0
  227. package/lib/shared/ui/canFillScreen.d.ts +7 -0
  228. package/lib/shared/ui/canFillScreen.d.ts.map +1 -0
  229. package/lib/shared/ui/canFillScreen.js +9 -0
  230. package/lib/shared/ui/canFillScreen.js.map +1 -0
  231. package/lib/shared/ui/index.d.ts +3 -0
  232. package/lib/shared/ui/index.d.ts.map +1 -1
  233. package/lib/shared/ui/index.js +2 -0
  234. package/lib/shared/ui/index.js.map +1 -1
  235. package/lib/split/ui/splitPanes.d.ts +3 -3
  236. package/lib/split/ui/splitPanes.js +1 -1
  237. package/lib/structure/core/structureNotations.d.ts +8 -1
  238. package/lib/structure/core/structureNotations.d.ts.map +1 -1
  239. package/lib/structure/core/structureNotations.js +14 -8
  240. package/lib/structure/core/structureNotations.js.map +1 -1
  241. package/lib/structure/ui/StructureExportPanel.d.ts +8 -0
  242. package/lib/structure/ui/StructureExportPanel.d.ts.map +1 -1
  243. package/lib/structure/ui/StructureExportPanel.js +9 -4
  244. package/lib/structure/ui/StructureExportPanel.js.map +1 -1
  245. package/lib/structure/ui/index.d.ts +1 -0
  246. package/lib/structure/ui/index.d.ts.map +1 -1
  247. package/lib/structure/ui/index.js.map +1 -1
  248. package/package.json +4 -1
  249. package/src/card/core/cardAddress.ts +39 -3
  250. package/src/card/core/cardPageUrl.ts +10 -3
  251. package/src/card/core/index.ts +7 -1
  252. package/src/credits/core/credits.ts +40 -0
  253. package/src/download/core/downloadFigure.ts +26 -1
  254. package/src/download/core/figureDpi.ts +94 -0
  255. package/src/download/core/figurePng.ts +11 -2
  256. package/src/download/core/index.ts +13 -1
  257. package/src/download/core/pngDpi.ts +84 -0
  258. package/src/download/ui/FigureDownloadPanel.tsx +41 -51
  259. package/src/download/ui/figureDownloadHint.ts +13 -0
  260. package/src/download/ui/figureDownloadOptions.ts +85 -0
  261. package/src/download/ui/resolutionChoices.ts +67 -0
  262. package/src/download/ui/useFigureActions.ts +24 -9
  263. package/src/download/ui/useFigureDownload.tsx +57 -71
  264. package/src/ecosystem/core/sites.ts +14 -0
  265. package/src/ecosystem/core/types.ts +2 -1
  266. package/src/ecosystem/ui/glyphs.tsx +20 -0
  267. package/src/locales/de.json +30 -1
  268. package/src/locales/en.json +30 -1
  269. package/src/locales/es.json +30 -1
  270. package/src/locales/fr.json +30 -1
  271. package/src/locales/it.json +30 -1
  272. package/src/molecule3d/ui/Molecule3DStage.tsx +4 -88
  273. package/src/osiris/core/barGeometry.ts +74 -0
  274. package/src/osiris/core/computeProperties.ts +175 -0
  275. package/src/osiris/core/constants.ts +110 -0
  276. package/src/osiris/core/fitness.ts +59 -0
  277. package/src/osiris/core/index.ts +78 -0
  278. package/src/osiris/core/poolSize.ts +24 -0
  279. package/src/osiris/core/poolTypes.ts +140 -0
  280. package/src/osiris/core/predictIdCode.ts +69 -0
  281. package/src/osiris/core/predictProtocol.ts +78 -0
  282. package/src/osiris/core/predictionPool.ts +237 -0
  283. package/src/osiris/core/predictionQueue.ts +89 -0
  284. package/src/osiris/core/predictors.ts +142 -0
  285. package/src/osiris/core/propertyKeys.ts +54 -0
  286. package/src/osiris/core/riskAppearance.ts +86 -0
  287. package/src/osiris/core/riskDetail.ts +120 -0
  288. package/src/osiris/core/riskDetailRequest.ts +64 -0
  289. package/src/osiris/core/riskFindings.ts +92 -0
  290. package/src/osiris/core/runPredictionJob.ts +49 -0
  291. package/src/osiris/core/scales.ts +236 -0
  292. package/src/osiris/core/types.ts +103 -0
  293. package/src/osiris/core/valuation.ts +44 -0
  294. package/src/osiris/ui/PropertyBar.tsx +77 -0
  295. package/src/osiris/ui/PropertyPanel.tsx +227 -0
  296. package/src/osiris/ui/PropertyRows.tsx +92 -0
  297. package/src/osiris/ui/RiskFindings.tsx +137 -0
  298. package/src/osiris/ui/RiskPanel.tsx +81 -0
  299. package/src/osiris/ui/RiskSquare.tsx +108 -0
  300. package/src/osiris/ui/index.ts +17 -0
  301. package/src/osiris/ui/usePredictions.ts +194 -0
  302. package/src/osiris-ui.ts +11 -0
  303. package/src/osiris.ts +17 -0
  304. package/src/panel/ui/FilterInput.tsx +4 -1
  305. package/src/panel/ui/SidePanelItem.tsx +162 -0
  306. package/src/panel/ui/SidePanelStack.tsx +29 -36
  307. package/src/panel/ui/SpectrumColorSwatch.tsx +5 -3
  308. package/src/panel/ui/usePanelFolds.ts +103 -0
  309. package/src/shared/ui/FullscreenStage.tsx +96 -0
  310. package/src/shared/ui/canFillScreen.ts +8 -0
  311. package/src/shared/ui/index.ts +6 -0
  312. package/src/split/ui/splitPanes.tsx +4 -4
  313. package/src/structure/core/structureNotations.ts +25 -8
  314. package/src/structure/ui/StructureExportPanel.tsx +23 -4
  315. package/src/structure/ui/index.ts +4 -0
  316. package/styles/osiris.css +339 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Prediction from the only thing worth keeping per row.
3
+ *
4
+ * A row never holds an `OCL.Molecule` — ten thousand of them cost 198 MB — so
5
+ * what travels to a worker is the idCode, and the molecule is rebuilt there in
6
+ * a quarter of a millisecond against the 380 ms the prediction itself costs.
7
+ */
8
+
9
+ import { splitIdCode } from '../../structure/core/editorValue.ts';
10
+
11
+ import type { ComputePropertiesOptions } from './computeProperties.ts';
12
+ import { computeProperties } from './computeProperties.ts';
13
+ import type { OsirisPredictors } from './predictors.ts';
14
+ import { riskDetail } from './riskDetail.ts';
15
+ import type { OsirisProperties, RiskDetail, RiskType } from './types.ts';
16
+
17
+ /**
18
+ * What is said when an idCode cannot be read.
19
+ *
20
+ * An idCode is written by a program, not typed by a chemist, so there is no
21
+ * position to point at and no parser reason worth repeating: OpenChemLib
22
+ * answers a malformed one with `Cannot set properties of undefined`, which
23
+ * names nothing anyone can act on. Worse, it does not always throw — `zzzz`
24
+ * reads as a 117-atom molecule — so this is the floor, not the whole guard.
25
+ */
26
+ export const UNREADABLE_ID_CODE_MESSAGE = 'This structure could not be read.';
27
+
28
+ /**
29
+ * Predict everything the tool shows, from an idCode.
30
+ * @param value - The idCode, with the editor's atom coordinates after a space or without them.
31
+ * @param predictors - OpenChemLib with its resources registered and checked.
32
+ * @param options - See {@link ComputePropertiesOptions}.
33
+ * @returns The ten numbers, the four risks, and what to call the molecule.
34
+ * @throws {Error} When the idCode cannot be read, or describes no atoms.
35
+ */
36
+ export function predictIdCode(
37
+ value: string,
38
+ predictors: OsirisPredictors,
39
+ options: ComputePropertiesOptions = {},
40
+ ): OsirisProperties {
41
+ return computeProperties(read(value, predictors), predictors, options);
42
+ }
43
+
44
+ /**
45
+ * Why one risk came back as it did, from an idCode.
46
+ * @param value - The idCode, coordinates included or not.
47
+ * @param risk - Which of the four risks to explain.
48
+ * @param predictors - OpenChemLib with its resources registered and checked.
49
+ * @returns The findings, with the matched atoms and bonds of every fragment.
50
+ * @throws {Error} When the idCode cannot be read.
51
+ */
52
+ export function riskDetailOfIdCode(
53
+ value: string,
54
+ risk: RiskType,
55
+ predictors: OsirisPredictors,
56
+ ): RiskDetail {
57
+ return riskDetail(read(value, predictors), risk, predictors);
58
+ }
59
+
60
+ function read(value: string, predictors: OsirisPredictors) {
61
+ const { idCode, coordinates } = splitIdCode(value);
62
+ try {
63
+ return coordinates === undefined
64
+ ? predictors.Molecule.fromIDCode(idCode)
65
+ : predictors.Molecule.fromIDCode(idCode, coordinates);
66
+ } catch (error) {
67
+ throw new Error(UNREADABLE_ID_CODE_MESSAGE, { cause: error });
68
+ }
69
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * What the page asks a prediction worker, and what it answers.
3
+ *
4
+ * One molecule per request. Two of the three answers are ordinary results; the
5
+ * third, `unavailable`, is how a worker says its resource tables did not load.
6
+ * That one travels as a *successful* answer on purpose: a rejection would be
7
+ * indistinguishable from the ordinary failure of one unreadable structure,
8
+ * whereas this one means every answer from every worker is worthless and the
9
+ * run must stop and say so.
10
+ */
11
+
12
+ import type { ResourceSource } from './predictors.ts';
13
+ import type { OsirisProperties, RiskDetail, RiskType } from './types.ts';
14
+
15
+ /** What every request carries, whichever job it asks for. */
16
+ interface BaseRequest {
17
+ /**
18
+ * Where the worker registers OpenChemLib's resources from. In a browser this
19
+ * is always the absolute URL of the site's own `openchemlib-resources.json`,
20
+ * resolved on the main thread: a worker has no `document`, so it cannot read
21
+ * the deployment's mount path itself. The parsed tables are accepted as well,
22
+ * for the in-process fallback where there is no worker to post them to.
23
+ */
24
+ resources: ResourceSource;
25
+ }
26
+
27
+ /** Predict everything the tool shows for one molecule. */
28
+ export interface PropertiesRequest extends BaseRequest {
29
+ kind: 'properties';
30
+ /** The caller's key for the row, echoed back so answers can arrive in any order. */
31
+ key: string;
32
+ /** The idCode, with the editor's atom coordinates after a space or without. */
33
+ idCode: string;
34
+ /**
35
+ * The name the source carried.
36
+ * @default the canonical SMILES
37
+ */
38
+ label?: string;
39
+ }
40
+
41
+ /** Explain why one risk came back as it did. */
42
+ export interface RiskDetailRequest extends BaseRequest {
43
+ kind: 'riskDetail';
44
+ /** The idCode, coordinates included or not. */
45
+ idCode: string;
46
+ /** Which of the four risks to explain. */
47
+ risk: RiskType;
48
+ }
49
+
50
+ /** One job for a prediction worker. */
51
+ export type PredictRequest = PropertiesRequest | RiskDetailRequest;
52
+
53
+ /** What a prediction worker answers. */
54
+ export type PredictResponse =
55
+ | { kind: 'properties'; key: string; properties: OsirisProperties }
56
+ | { kind: 'riskDetail'; detail: RiskDetail }
57
+ | { kind: 'unavailable'; message: string };
58
+
59
+ /**
60
+ * Whether a message is a job this worker handles, so anything else is refused
61
+ * rather than read as one.
62
+ * @param value - Whatever was posted.
63
+ * @returns True when it is a well-formed request.
64
+ */
65
+ export function isPredictRequest(value: unknown): value is PredictRequest {
66
+ if (typeof value !== 'object' || value === null) return false;
67
+ const request = value as {
68
+ kind?: unknown;
69
+ resources?: unknown;
70
+ idCode?: unknown;
71
+ key?: unknown;
72
+ risk?: unknown;
73
+ };
74
+ if (typeof request.idCode !== 'string') return false;
75
+ if (request.resources === undefined) return false;
76
+ if (request.kind === 'properties') return typeof request.key === 'string';
77
+ return request.kind === 'riskDetail' && typeof request.risk === 'string';
78
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * A pool of prediction workers, answering one molecule at a time each.
3
+ *
4
+ * `createWorkerChannel` from `react-cheminfo/core` does the per-worker half —
5
+ * request identifiers so answers match callers whatever order they arrive in,
6
+ * timeouts, cancellation, and the in-process fallback where a page has no
7
+ * `Worker` at all. What it does not do is spread work over several workers, so
8
+ * that is what this adds: one channel per worker, a queue the pool owns, and at
9
+ * most one outstanding job per channel, which is what makes reordering possible
10
+ * at all. A channel asked for everything at once would post everything at once,
11
+ * and the row the reader is looking at would be answered forty-eighth.
12
+ */
13
+
14
+ import { errorMessage } from '../../error/core/toError.ts';
15
+ import { CancelledRequestError } from '../../worker/core/errors.ts';
16
+ import { createWorkerChannel } from '../../worker/core/workerChannel.ts';
17
+ import type { WorkerChannel } from '../../worker/core/workerChannelTypes.ts';
18
+
19
+ import { predictionPoolSize } from './poolSize.ts';
20
+ import type {
21
+ PredictionInput,
22
+ PredictionPool,
23
+ PredictionPoolOptions,
24
+ PredictionProgress,
25
+ } from './poolTypes.ts';
26
+ import type { PredictRequest, PredictResponse } from './predictProtocol.ts';
27
+ import { createPredictionQueue } from './predictionQueue.ts';
28
+ import { PredictorsUnavailableError } from './predictors.ts';
29
+ import { requestRiskDetail } from './riskDetailRequest.ts';
30
+ import { runPredictionJob } from './runPredictionJob.ts';
31
+ import type { RiskDetail, RiskType } from './types.ts';
32
+
33
+ type Channel = WorkerChannel<PredictRequest, PredictResponse>;
34
+
35
+ interface Slot {
36
+ channel: Channel;
37
+ busy: boolean;
38
+ }
39
+
40
+ /**
41
+ * Start a pool of prediction workers.
42
+ *
43
+ * Workers are created lazily by the channels, on their first job, so a page
44
+ * that never predicts anything never loads OpenChemLib.
45
+ * @param options - See {@link PredictionPoolOptions}.
46
+ * @returns The pool.
47
+ */
48
+ export function createPredictionPool(
49
+ options: PredictionPoolOptions,
50
+ ): PredictionPool {
51
+ const {
52
+ resources,
53
+ onResult,
54
+ onProgress,
55
+ onUnavailable,
56
+ createWorker,
57
+ runInProcess,
58
+ timeoutMs = 60_000,
59
+ } = options;
60
+ const size = predictionPoolSize(options.size);
61
+ const run = runInProcess ?? runPredictionJob;
62
+
63
+ const slots: Slot[] = [];
64
+ for (let index = 0; index < size; index++) {
65
+ slots.push({
66
+ channel: createWorkerChannel<PredictRequest, PredictResponse>(
67
+ createWorker,
68
+ {
69
+ name: `osiris-prediction-${index}`,
70
+ defaultTimeoutMs: timeoutMs,
71
+ runInProcess: run,
72
+ },
73
+ ),
74
+ busy: false,
75
+ });
76
+ }
77
+
78
+ const queue = createPredictionQueue();
79
+ const inFlight = new Set<string>();
80
+ let generation = 0;
81
+ let done = 0;
82
+ let total = 0;
83
+ let running = false;
84
+ let terminated = false;
85
+ let unavailable: string | null = null;
86
+
87
+ function progress(): PredictionProgress {
88
+ return { done, total, pending: queue.size + inFlight.size, running };
89
+ }
90
+
91
+ function report(): void {
92
+ onProgress?.(progress());
93
+ }
94
+
95
+ function pump(): void {
96
+ if (unavailable !== null) return;
97
+ for (const slot of slots) {
98
+ if (slot.busy) continue;
99
+ const next = queue.take();
100
+ if (next === undefined) break;
101
+ dispatch(slot, next);
102
+ }
103
+ }
104
+
105
+ function dispatch(slot: Slot, input: PredictionInput): void {
106
+ const era = generation;
107
+ slot.busy = true;
108
+ inFlight.add(input.key);
109
+ const request: PredictRequest = {
110
+ kind: 'properties',
111
+ resources,
112
+ key: input.key,
113
+ idCode: input.idCode,
114
+ ...(input.label === undefined ? {} : { label: input.label }),
115
+ };
116
+ slot.channel.request(request).then(
117
+ (response) => {
118
+ settle(slot, input, era, response, null);
119
+ },
120
+ (error: unknown) => {
121
+ settle(slot, input, era, null, error);
122
+ },
123
+ );
124
+ }
125
+
126
+ function settle(
127
+ slot: Slot,
128
+ input: PredictionInput,
129
+ era: number,
130
+ response: PredictResponse | null,
131
+ error: unknown,
132
+ ): void {
133
+ slot.busy = false;
134
+ inFlight.delete(input.key);
135
+
136
+ // An answer from a cancelled run is not counted and not reported, but the
137
+ // slot it just freed is still dispatched from: a submit made in the same
138
+ // turn as the cancel found every slot busy and queued its molecules here.
139
+ if (era === generation) {
140
+ if (response?.kind === 'unavailable') {
141
+ stop(response.message);
142
+ return;
143
+ }
144
+ if (response?.kind === 'properties') {
145
+ done += 1;
146
+ onResult?.({
147
+ key: input.key,
148
+ ok: true,
149
+ properties: response.properties,
150
+ });
151
+ } else if (error !== null && !(error instanceof CancelledRequestError)) {
152
+ done += 1;
153
+ onResult?.({ key: input.key, ok: false, message: errorMessage(error) });
154
+ }
155
+ }
156
+
157
+ pump();
158
+ if (queue.size === 0 && inFlight.size === 0) running = false;
159
+ report();
160
+ }
161
+
162
+ function clear(): void {
163
+ generation += 1;
164
+ queue.clear();
165
+ inFlight.clear();
166
+ running = false;
167
+ for (const slot of slots) slot.channel.cancel();
168
+ }
169
+
170
+ function stop(message: string): void {
171
+ clear();
172
+ unavailable = message;
173
+ report();
174
+ onUnavailable?.(message);
175
+ }
176
+
177
+ function leastBusy(): Slot {
178
+ let best = slots[0] as Slot;
179
+ for (const slot of slots) {
180
+ if (slot.channel.pendingCount < best.channel.pendingCount) best = slot;
181
+ }
182
+ return best;
183
+ }
184
+
185
+ return {
186
+ size,
187
+ get progress() {
188
+ return progress();
189
+ },
190
+
191
+ submit(molecules) {
192
+ if (unavailable !== null) return;
193
+ if (!running && queue.size === 0 && inFlight.size === 0) {
194
+ done = 0;
195
+ total = 0;
196
+ }
197
+ for (const input of molecules) {
198
+ if (inFlight.has(input.key)) continue;
199
+ if (queue.add(input)) total += 1;
200
+ }
201
+ running = queue.size > 0 || inFlight.size > 0;
202
+ pump();
203
+ report();
204
+ },
205
+
206
+ prioritize(keys) {
207
+ queue.prioritize(keys);
208
+ },
209
+
210
+ async riskDetail(idCode: string, risk: RiskType): Promise<RiskDetail> {
211
+ const response = await requestRiskDetail(
212
+ () => leastBusy().channel,
213
+ { kind: 'riskDetail', resources, idCode, risk },
214
+ () => !terminated && unavailable === null,
215
+ );
216
+ if (response.kind === 'unavailable') {
217
+ stop(response.message);
218
+ throw new PredictorsUnavailableError(response.message);
219
+ }
220
+ if (response.kind !== 'riskDetail') {
221
+ throw new Error('The worker answered something other than a detail.');
222
+ }
223
+ return response.detail;
224
+ },
225
+
226
+ cancel() {
227
+ clear();
228
+ report();
229
+ },
230
+
231
+ terminate() {
232
+ terminated = true;
233
+ clear();
234
+ for (const slot of slots) slot.channel.terminate();
235
+ },
236
+ };
237
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * What is still to be predicted, in the order it will be.
3
+ *
4
+ * Arrival order by default, because that is the order the reader read the file
5
+ * in. On top of it sits a short list of keys to answer first — the rows
6
+ * actually on screen — which is re-laid every time the reader scrolls or
7
+ * focuses a row. A key named as wanted that has since been answered or removed
8
+ * is skipped rather than looked up again.
9
+ */
10
+
11
+ import type { PredictionInput } from './poolTypes.ts';
12
+
13
+ /** The molecules waiting, and which of them to take next. */
14
+ export interface PredictionQueue {
15
+ /** How many are waiting. */
16
+ readonly size: number;
17
+ /**
18
+ * Whether one is waiting.
19
+ * @param key - The row's key.
20
+ * @returns True when it is still queued.
21
+ */
22
+ has: (key: string) => boolean;
23
+ /**
24
+ * Queue one molecule, unless its key is already waiting.
25
+ * @param input - The molecule.
26
+ * @returns True when it was added.
27
+ */
28
+ add: (input: PredictionInput) => boolean;
29
+ /**
30
+ * Take the next molecule to predict: the first still-queued key of the wanted
31
+ * list, else the one that has waited longest.
32
+ * @returns The molecule, or `undefined` when nothing is waiting.
33
+ */
34
+ take: () => PredictionInput | undefined;
35
+ /**
36
+ * Replace the list of keys to answer first.
37
+ * @param keys - The keys, most wanted first.
38
+ */
39
+ prioritize: (keys: readonly string[]) => void;
40
+ /** Forget everything waiting. */
41
+ clear: () => void;
42
+ }
43
+
44
+ /**
45
+ * A queue of molecules to predict.
46
+ * @returns The queue, empty.
47
+ */
48
+ export function createPredictionQueue(): PredictionQueue {
49
+ const waiting = new Map<string, PredictionInput>();
50
+ let wanted: readonly string[] = [];
51
+ let cursor = 0;
52
+
53
+ return {
54
+ get size() {
55
+ return waiting.size;
56
+ },
57
+ has: (key) => waiting.has(key),
58
+ add(input) {
59
+ if (waiting.has(input.key)) return false;
60
+ waiting.set(input.key, input);
61
+ return true;
62
+ },
63
+ take() {
64
+ while (cursor < wanted.length) {
65
+ const key = wanted[cursor] as string;
66
+ cursor += 1;
67
+ const next = waiting.get(key);
68
+ if (next !== undefined) {
69
+ waiting.delete(key);
70
+ return next;
71
+ }
72
+ }
73
+ const first = waiting.keys().next();
74
+ if (first.done === true) return undefined;
75
+ const next = waiting.get(first.value) as PredictionInput;
76
+ waiting.delete(first.value);
77
+ return next;
78
+ },
79
+ prioritize(keys) {
80
+ wanted = [...keys];
81
+ cursor = 0;
82
+ },
83
+ clear() {
84
+ waiting.clear();
85
+ wanted = [];
86
+ cursor = 0;
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * OpenChemLib, loaded once and proved to be working before anything is
3
+ * predicted with it.
4
+ *
5
+ * Both predictors throw `static resources must be registered first` until
6
+ * `Resources.register*` has run — but only when *nothing* was registered. If
7
+ * something else was, `new ToxicityPredictor()` constructs happily, prints to
8
+ * the console, and answers every `assessRisk` with `RISK_UNKNOWN`, on which
9
+ * `DrugScoreCalculator` applies no penalty at all. The failure therefore reads
10
+ * on screen as four clean molecules and a healthy score. So the tables are
11
+ * proved here, at start-up, against a molecule whose answer is known: a
12
+ * fragment match that must come back high, and a drug-likeness that must be a
13
+ * real number. Between them the two families of resource file are both covered.
14
+ *
15
+ * The check costs about 160 ms once per worker, most of which is warm-up the
16
+ * first real molecule would have paid anyway — measured, a first prediction is
17
+ * 320 ms cold against 191 ms warm.
18
+ */
19
+
20
+ import type * as OCL from 'openchemlib';
21
+
22
+ import {
23
+ NO_FRAGMENT_MATCHED,
24
+ RISK_TYPE_CODES,
25
+ UNKNOWN_NUMBER,
26
+ } from './constants.ts';
27
+
28
+ /**
29
+ * Where the resource tables come from: the URL of the site's own
30
+ * `openchemlib-resources.json`, or the parsed file itself.
31
+ *
32
+ * A URL must be absolute. `registerFromUrl` resolves a relative one against
33
+ * `import.meta.url`, which after a build is a hashed chunk under `assets/`, so
34
+ * it answers 404 and every prediction quietly becomes unknown.
35
+ */
36
+ export type ResourceSource = string | Readonly<Record<string, string>>;
37
+
38
+ /** OpenChemLib, registered, checked and ready to predict with. */
39
+ export interface OsirisPredictors {
40
+ /** Reads an idCode or a SMILES into a molecule. */
41
+ Molecule: typeof OCL.Molecule;
42
+ /** cLogP, solubility, TPSA and the four counts. */
43
+ MoleculeProperties: typeof OCL.MoleculeProperties;
44
+ /** Finds a toxicophore on a molecule, in the predictor's own match mode. */
45
+ SSSearcher: typeof OCL.SSSearcher;
46
+ /** The four spline terms and the four risk factors. */
47
+ DrugScoreCalculator: typeof OCL.DrugScoreCalculator;
48
+ /** The four risk assessments and their explanations. */
49
+ toxicity: OCL.ToxicityPredictor;
50
+ /** The 5294-fragment drug-likeness table. */
51
+ druglikeness: OCL.DruglikenessPredictor;
52
+ }
53
+
54
+ /** The tables did not load, so nothing may be predicted. */
55
+ export class PredictorsUnavailableError extends Error {
56
+ override name = 'PredictorsUnavailableError';
57
+ }
58
+
59
+ /**
60
+ * OpenChemLib with its resources registered and checked.
61
+ *
62
+ * Loaded lazily — the library is 340 kB gzipped and does not tree-shake — and
63
+ * kept, because `Resources.register` replaces the whole map rather than merging
64
+ * into it, so registering twice with different keys silently unregisters the
65
+ * first set.
66
+ * @param source - The resources URL, or the parsed resources themselves.
67
+ * @returns The predictors, proved to answer.
68
+ * @throws {PredictorsUnavailableError} When the resources did not load, or loaded and answer nothing.
69
+ */
70
+ export function loadPredictors(
71
+ source: ResourceSource,
72
+ ): Promise<OsirisPredictors> {
73
+ if (loaded === null) loaded = start(source);
74
+ return loaded;
75
+ }
76
+
77
+ /** Forget the loaded predictors, so the next call registers again. */
78
+ export function resetPredictors(): void {
79
+ loaded = null;
80
+ }
81
+
82
+ let loaded: Promise<OsirisPredictors> | null = null;
83
+
84
+ /**
85
+ * A failed load is not kept: a fetch that failed once may well answer on the
86
+ * next try, and a cached rejection would turn one lost packet into a page that
87
+ * stays broken until it is reloaded.
88
+ * @param source - The resources URL, or the parsed resources themselves.
89
+ * @returns The predictors, proved to answer.
90
+ */
91
+ function start(source: ResourceSource): Promise<OsirisPredictors> {
92
+ const pending = build(source);
93
+ void pending.catch(() => {
94
+ if (loaded === pending) loaded = null;
95
+ });
96
+ return pending;
97
+ }
98
+
99
+ /** Benzidine — its mutagenicity comes from a fragment match, not from a list. */
100
+ const PROBE_ID_CODE = 'dg}@@@mIe]e^ftx@H@H@@';
101
+
102
+ const UNAVAILABLE =
103
+ 'The prediction tables did not load, so no risk or drug-likeness can be computed. Reload the page.';
104
+
105
+ async function build(source: ResourceSource): Promise<OsirisPredictors> {
106
+ const ocl = await import('openchemlib');
107
+ try {
108
+ if (typeof source === 'string') await ocl.Resources.registerFromUrl(source);
109
+ else ocl.Resources.register({ ...source });
110
+ } catch {
111
+ throw new PredictorsUnavailableError(UNAVAILABLE);
112
+ }
113
+
114
+ let toxicity: OCL.ToxicityPredictor;
115
+ let druglikeness: OCL.DruglikenessPredictor;
116
+ try {
117
+ toxicity = new ocl.ToxicityPredictor();
118
+ druglikeness = new ocl.DruglikenessPredictor();
119
+ } catch {
120
+ throw new PredictorsUnavailableError(UNAVAILABLE);
121
+ }
122
+
123
+ const probe = ocl.Molecule.fromIDCode(PROBE_ID_CODE);
124
+ const risk = toxicity.assessRisk(probe, RISK_TYPE_CODES.mutagenic);
125
+ const likeness = druglikeness.assessDruglikeness(probe);
126
+ if (
127
+ risk !== 3 ||
128
+ likeness === UNKNOWN_NUMBER ||
129
+ likeness === NO_FRAGMENT_MATCHED
130
+ ) {
131
+ throw new PredictorsUnavailableError(UNAVAILABLE);
132
+ }
133
+
134
+ return {
135
+ Molecule: ocl.Molecule,
136
+ MoleculeProperties: ocl.MoleculeProperties,
137
+ SSSearcher: ocl.SSSearcher,
138
+ DrugScoreCalculator: ocl.DrugScoreCalculator,
139
+ toxicity,
140
+ druglikeness,
141
+ };
142
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Which of a molecule's predicted numbers the tool shows, and in which order.
3
+ *
4
+ * The order is the legacy panel's, top to bottom, and it is the same order the
5
+ * comparison table's columns and the plot's default axes take: a reader who
6
+ * learned the panel reads the table without relearning it. The keys are the
7
+ * field names of {@link OsirisProperties}, so an address carrying
8
+ * `axes=logP,logS` names the same things the panel does.
9
+ */
10
+
11
+ import type { OsirisProperties } from './types.ts';
12
+
13
+ /** The ten numbers the tool shows, in the order it shows them. */
14
+ export const PROPERTY_KEYS = [
15
+ 'logP',
16
+ 'logS',
17
+ 'molecularWeight',
18
+ 'polarSurfaceArea',
19
+ 'druglikeness',
20
+ 'acceptorCount',
21
+ 'donorCount',
22
+ 'stereoCenterCount',
23
+ 'rotatableBondCount',
24
+ 'drugScore',
25
+ ] as const;
26
+
27
+ /** One of the ten displayed properties. */
28
+ export type PropertyKey = (typeof PROPERTY_KEYS)[number];
29
+
30
+ /**
31
+ * Whether a string names a displayed property, so an address that carries
32
+ * something else is ignored rather than plotted as a blank axis.
33
+ * @param value - Whatever the address or the reader supplied.
34
+ * @returns True when it is one of the ten.
35
+ */
36
+ export function isPropertyKey(value: string): value is PropertyKey {
37
+ return (PROPERTY_KEYS as readonly string[]).includes(value);
38
+ }
39
+
40
+ /**
41
+ * One property of one molecule.
42
+ *
43
+ * `null` means the predictor could not answer — never zero, and never the
44
+ * library's own sentinel, both of which plot and sort as real values.
45
+ * @param properties - What was predicted for the molecule.
46
+ * @param key - Which of the ten to read.
47
+ * @returns The number, or `null` when it is unknown.
48
+ */
49
+ export function propertyValue(
50
+ properties: OsirisProperties,
51
+ key: PropertyKey,
52
+ ): number | null {
53
+ return properties[key];
54
+ }