@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
@@ -8,15 +8,23 @@
8
8
  * than fabricated. Aggregate rows preserve their contributing evidence
9
9
  * under `evidence: [...]` arrays.
10
10
  */
11
- import type { EvidenceItem, RegulatoryActionRow } from "@inflexa-ai/harness/contracts/target-dossier.js";
11
+ import type { ClaimSupport, EvidenceItem, RegulatoryActionRow, RegulatoryOrganSignals } from "@inflexa-ai/harness/contracts/target-dossier.js";
12
+ import { type OrganSignalProjection } from "../lib/fda-label-safety.js";
13
+ import { type OrganSystem } from "../../../contracts/organ-system.js";
12
14
  import type { Phase2Bundle } from "../steps/phase2-aggregate.js";
13
15
  import type { Phase3Bundle } from "../steps/phase3-aggregate.js";
14
16
  import type { FamilyComplexesBundle } from "../schemas.js";
15
- import { type CanonicalOrgan } from "../lib/meddra-organ-map.js";
16
17
  import { HIGH_EXPRESSION_TPM_THRESHOLD } from "../lib/expression-constants.js";
17
18
  export { HIGH_EXPRESSION_TPM_THRESHOLD };
18
19
  import type { ChemblModulator } from "../../../tools/lib/chembl-client.js";
19
20
  export declare const SAFETY_RELEVANT_ORGANS: Set<string>;
21
+ /**
22
+ * Resolve a tissue (and its atlas organ label, when present) onto the
23
+ * canonical organ vocabulary. Returns null when the anatomy has no organ
24
+ * system to belong to — the caller drops the row rather than filing it
25
+ * under a neighbouring organ.
26
+ */
27
+ export declare function resolveTissueOrganSystem(tissue: string, organ?: string | null): OrganSystem | null;
20
28
  export declare function isSafetyRelevant(tissue: string, organ?: string | null): boolean;
21
29
  export declare const REPRODUCTIVE_TOX_TISSUES: Set<string>;
22
30
  export declare const REPRODUCTIVE_TOX_TPM_FLOOR = 20;
@@ -55,7 +63,7 @@ export declare function aggregateFaersAcrossModulators(fanout: FanoutResults | u
55
63
  };
56
64
  top_signals: {
57
65
  meddra_term: string;
58
- organ: CanonicalOrgan | undefined;
66
+ organ: "hematologic" | "metabolic" | "immune" | "hepatic" | "musculoskeletal" | "cns" | "renal" | "reproductive" | "respiratory" | "cardiac" | "vascular" | "pns" | "gi" | "pancreas" | "endocrine_thyroid" | "dermatologic" | "ocular" | "oncology" | undefined;
59
67
  report_count: number;
60
68
  }[];
