@inflexa-ai/harness 0.30.3 → 0.31.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/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/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,14 +2,19 @@
|
|
|
2
2
|
* Pure async client functions for biological pathway lookups via KEGG and Reactome.
|
|
3
3
|
*
|
|
4
4
|
* Used by §3.7 (Pathway Context).
|
|
5
|
+
*
|
|
6
|
+
* Absence policy: Reactome omits the key of an absent value, thus a maybe-absent
|
|
7
|
+
* field carries `.optional()`. KEGG serves plain text, not JSON, thus it has no
|
|
8
|
+
* key to omit.
|
|
5
9
|
*/
|
|
6
10
|
import { z } from "zod";
|
|
7
11
|
import { apiFetch, apiFetchValidated, parseTSV } from "./api-utils.js";
|
|
8
12
|
const KEGG_BASE = "https://rest.kegg.jp";
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
+
// Both `reactome.org/ContentService` and `release.reactome.org/ContentService`
|
|
14
|
+
// answer 200 on each path that this file calls. The two hosts serve different
|
|
15
|
+
// data releases, thus the same stable identifier gives a different record on
|
|
16
|
+
// each. The release host is the pinned one, and the web pages that a result
|
|
17
|
+
// links to stay on the primary host.
|
|
13
18
|
const REACTOME_BASE = "https://release.reactome.org/ContentService";
|
|
14
19
|
export function stripHtmlAndCollapseWs(s) {
|
|
15
20
|
// Strip tags to a fixpoint: a single pass can leave a `<…>` behind when
|
|
@@ -39,11 +44,11 @@ const ReactomeSearchEntrySchema = z.object({
|
|
|
39
44
|
stId: z.string().optional(),
|
|
40
45
|
name: z.string().optional(),
|
|
41
46
|
});
|
|
42
|
-
const ReactomeSearchResponseSchema = z.object({
|
|
47
|
+
export const ReactomeSearchResponseSchema = z.object({
|
|
43
48
|
results: z.array(z.object({ entries: z.array(ReactomeSearchEntrySchema).optional() })).optional(),
|
|
44
49
|
});
|
|
45
50
|
/** Participant list — `searchReactome` reads `displayName` per participant. */
|
|
46
|
-
const ReactomeParticipantsSchema = z.array(z.object({ displayName: z.string().optional() }));
|
|
51
|
+
export const ReactomeParticipantsSchema = z.array(z.object({ displayName: z.string().optional() }));
|
|
47
52
|
/** One Reactome event record, as `/data/query/{stId}` serves it. */
|
|
48
53
|
const ReactomeEntrySchema = z.object({
|
|
49
54
|
stId: z.string().optional(),
|
|
@@ -51,7 +56,7 @@ const ReactomeEntrySchema = z.object({
|
|
|
51
56
|
summation: z.array(z.object({ text: z.string().optional() })).optional(),
|
|
52
57
|
});
|
|
53
58
|
/** Participant list — `getPathwayMemberships` reads `refEntities` per participant. */
|
|
54
|
-
const ReactomeParticipantEntitiesSchema = z.array(z.object({
|
|
59
|
+
export const ReactomeParticipantEntitiesSchema = z.array(z.object({
|
|
55
60
|
refEntities: z.array(z.object({ schemaClass: z.string().optional(), identifier: z.string().optional() })).optional(),
|
|
56
61
|
}));
|
|
57
62
|
export async function searchKegg(query, organism, maxResults, includeGenes) {
|
|
@@ -79,6 +84,81 @@ export async function searchKegg(query, organism, maxResults, includeGenes) {
|
|
|
79
84
|
}
|
|
80
85
|
return pathways;
|
|
81
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Read the KEGG gene identifier of a symbol, for example `hsa:7157` for `TP53`.
|
|
89
|
+
*
|
|
90
|
+
* `find/{organism}/{symbol}` searches the gene names of one organism, and it
|
|
91
|
+
* ranks a partial name match above an exact one. A query for `TP53` puts
|
|
92
|
+
* `TP53BP2` first. Thus the caller matches the symbol against the alias list of
|
|
93
|
+
* each row, and it never takes the first row.
|
|
94
|
+
*/
|
|
95
|
+
export function selectKeggGeneId(rows, symbol) {
|
|
96
|
+
const wanted = symbol.trim().toUpperCase();
|
|
97
|
+
for (const cols of rows) {
|
|
98
|
+
const id = (cols[0] ?? "").trim();
|
|
99
|
+
// Column two is `ALIAS1, ALIAS2; definition`.
|
|
100
|
+
const aliases = (cols[1] ?? "").split(";")[0] ?? "";
|
|
101
|
+
for (const alias of aliases.split(",")) {
|
|
102
|
+
if (alias.trim().toUpperCase() === wanted)
|
|
103
|
+
return id;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
/** The pathway identifiers of a `link/pathway/{geneId}` answer, without the `path:` prefix. */
|
|
109
|
+
export function parseKeggPathwayLinks(rows) {
|
|
110
|
+
const ids = [];
|
|
111
|
+
for (const cols of rows) {
|
|
112
|
+
const id = (cols[1] ?? "").replace("path:", "").trim();
|
|
113
|
+
if (id)
|
|
114
|
+
ids.push(id);
|
|
115
|
+
}
|
|
116
|
+
return ids;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The pathways that hold one gene, from KEGG.
|
|
120
|
+
*
|
|
121
|
+
* `find/pathway/{query}` matches a pathway NAME only, thus a gene symbol gives
|
|
122
|
+
* an empty body there. The membership path is a gene resolution, then
|
|
123
|
+
* `link/pathway/{geneId}`, then one `list/pathway/{organism}` call for the
|
|
124
|
+
* names. The one list call costs about 22 kB and names every pathway of the
|
|
125
|
+
* organism, thus it replaces one `get/{id}` call for each membership.
|
|
126
|
+
*/
|
|
127
|
+
export async function keggPathwaysForGene(geneSymbol, organism, maxResults) {
|
|
128
|
+
const geneRes = await apiFetch(`${KEGG_BASE}/find/${organism}/${encodeURIComponent(geneSymbol)}`, { parseAs: "text" });
|
|
129
|
+
if (geneRes.isErr())
|
|
130
|
+
return [];
|
|
131
|
+
const geneId = selectKeggGeneId(parseTSV(geneRes.value), geneSymbol);
|
|
132
|
+
if (!geneId)
|
|
133
|
+
return [];
|
|
134
|
+
const linkRes = await apiFetch(`${KEGG_BASE}/link/pathway/${encodeURIComponent(geneId)}`, { parseAs: "text" });
|
|
135
|
+
if (linkRes.isErr())
|
|
136
|
+
return [];
|
|
137
|
+
const pathwayIds = parseKeggPathwayLinks(parseTSV(linkRes.value)).slice(0, maxResults);
|
|
138
|
+
if (pathwayIds.length === 0)
|
|
139
|
+
return [];
|
|
140
|
+
const names = await keggPathwayNames(organism);
|
|
141
|
+
return pathwayIds.map((id) => ({
|
|
142
|
+
id,
|
|
143
|
+
name: names.get(id) ?? id,
|
|
144
|
+
source: "kegg",
|
|
145
|
+
url: `https://www.kegg.jp/pathway/${id}`,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
/** The pathway vocabulary of one organism, keyed by the pathway identifier. */
|
|
149
|
+
async function keggPathwayNames(organism) {
|
|
150
|
+
const names = new Map();
|
|
151
|
+
const res = await apiFetch(`${KEGG_BASE}/list/pathway/${encodeURIComponent(organism)}`, { parseAs: "text" });
|
|
152
|
+
if (res.isErr())
|
|
153
|
+
return names;
|
|
154
|
+
for (const cols of parseTSV(res.value)) {
|
|
155
|
+
const id = (cols[0] ?? "").trim();
|
|
156
|
+
const name = (cols[1] ?? "").trim();
|
|
157
|
+
if (id && name)
|
|
158
|
+
names.set(id, name);
|
|
159
|
+
}
|
|
160
|
+
return names;
|
|
161
|
+
}
|
|
82
162
|
export async function searchReactome(query, species, maxResults, includeGenes) {
|
|
83
163
|
const params = new URLSearchParams({
|
|
84
164
|
query,
|
|
@@ -195,7 +275,7 @@ export async function getPathwayById(id) {
|
|
|
195
275
|
}
|
|
196
276
|
/** Pathway memberships for a gene symbol — used directly by §3.7 collector. */
|
|
197
277
|
export async function getPathwayMemberships(geneSymbol) {
|
|
198
|
-
const [kegg, reactome] = await Promise.all([
|
|
278
|
+
const [kegg, reactome] = await Promise.all([keggPathwaysForGene(geneSymbol, "hsa", 25), searchReactome(geneSymbol, "Homo sapiens", 25, false)]);
|
|
199
279
|
// For Reactome pathways, fetch participant UniProts (bounded to avoid rate limits).
|
|
200
280
|
await Promise.all(reactome.slice(0, 50).map(async (pw) => {
|
|
201
281
|
try {
|
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure async client functions for PharmGKB clinical annotations
|
|
2
|
+
* Pure async client functions for the PharmGKB clinical annotations, which
|
|
3
|
+
* ClinPGx now serves.
|
|
3
4
|
*
|
|
4
5
|
* Public API, no key required. Matching is an exact field filter, not a search:
|
|
5
6
|
* `location.genes.symbol` for a gene, `relatedChemicals.name` for a drug.
|
|
7
|
+
*
|
|
8
|
+
* Absence policy: the sampled ClinPGx payloads carry no explicit `null`, and a
|
|
9
|
+
* record omits the key of an absent value. Thus a maybe-absent field carries
|
|
10
|
+
* `.optional()`, not `.nullable()`.
|
|
6
11
|
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
export declare const PharmgkbAnnotationSchema: z.ZodObject<{
|
|
14
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
15
|
+
genes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
16
|
+
symbol: z.ZodOptional<z.ZodString>;
|
|
17
|
+
}, z.core.$strip>>>;
|
|
18
|
+
}, z.core.$strip>>;
|
|
19
|
+
relatedChemicals: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
20
|
+
name: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strip>>>;
|
|
22
|
+
levelOfEvidence: z.ZodOptional<z.ZodObject<{
|
|
23
|
+
term: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>>;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
export declare const PharmgkbResponseSchema: z.ZodObject<{
|
|
27
|
+
data: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
28
|
+
location: z.ZodOptional<z.ZodObject<{
|
|
29
|
+
genes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
30
|
+
symbol: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>>>;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
relatedChemicals: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
|
+
name: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strip>>>;
|
|
36
|
+
levelOfEvidence: z.ZodOptional<z.ZodObject<{
|
|
37
|
+
term: z.ZodOptional<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>>;
|
|
39
|
+
}, z.core.$strip>>>;
|
|
40
|
+
}, z.core.$strip>;
|
|
7
41
|
/**
|
|
8
42
|
* One PharmGKB clinical annotation.
|
|
9
43
|
*
|
|
@@ -1,27 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure async client functions for PharmGKB clinical annotations
|
|
2
|
+
* Pure async client functions for the PharmGKB clinical annotations, which
|
|
3
|
+
* ClinPGx now serves.
|
|
3
4
|
*
|
|
4
5
|
* Public API, no key required. Matching is an exact field filter, not a search:
|
|
5
6
|
* `location.genes.symbol` for a gene, `relatedChemicals.name` for a drug.
|
|
7
|
+
*
|
|
8
|
+
* Absence policy: the sampled ClinPGx payloads carry no explicit `null`, and a
|
|
9
|
+
* record omits the key of an absent value. Thus a maybe-absent field carries
|
|
10
|
+
* `.optional()`, not `.nullable()`.
|
|
6
11
|
*/
|
|
7
12
|
import { z } from "zod";
|
|
8
13
|
import { apiFetchValidated, describeApiError } from "./api-utils.js";
|
|
9
14
|
import { PHARMGKB_BASE, PHARMGKB_HEADERS } from "./pharmgkb-config.js";
|
|
10
|
-
// Raw
|
|
11
|
-
|
|
12
|
-
const PharmgkbAnnotationSchema = z.object({
|
|
15
|
+
// Raw ClinPGx clinicalAnnotation wire shape, validated at the fetch boundary.
|
|
16
|
+
export const PharmgkbAnnotationSchema = z.object({
|
|
13
17
|
location: z.object({ genes: z.array(z.object({ symbol: z.string().optional() })).optional() }).optional(),
|
|
14
18
|
relatedChemicals: z.array(z.object({ name: z.string().optional() })).optional(),
|
|
15
19
|
levelOfEvidence: z.object({ term: z.string().optional() }).optional(),
|
|
16
20
|
});
|
|
17
|
-
|
|
21
|
+
// `data` is polymorphic by status: an array of annotation records on HTTP 200,
|
|
22
|
+
// and an error OBJECT (`{"errors":[…]}`) on a 4xx. The undeclared sibling key
|
|
23
|
+
// `status` carries `"success"` or `"fail"`. This schema models the 200 arm only,
|
|
24
|
+
// because the 4xx arm never reaches it — `apiFetch` reports a non-ok status as
|
|
25
|
+
// `http_status` before the body is parsed.
|
|
26
|
+
export const PharmgkbResponseSchema = z.object({
|
|
18
27
|
data: z.array(PharmgkbAnnotationSchema).optional(),
|
|
19
28
|
});
|
|
20
29
|
export async function searchPharmgkb(query, searchType, limit = 20) {
|
|
21
30
|
const filter = searchType === "gene" ? `location.genes.symbol=${encodeURIComponent(query)}` : `relatedChemicals.name=${encodeURIComponent(query)}`;
|
|
22
31
|
const res = await apiFetchValidated(`${PHARMGKB_BASE}/clinicalAnnotation?${filter}`, PharmgkbResponseSchema, { headers: PHARMGKB_HEADERS });
|
|
23
|
-
if (res.isErr())
|
|
32
|
+
if (res.isErr()) {
|
|
33
|
+
// ClinPGx answers "no results matching criteria" with HTTP 404, not with
|
|
34
|
+
// an empty array. A gene or a drug that the corpus does not annotate is
|
|
35
|
+
// an expected miss, thus it gives an empty result. Every other failure
|
|
36
|
+
// still throws, because it is an outage or a contract break.
|
|
37
|
+
if (res.error.type === "http_status" && res.error.status === 404)
|
|
38
|
+
return { annotations: [], totalFound: 0 };
|
|
24
39
|
throw new Error(describeApiError(res.error));
|
|
40
|
+
}
|
|
25
41
|
const data = res.value?.data ?? [];
|
|
26
42
|
const annotations = data.slice(0, limit).map((ann) => ({
|
|
27
43
|
gene: ann.location?.genes?.map((g) => g.symbol).join(", ") ?? query,
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the PharmGKB clinical-annotation API.
|
|
3
|
+
*
|
|
4
|
+
* PharmGKB is now ClinPGx, and `api.pharmgkb.org` no longer resolves. The
|
|
5
|
+
* successor host serves the same paths, the same envelope, and the same field
|
|
6
|
+
* names, thus only the host changes here.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PHARMGKB_BASE = "https://api.clinpgx.org/v1/data";
|
|
2
9
|
export declare const PHARMGKB_HEADERS: {
|
|
3
10
|
readonly Accept: "application/json";
|
|
4
11
|
};
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the PharmGKB clinical-annotation API.
|
|
3
|
+
*
|
|
4
|
+
* PharmGKB is now ClinPGx, and `api.pharmgkb.org` no longer resolves. The
|
|
5
|
+
* successor host serves the same paths, the same envelope, and the same field
|
|
6
|
+
* names, thus only the host changes here.
|
|
7
|
+
*/
|
|
8
|
+
export const PHARMGKB_BASE = "https://api.clinpgx.org/v1/data";
|
|
2
9
|
export const PHARMGKB_HEADERS = { Accept: "application/json" };
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
* exposes molecular weight + SMILES + InChI properties that let us
|
|
8
8
|
* heuristically classify peptides, small molecules, and proteins without a
|
|
9
9
|
* hand-curated override table.
|
|
10
|
+
*
|
|
11
|
+
* Absence policy: PubChem omits the key of an absent value, per `minOccurs=0`
|
|
12
|
+
* in its XSD, and it never sends an explicit `null`. Thus a maybe-absent field
|
|
13
|
+
* carries `.optional()`, not `.nullable()`.
|
|
10
14
|
*/
|
|
15
|
+
import { z } from "zod";
|
|
11
16
|
export interface PubChemCompoundProperties {
|
|
12
17
|
cid: number;
|
|
13
18
|
molecularWeight: number | null;
|
|
@@ -16,6 +21,23 @@ export interface PubChemCompoundProperties {
|
|
|
16
21
|
hBondDonorCount: number | null;
|
|
17
22
|
hBondAcceptorCount: number | null;
|
|
18
23
|
}
|
|
24
|
+
export declare const PubChemPropertyResponseSchema: z.ZodObject<{
|
|
25
|
+
PropertyTable: z.ZodOptional<z.ZodObject<{
|
|
26
|
+
Properties: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
27
|
+
CID: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
MolecularWeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<number | null, string | number>>>;
|
|
29
|
+
InChIKey: z.ZodOptional<z.ZodString>;
|
|
30
|
+
ConnectivitySMILES: z.ZodOptional<z.ZodString>;
|
|
31
|
+
HBondDonorCount: z.ZodOptional<z.ZodNumber>;
|
|
32
|
+
HBondAcceptorCount: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
}, z.core.$strip>>>;
|
|
34
|
+
}, z.core.$strip>>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
export declare const PubChemCidListResponseSchema: z.ZodObject<{
|
|
37
|
+
IdentifierList: z.ZodOptional<z.ZodObject<{
|
|
38
|
+
CID: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
|
+
}, z.core.$strip>;
|
|
19
41
|
export declare function getCompoundPropertiesByCID(cid: number): Promise<PubChemCompoundProperties | null>;
|
|
20
42
|
/**
|
|
21
43
|
* Look up a PubChem compound by InChI key (the canonical hash of the 3-D
|
|
@@ -7,61 +7,63 @@
|
|
|
7
7
|
* exposes molecular weight + SMILES + InChI properties that let us
|
|
8
8
|
* heuristically classify peptides, small molecules, and proteins without a
|
|
9
9
|
* hand-curated override table.
|
|
10
|
+
*
|
|
11
|
+
* Absence policy: PubChem omits the key of an absent value, per `minOccurs=0`
|
|
12
|
+
* in its XSD, and it never sends an explicit `null`. Thus a maybe-absent field
|
|
13
|
+
* carries `.optional()`, not `.nullable()`.
|
|
10
14
|
*/
|
|
11
15
|
import { z } from "zod";
|
|
12
|
-
import { apiFetchValidated, describeApiError } from "./api-utils.js";
|
|
16
|
+
import { apiFetchValidated, describeApiError, zWireNumber } from "./api-utils.js";
|
|
13
17
|
import { PUBCHEM_BASE, PUBCHEM_HEADERS } from "./pubchem-config.js";
|
|
14
18
|
const PROPERTY_FIELDS = [
|
|
15
19
|
"MolecularWeight",
|
|
16
20
|
"InChIKey",
|
|
17
|
-
//
|
|
18
|
-
// ConnectivitySMILES
|
|
19
|
-
// deprecated. We request both and fall back through them.
|
|
21
|
+
// PubChem retired the response key `CanonicalSMILES`, and it answers a request
|
|
22
|
+
// for that name with `ConnectivitySMILES`. Thus the request names the live key.
|
|
20
23
|
"ConnectivitySMILES",
|
|
21
|
-
"CanonicalSMILES",
|
|
22
24
|
"HBondDonorCount",
|
|
23
25
|
"HBondAcceptorCount",
|
|
24
26
|
].join(",");
|
|
25
27
|
// A single record from the PUG-REST `PropertyTable.Properties` array, validated
|
|
26
|
-
// at the fetch boundary.
|
|
27
|
-
//
|
|
28
|
-
//
|
|
28
|
+
// at the fetch boundary. PubChem omits an absent property and it never sends
|
|
29
|
+
// `null`, thus each property carries `.optional()` and a declared type. Under
|
|
30
|
+
// zod 4 a bare `z.unknown()` is required, which rejects an omission.
|
|
29
31
|
const RawPubChemPropertySchema = z.object({
|
|
30
32
|
CID: z.number().optional(),
|
|
31
|
-
MolecularWeight
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// PUG-REST serializes MolecularWeight as a string (e.g. "180.16") to
|
|
34
|
+
// preserve significant figures, thus it reads through the wire-number helper.
|
|
35
|
+
MolecularWeight: zWireNumber.optional(),
|
|
36
|
+
InChIKey: z.string().optional(),
|
|
37
|
+
ConnectivitySMILES: z.string().optional(),
|
|
38
|
+
HBondDonorCount: z.number().optional(),
|
|
39
|
+
HBondAcceptorCount: z.number().optional(),
|
|
37
40
|
});
|
|
38
|
-
const PubChemPropertyResponseSchema = z.object({
|
|
41
|
+
export const PubChemPropertyResponseSchema = z.object({
|
|
39
42
|
PropertyTable: z.object({ Properties: z.array(RawPubChemPropertySchema).optional() }).optional(),
|
|
40
43
|
});
|
|
41
|
-
const PubChemCidListResponseSchema = z.object({
|
|
44
|
+
export const PubChemCidListResponseSchema = z.object({
|
|
42
45
|
IdentifierList: z.object({ CID: z.array(z.number()).optional() }).optional(),
|
|
43
46
|
});
|
|
44
|
-
function parseNumber(value) {
|
|
45
|
-
if (value == null)
|
|
46
|
-
return null;
|
|
47
|
-
if (typeof value === "number")
|
|
48
|
-
return Number.isFinite(value) ? value : null;
|
|
49
|
-
if (typeof value === "string") {
|
|
50
|
-
const n = parseFloat(value);
|
|
51
|
-
return Number.isFinite(n) ? n : null;
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
47
|
function mapProps(raw) {
|
|
56
48
|
return {
|
|
57
49
|
cid: raw.CID ?? 0,
|
|
58
|
-
molecularWeight:
|
|
59
|
-
inchiKey:
|
|
60
|
-
canonicalSmiles:
|
|
61
|
-
hBondDonorCount:
|
|
62
|
-
hBondAcceptorCount:
|
|
50
|
+
molecularWeight: raw.MolecularWeight ?? null,
|
|
51
|
+
inchiKey: raw.InChIKey ?? null,
|
|
52
|
+
canonicalSmiles: raw.ConnectivitySMILES ?? null,
|
|
53
|
+
hBondDonorCount: raw.HBondDonorCount ?? null,
|
|
54
|
+
hBondAcceptorCount: raw.HBondAcceptorCount ?? null,
|
|
63
55
|
};
|
|
64
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Does the record hold a property?
|
|
59
|
+
*
|
|
60
|
+
* PubChem answers HTTP 200 for a CID that does not exist, with one row that
|
|
61
|
+
* carries the `CID` alone. Thus a record with no property is an absence, and a
|
|
62
|
+
* caller must not read it as a compound.
|
|
63
|
+
*/
|
|
64
|
+
function carriesProperties(props) {
|
|
65
|
+
return Object.entries(props).some(([key, value]) => key !== "cid" && value !== null);
|
|
66
|
+
}
|
|
65
67
|
export async function getCompoundPropertiesByCID(cid) {
|
|
66
68
|
const url = `${PUBCHEM_BASE}/compound/cid/${cid}/property/${PROPERTY_FIELDS}/JSON`;
|
|
67
69
|
const res = await apiFetchValidated(url, PubChemPropertyResponseSchema, {
|
|
@@ -75,7 +77,8 @@ export async function getCompoundPropertiesByCID(cid) {
|
|
|
75
77
|
const props = res.value.PropertyTable?.Properties?.[0];
|
|
76
78
|
if (!props || typeof props.CID !== "number")
|
|
77
79
|
return null;
|
|
78
|
-
|
|
80
|
+
const mapped = mapProps(props);
|
|
81
|
+
return carriesProperties(mapped) ? mapped : null;
|
|
79
82
|
}
|
|
80
83
|
/**
|
|
81
84
|
* Look up a PubChem compound by InChI key (the canonical hash of the 3-D
|
|
@@ -4,14 +4,18 @@
|
|
|
4
4
|
* One home for the wire schemas, the URL construction, and the "PubChem 404
|
|
5
5
|
* means no data" contract, so a caller cannot drift from it: every function
|
|
6
6
|
* returns an empty collection for a 404 and throws on any other API failure.
|
|
7
|
+
*
|
|
8
|
+
* Absence policy: PubChem omits the key of an absent value, per `minOccurs=0`
|
|
9
|
+
* in its XSD, and it never sends an explicit `null`. Thus a maybe-absent field
|
|
10
|
+
* carries `.optional()`, not `.nullable()`.
|
|
7
11
|
*/
|
|
8
12
|
import { z } from "zod";
|
|
9
13
|
export type PubChemSearchBy = "name" | "smiles" | "inchi" | "inchikey" | "cid";
|
|
10
14
|
declare const PugPropertyRowSchema: z.ZodPipe<z.ZodObject<{
|
|
11
15
|
CID: z.ZodOptional<z.ZodNumber>;
|
|
12
|
-
|
|
16
|
+
ConnectivitySMILES: z.ZodOptional<z.ZodString>;
|
|
13
17
|
MolecularFormula: z.ZodOptional<z.ZodString>;
|
|
14
|
-
MolecularWeight: z.ZodOptional<z.ZodUnion<readonly [z.
|
|
18
|
+
MolecularWeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<number | null, string | number>>>;
|
|
15
19
|
IUPACName: z.ZodOptional<z.ZodString>;
|
|
16
20
|
InChI: z.ZodOptional<z.ZodString>;
|
|
17
21
|
InChIKey: z.ZodOptional<z.ZodString>;
|
|
@@ -37,9 +41,9 @@ declare const PugPropertyRowSchema: z.ZodPipe<z.ZodObject<{
|
|
|
37
41
|
complexity: number | null;
|
|
38
42
|
}, {
|
|
39
43
|
CID?: number | undefined;
|
|
40
|
-
|
|
44
|
+
ConnectivitySMILES?: string | undefined;
|
|
41
45
|
MolecularFormula?: string | undefined;
|
|
42
|
-
MolecularWeight?:
|
|
46
|
+
MolecularWeight?: number | null | undefined;
|
|
43
47
|
IUPACName?: string | undefined;
|
|
44
48
|
InChI?: string | undefined;
|
|
45
49
|
InChIKey?: string | undefined;
|
|
@@ -51,10 +55,68 @@ declare const PugPropertyRowSchema: z.ZodPipe<z.ZodObject<{
|
|
|
51
55
|
Complexity?: number | undefined;
|
|
52
56
|
}>>;
|
|
53
57
|
export type PubChemCompound = z.infer<typeof PugPropertyRowSchema>;
|
|
58
|
+
export declare const PubChemPropertyResponseSchema: z.ZodObject<{
|
|
59
|
+
PropertyTable: z.ZodOptional<z.ZodObject<{
|
|
60
|
+
Properties: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodObject<{
|
|
61
|
+
CID: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
ConnectivitySMILES: z.ZodOptional<z.ZodString>;
|
|
63
|
+
MolecularFormula: z.ZodOptional<z.ZodString>;
|
|
64
|
+
MolecularWeight: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodTransform<number | null, string | number>>>;
|
|
65
|
+
IUPACName: z.ZodOptional<z.ZodString>;
|
|
66
|
+
InChI: z.ZodOptional<z.ZodString>;
|
|
67
|
+
InChIKey: z.ZodOptional<z.ZodString>;
|
|
68
|
+
XLogP: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
TPSA: z.ZodOptional<z.ZodNumber>;
|
|
70
|
+
HBondDonorCount: z.ZodOptional<z.ZodNumber>;
|
|
71
|
+
HBondAcceptorCount: z.ZodOptional<z.ZodNumber>;
|
|
72
|
+
RotatableBondCount: z.ZodOptional<z.ZodNumber>;
|
|
73
|
+
Complexity: z.ZodOptional<z.ZodNumber>;
|
|
74
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
75
|
+
cid: number;
|
|
76
|
+
canonicalSmiles: string | null;
|
|
77
|
+
molecularFormula: string | null;
|
|
78
|
+
molecularWeight: number | null;
|
|
79
|
+
iupacName: string | null;
|
|
80
|
+
inchi: string | null;
|
|
81
|
+
inchiKey: string | null;
|
|
82
|
+
xlogp: number | null;
|
|
83
|
+
tpsa: number | null;
|
|
84
|
+
hbondDonorCount: number | null;
|
|
85
|
+
hbondAcceptorCount: number | null;
|
|
86
|
+
rotatableBondCount: number | null;
|
|
87
|
+
complexity: number | null;
|
|
88
|
+
}, {
|
|
89
|
+
CID?: number | undefined;
|
|
90
|
+
ConnectivitySMILES?: string | undefined;
|
|
91
|
+
MolecularFormula?: string | undefined;
|
|
92
|
+
MolecularWeight?: number | null | undefined;
|
|
93
|
+
IUPACName?: string | undefined;
|
|
94
|
+
InChI?: string | undefined;
|
|
95
|
+
InChIKey?: string | undefined;
|
|
96
|
+
XLogP?: number | undefined;
|
|
97
|
+
TPSA?: number | undefined;
|
|
98
|
+
HBondDonorCount?: number | undefined;
|
|
99
|
+
HBondAcceptorCount?: number | undefined;
|
|
100
|
+
RotatableBondCount?: number | undefined;
|
|
101
|
+
Complexity?: number | undefined;
|
|
102
|
+
}>>>>;
|
|
103
|
+
}, z.core.$strip>>;
|
|
104
|
+
}, z.core.$strip>;
|
|
54
105
|
export interface PubChemCrossRef {
|
|
55
|
-
|
|
106
|
+
/** The registry that the identifier pattern names, or `null` when no pattern matches. */
|
|
107
|
+
source: string | null;
|
|
56
108
|
id: string;
|
|
57
109
|
}
|
|
110
|
+
export declare const PubChemXrefResponseSchema: z.ZodObject<{
|
|
111
|
+
InformationList: z.ZodOptional<z.ZodObject<{
|
|
112
|
+
Information: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
113
|
+
CID: z.ZodOptional<z.ZodNumber>;
|
|
114
|
+
RegistryID: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
115
|
+
}, z.core.$strip>>>;
|
|
116
|
+
}, z.core.$strip>>;
|
|
117
|
+
}, z.core.$strip>;
|
|
118
|
+
/** The registry of one identifier, or `null` when no pattern matches it. */
|
|
119
|
+
export declare function classifyRegistryId(id: string): string | null;
|
|
58
120
|
export interface PubChemAssay {
|
|
59
121
|
aid: number;
|
|
60
122
|
assayName: string | null;
|
|
@@ -62,6 +124,17 @@ export interface PubChemAssay {
|
|
|
62
124
|
activityOutcome: string | null;
|
|
63
125
|
activityValue: number | null;
|
|
64
126
|
}
|
|
127
|
+
export declare const PugAssaySummarySchema: z.ZodObject<{
|
|
128
|
+
Table: z.ZodOptional<z.ZodObject<{
|
|
129
|
+
Columns: z.ZodOptional<z.ZodObject<{
|
|
130
|
+
Column: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
131
|
+
}, z.core.$strip>>;
|
|
132
|
+
Row: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
133
|
+
Cell: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
134
|
+
}, z.core.$strip>>>;
|
|
135
|
+
}, z.core.$strip>>;
|
|
136
|
+
}, z.core.$strip>;
|
|
137
|
+
type PugAssaySummary = z.infer<typeof PugAssaySummarySchema>;
|
|
65
138
|
/** Resolve a compound to its identity + computed properties. Empty array = no match. */
|
|
66
139
|
export declare function fetchPubchemCompounds(query: string, searchBy: PubChemSearchBy): Promise<PubChemCompound[]>;
|
|
67
140
|
/** External registry ids for a CID. Empty array = the CID is in no external registry. */
|
|
@@ -71,4 +144,11 @@ export declare function fetchPubchemAssays(cid: number, opts: {
|
|
|
71
144
|
activeOnly: boolean;
|
|
72
145
|
limit: number;
|
|
73
146
|
}): Promise<PubChemAssay[]>;
|
|
147
|
+
/**
|
|
148
|
+
* Read the assay table by its wire headings.
|
|
149
|
+
*
|
|
150
|
+
* The `targetName` of a record is the target accession, because the table names
|
|
151
|
+
* no protein. The accession resolves against UniProt or against a ChEMBL target.
|
|
152
|
+
*/
|
|
153
|
+
export declare function parseAssaySummary(data: PugAssaySummary): PubChemAssay[];
|
|
74
154
|
export {};
|