@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
@@ -1,3 +1,18 @@
1
+ /**
2
+ * MedDRA terms and CT.gov system-organ classes onto the canonical organ
3
+ * vocabulary.
4
+ *
5
+ * The values are the canonical tokens, so this module declares no organ
6
+ * enumeration of its own. Every token in the vocabulary is reachable through
7
+ * the rules below, so nothing a producer may legitimately attribute is
8
+ * unreachable by accident.
9
+ *
10
+ * A term resolves onto the organ it names, or onto nothing. A term whose
11
+ * compartment is genuinely ambiguous — "Paraesthesia" and the other nervous
12
+ * system terms that name no compartment, "Adrenal insufficiency" and the other
13
+ * endocrine terms outside the thyroid — matches no rule, so the caller drops
14
+ * and counts it rather than filing it under a neighbour.
15
+ */
1
16
  /**
2
17
  * Order matters: more-specific patterns first. GI is matched before
3
18
  * "metabolic" because terms like "blood glucose" should land in
@@ -11,8 +26,9 @@
11
26
  const RULES = [
12
27
  // pancreas (specific) — must come before generic GI
13
28
  { rx: /\bpancreatit|\bamylase|\blipase/i, organ: "pancreas" },
14
- // thyroid / C-cell
15
- { rx: /\b(thyroid|c[-\s]?cell|calcitonin|goit[re]+|medullar[a-z]*\s*(?:thyroid|carcinoma))/i, organ: "endocrine_thyroid" },
29
+ // thyroid / C-cell. The token names the thyroid, not the endocrine system,
30
+ // so a non-thyroid endocrine term deliberately matches nothing here.
31
+ { rx: /thyroid|\b(c[-\s]?cell|calcitonin|goit[re]+|medullar[a-z]*\s*carcinoma)/i, organ: "endocrine_thyroid" },
16
32
  // gastrointestinal
17
33
  { rx: /\b(nause|vomit|diarrhoe|diarrhea|constipat|ileus|gastropares|abdominal\s*pain|cholecystit|cholelithias|gallbladder|colit|heartburn)/i, organ: "gi" },
18
34
  // hepatic — liver/alt/ast forms added; alt/ast pinned to "increased" to avoid
@@ -27,23 +43,48 @@ const RULES = [
27
43
  // cardiac — cardi(ac|al|o) covers CARDIOMYOPATHY/CARDIOVASCULAR/CARDIOTOXICITY;
28
44
  // bare heart covers HEART FAILURE, HEART RATE INCREASED, etc.
29
45
  { rx: /\b(cardi(?:ac|al|o)|heart|myocard|tachycard|brady\w*card|arrhythm|atrial\s+fibrillat|qt\s+prolong|torsade)/i, organ: "cardiac" },
46
+ // vascular — placed after cardiac so a myocardial event stays cardiac.
47
+ // `thrombos` rather than the `thrombo` stem, which would swallow
48
+ // THROMBOCYTOPENIA; `\bvascul` never matches inside CEREBROVASCULAR.
49
+ { rx: /\b(hypertens|hypotens|vasculit|vascular|thrombos|thromboemboli|thrombophleb|embolism|aneurysm|varicose|phlebit)/i, organ: "vascular" },
30
50
  // metabolic (glucose, weight, lipid)
31
51
  // triglyc has no leading \b because it appears mid-word in HYPERTRIGLYCERIDAEMIA
32
52
  { rx: /\b(glucose|glycaemi|hba1c|insulin|weight\s+(?:decreased|increased)|lipid|cholester|dehydration)|triglyc/i, organ: "metabolic" },
33
- // cns
34
- { rx: /\b(headache|dizz|seizure|suici|depression|anxiet|paraesthes|neuropath|encephalo|migrain)/i, organ: "cns" },
35
- // hematologic
36
- { rx: /\b(haemoglobin|hemoglobin|haematocrit|leuko|neutro|thrombocyt|anaemi|haemat|platelet)/i, organ: "hematologic" },
37
- // respiratory
38
- { rx: /\b(nasopharyng|pharyng|bronch|pneumon|dyspnoe|dyspnea|cough|respiratory|sinusit)/i, organ: "respiratory" },
53
+ // respiratory — above the neurological rules so RESPIRATORY DEPRESSION is
54
+ // read as a respiratory event rather than by the bare "depression" stem
55
+ { rx: /\b(nasopharyng|pharyng|bronch|pneumon|dyspnoe|dyspnea|cough|respiratory|sinusit|pulmonary)/i, organ: "respiratory" },
56
+ // hematologic — above the neurological rules for the same reason, so
57
+ // BONE MARROW DEPRESSION is read as haematologic
58
+ { rx: /\b(haemoglobin|hemoglobin|haematocrit|leuko|neutro|thrombocyt|anaemi|haemat|platelet|marrow)/i, organ: "hematologic" },
59
+ // ocular — above the nerve rules so OPTIC NEUROPATHY stays ocular.
60
+ // `macular` is pinned to its ocular compounds so it never matches
61
+ // MACULOPAPULAR RASH.
62
+ {
63
+ rx: /\b(ocular|ophthalm|retinopath|retinal|glaucoma|cataract|uveit|conjunctivit|blindness|vision|visual|keratit|scleriti|blephariti|eye\s|macular\s+(?:oedema|edema|degenerat)|optic\s+(?:neuropath|neurit|atroph))/i,
64
+ organ: "ocular",
65
+ },
66
+ // peripheral nervous system — a neuropathy is a disease of the peripheral
67
+ // nerves; a central lesion is named by the terms in the cns rule below
68
+ // `neuropath` carries no leading \b so POLYNEUROPATHY and MONONEUROPATHY
69
+ // resolve as well as NEUROPATHY PERIPHERAL
70
+ { rx: /neuropath|neurit|\b(radiculopath|neuralg|carpal\s+tunnel|guillain|plexopath|peripheral\s+(?:motor|sensory))/i, organ: "pns" },
71
+ // central nervous system
72
+ {
73
+ rx: /\b(headache|dizz|seizure|convuls|suici|depression|anxiet|encephalo|migrain|myelopath|meningit|cerebr|intracranial|somnolen|ataxi|tremor|stroke)/i,
74
+ organ: "cns",
75
+ },
39
76
  // dermatologic
40
77
  { rx: /\b(rash|eczem|prurit|urticari|injection\s+site\s+(?:pain|haemorrhag|erythem))/i, organ: "dermatologic" },
41
- // musculoskeletal
42
- { rx: /\b(arthralg|myalg|musculoskel|fracture|bone\s+pain)/i, organ: "musculoskeletal" },
78
+ // musculoskeletal — malignant hyperthermia is a skeletal-muscle crisis and
79
+ // is matched here so the malignancy stem below never claims it
80
+ { rx: /\b(arthralg|myalg|musculoskel|fracture|bone\s+pain|rhabdomyol|malignant\s+hyperthermi)/i, organ: "musculoskeletal" },
43
81
  // immune
44
82
  { rx: /\b(hypersensitiv|anaphyl|autoimmune|cytokine\s+release)/i, organ: "immune" },
45
83
  // reproductive
46
84
  { rx: /\b(menstrual|menorrhag|ovari|endometr|prostat|erectile)/i, organ: "reproductive" },
85
+ // oncology — last, so a malignancy that names its site is filed under that
86
+ // site and only a term whose content is the malignancy itself lands here
87
+ { rx: /\b(neoplasm|malignan|carcinoma|sarcoma|lymphoma|leukaemi|leukemi|myeloma|myelodysplas|melanoma|metastas|tumour|tumor|cancer)/i, organ: "oncology" },
47
88
  ];
48
89
  const UNINFORMATIVE_RE = /\b(incorrect\s+dose|off\s+label|drug\s+ineffective|product\s+(?:use|storage|administration)|accidental\s+exposure|inappropriate\s+schedule|exposure\s+via\s+skin|extra\s+dose|wrong\s+technique)\b/i;
49
90
  export function classifyOrgan(meddraTerm) {
@@ -61,14 +102,18 @@ export function classifyOrgan(meddraTerm) {
61
102
  * do not contain MedDRA PT stems, so `classifyOrgan` returns null for them —
62
103
  * the SOC map is the primary path; `classifyOrgan(term)` is the fallback when
63
104
  * the SOC string isn't in the map.
105
+ *
106
+ * A SOC that spans more than one canonical organ is absent: "Nervous system
107
+ * disorders" covers both nervous-system tokens and "Endocrine disorders" is
108
+ * broader than the thyroid, so a row under either resolves by its preferred
109
+ * term or not at all.
64
110
  */
