react-cheminfo 0.75.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.
- package/README.md +141 -31
- package/lib/card/core/cardAddress.d.ts +19 -2
- package/lib/card/core/cardAddress.d.ts.map +1 -1
- package/lib/card/core/cardAddress.js +35 -2
- package/lib/card/core/cardAddress.js.map +1 -1
- package/lib/card/core/cardPageUrl.d.ts +7 -1
- package/lib/card/core/cardPageUrl.d.ts.map +1 -1
- package/lib/card/core/cardPageUrl.js +4 -2
- package/lib/card/core/cardPageUrl.js.map +1 -1
- package/lib/card/core/index.d.ts +1 -1
- package/lib/card/core/index.d.ts.map +1 -1
- package/lib/card/core/index.js +1 -1
- package/lib/card/core/index.js.map +1 -1
- package/lib/credits/core/credits.d.ts +30 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +35 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/download/core/copySvg.d.ts +12 -0
- package/lib/download/core/copySvg.d.ts.map +1 -0
- package/lib/download/core/copySvg.js +38 -0
- package/lib/download/core/copySvg.js.map +1 -0
- package/lib/download/core/downloadFigure.d.ts +12 -1
- package/lib/download/core/downloadFigure.d.ts.map +1 -1
- package/lib/download/core/downloadFigure.js +21 -0
- package/lib/download/core/downloadFigure.js.map +1 -1
- package/lib/download/core/figureDpi.d.ts +59 -0
- package/lib/download/core/figureDpi.d.ts.map +1 -0
- package/lib/download/core/figureDpi.js +75 -0
- package/lib/download/core/figureDpi.js.map +1 -0
- package/lib/download/core/figurePng.d.ts +4 -1
- package/lib/download/core/figurePng.d.ts.map +1 -1
- package/lib/download/core/figurePng.js +11 -2
- package/lib/download/core/figurePng.js.map +1 -1
- package/lib/download/core/index.d.ts +5 -1
- package/lib/download/core/index.d.ts.map +1 -1
- package/lib/download/core/index.js +4 -1
- package/lib/download/core/index.js.map +1 -1
- package/lib/download/core/pngDpi.d.ts +22 -0
- package/lib/download/core/pngDpi.d.ts.map +1 -0
- package/lib/download/core/pngDpi.js +79 -0
- package/lib/download/core/pngDpi.js.map +1 -0
- package/lib/download/ui/FigureDownloadPanel.d.ts +13 -0
- package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
- package/lib/download/ui/FigureDownloadPanel.js +18 -34
- package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
- package/lib/download/ui/figureDownloadHint.d.ts +5 -0
- package/lib/download/ui/figureDownloadHint.d.ts.map +1 -1
- package/lib/download/ui/figureDownloadHint.js +8 -0
- package/lib/download/ui/figureDownloadHint.js.map +1 -1
- package/lib/download/ui/figureDownloadOptions.d.ts +84 -0
- package/lib/download/ui/figureDownloadOptions.d.ts.map +1 -0
- package/lib/download/ui/figureDownloadOptions.js +2 -0
- package/lib/download/ui/figureDownloadOptions.js.map +1 -0
- package/lib/download/ui/resolutionChoices.d.ts +27 -0
- package/lib/download/ui/resolutionChoices.d.ts.map +1 -0
- package/lib/download/ui/resolutionChoices.js +48 -0
- package/lib/download/ui/resolutionChoices.js.map +1 -0
- package/lib/download/ui/useFigureActions.d.ts +4 -1
- package/lib/download/ui/useFigureActions.d.ts.map +1 -1
- package/lib/download/ui/useFigureActions.js +17 -8
- package/lib/download/ui/useFigureActions.js.map +1 -1
- package/lib/download/ui/useFigureDownload.d.ts +2 -55
- package/lib/download/ui/useFigureDownload.d.ts.map +1 -1
- package/lib/download/ui/useFigureDownload.js +45 -13
- package/lib/download/ui/useFigureDownload.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +14 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/types.d.ts +1 -1
- package/lib/ecosystem/core/types.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +3 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts +1 -1
- package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
- package/lib/ir/core/bandAssignments.d.ts.map +1 -1
- package/lib/ir/core/bandAssignments.js +27 -0
- package/lib/ir/core/bandAssignments.js.map +1 -1
- package/lib/ir/ui/BandRow.d.ts +4 -4
- package/lib/ir/ui/BandRow.d.ts.map +1 -1
- package/lib/ir/ui/BandRow.js +41 -14
- package/lib/ir/ui/BandRow.js.map +1 -1
- package/lib/locales/de.json +32 -1
- package/lib/locales/en.json +32 -1
- package/lib/locales/es.json +32 -1
- package/lib/locales/fr.json +32 -1
- package/lib/locales/it.json +32 -1
- package/lib/molecule3d/ui/Molecule3DStage.d.ts +4 -33
- package/lib/molecule3d/ui/Molecule3DStage.d.ts.map +1 -1
- package/lib/molecule3d/ui/Molecule3DStage.js +3 -37
- package/lib/molecule3d/ui/Molecule3DStage.js.map +1 -1
- package/lib/osiris/core/barGeometry.d.ts +42 -0
- package/lib/osiris/core/barGeometry.d.ts.map +1 -0
- package/lib/osiris/core/barGeometry.js +46 -0
- package/lib/osiris/core/barGeometry.js.map +1 -0
- package/lib/osiris/core/computeProperties.d.ts +42 -0
- package/lib/osiris/core/computeProperties.d.ts.map +1 -0
- package/lib/osiris/core/computeProperties.js +115 -0
- package/lib/osiris/core/computeProperties.js.map +1 -0
- package/lib/osiris/core/constants.d.ts +70 -0
- package/lib/osiris/core/constants.d.ts.map +1 -0
- package/lib/osiris/core/constants.js +99 -0
- package/lib/osiris/core/constants.js.map +1 -0
- package/lib/osiris/core/fitness.d.ts +32 -0
- package/lib/osiris/core/fitness.d.ts.map +1 -0
- package/lib/osiris/core/fitness.js +48 -0
- package/lib/osiris/core/fitness.js.map +1 -0
- package/lib/osiris/core/index.d.ts +29 -0
- package/lib/osiris/core/index.d.ts.map +1 -0
- package/lib/osiris/core/index.js +19 -0
- package/lib/osiris/core/index.js.map +1 -0
- package/lib/osiris/core/poolSize.d.ts +17 -0
- package/lib/osiris/core/poolSize.d.ts.map +1 -0
- package/lib/osiris/core/poolSize.js +22 -0
- package/lib/osiris/core/poolSize.js.map +1 -0
- package/lib/osiris/core/poolTypes.d.ts +140 -0
- package/lib/osiris/core/poolTypes.d.ts.map +1 -0
- package/lib/osiris/core/poolTypes.js +12 -0
- package/lib/osiris/core/poolTypes.js.map +1 -0
- package/lib/osiris/core/predictIdCode.d.ts +39 -0
- package/lib/osiris/core/predictIdCode.d.ts.map +1 -0
- package/lib/osiris/core/predictIdCode.js +54 -0
- package/lib/osiris/core/predictIdCode.js.map +1 -0
- package/lib/osiris/core/predictProtocol.d.ts +67 -0
- package/lib/osiris/core/predictProtocol.d.ts.map +1 -0
- package/lib/osiris/core/predictProtocol.js +29 -0
- package/lib/osiris/core/predictProtocol.js.map +1 -0
- package/lib/osiris/core/predictionPool.d.ts +23 -0
- package/lib/osiris/core/predictionPool.d.ts.map +1 -0
- package/lib/osiris/core/predictionPool.js +186 -0
- package/lib/osiris/core/predictionPool.js.map +1 -0
- package/lib/osiris/core/predictionQueue.d.ts +46 -0
- package/lib/osiris/core/predictionQueue.d.ts.map +1 -0
- package/lib/osiris/core/predictionQueue.js +57 -0
- package/lib/osiris/core/predictionQueue.js.map +1 -0
- package/lib/osiris/core/predictors.d.ts +62 -0
- package/lib/osiris/core/predictors.d.ts.map +1 -0
- package/lib/osiris/core/predictors.js +100 -0
- package/lib/osiris/core/predictors.js.map +1 -0
- package/lib/osiris/core/propertyKeys.d.ts +32 -0
- package/lib/osiris/core/propertyKeys.d.ts.map +1 -0
- package/lib/osiris/core/propertyKeys.js +44 -0
- package/lib/osiris/core/propertyKeys.js.map +1 -0
- package/lib/osiris/core/riskAppearance.d.ts +60 -0
- package/lib/osiris/core/riskAppearance.d.ts.map +1 -0
- package/lib/osiris/core/riskAppearance.js +59 -0
- package/lib/osiris/core/riskAppearance.js.map +1 -0
- package/lib/osiris/core/riskDetail.d.ts +22 -0
- package/lib/osiris/core/riskDetail.d.ts.map +1 -0
- package/lib/osiris/core/riskDetail.js +86 -0
- package/lib/osiris/core/riskDetail.js.map +1 -0
- package/lib/osiris/core/riskDetailRequest.d.ts +31 -0
- package/lib/osiris/core/riskDetailRequest.d.ts.map +1 -0
- package/lib/osiris/core/riskDetailRequest.js +49 -0
- package/lib/osiris/core/riskDetailRequest.js.map +1 -0
- package/lib/osiris/core/riskFindings.d.ts +26 -0
- package/lib/osiris/core/riskFindings.d.ts.map +1 -0
- package/lib/osiris/core/riskFindings.js +79 -0
- package/lib/osiris/core/riskFindings.js.map +1 -0
- package/lib/osiris/core/runPredictionJob.d.ts +20 -0
- package/lib/osiris/core/runPredictionJob.d.ts.map +1 -0
- package/lib/osiris/core/runPredictionJob.js +43 -0
- package/lib/osiris/core/runPredictionJob.js.map +1 -0
- package/lib/osiris/core/scales.d.ts +70 -0
- package/lib/osiris/core/scales.d.ts.map +1 -0
- package/lib/osiris/core/scales.js +185 -0
- package/lib/osiris/core/scales.js.map +1 -0
- package/lib/osiris/core/types.d.ts +91 -0
- package/lib/osiris/core/types.d.ts.map +1 -0
- package/lib/osiris/core/types.js +19 -0
- package/lib/osiris/core/types.js.map +1 -0
- package/lib/osiris/core/valuation.d.ts +28 -0
- package/lib/osiris/core/valuation.d.ts.map +1 -0
- package/lib/osiris/core/valuation.js +39 -0
- package/lib/osiris/core/valuation.js.map +1 -0
- package/lib/osiris/ui/PropertyBar.d.ts +27 -0
- package/lib/osiris/ui/PropertyBar.d.ts.map +1 -0
- package/lib/osiris/ui/PropertyBar.js +27 -0
- package/lib/osiris/ui/PropertyBar.js.map +1 -0
- package/lib/osiris/ui/PropertyPanel.d.ts +91 -0
- package/lib/osiris/ui/PropertyPanel.d.ts.map +1 -0
- package/lib/osiris/ui/PropertyPanel.js +77 -0
- package/lib/osiris/ui/PropertyPanel.js.map +1 -0
- package/lib/osiris/ui/PropertyRows.d.ts +40 -0
- package/lib/osiris/ui/PropertyRows.d.ts.map +1 -0
- package/lib/osiris/ui/PropertyRows.js +23 -0
- package/lib/osiris/ui/PropertyRows.js.map +1 -0
- package/lib/osiris/ui/RiskFindings.d.ts +43 -0
- package/lib/osiris/ui/RiskFindings.d.ts.map +1 -0
- package/lib/osiris/ui/RiskFindings.js +51 -0
- package/lib/osiris/ui/RiskFindings.js.map +1 -0
- package/lib/osiris/ui/RiskPanel.d.ts +46 -0
- package/lib/osiris/ui/RiskPanel.d.ts.map +1 -0
- package/lib/osiris/ui/RiskPanel.js +32 -0
- package/lib/osiris/ui/RiskPanel.js.map +1 -0
- package/lib/osiris/ui/RiskSquare.d.ts +51 -0
- package/lib/osiris/ui/RiskSquare.d.ts.map +1 -0
- package/lib/osiris/ui/RiskSquare.js +23 -0
- package/lib/osiris/ui/RiskSquare.js.map +1 -0
- package/lib/osiris/ui/index.d.ts +15 -0
- package/lib/osiris/ui/index.d.ts.map +1 -0
- package/lib/osiris/ui/index.js +8 -0
- package/lib/osiris/ui/index.js.map +1 -0
- package/lib/osiris/ui/usePredictions.d.ts +81 -0
- package/lib/osiris/ui/usePredictions.d.ts.map +1 -0
- package/lib/osiris/ui/usePredictions.js +102 -0
- package/lib/osiris/ui/usePredictions.js.map +1 -0
- package/lib/osiris-ui.d.ts +11 -0
- package/lib/osiris-ui.d.ts.map +1 -0
- package/lib/osiris-ui.js +11 -0
- package/lib/osiris-ui.js.map +1 -0
- package/lib/osiris.d.ts +17 -0
- package/lib/osiris.d.ts.map +1 -0
- package/lib/osiris.js +17 -0
- package/lib/osiris.js.map +1 -0
- package/lib/panel/ui/FilterInput.d.ts.map +1 -1
- package/lib/panel/ui/FilterInput.js +3 -1
- package/lib/panel/ui/FilterInput.js.map +1 -1
- package/lib/panel/ui/SidePanelItem.d.ts +39 -0
- package/lib/panel/ui/SidePanelItem.d.ts.map +1 -0
- package/lib/panel/ui/SidePanelItem.js +63 -0
- package/lib/panel/ui/SidePanelItem.js.map +1 -0
- package/lib/panel/ui/SidePanelStack.d.ts +11 -10
- package/lib/panel/ui/SidePanelStack.d.ts.map +1 -1
- package/lib/panel/ui/SidePanelStack.js +22 -15
- package/lib/panel/ui/SidePanelStack.js.map +1 -1
- package/lib/panel/ui/SpectrumColorSwatch.d.ts.map +1 -1
- package/lib/panel/ui/SpectrumColorSwatch.js +4 -2
- package/lib/panel/ui/SpectrumColorSwatch.js.map +1 -1
- package/lib/panel/ui/usePanelFolds.d.ts +35 -0
- package/lib/panel/ui/usePanelFolds.d.ts.map +1 -0
- package/lib/panel/ui/usePanelFolds.js +73 -0
- package/lib/panel/ui/usePanelFolds.js.map +1 -0
- package/lib/shared/ui/FullscreenStage.d.ts +43 -0
- package/lib/shared/ui/FullscreenStage.d.ts.map +1 -0
- package/lib/shared/ui/FullscreenStage.js +38 -0
- package/lib/shared/ui/FullscreenStage.js.map +1 -0
- package/lib/shared/ui/canFillScreen.d.ts +7 -0
- package/lib/shared/ui/canFillScreen.d.ts.map +1 -0
- package/lib/shared/ui/canFillScreen.js +9 -0
- package/lib/shared/ui/canFillScreen.js.map +1 -0
- package/lib/shared/ui/index.d.ts +3 -0
- package/lib/shared/ui/index.d.ts.map +1 -1
- package/lib/shared/ui/index.js +2 -0
- package/lib/shared/ui/index.js.map +1 -1
- package/lib/split/ui/splitPanes.d.ts +3 -3
- package/lib/split/ui/splitPanes.js +1 -1
- package/lib/structure/core/structureNotations.d.ts +8 -1
- package/lib/structure/core/structureNotations.d.ts.map +1 -1
- package/lib/structure/core/structureNotations.js +14 -8
- package/lib/structure/core/structureNotations.js.map +1 -1
- package/lib/structure/ui/CardStructure.d.ts +43 -0
- package/lib/structure/ui/CardStructure.d.ts.map +1 -0
- package/lib/structure/ui/CardStructure.js +28 -0
- package/lib/structure/ui/CardStructure.js.map +1 -0
- package/lib/structure/ui/StructureExportPanel.d.ts +8 -0
- package/lib/structure/ui/StructureExportPanel.d.ts.map +1 -1
- package/lib/structure/ui/StructureExportPanel.js +9 -4
- package/lib/structure/ui/StructureExportPanel.js.map +1 -1
- package/lib/structure/ui/StructurePicturePane.d.ts +1 -1
- package/lib/structure/ui/StructurePicturePane.d.ts.map +1 -1
- package/lib/structure/ui/StructurePicturePane.js +5 -2
- package/lib/structure/ui/StructurePicturePane.js.map +1 -1
- package/lib/structure/ui/index.d.ts +3 -0
- package/lib/structure/ui/index.d.ts.map +1 -1
- package/lib/structure/ui/index.js +1 -0
- package/lib/structure/ui/index.js.map +1 -1
- package/package.json +4 -1
- package/src/card/core/cardAddress.ts +39 -3
- package/src/card/core/cardPageUrl.ts +10 -3
- package/src/card/core/index.ts +7 -1
- package/src/credits/core/credits.ts +40 -0
- package/src/download/core/copySvg.ts +40 -0
- package/src/download/core/downloadFigure.ts +26 -1
- package/src/download/core/figureDpi.ts +94 -0
- package/src/download/core/figurePng.ts +11 -2
- package/src/download/core/index.ts +14 -1
- package/src/download/core/pngDpi.ts +84 -0
- package/src/download/ui/FigureDownloadPanel.tsx +41 -51
- package/src/download/ui/figureDownloadHint.ts +13 -0
- package/src/download/ui/figureDownloadOptions.ts +85 -0
- package/src/download/ui/resolutionChoices.ts +67 -0
- package/src/download/ui/useFigureActions.ts +24 -9
- package/src/download/ui/useFigureDownload.tsx +57 -71
- package/src/ecosystem/core/sites.ts +14 -0
- package/src/ecosystem/core/types.ts +2 -1
- package/src/ecosystem/ui/glyphs.tsx +20 -0
- package/src/ir/core/bandAssignments.ts +27 -0
- package/src/ir/ui/BandRow.tsx +84 -26
- package/src/locales/de.json +32 -1
- package/src/locales/en.json +32 -1
- package/src/locales/es.json +32 -1
- package/src/locales/fr.json +32 -1
- package/src/locales/it.json +32 -1
- package/src/molecule3d/ui/Molecule3DStage.tsx +4 -88
- package/src/osiris/core/barGeometry.ts +74 -0
- package/src/osiris/core/computeProperties.ts +175 -0
- package/src/osiris/core/constants.ts +110 -0
- package/src/osiris/core/fitness.ts +59 -0
- package/src/osiris/core/index.ts +78 -0
- package/src/osiris/core/poolSize.ts +24 -0
- package/src/osiris/core/poolTypes.ts +140 -0
- package/src/osiris/core/predictIdCode.ts +69 -0
- package/src/osiris/core/predictProtocol.ts +78 -0
- package/src/osiris/core/predictionPool.ts +237 -0
- package/src/osiris/core/predictionQueue.ts +89 -0
- package/src/osiris/core/predictors.ts +142 -0
- package/src/osiris/core/propertyKeys.ts +54 -0
- package/src/osiris/core/riskAppearance.ts +86 -0
- package/src/osiris/core/riskDetail.ts +120 -0
- package/src/osiris/core/riskDetailRequest.ts +64 -0
- package/src/osiris/core/riskFindings.ts +92 -0
- package/src/osiris/core/runPredictionJob.ts +49 -0
- package/src/osiris/core/scales.ts +236 -0
- package/src/osiris/core/types.ts +103 -0
- package/src/osiris/core/valuation.ts +44 -0
- package/src/osiris/ui/PropertyBar.tsx +77 -0
- package/src/osiris/ui/PropertyPanel.tsx +227 -0
- package/src/osiris/ui/PropertyRows.tsx +92 -0
- package/src/osiris/ui/RiskFindings.tsx +137 -0
- package/src/osiris/ui/RiskPanel.tsx +81 -0
- package/src/osiris/ui/RiskSquare.tsx +108 -0
- package/src/osiris/ui/index.ts +17 -0
- package/src/osiris/ui/usePredictions.ts +194 -0
- package/src/osiris-ui.ts +11 -0
- package/src/osiris.ts +17 -0
- package/src/panel/ui/FilterInput.tsx +4 -1
- package/src/panel/ui/SidePanelItem.tsx +162 -0
- package/src/panel/ui/SidePanelStack.tsx +29 -36
- package/src/panel/ui/SpectrumColorSwatch.tsx +5 -3
- package/src/panel/ui/usePanelFolds.ts +103 -0
- package/src/shared/ui/FullscreenStage.tsx +96 -0
- package/src/shared/ui/canFillScreen.ts +8 -0
- package/src/shared/ui/index.ts +6 -0
- package/src/split/ui/splitPanes.tsx +4 -4
- package/src/structure/core/structureNotations.ts +25 -8
- package/src/structure/ui/CardStructure.tsx +76 -0
- package/src/structure/ui/StructureExportPanel.tsx +23 -4
- package/src/structure/ui/StructurePicturePane.tsx +14 -1
- package/src/structure/ui/index.ts +6 -0
- package/styles/chrome.css +3 -3
- package/styles/osiris.css +339 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How one risk reads on screen: the word for it, and the tone its square takes.
|
|
3
|
+
*
|
|
4
|
+
* Three levels, and they are exactly the three `DrugScoreCalculator` multiplies
|
|
5
|
+
* by — no risk ×1, medium ×0.8, high ×0.6 — so the colours say what the score
|
|
6
|
+
* already counted. A fourth tone exists for the state the library calls
|
|
7
|
+
* `RISK_UNKNOWN`, which means its tables did not load: it is grey, never green,
|
|
8
|
+
* because "nothing was assessed" and "nothing was found" are different answers
|
|
9
|
+
* and showing them alike is the one mistake that makes the panel dishonest.
|
|
10
|
+
*
|
|
11
|
+
* A fifth, `pending`, is ours: the prediction costs a fifth of a second and the
|
|
12
|
+
* square is on screen before it comes back.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { RISK_LEVEL_LABELS } from './constants.ts';
|
|
16
|
+
import type { RiskLevel } from './types.ts';
|
|
17
|
+
|
|
18
|
+
/** What a square is painted with, before the stylesheet turns it into a colour. */
|
|
19
|
+
export type RiskTone = 'pending' | 'unknown' | 'none' | 'low' | 'high';
|
|
20
|
+
|
|
21
|
+
/** How one risk is shown. */
|
|
22
|
+
export interface RiskAppearance {
|
|
23
|
+
/** The stylesheet's `data-level`, which picks the square's colour. */
|
|
24
|
+
tone: RiskTone;
|
|
25
|
+
/** What the square says next to the risk's name, e.g. `High risk`. */
|
|
26
|
+
label: string;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the level is worth opening: an explanation exists for an assessed
|
|
29
|
+
* risk and for nothing else.
|
|
30
|
+
*/
|
|
31
|
+
explainable: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** What a square says while its molecule is still being predicted. */
|
|
35
|
+
export const PENDING_RISK_LABEL = 'Predicting…';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What a square says when there is no molecule to predict.
|
|
39
|
+
*
|
|
40
|
+
* The comparison page draws the panel before a row is picked, and a square that
|
|
41
|
+
* said `Predicting…` there would claim work nobody asked for — under a heading
|
|
42
|
+
* inviting the reader to pick a row, which is a plain contradiction.
|
|
43
|
+
*/
|
|
44
|
+
export const IDLE_RISK_LABEL = 'Not predicted yet';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What the predictor's own page says about its alerts, word for word.
|
|
48
|
+
*
|
|
49
|
+
* Quoted rather than paraphrased: it is the sentence that stops a red square
|
|
50
|
+
* being read as a verdict, and rewriting it would put our words over his.
|
|
51
|
+
*/
|
|
52
|
+
export const RISK_CAVEAT =
|
|
53
|
+
'Toxicity risk alerts are by no means meant to be a fully reliable toxicity prediction. Nor should be concluded from the absence of risk alerts that a particular substance is completely free of any toxic effect.';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* What is said in place of a highlight when the compound's own idCode is on one
|
|
57
|
+
* of the RTECS-derived lists. There is no fragment to point at, and painting the
|
|
58
|
+
* whole molecule would call the compound its own toxicophore.
|
|
59
|
+
*/
|
|
60
|
+
export const KNOWN_MOLECULE_NOTE =
|
|
61
|
+
'This exact compound is on the list, so there is no fragment to point at.';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The tone and the word for one risk.
|
|
65
|
+
* @param level - How the risk came back, or `undefined` when there is none yet.
|
|
66
|
+
* @param pending - Whether a prediction is actually running for this molecule.
|
|
67
|
+
* @default false
|
|
68
|
+
* @returns See {@link RiskAppearance}.
|
|
69
|
+
*/
|
|
70
|
+
export function riskAppearance(
|
|
71
|
+
level: RiskLevel | undefined,
|
|
72
|
+
pending = false,
|
|
73
|
+
): RiskAppearance {
|
|
74
|
+
if (level === undefined) {
|
|
75
|
+
return {
|
|
76
|
+
tone: 'pending',
|
|
77
|
+
label: pending ? PENDING_RISK_LABEL : IDLE_RISK_LABEL,
|
|
78
|
+
explainable: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
tone: level,
|
|
83
|
+
label: RISK_LEVEL_LABELS[level],
|
|
84
|
+
explainable: level !== 'unknown',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why one risk came back as it did, with the offending substructure located on
|
|
3
|
+
* the molecule.
|
|
4
|
+
*
|
|
5
|
+
* The match runs in the predictor's own mode —
|
|
6
|
+
* `new SSSearcher({ matchAtomCharge: true, matchAromDBondToDelocalized: false })`,
|
|
7
|
+
* which is OpenChemLib's mode 1. The JavaScript default is mode 8, and it can
|
|
8
|
+
* disagree with what `assessRisk` actually found, which would leave a risk
|
|
9
|
+
* reported with nothing highlighted and no way to tell why.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type * as OCL from 'openchemlib';
|
|
13
|
+
|
|
14
|
+
import { RISK_TYPE_CODES } from './constants.ts';
|
|
15
|
+
import type { OsirisPredictors } from './predictors.ts';
|
|
16
|
+
import { parseRiskFindings } from './riskFindings.ts';
|
|
17
|
+
import type {
|
|
18
|
+
DetailEntry,
|
|
19
|
+
RiskDetail,
|
|
20
|
+
RiskFinding,
|
|
21
|
+
RiskType,
|
|
22
|
+
} from './types.ts';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* What the predictor found, and where on the molecule it found it.
|
|
26
|
+
* @param molecule - The molecule the risk was assessed on. It is copied before being searched.
|
|
27
|
+
* @param risk - Which of the four risks to explain.
|
|
28
|
+
* @param predictors - OpenChemLib with its resources registered and checked.
|
|
29
|
+
* @returns One finding per sentence the predictor printed, and one per matched fragment.
|
|
30
|
+
*/
|
|
31
|
+
export function riskDetail(
|
|
32
|
+
molecule: OCL.Molecule,
|
|
33
|
+
risk: RiskType,
|
|
34
|
+
predictors: OsirisPredictors,
|
|
35
|
+
): RiskDetail {
|
|
36
|
+
const working = molecule.getCompactCopy();
|
|
37
|
+
const entries = predictors.toxicity.getDetail(
|
|
38
|
+
working,
|
|
39
|
+
RISK_TYPE_CODES[risk],
|
|
40
|
+
) as readonly DetailEntry[];
|
|
41
|
+
|
|
42
|
+
const findings: RiskFinding[] = [];
|
|
43
|
+
for (const finding of parseRiskFindings(entries)) {
|
|
44
|
+
findings.push(
|
|
45
|
+
finding.idCode === null
|
|
46
|
+
? finding
|
|
47
|
+
: { ...finding, ...locate(working, finding.idCode, predictors) },
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return { risk, findings };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface Match {
|
|
54
|
+
atoms: number[];
|
|
55
|
+
bonds: number[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function locate(
|
|
59
|
+
molecule: OCL.Molecule,
|
|
60
|
+
fragmentIdCode: string,
|
|
61
|
+
predictors: OsirisPredictors,
|
|
62
|
+
): Match {
|
|
63
|
+
let fragment: OCL.Molecule;
|
|
64
|
+
try {
|
|
65
|
+
fragment = predictors.Molecule.fromIDCode(fragmentIdCode);
|
|
66
|
+
} catch {
|
|
67
|
+
return { atoms: [], bonds: [] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const searcher = new predictors.SSSearcher({
|
|
71
|
+
matchAtomCharge: true,
|
|
72
|
+
matchAromDBondToDelocalized: false,
|
|
73
|
+
});
|
|
74
|
+
searcher.setMol(fragment, molecule);
|
|
75
|
+
if (!searcher.isFragmentInMolecule()) return { atoms: [], bonds: [] };
|
|
76
|
+
searcher.findFragmentInMolecule({ countMode: 'overlapping' });
|
|
77
|
+
|
|
78
|
+
const atoms = new Set<number>();
|
|
79
|
+
const bonds = new Set<number>();
|
|
80
|
+
for (const match of searcher.getMatchList()) {
|
|
81
|
+
collect(molecule, fragment, match, atoms, bonds);
|
|
82
|
+
}
|
|
83
|
+
return { atoms: sorted(atoms), bonds: sorted(bonds) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Add one match's atoms and bonds to the running sets.
|
|
88
|
+
*
|
|
89
|
+
* A fragment atom the query marked as an exclude group maps to -1, and a bond
|
|
90
|
+
* between two matched atoms may not exist in the molecule when the fragment
|
|
91
|
+
* matched through a bridge, so both are checked rather than assumed.
|
|
92
|
+
* @param molecule - The molecule searched.
|
|
93
|
+
* @param fragment - The fragment that matched.
|
|
94
|
+
* @param map - The molecule's atom for each of the fragment's, -1 for none.
|
|
95
|
+
* @param atoms - The matched atoms so far, added to.
|
|
96
|
+
* @param bonds - The matched bonds so far, added to.
|
|
97
|
+
*/
|
|
98
|
+
function collect(
|
|
99
|
+
molecule: OCL.Molecule,
|
|
100
|
+
fragment: OCL.Molecule,
|
|
101
|
+
map: readonly number[],
|
|
102
|
+
atoms: Set<number>,
|
|
103
|
+
bonds: Set<number>,
|
|
104
|
+
): void {
|
|
105
|
+
for (const atom of map) {
|
|
106
|
+
if (atom >= 0) atoms.add(atom);
|
|
107
|
+
}
|
|
108
|
+
for (let bond = 0; bond < fragment.getAllBonds(); bond++) {
|
|
109
|
+
const first = map[fragment.getBondAtom(0, bond)];
|
|
110
|
+
const second = map[fragment.getBondAtom(1, bond)];
|
|
111
|
+
if (first === undefined || second === undefined) continue;
|
|
112
|
+
if (first < 0 || second < 0) continue;
|
|
113
|
+
const found = molecule.getBond(first, second);
|
|
114
|
+
if (found !== -1) bonds.add(found);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sorted(values: Set<number>): number[] {
|
|
119
|
+
return Array.from(values).toSorted((a, b) => a - b);
|
|
120
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asking a worker why one risk came back as it did, and asking again when the
|
|
3
|
+
* set's cancel took the question with it.
|
|
4
|
+
*
|
|
5
|
+
* An explanation travels on a properties channel, and `cancel` drops every
|
|
6
|
+
* request on every channel — the reader's click included, which then reads as
|
|
7
|
+
* the channel's own words in place of the findings. It is not part of the set
|
|
8
|
+
* being abandoned, so it is simply asked for again.
|
|
9
|
+
*
|
|
10
|
+
* A channel of its own would be the other way, and is worse here: a channel
|
|
11
|
+
* makes its worker on the first request, so the first click would wait for
|
|
12
|
+
* OpenChemLib and the 398 kB of resource tables to load in a cold worker,
|
|
13
|
+
* where the shared one is already warm. The retry costs one more message on a
|
|
14
|
+
* worker that is running anyway.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { CancelledRequestError } from '../../worker/core/errors.ts';
|
|
18
|
+
import type { WorkerChannel } from '../../worker/core/workerChannelTypes.ts';
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
PredictRequest,
|
|
22
|
+
PredictResponse,
|
|
23
|
+
RiskDetailRequest,
|
|
24
|
+
} from './predictProtocol.ts';
|
|
25
|
+
|
|
26
|
+
type Channel = WorkerChannel<PredictRequest, PredictResponse>;
|
|
27
|
+
|
|
28
|
+
/** What the reader is told when the second ask was dropped as well. */
|
|
29
|
+
export const RISK_DETAIL_DROPPED =
|
|
30
|
+
'That explanation was dropped when the run stopped. Close the risk square and open it again.';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ask a worker to explain one risk, once more if a cancel dropped the ask.
|
|
34
|
+
* @param channelOf - Picks the channel to ask on, called again for the retry so it lands on whichever worker is least busy by then.
|
|
35
|
+
* @param request - The explanation to ask for.
|
|
36
|
+
* @param mayRetry - Whether asking again can still be answered: false once the pool is terminated, or once its resources are known not to have loaded.
|
|
37
|
+
* @returns Whatever the worker answered, including its `unavailable`.
|
|
38
|
+
* @throws {Error} When the ask was dropped and cannot be made again.
|
|
39
|
+
*/
|
|
40
|
+
export async function requestRiskDetail(
|
|
41
|
+
channelOf: () => Channel,
|
|
42
|
+
request: RiskDetailRequest,
|
|
43
|
+
mayRetry: () => boolean,
|
|
44
|
+
): Promise<PredictResponse> {
|
|
45
|
+
try {
|
|
46
|
+
return await channelOf().request(request);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (!(error instanceof CancelledRequestError)) throw error;
|
|
49
|
+
if (!mayRetry()) throw new Error(RISK_DETAIL_DROPPED, { cause: error });
|
|
50
|
+
}
|
|
51
|
+
return askAgain(channelOf, request);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function askAgain(
|
|
55
|
+
channelOf: () => Channel,
|
|
56
|
+
request: RiskDetailRequest,
|
|
57
|
+
): Promise<PredictResponse> {
|
|
58
|
+
try {
|
|
59
|
+
return await channelOf().request(request);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof CancelledRequestError)) throw error;
|
|
62
|
+
throw new Error(RISK_DETAIL_DROPPED, { cause: error });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading what the toxicity predictor printed.
|
|
3
|
+
*
|
|
4
|
+
* `getDetail` answers a flat list of typed strings — 1 is an idCode, 2 is text,
|
|
5
|
+
* 3 is a number — in which a text line heads the idCodes that follow it. Which
|
|
6
|
+
* text line it is matters more than anything else the tool shows: "This
|
|
7
|
+
* molecule is known to be mutagenic" means the compound's own idCode is in a
|
|
8
|
+
* list derived from RTECS, and the idCode that follows is **the molecule
|
|
9
|
+
* itself**; "High-risk fragments indicating Mutagenicity" means a substructure
|
|
10
|
+
* matched, and the idCodes that follow are the substructures.
|
|
11
|
+
*
|
|
12
|
+
* Telling them apart is what keeps the red square honest: aspirin, caffeine and
|
|
13
|
+
* paracetamol all come back high through the first branch, and highlighting the
|
|
14
|
+
* whole molecule there would call the compound its own toxicophore.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { DetailEntry, RiskFinding } from './types.ts';
|
|
18
|
+
|
|
19
|
+
/** An idCode, to be drawn as a structure. */
|
|
20
|
+
const ID_CODE = 1;
|
|
21
|
+
/** Plain text. */
|
|
22
|
+
const TEXT = 2;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Turn what the predictor printed into the lines a page shows.
|
|
26
|
+
*
|
|
27
|
+
* The matched atoms are left empty here: finding them needs the molecule and
|
|
28
|
+
* the predictor's own match mode, which `riskDetail` supplies.
|
|
29
|
+
* @param entries - What `ToxicityPredictor.getDetail` returned.
|
|
30
|
+
* @returns One finding per sentence, and one per matched fragment.
|
|
31
|
+
*/
|
|
32
|
+
export function parseRiskFindings(
|
|
33
|
+
entries: readonly DetailEntry[],
|
|
34
|
+
): RiskFinding[] {
|
|
35
|
+
const findings: RiskFinding[] = [];
|
|
36
|
+
let heading: Heading = { kind: 'note', description: '', severity: null };
|
|
37
|
+
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (entry.type === TEXT) {
|
|
40
|
+
heading = readHeading(entry.value);
|
|
41
|
+
// A fragment heading says nothing on its own; it is repeated on each of
|
|
42
|
+
// the fragments that follow it, so the page can draw one row per match.
|
|
43
|
+
if (heading.kind !== 'fragment') findings.push(line(heading));
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (entry.type !== ID_CODE) continue;
|
|
47
|
+
// The idCode under a known-molecule heading is the molecule itself.
|
|
48
|
+
if (heading.kind !== 'fragment') continue;
|
|
49
|
+
findings.push({ ...line(heading), idCode: entry.value });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return findings;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Heading {
|
|
56
|
+
kind: RiskFinding['kind'];
|
|
57
|
+
description: string;
|
|
58
|
+
severity: RiskFinding['severity'];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function line(heading: Heading): RiskFinding {
|
|
62
|
+
return {
|
|
63
|
+
kind: heading.kind,
|
|
64
|
+
description: heading.description,
|
|
65
|
+
severity: heading.severity,
|
|
66
|
+
idCode: null,
|
|
67
|
+
atoms: [],
|
|
68
|
+
bonds: [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** `This molecule is known to be mutagenic:` and its three siblings. */
|
|
73
|
+
const KNOWN = /^This molecule is known to be /i;
|
|
74
|
+
const HIGH_RISK = /^High-risk fragments/i;
|
|
75
|
+
const MEDIUM_RISK = /^Medium-risk fragments/i;
|
|
76
|
+
const NOTHING_FOUND = /^No indication for /i;
|
|
77
|
+
|
|
78
|
+
function readHeading(value: string): Heading {
|
|
79
|
+
if (KNOWN.test(value)) {
|
|
80
|
+
return { kind: 'known', description: value, severity: null };
|
|
81
|
+
}
|
|
82
|
+
if (HIGH_RISK.test(value)) {
|
|
83
|
+
return { kind: 'fragment', description: value, severity: 'high' };
|
|
84
|
+
}
|
|
85
|
+
if (MEDIUM_RISK.test(value)) {
|
|
86
|
+
return { kind: 'fragment', description: value, severity: 'medium' };
|
|
87
|
+
}
|
|
88
|
+
if (NOTHING_FOUND.test(value)) {
|
|
89
|
+
return { kind: 'none', description: value, severity: null };
|
|
90
|
+
}
|
|
91
|
+
return { kind: 'note', description: value, severity: null };
|
|
92
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One prediction, wherever it runs.
|
|
3
|
+
*
|
|
4
|
+
* The worker serves this, and a page with no `Worker` at all — a unit test,
|
|
5
|
+
* Node — runs the same function on its own thread. One code path, so what the
|
|
6
|
+
* tests prove is what the page does.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { errorMessage } from '../../error/core/toError.ts';
|
|
10
|
+
|
|
11
|
+
import { predictIdCode, riskDetailOfIdCode } from './predictIdCode.ts';
|
|
12
|
+
import type { PredictRequest, PredictResponse } from './predictProtocol.ts';
|
|
13
|
+
import { loadPredictors } from './predictors.ts';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Answer one job.
|
|
17
|
+
*
|
|
18
|
+
* A structure that cannot be read, or that holds no atoms, throws: that is one
|
|
19
|
+
* bad row, and the run carries on without it. Resources that did not load
|
|
20
|
+
* return `unavailable` instead, because that is every row and the run must stop.
|
|
21
|
+
* @param request - The job.
|
|
22
|
+
* @returns The answer.
|
|
23
|
+
* @throws {Error} When the molecule itself could not be read or predicted.
|
|
24
|
+
*/
|
|
25
|
+
export async function runPredictionJob(
|
|
26
|
+
request: PredictRequest,
|
|
27
|
+
): Promise<PredictResponse> {
|
|
28
|
+
let predictors;
|
|
29
|
+
try {
|
|
30
|
+
predictors = await loadPredictors(request.resources);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return { kind: 'unavailable', message: errorMessage(error) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (request.kind === 'riskDetail') {
|
|
36
|
+
return {
|
|
37
|
+
kind: 'riskDetail',
|
|
38
|
+
detail: riskDetailOfIdCode(request.idCode, request.risk, predictors),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
kind: 'properties',
|
|
44
|
+
key: request.key,
|
|
45
|
+
properties: predictIdCode(request.idCode, predictors, {
|
|
46
|
+
...(request.label === undefined ? {} : { label: request.label }),
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
}
|