@inflexa-ai/harness 0.16.0 → 0.18.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 (295) hide show
  1. package/dist/agents/conversation-agent.d.ts +3 -0
  2. package/dist/agents/conversation-agent.js +5 -2
  3. package/dist/agents/report-builder.d.ts +2 -0
  4. package/dist/agents/report-builder.js +2 -0
  5. package/dist/agents/sandbox/shared.d.ts +3 -0
  6. package/dist/agents/sandbox/shared.js +5 -0
  7. package/dist/agents/sandbox/types.d.ts +1 -1
  8. package/dist/app/chat-turn.d.ts +2 -0
  9. package/dist/app/chat-turn.js +11 -2
  10. package/dist/app/spawn-report-session.d.ts +85 -0
  11. package/dist/app/spawn-report-session.js +83 -0
  12. package/dist/app/synthesize-run.d.ts +3 -0
  13. package/dist/app/synthesize-run.js +2 -1
  14. package/dist/citations/__fixtures__/resolver.d.ts +3 -0
  15. package/dist/citations/__fixtures__/resolver.js +9 -0
  16. package/dist/citations/aggregate.d.ts +3 -0
  17. package/dist/citations/aggregate.js +44 -0
  18. package/dist/citations/clients/arxiv.d.ts +6 -0
  19. package/dist/citations/clients/arxiv.js +34 -0
  20. package/dist/citations/clients/common.d.ts +11 -0
  21. package/dist/citations/clients/common.js +43 -0
  22. package/dist/citations/clients/crossref.d.ts +8 -0
  23. package/dist/citations/clients/crossref.js +98 -0
  24. package/dist/citations/clients/doi-registry.d.ts +3 -0
  25. package/dist/citations/clients/doi-registry.js +95 -0
  26. package/dist/citations/clients/pubmed.d.ts +6 -0
  27. package/dist/citations/clients/pubmed.js +83 -0
  28. package/dist/citations/clients/semantic-scholar.d.ts +6 -0
  29. package/dist/citations/clients/semantic-scholar.js +60 -0
  30. package/dist/citations/compare.d.ts +9 -0
  31. package/dist/citations/compare.js +72 -0
  32. package/dist/citations/match.d.ts +11 -0
  33. package/dist/citations/match.js +173 -0
  34. package/dist/citations/normalize.d.ts +31 -0
  35. package/dist/citations/normalize.js +187 -0
  36. package/dist/citations/plan.d.ts +2 -0
  37. package/dist/citations/plan.js +59 -0
  38. package/dist/citations/resolve.d.ts +28 -0
  39. package/dist/citations/resolve.js +238 -0
  40. package/dist/citations/types.d.ts +583 -0
  41. package/dist/citations/types.js +129 -0
  42. package/dist/contracts/chat-events.d.ts +9 -0
  43. package/dist/contracts/index.d.ts +6 -2
  44. package/dist/contracts/index.js +3 -1
  45. package/dist/contracts/organ-system.d.ts +49 -0
  46. package/dist/contracts/organ-system.js +67 -0
  47. package/dist/contracts/report-blocks.d.ts +564 -0
  48. package/dist/contracts/report-blocks.js +117 -0
  49. package/dist/contracts/report-reference.d.ts +459 -0
  50. package/dist/contracts/report-reference.js +256 -0
  51. package/dist/contracts/schemas/chat-events.d.ts +2 -0
  52. package/dist/contracts/schemas/chat-events.js +2 -0
  53. package/dist/contracts/schemas/chat-parts.d.ts +8 -8
  54. package/dist/contracts/severity.d.ts +15 -0
  55. package/dist/contracts/severity.js +10 -0
  56. package/dist/contracts/target-dossier.d.ts +8673 -12970
  57. package/dist/contracts/target-dossier.js +534 -591
  58. package/dist/data/safety-panel-schema.d.ts +18 -5
  59. package/dist/data/safety-panel-schema.js +4 -4
  60. package/dist/execution/artifact-metadata.js +1 -0
  61. package/dist/execution/report-runner.js +1 -3
  62. package/dist/execution/run-synthesis.d.ts +3 -0
  63. package/dist/execution/run-synthesis.js +3 -0
  64. package/dist/index.d.ts +12 -1
  65. package/dist/index.js +22 -0
  66. package/dist/lib/async-utils.d.ts +16 -4
  67. package/dist/lib/async-utils.js +20 -15
  68. package/dist/lib/cache.d.ts +8 -0
  69. package/dist/lib/cache.js +35 -0
  70. package/dist/lib/host-concurrency.js +1 -0
  71. package/dist/lib/rate-limit.d.ts +35 -0
  72. package/dist/lib/rate-limit.js +121 -0
  73. package/dist/literature/sources/arxiv.d.ts +22 -0
  74. package/dist/literature/sources/arxiv.js +103 -0
  75. package/dist/literature/sources/http.d.ts +28 -0
  76. package/dist/literature/sources/http.js +97 -0
  77. package/dist/literature/sources/pubmed.d.ts +68 -0
  78. package/dist/literature/sources/pubmed.js +266 -0
  79. package/dist/literature/sources/semantic-scholar.d.ts +24 -0
  80. package/dist/literature/sources/semantic-scholar.js +71 -0
  81. package/dist/loop/__fixtures__/scripted-provider.js +23 -2
  82. package/dist/loop/run-agent.js +58 -10
  83. package/dist/loop/tool-detail.d.ts +40 -0
  84. package/dist/loop/tool-detail.js +35 -3
  85. package/dist/loop/types.d.ts +13 -0
  86. package/dist/memory/thread-history.d.ts +10 -0
  87. package/dist/memory/thread-history.js +12 -1
  88. package/dist/memory/thread-store.d.ts +142 -25
  89. package/dist/memory/thread-store.js +228 -40
  90. package/dist/prompts/literature-reviewer.d.ts +1 -1
  91. package/dist/prompts/literature-reviewer.js +2 -0
  92. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
  93. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
  94. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
  95. package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
  96. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
  97. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
  98. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
  99. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
  100. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
  101. package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
  102. package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
  103. package/dist/prompts/target-assessment/investigation/index.js +3 -0
  104. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
  105. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
  106. package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
  107. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
  108. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
  109. package/dist/providers/__fixtures__/session.d.ts +5 -4
  110. package/dist/providers/__fixtures__/session.js +4 -4
  111. package/dist/report-model/block-walk.d.ts +56 -0
  112. package/dist/report-model/block-walk.js +89 -0
  113. package/dist/report-model/draft-finish.d.ts +60 -0
  114. package/dist/report-model/draft-finish.js +56 -0
  115. package/dist/report-model/draft-operations.d.ts +125 -0
  116. package/dist/report-model/draft-operations.js +341 -0
  117. package/dist/report-model/draft-read.d.ts +59 -0
  118. package/dist/report-model/draft-read.js +109 -0
  119. package/dist/report-model/draft.d.ts +51 -0
  120. package/dist/report-model/draft.js +32 -0
  121. package/dist/report-model/fixture-resolver.d.ts +8 -0
  122. package/dist/report-model/fixture-resolver.js +252 -0
  123. package/dist/report-model/mint-snapshot.d.ts +30 -0
  124. package/dist/report-model/mint-snapshot.js +40 -0
  125. package/dist/report-model/reference-resolver.d.ts +113 -0
  126. package/dist/report-model/reference-resolver.js +60 -0
  127. package/dist/report-model/structural-validation.d.ts +22 -0
  128. package/dist/report-model/structural-validation.js +68 -0
  129. package/dist/report-model/validate.d.ts +40 -0
  130. package/dist/report-model/validate.js +80 -0
  131. package/dist/report-render/chart.d.ts +33 -0
  132. package/dist/report-render/chart.js +538 -0
  133. package/dist/report-render/page.d.ts +127 -0
  134. package/dist/report-render/page.js +307 -0
  135. package/dist/report-render/references.d.ts +32 -0
  136. package/dist/report-render/references.js +44 -0
  137. package/dist/report-render/render.d.ts +25 -0
  138. package/dist/report-render/render.js +125 -0
  139. package/dist/report-render/script-json.d.ts +13 -0
  140. package/dist/report-render/script-json.js +15 -0
  141. package/dist/report-render/types.d.ts +39 -0
  142. package/dist/report-render/types.js +10 -0
  143. package/dist/report-render/views/chart-view.d.ts +11 -0
  144. package/dist/report-render/views/chart-view.js +23 -0
  145. package/dist/report-render/views/page-view.d.ts +20 -0
  146. package/dist/report-render/views/page-view.js +49 -0
  147. package/dist/report-render/views/prose.d.ts +28 -0
  148. package/dist/report-render/views/prose.js +75 -0
  149. package/dist/report-render/views/references-view.d.ts +20 -0
  150. package/dist/report-render/views/references-view.js +64 -0
  151. package/dist/report-render/views/values.d.ts +42 -0
  152. package/dist/report-render/views/values.js +43 -0
  153. package/dist/runtime/assemble.d.ts +50 -4
  154. package/dist/runtime/assemble.js +47 -3
  155. package/dist/runtime/boot.d.ts +3 -3
  156. package/dist/runtime/boot.js +22 -5
  157. package/dist/runtime/dbos.d.ts +4 -4
  158. package/dist/state/artifacts.d.ts +16 -0
  159. package/dist/state/artifacts.js +18 -0
  160. package/dist/state/init.js +77 -1
  161. package/dist/state/purge-analysis.js +1 -0
  162. package/dist/state/report-versions.d.ts +132 -0
  163. package/dist/state/report-versions.js +194 -0
  164. package/dist/state/schema.d.ts +2 -2
  165. package/dist/tasks/data-profile.js +1 -0
  166. package/dist/tools/bio/chembl.js +1 -0
  167. package/dist/tools/bio/comptox.js +1 -0
  168. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  169. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  170. package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -1
  171. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  172. package/dist/tools/bio/keys.d.ts +4 -2
  173. package/dist/tools/bio/keys.js +3 -2
  174. package/dist/tools/bio/lookup-annotation.js +1 -0
  175. package/dist/tools/bio/opentargets.d.ts +3 -3
  176. package/dist/tools/bio/opentargets.js +4 -3
  177. package/dist/tools/bio/pubchem.js +1 -0
  178. package/dist/tools/bio/pubmed.js +4 -4
  179. package/dist/tools/bio/search-clinical-trials.js +1 -0
  180. package/dist/tools/bio/search-faers.js +1 -0
  181. package/dist/tools/bio/search-geo-datasets.js +1 -0
  182. package/dist/tools/bio/search-interactions.js +1 -0
  183. package/dist/tools/bio/target-safety.d.ts +4 -6
  184. package/dist/tools/bio/target-safety.js +7 -6
  185. package/dist/tools/define-tool.d.ts +19 -12
  186. package/dist/tools/define-tool.js +4 -2
  187. package/dist/tools/display/show-user.js +1 -0
  188. package/dist/tools/iterate-report.js +2 -0
  189. package/dist/tools/lib/chembl-client.d.ts +2 -0
  190. package/dist/tools/lib/chembl-client.js +1 -0
  191. package/dist/tools/lib/monarch-client.d.ts +42 -0
  192. package/dist/tools/lib/monarch-client.js +100 -0
  193. package/dist/tools/lib/ncbi-utils.d.ts +3 -58
  194. package/dist/tools/lib/ncbi-utils.js +2 -286
  195. package/dist/tools/lib/pubmed-client.d.ts +5 -12
  196. package/dist/tools/lib/pubmed-client.js +20 -79
  197. package/dist/tools/report/build-report.js +1 -0
  198. package/dist/tools/report/mint-preview-url.js +1 -0
  199. package/dist/tools/report/preview-snapshot.js +1 -0
  200. package/dist/tools/report/submit-report.js +1 -0
  201. package/dist/tools/report/version-fs.js +4 -0
  202. package/dist/tools/report-authoring/authoring-tools.d.ts +134 -0
  203. package/dist/tools/report-authoring/authoring-tools.js +352 -0
  204. package/dist/tools/research/context7-docs.js +2 -0
  205. package/dist/tools/research/generate-analogy-report.js +3 -2
  206. package/dist/tools/research/generate-plan.js +3 -0
  207. package/dist/tools/research/index.d.ts +1 -0
  208. package/dist/tools/research/index.js +1 -0
  209. package/dist/tools/research/inspect-data-profile.js +4 -0
  210. package/dist/tools/research/inspect-run.d.ts +1 -1
  211. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  212. package/dist/tools/research/literature-reviewer.js +3 -0
  213. package/dist/tools/research/resolve-citation.d.ts +3 -0
  214. package/dist/tools/research/resolve-citation.js +15 -0
  215. package/dist/tools/research/search-arxiv.d.ts +3 -19
  216. package/dist/tools/research/search-arxiv.js +10 -105
  217. package/dist/tools/research/search-github-repos.js +1 -0
  218. package/dist/tools/research/search-semantic-scholar.d.ts +8 -33
  219. package/dist/tools/research/search-semantic-scholar.js +30 -88
  220. package/dist/tools/sandbox/list-available-packages.js +23 -0
  221. package/dist/tools/sandbox/list-available-refs.js +37 -2
  222. package/dist/tools/sandbox/report-blocker.js +1 -0
  223. package/dist/tools/sandbox/skills.js +2 -0
  224. package/dist/tools/workspace/file-stat.js +1 -0
  225. package/dist/tools/workspace/show-file.js +2 -0
  226. package/dist/tools/workspace/show-plan.js +1 -0
  227. package/dist/workflows/execute-analysis.d.ts +8 -1
  228. package/dist/workflows/execute-analysis.js +6 -5
  229. package/dist/workflows/execute-target-assessment.d.ts +13 -2
  230. package/dist/workflows/execute-target-assessment.js +78 -16
  231. package/dist/workflows/sandbox-step.d.ts +5 -0
  232. package/dist/workflows/sandbox-step.js +1 -0
  233. package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
  234. package/dist/workflows/target-assessment/assemblers/index.js +1 -1
  235. package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
  236. package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
  237. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
  238. package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
  239. package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
  240. package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
  241. package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
  242. package/dist/workflows/target-assessment/collectors/index.js +48 -5
  243. package/dist/workflows/target-assessment/coverage.d.ts +43 -27
  244. package/dist/workflows/target-assessment/coverage.js +33 -29
  245. package/dist/workflows/target-assessment/fanout/index.js +1 -0
  246. package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
  247. package/dist/workflows/target-assessment/investigation/index.js +419 -0
  248. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
  249. package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
  250. package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
  251. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
  252. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
  253. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
  254. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
  255. package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
  256. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
  257. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
  258. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
  259. package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
  260. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
  261. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
  262. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
  263. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
  264. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
  265. package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
  266. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
  267. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
  268. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
  269. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
  270. package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
  271. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
  272. package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
  273. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
  274. package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
  275. package/dist/workflows/target-assessment/metrics.d.ts +1 -6
  276. package/dist/workflows/target-assessment/metrics.js +1 -13
  277. package/dist/workflows/target-assessment/phase4-assemble.d.ts +2149 -809
  278. package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
  279. package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
  280. package/dist/workflows/target-assessment/phase5-persist.js +56 -173
  281. package/dist/workflows/target-assessment/schemas.d.ts +48 -3
  282. package/dist/workflows/target-assessment/schemas.js +19 -0
  283. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
  284. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
  285. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +27 -63
  286. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
  287. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +28 -155
  288. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
  289. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
  290. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
  291. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
  292. package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
  293. package/dist/workflows/target-assessment/synthesis/index.js +4 -1
  294. package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
  295. package/package.json +4 -1
