@inflexa-ai/harness 0.17.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 (221) hide show
  1. package/dist/agents/report-builder.d.ts +2 -0
  2. package/dist/agents/report-builder.js +2 -0
  3. package/dist/app/spawn-report-session.d.ts +85 -0
  4. package/dist/app/spawn-report-session.js +83 -0
  5. package/dist/contracts/chat-events.d.ts +9 -0
  6. package/dist/contracts/index.d.ts +6 -2
  7. package/dist/contracts/index.js +3 -1
  8. package/dist/contracts/organ-system.d.ts +49 -0
  9. package/dist/contracts/organ-system.js +67 -0
  10. package/dist/contracts/report-blocks.d.ts +564 -0
  11. package/dist/contracts/report-blocks.js +117 -0
  12. package/dist/contracts/report-reference.d.ts +459 -0
  13. package/dist/contracts/report-reference.js +256 -0
  14. package/dist/contracts/schemas/chat-events.d.ts +2 -0
  15. package/dist/contracts/schemas/chat-events.js +2 -0
  16. package/dist/contracts/severity.d.ts +15 -0
  17. package/dist/contracts/severity.js +10 -0
  18. package/dist/contracts/target-dossier.d.ts +8675 -12972
  19. package/dist/contracts/target-dossier.js +534 -591
  20. package/dist/data/safety-panel-schema.d.ts +18 -5
  21. package/dist/data/safety-panel-schema.js +4 -4
  22. package/dist/execution/artifact-metadata.js +1 -0
  23. package/dist/execution/report-runner.js +1 -3
  24. package/dist/execution/run-synthesis.js +2 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.js +19 -0
  27. package/dist/lib/async-utils.d.ts +16 -4
  28. package/dist/lib/async-utils.js +20 -15
  29. package/dist/lib/host-concurrency.js +1 -0
  30. package/dist/loop/__fixtures__/scripted-provider.js +23 -2
  31. package/dist/loop/run-agent.js +58 -10
  32. package/dist/loop/tool-detail.d.ts +40 -0
  33. package/dist/loop/tool-detail.js +35 -3
  34. package/dist/loop/types.d.ts +13 -0
  35. package/dist/memory/thread-history.d.ts +10 -0
  36. package/dist/memory/thread-history.js +12 -1
  37. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
  38. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
  39. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
  40. package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
  41. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
  42. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
  43. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
  44. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
  45. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
  46. package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
  47. package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
  48. package/dist/prompts/target-assessment/investigation/index.js +3 -0
  49. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
  50. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
  51. package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
  52. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
  53. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
  54. package/dist/providers/__fixtures__/session.d.ts +5 -4
  55. package/dist/providers/__fixtures__/session.js +4 -4
  56. package/dist/report-model/block-walk.d.ts +56 -0
  57. package/dist/report-model/block-walk.js +89 -0
  58. package/dist/report-model/draft-finish.d.ts +60 -0
  59. package/dist/report-model/draft-finish.js +56 -0
  60. package/dist/report-model/draft-operations.d.ts +125 -0
  61. package/dist/report-model/draft-operations.js +341 -0
  62. package/dist/report-model/draft-read.d.ts +59 -0
  63. package/dist/report-model/draft-read.js +109 -0
  64. package/dist/report-model/draft.d.ts +51 -0
  65. package/dist/report-model/draft.js +32 -0
  66. package/dist/report-model/fixture-resolver.d.ts +8 -0
  67. package/dist/report-model/fixture-resolver.js +252 -0
  68. package/dist/report-model/mint-snapshot.d.ts +30 -0
  69. package/dist/report-model/mint-snapshot.js +40 -0
  70. package/dist/report-model/reference-resolver.d.ts +113 -0
  71. package/dist/report-model/reference-resolver.js +60 -0
  72. package/dist/report-model/structural-validation.d.ts +22 -0
  73. package/dist/report-model/structural-validation.js +68 -0
  74. package/dist/report-model/validate.d.ts +40 -0
  75. package/dist/report-model/validate.js +80 -0
  76. package/dist/report-render/chart.d.ts +33 -0
  77. package/dist/report-render/chart.js +538 -0
  78. package/dist/report-render/page.d.ts +127 -0
  79. package/dist/report-render/page.js +307 -0
  80. package/dist/report-render/references.d.ts +32 -0
  81. package/dist/report-render/references.js +44 -0
  82. package/dist/report-render/render.d.ts +25 -0
  83. package/dist/report-render/render.js +125 -0
  84. package/dist/report-render/script-json.d.ts +13 -0
  85. package/dist/report-render/script-json.js +15 -0
  86. package/dist/report-render/types.d.ts +39 -0
  87. package/dist/report-render/types.js +10 -0
  88. package/dist/report-render/views/chart-view.d.ts +11 -0
  89. package/dist/report-render/views/chart-view.js +23 -0
  90. package/dist/report-render/views/page-view.d.ts +20 -0
  91. package/dist/report-render/views/page-view.js +49 -0
  92. package/dist/report-render/views/prose.d.ts +28 -0
  93. package/dist/report-render/views/prose.js +75 -0
  94. package/dist/report-render/views/references-view.d.ts +20 -0
  95. package/dist/report-render/views/references-view.js +64 -0
  96. package/dist/report-render/views/values.d.ts +42 -0
  97. package/dist/report-render/views/values.js +43 -0
  98. package/dist/runtime/boot.d.ts +3 -3
  99. package/dist/runtime/boot.js +15 -4
  100. package/dist/runtime/dbos.d.ts +4 -4
  101. package/dist/state/artifacts.d.ts +16 -0
  102. package/dist/state/artifacts.js +18 -0
  103. package/dist/state/init.js +31 -0
  104. package/dist/state/purge-analysis.js +1 -0
  105. package/dist/state/report-versions.d.ts +132 -0
  106. package/dist/state/report-versions.js +194 -0
  107. package/dist/tasks/data-profile.js +1 -0
  108. package/dist/tools/bio/chembl.js +1 -0
  109. package/dist/tools/bio/comptox.js +1 -0
  110. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  111. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  112. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  113. package/dist/tools/bio/keys.d.ts +3 -2
  114. package/dist/tools/bio/keys.js +3 -2
  115. package/dist/tools/bio/lookup-annotation.js +1 -0
  116. package/dist/tools/bio/opentargets.d.ts +3 -3
  117. package/dist/tools/bio/opentargets.js +4 -3
  118. package/dist/tools/bio/pubchem.js +1 -0
  119. package/dist/tools/bio/search-clinical-trials.js +1 -0
  120. package/dist/tools/bio/search-faers.js +1 -0
  121. package/dist/tools/bio/search-geo-datasets.js +1 -0
  122. package/dist/tools/bio/search-interactions.js +1 -0
  123. package/dist/tools/bio/target-safety.d.ts +4 -6
  124. package/dist/tools/bio/target-safety.js +7 -6
  125. package/dist/tools/define-tool.d.ts +19 -12
  126. package/dist/tools/define-tool.js +4 -2
  127. package/dist/tools/display/show-user.js +1 -0
  128. package/dist/tools/iterate-report.js +2 -0
  129. package/dist/tools/lib/chembl-client.d.ts +2 -0
  130. package/dist/tools/lib/chembl-client.js +1 -0
  131. package/dist/tools/lib/monarch-client.d.ts +42 -0
  132. package/dist/tools/lib/monarch-client.js +100 -0
  133. package/dist/tools/report/build-report.js +1 -0
  134. package/dist/tools/report/mint-preview-url.js +1 -0
  135. package/dist/tools/report/preview-snapshot.js +1 -0
  136. package/dist/tools/report/submit-report.js +1 -0
  137. package/dist/tools/report/version-fs.js +4 -0
  138. package/dist/tools/report-authoring/authoring-tools.d.ts +134 -0
  139. package/dist/tools/report-authoring/authoring-tools.js +352 -0
  140. package/dist/tools/research/context7-docs.js +2 -0
  141. package/dist/tools/research/generate-analogy-report.js +1 -0
  142. package/dist/tools/research/generate-plan.js +3 -0
  143. package/dist/tools/research/inspect-data-profile.js +4 -0
  144. package/dist/tools/research/literature-reviewer.js +1 -0
  145. package/dist/tools/research/search-arxiv.js +1 -0
  146. package/dist/tools/research/search-github-repos.js +1 -0
  147. package/dist/tools/research/search-semantic-scholar.js +1 -0
  148. package/dist/tools/sandbox/list-available-packages.js +23 -0
  149. package/dist/tools/sandbox/list-available-refs.js +36 -0
  150. package/dist/tools/sandbox/report-blocker.js +1 -0
  151. package/dist/tools/sandbox/skills.js +2 -0
  152. package/dist/tools/workspace/file-stat.js +1 -0
  153. package/dist/tools/workspace/show-file.js +2 -0
  154. package/dist/tools/workspace/show-plan.js +1 -0
  155. package/dist/workflows/execute-analysis.d.ts +5 -1
  156. package/dist/workflows/execute-analysis.js +5 -5
  157. package/dist/workflows/execute-target-assessment.d.ts +13 -2
  158. package/dist/workflows/execute-target-assessment.js +78 -16
  159. package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
  160. package/dist/workflows/target-assessment/assemblers/index.js +1 -1
  161. package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
  162. package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
  163. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
  164. package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
  165. package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
  166. package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
  167. package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
  168. package/dist/workflows/target-assessment/collectors/index.js +48 -5
  169. package/dist/workflows/target-assessment/coverage.d.ts +43 -27
  170. package/dist/workflows/target-assessment/coverage.js +33 -29
  171. package/dist/workflows/target-assessment/fanout/index.js +1 -0
  172. package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
  173. package/dist/workflows/target-assessment/investigation/index.js +419 -0
  174. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
  175. package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
  176. package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
  177. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
  178. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
  179. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
  180. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
  181. package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
  182. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
  183. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
  184. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
  185. package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
  186. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
  187. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
  188. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
  189. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
  190. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
  191. package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
  192. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
  193. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
  194. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
  195. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
  196. package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
  197. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
  198. package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
  199. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
  200. package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
  201. package/dist/workflows/target-assessment/metrics.d.ts +1 -6
  202. package/dist/workflows/target-assessment/metrics.js +1 -13
  203. package/dist/workflows/target-assessment/phase4-assemble.d.ts +2147 -807
  204. package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
  205. package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
  206. package/dist/workflows/target-assessment/phase5-persist.js +56 -173
  207. package/dist/workflows/target-assessment/schemas.d.ts +46 -1
  208. package/dist/workflows/target-assessment/schemas.js +19 -0
  209. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
  210. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
  211. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +26 -62
  212. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
  213. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +27 -154
  214. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
  215. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
  216. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
  217. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
  218. package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
  219. package/dist/workflows/target-assessment/synthesis/index.js +4 -1
  220. package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
  221. package/package.json +4 -1