65
111
  export const TRIAL_AE_ORGAN_MAP = {
66
112
  "Gastrointestinal disorders": "gi",
67
113
  "Cardiac disorders": "cardiac",
68
- "Nervous system disorders": "cns",
114
+ "Vascular disorders": "vascular",
69
115
  "Hepatobiliary disorders": "hepatic",
70
116
  "Renal and urinary disorders": "renal",
71
- "Endocrine disorders": "endocrine_thyroid",
72
117
  "Respiratory, thoracic and mediastinal disorders": "respiratory",
73
118
  "Skin and subcutaneous tissue disorders": "dermatologic",
74
119
  "Musculoskeletal and connective tissue disorders": "musculoskeletal",
@@ -76,6 +121,8 @@ export const TRIAL_AE_ORGAN_MAP = {
76
121
  "Blood and lymphatic system disorders": "hematologic",
77
122
  "Reproductive system and breast disorders": "reproductive",
78
123
  "Immune system disorders": "immune",
124
+ "Eye disorders": "ocular",
125
+ "Neoplasms benign, malignant and unspecified (incl cysts and polyps)": "oncology",
79
126
  };
80
127
  /**
81
128
  * Classify a trial AE row by its SOC (preferred) or its individual MedDRA
@@ -89,38 +136,3 @@ export function classifyTrialAe(ae) {
89
136
  return fromSoc;
90
137
  return classifyOrgan(ae.term ?? "");
91
138
  }
92
- /**
93
- * Classify a polypharm off-target panel row by its canonical `organ_system`
94
- * string (set by the curated safety panel — `cardiac` | `hepatic` | `cns` |
95
- * `renal` | `gi` | `hematologic` | `immune` | `metabolic` | `respiratory`).
96
- *
97
- * Falls back to `classifyOrgan` if the upstream string is non-canonical;
98
- * this preserves coverage if a future collector emits MedDRA terms instead
99
- * of canonical organ names.
100
- */
101
- export function classifyPolypharmOrgan(organSystem) {
102
- const sys = (organSystem ?? "").toLowerCase().trim();
103
- if (!sys)
104
- return null;
105
- // Canonical names from the safety panel enum match by direct string equality.
106
- if (CANONICAL_ORGAN_NAMES.has(sys))
107
- return sys;
108
- return classifyOrgan(sys);
109
- }
110
- const CANONICAL_ORGAN_NAMES = new Set([
111
- "cardiac",
112
- "hepatic",
113
- "cns",
114
- "renal",
115
- "gi",
116
- "pancreas",
117
- "endocrine_thyroid",
118
- "metabolic",
119
- "hematologic",
120
- "immune",
121
- "respiratory",
122
- "reproductive",
123
- "dermatologic",
124
- "musculoskeletal",
125
- "oncology",
126
- ]);
@@ -13,7 +13,7 @@
13
13
  */
14
14
  const SCAFFOLD_TOKEN_RE = /per_section_synthesis\.[A-Za-z_][\w.]*(?:\[[\d–-]+\])?(?:\s+row\s+\d+)?/g;
15
15
  // Citations to dossier.per_section_synthesis.* — this namespace doesn't exist
16
- // in the v4 schema. The synthesis prompt occasionally emits these paths;
16
+ // in the dossier schema. The synthesis prompt occasionally emits these paths;
17
17
  // strip them to prevent non-resolvable citations in recommendation prose.
18
18
  const INVALID_PATH_RE = /\(?dossier\.per_section_synthesis\.[a-z_.[\]0-9]*\)?/gi;
19
19
  // Dangling "dossier." with no path suffix is leftover templating; strip it.
@@ -5,14 +5,13 @@
5
5
  * for each drug, then maps both into the dossier's RegulatoryActionRow
6
6
  * shape.
7
7
  *
8
- * This is the data-source replacement for the hand-curated calcitonin seed
9
- * that previously lived next to it. Coverage that legitimately has no
8
+ * Coverage that legitimately has no
10
9
  * machine-readable source (e.g., EMA assessment-report procedure codes like
11
10
  * EMEA/H/A-31/1291) is omitted rather than backfilled with curation — the
12
11
  * source_url points at the canonical document where the human reader can
13
12
  * extract the additional context.
14
13
  */
15
- import type { RegulatoryActionRowV5 as RegulatoryActionRow } from "@inflexa-ai/harness/contracts/target-dossier.js";
14
+ import type { RegulatoryActionRow } from "@inflexa-ai/harness/contracts/target-dossier.js";
16
15
  export interface DrugForRegulatoryActions {
17
16
  chemblId: string;
18
17
  name: string;
@@ -5,8 +5,7 @@
5
5
  * for each drug, then maps both into the dossier's RegulatoryActionRow
6
6
  * shape.
7
7
  *
8
- * This is the data-source replacement for the hand-curated calcitonin seed
9
- * that previously lived next to it. Coverage that legitimately has no
8
+ * Coverage that legitimately has no
10
9
  * machine-readable source (e.g., EMA assessment-report procedure codes like
11
10
  * EMEA/H/A-31/1291) is omitted rather than backfilled with curation — the
12
11
  * source_url points at the canonical document where the human reader can
@@ -15,6 +14,7 @@
15
14
  import { withHost } from "../../../lib/host-concurrency.js";
16
15
  import { getReferralsByDrug } from "../../../tools/lib/ema-client.js";
17
16
  import { getDrugLabelActions } from "../../../tools/lib/openfda-client.js";
17
+ import { segmentLabelSafety } from "./fda-label-safety.js";
18
18
  function formatEffectiveTime(yyyymmdd) {
19
19
  if (!yyyymmdd || yyyymmdd.length < 6)
20
20
  return "";
@@ -35,6 +35,29 @@ function clipFinding(text) {
35
35
  return cleaned;
36
36
  return `${cleaned.slice(0, FINDING_CHAR_LIMIT - 1).trimEnd()}…`;
37
37
  }
38
+ /**
39
+ * Project a fetched label onto the shared typed safety form. Returns null when
40
+ * the label carries no application number: its warnings could not then be cited
41
+ * back to a document, and an uncitable warning is not evidence.
42
+ */
43
+ function labelSafetyOf(drug, label) {
44
+ if (!label.applicationNumber)
45
+ return null;
46
+ const sections = [];
47
+ if (label.boxedWarning)
48
+ sections.push({ section: "boxed_warning", text: label.boxedWarning });
49
+ if (label.warningsAndCautions)
50
+ sections.push({ section: "warnings_and_precautions", text: label.warningsAndCautions });
51
+ if (sections.length === 0)
52
+ return null;
53
+ return {
54
+ application_number: label.applicationNumber,
55
+ drug_name: label.genericName ?? label.brandName ?? drug.name,
56
+ effective_time: label.effectiveTime ?? undefined,
57
+ source_url: label.sourceUrl || undefined,
58
+ sections,
59
+ };
60
+ }
38
61
  function fdaRowsFor(drug, labels) {
39
62
  if (labels.length === 0)
40
63
  return [];
@@ -42,6 +65,12 @@ function fdaRowsFor(drug, labels) {
42
65
  const latest = sorted[0];
43
66
  const rows = [];
44
67
  const actionDate = formatEffectiveTime(latest.effectiveTime);
68
+ // The label's own prose, segmented once and attributed to organs, is what
69
+ // lets each row cite the section a reader can open rather than only the
70
+ // agency and the date.
71
+ const labelSafety = labelSafetyOf(drug, latest);
72
+ const signals = labelSafety ? segmentLabelSafety([labelSafety]).signals : [];
73
+ const evidenceFrom = (section) => signals.filter((s) => s.source_section === section).map((s) => s.evidence);
45
74
  if (latest.boxedWarning) {
46
75
  rows.push({
47
76
  drug_chembl_id: drug.chemblId,
@@ -54,7 +83,7 @@ function fdaRowsFor(drug, labels) {
54
83
  application_number: latest.applicationNumber ?? undefined,
55
84
  finding: clipFinding(latest.boxedWarning),
56
85
  source_url: latest.sourceUrl || undefined,
57
- evidence: [],
86
+ evidence: evidenceFrom("boxed_warning"),
58
87
  });
59
88
  }
60
89
  if (latest.warningsAndCautions) {
@@ -70,7 +99,7 @@ function fdaRowsFor(drug, labels) {
70
99
  label_section: "5",
71
100
  finding: clipFinding(latest.warningsAndCautions),
72
101
  source_url: latest.sourceUrl || undefined,
73
- evidence: [],
102
+ evidence: evidenceFrom("warnings_and_precautions"),
74
103
  });
75
104
  }
76
105
  if (latest.hasRems) {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The safety-evidence corroboration spine.
3
+ *
4
+ * A run queries a dozen-plus independent sources and files each one's output in
5
+ * its own dossier section. This fold asks the question none of those sections
6
+ * can: for a given organ, do the sources agree? Agreement across independent
7
+ * sources is itself evidence, and it is what separates a real organ liability
8
+ * from a single-source artefact.
9
+ *
10
+ * The fold re-fetches nothing. Every signal it reads is already in hand when it
11
+ * runs, and a source that also populates a section of its own is referenced
12
+ * here as a contribution rather than restated — there is one evidence path, not
13
+ * two.
14
+ *
15
+ * **The source list is open.** A source is one entry in `SIGNAL_SOURCES`
16
+ * declaring its id and how to read its signals out of the run's inputs.
17
+ * Everything downstream — grouping, counting, thresholding, evidence assembly —
18
+ * is written against the id-bearing record, so a fifteenth collector joins the
19
+ * spine by adding an entry and changes neither the fold nor the schema.
20
+ */
21
+ import type { SafetyCorroboration } from "@inflexa-ai/harness/contracts/target-dossier.js";
22
+ import type { Phase1Bundle } from "../schemas.js";
23
+ import type { OrganSignalProjection } from "./fda-label-safety.js";
24
+ /** Distinct sources an organ needs before the spine calls it corroborated. */
25
+ export declare const MIN_INDEPENDENT_SOURCES = 2;
26
+ /** What the section reports as having emptied it. */
27
+ export declare const CORROBORATION_FILTER: string;
28
+ /** Everything the fold reads. Already collected — nothing here is fetched. */
29
+ export interface CorroborationInput {
30
+ readonly phase1: Phase1Bundle;
31
+ /** Per-organ FDA label warnings segmented at the approval-precedent step. */
32
+ readonly regulatoryOrganSignals: OrganSignalProjection | null;
33
+ }
34
+ /**
35
+ * Fold the run's per-organ safety signals into one corroboration record per
36
+ * organ.
37
+ *
38
+ * `dropped_count` counts signals — the fold's input unit — discarded because
39
+ * their organ did not resolve, because their source produced no locator, or
40
+ * because their organ drew fewer than `MIN_INDEPENDENT_SOURCES` distinct
41
+ * sources. A fold that ends with no record reports `filtered` with that count
42
+ * rather than an empty `available`.
43
+ */
44
+ export declare function assembleSafetyCorroboration(input: CorroborationInput): SafetyCorroboration;
@@ -0,0 +1,273 @@
1
+ /**
2
+ * The safety-evidence corroboration spine.
3
+ *
4
+ * A run queries a dozen-plus independent sources and files each one's output in
5
+ * its own dossier section. This fold asks the question none of those sections
6
+ * can: for a given organ, do the sources agree? Agreement across independent
7
+ * sources is itself evidence, and it is what separates a real organ liability
8
+ * from a single-source artefact.
9
+ *
10
+ * The fold re-fetches nothing. Every signal it reads is already in hand when it
11
+ * runs, and a source that also populates a section of its own is referenced
12
+ * here as a contribution rather than restated — there is one evidence path, not
13
+ * two.
14
+ *
15
+ * **The source list is open.** A source is one entry in `SIGNAL_SOURCES`
16
+ * declaring its id and how to read its signals out of the run's inputs.
17
+ * Everything downstream — grouping, counting, thresholding, evidence assembly —
18
+ * is written against the id-bearing record, so a fifteenth collector joins the
19
+ * spine by adding an entry and changes neither the fold nor the schema.
20
+ */
21
+ import { isSafetyRelevant, meetsTpmFloor, resolveTissueOrganSystem } from "../assemblers/safety.js";
22
+ import { resolveHpoOrgan } from "./hpo-organ-map.js";
23
+ import { resolveImpcOrgan } from "./impc-organ-map.js";
24
+ import { classifyOrgan } from "./meddra-organ-map.js";
25
+ /** Distinct sources an organ needs before the spine calls it corroborated. */
26
+ export const MIN_INDEPENDENT_SOURCES = 2;
27
+ /** What the section reports as having emptied it. */
28
+ export const CORROBORATION_FILTER = `organ signals with no resolvable organ or locator, or whose organ drew fewer than ` + `${MIN_INDEPENDENT_SOURCES} independent sources`;
29
+ /**
30
+ * Curated human phenotypes ranked per organ. A well-annotated gene carries
31
+ * hundreds of HPO terms under one organ system; beyond the ranked head they
32
+ * restate the same organ-level signal, so the source raises it once at the
33
+ * granularity a reader can act on.
34
+ */
35
+ const PHENOTYPES_PER_ORGAN = 3;
36
+ // ── Source extractors ────────────────────────────────────────────────
37
+ /** Warning fragments from FDA prescribing information, already organ-keyed. */
38
+ function extractRegulatoryLabels(input) {
39
+ const projection = input.regulatoryOrganSignals;
40
+ if (!projection)
41
+ return [];
42
+ return projection.signals.map((s) => ({
43
+ organ: s.organ,
44
+ signal: `${s.drug_name} label §${s.label_section}: ${s.excerpt}`,
45
+ evidence: s.evidence.regulatory_reference ? s.evidence : null,
46
+ }));
47
+ }
48
+ /** Mouse knockout phenotype systems, keyed by the MGI accession for the gene. */
49
+ function extractKnockoutPhenotypes(input) {
50
+ const impc = input.phase1.collectors.impc;
51
+ if (impc.coverage !== "available")
52
+ return [];
53
+ const accession = impc.data.mgiAccessionId;
54
+ return impc.data.organSystems.map((bucket) => ({
55
+ organ: resolveImpcOrgan(bucket),
56
+ signal: `mouse knockout phenotype in the ${bucket} system`,
57
+ evidence: accession
58
+ ? {
59
+ source: "impc",
60
+ accession,
61
+ predicate: "knockout_phenotype",
62
+ excerpt: `${impc.data.mouseMarkerSymbol ?? ""} knockout phenotypes in the ${bucket} system`.trim(),
63
+ metadata: { bucket, phenotype_count: impc.data.phenotypeCount },
64
+ }
65
+ : null,
66
+ }));
67
+ }
68
+ /** Curated human phenotypes, resolved by HPO ancestry rather than by label. */
69
+ function extractHumanPhenotypes(input) {
70
+ const monarch = input.phase1.collectors.monarch;
71
+ if (monarch.coverage !== "available")
72
+ return [];
73
+ const ranked = [...monarch.data.phenotypes].sort((a, b) => b.publications.length - a.publications.length || (b.frequencyPercent ?? 0) - (a.frequencyPercent ?? 0) || a.label.localeCompare(b.label));
74
+ const perOrgan = new Map();
75
+ const out = [];
76
+ for (const p of ranked) {
77
+ const organ = resolveHpoOrgan(p.hpoId, p.ancestorIds);
78
+ if (organ === null) {
79
+ out.push({ organ: null, signal: p.label, evidence: null });
80
+ continue;
81
+ }
82
+ const seen = perOrgan.get(organ) ?? 0;
83
+ if (seen >= PHENOTYPES_PER_ORGAN)
84
+ continue;
85
+ perOrgan.set(organ, seen + 1);
86
+ const pmid = p.publications.find((pub) => pub.startsWith("PMID:"))?.slice("PMID:".length);
87
+ out.push({
88
+ organ,
89
+ signal: `curated human phenotype: ${p.label}`,
90
+ evidence: {
91
+ source: "monarch",
92
+ accession: p.hpoId,
93
+ ...(pmid ? { pmid } : {}),
94
+ predicate: "human_phenotype",
95
+ is_human: true,
96
+ excerpt: p.label,
97
+ metadata: {
98
+ ...(p.diseaseContext ? { disease_context: p.diseaseContext } : {}),
99
+ ...(p.frequencyPercent != null ? { frequency_percent: p.frequencyPercent } : {}),
100
+ ...(p.primaryKnowledgeSource ? { knowledge_source: p.primaryKnowledgeSource } : {}),
101
+ },
102
+ },
103
+ });
104
+ }
105
+ return out;
106
+ }
107
+ /** Open Targets' curated safety liabilities, keyed by the Ensembl accession. */
108
+ function extractCuratedSafetyLiabilities(input) {
109
+ const ot = input.phase1.collectors.opentargets;
110
+ if (ot.coverage !== "available")
111
+ return [];
112
+ return ot.data.safetyLiabilities.map((liability) => ({
113
+ organ: classifyOrgan(liability.event) ?? classifyOrgan(liability.biosamples.join(" ")),
114
+ signal: `curated safety liability: ${liability.event}`,
115
+ evidence: {
116
+ source: "opentargets:safety",
117
+ accession: ot.data.ensemblId,
118
+ predicate: "known_safety_liability",
119
+ excerpt: liability.effects ? `${liability.event} — ${liability.effects}` : liability.event,
120
+ metadata: { biosamples: liability.biosamples, curator: liability.source },
121
+ },
122
+ }));
123
+ }
124
+ /**
125
+ * Baseline expression in safety-relevant tissues.
126
+ *
127
+ * The atlas reaches the dossier through Open Targets' consensus expression,
128
+ * which is Human Protein Atlas data — the same path
129
+ * `reference_biology.normal_tissue_expression` and the off-tissue risk rows
130
+ * read, with the same safety-relevance and floor rules applied. Only the
131
+ * highest-expressing tissue per organ is raised: several tissues mapping to one
132
+ * organ are one expression signal about that organ.
133
+ */
134
+ function extractExpressionAtlas(input) {
135
+ const expression = input.phase1.collectors.expressionHuman;
136
+ if (expression.coverage !== "available")
137
+ return [];
138
+ const accession = input.phase1.resolved.ids.ensembl;
139
+ const best = new Map();
140
+ const unresolved = [];
141
+ for (const t of expression.data.tissues) {
142
+ const value = t.value ?? 0;
143
+ if (!isSafetyRelevant(t.tissueLabel, t.organSystem) || !meetsTpmFloor(t.tissueLabel, value))
144
+ continue;
145
+ const organ = resolveTissueOrganSystem(t.tissueLabel, t.organSystem);
146
+ if (organ === null) {
147
+ unresolved.push({ organ: null, signal: `${t.tissueLabel} expression`, evidence: null });
148
+ continue;
149
+ }
150
+ const current = best.get(organ);
151
+ if (!current || value > current.value)
152
+ best.set(organ, { tissue: t.tissueLabel, value });
153
+ }
154
+ const resolved = [...best.entries()].map(([organ, hit]) => {
155
+ const signal = `${expression.data.unit} expression ${hit.value} in ${hit.tissue}`;
156
+ return {
157
+ organ,
158
+ signal,
159
+ evidence: accession
160
+ ? {
161
+ source: `expression:${expression.data.source}`,
162
+ accession,
163
+ predicate: "expressed_in_safety_tissue",
164
+ score: hit.value,
165
+ excerpt: `${hit.tissue}: ${hit.value} (${expression.data.unit})`,
166
+ metadata: {
167
+ tissue: hit.tissue,
168
+ unit: expression.data.unit,
169
+ normalization_notes: expression.data.normalization_notes,
170
+ },
171
+ }
172
+ : null,
173
+ };
174
+ });
175
+ return [...resolved, ...unresolved];
176
+ }
177
+ /**
178
+ * The registered sources.
179
+ *
180
+ * This array is the whole integration surface: an id, and how to read that
181
+ * source's organ-bearing signals out of what the run already collected.
182
+ */
183
+ const SIGNAL_SOURCES = [
184
+ { id: "fda_label", extract: extractRegulatoryLabels },
185
+ { id: "impc", extract: extractKnockoutPhenotypes },
186
+ { id: "monarch", extract: extractHumanPhenotypes },
187
+ { id: "opentargets_safety", extract: extractCuratedSafetyLiabilities },
188
+ { id: "expression_atlas", extract: extractExpressionAtlas },
189
+ ];
190
+ /**
191
+ * Fold the run's per-organ safety signals into one corroboration record per
192
+ * organ.
193
+ *
194
+ * `dropped_count` counts signals — the fold's input unit — discarded because
195
+ * their organ did not resolve, because their source produced no locator, or
196
+ * because their organ drew fewer than `MIN_INDEPENDENT_SOURCES` distinct
197
+ * sources. A fold that ends with no record reports `filtered` with that count
198
+ * rather than an empty `available`.
199
+ */
200
+ export function assembleSafetyCorroboration(input) {
201
+ const sourcesConsidered = [];
202
+ const admitted = [];
203
+ let dropped = 0;
204
+ for (const source of SIGNAL_SOURCES) {
205
+ const signals = source.extract(input);
206
+ if (signals.length === 0)
207
+ continue;
208
+ sourcesConsidered.push(source.id);
209
+ const seen = new Set();
210
+ for (const raw of signals) {
211
+ if (raw.organ === null || raw.evidence === null) {
212
+ dropped += 1;
213
+ continue;
214
+ }
215
+ const key = `${raw.organ}\u0000${raw.signal}`;
216
+ if (seen.has(key))
217
+ continue;
218
+ seen.add(key);
219
+ admitted.push({
220
+ source: source.id,
221
+ organ: raw.organ,
222
+ contribution: { source: source.id, signal: raw.signal, evidence: raw.evidence },
223
+ });
224
+ }
225
+ }
226
+ if (sourcesConsidered.length === 0) {
227
+ return {
228
+ coverage: "queried_no_data",
229
+ error: { message: "no source produced an organ-bearing safety signal" },
230
+ };
231
+ }
232
+ const byOrgan = new Map();
233
+ for (const item of admitted) {
234
+ const bucket = byOrgan.get(item.organ);
235
+ if (bucket)
236
+ bucket.push(item);
237
+ else
238
+ byOrgan.set(item.organ, [item]);
239
+ }
240
+ const rows = [];
241
+ for (const [organ, items] of byOrgan) {
242
+ const corroborating = [...new Set(items.map((i) => i.source))].sort();
243
+ if (corroborating.length < MIN_INDEPENDENT_SOURCES) {
244
+ dropped += items.length;
245
+ continue;
246
+ }
247
+ const contributions = items.map((i) => i.contribution);
248
+ rows.push({
249
+ organ,
250
+ contributions,
251
+ corroborating_sources: corroborating,
252
+ independent_source_count: corroborating.length,
253
+ support: { state: "scored", evidence: contributions.map((c) => c.evidence) },
254
+ });
255
+ }
256
+ rows.sort((a, b) => b.independent_source_count - a.independent_source_count || a.organ.localeCompare(b.organ));
257
+ // Reaching here means at least one source emitted a signal — that is what put
258
+ // it in `sourcesConsidered` — and every emitted signal was either admitted or
259
+ // counted as dropped. So no record left standing means the fold's own
260
+ // thresholds took them all, and `dropped` is necessarily positive.
261
+ if (rows.length === 0) {
262
+ return { coverage: "filtered", filter: CORROBORATION_FILTER, dropped_count: dropped };
263
+ }
264
+ return {
265
+ coverage: "available",
266
+ data: {
267
+ rows,
268
+ sources_considered: [...sourcesConsidered].sort(),
269
+ min_independent_sources: MIN_INDEPENDENT_SOURCES,
270
+ },
271
+ ...(dropped > 0 ? { dropped_count: dropped } : {}),
272
+ };
273
+ }
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Trial-attribution classifier + UniProt-backed identity resolvers.
3
3
  *
4
- * `resolveOnTargetChemblIds` and `resolveFamilySiblingUniprots` replace the
5
- * hand-curated ALTERNATE_CHEMBL_IDS and RELATED_FAMILY_UNIPROTS maps that
6
- * previously lived here. Both fetch from authoritative sources at assembly
7
- * time (UniProt for ChEMBL cross-references; IUPHAR Guide to Pharmacology
8
- * for receptor-family siblings) so no per-target editing is needed when a
9
- * new HGNC target is assessed.
4
+ * `resolveOnTargetChemblIds` and `resolveFamilySiblingUniprots` fetch from
5
+ * authoritative sources at assembly time — UniProt for ChEMBL cross-references,
6
+ * IUPHAR Guide to Pharmacology for receptor-family siblings — so assessing a
7
+ * new HGNC target needs no per-target curation here.
10
8
  *
11
9
  * The actual loop-level checks (`isOnTargetChemblId` etc.) take the
12
10
  * resolved arrays as plain inputs so the per-row assembly path stays
@@ -48,7 +46,7 @@ export declare function resolveOnTargetChemblIds(assessmentUniprot: string): Pro
48
46
  * Resolve UniProt accessions of sibling receptors in the same IUPHAR family
49
47
  * as the assessment target. Used by `classifyTrialAttribution` so a trial
50
48
  * whose intervention hits a paralog (e.g., CALCRL when CALCR is the
51
- * assessment target) lands in `related_target_trials[]` rather than the
49
+ * assessment target) lands in the related bucket rather than the
52
50
  * primary trials list.
53
51
  */
54
52
  export declare function resolveFamilySiblingUniprots(uniprotOrGeneSymbol: string): Promise<string[]>;
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Trial-attribution classifier + UniProt-backed identity resolvers.
3
3
  *
4
- * `resolveOnTargetChemblIds` and `resolveFamilySiblingUniprots` replace the
5
- * hand-curated ALTERNATE_CHEMBL_IDS and RELATED_FAMILY_UNIPROTS maps that
6
- * previously lived here. Both fetch from authoritative sources at assembly
7
- * time (UniProt for ChEMBL cross-references; IUPHAR Guide to Pharmacology
8
- * for receptor-family siblings) so no per-target editing is needed when a
9
- * new HGNC target is assessed.
4
+ * `resolveOnTargetChemblIds` and `resolveFamilySiblingUniprots` fetch from
5
+ * authoritative sources at assembly time — UniProt for ChEMBL cross-references,
6
+ * IUPHAR Guide to Pharmacology for receptor-family siblings — so assessing a
7
+ * new HGNC target needs no per-target curation here.
10
8
  *
11
9
  * The actual loop-level checks (`isOnTargetChemblId` etc.) take the
12
10
  * resolved arrays as plain inputs so the per-row assembly path stays
@@ -63,7 +61,7 @@ export async function resolveOnTargetChemblIds(assessmentUniprot) {
63
61
  * Resolve UniProt accessions of sibling receptors in the same IUPHAR family
64
62
  * as the assessment target. Used by `classifyTrialAttribution` so a trial
65
63
  * whose intervention hits a paralog (e.g., CALCRL when CALCR is the
66
- * assessment target) lands in `related_target_trials[]` rather than the
64
+ * assessment target) lands in the related bucket rather than the
67
65
  * primary trials list.
68
66
  */
69
67
  export async function resolveFamilySiblingUniprots(uniprotOrGeneSymbol) {
@@ -1,13 +1,11 @@
1
1
  /**
2
2
  * OpenTelemetry counters for the target-assessment DBOS workflow.
3
3
  *
4
- * Surfaces three signals:
4
+ * Surfaces two signals:
5
5
  * - `cortex.harness.ta.workflow.terminal_reason` — per terminal path; the
6
6
  * alert in §16.5/§16.6 reads this. `reason` ∈ {completed,
7
7
  * target-unresolved, schema-violation, derived-invariant-violation,
8
8
  * unexpected-throw, suspended-on-402, operator-cancelled}.
9
- * - `cortex.harness.ta.llm.attempts` — histogram per LLM step (caller
10
- * samples on entry to detect probe-retry loops).
11
9
  * - billing cache_hit/_miss live in `harness/billing/target-assessment-resolver.ts`.
12
10
  *
13
11
  * Memoised meter so a re-imported module in tests doesn't recreate the
@@ -15,7 +13,4 @@
15
13
  * "duplicate instrument" warnings).
16
14
  */
17
15
  export type TaTerminalReason = "completed" | "target-unresolved" | "schema-violation" | "derived-invariant-violation" | "unexpected-throw" | "suspended-on-402" | "operator-cancelled" | "deleted";
18
- /** Test hook — drop memoised instruments to rebind a fresh MeterProvider. */
19
- export declare function __resetTaWorkflowMetricsForTest(): void;
20
16
  export declare function recordTerminalReason(reason: TaTerminalReason): void;
21
- export declare function recordLlmAttempt(agentId: string, attempt: number): void;