@@ -2,28 +2,46 @@
2
2
  * Phase-4 deterministic assemblers.
3
3
  *
4
4
  * Each assembler maps Phase-1 / Phase-2 / Phase-3 outputs onto the
5
- * corresponding dossier section schema. Coverage is honored end-to-end:
6
- * when an upstream came back `queried_no_data` or the derived row set is
7
- * empty, the section is marked `queried_no_data` / `not_loaded` rather
8
- * than fabricated. Aggregate rows preserve their contributing evidence
9
- * under `evidence: [...]` arrays.
5
+ * corresponding dossier section schema. Coverage is honored end-to-end: an
6
+ * upstream that came back empty or was never consulted yields
7
+ * `queried_no_data` / `not_loaded` rather than a fabricated section.
8
+ *
9
+ * Where a section applies a threshold of its own, the decision runs through
10
+ * `coverageFromFilteredRows` over both counts — what survived and what the
11
+ * threshold discarded — so a section our own filter emptied reports
12
+ * `filtered` rather than an `available` section holding nothing. Deciding
13
+ * coverage first and filtering the payload afterwards is the shape that loses
14
+ * that distinction.
15
+ *
16
+ * Aggregate rows preserve their contributing evidence under `evidence: [...]`
17
+ * arrays.
10
18
  */