@@ -18,7 +18,7 @@
18
18
  import { z } from "zod";
19
19
  import type { Pool } from "pg";
20
20
  import type { AgentSession } from "../../../auth/types.js";
21
- import type { ChatProvider } from "../../../providers/types.js";
21
+ import type { AgentChat } from "../../../providers/types.js";
22
22
  import type { UsageRecorder } from "../../../billing/usage-recorder.js";
23
23
  export declare const OffTargetAnnotationSchema: z.ZodObject<{
24
24
  clinical_consequence: z.ZodString;
@@ -26,7 +26,7 @@ export declare const OffTargetAnnotationSchema: z.ZodObject<{
26
26
  }, z.core.$strip>;
27
27
  export type OffTargetAnnotation = z.infer<typeof OffTargetAnnotationSchema>;
28
28
  export interface ClinicalConsequenceAnnotatorDeps {
29
- readonly provider: ChatProvider;
29
+ readonly provider: AgentChat;
30
30
  readonly session: AgentSession;
31
31
  readonly model: string;
32
32
  /** LLM usage-accounting seam for the annotator loop; omitted falls back to the no-op recorder. */
@@ -1,4 +1,4 @@
1
- import type { ClinicalTrialAttributionV5, ClinicalTrialRowV5 } from "@inflexa-ai/harness/contracts/target-dossier.js";
1
+ import type { ClinicalTrialAttribution } from "@inflexa-ai/harness/contracts/target-dossier.js";
2
2
  type TrialLike = {
3
3
  nctId: string;
4
4
  title: string;
@@ -37,9 +37,8 @@ export type ClinicalEvidenceAttributionContext = {
37
37
  therapeuticPrograms: TherapeuticProgram[];
38
38
  };
39
39
  export type ClinicalEvidenceAttributionResult = {
40
- attribution: ClinicalTrialAttributionV5;
40
+ attribution: ClinicalTrialAttribution;
41
41
  eligible_for_toxicology_aggregation: boolean;
42
42
  };
43
43
  export declare function classifyClinicalEvidenceTrial(trial: TrialLike, ctx: ClinicalEvidenceAttributionContext): ClinicalEvidenceAttributionResult;
44
- export declare function attachClinicalEvidenceAttribution<T extends TrialLike>(trial: T, ctx: ClinicalEvidenceAttributionContext): T & Pick<ClinicalTrialRowV5, "attribution" | "eligible_for_toxicology_aggregation">;
45
44
  export {};
@@ -150,7 +150,3 @@ export function classifyClinicalEvidenceTrial(trial, ctx) {
150
150
  }
151
151
  return excluded("unrelated", "No intervention or mechanism evidence links this trial to the assessed target.", "text_match", "ctgov", details.map((i) => toResolvedIntervention(i, "ctgov")));
152
152
  }
153
- export function attachClinicalEvidenceAttribution(trial, ctx) {
154
- const result = classifyClinicalEvidenceTrial(trial, ctx);
155
- return { ...trial, ...result };
156
- }
@@ -1,4 +1,5 @@
1
- import type { DerivedV4 } from "@inflexa-ai/harness/contracts/target-dossier.js";
1
+ import type { Derived } from "@inflexa-ai/harness/contracts/target-dossier.js";
2
+ import { type OrganSystem } from "../../../contracts/organ-system.js";
2
3
  /**
3
4
  * Derive the set of organs that should appear in the organ rollup based on
4
5
  * available safety signals (FAERS top signals, serious trial AEs, and
@@ -8,12 +9,12 @@ import type { DerivedV4 } from "@inflexa-ai/harness/contracts/target-dossier.js"
8
9
  * organ_rollup rows — ensuring the set of "expected" organs is identical at
9
10
  * assembly time and at derived-validation time.
10
11
  */
11
- export declare function expectedOrgansFromBody(bodyInput: unknown): string[];
12
+ export declare function expectedOrgansFromBody(bodyInput: unknown): OrganSystem[];
12
13
  /**
13
- * Build the v4 derived sub-tree from a raw dossier body.
14
+ * Build the derived sub-tree from a raw dossier body.
14
15
  *
15
16
  * Pure function: no I/O, no LLM calls, no side effects. Defensive against
16
17
  * missing/partial bodies — always returns a best-effort result and never throws.
17
- * Schema validation happens at the persist boundary (Task 11), not here.
18
+ * Schema validation happens at the persist boundary, not here.
18
19
  */
19
- export declare function computeDerivedFields(bodyInput: unknown): DerivedV4;
20
+ export declare function computeDerivedFields(bodyInput: unknown): Derived;
@@ -1,6 +1,15 @@
1
+ import { ORGAN_SYSTEMS } from "../../../contracts/organ-system.js";
1
2
  import { classifyOrgan, classifyTrialAe } from "./meddra-organ-map.js";
2
3
  import { HIGH_EXPRESSION_TPM_THRESHOLD } from "./expression-constants.js";
3
4
  const FAERS_TOP_SIGNAL_THRESHOLD = 1000;
5
+ const ORGAN_SYSTEM_SET = new Set(ORGAN_SYSTEMS);
6
+ /**
7
+ * The body arrives here unvalidated, so an organ token is checked against the
8
+ * vocabulary before it is carried into the derived sub-tree.
9
+ */
10
+ function isOrganSystem(value) {
11
+ return typeof value === "string" && ORGAN_SYSTEM_SET.has(value);
12
+ }
4
13
  /**
5
14
  * Derive the set of organs that should appear in the organ rollup based on
6
15
  * available safety signals (FAERS top signals, serious trial AEs, and
@@ -29,7 +38,7 @@ export function expectedOrgansFromBody(bodyInput) {
29
38
  }
30
39
  const offTargets = body?.safety_profile?.off_target_panel?.data?.rows ?? [];
31
40
  for (const r of offTargets) {
32
- if (r.organ_system && typeof r.organ_system === "string")
41
+ if (r.organ_system && isOrganSystem(r.organ_system))
33
42
  expected.add(r.organ_system);
34
43
  }
35
44
  return [...expected].sort();
@@ -38,7 +47,7 @@ function presentOrgans(body) {
38
47
  const rows = body?.safety_profile?.organ_rollup?.data?.rows ?? [];
39
48
  return rows
40
49
  .map((r) => r.organ)
41
- .filter((o) => typeof o === "string")
50
+ .filter(isOrganSystem)
42
51
  .sort();
43
52
  }
44
53
  function countDistinctPapers(body) {
@@ -51,7 +60,7 @@ function countDistinctPapers(body) {
51
60
  }
52
61
  function countDistinctTrials(body) {
53
62
  const set = new Set();
54
- // V5 partitions ineligible trials into `excluded_rows`, so the distinct
63
+ // Ineligible trials are partitioned into `excluded_rows`, so the distinct
55
64
  // count must union both buckets for trials, failed_trials, and
56
65
  // discovery_trials. Outcomes preserves every row in `rows` and has no
57
66
  // `excluded_rows` bucket.
@@ -189,11 +198,11 @@ function countHighExpressionTissues(body) {
189
198
  return set.size;
190
199
  }
191
200
  /**
192
- * Build the v4 derived sub-tree from a raw dossier body.
201
+ * Build the derived sub-tree from a raw dossier body.
193
202
  *
194
203
  * Pure function: no I/O, no LLM calls, no side effects. Defensive against
195
204
  * missing/partial bodies — always returns a best-effort result and never throws.
196
- * Schema validation happens at the persist boundary (Task 11), not here.
205
+ * Schema validation happens at the persist boundary, not here.
197
206
  */
198
207
  export function computeDerivedFields(bodyInput) {
199
208
  const body = bodyInput;
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Build the obligate-cofactor supplement rows shown in
3
- * OffTargetPanelV4.excluded_rows from the IUPHAR family-complexes collector
4
- * bundle. Replaces the hand-curated calcitonin-only supplement that
5
- * previously lived next to it.
3
+ * OffTargetPanel.excluded_rows from the IUPHAR family-complexes collector
4
+ * bundle.
6
5
  *
7
6
  * pchembl is set to 0 because IUPHAR exposes complex structure, not
8
7
  * ligand affinity for the complex; the row is marked selectivity_unknown
@@ -11,11 +10,11 @@
11
10
  * clinical_consequence annotator populates `clinical_consequence` once
12
11
  * the per-row annotation step runs.
13
12
  */
14
- import type { ExcludedOffTargetRowV4Schema } from "@inflexa-ai/harness/contracts/target-dossier.js";
13
+ import type { ExcludedOffTargetRowSchema } from "@inflexa-ai/harness/contracts/target-dossier.js";
15
14
  import type { z } from "zod";
16
15
  import type { FamilyComplexesBundle } from "../schemas.js";
17
- type ExcludedOffTargetRowV4 = z.infer<typeof ExcludedOffTargetRowV4Schema>;
18
- export declare function buildFamilyComplexSupplement(bundle: FamilyComplexesBundle): (Omit<ExcludedOffTargetRowV4, "evidence"> & {
16
+ type ExcludedOffTargetRow = z.infer<typeof ExcludedOffTargetRowSchema>;
17
+ export declare function buildFamilyComplexSupplement(bundle: FamilyComplexesBundle): (Omit<ExcludedOffTargetRow, "evidence"> & {
19
18
  evidence: [];
20
19
  })[];
21
20
  export {};
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Build the obligate-cofactor supplement rows shown in
3
- * OffTargetPanelV4.excluded_rows from the IUPHAR family-complexes collector
4
- * bundle. Replaces the hand-curated calcitonin-only supplement that
5
- * previously lived next to it.
3
+ * OffTargetPanel.excluded_rows from the IUPHAR family-complexes collector
4
+ * bundle.
6
5
  *
7
6
  * pchembl is set to 0 because IUPHAR exposes complex structure, not
8
7
  * ligand affinity for the complex; the row is marked selectivity_unknown
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The typed form of an FDA Structured Product Label's safety prose, and the
3
+ * per-organ projection over it.
4
+ *
5
+ * A label arrives as prose in a handful of named sections. Ingesting it once
6
+ * into this shape is what lets the same warning reach two places without being
7
+ * fetched twice: the synthesis markdown renders the sections, and the dossier's
8
+ * per-organ regulatory signals are segmented out of them. Everything a reader
9
+ * needs to find a signal again — the application number and the label section —
10
+ * rides on the signal, so it satisfies the claim-evidence locator requirement.
11
+ */
12
+ import type { OrganSystem } from "../../../contracts/organ-system.js";
13
+ import type { EvidenceItem } from "../../../contracts/target-dossier.js";
14
+ /** The label sections whose prose carries a safety finding. */
15
+ export declare const LABEL_SAFETY_SECTIONS: readonly ["boxed_warning", "warnings_and_precautions", "warnings", "contraindications"];
16
+ export type LabelSafetySection = (typeof LABEL_SAFETY_SECTIONS)[number];
17
+ /** One block of prose, keyed to the label section it was published under. */
18
+ export interface LabelSafetyText {
19
+ readonly section: LabelSafetySection;
20
+ readonly text: string;
21
+ }
22
+ /**
23
+ * One label's identity plus its safety prose.
24
+ *
25
+ * `application_number` is required rather than nullable because it is half of
26
+ * what makes a signal resolvable; a producer holding a label without one has
27
+ * nothing to cite and does not construct this shape.
28
+ */
29
+ export interface FdaLabelSafety {
30
+ readonly application_number: string;
31
+ readonly drug_name: string;
32
+ readonly effective_time?: string;
33
+ readonly source_url?: string;
34
+ readonly sections: readonly LabelSafetyText[];
35
+ }
36
+ /** A warning fragment attributed to one canonical organ, with its locator. */
37
+ export interface RegulatoryOrganSignal {
38
+ readonly organ: OrganSystem;
39
+ readonly drug_name: string;
40
+ readonly application_number: string;
41
+ /** The label section the prose was published under. */
42
+ readonly source_section: LabelSafetySection;
43
+ /** What a reader opens to find the excerpt: a numbered marker when the prose carried one. */
44
+ readonly label_section: string;
45
+ readonly excerpt: string;
46
+ readonly evidence: EvidenceItem;
47
+ }
48
+ export interface OrganSignalProjection {
49
+ readonly signals: readonly RegulatoryOrganSignal[];
50
+ /** Fragments whose organ did not resolve and were therefore not emitted. */
51
+ readonly dropped_count: number;
52
+ }
53
+ /** What the section reports as having emptied it when nothing resolved. */
54
+ export declare const ORGAN_RESOLUTION_FILTER = "label warning fragments with no resolvable organ";
55
+ interface WarningFragment {
56
+ readonly marker: string | null;
57
+ readonly text: string;
58
+ }
59
+ /**
60
+ * Split one section's prose into the fragments an organ can be attributed to:
61
+ * numbered label-section markers first, then bullets inside each marked block.
62
+ */
63
+ export declare function splitWarningProse(text: string): WarningFragment[];
64
+ /**
65
+ * Project a set of ingested labels onto per-organ regulatory signals.
66
+ *
67
+ * A fragment that resolves to no canonical organ is dropped and counted rather
68
+ * than filed under a neighbouring organ. A fragment the highlights section
69
+ * repeats from the full section is emitted once.
70
+ */
71
+ export declare function segmentLabelSafety(labels: readonly FdaLabelSafety[]): OrganSignalProjection;
72
+ export {};
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The typed form of an FDA Structured Product Label's safety prose, and the
3
+ * per-organ projection over it.
4
+ *
5
+ * A label arrives as prose in a handful of named sections. Ingesting it once
6
+ * into this shape is what lets the same warning reach two places without being
7
+ * fetched twice: the synthesis markdown renders the sections, and the dossier's
8
+ * per-organ regulatory signals are segmented out of them. Everything a reader
9
+ * needs to find a signal again — the application number and the label section —
10
+ * rides on the signal, so it satisfies the claim-evidence locator requirement.
11
+ */
12
+ import { classifyOrgan } from "./meddra-organ-map.js";
13
+ /** The label sections whose prose carries a safety finding. */
14
+ export const LABEL_SAFETY_SECTIONS = ["boxed_warning", "warnings_and_precautions", "warnings", "contraindications"];
15
+ /** What the section reports as having emptied it when nothing resolved. */
16
+ export const ORGAN_RESOLUTION_FILTER = "label warning fragments with no resolvable organ";
17
+ /**
18
+ * Numbered label-section markers ("5.1 Hepatotoxicity"). Split as a lookahead
19
+ * so the marker stays with the prose it heads. The trailing uppercase-or-paren
20
+ * requirement is what keeps a dose ("2.5 mg") from reading as a marker.
21
+ */
22
+ const SECTION_MARKER_SPLIT = /(?=\b\d{1,2}\.\d{1,2}\s+[A-Z(])/;
23
+ const SECTION_MARKER_HEAD = /^(\d{1,2}\.\d{1,2})\s+/;
24
+ /**
25
+ * Bullet boundaries. Single newlines are NOT boundaries — labels wrap prose
26
+ * mid-sentence, and splitting on the wrap would sever a finding from the organ
27
+ * named in its own heading.
28
+ */
29
+ const FRAGMENT_SPLIT = /[•·▪‣]+|\r?\n\s*\r?\n+|\r?\n[ \t]*[-*]\s+/;
30
+ /** Below this a fragment is a stray heading remnant, not a finding. */
31
+ const MIN_FRAGMENT_CHARS = 16;
32
+ /** Excerpt budget per signal — enough to read the finding, bounded for storage. */
33
+ const MAX_EXCERPT_CHARS = 400;
34
+ function clipExcerpt(text) {
35
+ if (text.length <= MAX_EXCERPT_CHARS)
36
+ return text;
37
+ return `${text.slice(0, MAX_EXCERPT_CHARS - 1).trimEnd()}…`;
38
+ }
39
+ /**
40
+ * Split one section's prose into the fragments an organ can be attributed to:
41
+ * numbered label-section markers first, then bullets inside each marked block.
42
+ */
43
+ export function splitWarningProse(text) {
44
+ const fragments = [];
45
+ for (const block of text.split(SECTION_MARKER_SPLIT)) {
46
+ const head = SECTION_MARKER_HEAD.exec(block);
47
+ const marker = head?.[1] ?? null;
48
+ const body = head ? block.slice(head[0].length) : block;
49
+ for (const piece of body.split(FRAGMENT_SPLIT)) {
50
+ const collapsed = piece.replace(/\s+/g, " ").trim();
51
+ if (collapsed.length < MIN_FRAGMENT_CHARS)
52
+ continue;
53
+ fragments.push({ marker, text: collapsed });
54
+ }
55
+ }
56
+ return fragments;
57
+ }
58
+ /**
59
+ * Resolve a fragment onto the canonical organ vocabulary at its own boundary.
60
+ * The heading — the prose up to its first colon — is tried first, because a
61
+ * label states the organ there and then discusses it; scanning the whole
62
+ * fragment first would let an incidental mention outrank the heading.
63
+ */
64
+ function resolveFragmentOrgan(fragment) {
65
+ const colon = fragment.indexOf(":");
66
+ const heading = colon > 0 ? fragment.slice(0, colon) : "";
67
+ return (heading ? classifyOrgan(heading) : null) ?? classifyOrgan(fragment);
68
+ }
69
+ function buildEvidence(label, labelSection, excerpt, organ) {
70
+ return {
71
+ source: "openfda:label",
72
+ predicate: "label_warning",
73
+ excerpt,
74
+ regulatory_reference: {
75
+ document: `FDA prescribing information — ${label.drug_name}`,
76
+ section: labelSection,
77
+ doc_id: label.application_number,
78
+ ...(label.source_url ? { doc_url: label.source_url } : {}),
79
+ },
80
+ metadata: {
81
+ organ,
82
+ drug_name: label.drug_name,
83
+ ...(label.effective_time ? { effective_time: label.effective_time } : {}),
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * A short fragment can be a substring of a longer unrelated one, so containment
89
+ * only settles a repeat once the contained text is long enough to be prose
90
+ * rather than a phrase.
91
+ */
92
+ const MIN_CONTAINMENT_CHARS = 60;
93
+ function isRepeatOf(seen, candidate) {
94
+ if (seen === candidate)
95
+ return true;
96
+ if (seen.includes(candidate))
97
+ return candidate.length >= MIN_CONTAINMENT_CHARS;
98
+ if (candidate.includes(seen))
99
+ return seen.length >= MIN_CONTAINMENT_CHARS;
100
+ return false;
101
+ }
102
+ /**
103
+ * Order a label's sections for segmentation: published section order, then
104
+ * longest prose first within a section. openFDA returns the highlights summary
105
+ * and the full section under the same key, so reading the longer one first is
106
+ * what makes the shorter repeat recognisable as a repeat rather than the other
107
+ * way round.
108
+ */
109
+ function segmentationOrder(sections) {
110
+ const firstSeenAt = new Map();
111
+ for (const [index, s] of sections.entries()) {
112
+ if (!firstSeenAt.has(s.section))
113
+ firstSeenAt.set(s.section, index);
114
+ }
115
+ return [...sections].sort((a, b) => firstSeenAt.get(a.section) - firstSeenAt.get(b.section) || b.text.length - a.text.length);
116
+ }
117
+ /**
118
+ * Project a set of ingested labels onto per-organ regulatory signals.
119
+ *
120
+ * A fragment that resolves to no canonical organ is dropped and counted rather
121
+ * than filed under a neighbouring organ. A fragment the highlights section
122
+ * repeats from the full section is emitted once.
123
+ */
124
+ export function segmentLabelSafety(labels) {
125
+ const signals = [];
126
+ let droppedCount = 0;
127
+ for (const label of labels) {
128
+ const seenMarkers = new Set();
129
+ const seenText = [];
130
+ for (const section of segmentationOrder(label.sections)) {
131
+ // A numbered marker settles a repeat only across blocks: one block
132
+ // legitimately carries several fragments under its own `5.1`, while
133
+ // a second block reprising `5.1` is the highlights summary of it.
134
+ const blockMarkers = new Set();
135
+ for (const fragment of splitWarningProse(section.text)) {
136
+ const normalized = fragment.text.toLowerCase();
137
+ const markerKey = fragment.marker ? `${section.section}:${fragment.marker}` : null;
138
+ const repeated = (markerKey !== null && seenMarkers.has(markerKey)) || seenText.some((seen) => isRepeatOf(seen, normalized));
139
+ if (repeated)
140
+ continue;
141
+ if (markerKey !== null)
142
+ blockMarkers.add(markerKey);
143
+ seenText.push(normalized);
144
+ const organ = resolveFragmentOrgan(fragment.text);
145
+ if (organ === null) {
146
+ droppedCount += 1;
147
+ continue;
148
+ }
149
+ const labelSection = fragment.marker ?? section.section;
150
+ const excerpt = clipExcerpt(fragment.text);
151
+ signals.push({
152
+ organ,
153
+ drug_name: label.drug_name,
154
+ application_number: label.application_number,
155
+ source_section: section.section,
156
+ label_section: labelSection,
157
+ excerpt,
158
+ evidence: buildEvidence(label, labelSection, excerpt, organ),
159
+ });
160
+ }
161
+ for (const marker of blockMarkers)
162
+ seenMarkers.add(marker);
163
+ }
164
+ }
165
+ return { signals, dropped_count: droppedCount };
166
+ }
@@ -28,12 +28,3 @@ export interface HeterodimerFilterInput {
28
28
  * "<primary>-<accessory>" (some sources use a hyphen)
29
29
  */
30
30
  export declare function makeHeterodimerOfAssessmentFilter(input: HeterodimerFilterInput): (offTargetName: string) => boolean;
31
- /**
32
- * One-shot helper for callers that already have an accessory list in hand.
33
- * Equivalent to `makeHeterodimerOfAssessmentFilter(input)(offTargetName)`.
34
- */
35
- export declare function isHeterodimerOfAssessment(input: {
36
- assessmentGeneSymbol: string;
37
- accessoryProteinNames: string[];
38
- offTargetName: string;
39
- }): boolean;
@@ -42,13 +42,3 @@ export function makeHeterodimerOfAssessmentFilter(input) {
42
42
  return m[1].toUpperCase() === target;
43
43
  };
44
44
  }
45
- /**
46
- * One-shot helper for callers that already have an accessory list in hand.
47
- * Equivalent to `makeHeterodimerOfAssessmentFilter(input)(offTargetName)`.
48
- */
49
- export function isHeterodimerOfAssessment(input) {
50
- return makeHeterodimerOfAssessmentFilter({
51
- assessmentGeneSymbol: input.assessmentGeneSymbol,
52
- accessoryProteinNames: input.accessoryProteinNames,
53
- })(input.offTargetName);
54
- }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Human Phenotype Ontology terms onto the canonical organ vocabulary.
3
+ *
4
+ * HPO organises every phenotypic abnormality under organ-system roots, and a
5
+ * phenotype association carries its full ancestor closure. Resolution is
6
+ * therefore membership of an ancestor id in this table — equality on
7
+ * identifiers, not matching on prose, so a renamed term keeps resolving and an
8
+ * incidental word in a label never decides an organ.
9
+ *
10
+ * The table is ordered most specific first, because a term sits under several
11
+ * roots at once: a liver phenotype's closure also contains the digestive-system
12
+ * root, and reading the broad root first would file every hepatic finding under
13
+ * `gi`. Roots with no canonical organ to land in (ear, head and neck, growth,
14
+ * prenatal) are absent — a phenotype is reported under the organ it affects or
15
+ * not at all.
16
+ *
17
+ * A root broader than any single token is absent for that same reason, even
18
+ * where a token's name is close to it: the endocrine-system root spans every
19
+ * endocrine organ while the only endocrine token names the thyroid, so an
20
+ * adrenal phenotype resolves through no root and is dropped. The neoplasm root
21
+ * says a phenotype is malignant, not where it sits, so a neoplasm resolves
22
+ * through the organ root it also carries — a lung neoplasm is respiratory, and
23
+ * a neoplasm with no organ root at all resolves to nothing.
24
+ *
25
+ * The values are the canonical tokens, so this module declares no organ
26
+ * enumeration of its own.
27
+ */
28
+ import type { OrganSystem } from "../../../contracts/organ-system.js";
29
+ /**
30
+ * Resolve an HPO term onto a canonical organ from its own id plus its ancestor
31
+ * closure. Returns null when nothing in the ancestry denotes a canonical organ
32
+ * system — the caller drops the phenotype and counts it.
33
+ */
34
+ export declare function resolveHpoOrgan(termId: string, ancestorIds: readonly string[]): OrganSystem | null;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Human Phenotype Ontology terms onto the canonical organ vocabulary.
3
+ *
4
+ * HPO organises every phenotypic abnormality under organ-system roots, and a
5
+ * phenotype association carries its full ancestor closure. Resolution is
6
+ * therefore membership of an ancestor id in this table — equality on
7
+ * identifiers, not matching on prose, so a renamed term keeps resolving and an
8
+ * incidental word in a label never decides an organ.
9
+ *
10
+ * The table is ordered most specific first, because a term sits under several
11
+ * roots at once: a liver phenotype's closure also contains the digestive-system
12
+ * root, and reading the broad root first would file every hepatic finding under
13
+ * `gi`. Roots with no canonical organ to land in (ear, head and neck, growth,
14
+ * prenatal) are absent — a phenotype is reported under the organ it affects or
15
+ * not at all.
16
+ *
17
+ * A root broader than any single token is absent for that same reason, even
18
+ * where a token's name is close to it: the endocrine-system root spans every
19
+ * endocrine organ while the only endocrine token names the thyroid, so an
20
+ * adrenal phenotype resolves through no root and is dropped. The neoplasm root
21
+ * says a phenotype is malignant, not where it sits, so a neoplasm resolves
22
+ * through the organ root it also carries — a lung neoplasm is respiratory, and
23
+ * a neoplasm with no organ root at all resolves to nothing.
24
+ *
25
+ * The values are the canonical tokens, so this module declares no organ
26
+ * enumeration of its own.
27
+ */
28
+ /** HPO ancestor id → canonical organ, most specific ancestor first. */
29
+ const HPO_ORGAN_ANCESTORS = [
30
+ ["HP:0001392", "hepatic"], // Abnormality of the liver
31
+ ["HP:0001733", "pancreas"], // Abnormality of the pancreas
32
+ ["HP:0000820", "endocrine_thyroid"], // Abnormality of the thyroid gland
33
+ ["HP:0000079", "renal"], // Abnormality of the urinary system
34
+ ["HP:0000078", "reproductive"], // Abnormality of the genital system
35
+ ["HP:0002597", "vascular"], // Abnormality of the vasculature
36
+ ["HP:0001627", "cardiac"], // Abnormal heart morphology
37
+ ["HP:0001626", "cardiac"], // Abnormality of the cardiovascular system
38
+ ["HP:0000759", "pns"], // Abnormal peripheral nervous system morphology
39
+ ["HP:0012639", "cns"], // Abnormal nervous system morphology
40
+ ["HP:0000707", "cns"], // Abnormality of the nervous system
41
+ ["HP:0002715", "immune"], // Abnormality of the immune system
42
+ ["HP:0001871", "hematologic"], // Abnormality of blood and blood-forming tissues
43
+ ["HP:0001939", "metabolic"], // Abnormality of metabolism/homeostasis
44
+ ["HP:0002086", "respiratory"], // Abnormality of the respiratory system
45
+ ["HP:0025031", "gi"], // Abnormality of the digestive system
46
+ ["HP:0000478", "ocular"], // Abnormality of the eye
47
+ ["HP:0001574", "dermatologic"], // Abnormality of the integument
48
+ ["HP:0003011", "musculoskeletal"], // Abnormality of the musculature
49
+ ["HP:0000924", "musculoskeletal"], // Abnormality of the skeletal system
50
+ ];
51
+ /**
52
+ * Resolve an HPO term onto a canonical organ from its own id plus its ancestor
53
+ * closure. Returns null when nothing in the ancestry denotes a canonical organ
54
+ * system — the caller drops the phenotype and counts it.
55
+ */
56
+ export function resolveHpoOrgan(termId, ancestorIds) {
57
+ const ancestry = new Set([termId, ...ancestorIds]);
58
+ for (const [id, organ] of HPO_ORGAN_ANCESTORS) {
59
+ if (ancestry.has(id))
60
+ return organ;
61
+ }
62
+ return null;
63
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The IMPC top-level-phenotype vocabulary onto the canonical organ vocabulary.
3
+ *
4
+ * Buckets with no organ system to land in (hearing, growth, mortality,
5
+ * craniofacial) are absent: a phenotype is reported under the organ it affects
6
+ * or not at all, never under an approximate neighbour. The map's values are the
7
+ * canonical tokens, so it declares no enumeration of its own.
8
+ *
9
+ * The endocrine bucket is absent for the same reason: IMPC's
10
+ * "endocrine/exocrine gland phenotype" spans every endocrine organ and the
11
+ * exocrine glands besides, while the only endocrine token names the thyroid.
12
+ * A knockout in that bucket names no canonical organ, so it is dropped and
13
+ * counted rather than read as a thyroid finding.
14
+ */
15
+ import type { OrganSystem } from "../../../contracts/organ-system.js";
16
+ /** Resolve one IMPC bucket, or null when it denotes no canonical organ. */
17
+ export declare function resolveImpcOrgan(bucket: string): OrganSystem | null;
18
+ /** Resolve a set of IMPC buckets, dropping those with no canonical organ. */
19
+ export declare function toOrganSystems(impcOrganSystems: readonly string[]): OrganSystem[];
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The IMPC top-level-phenotype vocabulary onto the canonical organ vocabulary.
3
+ *
4
+ * Buckets with no organ system to land in (hearing, growth, mortality,
5
+ * craniofacial) are absent: a phenotype is reported under the organ it affects
6
+ * or not at all, never under an approximate neighbour. The map's values are the
7
+ * canonical tokens, so it declares no enumeration of its own.
8
+ *
9
+ * The endocrine bucket is absent for the same reason: IMPC's
10
+ * "endocrine/exocrine gland phenotype" spans every endocrine organ and the
11
+ * exocrine glands besides, while the only endocrine token names the thyroid.
12
+ * A knockout in that bucket names no canonical organ, so it is dropped and
13
+ * counted rather than read as a thyroid finding.
14
+ */
15
+ const IMPC_ORGAN_SYSTEMS = {
16
+ cardiovascular: "cardiac",
17
+ gastrointestinal: "gi",
18
+ hematologic: "hematologic",
19
+ metabolic: "metabolic",
20
+ immune: "immune",
21
+ skin: "dermatologic",
22
+ hepatic: "hepatic",
23
+ musculoskeletal: "musculoskeletal",
24
+ skeleton: "musculoskeletal",
25
+ cns: "cns",
26
+ renal: "renal",
27
+ reproductive: "reproductive",
28
+ respiratory: "respiratory",
29
+ vision: "ocular",
30
+ };
31
+ /** Resolve one IMPC bucket, or null when it denotes no canonical organ. */
32
+ export function resolveImpcOrgan(bucket) {
33
+ return IMPC_ORGAN_SYSTEMS[bucket] ?? null;
34
+ }
35
+ /** Resolve a set of IMPC buckets, dropping those with no canonical organ. */
36
+ export function toOrganSystems(impcOrganSystems) {
37
+ const resolved = impcOrganSystems.map((o) => resolveImpcOrgan(o)).filter((o) => o !== null);
38
+ return [...new Set(resolved)];
39
+ }
@@ -1,13 +1,33 @@
1
- export type CanonicalOrgan = "cardiac" | "hepatic" | "cns" | "renal" | "gi" | "pancreas" | "endocrine_thyroid" | "metabolic" | "hematologic" | "immune" | "respiratory" | "reproductive" | "dermatologic" | "musculoskeletal" | "oncology";
2
- export declare function classifyOrgan(meddraTerm: string): CanonicalOrgan | null;
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
+ */
16
+ import type { OrganSystem } from "../../../contracts/organ-system.js";
17
+ export declare function classifyOrgan(meddraTerm: string): OrganSystem | null;
3
18
  /**
4
19
  * MedDRA SOC (System Organ Class) names as they appear in CT.gov structured
5
20
  * AE tables → canonical organ. SOC strings (e.g. "Gastrointestinal disorders")
6
21
  * do not contain MedDRA PT stems, so `classifyOrgan` returns null for them —
7
22
  * the SOC map is the primary path; `classifyOrgan(term)` is the fallback when
8
23
  * the SOC string isn't in the map.
24
+ *
25
+ * A SOC that spans more than one canonical organ is absent: "Nervous system
26
+ * disorders" covers both nervous-system tokens and "Endocrine disorders" is
27
+ * broader than the thyroid, so a row under either resolves by its preferred
28
+ * term or not at all.
9
29
  */
10
- export declare const TRIAL_AE_ORGAN_MAP: Record<string, CanonicalOrgan>;
30
+ export declare const TRIAL_AE_ORGAN_MAP: Record<string, OrganSystem>;
11
31
  /**
12
32
  * Classify a trial AE row by its SOC (preferred) or its individual MedDRA
13
33
  * preferred term (fallback). Shared between the Phase-4 assembler's
@@ -17,14 +37,4 @@ export declare const TRIAL_AE_ORGAN_MAP: Record<string, CanonicalOrgan>;
17
37
  export declare function classifyTrialAe(ae: {
18
38
  term?: string | null;
19
39
  organ?: string | null;
20
- }): CanonicalOrgan | null;
21
- /**
22
- * Classify a polypharm off-target panel row by its canonical `organ_system`
23
- * string (set by the curated safety panel — `cardiac` | `hepatic` | `cns` |
24
- * `renal` | `gi` | `hematologic` | `immune` | `metabolic` | `respiratory`).
25
- *
26
- * Falls back to `classifyOrgan` if the upstream string is non-canonical;
27
- * this preserves coverage if a future collector emits MedDRA terms instead
28
- * of canonical organ names.
29
- */
30
- export declare function classifyPolypharmOrgan(organSystem: string | null | undefined): CanonicalOrgan | null;
40
+ }): OrganSystem | null;