61
69
  per_modulator: {
@@ -102,7 +110,7 @@ export type RawOffTargetRow = {
102
110
  };
103
111
  selectivity_window_below_threshold?: boolean;
104
112
  evidence?: EvidenceItem[];
105
- organ_system?: string | null;
113
+ organ_system?: OrganSystem | null;
106
114
  target_class?: string | undefined;
107
115
  is_safety_panel_target?: boolean;
108
116
  clinical_consequence?: string | null;
@@ -113,7 +121,7 @@ export type RawOffTargetRow = {
113
121
  export type CleanOffTargetRow = Omit<RawOffTargetRow, "target_chembl_id" | "target_type" | "accession" | "off_target_id" | "organ_system" | "evidence"> & {
114
122
  off_target_id: string | null;
115
123
  off_target_name: string;
116
- organ_system: string | null;
124
+ organ_system: OrganSystem | null;
117
125
  evidence: EvidenceItem[];
118
126
  selectivity_window_below_threshold: boolean;
119
127
  clinical_consequence: string | null;
@@ -123,20 +131,39 @@ export type CleanOffTargetRow = Omit<RawOffTargetRow, "target_chembl_id" | "targ
123
131
  };
124
132
  };
125
133
  /**
126
- * V4 off-target row shape after relationship classification. Used by the
134
+ * Off-target row shape after relationship classification. Used by the
127
135
  * organ-rollup builder, evidence tally, and liability-summary signal count —
128
136
  * the typed members all rely on `relationship`, `organ_system`, `pchembl`,
129
- * `evidence`, and `is_safety_panel_target` only.
137
+ * `support`, and `is_safety_panel_target` only.
130
138
  */
131
139
  export type OffTargetPanelRows = Array<{
132
140
  off_target_id: string | null;
133
141
  off_target_name: string;
134
142
  pchembl: number;
135
143
  is_safety_panel_target: boolean;
136
- organ_system: string | null;
137
- evidence: EvidenceItem[];
144
+ organ_system: OrganSystem | null;
145
+ support: ClaimSupport;
138
146
  relationship: "off_target";
139
147
  }>;
148
+ /**
149
+ * Partition evidence into what a claim can stand on: an item resolves to a
150
+ * record a reader can check, or it does not. A claim with nothing checkable
151
+ * says so rather than presenting provenance labels as citations.
152
+ */
153
+ export declare function claimSupportFrom(evidence: EvidenceItem[], reasonWhenUnsupported: string): ClaimSupport;
154
+ /** The evidence behind a claim, or nothing when the claim states it has none. */
155
+ export declare function claimSupportEvidence(support: ClaimSupport): EvidenceItem[];
156
+ /**
157
+ * Turn segmented FDA label warnings into the dossier's per-organ regulatory
158
+ * signal section.
159
+ *
160
+ * The organ-resolution filter can legitimately empty this section: a set of
161
+ * labels whose every warning fragment names no canonical organ produces no
162
+ * rows. That is `filtered` with the count it discarded, never an `available`
163
+ * section with an empty list — the difference is whether a reader learns that
164
+ * signals existed and did not survive attribution.
165
+ */
166
+ export declare function assembleRegulatoryOrganSignals(projection: OrganSignalProjection | null): RegulatoryOrganSignals;
140
167
  /**
141
168
  * Filter and deduplicate raw off-target rows before panel assembly.
142
169
  *
@@ -150,7 +177,7 @@ export declare function filterAndDedupOffTargetRows(rows: RawOffTargetRow[], ctx
150
177
  assessmentTargetChemblId: string | null;
151
178
  }): CleanOffTargetRow[];
152
179
  /**
153
- * V4 off-target panel row split. Each clean off-target row is classified as:
180
+ * Off-target panel row split. Each clean off-target row is classified as:
154
181
  * - `on_target_self_hit`: alternate ChEMBL ID for the assessment target itself
155
182
  * - `intended_co_target`: every modulator that hits this target intends it
156
183
  * (e.g., GLP1R + GIPR for tirzepatide); not an off-target liability
@@ -162,7 +189,7 @@ export declare function aggregateOffTargetPanel(phase2: Phase2Bundle, fanout: Fa
162
189
  off_target_name: string;
163
190
  pchembl: number;
164
191
  is_safety_panel_target: boolean;
165
- organ_system: string | null;
192
+ organ_system: "hematologic" | "metabolic" | "immune" | "hepatic" | "musculoskeletal" | "cns" | "renal" | "reproductive" | "respiratory" | "cardiac" | "vascular" | "pns" | "gi" | "pancreas" | "endocrine_thyroid" | "dermatologic" | "ocular" | "oncology" | null;
166
193
  clinical_consequence: string | null;
167
194
  selectivity: {
168
195
  vs_primary_potency: {
@@ -176,25 +203,32 @@ export declare function aggregateOffTargetPanel(phase2: Phase2Bundle, fanout: Fa
176
203
  reason: string;
177
204
  };
178
205
  selectivity_window_below_threshold: boolean;
179
- evidence: {
180
- source: string;
181
- pmid?: string | undefined;
182
- doi?: string | undefined;
183
- predicate?: string | undefined;
184
- score?: number | undefined;
185
- strength?: string | undefined;
186
- is_human?: boolean | undefined;
187
- is_clinical?: boolean | undefined;
188
- excerpt?: string | undefined;
189
- metadata?: Record<string, unknown> | undefined;
190
- regulatory_reference?: {
191
- document: string;
192
- section?: string | undefined;
193
- doc_id?: string | undefined;
194
- doc_url?: string | undefined;
195
- } | undefined;
196
- }[];
197
206
  relationship: "off_target";
207
+ support: {
208
+ state: "scored";
209
+ evidence: {
210
+ source: string;
211
+ pmid?: string | undefined;
212
+ doi?: string | undefined;
213
+ accession?: string | undefined;
214
+ predicate?: string | undefined;
215
+ score?: number | undefined;
216
+ strength?: string | undefined;
217
+ is_human?: boolean | undefined;
218
+ is_clinical?: boolean | undefined;
219
+ excerpt?: string | undefined;
220
+ metadata?: Record<string, unknown> | undefined;
221
+ regulatory_reference?: {
222
+ document: string;
223
+ section?: string | undefined;
224
+ doc_id?: string | undefined;
225
+ doc_url?: string | undefined;
226
+ } | undefined;
227
+ }[];
228
+ } | {
229
+ state: "unknown";
230
+ reason: string;
231
+ };
198
232
  target_class?: string | undefined;
199
233
  metadata?: {
200
234
  merged_chembl_ids?: string[] | undefined;
@@ -205,7 +239,7 @@ export declare function aggregateOffTargetPanel(phase2: Phase2Bundle, fanout: Fa
205
239
  off_target_name: string;
206
240
  pchembl: number;
207
241
  is_safety_panel_target: boolean;
208
- organ_system: string | null;
242
+ organ_system: "hematologic" | "metabolic" | "immune" | "hepatic" | "musculoskeletal" | "cns" | "renal" | "reproductive" | "respiratory" | "cardiac" | "vascular" | "pns" | "gi" | "pancreas" | "endocrine_thyroid" | "dermatologic" | "ocular" | "oncology" | null;
209
243
  clinical_consequence: string | null;
210
244
  selectivity: {
211
245
  vs_primary_potency: {
@@ -219,10 +253,13 @@ export declare function aggregateOffTargetPanel(phase2: Phase2Bundle, fanout: Fa
219
253
  reason: string;
220
254
  };
221
255
  selectivity_window_below_threshold: boolean;
256
+ relationship: "intended_co_target" | "on_target_self_hit" | "obligate_cofactor";
257
+ reason: string;
222
258
  evidence: {
223
259
  source: string;
224
260
  pmid?: string | undefined;
225
261
  doi?: string | undefined;
262
+ accession?: string | undefined;
226
263
  predicate?: string | undefined;
227
264
  score?: number | undefined;
228
265
  strength?: string | undefined;
@@ -237,8 +274,6 @@ export declare function aggregateOffTargetPanel(phase2: Phase2Bundle, fanout: Fa
237
274
  doc_url?: string | undefined;
238
275
  } | undefined;
239
276
  }[];
240
- relationship: "intended_co_target" | "on_target_self_hit" | "obligate_cofactor";
241
- reason: string;
242
277
  target_class?: string | undefined;
243
278
  metadata?: {
244
279
  merged_chembl_ids?: string[] | undefined;
@@ -270,7 +305,7 @@ export declare function computeClassPrecedentCounts(drugs: ClassDrug[]): {
270
305
  export declare function aggregateClassPrecedent(phase2: Phase2Bundle, fanout: FanoutResults | undefined): {
271
306
  drugs_in_class: ClassDrug[];
272
307
  per_organ: {
273
- organ: CanonicalOrgan;
308
+ organ: "hematologic" | "metabolic" | "immune" | "hepatic" | "musculoskeletal" | "cns" | "renal" | "reproductive" | "respiratory" | "cardiac" | "vascular" | "pns" | "gi" | "pancreas" | "endocrine_thyroid" | "dermatologic" | "ocular" | "oncology";
274
309
  drug_count_in_class: number;
275
310
  drugs_with_signal: number;
276
311
  signal_fraction: number;
@@ -283,7 +318,7 @@ export declare function aggregateClassPrecedent(phase2: Phase2Bundle, fanout: Fa
283
318
  }[];
284
319
  } | null;
285
320
  export declare function buildOrganRollup(faers: ReturnType<typeof aggregateFaersAcrossModulators>, trialAes: ReturnType<typeof aggregateTrialAes>, offTarget: OffTargetPanelRows | null, classP: ReturnType<typeof aggregateClassPrecedent>, regulatoryActions?: RegulatoryActionRow[] | null): {
286
- organ: CanonicalOrgan;
321
+ organ: "hematologic" | "metabolic" | "immune" | "hepatic" | "musculoskeletal" | "cns" | "renal" | "reproductive" | "respiratory" | "cardiac" | "vascular" | "pns" | "gi" | "pancreas" | "endocrine_thyroid" | "dermatologic" | "ocular" | "oncology";
287
322
  risk_level: "high" | "medium" | "low";
288
323
  signal_type_count: number;
289
324
  signals: {
@@ -296,6 +331,7 @@ export declare function buildOrganRollup(faers: ReturnType<typeof aggregateFaers
296
331
  source: string;
297
332
  pmid?: string | undefined;
298
333
  doi?: string | undefined;
334
+ accession?: string | undefined;
299
335
  predicate?: string | undefined;
300
336
  score?: number | undefined;
301
337
  strength?: string | undefined;
@@ -311,20 +347,6 @@ export declare function buildOrganRollup(faers: ReturnType<typeof aggregateFaers
311
347
  } | undefined;
312
348
  }[];
313
349
  }[] | null;
314
- /**
315
- * Testable wrapper around `buildOrganRollup` that accepts named parameters.
316
- * Exported for unit tests; production code calls `buildOrganRollup` directly
317
- * inside `assembleDossier`.
318
- */
319
- export declare function assembleOrganRollupRows(opts: {
320
- classPrecedent?: ReturnType<typeof aggregateClassPrecedent> | null;
321
- regulatoryActions?: {
322
- coverage: string;
323
- data?: {
324
- rows: RegulatoryActionRow[];
325
- };
326
- } | null;
327
- }): ReturnType<typeof buildOrganRollup> extends null ? never[] : NonNullable<ReturnType<typeof buildOrganRollup>>;
328
350
  export declare function assembleDrugInteractions(phase2: Phase2Bundle, enrichedModulators?: ChemblModulator[]): {
329
351
  drug_id: string;
330
352
  drug_name: string;
@@ -8,6 +8,8 @@
8
8
  * than fabricated. Aggregate rows preserve their contributing evidence
9
9
  * under `evidence: [...]` arrays.
10
10
  */
11
+ import { ORGAN_RESOLUTION_FILTER } from "../lib/fda-label-safety.js";
12
+ import { ORGAN_SYSTEMS } from "../../../contracts/organ-system.js";
11
13
  import { expectedOrgansFromBody } from "../lib/compute-derived.js";
12
14
  import { inferTherapeuticArea } from "../../../tools/lib/clinical-benchmarks-client.js";
13
15
  import { SafetyPanelFileSchema } from "../../../data/safety-panel-schema.js";
@@ -17,7 +19,7 @@ import { isIntendedCoTarget } from "../lib/intended-polypharm-filter.js";
17
19
  import { makeHeterodimerOfAssessmentFilter } from "../lib/heterodimer-filter.js";
18
20
  import { buildFamilyComplexSupplement } from "../lib/family-complex-supplement.js";
19
21
  import { computeSelectivity } from "../lib/compute-selectivity.js";
20
- import { classifyOrgan, classifyPolypharmOrgan, classifyTrialAe } from "../lib/meddra-organ-map.js";
22
+ import { classifyOrgan, classifyTrialAe } from "../lib/meddra-organ-map.js";
21
23
  import { HIGH_EXPRESSION_TPM_THRESHOLD, CNS_REGION_TPM_FLOOR, MUSCULOSKELETAL_TPM_FLOOR } from "../lib/expression-constants.js";
22
24
  export { HIGH_EXPRESSION_TPM_THRESHOLD };
23
25
  // ── Safety panel lookup ─────────────────────────────────────────────
@@ -71,6 +73,54 @@ export const SAFETY_RELEVANT_ORGANS = new Set([
71
73
  "femur",
72
74
  "vertebra",
73
75
  ]);
76
+ /**
77
+ * Anatomical tissue and organ labels from the expression atlases onto the
78
+ * canonical organ vocabulary. Longest keys first so "bone marrow" is read as
79
+ * haematopoietic tissue rather than as skeleton.
80
+ */
81
+ const TISSUE_ORGAN_SYSTEMS = [
82
+ ["bone marrow", "hematologic"],
83
+ ["lymph node", "immune"],
84
+ ["seminal vesicle", "reproductive"],
85
+ ["fallopian tube", "reproductive"],
86
+ ["endometrium", "reproductive"],
87
+ ["epididymis", "reproductive"],
88
+ ["placenta", "reproductive"],
89
+ ["prostate", "reproductive"],
90
+ ["intestine", "gi"],
91
+ ["vertebra", "musculoskeletal"],
92
+ ["stomach", "gi"],
93
+ ["thyroid", "endocrine_thyroid"],
94
+ ["pancreas", "pancreas"],
95
+ ["spleen", "immune"],
96
+ ["retina", "ocular"],
97
+ ["kidney", "renal"],
98
+ ["uterus", "reproductive"],
99
+ ["testis", "reproductive"],
100
+ ["gonad", "reproductive"],
101
+ ["ovary", "reproductive"],
102
+ ["heart", "cardiac"],
103
+ ["liver", "hepatic"],
104
+ ["brain", "cns"],
105
+ ["femur", "musculoskeletal"],
106
+ ["tibia", "musculoskeletal"],
107
+ ["lung", "respiratory"],
108
+ ["bone", "musculoskeletal"],
109
+ ];
110
+ /**
111
+ * Resolve a tissue (and its atlas organ label, when present) onto the
112
+ * canonical organ vocabulary. Returns null when the anatomy has no organ
113
+ * system to belong to — the caller drops the row rather than filing it
114
+ * under a neighbouring organ.
115
+ */
116
+ export function resolveTissueOrganSystem(tissue, organ) {
117
+ const haystack = `${tissue} ${organ ?? ""}`.toLowerCase();
118
+ for (const [label, system] of TISSUE_ORGAN_SYSTEMS) {
119
+ if (haystack.includes(label))
120
+ return system;
121
+ }
122
+ return null;
123
+ }
74
124
  export function isSafetyRelevant(tissue, organ) {
75
125
  const t = tissue.toLowerCase();
76
126
  const o = (organ ?? "").toLowerCase();
@@ -160,22 +210,6 @@ export function meetsTpmFloor(tissue, value) {
160
210
  }
161
211
  return value > DEFAULT_TPM_FLOOR;
162
212
  }
163
- const CANONICAL_ORGANS = [
164
- "cardiac",
165
- "hepatic",
166
- "cns",
167
- "renal",
168
- "gi",
169
- "pancreas",
170
- "endocrine_thyroid",
171
- "metabolic",
172
- "hematologic",
173
- "immune",
174
- "respiratory",
175
- "reproductive",
176
- "dermatologic",
177
- "musculoskeletal",
178
- ];
179
213
  // ── Liability summary ───────────────────────────────────────────────
180
214
  export function assembleLiabilitySummary(phase2, context) {
181
215
  const ot = phase2.phase1.collectors.opentargets;
@@ -381,6 +415,53 @@ const SENTINEL_OFF_TARGET_NAMES = new Set(["unchecked", "unknown", "n/a"]);
381
415
  * biological targets. Filtered out before panel assembly.
382
416
  */
383
417
  const CELL_LINE_NAME_PATTERNS = /\b(hek293|hek-293|cho|hela|3t3|293t|293|jurkat|raw264|raw\s*264|cos-7|cos7|mcf-7|mcf7|pc-3|pc3|lncap|a549|u2os|ht-29|ht29|sk-ov-3|skov3|t47d|mda-mb|nih3t3)\b/i;
418
+ /**
419
+ * Partition evidence into what a claim can stand on: an item resolves to a
420
+ * record a reader can check, or it does not. A claim with nothing checkable
421
+ * says so rather than presenting provenance labels as citations.
422
+ */
423
+ export function claimSupportFrom(evidence, reasonWhenUnsupported) {
424
+ const locatable = evidence.filter((e) => Boolean(e.pmid ?? e.doi ?? e.accession ?? e.regulatory_reference));
425
+ if (locatable.length === 0)
426
+ return { state: "unknown", reason: reasonWhenUnsupported };
427
+ return { state: "scored", evidence: locatable };
428
+ }
429
+ /** The evidence behind a claim, or nothing when the claim states it has none. */
430
+ export function claimSupportEvidence(support) {
431
+ return support.state === "scored" ? support.evidence : [];
432
+ }
433
+ // ── Per-organ regulatory signals ────────────────────────────────────
434
+ /**
435
+ * Turn segmented FDA label warnings into the dossier's per-organ regulatory
436
+ * signal section.
437
+ *
438
+ * The organ-resolution filter can legitimately empty this section: a set of
439
+ * labels whose every warning fragment names no canonical organ produces no
440
+ * rows. That is `filtered` with the count it discarded, never an `available`
441
+ * section with an empty list — the difference is whether a reader learns that
442
+ * signals existed and did not survive attribution.
443
+ */
444
+ export function assembleRegulatoryOrganSignals(projection) {
445
+ if (projection === null) {
446
+ return { coverage: "not_loaded", reason: "no indication resolved, so no FDA label was queried" };
447
+ }
448
+ const rows = projection.signals.map((s) => ({
449
+ organ: s.organ,
450
+ drug_name: s.drug_name,
451
+ agency: "FDA",
452
+ application_number: s.application_number,
453
+ label_section: s.label_section,
454
+ excerpt: s.excerpt,
455
+ support: claimSupportFrom([s.evidence], `the ${s.label_section} warning for ${s.drug_name} carries no citable label reference`),
456
+ }));
457
+ if (rows.length === 0) {
458
+ if (projection.dropped_count > 0) {
459
+ return { coverage: "filtered", filter: ORGAN_RESOLUTION_FILTER, dropped_count: projection.dropped_count };
460
+ }
461
+ return { coverage: "queried_no_data", error: { message: "the FDA label query returned no attributable warning prose" } };
462
+ }
463
+ return { coverage: "available", data: { rows }, dropped_count: projection.dropped_count };
464
+ }
384
465
  /**
385
466
  * Filter and deduplicate raw off-target rows before panel assembly.
386
467
  *
@@ -444,7 +525,7 @@ export function filterAndDedupOffTargetRows(rows, ctx) {
444
525
  }
445
526
  // ── Off-target panel assembly ───────────────────────────────────────
446
527
  /**
447
- * V4 off-target panel row split. Each clean off-target row is classified as:
528
+ * Off-target panel row split. Each clean off-target row is classified as:
448
529
  * - `on_target_self_hit`: alternate ChEMBL ID for the assessment target itself
449
530
  * - `intended_co_target`: every modulator that hits this target intends it
450
531
  * (e.g., GLP1R + GIPR for tirzepatide); not an off-target liability
@@ -486,10 +567,10 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
486
567
  name: hit.targetName ?? hit.targetChemblId,
487
568
  pchembl: pch,
488
569
  modulatorPrimaryPchembl: primaryPchemblByModulator.get(item.data.moleculeChemblId) ?? null,
489
- modulators: cur?.modulators ?? new Set(),
570
+ assayByModulator: cur?.assayByModulator ?? new Map(),
490
571
  });
491
572
  }
492
- byTarget.get(hit.targetChemblId).modulators.add(item.data.moleculeChemblId);
573
+ byTarget.get(hit.targetChemblId).assayByModulator.set(item.data.moleculeChemblId, hit.assayChemblId);
493
574
  }
494
575
  }
495
576
  // Map off-target ChEMBL ID → primaryPchembl of the winner modulator so the
@@ -519,9 +600,15 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
519
600
  clinical_consequence: safetyHit?.clinical_consequence ?? null,
520
601
  selectivity: { log_units: 0, fold: 1 },
521
602
  selectivity_window_below_threshold: false,
522
- modulators: [...v.modulators],
523
- evidence: [...v.modulators].map((m) => ({
603
+ modulators: [...v.assayByModulator.keys()],
604
+ // The locator is the assay the binding was measured in — the
605
+ // record a reader can open to check the asserted activity. The
606
+ // off-target's own ChEMBL id names the protein, not the
607
+ // measurement, so an activity ChEMBL did not attribute to an
608
+ // assay carries no locator and the claim states it has none.
609
+ evidence: [...v.assayByModulator].map(([m, assayChemblId]) => ({
524
610
  source: "chembl:polypharm",
611
+ ...(assayChemblId ? { accession: assayChemblId } : {}),
525
612
  predicate: "binds",
526
613
  score: v.pchembl,
527
614
  metadata: { off_target_id: v.chemblId, modulator: m },
@@ -529,7 +616,7 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
529
616
  };
530
617
  })
531
618
  .filter((r) => r !== null);
532
- // Preserve modulator membership across dedup so the v4 relationship
619
+ // Preserve modulator membership across dedup so the relationship
533
620
  // classifier (intended co-target / on-target self-hit) can see which
534
621
  // molecules actually hit each off-target.
535
622
  const modulatorsByKey = new Map();
@@ -549,7 +636,7 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
549
636
  }
550
637
  return b.pchembl - a.pchembl;
551
638
  });
552
- // Helper: project a clean row onto the V4 base shape (no relationship yet).
639
+ // Helper: project a clean row onto the shared body shape (no relationship yet).
553
640
  const toBaseRow = (r) => ({
554
641
  off_target_id: r.off_target_id,
555
642
  off_target_name: r.off_target_name,
@@ -558,7 +645,6 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
558
645
  is_safety_panel_target: r.is_safety_panel_target,
559
646
  organ_system: r.organ_system,
560
647
  clinical_consequence: r.clinical_consequence,
561
- evidence: r.evidence,
562
648
  metadata: r.metadata,
563
649
  });
564
650
  const rows = [];
@@ -573,6 +659,7 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
573
659
  if (assessmentGeneSymbol && isHeterodimer(r.off_target_name)) {
574
660
  excludedRows.push({
575
661
  ...base,
662
+ evidence: r.evidence,
576
663
  relationship: "obligate_cofactor",
577
664
  reason: `${r.off_target_name} is ${assessmentGeneSymbol} heterodimerised with a different obligate cofactor; selectivity is not pharmacologically attainable.`,
578
665
  selectivity: { selectivity_unknown: true, reason: "obligate cofactor (same protein, different RAMP/accessory)" },
@@ -584,6 +671,7 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
584
671
  if (r.off_target_id && assessmentUniprot && isOnTargetChemblId(r.off_target_id, onTargetChemblIds)) {
585
672
  excludedRows.push({
586
673
  ...base,
674
+ evidence: r.evidence,
587
675
  relationship: "on_target_self_hit",
588
676
  reason: `${r.off_target_id} is an alternate ChEMBL ID for the on-target`,
589
677
  selectivity: { selectivity_unknown: true, reason: "on-target self-hit" },
@@ -597,6 +685,7 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
597
685
  if (modulators.length > 0 && intendedHits.length === modulators.length) {
598
686
  excludedRows.push({
599
687
  ...base,
688
+ evidence: r.evidence,
600
689
  relationship: "intended_co_target",
601
690
  reason: intendedHits[0].hit.reason,
602
691
  selectivity: { selectivity_unknown: true, reason: "intended co-target" },
@@ -619,6 +708,7 @@ export function aggregateOffTargetPanel(phase2, fanout, assessmentUniprot, asses
619
708
  relationship: "off_target",
620
709
  selectivity,
621
710
  selectivity_window_below_threshold: below,
711
+ support: claimSupportFrom(r.evidence, `${r.off_target_name} was surfaced by ChEMBL polypharmacology with no citable binding record`),
622
712
  });
623
713
  }
624
714
  // Append the IUPHAR-driven obligate-cofactor supplement for receptor-
@@ -778,13 +868,15 @@ const MALIGNANCY_RE = /malignan|cancer|carcinoma|neoplasm/i;
778
868
  export function buildOrganRollup(faers, trialAes, offTarget, classP, regulatoryActions) {
779
869
  if (!faers && !trialAes && !offTarget && !classP && !regulatoryActions?.length)
780
870
  return null;
781
- const rows = CANONICAL_ORGANS.map((organ) => {
782
- // off_target_panel.organ_system is the curated safety-panel canonical
783
- // string (`cardiac` | `hepatic` | `cns` | `renal` | `gi` |
784
- // `hematologic` | `immune` | `metabolic` | `respiratory`). Use direct
785
- // string equality first; `classifyOrgan` falls back if a future
786
- // collector emits a non-canonical string.
787
- const polypharmHits = (offTarget ?? []).filter((r) => classifyPolypharmOrgan(r.organ_system) === organ);
871
+ // The rollup walks the canonical vocabulary itself, so the organs it can
872
+ // emit, the organs `expectedOrgansFromBody` can expect, and the organs a
873
+ // producer can attribute are one set. A row with no signal is dropped
874
+ // below, so widening the vocabulary widens the rollup rather than opening
875
+ // a gap between expected and present organs.
876
+ const rows = ORGAN_SYSTEMS.map((organ) => {
877
+ // off_target_panel.organ_system is a canonical token, so the match is
878
+ // equality on the vocabulary rather than a comparison of prose.
879
+ const polypharmHits = (offTarget ?? []).filter((r) => r.organ_system === organ);
788
880
  const polypharmCount = polypharmHits.length;
789
881
  const faersHits = (faers?.top_signals ?? []).filter((s) => s.organ === organ);
790
882
  const faersCount = faersHits.length;
@@ -894,7 +986,7 @@ export function buildOrganRollup(faers, trialAes, offTarget, classP, regulatoryA
894
986
  }
895
987
  // Oncology synthesis: when regulatory_actions carries malignancy-keyword
896
988
  // findings, emit an oncology rollup row. This is ADDITIVE — it does not
897
- // replace any row produced by the main CANONICAL_ORGANS loop above.
989
+ // replace any row produced by the vocabulary loop above.
898
990
  if (!presentOrgans.has("oncology") && regulatoryActions?.length) {
899
991
  const malignancyRows = regulatoryActions.filter((r) => MALIGNANCY_RE.test(r.finding));
900
992
  if (malignancyRows.length > 0) {
@@ -923,15 +1015,6 @@ export function buildOrganRollup(faers, trialAes, offTarget, classP, regulatoryA
923
1015
  }
924
1016
  return filtered;
925
1017
  }
926
- /**
927
- * Testable wrapper around `buildOrganRollup` that accepts named parameters.
928
- * Exported for unit tests; production code calls `buildOrganRollup` directly
929
- * inside `assembleDossier`.
930
- */
931
- export function assembleOrganRollupRows(opts) {
932
- const regRows = opts.regulatoryActions?.coverage === "available" ? (opts.regulatoryActions.data?.rows ?? null) : null;
933
- return buildOrganRollup(null, null, null, opts.classPrecedent ?? null, regRows) ?? [];
934
- }
935
1018
  // ── §2.4 Drug Interactions ──────────────────────────────────────────
936
1019
  export function assembleDrugInteractions(phase2, enrichedModulators) {
937
1020
  const mods = phase2.phase1.collectors.chemblModulators;
@@ -24,7 +24,7 @@ export interface CollectorCtx {
24
24
  readonly ncbiApiKey?: string;
25
25
  }
26
26
  import type { SerializedError } from "../coverage.js";
27
- import type { CbioportalBundle, ChemblModulatorsBundle, ClinvarBundle, CtgovBundle, ExpressionHumanBundle, ExpressionMultiSpeciesBundle, FaersByTargetBundle, FamilyComplexesBundle, ImpcBundle, OpenTargetsBundle, PathwaysBundle, PubmedIndexBundle, ResolvedTarget, StringPpiBundle, TherapeuticProgramsBundle } from "../schemas.js";
27
+ import type { CbioportalBundle, ChemblModulatorsBundle, ClinvarBundle, CtgovBundle, ExpressionHumanBundle, ExpressionMultiSpeciesBundle, FaersByTargetBundle, FamilyComplexesBundle, ImpcBundle, MonarchBundle, OpenTargetsBundle, PathwaysBundle, PubmedIndexBundle, ResolvedTarget, StringPpiBundle, TherapeuticProgramsBundle } from "../schemas.js";
28
28
  type CoverageAvailable<T> = {
29
29
  coverage: "available";
30
30
  data: T;
@@ -47,6 +47,7 @@ export declare function collectExpressionMultiSpecies(input: ResolvedTarget): Pr
47
47
  export declare function collectFaersByTarget(input: ResolvedTarget): Promise<CoverageEnvelope<FaersByTargetBundle>>;
48
48
  export declare function collectFamilyComplexes(input: ResolvedTarget): Promise<CoverageEnvelope<FamilyComplexesBundle>>;
49
49
  export declare function collectImpc(input: ResolvedTarget): Promise<CoverageEnvelope<ImpcBundle>>;
50
+ export declare function collectMonarch(input: ResolvedTarget): Promise<CoverageEnvelope<MonarchBundle>>;
50
51
  export declare function collectOpenTargets(input: ResolvedTarget): Promise<CoverageEnvelope<OpenTargetsBundle>>;
51
52
  export declare function collectPathways(input: ResolvedTarget): Promise<CoverageEnvelope<PathwaysBundle>>;
52
53
  export declare function collectPubmedIndex(input: ResolvedTarget, ctx: CollectorCtx): Promise<CoverageEnvelope<PubmedIndexBundle>>;
@@ -85,6 +86,9 @@ export declare const COLLECTOR_MANIFEST: readonly [{
85
86
  }, {
86
87
  readonly id: "impc";
87
88
  readonly run: typeof collectImpc;
89
+ }, {
90
+ readonly id: "monarch";
91
+ readonly run: typeof collectMonarch;
88
92
  }, {
89
93
  readonly id: "opentargets";
90
94
  readonly run: typeof collectOpenTargets;
@@ -101,5 +105,4 @@ export declare const COLLECTOR_MANIFEST: readonly [{
101
105
  readonly id: "therapeutic-programs";
102
106
  readonly run: typeof collectTherapeuticPrograms;
103
107
  }];
104
- export type CollectorId = (typeof COLLECTOR_MANIFEST)[number]["id"];
105
108
  export {};