19
+ import { classifyClinicalEvidenceTrial, } from "../lib/clinical-evidence-attribution.js";
11
20
  import { inferTherapeuticArea, getBenchmarks, getDatasetAttribution } from "../../../tools/lib/clinical-benchmarks-client.js";
12
21
  import { inferModalityFromFamily } from "../../../tools/lib/protein-family-modality.js";
13
22
  import { resolveFamilySiblingUniprots, resolveOnTargetChemblIds } from "../lib/target-identity-filter.js";
14
23
  import { getDrugPrimaryTargetUniprots } from "../../../tools/lib/chembl-client.js";
15
24
  import { annotateOffTargetPanel } from "../lib/clinical-consequence-annotator.js";
16
- import { coverageFromRows } from "../coverage.js";
25
+ import { coverageFromFilteredRows, coverageFromRows } from "../coverage.js";
26
+ import { toOrganSystems } from "../lib/impc-organ-map.js";
17
27
  import { fetchRegulatoryActions } from "../lib/regulatory-actions.js";
18
28
  import { HIGH_EXPRESSION_TPM_THRESHOLD } from "../lib/expression-constants.js";
19
29
  export { HIGH_EXPRESSION_TPM_THRESHOLD };
20
30
  import { resolveModulatorMoleculeType } from "../lib/dedup-modulators.js";
