@holoscript/structural-biology-plugin 0.0.1
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/dist/index.d.mts +657 -0
- package/dist/index.d.ts +657 -0
- package/dist/index.js +681 -0
- package/dist/index.mjs +629 -0
- package/package.json +32 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
// src/docking.ts
|
|
2
|
+
function traitToConfig(trait) {
|
|
3
|
+
return {
|
|
4
|
+
backend: trait.backend,
|
|
5
|
+
receptor: trait.receptor,
|
|
6
|
+
ligand: trait.ligand,
|
|
7
|
+
centerX: trait.searchCenter.x,
|
|
8
|
+
centerY: trait.searchCenter.y,
|
|
9
|
+
centerZ: trait.searchCenter.z,
|
|
10
|
+
sizeX: trait.searchSize.x,
|
|
11
|
+
sizeY: trait.searchSize.y,
|
|
12
|
+
sizeZ: trait.searchSize.z,
|
|
13
|
+
numRuns: trait.numRuns,
|
|
14
|
+
scoringFunction: trait.scoringFunction
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function dockingProvenance(result) {
|
|
18
|
+
const { fnv1a: fnv1a2 } = /* @__PURE__ */ (() => {
|
|
19
|
+
let h = 2166136261;
|
|
20
|
+
const fnv = (s) => {
|
|
21
|
+
h = 2166136261;
|
|
22
|
+
for (let i = 0; i < s.length; i++) {
|
|
23
|
+
h ^= s.charCodeAt(i);
|
|
24
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
25
|
+
}
|
|
26
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
27
|
+
};
|
|
28
|
+
return { fnv1a: fnv };
|
|
29
|
+
})();
|
|
30
|
+
const head = `structural-biology@0.1.0|docking|${result.backend}`;
|
|
31
|
+
const receptorHash = fnv1a2(result.receptorId);
|
|
32
|
+
const ligandHash = fnv1a2(result.ligandId);
|
|
33
|
+
const affinityKey = `best:${result.bestAffinity?.toFixed(2) ?? "unknown"}`;
|
|
34
|
+
const poseKey = `poses:${result.poses.length}`;
|
|
35
|
+
return fnv1a2(`${head}|r:${receptorHash}|l:${ligandHash}|${affinityKey}|${poseKey}`);
|
|
36
|
+
}
|
|
37
|
+
function selectDockingBackend(questionType) {
|
|
38
|
+
const q = questionType.toLowerCase();
|
|
39
|
+
if (q.includes("screen") || q.includes("high-throughput") || q.includes("batch") || q.includes("many")) {
|
|
40
|
+
return "autodock-gpu";
|
|
41
|
+
}
|
|
42
|
+
if (q.includes("cnn") || q.includes("ml") || q.includes("gnina") || q.includes("neural") || q.includes("deep learning")) {
|
|
43
|
+
return "gnina";
|
|
44
|
+
}
|
|
45
|
+
return "autodock-vina";
|
|
46
|
+
}
|
|
47
|
+
function compileDocking(traits, opts = {}) {
|
|
48
|
+
const format = opts.format ?? "pdbqt";
|
|
49
|
+
switch (format) {
|
|
50
|
+
case "pdbqt":
|
|
51
|
+
return compileDockingToPdbqt(traits);
|
|
52
|
+
case "sdf":
|
|
53
|
+
return compileDockingToSdf(traits);
|
|
54
|
+
case "csv":
|
|
55
|
+
return compileDockingToCsv(traits);
|
|
56
|
+
case "holo":
|
|
57
|
+
return compileDockingToHolo(traits);
|
|
58
|
+
default:
|
|
59
|
+
throw new Error(`Unsupported docking format: ${format}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function compileDockingToPdbqt(traits) {
|
|
63
|
+
const lines = ["HEADER HOLOSCRIPT DOCKING PLUGIN"];
|
|
64
|
+
for (const t of traits) {
|
|
65
|
+
if (t.trait === "auto_dock") {
|
|
66
|
+
lines.push(`REMARK 400 DOCKING backend=${t.backend}`);
|
|
67
|
+
lines.push(
|
|
68
|
+
`REMARK 400 SEARCH_CENTER ${t.searchCenter.x},${t.searchCenter.y},${t.searchCenter.z}`
|
|
69
|
+
);
|
|
70
|
+
lines.push(`REMARK 400 SEARCH_SIZE ${t.searchSize.x},${t.searchSize.y},${t.searchSize.z}`);
|
|
71
|
+
lines.push(`REMARK 400 SCORING ${t.scoringFunction ?? "vina"}`);
|
|
72
|
+
if (t.numRuns) lines.push(`REMARK 400 RUNS ${t.numRuns}`);
|
|
73
|
+
} else if (t.trait === "binding_affinity") {
|
|
74
|
+
lines.push(`REMARK 500 BINDING_AFFINITY best=${t.bestAffinity} kcal/mol`);
|
|
75
|
+
lines.push(`REMARK 500 POSES ${t.poseCount}`);
|
|
76
|
+
lines.push(
|
|
77
|
+
`REMARK 500 RMSD_RANGE ${t.bestRmsdRange[0].toFixed(3)}-${t.bestRmsdRange[1].toFixed(3)}`
|
|
78
|
+
);
|
|
79
|
+
lines.push(`REMARK 500 BACKEND ${t.backend}`);
|
|
80
|
+
if (t.ki_nm !== void 0) lines.push(`REMARK 500 KI ${t.ki_nm.toFixed(2)} nM`);
|
|
81
|
+
if (t.hitRate !== void 0)
|
|
82
|
+
lines.push(`REMARK 500 HIT_RATE ${(t.hitRate * 100).toFixed(1)}%`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
lines.push("END");
|
|
86
|
+
return lines.join("\n");
|
|
87
|
+
}
|
|
88
|
+
function compileDockingToSdf(traits) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
for (const t of traits) {
|
|
91
|
+
if (t.trait === "binding_affinity") {
|
|
92
|
+
lines.push("ligand");
|
|
93
|
+
lines.push(" HoloScript");
|
|
94
|
+
lines.push("");
|
|
95
|
+
lines.push(" 0 0 0 0 0 0 0 0 0 0 0 V2000");
|
|
96
|
+
lines.push("M END");
|
|
97
|
+
lines.push(`> <docking.backend> (${t.backend})`);
|
|
98
|
+
lines.push(`${t.backend}`);
|
|
99
|
+
lines.push("");
|
|
100
|
+
lines.push(`> <docking.bestAffinity> (kcal/mol)`);
|
|
101
|
+
lines.push(`${t.bestAffinity}`);
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push(`> <docking.poseCount>`);
|
|
104
|
+
lines.push(`${t.poseCount}`);
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push(`> <docking.rmsdRange>`);
|
|
107
|
+
lines.push(`${t.bestRmsdRange[0].toFixed(3)},${t.bestRmsdRange[1].toFixed(3)}`);
|
|
108
|
+
lines.push("");
|
|
109
|
+
if (t.ki_nm !== void 0) {
|
|
110
|
+
lines.push(`> <docking.ki_nm>`);
|
|
111
|
+
lines.push(`${t.ki_nm.toFixed(2)}`);
|
|
112
|
+
lines.push("");
|
|
113
|
+
}
|
|
114
|
+
lines.push("$$$$");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
function compileDockingToCsv(traits) {
|
|
120
|
+
const rows = [
|
|
121
|
+
"trait,backend,bestAffinity_kcal_mol,poseCount,rmsdLb,rmsdUb,ki_nm,hitRate"
|
|
122
|
+
];
|
|
123
|
+
for (const t of traits) {
|
|
124
|
+
if (t.trait === "binding_affinity") {
|
|
125
|
+
rows.push(
|
|
126
|
+
`binding_affinity,${t.backend},${t.bestAffinity},${t.poseCount},${t.bestRmsdRange[0]},${t.bestRmsdRange[1]},${t.ki_nm ?? ""},${t.hitRate ?? ""}`
|
|
127
|
+
);
|
|
128
|
+
} else if (t.trait === "auto_dock") {
|
|
129
|
+
rows.push(
|
|
130
|
+
`auto_dock,${t.backend},,,${t.searchCenter.x},${t.searchCenter.y},${t.searchCenter.z},`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return rows.join("\n");
|
|
135
|
+
}
|
|
136
|
+
function compileDockingToHolo(traits) {
|
|
137
|
+
const lines = ['composition "DockingScene" {'];
|
|
138
|
+
for (const t of traits) {
|
|
139
|
+
if (t.trait === "auto_dock") {
|
|
140
|
+
lines.push(` object "DockingConfig" @auto_dock {`);
|
|
141
|
+
lines.push(` backend: "${t.backend}"`);
|
|
142
|
+
lines.push(
|
|
143
|
+
` center: { x: ${t.searchCenter.x}, y: ${t.searchCenter.y}, z: ${t.searchCenter.z} }`
|
|
144
|
+
);
|
|
145
|
+
lines.push(` size: { x: ${t.searchSize.x}, y: ${t.searchSize.y}, z: ${t.searchSize.z} }`);
|
|
146
|
+
if (t.numRuns) lines.push(` numRuns: ${t.numRuns}`);
|
|
147
|
+
if (t.scoringFunction) lines.push(` scoringFunction: "${t.scoringFunction}"`);
|
|
148
|
+
lines.push(" }");
|
|
149
|
+
} else if (t.trait === "binding_affinity") {
|
|
150
|
+
lines.push(` object "BindingAffinity" @binding_affinity {`);
|
|
151
|
+
lines.push(` bestAffinity: ${t.bestAffinity}`);
|
|
152
|
+
lines.push(` poseCount: ${t.poseCount}`);
|
|
153
|
+
lines.push(` rmsdRange: [${t.bestRmsdRange[0]}, ${t.bestRmsdRange[1]}]`);
|
|
154
|
+
lines.push(` backend: "${t.backend}"`);
|
|
155
|
+
if (t.ki_nm !== void 0) lines.push(` ki_nm: ${t.ki_nm.toFixed(2)}`);
|
|
156
|
+
if (t.hitRate !== void 0) lines.push(` hitRate: ${(t.hitRate * 100).toFixed(1)}%`);
|
|
157
|
+
lines.push(" }");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
lines.push("}");
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/admet.ts
|
|
165
|
+
var ADMET_PROPERTIES = [
|
|
166
|
+
// Absorption
|
|
167
|
+
"caco2_permeability",
|
|
168
|
+
// Caco-2 cell permeability (logPapp)
|
|
169
|
+
"hia",
|
|
170
|
+
// Human intestinal absorption (%)
|
|
171
|
+
"pgp_substrate",
|
|
172
|
+
// P-glycoprotein substrate (binary)
|
|
173
|
+
"pgp_inhibitor",
|
|
174
|
+
// P-glycoprotein inhibitor (binary)
|
|
175
|
+
"bbb_permeability",
|
|
176
|
+
// Blood-brain barrier penetration (binary)
|
|
177
|
+
"cns_permeability",
|
|
178
|
+
// CNS permeability (binary)
|
|
179
|
+
// Distribution
|
|
180
|
+
"vdss",
|
|
181
|
+
// Volume of distribution at steady state (L/kg)
|
|
182
|
+
"fraction_unbound",
|
|
183
|
+
// Fraction unbound in plasma (Fu)
|
|
184
|
+
"ppb",
|
|
185
|
+
// Plasma protein binding (%)
|
|
186
|
+
// Metabolism
|
|
187
|
+
"cyp2d6_substrate",
|
|
188
|
+
// CYP2D6 substrate (binary)
|
|
189
|
+
"cyp3a4_substrate",
|
|
190
|
+
// CYP3A4 substrate (binary)
|
|
191
|
+
"cyp1a2_inhibitor",
|
|
192
|
+
// CYP1A2 inhibitor (binary)
|
|
193
|
+
"cyp2c19_inhibitor",
|
|
194
|
+
// CYP2C19 inhibitor (binary)
|
|
195
|
+
"cyp2c9_inhibitor",
|
|
196
|
+
// CYP2C9 inhibitor (binary)
|
|
197
|
+
"cyp2d6_inhibitor",
|
|
198
|
+
// CYP2D6 inhibitor (binary)
|
|
199
|
+
"cyp3a4_inhibitor",
|
|
200
|
+
// CYP3A4 inhibitor (binary)
|
|
201
|
+
"cyp2d6_inhibitor_v2",
|
|
202
|
+
// CYP2D6 inhibitor v2 (binary, updated model)
|
|
203
|
+
// Excretion
|
|
204
|
+
"half_life",
|
|
205
|
+
// Elimination half-life (hours)
|
|
206
|
+
"clearance",
|
|
207
|
+
// Hepatic clearance (mL/min/kg)
|
|
208
|
+
// Toxicity
|
|
209
|
+
"hht",
|
|
210
|
+
// Human hepatotoxicity (binary)
|
|
211
|
+
"ames",
|
|
212
|
+
// Ames mutagenicity (binary)
|
|
213
|
+
"dili",
|
|
214
|
+
// Drug-induced liver injury (binary)
|
|
215
|
+
"skin_sensitization",
|
|
216
|
+
// Skin sensitization (binary)
|
|
217
|
+
"carcinogenesis",
|
|
218
|
+
// Carcinogenicity (binary)
|
|
219
|
+
"rat_oral_ld50"
|
|
220
|
+
// Rat oral LD50 (mol/kg)
|
|
221
|
+
];
|
|
222
|
+
function countLipinskiViolations(smiles) {
|
|
223
|
+
let violations = 0;
|
|
224
|
+
const heavyAtomCount = (smiles.match(/[A-Z]/g) ?? []).length;
|
|
225
|
+
const estimatedMW = heavyAtomCount * 13;
|
|
226
|
+
if (estimatedMW > 500) violations++;
|
|
227
|
+
const carbonCount = (smiles.match(/C/g) ?? []).length;
|
|
228
|
+
const polarAtomCount = (smiles.match(/[NO]/g) ?? []).length;
|
|
229
|
+
const estimatedLogP = carbonCount * 0.54 - polarAtomCount * 1;
|
|
230
|
+
if (estimatedLogP > 5) violations++;
|
|
231
|
+
const hbdCount = (smiles.match(/OH|NH/g) ?? []).length;
|
|
232
|
+
if (hbdCount > 5) violations++;
|
|
233
|
+
if (polarAtomCount > 10) violations++;
|
|
234
|
+
return violations;
|
|
235
|
+
}
|
|
236
|
+
function computeDrugLikeness(predictions, lipinskiViolations) {
|
|
237
|
+
let score = 1;
|
|
238
|
+
score -= lipinskiViolations * 0.2;
|
|
239
|
+
for (const p of predictions) {
|
|
240
|
+
if (p.property === "bbb_permeability" && p.value < 0.5) {
|
|
241
|
+
score -= 0.15;
|
|
242
|
+
}
|
|
243
|
+
if (p.property === "hia" && p.value < 80) {
|
|
244
|
+
score -= 0.1;
|
|
245
|
+
}
|
|
246
|
+
if ((p.property === "ames" || p.property === "hht" || p.property === "dili") && p.value > 0.5) {
|
|
247
|
+
score -= 0.25;
|
|
248
|
+
}
|
|
249
|
+
if (p.property === "half_life" && p.value >= 5 && p.value <= 24) {
|
|
250
|
+
score += 0.1;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return Math.max(0, Math.min(1, score));
|
|
254
|
+
}
|
|
255
|
+
function selectAdmetBackend(questionType) {
|
|
256
|
+
const q = questionType.toLowerCase();
|
|
257
|
+
if (q.includes("comprehensive") || q.includes("full") || q.includes("admetlab")) {
|
|
258
|
+
return "admetlab";
|
|
259
|
+
}
|
|
260
|
+
if (q.includes("offline") || q.includes("local") || q.includes("latency") || q.includes("edge")) {
|
|
261
|
+
return "local-ml";
|
|
262
|
+
}
|
|
263
|
+
return "rdkit";
|
|
264
|
+
}
|
|
265
|
+
function admetProvenance(result) {
|
|
266
|
+
let h = 2166136261;
|
|
267
|
+
const fnv = (s) => {
|
|
268
|
+
h = 2166136261;
|
|
269
|
+
for (let i = 0; i < s.length; i++) {
|
|
270
|
+
h ^= s.charCodeAt(i);
|
|
271
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
272
|
+
}
|
|
273
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
274
|
+
};
|
|
275
|
+
const head = `structural-biology@0.1.0|admet|${result.backend}`;
|
|
276
|
+
const smilesHash = fnv(result.smiles);
|
|
277
|
+
const predictionKey = `props:${result.predictions.length}`;
|
|
278
|
+
const scoreKey = `dl:${result.drugLikenessScore?.toFixed(2) ?? "na"}`;
|
|
279
|
+
const lipinskiKey = `lipinski:${result.lipinskiViolations ?? "na"}`;
|
|
280
|
+
return fnv(`${head}|smiles:${smilesHash}|${predictionKey}|${scoreKey}|${lipinskiKey}`);
|
|
281
|
+
}
|
|
282
|
+
function compileAdmet(traits, opts = {}) {
|
|
283
|
+
const format = opts.format ?? "csv";
|
|
284
|
+
switch (format) {
|
|
285
|
+
case "csv":
|
|
286
|
+
return compileAdmetToCsv(traits);
|
|
287
|
+
case "json":
|
|
288
|
+
return compileAdmetToJson(traits);
|
|
289
|
+
case "sdf":
|
|
290
|
+
return compileAdmetToSdf(traits);
|
|
291
|
+
case "holo":
|
|
292
|
+
return compileAdmetToHolo(traits);
|
|
293
|
+
default:
|
|
294
|
+
throw new Error(`Unsupported ADMET format: ${format}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function compileAdmetToCsv(traits) {
|
|
298
|
+
const rows = [
|
|
299
|
+
"trait,smiles,backend,properties,drugLikenessScore,lipinskiViolations,passes"
|
|
300
|
+
];
|
|
301
|
+
for (const t of traits) {
|
|
302
|
+
if (t.trait === "admet_prediction") {
|
|
303
|
+
const props = t.properties?.join(";") ?? "all";
|
|
304
|
+
rows.push(`admet_prediction,${t.smiles},${t.backend ?? "rdkit"},${props},,`);
|
|
305
|
+
} else if (t.trait === "admet_result") {
|
|
306
|
+
rows.push(
|
|
307
|
+
`admet_result,${t.smiles},${t.backend},${t.propertyCount},${t.drugLikenessScore},${t.lipinskiViolations},${t.passes}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return rows.join("\n");
|
|
312
|
+
}
|
|
313
|
+
function compileAdmetToJson(traits) {
|
|
314
|
+
return JSON.stringify(traits, null, 2);
|
|
315
|
+
}
|
|
316
|
+
function compileAdmetToSdf(traits) {
|
|
317
|
+
const lines = [];
|
|
318
|
+
for (const t of traits) {
|
|
319
|
+
lines.push("molecule");
|
|
320
|
+
lines.push(" HoloScript/ADMET");
|
|
321
|
+
lines.push("");
|
|
322
|
+
lines.push(" 0 0 0 0 0 0 0 0 0 0 0 V2000");
|
|
323
|
+
lines.push("M END");
|
|
324
|
+
if (t.trait === "admet_result") {
|
|
325
|
+
lines.push(`> <admet.drugLikenessScore>`);
|
|
326
|
+
lines.push(`${t.drugLikenessScore}`);
|
|
327
|
+
lines.push("");
|
|
328
|
+
lines.push(`> <admet.lipinskiViolations>`);
|
|
329
|
+
lines.push(`${t.lipinskiViolations}`);
|
|
330
|
+
lines.push("");
|
|
331
|
+
lines.push(`> <admet.passes>`);
|
|
332
|
+
lines.push(`${t.passes}`);
|
|
333
|
+
lines.push("");
|
|
334
|
+
}
|
|
335
|
+
lines.push("$$$$");
|
|
336
|
+
}
|
|
337
|
+
return lines.join("\n");
|
|
338
|
+
}
|
|
339
|
+
function compileAdmetToHolo(traits) {
|
|
340
|
+
const lines = ['composition "AdmetScene" {'];
|
|
341
|
+
for (const t of traits) {
|
|
342
|
+
if (t.trait === "admet_prediction") {
|
|
343
|
+
lines.push(` object "AdmetPrediction" @admet_prediction {`);
|
|
344
|
+
lines.push(` smiles: "${t.smiles}"`);
|
|
345
|
+
if (t.backend) lines.push(` backend: "${t.backend}"`);
|
|
346
|
+
if (t.properties && t.properties.length > 0) {
|
|
347
|
+
lines.push(` properties: [${t.properties.map((p) => `"${p}"`).join(", ")}]`);
|
|
348
|
+
}
|
|
349
|
+
lines.push(" }");
|
|
350
|
+
} else if (t.trait === "admet_result") {
|
|
351
|
+
lines.push(` object "AdmetResult" @admet_result {`);
|
|
352
|
+
lines.push(` smiles: "${t.smiles}"`);
|
|
353
|
+
lines.push(` drugLikenessScore: ${t.drugLikenessScore}`);
|
|
354
|
+
lines.push(` lipinskiViolations: ${t.lipinskiViolations}`);
|
|
355
|
+
lines.push(` propertyCount: ${t.propertyCount}`);
|
|
356
|
+
lines.push(` backend: "${t.backend}"`);
|
|
357
|
+
lines.push(` passes: ${t.passes}`);
|
|
358
|
+
lines.push(" }");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
lines.push("}");
|
|
362
|
+
return lines.join("\n");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/receipt.ts
|
|
366
|
+
var PLUGIN_ID = "structural-biology@0.2.0";
|
|
367
|
+
function createDockingReceipt(config, result) {
|
|
368
|
+
const provenanceHash = dockingProvenance(result);
|
|
369
|
+
const verified = result.status === "success";
|
|
370
|
+
return {
|
|
371
|
+
receiptId: `dock-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
372
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
373
|
+
pluginId: PLUGIN_ID,
|
|
374
|
+
scale: "atomistic",
|
|
375
|
+
backend: result.backend,
|
|
376
|
+
config,
|
|
377
|
+
result,
|
|
378
|
+
provenanceHash,
|
|
379
|
+
executionTimeMs: result.executionTimeMs,
|
|
380
|
+
verified
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function createAdmetReceipt(config, result) {
|
|
384
|
+
const provenanceHash = admetProvenance(result);
|
|
385
|
+
const verified = result.status === "success";
|
|
386
|
+
return {
|
|
387
|
+
receiptId: `admet-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
388
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
389
|
+
pluginId: PLUGIN_ID,
|
|
390
|
+
scale: "empirical-surrogate",
|
|
391
|
+
backend: result.backend,
|
|
392
|
+
config,
|
|
393
|
+
result,
|
|
394
|
+
provenanceHash,
|
|
395
|
+
executionTimeMs: result.executionTimeMs,
|
|
396
|
+
verified
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function createDrugDiscoveryReceipt(dockingReceipt, admetReceipt) {
|
|
400
|
+
const compositeInput = `${dockingReceipt.provenanceHash}|${admetReceipt.provenanceHash}`;
|
|
401
|
+
let h = 2166136261;
|
|
402
|
+
for (let i = 0; i < compositeInput.length; i++) {
|
|
403
|
+
h ^= compositeInput.charCodeAt(i);
|
|
404
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
405
|
+
}
|
|
406
|
+
const compositeProvenanceHash = (h >>> 0).toString(16).padStart(8, "0");
|
|
407
|
+
const bestAffinity = dockingReceipt.result.bestAffinity ?? 0;
|
|
408
|
+
const drugLikenessScore = admetReceipt.result.drugLikenessScore ?? 0;
|
|
409
|
+
const lipinskiViolations = admetReceipt.result.lipinskiViolations ?? 0;
|
|
410
|
+
const propertyCount = admetReceipt.result.predictions.length;
|
|
411
|
+
const passes = drugLikenessScore > 0.5 && lipinskiViolations <= 1;
|
|
412
|
+
const assessment = {
|
|
413
|
+
bestAffinity,
|
|
414
|
+
drugLikenessScore,
|
|
415
|
+
lipinskiViolations,
|
|
416
|
+
propertyCount,
|
|
417
|
+
passes,
|
|
418
|
+
ki_nm: dockingReceipt.result.poses[0] ? Math.exp(dockingReceipt.result.bestAffinity * 1e3 / (1989e-6 * 298.15)) : void 0,
|
|
419
|
+
hitRate: dockingReceipt.result.poses.length > 0 ? dockingReceipt.result.poses.filter((p) => p.affinity <= -7).length / dockingReceipt.result.poses.length : void 0,
|
|
420
|
+
summary: formatAssessmentSummary(bestAffinity, drugLikenessScore, lipinskiViolations, passes)
|
|
421
|
+
};
|
|
422
|
+
return {
|
|
423
|
+
receiptId: `drug-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
424
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
425
|
+
pluginId: PLUGIN_ID,
|
|
426
|
+
docking: dockingReceipt,
|
|
427
|
+
admet: admetReceipt,
|
|
428
|
+
compositeProvenanceHash,
|
|
429
|
+
assessment
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function formatAssessmentSummary(bestAffinity, drugLikenessScore, lipinskiViolations, passes) {
|
|
433
|
+
const affinityStr = bestAffinity < -7 ? `strong binding (${bestAffinity.toFixed(1)} kcal/mol)` : bestAffinity < -5 ? `moderate binding (${bestAffinity.toFixed(1)} kcal/mol)` : `weak binding (${bestAffinity.toFixed(1)} kcal/mol)`;
|
|
434
|
+
const drugStr = passes ? `drug-like (score=${drugLikenessScore.toFixed(2)}, ${lipinskiViolations} Lipinski violations)` : `not drug-like (score=${drugLikenessScore.toFixed(2)}, ${lipinskiViolations} Lipinski violations)`;
|
|
435
|
+
return `${affinityStr}, ${drugStr}`;
|
|
436
|
+
}
|
|
437
|
+
function runDrugDiscoveryPipeline(dockingConfig, dockingResult, admetConfig, admetResult) {
|
|
438
|
+
const dockingReceipt = createDockingReceipt(dockingConfig, dockingResult);
|
|
439
|
+
const admetReceipt = createAdmetReceipt(admetConfig, admetResult);
|
|
440
|
+
return createDrugDiscoveryReceipt(dockingReceipt, admetReceipt);
|
|
441
|
+
}
|
|
442
|
+
function compileDockingReceiptToHolo(receipt) {
|
|
443
|
+
const lines = [
|
|
444
|
+
'composition "DockingReceipt" {',
|
|
445
|
+
` meta {`,
|
|
446
|
+
` receiptId: "${receipt.receiptId}"`,
|
|
447
|
+
` issuedAt: "${receipt.issuedAt}"`,
|
|
448
|
+
` pluginId: "${receipt.pluginId}"`,
|
|
449
|
+
` scale: "${receipt.scale}"`,
|
|
450
|
+
` backend: "${receipt.backend}"`,
|
|
451
|
+
` provenanceHash: "${receipt.provenanceHash}"`,
|
|
452
|
+
` verified: ${receipt.verified}`,
|
|
453
|
+
` }`,
|
|
454
|
+
` config @auto_dock {`,
|
|
455
|
+
` receptor: "${receipt.config.receptor}"`,
|
|
456
|
+
` ligand: "${receipt.config.ligand}"`,
|
|
457
|
+
` center: { x: ${receipt.config.centerX}, y: ${receipt.config.centerY}, z: ${receipt.config.centerZ} }`,
|
|
458
|
+
` size: { x: ${receipt.config.sizeX}, y: ${receipt.config.sizeY}, z: ${receipt.config.sizeZ} }`,
|
|
459
|
+
` }`,
|
|
460
|
+
` result @binding_affinity {`,
|
|
461
|
+
` bestAffinity: ${receipt.result.bestAffinity ?? "null"}`,
|
|
462
|
+
` poseCount: ${receipt.result.poses.length}`,
|
|
463
|
+
` status: "${receipt.result.status}"`,
|
|
464
|
+
` }`,
|
|
465
|
+
`}`
|
|
466
|
+
];
|
|
467
|
+
return lines.join("\n");
|
|
468
|
+
}
|
|
469
|
+
function compileAdmetReceiptToHolo(receipt) {
|
|
470
|
+
const lines = [
|
|
471
|
+
'composition "AdmetReceipt" {',
|
|
472
|
+
` meta {`,
|
|
473
|
+
` receiptId: "${receipt.receiptId}"`,
|
|
474
|
+
` issuedAt: "${receipt.issuedAt}"`,
|
|
475
|
+
` pluginId: "${receipt.pluginId}"`,
|
|
476
|
+
` scale: "${receipt.scale}"`,
|
|
477
|
+
` backend: "${receipt.backend}"`,
|
|
478
|
+
` provenanceHash: "${receipt.provenanceHash}"`,
|
|
479
|
+
` verified: ${receipt.verified}`,
|
|
480
|
+
` }`,
|
|
481
|
+
` config @admet_prediction {`,
|
|
482
|
+
` smiles: "${receipt.config.smiles}"`,
|
|
483
|
+
` }`,
|
|
484
|
+
` result @admet_result {`,
|
|
485
|
+
` drugLikenessScore: ${receipt.result.drugLikenessScore ?? "null"}`,
|
|
486
|
+
` lipinskiViolations: ${receipt.result.lipinskiViolations ?? "null"}`,
|
|
487
|
+
` propertyCount: ${receipt.result.predictions.length}`,
|
|
488
|
+
` status: "${receipt.result.status}"`,
|
|
489
|
+
` }`,
|
|
490
|
+
`}`
|
|
491
|
+
];
|
|
492
|
+
return lines.join("\n");
|
|
493
|
+
}
|
|
494
|
+
function compileDrugDiscoveryReceiptToHolo(receipt) {
|
|
495
|
+
const lines = [
|
|
496
|
+
'composition "DrugDiscoveryReceipt" {',
|
|
497
|
+
` meta {`,
|
|
498
|
+
` receiptId: "${receipt.receiptId}"`,
|
|
499
|
+
` issuedAt: "${receipt.issuedAt}"`,
|
|
500
|
+
` pluginId: "${receipt.pluginId}"`,
|
|
501
|
+
` compositeProvenanceHash: "${receipt.compositeProvenanceHash}"`,
|
|
502
|
+
` }`,
|
|
503
|
+
` docking {`,
|
|
504
|
+
` receiptId: "${receipt.docking.receiptId}"`,
|
|
505
|
+
` provenanceHash: "${receipt.docking.provenanceHash}"`,
|
|
506
|
+
` backend: "${receipt.docking.backend}"`,
|
|
507
|
+
` bestAffinity: ${receipt.assessment.bestAffinity}`,
|
|
508
|
+
` poseCount: ${receipt.docking.result.poses.length}`,
|
|
509
|
+
` }`,
|
|
510
|
+
` admet {`,
|
|
511
|
+
` receiptId: "${receipt.admet.receiptId}"`,
|
|
512
|
+
` provenanceHash: "${receipt.admet.provenanceHash}"`,
|
|
513
|
+
` backend: "${receipt.admet.backend}"`,
|
|
514
|
+
` drugLikenessScore: ${receipt.assessment.drugLikenessScore}`,
|
|
515
|
+
` lipinskiViolations: ${receipt.assessment.lipinskiViolations}`,
|
|
516
|
+
` passes: ${receipt.assessment.passes}`,
|
|
517
|
+
` }`,
|
|
518
|
+
` assessment {`,
|
|
519
|
+
` passes: ${receipt.assessment.passes}`,
|
|
520
|
+
` drugLikenessScore: ${receipt.assessment.drugLikenessScore}`,
|
|
521
|
+
` bestAffinity: ${receipt.assessment.bestAffinity}`,
|
|
522
|
+
` summary: "${receipt.assessment.summary}"`,
|
|
523
|
+
` }`,
|
|
524
|
+
`}`
|
|
525
|
+
];
|
|
526
|
+
return lines.join("\n");
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/index.ts
|
|
530
|
+
var STRUCTURAL_BIOLOGY_OBJECT_TYPES = ["protein", "ligand", "chain"];
|
|
531
|
+
var STRUCTURAL_BIOLOGY_TRAITS = [
|
|
532
|
+
"foldable",
|
|
533
|
+
// protein backbone supports folding state transitions
|
|
534
|
+
"helix",
|
|
535
|
+
// alpha-helix secondary-structure annotation
|
|
536
|
+
"sheet",
|
|
537
|
+
// beta-sheet secondary-structure annotation
|
|
538
|
+
"residue_anchor"
|
|
539
|
+
// per-residue provenance anchor (chain id + index)
|
|
540
|
+
];
|
|
541
|
+
var BRIDGE_TRAITS = [
|
|
542
|
+
"auto_dock",
|
|
543
|
+
// Automated molecular docking (core trait)
|
|
544
|
+
"binding_affinity",
|
|
545
|
+
// Binding affinity metrics (core trait)
|
|
546
|
+
"admet_prediction",
|
|
547
|
+
// ADMET property prediction (new)
|
|
548
|
+
"admet_result"
|
|
549
|
+
// ADMET prediction result (new)
|
|
550
|
+
];
|
|
551
|
+
var PLUGIN_DESCRIPTOR = {
|
|
552
|
+
id: "structural-biology",
|
|
553
|
+
version: "0.2.0",
|
|
554
|
+
objectTypes: STRUCTURAL_BIOLOGY_OBJECT_TYPES,
|
|
555
|
+
traits: STRUCTURAL_BIOLOGY_TRAITS,
|
|
556
|
+
bridgeTraits: BRIDGE_TRAITS
|
|
557
|
+
};
|
|
558
|
+
function register(host) {
|
|
559
|
+
for (const name of STRUCTURAL_BIOLOGY_OBJECT_TYPES) {
|
|
560
|
+
host.registerObjectType(name, { plugin: PLUGIN_DESCRIPTOR.id });
|
|
561
|
+
}
|
|
562
|
+
for (const name of STRUCTURAL_BIOLOGY_TRAITS) {
|
|
563
|
+
host.registerTrait(name, { plugin: PLUGIN_DESCRIPTOR.id });
|
|
564
|
+
}
|
|
565
|
+
for (const name of BRIDGE_TRAITS) {
|
|
566
|
+
host.registerTrait(name, { plugin: PLUGIN_DESCRIPTOR.id });
|
|
567
|
+
}
|
|
568
|
+
return PLUGIN_DESCRIPTOR;
|
|
569
|
+
}
|
|
570
|
+
function fnv1a(s) {
|
|
571
|
+
let h = 2166136261;
|
|
572
|
+
for (let i = 0; i < s.length; i++) {
|
|
573
|
+
h ^= s.charCodeAt(i);
|
|
574
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
575
|
+
}
|
|
576
|
+
return h >>> 0;
|
|
577
|
+
}
|
|
578
|
+
function hashHex(s) {
|
|
579
|
+
return fnv1a(s).toString(16).padStart(8, "0");
|
|
580
|
+
}
|
|
581
|
+
function residueAnchor(residue) {
|
|
582
|
+
return hashHex(
|
|
583
|
+
`${PLUGIN_DESCRIPTOR.id}@${PLUGIN_DESCRIPTOR.version}|${residue.chain}:${residue.index}:${residue.resname}:${residue.secondary ?? "loop"}`
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
function chainHash(obj) {
|
|
587
|
+
const head = `${PLUGIN_DESCRIPTOR.id}@${PLUGIN_DESCRIPTOR.version}|${obj.type}|${obj.name}`;
|
|
588
|
+
if (obj.type === "protein") {
|
|
589
|
+
const tail = obj.residues.map(residueAnchor).join(":");
|
|
590
|
+
return hashHex(`${head}|${tail}|traits:${obj.traits.join(",")}`);
|
|
591
|
+
}
|
|
592
|
+
const traitsTail = `traits:${obj.traits.join(",")}`;
|
|
593
|
+
if (obj.type === "ligand") {
|
|
594
|
+
return hashHex(`${head}|smiles:${obj.smiles ?? ""}|${traitsTail}`);
|
|
595
|
+
}
|
|
596
|
+
return hashHex(`${head}|parent:${obj.parentProtein}|count:${obj.residueCount}|${traitsTail}`);
|
|
597
|
+
}
|
|
598
|
+
function verifyChain(obj, expected) {
|
|
599
|
+
return chainHash(obj) === expected;
|
|
600
|
+
}
|
|
601
|
+
var VERSION = "0.2.0";
|
|
602
|
+
export {
|
|
603
|
+
ADMET_PROPERTIES,
|
|
604
|
+
BRIDGE_TRAITS,
|
|
605
|
+
PLUGIN_DESCRIPTOR,
|
|
606
|
+
STRUCTURAL_BIOLOGY_OBJECT_TYPES,
|
|
607
|
+
STRUCTURAL_BIOLOGY_TRAITS,
|
|
608
|
+
VERSION,
|
|
609
|
+
admetProvenance,
|
|
610
|
+
chainHash,
|
|
611
|
+
compileAdmet,
|
|
612
|
+
compileAdmetReceiptToHolo,
|
|
613
|
+
compileDocking,
|
|
614
|
+
compileDockingReceiptToHolo,
|
|
615
|
+
compileDrugDiscoveryReceiptToHolo,
|
|
616
|
+
computeDrugLikeness,
|
|
617
|
+
countLipinskiViolations,
|
|
618
|
+
createAdmetReceipt,
|
|
619
|
+
createDockingReceipt,
|
|
620
|
+
createDrugDiscoveryReceipt,
|
|
621
|
+
dockingProvenance,
|
|
622
|
+
register,
|
|
623
|
+
residueAnchor,
|
|
624
|
+
runDrugDiscoveryPipeline,
|
|
625
|
+
selectAdmetBackend,
|
|
626
|
+
selectDockingBackend,
|
|
627
|
+
traitToConfig,
|
|
628
|
+
verifyChain
|
|
629
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@holoscript/structural-biology-plugin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Structural-biology domain plugin: protein/ligand/chain + docking (AutoDock-GPU/Vina/GNINA) + ADMET prediction (RDKit/ADMETlab/local-ML). Bridge from AlphaFold structure to binding affinity and drug-likeness.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"test:coverage": "vitest run --coverage"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"holoscript",
|
|
14
|
+
"plugin",
|
|
15
|
+
"structural-biology",
|
|
16
|
+
"protein",
|
|
17
|
+
"alphafold",
|
|
18
|
+
"autodock",
|
|
19
|
+
"docking",
|
|
20
|
+
"binding-affinity",
|
|
21
|
+
"admet",
|
|
22
|
+
"drug-discovery",
|
|
23
|
+
"paper-12"
|
|
24
|
+
],
|
|
25
|
+
"author": "brianonbased-dev",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
]
|
|
32
|
+
}
|