@inflexa-ai/harness 0.1.1 → 0.2.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/dist/agents/conversation-agent.d.ts +12 -11
- package/dist/agents/conversation-agent.js +30 -33
- package/dist/agents/report-builder.js +2 -1
- package/dist/agents/sandbox/bulk-transcriptomics-agent.js +1 -1
- package/dist/agents/sandbox/cheminformatics-agent.js +1 -19
- package/dist/agents/sandbox/chromatin-agent.js +1 -10
- package/dist/agents/sandbox/dna-methylation-agent.js +1 -10
- package/dist/agents/sandbox/drug-repurposing-agent.js +4 -14
- package/dist/agents/sandbox/enrichment-agent.js +1 -10
- package/dist/agents/sandbox/ephemeral-executor.js +1 -10
- package/dist/agents/sandbox/genomic-variant-agent.js +1 -3
- package/dist/agents/sandbox/immune-profiling-agent.js +1 -13
- package/dist/agents/sandbox/metabolomics-agent.js +1 -1
- package/dist/agents/sandbox/microbiome-agent.js +1 -1
- package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -12
- package/dist/agents/sandbox/multimodal-sc-agent.js +1 -1
- package/dist/agents/sandbox/network-agent.js +1 -10
- package/dist/agents/sandbox/pkpd-clinical-response-agent.js +2 -8
- package/dist/agents/sandbox/proteomics-agent.js +1 -1
- package/dist/agents/sandbox/scientific-executor.js +1 -10
- package/dist/agents/sandbox/shared.d.ts +17 -6
- package/dist/agents/sandbox/shared.js +49 -38
- package/dist/agents/sandbox/single-cell-agent.js +1 -1
- package/dist/agents/sandbox/spatial-omics-agent.js +1 -1
- package/dist/agents/sandbox/statistical-modeling-agent.js +1 -1
- package/dist/agents/sandbox/translational-safety-agent.js +3 -9
- package/dist/agents/sandbox/types.d.ts +1 -1
- package/dist/agents/system-prompt.d.ts +37 -11
- package/dist/agents/system-prompt.js +35 -12
- package/dist/app/data-profile-orientation.d.ts +34 -0
- package/dist/app/data-profile-orientation.js +117 -0
- package/dist/contracts/schemas/chat-parts.d.ts +9 -9
- package/dist/execution/artifact-metadata.js +2 -1
- package/dist/execution/ephemeral-runner.d.ts +13 -0
- package/dist/execution/ephemeral-runner.js +23 -1
- package/dist/execution/run-synthesis.js +20 -19
- package/dist/execution/step-summary.js +2 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +19 -5
- package/dist/loop/__fixtures__/scripted-provider.d.ts +2 -2
- package/dist/loop/__fixtures__/scripted-provider.js +2 -1
- package/dist/loop/metrics.d.ts +33 -1
- package/dist/loop/metrics.js +56 -2
- package/dist/loop/run-agent.d.ts +13 -1
- package/dist/loop/run-agent.js +19 -4
- package/dist/memory/card-builders.d.ts +8 -1
- package/dist/memory/card-builders.js +20 -3
- package/dist/memory/reconstruct-cards.js +4 -3
- package/dist/memory/thread-history.d.ts +27 -2
- package/dist/memory/thread-history.js +45 -5
- package/dist/memory/working-memory.d.ts +93 -10
- package/dist/memory/working-memory.js +220 -84
- package/dist/prompts/SOUL.d.ts +22 -9
- package/dist/prompts/SOUL.js +56 -37
- package/dist/prompts/analogical-reasoner.d.ts +1 -1
- package/dist/prompts/analogical-reasoner.js +2 -2
- package/dist/prompts/briefing.d.ts +100 -0
- package/dist/prompts/briefing.js +166 -0
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +94 -440
- package/dist/prompts/literature-reviewer.d.ts +1 -1
- package/dist/prompts/literature-reviewer.js +10 -10
- package/dist/prompts/planner.js +28 -59
- package/dist/prompts/report-builder.d.ts +1 -1
- package/dist/prompts/report-builder.js +71 -24
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +2 -7
- package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/cheminformatics-agent.js +11 -15
- package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
- package/dist/prompts/sandbox/chromatin-agent.js +3 -7
- package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
- package/dist/prompts/sandbox/data-profiler.js +13 -8
- package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
- package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
- package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
- package/dist/prompts/sandbox/drug-repurposing-agent.js +15 -16
- package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
- package/dist/prompts/sandbox/enrichment-agent.js +2 -9
- package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
- package/dist/prompts/sandbox/genomic-variant-agent.js +2 -6
- package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/immune-profiling-agent.js +3 -8
- package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/metabolomics-agent.js +1 -7
- package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
- package/dist/prompts/sandbox/microbiome-agent.js +2 -7
- package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multi-omics-integration-agent.js +2 -8
- package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
- package/dist/prompts/sandbox/multimodal-sc-agent.js +2 -7
- package/dist/prompts/sandbox/network-agent.d.ts +1 -1
- package/dist/prompts/sandbox/network-agent.js +2 -7
- package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/proteomics-agent.js +2 -9
- package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
- package/dist/prompts/sandbox/single-cell-agent.js +2 -9
- package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
- package/dist/prompts/sandbox/spatial-omics-agent.js +3 -6
- package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
- package/dist/prompts/sandbox/statistical-modeling-agent.js +2 -7
- package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
- package/dist/prompts/sandbox/translational-safety-agent.js +13 -14
- package/dist/prompts/sandbox-standards.d.ts +22 -14
- package/dist/prompts/sandbox-standards.js +91 -71
- package/dist/prompts/synthesis-agent.d.ts +1 -1
- package/dist/prompts/synthesis-agent.js +33 -131
- package/dist/provenance/collector.js +1 -1
- package/dist/providers/ai-sdk.js +31 -4
- package/dist/providers/prompt-cache.d.ts +74 -0
- package/dist/providers/prompt-cache.js +77 -0
- package/dist/providers/types.d.ts +33 -0
- package/dist/reference-data/catalog.d.ts +131 -0
- package/dist/reference-data/catalog.js +348 -0
- package/dist/reference-data/receipt.d.ts +41 -0
- package/dist/reference-data/receipt.js +40 -0
- package/dist/schemas/data-profile-schemas.js +5 -3
- package/dist/schemas/plan-schemas.d.ts +21 -21
- package/dist/schemas/plan-schemas.js +9 -3
- package/dist/schemas/run-synthesis.d.ts +8 -4
- package/dist/schemas/run-synthesis.js +92 -56
- package/dist/schemas/validate-plan.js +1 -1
- package/dist/schemas/workflow-state.d.ts +7 -1
- package/dist/schemas/workflow-state.js +33 -19
- package/dist/state/artifacts.d.ts +13 -0
- package/dist/state/artifacts.js +27 -0
- package/dist/state/data-profile.d.ts +64 -5
- package/dist/state/index.d.ts +3 -3
- package/dist/state/index.js +1 -1
- package/dist/tasks/data-profile.d.ts +20 -2
- package/dist/tasks/data-profile.js +71 -22
- package/dist/tools/bio/chembl.d.ts +38 -0
- package/dist/tools/bio/chembl.js +138 -0
- package/dist/tools/bio/comptox.d.ts +50 -0
- package/dist/tools/bio/comptox.js +149 -0
- package/dist/tools/bio/get-impc-ko-profile.js +9 -6
- package/dist/tools/bio/index.d.ts +5 -17
- package/dist/tools/bio/index.js +5 -17
- package/dist/tools/bio/keys.d.ts +14 -9
- package/dist/tools/bio/keys.js +16 -16
- package/dist/tools/bio/opentargets.d.ts +38 -0
- package/dist/tools/bio/opentargets.js +89 -0
- package/dist/tools/bio/pubchem.d.ts +29 -0
- package/dist/tools/bio/pubchem.js +92 -0
- package/dist/tools/bio/pubmed.d.ts +51 -0
- package/dist/tools/bio/pubmed.js +100 -0
- package/dist/tools/bio/search-bgee-expression.js +12 -6
- package/dist/tools/bio/search-clinical-trials.js +16 -5
- package/dist/tools/bio/search-faers.js +8 -4
- package/dist/tools/bio/search-geo-datasets.js +20 -6
- package/dist/tools/bio/search-gwas-catalog.d.ts +1 -1
- package/dist/tools/bio/search-pharmgkb.js +10 -4
- package/dist/tools/display/index.d.ts +1 -0
- package/dist/tools/display/index.js +1 -0
- package/dist/tools/display/normalize-echart-spec.d.ts +61 -0
- package/dist/tools/display/normalize-echart-spec.js +211 -0
- package/dist/tools/display/show-user.d.ts +6 -1
- package/dist/tools/display/show-user.js +25 -12
- package/dist/tools/execute-plan.js +5 -2
- package/dist/tools/iterate-report.d.ts +168 -118
- package/dist/tools/iterate-report.js +334 -96
- package/dist/tools/lib/chembl-client.d.ts +12 -0
- package/dist/tools/lib/chembl-client.js +127 -0
- package/dist/tools/lib/ctx-ops.d.ts +224 -0
- package/dist/tools/lib/ctx-ops.js +450 -0
- package/dist/tools/lib/iuphar-client.js +10 -1
- package/dist/tools/lib/openfda-client.js +11 -3
- package/dist/tools/lib/pathway-client.js +10 -4
- package/dist/tools/lib/pubchem-ops.d.ts +74 -0
- package/dist/tools/lib/pubchem-ops.js +224 -0
- package/dist/tools/memory/update-working-memory.d.ts +45 -7
- package/dist/tools/memory/update-working-memory.js +111 -41
- package/dist/tools/report/index.d.ts +3 -2
- package/dist/tools/report/index.js +3 -2
- package/dist/tools/research/generate-analogy-report.js +25 -12
- package/dist/tools/research/generate-plan.d.ts +12 -5
- package/dist/tools/research/generate-plan.js +168 -37
- package/dist/tools/research/index.d.ts +1 -0
- package/dist/tools/research/index.js +1 -0
- package/dist/tools/research/inspect-data-profile.d.ts +85 -0
- package/dist/tools/research/inspect-data-profile.js +161 -0
- package/dist/tools/research/literature-reviewer.js +3 -4
- package/dist/tools/sandbox/list-available-packages.d.ts +79 -5
- package/dist/tools/sandbox/list-available-packages.js +152 -10
- package/dist/tools/sandbox/list-available-refs.d.ts +141 -5
- package/dist/tools/sandbox/list-available-refs.js +647 -134
- package/dist/tools/sandbox/report-blocker.d.ts +39 -9
- package/dist/tools/sandbox/report-blocker.js +43 -20
- package/dist/tools/workspace/read-file.js +4 -5
- package/dist/tools/workspace/run-exec.d.ts +4 -4
- package/dist/tools/workspace/run-exec.js +4 -4
- package/dist/tools/workspace/show-file.js +10 -6
- package/dist/tools/workspace/show-plan.js +12 -6
- package/dist/tools/workspace/workspace-search.d.ts +1 -1
- package/dist/tools/workspace/workspace-search.js +11 -5
- package/dist/workflows/execute-analysis.d.ts +43 -4
- package/dist/workflows/execute-analysis.js +107 -6
- package/dist/workflows/target-assessment/lib/citation-validator.js +2 -2
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +2 -1
- package/dist/workspace/paths.d.ts +2 -2
- package/dist/workspace/paths.js +2 -2
- package/package.json +33 -33
- package/dist/execution/filesystem-artifact-registry.d.ts +0 -57
- package/dist/execution/filesystem-artifact-registry.js +0 -92
- package/dist/lib/identifiers.d.ts +0 -8
- package/dist/lib/identifiers.js +0 -10
- package/dist/lib/mime-detect.d.ts +0 -17
- package/dist/lib/mime-detect.js +0 -29
- package/dist/lib/streamed-download.d.ts +0 -28
- package/dist/lib/streamed-download.js +0 -111
- package/dist/lib/trace-context.d.ts +0 -11
- package/dist/lib/trace-context.js +0 -49
- package/dist/providers/llm-capabilities.d.ts +0 -33
- package/dist/providers/llm-capabilities.js +0 -58
- package/dist/schemas/render-step-prompt.d.ts +0 -32
- package/dist/schemas/render-step-prompt.js +0 -76
- package/dist/tools/bio/find-approval-precedent.d.ts +0 -32
- package/dist/tools/bio/find-approval-precedent.js +0 -72
- package/dist/tools/bio/get-article-details.d.ts +0 -11
- package/dist/tools/bio/get-article-details.js +0 -20
- package/dist/tools/bio/get-article-full-text.d.ts +0 -19
- package/dist/tools/bio/get-article-full-text.js +0 -32
- package/dist/tools/bio/get-bioactivity.d.ts +0 -11
- package/dist/tools/bio/get-bioactivity.js +0 -23
- package/dist/tools/bio/get-drug-info.d.ts +0 -9
- package/dist/tools/bio/get-drug-info.js +0 -22
- package/dist/tools/bio/get-mechanism.d.ts +0 -8
- package/dist/tools/bio/get-mechanism.js +0 -18
- package/dist/tools/bio/get-pubchem-assays.d.ts +0 -22
- package/dist/tools/bio/get-pubchem-assays.js +0 -107
- package/dist/tools/bio/get-pubchem-cross-refs.d.ts +0 -15
- package/dist/tools/bio/get-pubchem-cross-refs.js +0 -68
- package/dist/tools/bio/get-target-safety.d.ts +0 -18
- package/dist/tools/bio/get-target-safety.js +0 -26
- package/dist/tools/bio/search-compounds.d.ts +0 -22
- package/dist/tools/bio/search-compounds.js +0 -160
- package/dist/tools/bio/search-ctx-chemical.d.ts +0 -50
- package/dist/tools/bio/search-ctx-chemical.js +0 -130
- package/dist/tools/bio/search-ctx-exposure.d.ts +0 -55
- package/dist/tools/bio/search-ctx-exposure.js +0 -176
- package/dist/tools/bio/search-ctx-hazard.d.ts +0 -111
- package/dist/tools/bio/search-ctx-hazard.js +0 -178
- package/dist/tools/bio/search-opentargets.d.ts +0 -11
- package/dist/tools/bio/search-opentargets.js +0 -25
- package/dist/tools/bio/search-pubchem-compound.d.ts +0 -26
- package/dist/tools/bio/search-pubchem-compound.js +0 -110
- package/dist/tools/bio/search-pubmed.d.ts +0 -17
- package/dist/tools/bio/search-pubmed.js +0 -31
- package/dist/tools/bio/search-targets.d.ts +0 -9
- package/dist/tools/bio/search-targets.js +0 -20
- package/dist/tools/bio/search-toxcast.d.ts +0 -36
- package/dist/tools/bio/search-toxcast.js +0 -141
- package/dist/workflows/register-workflows.d.ts +0 -51
- package/dist/workflows/register-workflows.js +0 -48
- package/dist/workflows/resume-execute-analysis.d.ts +0 -70
- package/dist/workflows/resume-execute-analysis.js +0 -80
- package/dist/workflows/target-assessment/correction-loop.d.ts +0 -45
- package/dist/workflows/target-assessment/correction-loop.js +0 -62
- package/dist/workflows/target-assessment/steps/phase0-resolve.d.ts +0 -71
- package/dist/workflows/target-assessment/steps/phase0-resolve.js +0 -21
- package/dist/workflows/target-assessment/steps/phase1-aggregate.d.ts +0 -1018
- package/dist/workflows/target-assessment/steps/phase1-aggregate.js +0 -25
- package/dist/workspace/mount-strategy.d.ts +0 -50
- package/dist/workspace/mount-strategy.js +0 -67
- package/dist/workspace/provenance-collector.d.ts +0 -34
- package/dist/workspace/provenance-collector.js +0 -16
- package/dist/workspace/workspace-paths.d.ts +0 -26
- package/dist/workspace/workspace-paths.js +0 -31
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EPA CompTox (CTX) operations — the four datasets the CompTox tools expose:
|
|
3
|
+
* ToxCast/Tox21 bioactivity, hazard (ToxValDB/ToxRefDB), chemical identity +
|
|
4
|
+
* physicochemical properties, and exposure.
|
|
5
|
+
*
|
|
6
|
+
* Every dataset keys off a DTXSID, so `resolveDtxsid` is the single entry
|
|
7
|
+
* point they share: a DTXSID is used directly, anything else is resolved by
|
|
8
|
+
* exact match against the chemical-search endpoint. `null` means the query
|
|
9
|
+
* resolved to no chemical — an expected outcome, not an error.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { apiFetchValidated, describeApiError } from "./api-utils.js";
|
|
13
|
+
import { EPA_CCTE_BASE } from "./toxcast-config.js";
|
|
14
|
+
// The chemical-search endpoint returns rows the code reads `dtxsid` from
|
|
15
|
+
// unguarded, so `dtxsid` stays required — a row missing it is a contract break
|
|
16
|
+
// surfaced as `invalid_response`, not a silent `undefined`. The other fields
|
|
17
|
+
// are optional: the API omits absent values.
|
|
18
|
+
const CtxChemicalSearchRowSchema = z.object({
|
|
19
|
+
dtxsid: z.string(),
|
|
20
|
+
preferredName: z.string().nullable().optional(),
|
|
21
|
+
casrn: z.string().nullable().optional(),
|
|
22
|
+
});
|
|
23
|
+
/** Resolve a DTXSID / CASRN / name to a chemical. `null` = no match (valid no-data). */
|
|
24
|
+
export async function resolveDtxsid(query, headers) {
|
|
25
|
+
if (query.startsWith("DTXSID")) {
|
|
26
|
+
return { dtxsid: query, preferredName: query, casrn: null };
|
|
27
|
+
}
|
|
28
|
+
const url = `${EPA_CCTE_BASE}/chemical/search/equal/${encodeURIComponent(query)}`;
|
|
29
|
+
const res = await apiFetchValidated(url, z.array(CtxChemicalSearchRowSchema), { headers });
|
|
30
|
+
if (res.isErr())
|
|
31
|
+
throw new Error(describeApiError(res.error));
|
|
32
|
+
if (!res.value?.length)
|
|
33
|
+
return null;
|
|
34
|
+
const chem = res.value[0];
|
|
35
|
+
return {
|
|
36
|
+
dtxsid: chem.dtxsid,
|
|
37
|
+
preferredName: chem.preferredName ?? query,
|
|
38
|
+
casrn: chem.casrn ?? null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Raw CTX Bioactivity API wire shapes, validated at the fetch boundary. Fields
|
|
42
|
+
// are optional because the API omits absent values.
|
|
43
|
+
const ToxcastMc5ParamSchema = z.object({
|
|
44
|
+
ac50: z.number().optional(),
|
|
45
|
+
acc: z.number().optional(),
|
|
46
|
+
});
|
|
47
|
+
const ToxcastMc6ParamSchema = z.object({
|
|
48
|
+
flag: z.unknown().optional(),
|
|
49
|
+
});
|
|
50
|
+
const ToxcastBioactivityRowSchema = z.object({
|
|
51
|
+
aeid: z.number().optional(),
|
|
52
|
+
hitc: z.number().optional(),
|
|
53
|
+
maxMean: z.number().nullable().optional(),
|
|
54
|
+
modl: z.string().optional(),
|
|
55
|
+
mc5Param: ToxcastMc5ParamSchema.nullable().optional(),
|
|
56
|
+
mc6Param: ToxcastMc6ParamSchema.nullable().optional(),
|
|
57
|
+
});
|
|
58
|
+
const ToxcastAssaySummaryRowSchema = z.object({
|
|
59
|
+
aeid: z.number().optional(),
|
|
60
|
+
aenm: z.string().optional(),
|
|
61
|
+
});
|
|
62
|
+
/**
|
|
63
|
+
* ToxCast/Tox21 assay results for a chemical, sorted by AC50 ascending.
|
|
64
|
+
* `totalAssays` / `activeAssays` / `activeHitRate` always describe the full
|
|
65
|
+
* tested panel, never the returned slice.
|
|
66
|
+
*/
|
|
67
|
+
export async function fetchToxcastBioactivity(dtxsid, headers, opts) {
|
|
68
|
+
const aeidMap = await fetchAssayNames(dtxsid, headers);
|
|
69
|
+
const bioUrl = `${EPA_CCTE_BASE}/bioactivity/data/search/by-dtxsid/${dtxsid}`;
|
|
70
|
+
const bioRes = await apiFetchValidated(bioUrl, z.array(ToxcastBioactivityRowSchema), { headers });
|
|
71
|
+
if (bioRes.isErr())
|
|
72
|
+
throw new Error(describeApiError(bioRes.error));
|
|
73
|
+
const allResults = bioRes.value ?? [];
|
|
74
|
+
const totalAssays = allResults.length;
|
|
75
|
+
const activeAssays = allResults.filter((r) => r.hitc === 1).length;
|
|
76
|
+
const filtered = opts.activeOnly ? allResults.filter((r) => r.hitc === 1) : allResults;
|
|
77
|
+
filtered.sort((a, b) => extractAc50(a) - extractAc50(b));
|
|
78
|
+
const results = filtered.slice(0, opts.limit).map((r) => {
|
|
79
|
+
const aeid = r.aeid ?? 0;
|
|
80
|
+
const mc6 = r.mc6Param ?? {};
|
|
81
|
+
const flags = Array.isArray(mc6.flag) ? mc6.flag : [];
|
|
82
|
+
return {
|
|
83
|
+
aeid,
|
|
84
|
+
assayEndpoint: aeidMap.get(aeid) ?? `aeid:${aeid}`,
|
|
85
|
+
ac50: extractAc50Raw(r),
|
|
86
|
+
hitCall: r.hitc ?? 0,
|
|
87
|
+
maxMean: r.maxMean ?? null,
|
|
88
|
+
model: r.modl ?? "",
|
|
89
|
+
flags,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
totalAssays,
|
|
94
|
+
activeAssays,
|
|
95
|
+
activeHitRate: totalAssays > 0 ? Math.round((activeAssays / totalAssays) * 1000) / 1000 : 0,
|
|
96
|
+
results,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function extractAc50(r) {
|
|
100
|
+
return extractAc50Raw(r) ?? Infinity;
|
|
101
|
+
}
|
|
102
|
+
function extractAc50Raw(r) {
|
|
103
|
+
const mc5 = r.mc5Param;
|
|
104
|
+
if (mc5 && typeof mc5 === "object") {
|
|
105
|
+
if (typeof mc5.ac50 === "number")
|
|
106
|
+
return mc5.ac50;
|
|
107
|
+
if (typeof mc5.acc === "number")
|
|
108
|
+
return mc5.acc;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
async function fetchAssayNames(dtxsid, headers) {
|
|
113
|
+
const map = new Map();
|
|
114
|
+
const url = `${EPA_CCTE_BASE}/bioactivity/data/summary/search/by-dtxsid/${dtxsid}`;
|
|
115
|
+
const res = await apiFetchValidated(url, z.array(ToxcastAssaySummaryRowSchema), { headers });
|
|
116
|
+
if (res.isOk() && Array.isArray(res.value)) {
|
|
117
|
+
for (const s of res.value) {
|
|
118
|
+
if (s.aeid != null && s.aenm) {
|
|
119
|
+
map.set(s.aeid, s.aenm);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return map;
|
|
124
|
+
}
|
|
125
|
+
/* ---------------------------------- hazard ---------------------------------- */
|
|
126
|
+
// Each schema below both validates one raw CTX hazard row and normalizes it
|
|
127
|
+
// into the curated output shape via `.transform`; `z.infer` is that output
|
|
128
|
+
// type. Every wire field is optional (the API omits absent values); the two
|
|
129
|
+
// numeric fields the API sends as string-or-number stay `z.unknown()` so
|
|
130
|
+
// `toNumberOrNull` can coerce them without the schema rejecting the row.
|
|
131
|
+
const ToxValSchema = z
|
|
132
|
+
.object({
|
|
133
|
+
source: z.string().nullable().optional(),
|
|
134
|
+
toxvalType: z.string().nullable().optional(),
|
|
135
|
+
toxvalNumeric: z.unknown().optional(),
|
|
136
|
+
toxvalUnits: z.string().nullable().optional(),
|
|
137
|
+
studyType: z.string().nullable().optional(),
|
|
138
|
+
studyDurationClass: z.string().nullable().optional(),
|
|
139
|
+
speciesCommon: z.string().nullable().optional(),
|
|
140
|
+
exposureRoute: z.string().nullable().optional(),
|
|
141
|
+
toxicologicalEffect: z.string().nullable().optional(),
|
|
142
|
+
riskAssessmentClass: z.string().nullable().optional(),
|
|
143
|
+
humanEco: z.string().nullable().optional(),
|
|
144
|
+
year: z.unknown().optional(),
|
|
145
|
+
quality: z.string().nullable().optional(),
|
|
146
|
+
})
|
|
147
|
+
.transform((r) => ({
|
|
148
|
+
source: r.source ?? "",
|
|
149
|
+
toxvalType: r.toxvalType ?? "",
|
|
150
|
+
toxvalNumeric: toNumberOrNull(r.toxvalNumeric),
|
|
151
|
+
toxvalUnits: r.toxvalUnits ?? "",
|
|
152
|
+
studyType: r.studyType ?? "",
|
|
153
|
+
studyDurationClass: r.studyDurationClass ?? "",
|
|
154
|
+
species: r.speciesCommon ?? "",
|
|
155
|
+
exposureRoute: r.exposureRoute ?? "",
|
|
156
|
+
toxicologicalEffect: r.toxicologicalEffect ?? "",
|
|
157
|
+
riskAssessmentClass: r.riskAssessmentClass ?? "",
|
|
158
|
+
humanEco: r.humanEco ?? "",
|
|
159
|
+
year: toNumberOrNull(r.year),
|
|
160
|
+
quality: r.quality ?? "",
|
|
161
|
+
}));
|
|
162
|
+
const GenetoxSchema = z
|
|
163
|
+
.object({
|
|
164
|
+
source: z.string().nullable().optional(),
|
|
165
|
+
assayCategory: z.string().nullable().optional(),
|
|
166
|
+
assayType: z.string().nullable().optional(),
|
|
167
|
+
metabolicActivation: z.string().nullable().optional(),
|
|
168
|
+
species: z.string().nullable().optional(),
|
|
169
|
+
overallResult: z.string().nullable().optional(),
|
|
170
|
+
year: z.unknown().optional(),
|
|
171
|
+
})
|
|
172
|
+
.transform((r) => ({
|
|
173
|
+
source: r.source ?? "",
|
|
174
|
+
assayCategory: r.assayCategory ?? "",
|
|
175
|
+
assayType: r.assayType ?? "",
|
|
176
|
+
metabolicActivation: r.metabolicActivation ?? "",
|
|
177
|
+
species: r.species ?? "",
|
|
178
|
+
overallResult: r.overallResult ?? "",
|
|
179
|
+
year: toNumberOrNull(r.year),
|
|
180
|
+
}));
|
|
181
|
+
const CancerSchema = z
|
|
182
|
+
.object({
|
|
183
|
+
source: z.string().nullable().optional(),
|
|
184
|
+
classification: z.string().nullable().optional(),
|
|
185
|
+
cancerClassification: z.string().nullable().optional(),
|
|
186
|
+
url: z.string().nullable().optional(),
|
|
187
|
+
})
|
|
188
|
+
.transform((r) => ({
|
|
189
|
+
source: r.source ?? "",
|
|
190
|
+
classification: r.classification ?? r.cancerClassification ?? "",
|
|
191
|
+
url: r.url ?? "",
|
|
192
|
+
}));
|
|
193
|
+
/**
|
|
194
|
+
* In-vivo hazard data for a chemical. `limit` caps toxval and genetox only —
|
|
195
|
+
* cancer classifications always come back whole.
|
|
196
|
+
*/
|
|
197
|
+
export async function fetchCtxHazardData(dtxsid, headers, opts) {
|
|
198
|
+
const { dataType, limit } = opts;
|
|
199
|
+
const data = {};
|
|
200
|
+
const fetchers = [];
|
|
201
|
+
if (dataType === "toxval" || dataType === "all") {
|
|
202
|
+
fetchers.push(fetchToxval(dtxsid, headers, limit).then((v) => {
|
|
203
|
+
data.toxval = v;
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
if (dataType === "genetox" || dataType === "all") {
|
|
207
|
+
fetchers.push(fetchGenetox(dtxsid, headers, limit).then((v) => {
|
|
208
|
+
data.genetox = v;
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
if (dataType === "cancer" || dataType === "all") {
|
|
212
|
+
fetchers.push(fetchCancer(dtxsid, headers).then((v) => {
|
|
213
|
+
data.cancer = v;
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
await Promise.all(fetchers);
|
|
217
|
+
return data;
|
|
218
|
+
}
|
|
219
|
+
async function fetchToxval(dtxsid, headers, limit) {
|
|
220
|
+
const url = `${EPA_CCTE_BASE}/hazard/toxval/search/by-dtxsid/${dtxsid}`;
|
|
221
|
+
const res = await apiFetchValidated(url, z.array(ToxValSchema), { headers });
|
|
222
|
+
if (res.isErr() || !Array.isArray(res.value))
|
|
223
|
+
return [];
|
|
224
|
+
return res.value.slice(0, limit);
|
|
225
|
+
}
|
|
226
|
+
function toNumberOrNull(v) {
|
|
227
|
+
if (v == null || v === "")
|
|
228
|
+
return null;
|
|
229
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
230
|
+
return Number.isFinite(n) ? n : null;
|
|
231
|
+
}
|
|
232
|
+
async function fetchGenetox(dtxsid, headers, limit) {
|
|
233
|
+
const url = `${EPA_CCTE_BASE}/hazard/genetox/summary/search/by-dtxsid/${dtxsid}`;
|
|
234
|
+
const res = await apiFetchValidated(url, z.array(GenetoxSchema), { headers });
|
|
235
|
+
if (res.isErr() || !Array.isArray(res.value))
|
|
236
|
+
return [];
|
|
237
|
+
return res.value.slice(0, limit);
|
|
238
|
+
}
|
|
239
|
+
async function fetchCancer(dtxsid, headers) {
|
|
240
|
+
const url = `${EPA_CCTE_BASE}/hazard/cancer-summary/search/by-dtxsid/${dtxsid}`;
|
|
241
|
+
const res = await apiFetchValidated(url, z.array(CancerSchema), { headers });
|
|
242
|
+
if (res.isErr() || !Array.isArray(res.value))
|
|
243
|
+
return [];
|
|
244
|
+
return res.value;
|
|
245
|
+
}
|
|
246
|
+
// Raw CTX chemical-detail wire shape, validated at the fetch boundary. Every
|
|
247
|
+
// field is optional (the API omits absent values); the mapper below normalizes
|
|
248
|
+
// it into `ChemicalDetail`, folding in the resolved `dtxsid` fallback.
|
|
249
|
+
const RawChemicalDetailSchema = z.object({
|
|
250
|
+
dtxsid: z.string().optional(),
|
|
251
|
+
dtxcid: z.string().nullable().optional(),
|
|
252
|
+
casrn: z.string().nullable().optional(),
|
|
253
|
+
preferredName: z.string().nullable().optional(),
|
|
254
|
+
iupacName: z.string().nullable().optional(),
|
|
255
|
+
molFormula: z.string().nullable().optional(),
|
|
256
|
+
smiles: z.string().nullable().optional(),
|
|
257
|
+
inchikey: z.string().nullable().optional(),
|
|
258
|
+
monoisotopicMass: z.number().nullable().optional(),
|
|
259
|
+
averageMass: z.number().nullable().optional(),
|
|
260
|
+
qcLevel: z.number().nullable().optional(),
|
|
261
|
+
totalAssays: z.number().nullable().optional(),
|
|
262
|
+
activeAssays: z.number().nullable().optional(),
|
|
263
|
+
percentAssays: z.number().nullable().optional(),
|
|
264
|
+
pubchemCid: z.number().nullable().optional(),
|
|
265
|
+
pubmedCount: z.number().nullable().optional(),
|
|
266
|
+
sourcesCount: z.number().nullable().optional(),
|
|
267
|
+
});
|
|
268
|
+
const RawPropertySummarySchema = z.object({
|
|
269
|
+
propName: z.string().nullable().optional(),
|
|
270
|
+
unit: z.string().nullable().optional(),
|
|
271
|
+
experimentalCount: z.number().nullable().optional(),
|
|
272
|
+
experimentalMedian: z.number().nullable().optional(),
|
|
273
|
+
experimentalMin: z.number().nullable().optional(),
|
|
274
|
+
experimentalMax: z.number().nullable().optional(),
|
|
275
|
+
predictedCount: z.number().nullable().optional(),
|
|
276
|
+
predictedMedian: z.number().nullable().optional(),
|
|
277
|
+
predictedMin: z.number().nullable().optional(),
|
|
278
|
+
predictedMax: z.number().nullable().optional(),
|
|
279
|
+
});
|
|
280
|
+
/** Chemical identity, mass, and dashboard counts for a DTXSID. */
|
|
281
|
+
export async function fetchCtxChemicalDetail(dtxsid, headers) {
|
|
282
|
+
const detailUrl = `${EPA_CCTE_BASE}/chemical/detail/search/by-dtxsid/${dtxsid}?projection=chemicaldetailstandard`;
|
|
283
|
+
const detailRes = await apiFetchValidated(detailUrl, RawChemicalDetailSchema, { headers });
|
|
284
|
+
if (detailRes.isErr())
|
|
285
|
+
throw new Error(describeApiError(detailRes.error));
|
|
286
|
+
const d = detailRes.value ?? {};
|
|
287
|
+
return {
|
|
288
|
+
dtxsid: d.dtxsid ?? dtxsid,
|
|
289
|
+
dtxcid: d.dtxcid ?? "",
|
|
290
|
+
casrn: d.casrn ?? null,
|
|
291
|
+
preferredName: d.preferredName ?? "",
|
|
292
|
+
iupacName: d.iupacName ?? "",
|
|
293
|
+
molFormula: d.molFormula ?? "",
|
|
294
|
+
smiles: d.smiles ?? "",
|
|
295
|
+
inchikey: d.inchikey ?? "",
|
|
296
|
+
monoisotopicMass: d.monoisotopicMass ?? null,
|
|
297
|
+
averageMass: d.averageMass ?? null,
|
|
298
|
+
qcLevel: d.qcLevel ?? null,
|
|
299
|
+
totalAssays: d.totalAssays ?? null,
|
|
300
|
+
activeAssays: d.activeAssays ?? null,
|
|
301
|
+
percentAssays: d.percentAssays ?? null,
|
|
302
|
+
pubchemCid: d.pubchemCid ?? null,
|
|
303
|
+
pubmedCount: d.pubmedCount ?? null,
|
|
304
|
+
sourcesCount: d.sourcesCount ?? null,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/** Experimental + predicted physicochemical property summaries. `undefined` = none served. */
|
|
308
|
+
export async function fetchCtxChemicalProperties(dtxsid, headers) {
|
|
309
|
+
const propUrl = `${EPA_CCTE_BASE}/chemical/property/summary/search/by-dtxsid/${dtxsid}`;
|
|
310
|
+
const propRes = await apiFetchValidated(propUrl, z.array(RawPropertySummarySchema), { headers });
|
|
311
|
+
if (propRes.isOk() && Array.isArray(propRes.value)) {
|
|
312
|
+
return propRes.value.map((p) => ({
|
|
313
|
+
propName: p.propName ?? "",
|
|
314
|
+
unit: p.unit ?? "",
|
|
315
|
+
experimentalCount: p.experimentalCount ?? null,
|
|
316
|
+
experimentalMedian: p.experimentalMedian ?? null,
|
|
317
|
+
experimentalMin: p.experimentalMin ?? null,
|
|
318
|
+
experimentalMax: p.experimentalMax ?? null,
|
|
319
|
+
predictedCount: p.predictedCount ?? null,
|
|
320
|
+
predictedMedian: p.predictedMedian ?? null,
|
|
321
|
+
predictedMin: p.predictedMin ?? null,
|
|
322
|
+
predictedMax: p.predictedMax ?? null,
|
|
323
|
+
}));
|
|
324
|
+
}
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
// SEEM exposure-prediction wire shape (endpoint returns a single object or an
|
|
328
|
+
// array). `probabilityPesticde` is the API's own misspelling, read as a
|
|
329
|
+
// fallback before the corrected `probabilityPesticide`, so both stay modeled.
|
|
330
|
+
const RawSeemPredictionSchema = z.object({
|
|
331
|
+
dtxsid: z.string().optional(),
|
|
332
|
+
productionVolume: z.number().nullable().optional(),
|
|
333
|
+
units: z.string().nullable().optional(),
|
|
334
|
+
probabilityDietary: z.number().nullable().optional(),
|
|
335
|
+
probabilityResidential: z.number().nullable().optional(),
|
|
336
|
+
probabilityPesticde: z.number().nullable().optional(),
|
|
337
|
+
probabilityPesticide: z.number().nullable().optional(),
|
|
338
|
+
probabilityIndustrial: z.number().nullable().optional(),
|
|
339
|
+
});
|
|
340
|
+
const RawHttkRowSchema = z.object({
|
|
341
|
+
parameter: z.string().nullable().optional(),
|
|
342
|
+
measured: z.number().nullable().optional(),
|
|
343
|
+
predicted: z.number().nullable().optional(),
|
|
344
|
+
units: z.string().nullable().optional(),
|
|
345
|
+
model: z.string().nullable().optional(),
|
|
346
|
+
species: z.string().nullable().optional(),
|
|
347
|
+
reference: z.string().nullable().optional(),
|
|
348
|
+
});
|
|
349
|
+
const RawFunctionalUseRowSchema = z.object({
|
|
350
|
+
functioncategory: z.string().nullable().optional(),
|
|
351
|
+
reportedfunction: z.string().nullable().optional(),
|
|
352
|
+
doctitle: z.string().nullable().optional(),
|
|
353
|
+
});
|
|
354
|
+
const RawProductDataRowSchema = z.object({
|
|
355
|
+
productname: z.string().nullable().optional(),
|
|
356
|
+
gencat: z.string().nullable().optional(),
|
|
357
|
+
prodfam: z.string().nullable().optional(),
|
|
358
|
+
prodtype: z.string().nullable().optional(),
|
|
359
|
+
centralweightfraction: z.number().nullable().optional(),
|
|
360
|
+
weightfractiontype: z.string().nullable().optional(),
|
|
361
|
+
});
|
|
362
|
+
/**
|
|
363
|
+
* Human-exposure data for a chemical. `limit` caps functional-use and
|
|
364
|
+
* product-data only — seem and httk always come back whole.
|
|
365
|
+
*/
|
|
366
|
+
export async function fetchCtxExposureData(dtxsid, headers, opts) {
|
|
367
|
+
const { dataType, limit } = opts;
|
|
368
|
+
const data = {};
|
|
369
|
+
const fetchers = [];
|
|
370
|
+
if (dataType === "seem" || dataType === "all") {
|
|
371
|
+
fetchers.push(fetchSeem(dtxsid, headers).then((v) => {
|
|
372
|
+
data.seem = v;
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
if (dataType === "httk" || dataType === "all") {
|
|
376
|
+
fetchers.push(fetchHttk(dtxsid, headers).then((v) => {
|
|
377
|
+
data.httk = v;
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
if (dataType === "functional-use" || dataType === "all") {
|
|
381
|
+
fetchers.push(fetchFunctionalUse(dtxsid, headers, limit).then((v) => {
|
|
382
|
+
data.functionalUse = v;
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
if (dataType === "product-data" || dataType === "all") {
|
|
386
|
+
fetchers.push(fetchProductData(dtxsid, headers, limit).then((v) => {
|
|
387
|
+
data.productData = v;
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
await Promise.all(fetchers);
|
|
391
|
+
return data;
|
|
392
|
+
}
|
|
393
|
+
async function fetchSeem(dtxsid, headers) {
|
|
394
|
+
const url = `${EPA_CCTE_BASE}/exposure/seem/general/search/by-dtxsid/${dtxsid}`;
|
|
395
|
+
const res = await apiFetchValidated(url, z.union([RawSeemPredictionSchema, z.array(RawSeemPredictionSchema)]), { headers });
|
|
396
|
+
if (res.isErr() || !res.value)
|
|
397
|
+
return undefined;
|
|
398
|
+
const d = Array.isArray(res.value) ? res.value[0] : res.value;
|
|
399
|
+
if (!d)
|
|
400
|
+
return undefined;
|
|
401
|
+
return {
|
|
402
|
+
dtxsid: d.dtxsid ?? dtxsid,
|
|
403
|
+
productionVolume: d.productionVolume ?? null,
|
|
404
|
+
units: d.units ?? "",
|
|
405
|
+
probabilityDietary: d.probabilityDietary ?? null,
|
|
406
|
+
probabilityResidential: d.probabilityResidential ?? null,
|
|
407
|
+
probabilityPesticide: d.probabilityPesticde ?? d.probabilityPesticide ?? null,
|
|
408
|
+
probabilityIndustrial: d.probabilityIndustrial ?? null,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
async function fetchHttk(dtxsid, headers) {
|
|
412
|
+
const url = `${EPA_CCTE_BASE}/exposure/httk/search/by-dtxsid/${dtxsid}`;
|
|
413
|
+
const res = await apiFetchValidated(url, z.array(RawHttkRowSchema), { headers });
|
|
414
|
+
if (res.isErr() || !Array.isArray(res.value))
|
|
415
|
+
return [];
|
|
416
|
+
return res.value.map((r) => ({
|
|
417
|
+
parameter: r.parameter ?? "",
|
|
418
|
+
measured: r.measured ?? null,
|
|
419
|
+
predicted: r.predicted ?? null,
|
|
420
|
+
units: r.units ?? "",
|
|
421
|
+
model: r.model ?? "",
|
|
422
|
+
species: r.species ?? "",
|
|
423
|
+
reference: r.reference ?? "",
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
async function fetchFunctionalUse(dtxsid, headers, limit) {
|
|
427
|
+
const url = `${EPA_CCTE_BASE}/exposure/functional-use/search/by-dtxsid/${dtxsid}`;
|
|
428
|
+
const res = await apiFetchValidated(url, z.array(RawFunctionalUseRowSchema), { headers });
|
|
429
|
+
if (res.isErr() || !Array.isArray(res.value))
|
|
430
|
+
return [];
|
|
431
|
+
return res.value.slice(0, limit).map((r) => ({
|
|
432
|
+
functionCategory: r.functioncategory ?? "",
|
|
433
|
+
reportedFunction: r.reportedfunction ?? "",
|
|
434
|
+
docTitle: r.doctitle ?? "",
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
async function fetchProductData(dtxsid, headers, limit) {
|
|
438
|
+
const url = `${EPA_CCTE_BASE}/exposure/product-data/search/by-dtxsid/${dtxsid}`;
|
|
439
|
+
const res = await apiFetchValidated(url, z.array(RawProductDataRowSchema), { headers });
|
|
440
|
+
if (res.isErr() || !Array.isArray(res.value))
|
|
441
|
+
return [];
|
|
442
|
+
return res.value.slice(0, limit).map((r) => ({
|
|
443
|
+
productName: r.productname ?? "",
|
|
444
|
+
generalCategory: r.gencat ?? "",
|
|
445
|
+
productFamily: r.prodfam ?? "",
|
|
446
|
+
productType: r.prodtype ?? "",
|
|
447
|
+
centralWeightFraction: r.centralweightfraction ?? null,
|
|
448
|
+
weightFractionType: r.weightfractiontype ?? "",
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
@@ -28,7 +28,16 @@ const IupharTargetSchema = z.object({
|
|
|
28
28
|
});
|
|
29
29
|
const UNIPROT_ACCESSION_RE = /^[A-Z][0-9][A-Z0-9]{3}[0-9]$/;
|
|
30
30
|
function stripHtml(name) {
|
|
31
|
-
|
|
31
|
+
// Strip tags to a fixpoint: a single pass can leave a `<…>` behind when
|
|
32
|
+
// removing an inner tag lets the surrounding angle brackets form a new one,
|
|
33
|
+
// so repeat until the string stops changing.
|
|
34
|
+
let out = name;
|
|
35
|
+
let prev;
|
|
36
|
+
do {
|
|
37
|
+
prev = out;
|
|
38
|
+
out = out.replace(/<[^>]+>/g, "");
|
|
39
|
+
} while (out !== prev);
|
|
40
|
+
return out;
|
|
32
41
|
}
|
|
33
42
|
function notFound(error) {
|
|
34
43
|
return error.type === "http_status" && (error.status === 404 || error.status === 204);
|
|
@@ -34,8 +34,16 @@ const OpenFdaLabelSchema = z.object({
|
|
|
34
34
|
rems_indication: z.unknown().optional(),
|
|
35
35
|
medication_guide: z.unknown().optional(),
|
|
36
36
|
});
|
|
37
|
+
/**
|
|
38
|
+
* Escape a value for embedding inside a double-quoted openFDA (Lucene) phrase.
|
|
39
|
+
* The backslash is escaped first so a caller-supplied `\` cannot smuggle an
|
|
40
|
+
* unescaped `"` past the quote escaping and break out of the phrase.
|
|
41
|
+
*/
|
|
42
|
+
function escapeQueryPhrase(value) {
|
|
43
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
44
|
+
}
|
|
37
45
|
function buildSearch(drugName, serious) {
|
|
38
|
-
const escaped = drugName
|
|
46
|
+
const escaped = escapeQueryPhrase(drugName);
|
|
39
47
|
let search = `patient.drug.openfda.generic_name:"${escaped}"`;
|
|
40
48
|
if (serious)
|
|
41
49
|
search += "+AND+serious:1";
|
|
@@ -68,7 +76,7 @@ export async function getFaersByDrug(drugName, options = {}) {
|
|
|
68
76
|
* and §2.6.8 risk summary.
|
|
69
77
|
*/
|
|
70
78
|
export async function getFaersSeriousness(drugName) {
|
|
71
|
-
const escaped = drugName
|
|
79
|
+
const escaped = escapeQueryPhrase(drugName);
|
|
72
80
|
const search = `patient.drug.openfda.generic_name:"${escaped}"`;
|
|
73
81
|
const totalUrl = `${OPENFDA_BASE}?search=${encodeURIComponent(search)}&limit=1`;
|
|
74
82
|
const totalRes = await apiFetchValidated(totalUrl, FaersMetaResponseSchema);
|
|
@@ -179,7 +187,7 @@ const OpenFdaLabelResponseSchema = z.object({
|
|
|
179
187
|
*/
|
|
180
188
|
export async function getDrugLabelActions(genericName, options = {}) {
|
|
181
189
|
const limit = options.limit ?? 5;
|
|
182
|
-
const escaped = genericName
|
|
190
|
+
const escaped = escapeQueryPhrase(genericName);
|
|
183
191
|
const search = `openfda.generic_name:"${escaped}"`;
|
|
184
192
|
const url = `${OPENFDA_LABEL_BASE}?search=${encodeURIComponent(search)}` + `&sort=effective_time:desc&limit=${limit}`;
|
|
185
193
|
const res = await apiFetchValidated(url, OpenFdaLabelResponseSchema);
|
|
@@ -8,10 +8,16 @@ import { apiFetch, apiFetchValidated, parseTSV } from "./api-utils.js";
|
|
|
8
8
|
const KEGG_BASE = "https://rest.kegg.jp";
|
|
9
9
|
const REACTOME_BASE = "https://reactome.org/ContentService";
|
|
10
10
|
export function stripHtmlAndCollapseWs(s) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Strip tags to a fixpoint: a single pass can leave a `<…>` behind when
|
|
12
|
+
// removing an inner tag lets the surrounding angle brackets form a new one,
|
|
13
|
+
// so repeat until the string stops changing.
|
|
14
|
+
let out = s;
|
|
15
|
+
let prev;
|
|
16
|
+
do {
|
|
17
|
+
prev = out;
|
|
18
|
+
out = out.replace(/<[^>]+>/g, "");
|
|
19
|
+
} while (out !== prev);
|
|
20
|
+
return out.replace(/\s+/g, " ").trim();
|
|
15
21
|
}
|
|
16
22
|
const ORGANISM_MAP = {
|
|
17
23
|
hsa: "Homo sapiens",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PubChem PUG-REST operations — the three lookups the PubChem tools expose.
|
|
3
|
+
*
|
|
4
|
+
* One home for the wire schemas, the URL construction, and the "PubChem 404
|
|
5
|
+
* means no data" contract, so a caller cannot drift from it: every function
|
|
6
|
+
* returns an empty collection for a 404 and throws on any other API failure.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
export type PubChemSearchBy = "name" | "smiles" | "inchi" | "inchikey" | "cid";
|
|
10
|
+
declare const PugPropertyRowSchema: z.ZodPipe<z.ZodObject<{
|
|
11
|
+
CID: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
CanonicalSMILES: z.ZodOptional<z.ZodString>;
|
|
13
|
+
MolecularFormula: z.ZodOptional<z.ZodString>;
|
|
14
|
+
MolecularWeight: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
|
|
15
|
+
IUPACName: z.ZodOptional<z.ZodString>;
|
|
16
|
+
InChI: z.ZodOptional<z.ZodString>;
|
|
17
|
+
InChIKey: z.ZodOptional<z.ZodString>;
|
|
18
|
+
XLogP: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
TPSA: z.ZodOptional<z.ZodNumber>;
|
|
20
|
+
HBondDonorCount: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
HBondAcceptorCount: z.ZodOptional<z.ZodNumber>;
|
|
22
|
+
RotatableBondCount: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
Complexity: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
25
|
+
cid: number;
|
|
26
|
+
canonicalSmiles: string | null;
|
|
27
|
+
molecularFormula: string | null;
|
|
28
|
+
molecularWeight: number | null;
|
|
29
|
+
iupacName: string | null;
|
|
30
|
+
inchi: string | null;
|
|
31
|
+
inchiKey: string | null;
|
|
32
|
+
xlogp: number | null;
|
|
33
|
+
tpsa: number | null;
|
|
34
|
+
hbondDonorCount: number | null;
|
|
35
|
+
hbondAcceptorCount: number | null;
|
|
36
|
+
rotatableBondCount: number | null;
|
|
37
|
+
complexity: number | null;
|
|
38
|
+
}, {
|
|
39
|
+
CID?: number | undefined;
|
|
40
|
+
CanonicalSMILES?: string | undefined;
|
|
41
|
+
MolecularFormula?: string | undefined;
|
|
42
|
+
MolecularWeight?: string | number | undefined;
|
|
43
|
+
IUPACName?: string | undefined;
|
|
44
|
+
InChI?: string | undefined;
|
|
45
|
+
InChIKey?: string | undefined;
|
|
46
|
+
XLogP?: number | undefined;
|
|
47
|
+
TPSA?: number | undefined;
|
|
48
|
+
HBondDonorCount?: number | undefined;
|
|
49
|
+
HBondAcceptorCount?: number | undefined;
|
|
50
|
+
RotatableBondCount?: number | undefined;
|
|
51
|
+
Complexity?: number | undefined;
|
|
52
|
+
}>>;
|
|
53
|
+
export type PubChemCompound = z.infer<typeof PugPropertyRowSchema>;
|
|
54
|
+
export interface PubChemCrossRef {
|
|
55
|
+
source: string;
|
|
56
|
+
id: string;
|
|
57
|
+
}
|
|
58
|
+
export interface PubChemAssay {
|
|
59
|
+
aid: number;
|
|
60
|
+
assayName: string | null;
|
|
61
|
+
targetName: string | null;
|
|
62
|
+
activityOutcome: string | null;
|
|
63
|
+
activityValue: number | null;
|
|
64
|
+
}
|
|
65
|
+
/** Resolve a compound to its identity + computed properties. Empty array = no match. */
|
|
66
|
+
export declare function fetchPubchemCompounds(query: string, searchBy: PubChemSearchBy): Promise<PubChemCompound[]>;
|
|
67
|
+
/** External registry ids for a CID. Empty array = the CID is in no external registry. */
|
|
68
|
+
export declare function fetchPubchemCrossRefs(cid: number): Promise<PubChemCrossRef[]>;
|
|
69
|
+
/** Bioassay screening summaries for a CID. Empty array = never screened. */
|
|
70
|
+
export declare function fetchPubchemAssays(cid: number, opts: {
|
|
71
|
+
activeOnly: boolean;
|
|
72
|
+
limit: number;
|
|
73
|
+
}): Promise<PubChemAssay[]>;
|
|
74
|
+
export {};
|