21
31
  import { searchFailedTrialsForDrugNames } from "../../../tools/lib/clinical-trials-client.js";
22
- import { aggregateFaersAcrossModulators, aggregateTrialAes, aggregateOffTargetPanel, aggregateClassPrecedent, buildOrganRollup, assembleDrugInteractions, assembleLiabilitySummary, meetsTpmFloor, isSafetyRelevant, } from "./safety.js";
32
+ import { aggregateFaersAcrossModulators, aggregateTrialAes, aggregateOffTargetPanel, aggregateClassPrecedent, buildOrganRollup, assembleDrugInteractions, assembleLiabilitySummary, claimSupportEvidence, meetsTpmFloor, isSafetyRelevant, resolveTissueOrganSystem, } from "./safety.js";
23
33
  import { assemblePreclinicalLiterature, assembleKoSupportingLiterature, tallyInternalReferences, assembleKeyPapers, assembleEvidenceTimeline, assembleTranslationalChain, assembleMolecularInteractions, assembleBiomarkerPotential, assembleResistanceEvidence, assembleCombinationEvidence, assembleEvidenceConflicts, assembleAdditionalEvidence, isSelfReference, isClinicalMeasurement, indicationCompositeScore, partitionTrialsByAttribution, classifyClinicalTrialConfidence, assembleDiscoveryTrials, dedupePpiPartners, } from "./literature.js";
24
34
  import { buildTrialOutcomeFilter, aggregateTrialOutcomes } from "./trials.js";
25
35
  import { buildFailureCategoryDiscriminated } from "./literature.js";
26
36
  const NOT_LOADED_PHASE5 = "Phase 5 synthesis not yet implemented";
37
+ /** What emptied `clinical_development.trials` when nothing survived. */
38
+ const ACTIVE_TRIALS_FILTER = "text match confidence below medium, attribution not eligible for toxicology aggregation, or attributed to a related family receptor";
39
+ /** What emptied the two failed-trial sections when nothing survived. */
40
+ const FAILED_TRIALS_FILTER = "attribution not eligible for toxicology aggregation, or attributed to a related family receptor";
41
+ /** What emptied the two association sections when nothing survived. */
42
+ const INDICATIONS_FILTER = "association restates the target itself, names a clinical measurement rather than a disease, or carries no scored evidence source";
43
+ /** Sections the workflow body stamps between assembly and synthesis. */
44
+ const NOT_LOADED_POST_ASSEMBLY = "assembled after Phase 4, from the segmented regulatory label signals";
27
45
  // Order matches evidence-priority rank: genetic > known_drug > animal_model > somatic > literature.
