@inflexa-ai/harness 0.35.8 → 0.36.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.
Files changed (195) hide show
  1. package/dist/auth/types.d.ts +5 -14
  2. package/dist/auth/types.js +3 -10
  3. package/dist/billing/usage-recorder.d.ts +1 -1
  4. package/dist/contracts/analogy-report.d.ts +1 -1
  5. package/dist/contracts/analogy-report.js +1 -1
  6. package/dist/contracts/chat-parts.d.ts +2 -3
  7. package/dist/contracts/index.d.ts +0 -4
  8. package/dist/contracts/index.js +0 -2
  9. package/dist/index.d.ts +0 -7
  10. package/dist/index.js +0 -21
  11. package/dist/lib/metrics.d.ts +1 -1
  12. package/dist/lib/otel-spans.d.ts +0 -2
  13. package/dist/lib/otel-spans.js +0 -2
  14. package/dist/loop/run-agent.d.ts +1 -1
  15. package/dist/runtime/assemble.d.ts +0 -3
  16. package/dist/runtime/assemble.js +0 -3
  17. package/dist/state/index.d.ts +0 -2
  18. package/dist/state/index.js +0 -1
  19. package/dist/state/init.js +1 -60
  20. package/dist/state/purge-analysis.d.ts +1 -2
  21. package/dist/state/purge-analysis.js +1 -2
  22. package/dist/tasks/regulatory-corpus-boot-refresh.js +1 -1
  23. package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -3
  24. package/dist/tools/bio/gene-preclinical-profile.js +1 -3
  25. package/dist/tools/lib/chembl-client.d.ts +1 -4
  26. package/dist/tools/lib/chembl-client.js +1 -4
  27. package/dist/tools/lib/clinical-benchmarks-client.d.ts +1 -1
  28. package/dist/tools/lib/clinical-benchmarks-client.js +1 -1
  29. package/dist/tools/lib/clinical-trials-client.d.ts +0 -3
  30. package/dist/tools/lib/clinical-trials-client.js +0 -3
  31. package/dist/tools/lib/identifier-resolver.d.ts +5 -7
  32. package/dist/tools/lib/identifier-resolver.js +5 -7
  33. package/dist/tools/lib/iuphar-client.d.ts +1 -3
  34. package/dist/tools/lib/iuphar-client.js +1 -3
  35. package/dist/tools/lib/opentargets-client.d.ts +1 -1
  36. package/dist/tools/lib/opentargets-client.js +1 -1
  37. package/dist/tools/lib/protein-family-normalize.d.ts +5 -7
  38. package/dist/tools/lib/protein-family-normalize.js +5 -7
  39. package/dist/tools/lib/uniprot-client.d.ts +0 -7
  40. package/dist/tools/lib/uniprot-client.js +2 -9
  41. package/package.json +1 -1
  42. package/dist/contracts/target-assessment-row.d.ts +0 -85
  43. package/dist/contracts/target-assessment-row.js +0 -37
  44. package/dist/contracts/target-dossier.d.ts +0 -12423
  45. package/dist/contracts/target-dossier.js +0 -1282
  46. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +0 -1
  47. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +0 -85
  48. package/dist/prompts/target-assessment/briefs/index.d.ts +0 -4
  49. package/dist/prompts/target-assessment/briefs/index.js +0 -4
  50. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +0 -1
  51. package/dist/prompts/target-assessment/briefs/liability-bullets.js +0 -114
  52. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +0 -1
  53. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +0 -37
  54. package/dist/prompts/target-assessment/briefs/translational-commentary.d.ts +0 -1
  55. package/dist/prompts/target-assessment/briefs/translational-commentary.js +0 -23
  56. package/dist/prompts/target-assessment/drugs-in-class.d.ts +0 -1
  57. package/dist/prompts/target-assessment/drugs-in-class.js +0 -29
  58. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +0 -1
  59. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +0 -67
  60. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +0 -1
  61. package/dist/prompts/target-assessment/investigation/claim-reverification.js +0 -49
  62. package/dist/prompts/target-assessment/investigation/index.d.ts +0 -3
  63. package/dist/prompts/target-assessment/investigation/index.js +0 -3
  64. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +0 -1
  65. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +0 -49
  66. package/dist/prompts/target-assessment/modulator-triage.d.ts +0 -1
  67. package/dist/prompts/target-assessment/modulator-triage.js +0 -31
  68. package/dist/prompts/target-assessment/off-target-annotator.d.ts +0 -1
  69. package/dist/prompts/target-assessment/off-target-annotator.js +0 -28
  70. package/dist/prompts/target-assessment/tox-voice/core.d.ts +0 -1
  71. package/dist/prompts/target-assessment/tox-voice/core.js +0 -74
  72. package/dist/prompts/target-assessment/tox-voice/critique.d.ts +0 -7
  73. package/dist/prompts/target-assessment/tox-voice/critique.js +0 -109
  74. package/dist/prompts/target-assessment/tox-voice/exemplars.d.ts +0 -8
  75. package/dist/prompts/target-assessment/tox-voice/exemplars.js +0 -24
  76. package/dist/prompts/target-assessment/tox-voice/index.d.ts +0 -4
  77. package/dist/prompts/target-assessment/tox-voice/index.js +0 -4
  78. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +0 -18
  79. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +0 -61
  80. package/dist/state/target-assessments.d.ts +0 -68
  81. package/dist/state/target-assessments.js +0 -222
  82. package/dist/workflows/execute-target-assessment.d.ts +0 -118
  83. package/dist/workflows/execute-target-assessment.js +0 -631
  84. package/dist/workflows/target-assessment/assemblers/index.d.ts +0 -5
  85. package/dist/workflows/target-assessment/assemblers/index.js +0 -4
  86. package/dist/workflows/target-assessment/assemblers/literature.d.ts +0 -281
  87. package/dist/workflows/target-assessment/assemblers/literature.js +0 -971
  88. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +0 -118
  89. package/dist/workflows/target-assessment/assemblers/orchestrator.js +0 -945
  90. package/dist/workflows/target-assessment/assemblers/safety.d.ts +0 -374
  91. package/dist/workflows/target-assessment/assemblers/safety.js +0 -1068
  92. package/dist/workflows/target-assessment/assemblers/trials.d.ts +0 -30
  93. package/dist/workflows/target-assessment/assemblers/trials.js +0 -75
  94. package/dist/workflows/target-assessment/collectors/index.d.ts +0 -114
  95. package/dist/workflows/target-assessment/collectors/index.js +0 -538
  96. package/dist/workflows/target-assessment/coverage.d.ts +0 -97
  97. package/dist/workflows/target-assessment/coverage.js +0 -86
  98. package/dist/workflows/target-assessment/decisions/index.d.ts +0 -86
  99. package/dist/workflows/target-assessment/decisions/index.js +0 -127
  100. package/dist/workflows/target-assessment/fanout/index.d.ts +0 -32
  101. package/dist/workflows/target-assessment/fanout/index.js +0 -156
  102. package/dist/workflows/target-assessment/investigation/index.d.ts +0 -97
  103. package/dist/workflows/target-assessment/investigation/index.js +0 -419
  104. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +0 -73
  105. package/dist/workflows/target-assessment/lib/approval-precedents.js +0 -185
  106. package/dist/workflows/target-assessment/lib/citation-validator.d.ts +0 -46
  107. package/dist/workflows/target-assessment/lib/citation-validator.js +0 -316
  108. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +0 -65
  109. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +0 -174
  110. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +0 -44
  111. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -152
  112. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +0 -20
  113. package/dist/workflows/target-assessment/lib/compute-derived.js +0 -261
  114. package/dist/workflows/target-assessment/lib/compute-selectivity.d.ts +0 -15
  115. package/dist/workflows/target-assessment/lib/compute-selectivity.js +0 -18
  116. package/dist/workflows/target-assessment/lib/dedup-modulators.d.ts +0 -37
  117. package/dist/workflows/target-assessment/lib/dedup-modulators.js +0 -157
  118. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.d.ts +0 -17
  119. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.js +0 -100
  120. package/dist/workflows/target-assessment/lib/expression-constants.d.ts +0 -21
  121. package/dist/workflows/target-assessment/lib/expression-constants.js +0 -21
  122. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +0 -20
  123. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +0 -36
  124. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +0 -72
  125. package/dist/workflows/target-assessment/lib/fda-label-safety.js +0 -166
  126. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -30
  127. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -44
  128. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +0 -34
  129. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +0 -63
  130. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +0 -19
  131. package/dist/workflows/target-assessment/lib/impc-organ-map.js +0 -39
  132. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.d.ts +0 -4
  133. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.js +0 -24
  134. package/dist/workflows/target-assessment/lib/llm-step.d.ts +0 -80
  135. package/dist/workflows/target-assessment/lib/llm-step.js +0 -101
  136. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +0 -40
  137. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +0 -138
  138. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.d.ts +0 -17
  139. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.js +0 -99
  140. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.d.ts +0 -2
  141. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +0 -51
  142. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +0 -25
  143. package/dist/workflows/target-assessment/lib/regulatory-actions.js +0 -176
  144. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +0 -44
  145. package/dist/workflows/target-assessment/lib/safety-corroboration.js +0 -273
  146. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +0 -61
  147. package/dist/workflows/target-assessment/lib/structured-llm.js +0 -102
  148. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +0 -59
  149. package/dist/workflows/target-assessment/lib/target-identity-filter.js +0 -95
  150. package/dist/workflows/target-assessment/lib/therapeutic-programs.d.ts +0 -20
  151. package/dist/workflows/target-assessment/lib/therapeutic-programs.js +0 -68
  152. package/dist/workflows/target-assessment/metrics.d.ts +0 -16
  153. package/dist/workflows/target-assessment/metrics.js +0 -30
  154. package/dist/workflows/target-assessment/phase4-assemble.d.ts +0 -3182
  155. package/dist/workflows/target-assessment/phase4-assemble.js +0 -39
  156. package/dist/workflows/target-assessment/phase5-persist.d.ts +0 -59
  157. package/dist/workflows/target-assessment/phase5-persist.js +0 -349
  158. package/dist/workflows/target-assessment/progress-stream.d.ts +0 -75
  159. package/dist/workflows/target-assessment/progress-stream.js +0 -110
  160. package/dist/workflows/target-assessment/progress.d.ts +0 -39
  161. package/dist/workflows/target-assessment/progress.js +0 -77
  162. package/dist/workflows/target-assessment/schemas.d.ts +0 -970
  163. package/dist/workflows/target-assessment/schemas.js +0 -318
  164. package/dist/workflows/target-assessment/steps/collectors/pathways.d.ts +0 -15
  165. package/dist/workflows/target-assessment/steps/collectors/pathways.js +0 -58
  166. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.d.ts +0 -34
  167. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.js +0 -31
  168. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.d.ts +0 -57
  169. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.js +0 -54
  170. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.d.ts +0 -35
  171. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.js +0 -32
  172. package/dist/workflows/target-assessment/steps/fanout/index.d.ts +0 -14
  173. package/dist/workflows/target-assessment/steps/fanout/index.js +0 -10
  174. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +0 -29
  175. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +0 -29
  176. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +0 -657
  177. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -34
  178. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +0 -806
  179. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -16
  180. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -28
  181. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -27
  182. package/dist/workflows/target-assessment/sub-workflows/index.d.ts +0 -15
  183. package/dist/workflows/target-assessment/sub-workflows/index.js +0 -11
  184. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.d.ts +0 -41
  185. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.js +0 -12
  186. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.d.ts +0 -41
  187. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.js +0 -12
  188. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +0 -37
  189. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.js +0 -12
  190. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.d.ts +0 -66
  191. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.js +0 -12
  192. package/dist/workflows/target-assessment/synthesis/index.d.ts +0 -607
  193. package/dist/workflows/target-assessment/synthesis/index.js +0 -354
  194. package/dist/workflows/target-assessment/synthesis-probes.d.ts +0 -56
  195. package/dist/workflows/target-assessment/synthesis-probes.js +0 -320
