@inflexa-ai/harness 0.30.3 → 0.31.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/contracts/target-dossier.d.ts +61 -61
- package/dist/execution/run-synthesis.js +7 -2
- package/dist/lib/zod-issues.d.ts +8 -0
- package/dist/lib/zod-issues.js +13 -0
- package/dist/literature/sources/semantic-scholar.d.ts +34 -0
- package/dist/literature/sources/semantic-scholar.js +15 -4
- package/dist/prompts/briefing.d.ts +12 -1
- package/dist/prompts/briefing.js +27 -1
- package/dist/prompts/sandbox-standards.d.ts +1 -1
- package/dist/prompts/sandbox-standards.js +27 -0
- package/dist/sandbox/thread-env.d.ts +18 -12
- package/dist/sandbox/thread-env.js +36 -25
- package/dist/tools/bio/chembl.js +4 -3
- package/dist/tools/bio/drug-gene-interactions.d.ts +2 -14
- package/dist/tools/bio/drug-gene-interactions.js +10 -18
- package/dist/tools/bio/gene-disease-evidence.d.ts +3 -1
- package/dist/tools/bio/gene-disease-evidence.js +12 -9
- package/dist/tools/bio/keys.js +1 -0
- package/dist/tools/bio/lookup-annotation.d.ts +43 -0
- package/dist/tools/bio/lookup-annotation.js +19 -4
- package/dist/tools/bio/pubchem.js +5 -3
- package/dist/tools/bio/search-clinical-trials.js +5 -4
- package/dist/tools/bio/search-geo-datasets.d.ts +39 -9
- package/dist/tools/bio/search-geo-datasets.js +38 -20
- package/dist/tools/lib/api-utils.d.ts +27 -2
- package/dist/tools/lib/api-utils.js +43 -3
- package/dist/tools/lib/bgee-client.d.ts +16 -0
- package/dist/tools/lib/bgee-client.js +16 -5
- package/dist/tools/lib/cbioportal-client.d.ts +20 -0
- package/dist/tools/lib/cbioportal-client.js +16 -8
- package/dist/tools/lib/chembl-client.d.ts +105 -3
- package/dist/tools/lib/chembl-client.js +196 -84
- package/dist/tools/lib/clinical-benchmarks-client.d.ts +5 -0
- package/dist/tools/lib/clinical-benchmarks-client.js +5 -0
- package/dist/tools/lib/clinical-trials-client.d.ts +131 -2
- package/dist/tools/lib/clinical-trials-client.js +32 -6
- package/dist/tools/lib/clinvar-client.d.ts +35 -0
- package/dist/tools/lib/clinvar-client.js +34 -28
- package/dist/tools/lib/ctx-ops.d.ts +106 -35
- package/dist/tools/lib/ctx-ops.js +135 -69
- package/dist/tools/lib/dgidb-client.d.ts +73 -0
- package/dist/tools/lib/dgidb-client.js +25 -13
- package/dist/tools/lib/disgenet-client.d.ts +117 -24
- package/dist/tools/lib/disgenet-client.js +127 -34
- package/dist/tools/lib/disgenet-config.d.ts +1 -1
- package/dist/tools/lib/disgenet-config.js +5 -3
- package/dist/tools/lib/drugbank-client.d.ts +57 -58
- package/dist/tools/lib/drugbank-client.js +69 -64
- package/dist/tools/lib/drugbank-config.d.ts +6 -1
- package/dist/tools/lib/drugbank-config.js +6 -1
- package/dist/tools/lib/ema-client.d.ts +53 -0
- package/dist/tools/lib/ema-client.js +19 -7
- package/dist/tools/lib/ensembl-client.d.ts +4 -0
- package/dist/tools/lib/ensembl-client.js +4 -0
- package/dist/tools/lib/gwas-catalog-client.d.ts +64 -1
- package/dist/tools/lib/gwas-catalog-client.js +21 -6
- package/dist/tools/lib/identifier-resolver.d.ts +17 -1
- package/dist/tools/lib/identifier-resolver.js +13 -4
- package/dist/tools/lib/impc-client.d.ts +4 -0
- package/dist/tools/lib/impc-client.js +4 -0
- package/dist/tools/lib/iuphar-client.d.ts +10 -5
- package/dist/tools/lib/iuphar-client.js +49 -13
- package/dist/tools/lib/monarch-client.d.ts +4 -0
- package/dist/tools/lib/monarch-client.js +4 -0
- package/dist/tools/lib/openfda-client.d.ts +34 -0
- package/dist/tools/lib/openfda-client.js +22 -8
- package/dist/tools/lib/opentargets-client.d.ts +99 -4
- package/dist/tools/lib/opentargets-client.js +97 -37
- package/dist/tools/lib/pathway-client.d.ts +45 -0
- package/dist/tools/lib/pathway-client.js +88 -8
- package/dist/tools/lib/pharmgkb-client.d.ts +35 -1
- package/dist/tools/lib/pharmgkb-client.js +22 -6
- package/dist/tools/lib/pharmgkb-config.d.ts +8 -1
- package/dist/tools/lib/pharmgkb-config.js +8 -1
- package/dist/tools/lib/pubchem-client.d.ts +22 -0
- package/dist/tools/lib/pubchem-client.js +36 -33
- package/dist/tools/lib/pubchem-ops.d.ts +85 -5
- package/dist/tools/lib/pubchem-ops.js +96 -68
- package/dist/tools/lib/pubmed-client.d.ts +4 -0
- package/dist/tools/lib/pubmed-client.js +4 -0
- package/dist/tools/lib/string-client.d.ts +52 -0
- package/dist/tools/lib/string-client.js +12 -7
- package/dist/tools/lib/uniprot-client.d.ts +4 -0
- package/dist/tools/lib/uniprot-client.js +4 -0
- package/dist/tools/research/context7-docs.d.ts +23 -0
- package/dist/tools/research/context7-docs.js +49 -9
- package/dist/tools/research/search-github-repos.d.ts +4 -0
- package/dist/tools/research/search-github-repos.js +4 -0
- package/dist/workflows/execute-target-assessment.js +4 -4
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +2 -1
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +3 -3
- package/dist/workflows/target-assessment/collectors/index.d.ts +8 -2
- package/dist/workflows/target-assessment/collectors/index.js +37 -10
- package/dist/workflows/target-assessment/fanout/index.d.ts +2 -1
- package/dist/workflows/target-assessment/fanout/index.js +2 -2
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +2 -2
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +3 -2
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +16 -5
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +61 -60
- package/dist/workflows/target-assessment/phase4-assemble.js +2 -2
- package/dist/workflows/target-assessment/schemas.d.ts +2 -8
- package/dist/workflows/target-assessment/schemas.js +8 -2
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +1 -4
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +1 -4
- package/package.json +1 -1
- package/dist/agents/sandbox/__fixtures__/deps.d.ts +0 -23
- package/dist/agents/sandbox/__fixtures__/deps.js +0 -102
- package/dist/citations/__fixtures__/resolver.d.ts +0 -3
- package/dist/citations/__fixtures__/resolver.js +0 -9
- package/dist/loop/__fixtures__/scripted-provider.d.ts +0 -31
- package/dist/loop/__fixtures__/scripted-provider.js +0 -81
- package/dist/providers/__fixtures__/session.d.ts +0 -17
- package/dist/providers/__fixtures__/session.js +0 -20
- package/dist/tools/__fixtures__/tool-context.d.ts +0 -13
- package/dist/tools/__fixtures__/tool-context.js +0 -22
|
@@ -2,10 +2,32 @@
|
|
|
2
2
|
* Pure async client functions for the ChEMBL REST API.
|
|
3
3
|
*
|
|
4
4
|
* Used directly by target-assessment workflow steps and by tool wrappers.
|
|
5
|
+
*
|
|
6
|
+
* Absence policy: ChEMBL sends an explicit `null` for an absent value, and it
|
|
7
|
+
* never omits the key. Thus a maybe-absent field carries `.nullable()`, not
|
|
8
|
+
* `.optional()`.
|
|
5
9
|
*/
|
|
6
10
|
import { z } from "zod";
|
|
7
|
-
import {
|
|
11
|
+
import { createNoopLogger } from "../../lib/console-logger.js";
|
|
12
|
+
import { apiFetchValidated, describeApiError, isUnexpectedApiError, zWireNumber } from "./api-utils.js";
|
|
8
13
|
import { CHEMBL_BASE, CHEMBL_HEADERS as HEADERS } from "./chembl-config.js";
|
|
14
|
+
/** The `max_phase` of an approved drug. ChEMBL serializes it as the string "4.0". */
|
|
15
|
+
const APPROVED_MAX_PHASE = 4;
|
|
16
|
+
/**
|
|
17
|
+
* Report one molecule that a per-molecule lookup dropped from its result.
|
|
18
|
+
*
|
|
19
|
+
* These loops turned every error into a skipped molecule, thus a ChEMBL contract
|
|
20
|
+
* break emptied a whole modulator list in silence. An unexpected cause now
|
|
21
|
+
* reaches the injected `Logger` before the loop degrades. An expected miss, such
|
|
22
|
+
* as an HTTP 404, stays silent, because ChEMBL holds no record of that molecule.
|
|
23
|
+
*/
|
|
24
|
+
function reportSkippedMolecule(logger, error, moleculeChemblId) {
|
|
25
|
+
if (!isUnexpectedApiError(error))
|
|
26
|
+
return;
|
|
27
|
+
logger.error("molecule dropped from the result", { moleculeChemblId, cause: describeApiError(error) });
|
|
28
|
+
}
|
|
29
|
+
/** How many disease terms one drug can contribute to an indication read. */
|
|
30
|
+
const INDICATIONS_PER_DRUG = 20;
|
|
9
31
|
const TargetComponentSynonymSchema = z.object({
|
|
10
32
|
syn_type: z.string().optional(),
|
|
11
33
|
component_synonym: z.string().optional(),
|
|
@@ -18,54 +40,59 @@ const RawTargetSchema = z.object({
|
|
|
18
40
|
target_chembl_id: z.string().optional(),
|
|
19
41
|
pref_name: z.string().optional(),
|
|
20
42
|
target_type: z.string().optional(),
|
|
21
|
-
organism: z.string().optional(),
|
|
43
|
+
organism: z.string().nullable().optional(),
|
|
22
44
|
target_components: z.array(TargetComponentSchema).optional(),
|
|
23
45
|
});
|
|
46
|
+
// ChEMBL serves a Tastypie `decimal` as a JSON string ("41.0"), and it encodes an
|
|
47
|
+
// absent value as an explicit `null` rather than an omitted key. Thus a decimal
|
|
48
|
+
// field reads through `zWireNumber`, and a field that the published schema marks
|
|
49
|
+
// nullable carries `.nullable()`.
|
|
24
50
|
/** A raw activity row from the ChEMBL `activity` endpoint. */
|
|
25
51
|
const RawChemblActivitySchema = z.object({
|
|
26
52
|
activity_id: z.number().optional(),
|
|
27
53
|
molecule_chembl_id: z.string().optional(),
|
|
28
54
|
target_chembl_id: z.string().optional(),
|
|
29
55
|
standard_type: z.string().optional(),
|
|
30
|
-
standard_value:
|
|
31
|
-
standard_units: z.string().optional(),
|
|
56
|
+
standard_value: zWireNumber.nullable().optional(),
|
|
57
|
+
standard_units: z.string().nullable().optional(),
|
|
32
58
|
assay_chembl_id: z.string().optional(),
|
|
33
59
|
assay_type: z.string().optional(),
|
|
34
|
-
pchembl_value:
|
|
60
|
+
pchembl_value: zWireNumber.nullable().optional(),
|
|
35
61
|
});
|
|
36
62
|
/** A raw mechanism row from the ChEMBL `mechanism` endpoint. */
|
|
37
63
|
const RawChemblMechanismSchema = z.object({
|
|
38
64
|
mechanism_of_action: z.string().optional(),
|
|
39
|
-
action_type: z.string().optional(),
|
|
40
|
-
target_chembl_id: z.string().optional(),
|
|
65
|
+
action_type: z.string().nullable().optional(),
|
|
66
|
+
target_chembl_id: z.string().nullable().optional(),
|
|
41
67
|
molecule_chembl_id: z.string().optional(),
|
|
42
68
|
});
|
|
43
|
-
/** A
|
|
44
|
-
const
|
|
69
|
+
/** A raw row of the ChEMBL `drug_indication` resource, which pairs a molecule with a disease term. */
|
|
70
|
+
const RawDrugIndicationSchema = z.object({
|
|
71
|
+
molecule_chembl_id: z.string().nullable().optional(),
|
|
45
72
|
mesh_heading: z.string().optional(),
|
|
46
|
-
efo_term: z.string().optional(),
|
|
73
|
+
efo_term: z.string().nullable().optional(),
|
|
74
|
+
max_phase_for_ind: zWireNumber.nullable().optional(),
|
|
47
75
|
});
|
|
48
|
-
/** A raw molecule
|
|
76
|
+
/** A raw molecule record from the ChEMBL `molecule` endpoint. */
|
|
49
77
|
const RawChemblMoleculeSchema = z.object({
|
|
50
78
|
molecule_chembl_id: z.string().optional(),
|
|
51
|
-
pref_name: z.string().optional(),
|
|
52
|
-
max_phase:
|
|
53
|
-
molecule_type: z.string().optional(),
|
|
79
|
+
pref_name: z.string().nullable().optional(),
|
|
80
|
+
max_phase: zWireNumber.nullable().optional(),
|
|
81
|
+
molecule_type: z.string().nullable().optional(),
|
|
54
82
|
first_approval: z.number().nullable().optional(),
|
|
55
|
-
drug_indications: z.array(DrugIndicationRecordSchema).optional(),
|
|
56
83
|
molecule_hierarchy: z.object({ parent_chembl_id: z.string().optional() }).optional(),
|
|
57
84
|
});
|
|
58
85
|
// Response envelopes returned by the ChEMBL list/record endpoints, validated at
|
|
59
86
|
// the fetch boundary. Every wrapper field is optional because the API omits it
|
|
60
87
|
// when there is no data (a 404 is handled separately as an expected empty).
|
|
61
|
-
const TargetSearchResponseSchema = z.object({ targets: z.array(RawTargetSchema).optional() });
|
|
62
|
-
const ActivitiesResponseSchema = z.object({ activities: z.array(RawChemblActivitySchema).optional() });
|
|
63
|
-
const MechanismsResponseSchema = z.object({ mechanisms: z.array(RawChemblMechanismSchema).optional() });
|
|
64
|
-
const
|
|
65
|
-
const MoleculeSearchResponseSchema = z.object({ molecules: z.array(RawChemblMoleculeSchema).optional() });
|
|
88
|
+
export const TargetSearchResponseSchema = z.object({ targets: z.array(RawTargetSchema).optional() });
|
|
89
|
+
export const ActivitiesResponseSchema = z.object({ activities: z.array(RawChemblActivitySchema).optional() });
|
|
90
|
+
export const MechanismsResponseSchema = z.object({ mechanisms: z.array(RawChemblMechanismSchema).optional() });
|
|
91
|
+
export const DrugIndicationsResponseSchema = z.object({ drug_indications: z.array(RawDrugIndicationSchema).optional() });
|
|
92
|
+
export const MoleculeSearchResponseSchema = z.object({ molecules: z.array(RawChemblMoleculeSchema).optional() });
|
|
66
93
|
const TargetNameResponseSchema = z.object({ pref_name: z.string().optional() });
|
|
67
|
-
const MoleculeStructuresResponseSchema = z.object({
|
|
68
|
-
molecule_structures: z.object({ standard_inchi_key: z.string().nullable().optional() }).optional(),
|
|
94
|
+
export const MoleculeStructuresResponseSchema = z.object({
|
|
95
|
+
molecule_structures: z.object({ standard_inchi_key: z.string().nullable().optional() }).nullable().optional(),
|
|
69
96
|
});
|
|
70
97
|
const TargetComponentsResponseSchema = z.object({
|
|
71
98
|
target_components: z.array(z.object({ accession: z.string().optional(), component_type: z.string().optional() })).optional(),
|
|
@@ -90,9 +117,11 @@ const CompoundRecordSchema = z
|
|
|
90
117
|
.optional(),
|
|
91
118
|
molecule_properties: z
|
|
92
119
|
.object({
|
|
93
|
-
full_mwt:
|
|
94
|
-
alogp:
|
|
95
|
-
|
|
120
|
+
full_mwt: zWireNumber.nullable().optional(),
|
|
121
|
+
alogp: zWireNumber.nullable().optional(),
|
|
122
|
+
// `full_molformula` is the wire name of the molecular formula. The
|
|
123
|
+
// `molecule_properties` block carries no `molecular_formula` key.
|
|
124
|
+
full_molformula: z.string().nullable().optional(),
|
|
96
125
|
})
|
|
97
126
|
.nullable()
|
|
98
127
|
.optional(),
|
|
@@ -101,11 +130,11 @@ const CompoundRecordSchema = z
|
|
|
101
130
|
chemblId: raw.molecule_chembl_id ?? "",
|
|
102
131
|
preferredCompoundName: raw.pref_name ?? null,
|
|
103
132
|
canonicalSmiles: raw.molecule_structures?.canonical_smiles ?? null,
|
|
104
|
-
molecularWeight: raw.molecule_properties?.full_mwt
|
|
105
|
-
alogp: raw.molecule_properties?.alogp
|
|
106
|
-
molecularFormula: raw.molecule_properties?.
|
|
133
|
+
molecularWeight: raw.molecule_properties?.full_mwt ?? null,
|
|
134
|
+
alogp: raw.molecule_properties?.alogp ?? null,
|
|
135
|
+
molecularFormula: raw.molecule_properties?.full_molformula ?? null,
|
|
107
136
|
}));
|
|
108
|
-
const CompoundsResponseSchema = z.object({ molecules: z.array(CompoundRecordSchema).optional() });
|
|
137
|
+
export const CompoundsResponseSchema = z.object({ molecules: z.array(CompoundRecordSchema).optional() });
|
|
109
138
|
// The two hops of the target-mode compound search read one id each, so they
|
|
110
139
|
// project the response down to that id rather than reusing the richer
|
|
111
140
|
// target/activity schemas above.
|
|
@@ -139,12 +168,6 @@ function mapTarget(raw) {
|
|
|
139
168
|
geneNames: extractGeneNames(raw.target_components),
|
|
140
169
|
};
|
|
141
170
|
}
|
|
142
|
-
function parseNumeric(val) {
|
|
143
|
-
if (val == null)
|
|
144
|
-
return null;
|
|
145
|
-
const n = typeof val === "number" ? val : parseFloat(val);
|
|
146
|
-
return Number.isNaN(n) ? null : n;
|
|
147
|
-
}
|
|
148
171
|
/**
|
|
149
172
|
* Compounds assayed against a target: resolve the query to a target ChEMBL ID,
|
|
150
173
|
* read that target's activity rows, then fetch the unique molecules behind them.
|
|
@@ -264,11 +287,11 @@ export async function getBioactivity(chemblId, type, options = {}) {
|
|
|
264
287
|
compoundChemblId: raw.molecule_chembl_id ?? "",
|
|
265
288
|
targetChemblId: raw.target_chembl_id ?? null,
|
|
266
289
|
standardType: raw.standard_type ?? null,
|
|
267
|
-
standardValue:
|
|
290
|
+
standardValue: raw.standard_value ?? null,
|
|
268
291
|
standardUnits: raw.standard_units ?? null,
|
|
269
292
|
assayChemblId: raw.assay_chembl_id ?? null,
|
|
270
293
|
assayType: raw.assay_type ?? null,
|
|
271
|
-
pchemblValue:
|
|
294
|
+
pchemblValue: raw.pchembl_value ?? null,
|
|
272
295
|
}));
|
|
273
296
|
}
|
|
274
297
|
async function fetchTargetName(targetChemblId) {
|
|
@@ -311,56 +334,137 @@ export async function getMechanism(chemblId) {
|
|
|
311
334
|
moleculeChemblId: raw.molecule_chembl_id ?? "",
|
|
312
335
|
}));
|
|
313
336
|
}
|
|
314
|
-
function
|
|
315
|
-
|
|
316
|
-
|
|
337
|
+
function mapDrug(raw, indication) {
|
|
338
|
+
return {
|
|
339
|
+
moleculeChemblId: raw.molecule_chembl_id ?? "",
|
|
340
|
+
preferredName: raw.pref_name ?? null,
|
|
341
|
+
maxPhase: raw.max_phase ?? null,
|
|
342
|
+
moleculeType: raw.molecule_type ?? null,
|
|
343
|
+
firstApproval: raw.first_approval ?? null,
|
|
344
|
+
indication,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/** Join the disease terms of one molecule. The MeSH heading is the curated name, thus it comes first. */
|
|
348
|
+
function joinIndicationTerms(rows) {
|
|
317
349
|
const terms = new Set();
|
|
318
|
-
for (const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
terms.add(ind.efo_term);
|
|
350
|
+
for (const row of rows) {
|
|
351
|
+
const term = row.mesh_heading ?? row.efo_term;
|
|
352
|
+
if (term)
|
|
353
|
+
terms.add(term);
|
|
323
354
|
}
|
|
324
355
|
return terms.size > 0 ? [...terms].join("; ") : null;
|
|
325
356
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Read the `drug_indication` resource under one filter.
|
|
359
|
+
*
|
|
360
|
+
* The rows come highest-phase first, thus an approved indication survives the
|
|
361
|
+
* `limit` of a disease that hundreds of molecules carry.
|
|
362
|
+
*/
|
|
363
|
+
async function fetchDrugIndications(filter, limit) {
|
|
364
|
+
const url = `${CHEMBL_BASE}/drug_indication.json?${filter}&order_by=-max_phase_for_ind&limit=${limit}`;
|
|
365
|
+
const res = await apiFetchValidated(url, DrugIndicationsResponseSchema, { headers: HEADERS });
|
|
366
|
+
if (res.isErr()) {
|
|
367
|
+
if (res.error.type === "http_status" && res.error.status === 404)
|
|
368
|
+
return [];
|
|
369
|
+
throw new Error(describeApiError(res.error));
|
|
370
|
+
}
|
|
371
|
+
return res.value.drug_indications ?? [];
|
|
372
|
+
}
|
|
373
|
+
/** Read one batch of molecule records by id. */
|
|
374
|
+
async function fetchMoleculeSet(moleculeChemblIds) {
|
|
375
|
+
const res = await apiFetchValidated(`${CHEMBL_BASE}/molecule/set/${moleculeChemblIds.join(";")}.json`, MoleculeSearchResponseSchema, {
|
|
376
|
+
headers: HEADERS,
|
|
377
|
+
});
|
|
378
|
+
if (res.isErr()) {
|
|
379
|
+
if (res.error.type === "http_status" && res.error.status === 404)
|
|
380
|
+
return [];
|
|
381
|
+
throw new Error(describeApiError(res.error));
|
|
382
|
+
}
|
|
383
|
+
return res.value.molecules ?? [];
|
|
384
|
+
}
|
|
385
|
+
/** Collect the indication rows of each molecule, up to `maxMolecules` molecules. */
|
|
386
|
+
function groupIndicationsByMolecule(rows, maxMolecules) {
|
|
387
|
+
const byMolecule = new Map();
|
|
388
|
+
for (const row of rows) {
|
|
389
|
+
const moleculeChemblId = row.molecule_chembl_id;
|
|
390
|
+
if (!moleculeChemblId)
|
|
391
|
+
continue;
|
|
392
|
+
const collected = byMolecule.get(moleculeChemblId);
|
|
393
|
+
if (collected)
|
|
394
|
+
collected.push(row);
|
|
395
|
+
else if (byMolecule.size < maxMolecules)
|
|
396
|
+
byMolecule.set(moleculeChemblId, [row]);
|
|
338
397
|
}
|
|
339
|
-
|
|
340
|
-
|
|
398
|
+
return byMolecule;
|
|
399
|
+
}
|
|
400
|
+
/** The drugs that ChEMBL indexes under a disease term, with the terms that matched. */
|
|
401
|
+
async function searchDrugsByIndication(query, limit) {
|
|
402
|
+
const encoded = encodeURIComponent(query);
|
|
403
|
+
// The two filters cover the two vocabularies of the resource. EFO comes first,
|
|
404
|
+
// because it carries the finer term, and a MeSH-only query still reaches the
|
|
405
|
+
// second read.
|
|
406
|
+
let rows = await fetchDrugIndications(`efo_term__icontains=${encoded}`, limit);
|
|
407
|
+
if (rows.length === 0)
|
|
408
|
+
rows = await fetchDrugIndications(`mesh_heading__icontains=${encoded}`, limit);
|
|
409
|
+
const byMolecule = groupIndicationsByMolecule(rows, limit);
|
|
410
|
+
if (byMolecule.size === 0)
|
|
411
|
+
return [];
|
|
412
|
+
const molecules = await fetchMoleculeSet([...byMolecule.keys()]);
|
|
413
|
+
return molecules.map((raw) => mapDrug(raw, joinIndicationTerms(byMolecule.get(raw.molecule_chembl_id ?? "") ?? [])));
|
|
414
|
+
}
|
|
415
|
+
/** The indication terms of each named molecule, keyed by the molecule id. */
|
|
416
|
+
async function fetchIndicationsOf(moleculeChemblIds) {
|
|
417
|
+
if (moleculeChemblIds.length === 0)
|
|
418
|
+
return new Map();
|
|
419
|
+
// A molecule carries one row for each disease term, thus the cap scales with the
|
|
420
|
+
// number of molecules that the caller resolved.
|
|
421
|
+
const rows = await fetchDrugIndications(`molecule_chembl_id__in=${moleculeChemblIds.join(",")}`, moleculeChemblIds.length * INDICATIONS_PER_DRUG);
|
|
422
|
+
const terms = new Map();
|
|
423
|
+
for (const [moleculeChemblId, collected] of groupIndicationsByMolecule(rows, moleculeChemblIds.length)) {
|
|
424
|
+
const joined = joinIndicationTerms(collected);
|
|
425
|
+
if (joined)
|
|
426
|
+
terms.set(moleculeChemblId, joined);
|
|
341
427
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
428
|
+
return terms;
|
|
429
|
+
}
|
|
430
|
+
/** The approved molecules whose name matches the query. */
|
|
431
|
+
async function searchApprovedDrugsByName(query, limit) {
|
|
432
|
+
const url = `${CHEMBL_BASE}/molecule/search.json?q=${encodeURIComponent(query)}&limit=${limit}`;
|
|
433
|
+
const res = await apiFetchValidated(url, MoleculeSearchResponseSchema, { headers: HEADERS });
|
|
434
|
+
if (res.isErr()) {
|
|
435
|
+
if (res.error.type === "http_status" && res.error.status === 404)
|
|
346
436
|
return [];
|
|
347
|
-
throw new Error(describeApiError(
|
|
437
|
+
throw new Error(describeApiError(res.error));
|
|
348
438
|
}
|
|
349
|
-
const approved = (
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
439
|
+
const approved = (res.value.molecules ?? []).filter((mol) => mol.max_phase != null && mol.max_phase >= APPROVED_MAX_PHASE);
|
|
440
|
+
if (approved.length === 0)
|
|
441
|
+
return [];
|
|
442
|
+
const moleculeChemblIds = approved.map((mol) => mol.molecule_chembl_id).filter((id) => id !== undefined && id.length > 0);
|
|
443
|
+
const indications = await fetchIndicationsOf(moleculeChemblIds);
|
|
444
|
+
return approved.map((raw) => mapDrug(raw, indications.get(raw.molecule_chembl_id ?? "") ?? null));
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Approved drugs by name or by disease term.
|
|
448
|
+
*
|
|
449
|
+
* The `drug` resource of ChEMBL has no search endpoint, and it carries neither a
|
|
450
|
+
* name nor an indication. The molecule name search runs first, because an
|
|
451
|
+
* `icontains` read of `drug_indication` captures a drug name that is a substring
|
|
452
|
+
* of a disease term ("insulin" matches "insulin resistance"). A query that
|
|
453
|
+
* names no molecule falls through to the indication read, which answers
|
|
454
|
+
* highest-phase first and can carry a clinical-stage molecule below approval.
|
|
455
|
+
*/
|
|
456
|
+
export async function getDrugInfo(query, limit = 25) {
|
|
457
|
+
const byName = await searchApprovedDrugsByName(query, limit);
|
|
458
|
+
if (byName.length > 0)
|
|
459
|
+
return byName;
|
|
460
|
+
return searchDrugsByIndication(query, limit);
|
|
358
461
|
}
|
|
359
462
|
/**
|
|
360
463
|
* Fetch all clinical-stage modulators (max_phase ≥ 2 by default) for a target.
|
|
361
464
|
* Used for §2.6.6 (class precedent) and as the input to §2.6.4 (off-target panel).
|
|
362
465
|
*/
|
|
363
|
-
export async function getTargetModulators(targetChemblId, options = {}) {
|
|
466
|
+
export async function getTargetModulators(targetChemblId, options = {}, logger) {
|
|
467
|
+
const log = (logger ?? createNoopLogger()).named("chembl-client.target-modulators").with({ targetChemblId });
|
|
364
468
|
const minPhase = options.minPhase ?? 2;
|
|
365
469
|
const limit = options.limit ?? 200;
|
|
366
470
|
const mechUrl = `${CHEMBL_BASE}/mechanism.json?target_chembl_id=${encodeURIComponent(targetChemblId)}` + `&limit=${limit}`;
|
|
@@ -386,10 +490,12 @@ export async function getTargetModulators(targetChemblId, options = {}) {
|
|
|
386
490
|
const res = await apiFetchValidated(`${CHEMBL_BASE}/molecule/${id}.json`, RawChemblMoleculeSchema, {
|
|
387
491
|
headers: HEADERS,
|
|
388
492
|
});
|
|
389
|
-
if (res.isErr())
|
|
493
|
+
if (res.isErr()) {
|
|
494
|
+
reportSkippedMolecule(log, res.error, id);
|
|
390
495
|
return null;
|
|
496
|
+
}
|
|
391
497
|
const m = res.value;
|
|
392
|
-
const phase = m.max_phase
|
|
498
|
+
const phase = m.max_phase ?? null;
|
|
393
499
|
if (phase == null || phase < minPhase)
|
|
394
500
|
return null;
|
|
395
501
|
return {
|
|
@@ -429,7 +535,7 @@ export async function getModulatorPolypharmacology(moleculeChemblId, options = {
|
|
|
429
535
|
continue;
|
|
430
536
|
if (options.excludeTargetChemblId && tid === options.excludeTargetChemblId)
|
|
431
537
|
continue;
|
|
432
|
-
const pchembl =
|
|
538
|
+
const pchembl = raw.pchembl_value ?? null;
|
|
433
539
|
const existing = byTarget.get(tid);
|
|
434
540
|
if (!existing || (pchembl != null && (existing.pchemblValue == null || pchembl > existing.pchemblValue))) {
|
|
435
541
|
byTarget.set(tid, {
|
|
@@ -438,7 +544,7 @@ export async function getModulatorPolypharmacology(moleculeChemblId, options = {
|
|
|
438
544
|
targetName: null,
|
|
439
545
|
assayChemblId: raw.assay_chembl_id ?? null,
|
|
440
546
|
standardType: raw.standard_type ?? null,
|
|
441
|
-
standardValue:
|
|
547
|
+
standardValue: raw.standard_value ?? null,
|
|
442
548
|
standardUnits: raw.standard_units ?? null,
|
|
443
549
|
pchemblValue: pchembl,
|
|
444
550
|
});
|
|
@@ -466,14 +572,17 @@ export async function getModulatorPolypharmacology(moleculeChemblId, options = {
|
|
|
466
572
|
* Used by the polypharm fanout step so the assembler's computeSelectivity
|
|
467
573
|
* can produce real fold values instead of selectivity_unknown.
|
|
468
574
|
*/
|
|
469
|
-
export async function getModulatorOnTargetPchembl(modulatorChemblId, targetChemblId) {
|
|
575
|
+
export async function getModulatorOnTargetPchembl(modulatorChemblId, targetChemblId, logger) {
|
|
576
|
+
const log = (logger ?? createNoopLogger()).named("chembl-client.on-target-pchembl").with({ targetChemblId });
|
|
470
577
|
const url = `${CHEMBL_BASE}/activity.json?molecule_chembl_id=${encodeURIComponent(modulatorChemblId)}` +
|
|
471
578
|
`&target_chembl_id=${encodeURIComponent(targetChemblId)}` +
|
|
472
579
|
`&pchembl_value__isnull=false&limit=10`;
|
|
473
580
|
const res = await apiFetchValidated(url, ActivitiesResponseSchema, { headers: HEADERS });
|
|
474
|
-
if (res.isErr())
|
|
581
|
+
if (res.isErr()) {
|
|
582
|
+
reportSkippedMolecule(log, res.error, modulatorChemblId);
|
|
475
583
|
return null;
|
|
476
|
-
|
|
584
|
+
}
|
|
585
|
+
const values = (res.value.activities ?? []).map((a) => a.pchembl_value).filter((v) => v != null);
|
|
477
586
|
if (values.length === 0)
|
|
478
587
|
return null;
|
|
479
588
|
values.sort((a, b) => a - b);
|
|
@@ -493,8 +602,9 @@ export async function getModulatorOnTargetPchembl(modulatorChemblId, targetChemb
|
|
|
493
602
|
* Returns one `ChemblModulator` per unique molecule that passes the
|
|
494
603
|
* min-phase and molecule-type filters.
|
|
495
604
|
*/
|
|
496
|
-
export async function getModulatorsViaActivity(args) {
|
|
605
|
+
export async function getModulatorsViaActivity(args, logger) {
|
|
497
606
|
const { target_chembl_id, max_phase_min = 2, molecule_type, limit = 50 } = args;
|
|
607
|
+
const log = (logger ?? createNoopLogger()).named("chembl-client.modulators-via-activity").with({ targetChemblId: target_chembl_id });
|
|
498
608
|
let url = `${CHEMBL_BASE}/activity.json?target_chembl_id=${encodeURIComponent(target_chembl_id)}` + `&max_phase__gte=${max_phase_min}&limit=${limit}`;
|
|
499
609
|
if (molecule_type) {
|
|
500
610
|
url += `&molecule_type=${encodeURIComponent(molecule_type)}`;
|
|
@@ -522,10 +632,12 @@ export async function getModulatorsViaActivity(args) {
|
|
|
522
632
|
const molRes = await apiFetchValidated(`${CHEMBL_BASE}/molecule/${id}.json`, RawChemblMoleculeSchema, {
|
|
523
633
|
headers: HEADERS,
|
|
524
634
|
});
|
|
525
|
-
if (molRes.isErr())
|
|
635
|
+
if (molRes.isErr()) {
|
|
636
|
+
reportSkippedMolecule(log, molRes.error, id);
|
|
526
637
|
return null;
|
|
638
|
+
}
|
|
527
639
|
const m = molRes.value;
|
|
528
|
-
const phase = m.max_phase
|
|
640
|
+
const phase = m.max_phase ?? null;
|
|
529
641
|
if (phase == null || phase < max_phase_min)
|
|
530
642
|
return null;
|
|
531
643
|
const molType = m.molecule_type ?? null;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absence policy: this file calls no external API, thus no wire encodes an
|
|
3
|
+
* absent value. The bundled dataset gives each key, and `getBenchmarks` answers
|
|
4
|
+
* with the "all" baseline for an unknown therapeutic area.
|
|
5
|
+
*/
|
|
1
6
|
export interface PhaseTransitions {
|
|
2
7
|
phase1_to_phase2: number;
|
|
3
8
|
phase2_to_phase3: number;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absence policy: this file calls no external API, thus no wire encodes an
|
|
3
|
+
* absent value. The bundled dataset gives each key, and `getBenchmarks` answers
|
|
4
|
+
* with the "all" baseline for an unknown therapeutic area.
|
|
5
|
+
*/
|
|
1
6
|
import benchmarksData from "../../data/clinical-benchmarks.json" with { type: "json" };
|
|
2
7
|
const dataset = benchmarksData;
|
|
3
8
|
if (!dataset.phase_transitions || !dataset.phase_transitions.all) {
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Used by target-assessment workflow steps for §2.5 (clinical record),
|
|
5
5
|
* §2.6.3 (trial AEs), §2.5 (failed trials), and §6.5 (discovery trials).
|
|
6
|
+
*
|
|
7
|
+
* Absence policy: the API omits the key of an absent value, and its OpenAPI
|
|
8
|
+
* document marks nothing as nullable. Thus a maybe-absent field carries
|
|
9
|
+
* `.optional()`, not `.nullable()`.
|
|
6
10
|
*/
|
|
7
11
|
import { z } from "zod";
|
|
8
12
|
export interface ClinicalTrial {
|
|
@@ -70,12 +74,18 @@ export interface TrialDetails {
|
|
|
70
74
|
adverseEventGroups: AdverseEventGroup[];
|
|
71
75
|
adverseEvents: AdverseEvent[];
|
|
72
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* One phase token of `protocolSection.designModule.phases`. `NA` marks a study
|
|
79
|
+
* that runs under no phase, such as an observational study.
|
|
80
|
+
*/
|
|
81
|
+
export type TrialPhase = "EARLY_PHASE1" | "PHASE1" | "PHASE2" | "PHASE3" | "PHASE4" | "NA";
|
|
73
82
|
export interface SearchOptions {
|
|
74
|
-
phase?:
|
|
83
|
+
phase?: TrialPhase;
|
|
75
84
|
status?: "RECRUITING" | "ACTIVE_NOT_RECRUITING" | "COMPLETED" | "NOT_YET_RECRUITING" | "TERMINATED" | "WITHDRAWN" | "SUSPENDED";
|
|
76
85
|
limit?: number;
|
|
77
86
|
}
|
|
78
|
-
|
|
87
|
+
/** One study record. It is exported so that the golden-fixture table drives it. */
|
|
88
|
+
export declare const CtStudySchema: z.ZodObject<{
|
|
79
89
|
protocolSection: z.ZodOptional<z.ZodObject<{
|
|
80
90
|
identificationModule: z.ZodOptional<z.ZodObject<{
|
|
81
91
|
nctId: z.ZodOptional<z.ZodString>;
|
|
@@ -184,7 +194,126 @@ declare const CtStudySchema: z.ZodObject<{
|
|
|
184
194
|
}, z.core.$strip>>;
|
|
185
195
|
}, z.core.$strip>;
|
|
186
196
|
type CtStudy = z.infer<typeof CtStudySchema>;
|
|
197
|
+
/** The search envelope. It is exported so that the golden-fixture table drives it. */
|
|
198
|
+
export declare const CtSearchResponseSchema: z.ZodObject<{
|
|
199
|
+
studies: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
200
|
+
protocolSection: z.ZodOptional<z.ZodObject<{
|
|
201
|
+
identificationModule: z.ZodOptional<z.ZodObject<{
|
|
202
|
+
nctId: z.ZodOptional<z.ZodString>;
|
|
203
|
+
briefTitle: z.ZodOptional<z.ZodString>;
|
|
204
|
+
officialTitle: z.ZodOptional<z.ZodString>;
|
|
205
|
+
}, z.core.$strip>>;
|
|
206
|
+
statusModule: z.ZodOptional<z.ZodObject<{
|
|
207
|
+
overallStatus: z.ZodOptional<z.ZodString>;
|
|
208
|
+
whyStopped: z.ZodOptional<z.ZodString>;
|
|
209
|
+
startDateStruct: z.ZodOptional<z.ZodObject<{
|
|
210
|
+
date: z.ZodOptional<z.ZodString>;
|
|
211
|
+
}, z.core.$strip>>;
|
|
212
|
+
primaryCompletionDateStruct: z.ZodOptional<z.ZodObject<{
|
|
213
|
+
date: z.ZodOptional<z.ZodString>;
|
|
214
|
+
}, z.core.$strip>>;
|
|
215
|
+
}, z.core.$strip>>;
|
|
216
|
+
designModule: z.ZodOptional<z.ZodObject<{
|
|
217
|
+
phases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
218
|
+
studyType: z.ZodOptional<z.ZodString>;
|
|
219
|
+
designInfo: z.ZodOptional<z.ZodObject<{
|
|
220
|
+
primaryPurpose: z.ZodOptional<z.ZodString>;
|
|
221
|
+
}, z.core.$strip>>;
|
|
222
|
+
enrollmentInfo: z.ZodOptional<z.ZodObject<{
|
|
223
|
+
count: z.ZodOptional<z.ZodNumber>;
|
|
224
|
+
}, z.core.$strip>>;
|
|
225
|
+
}, z.core.$strip>>;
|
|
226
|
+
descriptionModule: z.ZodOptional<z.ZodObject<{
|
|
227
|
+
briefSummary: z.ZodOptional<z.ZodString>;
|
|
228
|
+
detailedDescription: z.ZodOptional<z.ZodString>;
|
|
229
|
+
}, z.core.$strip>>;
|
|
230
|
+
conditionsModule: z.ZodOptional<z.ZodObject<{
|
|
231
|
+
conditions: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
232
|
+
}, z.core.$strip>>;
|
|
233
|
+
armsInterventionsModule: z.ZodOptional<z.ZodObject<{
|
|
234
|
+
interventions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
235
|
+
name: z.ZodOptional<z.ZodString>;
|
|
236
|
+
type: z.ZodOptional<z.ZodString>;
|
|
237
|
+
description: z.ZodOptional<z.ZodString>;
|
|
238
|
+
otherNames: z.ZodOptional<z.ZodUnknown>;
|
|
239
|
+
}, z.core.$strip>>>;
|
|
240
|
+
}, z.core.$strip>>;
|
|
241
|
+
sponsorCollaboratorsModule: z.ZodOptional<z.ZodObject<{
|
|
242
|
+
leadSponsor: z.ZodOptional<z.ZodObject<{
|
|
243
|
+
name: z.ZodOptional<z.ZodString>;
|
|
244
|
+
}, z.core.$strip>>;
|
|
245
|
+
}, z.core.$strip>>;
|
|
246
|
+
outcomesModule: z.ZodOptional<z.ZodObject<{
|
|
247
|
+
primaryOutcomes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
248
|
+
measure: z.ZodOptional<z.ZodString>;
|
|
249
|
+
description: z.ZodOptional<z.ZodString>;
|
|
250
|
+
timeFrame: z.ZodOptional<z.ZodString>;
|
|
251
|
+
}, z.core.$strip>>>;
|
|
252
|
+
secondaryOutcomes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
253
|
+
measure: z.ZodOptional<z.ZodString>;
|
|
254
|
+
description: z.ZodOptional<z.ZodString>;
|
|
255
|
+
timeFrame: z.ZodOptional<z.ZodString>;
|
|
256
|
+
}, z.core.$strip>>>;
|
|
257
|
+
otherOutcomes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
258
|
+
measure: z.ZodOptional<z.ZodString>;
|
|
259
|
+
description: z.ZodOptional<z.ZodString>;
|
|
260
|
+
timeFrame: z.ZodOptional<z.ZodString>;
|
|
261
|
+
}, z.core.$strip>>>;
|
|
262
|
+
}, z.core.$strip>>;
|
|
263
|
+
}, z.core.$strip>>;
|
|
264
|
+
resultsSection: z.ZodOptional<z.ZodObject<{
|
|
265
|
+
outcomeMeasuresModule: z.ZodOptional<z.ZodObject<{
|
|
266
|
+
outcomeMeasures: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
267
|
+
title: z.ZodOptional<z.ZodString>;
|
|
268
|
+
unitOfMeasure: z.ZodOptional<z.ZodString>;
|
|
269
|
+
classes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
270
|
+
categories: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
271
|
+
measurements: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
272
|
+
value: z.ZodOptional<z.ZodString>;
|
|
273
|
+
lowerLimit: z.ZodOptional<z.ZodString>;
|
|
274
|
+
upperLimit: z.ZodOptional<z.ZodString>;
|
|
275
|
+
}, z.core.$strip>>>;
|
|
276
|
+
}, z.core.$strip>>>;
|
|
277
|
+
}, z.core.$strip>>>;
|
|
278
|
+
}, z.core.$strip>>>;
|
|
279
|
+
}, z.core.$strip>>;
|
|
280
|
+
adverseEventsModule: z.ZodOptional<z.ZodObject<{
|
|
281
|
+
eventGroups: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
282
|
+
id: z.ZodOptional<z.ZodString>;
|
|
283
|
+
title: z.ZodOptional<z.ZodString>;
|
|
284
|
+
description: z.ZodOptional<z.ZodString>;
|
|
285
|
+
}, z.core.$strip>>>;
|
|
286
|
+
seriousEvents: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
287
|
+
term: z.ZodOptional<z.ZodString>;
|
|
288
|
+
organSystem: z.ZodOptional<z.ZodString>;
|
|
289
|
+
stats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
290
|
+
groupId: z.ZodOptional<z.ZodString>;
|
|
291
|
+
numAffected: z.ZodOptional<z.ZodNumber>;
|
|
292
|
+
numAtRisk: z.ZodOptional<z.ZodNumber>;
|
|
293
|
+
}, z.core.$strip>>>;
|
|
294
|
+
}, z.core.$strip>>>;
|
|
295
|
+
otherEvents: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
296
|
+
term: z.ZodOptional<z.ZodString>;
|
|
297
|
+
organSystem: z.ZodOptional<z.ZodString>;
|
|
298
|
+
stats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
299
|
+
groupId: z.ZodOptional<z.ZodString>;
|
|
300
|
+
numAffected: z.ZodOptional<z.ZodNumber>;
|
|
301
|
+
numAtRisk: z.ZodOptional<z.ZodNumber>;
|
|
302
|
+
}, z.core.$strip>>>;
|
|
303
|
+
}, z.core.$strip>>>;
|
|
304
|
+
}, z.core.$strip>>;
|
|
305
|
+
}, z.core.$strip>>;
|
|
306
|
+
}, z.core.$strip>>>;
|
|
307
|
+
totalCount: z.ZodOptional<z.ZodNumber>;
|
|
308
|
+
}, z.core.$strip>;
|
|
187
309
|
export declare function mapClinicalTrialStudy(s: CtStudy): ClinicalTrial;
|
|
310
|
+
/**
|
|
311
|
+
* Search the registry.
|
|
312
|
+
*
|
|
313
|
+
* `totalFound` is the count of the whole query, which `countTotal=true` asks
|
|
314
|
+
* for. A phase filter runs over the returned page only, thus `totalFound` can
|
|
315
|
+
* exceed the number of trials that the answer carries.
|
|
316
|
+
*/
|
|
188
317
|
export declare function searchTrials(query: string, options?: SearchOptions): Promise<{
|
|
189
318
|
totalFound: number;
|
|
190
319
|
trials: ClinicalTrial[];
|