28
46
  function makeAssociationEvidence(a) {
29
47
  const out = [];
@@ -186,7 +204,7 @@ export function assembleTractability(resolved, ot, drugs) {
186
204
  ];
187
205
  const preferred = t.smallMolecule ? "small_molecule" : t.antibody ? "antibody" : t.otherModalities ? "other_clinical" : null;
188
206
  const modalitiesEnriched = enrichTractabilityModalitiesWithApprovals(modalities, drugs);
189
- // Collect molecule types from approved/clinical drugs for the v4 field.
207
+ // Collect molecule types from approved/clinical drugs for the tractability field.
190
208
  const drugMoleculeTypes = [...new Set(drugs.map((d) => d.molecule_type).filter((m) => !!m))];
191
209
  return {
192
210
  coverage: "available",
@@ -241,7 +259,7 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
241
259
  const internalRefCounts = tallyInternalReferences([
242
260
  koSupportingLiterature,
243
261
  ...(organRollupRows ?? []).map((r) => r.evidence),
244
- ...(offTargetRows ?? []).map((r) => r.evidence),
262
+ ...(offTargetRows ?? []).map((r) => claimSupportEvidence(r.support)),
245
263
  ]);
246
264
  const keyPapersRows = assembleKeyPapers(phase2, internalRefCounts);
247
265
  const evidenceTimeline = assembleEvidenceTimeline(phase2);
@@ -262,7 +280,6 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
262
280
  // receptor rather than the assessment target.
263
281
  const trialOutcomeFilter = await buildTrialOutcomeFilter(phase2, attrCtx, knownClassDrugNames, phase2.phase1.resolved.geneSymbol);
264
282
  const trialOutcomesRows = aggregateTrialOutcomes(fanout, trialOutcomeFilter);
265
- const discoveryTrialData = await assembleDiscoveryTrials(phase2, phase2.phase1.resolved.geneSymbol, attrCtx, knownClassDrugNames);
266
283
  const resolved = phase2.phase1.resolved;
267
284
  const ot = phase2.phase1.collectors.opentargets;
268
285
  const ctgov = phase2.phase1.collectors.ctgov;
@@ -274,20 +291,99 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
274
291
  const pubmed = phase2.phase1.collectors.pubmedIndex;
275
292
  const pathways = phase2.phase1.collectors.pathways;
276
293
  const ppi = phase2.phase1.collectors.stringPpi;
294
+ // Every trial row carries its attribution from assembly onward: the
295
+ // evidence role decides whether a row supports the target or is context.
296
+ const knownClassDrugs = await Promise.all((drugInteractionRows ?? []).map(async (d) => {
297
+ const resolvedTargets = d.drug_id ? await getDrugPrimaryTargetUniprots(d.drug_id).catch(() => []) : [];
298
+ return {
299
+ name: d.drug_name,
300
+ moleculeChemblId: d.drug_id,
301
+ targetUniprots: resolvedTargets.length > 0 ? resolvedTargets : assessmentUniprot ? [assessmentUniprot] : [],
302
+ };
303
+ }));
304
+ const programsCollector = phase2.phase1.collectors.therapeuticPrograms;
305
+ const therapeuticPrograms = programsCollector?.coverage === "available"
306
+ ? programsCollector.data.programs.map((program) => ({
307
+ programId: program.programId,
308
+ name: program.name,
309
+ targetSymbol: program.targetSymbol,
310
+ targetUniprot: program.targetUniprot,
311
+ modality: program.modality,
312
+ nctIds: program.nctIds,
313
+ }))
314
+ : [];
315
+ const evidenceAttribution = {
316
+ assessmentSymbol: geneSymbol,
317
+ assessmentUniprot,
318
+ familyUniprots: familySiblingUniprots,
319
+ knownClassDrugs,
320
+ therapeuticPrograms,
321
+ };
322
+ const trialSourceByNct = new Map();
323
+ if (ctgov.coverage === "available") {
324
+ for (const t of [...ctgov.data.active, ...ctgov.data.failed])
325
+ trialSourceByNct.set(t.nctId, t);
326
+ }
327
+ const attachTrialAttribution = (row) => {
328
+ const source = trialSourceByNct.get(row.nct_id);
329
+ const classified = source
330
+ ? classifyClinicalEvidenceTrial(source, evidenceAttribution)
331
+ : {
332
+ attribution: {
333
+ relationship: "unknown",
334
+ evidence_role: "excluded",
335
+ basis: [{ kind: "text_match", source: "ctgov" }],
336
+ resolved_interventions: [],
337
+ exclusion_reason: "no CT.gov record was available to attribute this trial to the assessed target",
338
+ },
339
+ eligible_for_toxicology_aggregation: false,
340
+ };
341
+ return {
342
+ ...row,
343
+ attribution: classified.attribution,
344
+ eligible_for_toxicology_aggregation: classified.eligible_for_toxicology_aggregation,
345
+ };
346
+ };
347
+ const discoveryTrials = await assembleDiscoveryTrials(phase2, phase2.phase1.resolved.geneSymbol, attrCtx, knownClassDrugNames, attachTrialAttribution);
277
348
  const indicationsRaw = ot.coverage === "available" ? ot.data.associations : [];
278
349
  const filteredIndications = indicationsRaw.filter((a) => !isSelfReference(resolved.canonicalId, a.diseaseId, resolved.geneSymbol, a.diseaseName) && !isClinicalMeasurement(a.diseaseName));
279
350
  const ta = inferTherapeuticArea(filteredIndications.map((a) => a.diseaseName));
280
351
  const benchmarks = getBenchmarks(ta);
281
352
  const datasetAttr = getDatasetAttribution();
282
- const offTissueRows = expression.coverage === "available"
283
- ? expression.data.tissues
284
- .filter((t) => meetsTpmFloor(t.tissueLabel, t.value ?? 0) && isSafetyRelevant(t.tissueLabel, t.organSystem))
285
- .map((t) => ({
286
- tissue: t.tissueLabel,
287
- organ: t.organSystem ?? "unspecified",
288
- tpm: t.value ?? 0,
289
- }))
353
+ const offTissueCandidates = expression.coverage === "available"
354
+ ? expression.data.tissues.filter((t) => meetsTpmFloor(t.tissueLabel, t.value ?? 0) && isSafetyRelevant(t.tissueLabel, t.organSystem))
290
355
  : [];
356
+ // A tissue whose anatomy does not resolve onto an organ system is dropped
357
+ // rather than filed under a neighbouring organ; the count is reported so
358
+ // the section does not overstate its own completeness.
359
+ const offTissueRows = offTissueCandidates.flatMap((t) => {
360
+ const organ = resolveTissueOrganSystem(t.tissueLabel, t.organSystem);
361
+ if (!organ)
362
+ return [];
363
+ const support = {
364
+ state: "unknown",
365
+ reason: `${t.tissueLabel} expression is a measured value from the expression atlas, not a citable per-tissue record`,
366
+ };
367
+ return [{ tissue: t.tissueLabel, organ, tpm: t.value ?? 0, support }];
368
+ });
369
+ const offTissueDroppedCount = offTissueCandidates.length - offTissueRows.length;
370
+ // Candidates that all failed to resolve leave the section empty for a
371
+ // reason of our own making, not because the atlas held nothing.
372
+ const offTissueSection = expression.coverage !== "available"
373
+ ? { coverage: "queried_no_data" }
374
+ : offTissueRows.length > 0
375
+ ? {
376
+ coverage: "available",
377
+ data: { rows: offTissueRows },
378
+ ...(offTissueDroppedCount > 0 ? { dropped_count: offTissueDroppedCount } : {}),
379
+ }
380
+ : offTissueDroppedCount > 0
381
+ ? {
382
+ coverage: "filtered",
383
+ filter: "tissue anatomy did not resolve onto an organ system",
384
+ dropped_count: offTissueDroppedCount,
385
+ }
386
+ : { coverage: "queried_no_data" };
291
387
  const drugsForTractability = enrichedModulators.map((m) => ({
292
388
  drug_id: m.moleculeChemblId,
293
389
  drug_name: m.preferredName,
@@ -334,6 +430,9 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
334
430
  // cannot fire and all trials land in primary — off-target detection activates
335
431
  // only when ChEMBL IDs are available downstream.
336
432
  const activeTrialsPartitioned = ctgov.coverage === "available" ? await partitionTrialsByAttribution(ctgov.data.active, attrCtx) : null;
433
+ // Everything CT.gov offered before any threshold of ours ran — the baseline
434
+ // the trials section measures its own drops against.
435
+ const activeTrialCandidateCount = (activeTrialsPartitioned?.primary.length ?? 0) + (activeTrialsPartitioned?.related.length ?? 0);
337
436
  // Map ctgov active trials to clinical-trial row shape with text-based confidence,
338
437
  // then remove rows identified as off-target by the attribution filter.
339
438
  // Drug-in-class matches (calcitonin drugs for CALCR, GLP-1 drugs for GLP1R, etc.)
@@ -418,17 +517,37 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
418
517
  failure_category: t.failure_category,
419
518
  classifier: t.classifier,
420
519
  })) ?? [];
520
+ const attributedFailedRows = failedTrialRows.map(attachTrialAttribution);
521
+ const attributedFailedRelatedRows = failedRelatedRows.map(attachTrialAttribution);
522
+ // The same partition the clinical-development section reports, shared with the
523
+ // safety lens: a related-receptor failure is context for both, and it must be
524
+ // told apart from a target-attributed one in both.
525
+ const eligibleFailedRows = attributedFailedRows.filter((r) => r.eligible_for_toxicology_aggregation);
526
+ const excludedFailedRows = [...attributedFailedRows.filter((r) => !r.eligible_for_toxicology_aggregation), ...attributedFailedRelatedRows];
527
+ // `clinical_development.failed_trials` and `safety_profile.failed_trials_safety_lens`
528
+ // report the same partition under two headings, so the section is decided once.
529
+ // Two constructions of it would let the same condition report two coverages.
530
+ const failedTrialsSection = ctgov.coverage === "available"
531
+ ? coverageFromFilteredRows({
532
+ data: {
533
+ rows: eligibleFailedRows,
534
+ excluded_rows: excludedFailedRows,
535
+ },
536
+ retainedCount: eligibleFailedRows.length,
537
+ droppedCount: excludedFailedRows.length,
538
+ filter: FAILED_TRIALS_FILTER,
539
+ emptyReason: "ClinicalTrials.gov returned no terminated or withdrawn trials for the assessment target",
540
+ })
541
+ : { coverage: "queried_no_data", error: { message: "ClinicalTrials.gov unavailable" } };
421
542
  // Typed explicitly so literal string coverage values narrow correctly.
422
543
  // Phase-5 persist attaches the derived sub-tree after synthesis is stamped.
423
544
  const dossierBody = {
424
- schema_version: "4",
425
545
  generated_at: new Date().toISOString(),
426
546
  entity: assembleEntity(resolved),
427
547
  liability_summary: liabilitySummary,
428
548
  tractability: tractabilitySection,
429
549
  indications: ot.coverage === "available"
430
- ? {
431
- coverage: "available",
550
+ ? coverageFromFilteredRows({
432
551
  data: {
433
552
  rows: indicationRows,
434
553
  excluded_unsupported_count: unsupportedCandidates.length,
@@ -441,7 +560,13 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
441
560
  }
442
561
  : {}),
443
562
  },
444
- }
563
+ retainedCount: indicationRows.length,
564
+ // The head cap is a carrying limit rather than a judgement, so
565
+ // only the two content filters count against completeness.
566
+ droppedCount: indicationsRaw.length - filteredIndications.length + unsupportedCandidates.length,
567
+ filter: INDICATIONS_FILTER,
568
+ emptyReason: "Open Targets returned no disease associations for the target",
569
+ })
445
570
  : {
446
571
  coverage: "queried_no_data",
447
572
  error: {
@@ -455,55 +580,43 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
455
580
  trials: clinicalTrialRows === null
456
581
  ? { coverage: "queried_no_data", error: { message: "ClinicalTrials.gov unavailable" } }
457
582
  : (() => {
458
- const base = coverageFromRows(clinicalTrialRows, { reason: "no trials matched the assessment target with confidence ≥ medium" });
459
- if (base.coverage !== "available")
460
- return base;
461
- return {
462
- coverage: "available",
583
+ const attributed = clinicalTrialRows.map(attachTrialAttribution);
584
+ const relatedTargetTrials = (activeTrialsPartitioned?.related ?? []).map((t) => attachTrialAttribution({
585
+ nct_id: t.nctId,
586
+ title: t.title,
587
+ phase: t.phase,
588
+ status: t.status,
589
+ conditions: t.conditions,
590
+ start_date: t.startDate,
591
+ completion_date: t.primaryCompletionDate,
592
+ match_confidence: "off_target",
593
+ }));
594
+ const rows = attributed.filter((r) => r.eligible_for_toxicology_aggregation);
595
+ return coverageFromFilteredRows({
463
596
  data: {
464
- rows: base.data.rows,
597
+ rows,
598
+ excluded_rows: [...attributed.filter((r) => !r.eligible_for_toxicology_aggregation), ...relatedTargetTrials],
465
599
  selection_criteria: {
466
600
  derived_from: "analytics.discovery_trials",
467
601
  min_confidence: "medium",
468
602
  excluded_off_target_count: activeTrialsPartitioned?.related.length ?? 0,
469
603
  },
470
- ...(activeTrialsPartitioned && activeTrialsPartitioned.related.length > 0
471
- ? {
472
- related_target_trials: activeTrialsPartitioned.related.map((t) => ({
473
- nct_id: t.nctId,
474
- title: t.title,
475
- phase: t.phase,
476
- status: t.status,
477
- conditions: t.conditions,
478
- start_date: t.startDate,
479
- completion_date: t.primaryCompletionDate,
480
- match_confidence: "off_target",
481
- })),
482
- related_receptor: activeTrialsPartitioned.related_receptor,
483
- }
484
- : {}),
485
604
  },
486
- };
605
+ retainedCount: rows.length,
606
+ // Everything CT.gov returned for the target, against what survived:
607
+ // the low-confidence cut, the attribution cut, and the related-receptor
608
+ // partition are all filters of ours.
609
+ droppedCount: activeTrialCandidateCount - rows.length,
610
+ filter: ACTIVE_TRIALS_FILTER,
611
+ emptyReason: "ClinicalTrials.gov returned no trials for the assessment target",
612
+ });
487
613
  })(),
488
614
  outcomes: trialOutcomesRows
489
- ? { coverage: "available", data: { rows: trialOutcomesRows } }
615
+ ? { coverage: "available", data: { rows: trialOutcomesRows.map(attachTrialAttribution) } }
490
616
  : phase3
491
617
  ? { coverage: "queried_no_data", error: { message: "no per-trial outcomes available" } }
492
618
  : { coverage: "not_loaded", reason: "Phase-3 fan-out not run" },
493
- failed_trials: ctgov.coverage === "available"
494
- ? {
495
- coverage: "available",
496
- data: {
497
- rows: failedTrialRows,
498
- ...(failedRelatedRows.length > 0
499
- ? {
500
- related_target_trials: failedRelatedRows,
501
- related_receptor: failedTrialsPartitioned?.related_receptor,
502
- }
503
- : {}),
504
- },
505
- }
506
- : { coverage: "queried_no_data" },
619
+ failed_trials: failedTrialsSection,
507
620
  benchmarks: {
508
621
  therapeutic_area: benchmarks.therapeutic_area,
509
622
  fallback_to_all_areas: benchmarks.source === "fallback",
@@ -541,33 +654,28 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
541
654
  : phase3
542
655
  ? { coverage: "queried_no_data", error: { message: "no off-target data" } }
543
656
  : { coverage: "not_loaded", reason: "Phase-3 fan-out not run" },
544
- failed_trials_safety_lens: ctgov.coverage === "available" && failedTrialRows.length > 0
545
- ? {
546
- coverage: "available",
547
- data: {
548
- rows: failedTrialRows,
549
- ...(failedRelatedRows.length > 0
550
- ? {
551
- related_target_trials: failedRelatedRows,
552
- related_receptor: failedTrialsPartitioned?.related_receptor,
553
- }
554
- : {}),
555
- },
556
- }
557
- : ctgov.coverage === "available"
558
- ? { coverage: "queried_no_data", error: { message: "no failed trials" } }
559
- : { coverage: "queried_no_data", error: { message: "ClinicalTrials.gov unavailable" } },
657
+ // The safety lens carries the same two buckets as
658
+ // `clinical_development.failed_trials`. Merging them would put a
659
+ // related-receptor termination in the same list as a target-attributed
660
+ // one, and the reader has no other way to tell them apart.
661
+ failed_trials_safety_lens: failedTrialsSection,
560
662
  class_precedent: classP
561
663
  ? { coverage: "available", data: classP }
562
664
  : phase3
563
665
  ? { coverage: "queried_no_data", error: { message: "no class precedent data" } }
564
666
  : { coverage: "not_loaded", reason: "Phase-3 fan-out not run" },
565
- target_organ_liabilities: [],
667
+ // Stamped by Phase 5 after the safety-flags-trail agent runs.
668
+ target_organ_liabilities: { coverage: "not_loaded", reason: NOT_LOADED_PHASE5 },
566
669
  regulatory_actions: regulatoryActionRows.length > 0
567
670
  ? { coverage: "available", data: { rows: regulatoryActionRows } }
568
671
  : { coverage: "queried_no_data" },
569
672
  },
570
- off_tissue_risk: expression.coverage === "available" ? { coverage: "available", data: { rows: offTissueRows } } : { coverage: "queried_no_data" },
673
+ // The corroboration fold reads the FDA label signals segmented after
674
+ // Phase 4, and the claim investigation reads the fold, so both are
675
+ // stamped by the workflow body before synthesis runs.
676
+ safety_corroboration: { coverage: "not_loaded", reason: NOT_LOADED_POST_ASSEMBLY },
677
+ claim_investigation: { coverage: "not_loaded", reason: "the claim investigation runs after Phase 4, over the corroborated organ claims" },
678
+ off_tissue_risk: offTissueSection,
571
679
  off_target_panel: offTargetPanel
572
680
  ? { coverage: "available", data: offTargetPanel }
573
681
  : phase3
@@ -575,8 +683,7 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
575
683
  : { coverage: "not_loaded", reason: "Phase-3 fan-out not run" },
576
684
  reference_biology: {
577
685
  therapeutic_area_associations: ot.coverage === "available"
578
- ? {
579
- coverage: "available",
686
+ ? coverageFromFilteredRows({
580
687
  data: {
581
688
  rows: filteredIndications.slice(0, 200).map((a) => {
582
689
  const evidence = makeAssociationEvidence(a);
@@ -603,7 +710,11 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
603
710
  };
604
711
  }),
605
712
  },
606
- }
713
+ retainedCount: Math.min(filteredIndications.length, 200),
714
+ droppedCount: indicationsRaw.length - filteredIndications.length,
715
+ filter: INDICATIONS_FILTER,
716
+ emptyReason: "Open Targets returned no disease associations for the target",
717
+ })
607
718
  : { coverage: "queried_no_data" },
608
719
  molecular_interactions: (() => {
609
720
  const rows = assembleMolecularInteractions(phase2, fanout);
@@ -695,7 +806,7 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
695
806
  marker_symbol: impc.data.mouseMarkerSymbol,
696
807
  viability: impc.data.viability,
697
808
  sex_dimorphism: impc.data.sexDimorphic,
698
- organ_systems_with_phenotype: impc.data.organSystems,
809
+ organ_systems_with_phenotype: toOrganSystems(impc.data.organSystems),
699
810
  top_mp_terms: impc.data.mpTerms.slice(0, 25).map((m) => m.term),
700
811
  total_phenotype_count: impc.data.phenotypeCount,
701
812
  pre_weaning_lethal: impc.data.viability === "lethal_pre_weaning",
@@ -769,11 +880,10 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
769
880
  const data = assembleAdditionalEvidence(phase2);
770
881
  return data ? { coverage: "available", data } : { coverage: "queried_no_data" };
771
882
  })(),
772
- discovery_trials: discoveryTrialData
773
- ? { coverage: "available", data: discoveryTrialData }
774
- : { coverage: "queried_no_data", error: { message: "no medium/low-confidence trials" } },
883
+ discovery_trials: discoveryTrials,
775
884
  // Stamped by Phase 5 after synthesis agents run; not available at assembly time.
776
885
  synthesis_diagnostics: { coverage: "not_loaded", reason: "Phase-5 synthesis not yet run" },
886
+ quality_gates: { coverage: "not_loaded", reason: "Phase-5 synthesis not yet run" },
777
887
  },
778
888
  // Stamped by Phase 5 after the dossier-recommendation agent runs.
779
889
  executive_recommendation: { coverage: "not_loaded", reason: "Phase-5 synthesis not yet run" },
@@ -785,7 +895,7 @@ export async function assembleDossier(pool, phase2, phase3, annotatorDeps) {
785
895
  *
786
896
  * When target-level FAERS has data (coverage: "available"), pass through
787
897
  * unchanged. When class_precedent has FAERS-derived reports in per_organ,
788
- * fold them into a class-scoped FaersSummaryV4 payload with
898
+ * fold them into a class-scoped FAERS summary payload with
789
899
  * coverage: "available" and inference_path tagged — rather than leaving
790
900
  * the section as queried_no_data, which creates an evidence conflict
791
901
  * that the conflict-detector flags. When neither has data, return