@@ -1,538 +0,0 @@
1
- /**
2
- * Phase-1 collector functions for the harness DBOS workflow.
3
- *
4
- * Each collector is a pure async function `(input: ResolvedTarget) =>
5
- * Promise<CoverageBundle>`. Bodies: try/catch wraps a `withHost`-
6
- * guarded tool-client call (or a couple in parallel) and returns
7
- * `{coverage: "available", data}` on success or `{coverage: "queried_no_data",
8
- * error}` on failure. A handful of collectors return
9
- * `{coverage: "not_loaded", reason}` when a required upstream identifier is
10
- * missing — that signals "we never tried" vs. "we tried and got nothing."
11
- *
12
- * No collector throws — coverage is the failure mode. The DBOS workflow body
13
- * wraps each call in `DBOS.runStep({name: "ta-collector:{collectorId}"})` so
14
- * recovery on a fresh replica replays the cached coverage envelope instead
15
- * of re-issuing the HTTP call.
16
- *
17
- * No `deps` parameter is threaded through today (collectors call the tool
18
- * clients directly) — every external request goes through the global
19
- * `withHost(...)` semaphore. The function signature reserves the future
20
- * `deps` slot for test injection without churning callers.
21
- */
22
- import { createNoopLogger } from "../../../lib/console-logger.js";
23
- import { withHost } from "../../../lib/host-concurrency.js";
24
- import { getMultiSpeciesExpression } from "../../../tools/lib/bgee-client.js";
25
- import { getSomaticMutationFrequencies } from "../../../tools/lib/cbioportal-client.js";
26
- import { getModulatorsViaActivity, getTargetModulators } from "../../../tools/lib/chembl-client.js";
27
- import { searchFailedTrials, searchTrialsForTarget } from "../../../tools/lib/clinical-trials-client.js";
28
- import { filterInformative, searchClinvar } from "../../../tools/lib/clinvar-client.js";
29
- import { getKoPhenotypeProfile } from "../../../tools/lib/impc-client.js";
30
- import { getFamilyHeterodimers } from "../../../tools/lib/iuphar-client.js";
31
- import { getGenePhenotypeProfile } from "../../../tools/lib/monarch-client.js";
32
- import { getFaersByDrug, getFaersSeriousness } from "../../../tools/lib/openfda-client.js";
33
- import { getBaselineExpression, getTargetSafetyLiabilities, searchTargetAssociations } from "../../../tools/lib/opentargets-client.js";
34
- import { getPathwayMemberships } from "../../../tools/lib/pathway-client.js";
35
- import { searchPubmed } from "../../../tools/lib/pubmed-client.js";
36
- import { getInteractionPartners } from "../../../tools/lib/string-client.js";
37
- import { findTherapeuticProgramsForTarget } from "../lib/therapeutic-programs.js";
38
- import { dedupPathwaysAcrossSpecies } from "../steps/collectors/pathways.js";
39
- function fail(err) {
40
- return {
41
- coverage: "queried_no_data",
42
- error: { message: err instanceof Error ? err.message : String(err) },
43
- };
44
- }
45
- // ── 1. cBioPortal ────────────────────────────────────────────────────
46
- export async function collectCbioportal(input) {
47
- try {
48
- const result = await withHost("cbioportal", () => getSomaticMutationFrequencies(input.geneSymbol));
49
- if (result.entrezGeneId == null) {
50
- return {
51
- coverage: "queried_no_data",
52
- error: {
53
- message: `cBioPortal could not resolve Entrez id for ${input.geneSymbol}`,
54
- },
55
- };
56
- }
57
- return { coverage: "available", data: result };
58
- }
59
- catch (err) {
60
- return fail(err);
61
- }
62
- }
63
- // ── 2. ChEMBL modulators ─────────────────────────────────────────────
64
- export async function collectChemblModulators(input, ctx) {
65
- const chemblId = input.ids.chembl;
66
- if (!chemblId) {
67
- return { coverage: "not_loaded", reason: "no ChEMBL target id resolved" };
68
- }
69
- try {
70
- const primaryRows = await withHost("chembl", () => getTargetModulators(chemblId, { minPhase: 2, limit: 200 }, ctx.logger));
71
- const activityRows = await withHost("chembl", () => getModulatorsViaActivity({
72
- target_chembl_id: chemblId,
73
- max_phase_min: 2,
74
- molecule_type: "Small molecule",
75
- }, ctx.logger));
76
- const primaryIds = new Set(primaryRows.map((m) => m.moleculeChemblId));
77
- const novelActivityRows = activityRows
78
- .filter((m) => !primaryIds.has(m.moleculeChemblId))
79
- .map((m) => ({ ...m, evidence_source: "chembl_activity" }));
80
- const modulators = [...primaryRows, ...novelActivityRows];
81
- return {
82
- coverage: "available",
83
- data: { targetChemblId: chemblId, modulators },
84
- };
85
- }
86
- catch (err) {
87
- return fail(err);
88
- }
89
- }
90
- // ── 3. ClinVar ───────────────────────────────────────────────────────
91
- export async function collectClinvar(input, ctx) {
92
- try {
93
- const result = await withHost("ncbi", () => searchClinvar(ctx.ncbiApiKey, input.geneSymbol, { limit: 200 }));
94
- const filtered = filterInformative(result.variants);
95
- return {
96
- coverage: "available",
97
- data: {
98
- totalFound: result.totalFound,
99
- variants: filtered.map((v) => ({
100
- variationId: v.variationId,
101
- title: v.title,
102
- clinicalSignificance: v.clinicalSignificance,
103
- reviewStatus: v.reviewStatus,
104
- conditions: v.conditions,
105
- molecularConsequence: v.molecularConsequence,
106
- accession: v.accession,
107
- })),
108
- },
109
- };
110
- }
111
- catch (err) {
112
- return fail(err);
113
- }
114
- }
115
- // ── 4. ClinicalTrials.gov ────────────────────────────────────────────
116
- function tagTrials(trials, collection_query, collection_channel) {
117
- return trials.map((trial) => ({
118
- ...trial,
119
- collection_query,
120
- collection_channel,
121
- }));
122
- }
123
- export async function collectCtgov(input) {
124
- try {
125
- const [active, failed] = await Promise.all([
126
- withHost("ctgov", () => searchTrialsForTarget(input.geneSymbol, { limit: 100 })),
127
- withHost("ctgov", () => searchFailedTrials(input.geneSymbol, 100)),
128
- ]);
129
- return {
130
- coverage: "available",
131
- data: {
132
- active: tagTrials(active.trials, input.geneSymbol, "target_symbol"),
133
- failed: tagTrials(failed.trials, input.geneSymbol, "failed_intervention"),
134
- },
135
- };
136
- }
137
- catch (err) {
138
- return fail(err);
139
- }
140
- }
141
- // ── 5. Expression — human (Open Targets baseline) ────────────────────
142
- export async function collectExpressionHuman(input) {
143
- const ensemblId = input.ids.ensembl;
144
- if (!ensemblId) {
145
- return { coverage: "not_loaded", reason: "no Ensembl id resolved" };
146
- }
147
- try {
148
- const expressions = await withHost("opentargets", () => getBaselineExpression(ensemblId));
149
- if (expressions.length === 0) {
150
- return {
151
- coverage: "queried_no_data",
152
- error: {
153
- message: `Open Targets returned no baseline expression for ${ensemblId}`,
154
- },
155
- };
156
- }
157
- // `baselineExpression` mixes datasources under one row type, and each one
158
- // carries its own unit: GTEx gives bulk-tissue TPM, Tabula Sapiens gives
159
- // single-cell CPM pseudobulk, and DICE gives immune-cell TPM. The bundle
160
- // declares one source and one unit, thus it can hold rows of one datasource
161
- // only. Tabula Sapiens also outnumbers GTEx about 20 to 1, thus an unfiltered
162
- // list drowns the tissue table in cell types.
163
- const gtex = expressions.filter((e) => e.datasourceId === "gtex");
164
- if (gtex.length === 0) {
165
- return {
166
- coverage: "queried_no_data",
167
- error: {
168
- message: `Open Targets carries no GTEx baseline expression for ${ensemblId} (${expressions.length} rows of other datasources dropped)`,
169
- },
170
- };
171
- }
172
- const tissues = gtex.map((e) => ({
173
- tissueLabel: e.tissueLabel,
174
- organSystem: e.organSystem,
175
- value: e.rna?.value ?? null,
176
- }));
177
- return {
178
- coverage: "available",
179
- data: {
180
- source: "gtex",
181
- unit: "tpm",
182
- normalization_notes: "Open Targets baseline expression, GTEx bulk-tissue rows only, as the median TPM of each tissue",
183
- tissues,
184
- },
185
- };
186
- }
187
- catch (err) {
188
- return fail(err);
189
- }
190
- }
191
- // ── 6. Expression — multi-species (Bgee) ─────────────────────────────
192
- export async function collectExpressionMultiSpecies(input) {
193
- try {
194
- const result = await withHost("bgee", () => getMultiSpeciesExpression(input.geneSymbol));
195
- if (!result.humanEnsemblId) {
196
- return {
197
- coverage: "queried_no_data",
198
- error: {
199
- message: `Bgee could not resolve Ensembl id for ${input.geneSymbol}`,
200
- },
201
- };
202
- }
203
- return {
204
- coverage: "available",
205
- data: {
206
- geneSymbol: result.geneSymbol,
207
- humanEnsemblId: result.humanEnsemblId,
208
- bySpecies: result.bySpecies.map((s) => ({
209
- species: s.species,
210
- taxonId: s.taxonId,
211
- ensemblId: s.ensemblId,
212
- source: "bgee",
213
- unit: "expression_score",
214
- normalization_notes: "Bgee expression score (0–100) bucketed into absent/low/medium/high; not a transcript-abundance unit",
215
- tissues: s.tissues.map((t) => ({
216
- tissue: t.tissue,
217
- rank: t.rank,
218
- expressionScore: t.expressionScore,
219
- })),
220
- })),
221
- notFound: result.notFound,
222
- },
223
- };
224
- }
225
- catch (err) {
226
- return fail(err);
227
- }
228
- }
229
- // ── 7. FAERS by target ───────────────────────────────────────────────
230
- export async function collectFaersByTarget(input) {
231
- const probe = input.geneSymbol;
232
- try {
233
- const [byDrug, seriousness] = await Promise.all([
234
- withHost("openfda", () => getFaersByDrug(probe, { limit: 25 })),
235
- withHost("openfda", () => getFaersSeriousness(probe)),
236
- ]);
237
- const total = byDrug.totalReports ?? 0;
238
- if (total === 0 && byDrug.adverseEvents.length === 0) {
239
- return {
240
- coverage: "queried_no_data",
241
- error: { message: `FAERS returned no reports for symbol ${probe}` },
242
- };
243
- }
244
- return {
245
- coverage: "available",
246
- data: {
247
- drugProbed: probe,
248
- totalReports: byDrug.totalReports ?? null,
249
- topReactions: byDrug.adverseEvents,
250
- seriousness,
251
- },
252
- };
253
- }
254
- catch (err) {
255
- return fail(err);
256
- }
257
- }
258
- // ── 8. Family complexes (IUPHAR) ─────────────────────────────────────
259
- export async function collectFamilyComplexes(input, ctx) {
260
- const geneSymbol = input.geneSymbol;
261
- const uniprot = input.ids.uniprot;
262
- const lookup = uniprot ?? geneSymbol;
263
- if (!lookup) {
264
- return {
265
- coverage: "not_loaded",
266
- reason: "no gene symbol or UniProt accession",
267
- };
268
- }
269
- try {
270
- const heterodimers = await withHost("iuphar", () => getFamilyHeterodimers(lookup));
271
- if (heterodimers.length === 0) {
272
- return {
273
- coverage: "queried_no_data",
274
- error: {
275
- message: `IUPHAR returned no heterodimer complexes for ${lookup}`,
276
- },
277
- };
278
- }
279
- const accessorySet = new Set();
280
- const complexes = heterodimers.map((h) => {
281
- const accessoryNames = h.accessories.map((a) => a.name).filter(Boolean);
282
- for (const n of accessoryNames)
283
- accessorySet.add(n);
284
- return {
285
- complexName: h.complex.name,
286
- complexId: h.complex.targetId,
287
- accessoryNames,
288
- subunitNames: h.subunits.map((s) => s.name).filter(Boolean),
289
- };
290
- });
291
- return {
292
- coverage: "available",
293
- data: {
294
- primaryTargetGene: geneSymbol,
295
- primaryTargetUniprot: uniprot,
296
- accessoryProteinNames: [...accessorySet].sort(),
297
- complexes,
298
- },
299
- };
300
- }
301
- catch (err) {
302
- // `getFamilyHeterodimers` answers an unknown target with an empty list, thus
303
- // a throw here is always an unexpected cause. The IUPHAR outage of 2026 hid
304
- // behind this envelope for months, because `queried_no_data` reads as a
305
- // clean miss to every consumer of the dossier.
306
- const log = (ctx.logger ?? createNoopLogger()).named("ta-collector.family-complexes");
307
- log.error("IUPHAR family-complex lookup failed", { lookup, ...log.errorFields(err) });
308
- return fail(err);
309
- }
310
- }
311
- // ── 9. IMPC ───────────────────────────────────────────────────────────
312
- export async function collectImpc(input) {
313
- try {
314
- const profile = await withHost("ncbi", () => getKoPhenotypeProfile(input.geneSymbol));
315
- if (!profile.mouseMarkerSymbol) {
316
- return {
317
- coverage: "queried_no_data",
318
- error: {
319
- message: `IMPC has no mouse marker for ${input.geneSymbol}`,
320
- },
321
- };
322
- }
323
- return {
324
- coverage: "available",
325
- data: {
326
- mouseMarkerSymbol: profile.mouseMarkerSymbol,
327
- mgiAccessionId: profile.mgiAccessionId,
328
- viability: profile.viability,
329
- viabilityCalls: profile.viabilityCalls,
330
- mpTerms: profile.mpTerms,
331
- organSystems: profile.organSystems,
332
- sexDimorphic: profile.sexDimorphic,
333
- phenotypeCount: profile.phenotypeCount,
334
- },
335
- };
336
- }
337
- catch (err) {
338
- return fail(err);
339
- }
340
- }
341
- // ── 10. Monarch Initiative ───────────────────────────────────────────
342
- export async function collectMonarch(input) {
343
- // Monarch resolves prefixed identifiers; HGNC is its native gene namespace
344
- // and NCBI Gene is the fallback it also indexes.
345
- const geneCurie = input.ids.hgnc ?? (input.ids.entrez ? `NCBIGene:${input.ids.entrez}` : null);
346
- if (!geneCurie) {
347
- return {
348
- coverage: "not_loaded",
349
- reason: "no HGNC or NCBI Gene id resolved",
350
- };
351
- }
352
- try {
353
- const profile = await withHost("monarch", () => getGenePhenotypeProfile(geneCurie));
354
- if (profile.phenotypes.length === 0) {
355
- return {
356
- coverage: "queried_no_data",
357
- error: {
358
- message: `Monarch holds no human phenotype associations for ${geneCurie}`,
359
- },
360
- };
361
- }
362
- return {
363
- coverage: "available",
364
- data: {
365
- geneCurie: profile.geneCurie,
366
- phenotypes: profile.phenotypes.map((p) => ({
367
- hpoId: p.hpoId,
368
- label: p.label,
369
- ancestorIds: p.ancestorIds,
370
- publications: p.publications,
371
- diseaseContext: p.diseaseContext,
372
- frequencyPercent: p.frequencyPercent,
373
- primaryKnowledgeSource: p.primaryKnowledgeSource,
374
- })),
375
- },
376
- };
377
- }
378
- catch (err) {
379
- return fail(err);
380
- }
381
- }
382
- // ── 11. Open Targets ─────────────────────────────────────────────────
383
- export async function collectOpenTargets(input) {
384
- const ensemblId = input.ids.ensembl;
385
- if (!ensemblId) {
386
- return { coverage: "not_loaded", reason: "no Ensembl id resolved" };
387
- }
388
- try {
389
- const [info, safety, expression] = await Promise.all([
390
- withHost("opentargets", () => searchTargetAssociations(ensemblId, 50)),
391
- withHost("opentargets", () => getTargetSafetyLiabilities(ensemblId)),
392
- withHost("opentargets", () => getBaselineExpression(ensemblId)),
393
- ]);
394
- if (!info) {
395
- return {
396
- coverage: "queried_no_data",
397
- error: { message: `no Open Targets entry for ${ensemblId}` },
398
- };
399
- }
400
- return {
401
- coverage: "available",
402
- data: {
403
- ensemblId: info.ensemblId,
404
- approvedSymbol: info.approvedSymbol,
405
- approvedName: info.approvedName,
406
- tractability: info.tractability,
407
- associations: info.associations,
408
- safetyLiabilities: safety?.safetyLiabilities ?? [],
409
- baselineExpression: expression.map((e) => ({
410
- tissueId: e.tissueId,
411
- tissueLabel: e.tissueLabel,
412
- organSystem: e.organSystem,
413
- datasourceId: e.datasourceId,
414
- rna: e.rna,
415
- })),
416
- },
417
- };
418
- }
419
- catch (err) {
420
- return fail(err);
421
- }
422
- }
423
- // ── 12. Pathways (Reactome + KEGG via the unified client) ────────────
424
- export async function collectPathways(input) {
425
- try {
426
- const raw = await withHost("reactome", () => getPathwayMemberships(input.geneSymbol));
427
- const rows = raw.map((p) => ({
428
- id: p.id,
429
- name: p.name,
430
- source: p.source,
431
- url: p.url ?? "",
432
- entity_uniprots: p.entity_uniprots,
433
- }));
434
- const cleaned = dedupPathwaysAcrossSpecies(rows, {
435
- geneSymbolEchoFilter: input.geneSymbol,
436
- assessmentUniprot: input.ids.uniprot ?? undefined,
437
- });
438
- if (cleaned.length === 0) {
439
- return {
440
- coverage: "queried_no_data",
441
- error: {
442
- message: `KEGG and Reactome returned no pathways for ${input.geneSymbol}`,
443
- },
444
- };
445
- }
446
- return { coverage: "available", data: { pathways: cleaned } };
447
- }
448
- catch (err) {
449
- return fail(err);
450
- }
451
- }
452
- // ── 13. PubMed index ─────────────────────────────────────────────────
453
- export async function collectPubmedIndex(input, ctx) {
454
- const query = `"${input.geneSymbol}"[Gene]`;
455
- try {
456
- const result = await withHost("ncbi", () => searchPubmed(ctx.ncbiApiKey, query, {
457
- maxResults: 50,
458
- sort: "relevance",
459
- }));
460
- if (result.totalFound === 0) {
461
- return {
462
- coverage: "queried_no_data",
463
- error: { message: `PubMed returned no hits for ${query}` },
464
- };
465
- }
466
- return {
467
- coverage: "available",
468
- data: {
469
- totalFound: result.totalFound,
470
- topPmids: result.results.map((r) => r.pmid),
471
- results: result.results,
472
- },
473
- };
474
- }
475
- catch (err) {
476
- return fail(err);
477
- }
478
- }
479
- // ── 14. STRING PPI ───────────────────────────────────────────────────
480
- export async function collectStringPpi(input) {
481
- try {
482
- const partners = await withHost("string", () => getInteractionPartners([input.geneSymbol], {
483
- species: 9606,
484
- minScore: 400,
485
- limit: 50,
486
- }));
487
- if (partners.length === 0) {
488
- return {
489
- coverage: "queried_no_data",
490
- error: {
491
- message: `STRING returned no partners for ${input.geneSymbol}`,
492
- },
493
- };
494
- }
495
- return { coverage: "available", data: { partners } };
496
- }
497
- catch (err) {
498
- return fail(err);
499
- }
500
- }
501
- // ── 15. Therapeutic programs (non-ChEMBL curated set) ────────────────
502
- export async function collectTherapeuticPrograms(input) {
503
- const programs = findTherapeuticProgramsForTarget({
504
- geneSymbol: input.geneSymbol,
505
- uniprot: input.ids.uniprot ?? null,
506
- });
507
- if (programs.length === 0) {
508
- return {
509
- coverage: "queried_no_data",
510
- error: { message: "no non-ChEMBL therapeutic programs registered" },
511
- };
512
- }
513
- return { coverage: "available", data: { programs } };
514
- }
515
- // ── Manifest ─────────────────────────────────────────────────────────
516
- /**
517
- * Stable id used as the durable-step name suffix —
518
- * `ta-collector:{collectorId}`. The DBOS workflow body iterates this
519
- * manifest so adding a collector is one entry here + one new exported
520
- * function above, with no scheduler edits.
521
- */
522
- export const COLLECTOR_MANIFEST = [
523
- { id: "cbioportal", run: collectCbioportal },
524
- { id: "chembl-modulators", run: collectChemblModulators },
525
- { id: "clinvar", run: collectClinvar },
526
- { id: "ctgov", run: collectCtgov },
527
- { id: "expression-human", run: collectExpressionHuman },
528
- { id: "expression-multi-species", run: collectExpressionMultiSpecies },
529
- { id: "faers-by-target", run: collectFaersByTarget },
530
- { id: "family-complexes", run: collectFamilyComplexes },
531
- { id: "impc", run: collectImpc },
532
- { id: "monarch", run: collectMonarch },
533
- { id: "opentargets", run: collectOpenTargets },
534
- { id: "pathways", run: collectPathways },
535
- { id: "pubmed-index", run: collectPubmedIndex },
536
- { id: "string-ppi", run: collectStringPpi },
537
- { id: "therapeutic-programs", run: collectTherapeuticPrograms },
538
- ];
@@ -1,97 +0,0 @@
1
- /**
2
- * Shared coverage discriminator for target-assessment workflow steps.
3
- *
4
- * Per design §6, every collector / decision / fan-out / synthesis step
5
- * MUST wrap its body in try/catch and return a `{ coverage, ... }` shape
6
- * instead of throwing. Only Phase 0 (target resolution) is allowed to
7
- * throw — that legitimately aborts the workflow.
8
- *
9
- * `CoverageSchema` in `contracts/target-dossier.ts` is the one enumeration of
10
- * the coverage states, and the helpers below emit its members. A step envelope
11
- * carries the three states a step can reach on its own; `filtered` is a
12
- * judgement one of our own filters makes, so only the assembler helpers below
13
- * produce it.
14
- */
15
- import { z } from "zod";
16
- export declare const SerializedErrorSchema: z.ZodObject<{
17
- message: z.ZodString;
18
- name: z.ZodOptional<z.ZodString>;
19
- stack: z.ZodOptional<z.ZodString>;
20
- }, z.core.$strip>;
21
- export type SerializedError = z.infer<typeof SerializedErrorSchema>;
22
- /**
23
- * Build the schema for a `{ coverage, data, error? }` step output.
24
- *
25
- * Use as: `outputSchema: withCoverage(MyDataSchema)` where `MyDataSchema`
26
- * is the shape of the Phase-1 / Phase-2 / etc. payload for this step.
27
- */
28
- export declare function withCoverage<TData extends z.ZodTypeAny>(dataSchema: TData): z.ZodDiscriminatedUnion<[z.ZodObject<{
29
- coverage: z.ZodLiteral<"available">;
30
- data: TData;
31
- }, z.core.$strip>, z.ZodObject<{
32
- coverage: z.ZodLiteral<"queried_no_data">;
33
- error: z.ZodOptional<z.ZodObject<{
34
- message: z.ZodString;
35
- name: z.ZodOptional<z.ZodString>;
36
- stack: z.ZodOptional<z.ZodString>;
37
- }, z.core.$strip>>;
38
- }, z.core.$strip>, z.ZodObject<{
39
- coverage: z.ZodLiteral<"not_loaded">;
40
- reason: z.ZodOptional<z.ZodString>;
41
- }, z.core.$strip>], "coverage">;
42
- /**
43
- * Convenience wrapper for `{ rows: T[] }` payloads. Maps an empty rows
44
- * array to `coverage: "queried_no_data"` so the dossier discriminator's
45
- * "available ≡ we have data" invariant holds without per-callsite
46
- * length checks. Use at any assembler callsite that emits a `rows`
47
- * shape from a query that may legitimately return zero items.
48
- */
49
- export declare function coverageFromRows<T>(rows: T[], opts?: {
50
- reason?: string;
51
- }): {
52
- coverage: "available";
53
- data: {
54
- rows: T[];
55
- };
56
- } | {
57
- coverage: "queried_no_data";
58
- error: SerializedError;
59
- };
60
- /**
61
- * Build a dossier-section envelope for a row list one of our own filters
62
- * narrowed.
63
- *
64
- * A filter is not the same as an empty upstream, and the difference is the
65
- * whole point of the four-state contract. Deciding coverage from the source
66
- * and then filtering the payload loses it: a section whose every row our
67
- * threshold discarded reports a clean `available` holding nothing, which reads
68
- * as "the source had nothing to say".
69
- *
70
- * So the decision is made once, from both counts:
71
- * - nothing survived and something was dropped → `filtered`, naming the
72
- * filter and the count it discarded, with no `data`;
73
- * - some survived and some were dropped → `available` carrying
74
- * `dropped_count`, so the section does not overstate its completeness;
75
- * - nothing survived and nothing was dropped → `queried_no_data`: the list
76
- * was empty before any filter of ours ran.
77
- */
78
- export declare function coverageFromFilteredRows<TData>(args: {
79
- data: TData;
80
- retainedCount: number;
81
- droppedCount: number;
82
- /** What ran, in a reader's terms — carried verbatim on the `filtered` branch. */
83
- filter: string;
84
- /** Why the source itself held nothing, used only when no filter dropped anything. */
85
- emptyReason: string;
86
- }): {
87
- coverage: "available";
88
- data: TData;
89
- dropped_count?: number;
90
- } | {
91
- coverage: "filtered";
92
- filter: string;
93
- dropped_count: number;
94
- } | {
95
- coverage: "queried_no_data";
96
- error: SerializedError;
97
- };