@inflexa-ai/harness 0.35.7 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/dist/auth/types.d.ts +5 -14
  2. package/dist/auth/types.js +3 -10
  3. package/dist/billing/usage-recorder.d.ts +1 -1
  4. package/dist/contracts/analogy-report.d.ts +1 -1
  5. package/dist/contracts/analogy-report.js +1 -1
  6. package/dist/contracts/chat-parts.d.ts +2 -3
  7. package/dist/contracts/index.d.ts +0 -4
  8. package/dist/contracts/index.js +0 -2
  9. package/dist/index.d.ts +0 -7
  10. package/dist/index.js +0 -21
  11. package/dist/lib/metrics.d.ts +1 -1
  12. package/dist/lib/otel-spans.d.ts +0 -2
  13. package/dist/lib/otel-spans.js +0 -2
  14. package/dist/loop/genai-spans.d.ts +20 -0
  15. package/dist/loop/genai-spans.js +53 -0
  16. package/dist/loop/run-agent.d.ts +1 -1
  17. package/dist/loop/run-agent.js +9 -4
  18. package/dist/providers/ai-sdk.js +8 -0
  19. package/dist/runtime/assemble.d.ts +0 -3
  20. package/dist/runtime/assemble.js +0 -3
  21. package/dist/state/index.d.ts +0 -2
  22. package/dist/state/index.js +0 -1
  23. package/dist/state/init.js +1 -60
  24. package/dist/state/purge-analysis.d.ts +1 -2
  25. package/dist/state/purge-analysis.js +1 -2
  26. package/dist/tasks/regulatory-corpus-boot-refresh.js +1 -1
  27. package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -3
  28. package/dist/tools/bio/gene-preclinical-profile.js +1 -3
  29. package/dist/tools/lib/chembl-client.d.ts +1 -4
  30. package/dist/tools/lib/chembl-client.js +1 -4
  31. package/dist/tools/lib/clinical-benchmarks-client.d.ts +1 -1
  32. package/dist/tools/lib/clinical-benchmarks-client.js +1 -1
  33. package/dist/tools/lib/clinical-trials-client.d.ts +0 -3
  34. package/dist/tools/lib/clinical-trials-client.js +0 -3
  35. package/dist/tools/lib/identifier-resolver.d.ts +5 -7
  36. package/dist/tools/lib/identifier-resolver.js +5 -7
  37. package/dist/tools/lib/iuphar-client.d.ts +1 -3
  38. package/dist/tools/lib/iuphar-client.js +1 -3
  39. package/dist/tools/lib/opentargets-client.d.ts +1 -1
  40. package/dist/tools/lib/opentargets-client.js +1 -1
  41. package/dist/tools/lib/protein-family-normalize.d.ts +5 -7
  42. package/dist/tools/lib/protein-family-normalize.js +5 -7
  43. package/dist/tools/lib/uniprot-client.d.ts +0 -7
  44. package/dist/tools/lib/uniprot-client.js +2 -9
  45. package/package.json +9 -8
  46. package/dist/contracts/target-assessment-row.d.ts +0 -85
  47. package/dist/contracts/target-assessment-row.js +0 -37
  48. package/dist/contracts/target-dossier.d.ts +0 -12423
  49. package/dist/contracts/target-dossier.js +0 -1282
  50. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +0 -1
  51. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +0 -85
  52. package/dist/prompts/target-assessment/briefs/index.d.ts +0 -4
  53. package/dist/prompts/target-assessment/briefs/index.js +0 -4
  54. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +0 -1
  55. package/dist/prompts/target-assessment/briefs/liability-bullets.js +0 -114
  56. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +0 -1
  57. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +0 -37
  58. package/dist/prompts/target-assessment/briefs/translational-commentary.d.ts +0 -1
  59. package/dist/prompts/target-assessment/briefs/translational-commentary.js +0 -23
  60. package/dist/prompts/target-assessment/drugs-in-class.d.ts +0 -1
  61. package/dist/prompts/target-assessment/drugs-in-class.js +0 -29
  62. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +0 -1
  63. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +0 -67
  64. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +0 -1
  65. package/dist/prompts/target-assessment/investigation/claim-reverification.js +0 -49
  66. package/dist/prompts/target-assessment/investigation/index.d.ts +0 -3
  67. package/dist/prompts/target-assessment/investigation/index.js +0 -3
  68. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +0 -1
  69. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +0 -49
  70. package/dist/prompts/target-assessment/modulator-triage.d.ts +0 -1
  71. package/dist/prompts/target-assessment/modulator-triage.js +0 -31
  72. package/dist/prompts/target-assessment/off-target-annotator.d.ts +0 -1
  73. package/dist/prompts/target-assessment/off-target-annotator.js +0 -28
  74. package/dist/prompts/target-assessment/tox-voice/core.d.ts +0 -1
  75. package/dist/prompts/target-assessment/tox-voice/core.js +0 -74
  76. package/dist/prompts/target-assessment/tox-voice/critique.d.ts +0 -7
  77. package/dist/prompts/target-assessment/tox-voice/critique.js +0 -109
  78. package/dist/prompts/target-assessment/tox-voice/exemplars.d.ts +0 -8
  79. package/dist/prompts/target-assessment/tox-voice/exemplars.js +0 -24
  80. package/dist/prompts/target-assessment/tox-voice/index.d.ts +0 -4
  81. package/dist/prompts/target-assessment/tox-voice/index.js +0 -4
  82. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +0 -18
  83. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +0 -61
  84. package/dist/state/target-assessments.d.ts +0 -68
  85. package/dist/state/target-assessments.js +0 -222
  86. package/dist/workflows/execute-target-assessment.d.ts +0 -118
  87. package/dist/workflows/execute-target-assessment.js +0 -631
  88. package/dist/workflows/target-assessment/assemblers/index.d.ts +0 -5
  89. package/dist/workflows/target-assessment/assemblers/index.js +0 -4
  90. package/dist/workflows/target-assessment/assemblers/literature.d.ts +0 -281
  91. package/dist/workflows/target-assessment/assemblers/literature.js +0 -971
  92. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +0 -118
  93. package/dist/workflows/target-assessment/assemblers/orchestrator.js +0 -945
  94. package/dist/workflows/target-assessment/assemblers/safety.d.ts +0 -374
  95. package/dist/workflows/target-assessment/assemblers/safety.js +0 -1068
  96. package/dist/workflows/target-assessment/assemblers/trials.d.ts +0 -30
  97. package/dist/workflows/target-assessment/assemblers/trials.js +0 -75
  98. package/dist/workflows/target-assessment/collectors/index.d.ts +0 -114
  99. package/dist/workflows/target-assessment/collectors/index.js +0 -538
  100. package/dist/workflows/target-assessment/coverage.d.ts +0 -97
  101. package/dist/workflows/target-assessment/coverage.js +0 -86
  102. package/dist/workflows/target-assessment/decisions/index.d.ts +0 -86
  103. package/dist/workflows/target-assessment/decisions/index.js +0 -127
  104. package/dist/workflows/target-assessment/fanout/index.d.ts +0 -32
  105. package/dist/workflows/target-assessment/fanout/index.js +0 -156
  106. package/dist/workflows/target-assessment/investigation/index.d.ts +0 -97
  107. package/dist/workflows/target-assessment/investigation/index.js +0 -419
  108. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +0 -73
  109. package/dist/workflows/target-assessment/lib/approval-precedents.js +0 -185
  110. package/dist/workflows/target-assessment/lib/citation-validator.d.ts +0 -46
  111. package/dist/workflows/target-assessment/lib/citation-validator.js +0 -316
  112. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +0 -65
  113. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +0 -174
  114. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +0 -44
  115. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -152
  116. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +0 -20
  117. package/dist/workflows/target-assessment/lib/compute-derived.js +0 -261
  118. package/dist/workflows/target-assessment/lib/compute-selectivity.d.ts +0 -15
  119. package/dist/workflows/target-assessment/lib/compute-selectivity.js +0 -18
  120. package/dist/workflows/target-assessment/lib/dedup-modulators.d.ts +0 -37
  121. package/dist/workflows/target-assessment/lib/dedup-modulators.js +0 -157
  122. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.d.ts +0 -17
  123. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.js +0 -100
  124. package/dist/workflows/target-assessment/lib/expression-constants.d.ts +0 -21
  125. package/dist/workflows/target-assessment/lib/expression-constants.js +0 -21
  126. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +0 -20
  127. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +0 -36
  128. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +0 -72
  129. package/dist/workflows/target-assessment/lib/fda-label-safety.js +0 -166
  130. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -30
  131. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -44
  132. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +0 -34
  133. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +0 -63
  134. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +0 -19
  135. package/dist/workflows/target-assessment/lib/impc-organ-map.js +0 -39
  136. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.d.ts +0 -4
  137. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.js +0 -24
  138. package/dist/workflows/target-assessment/lib/llm-step.d.ts +0 -80
  139. package/dist/workflows/target-assessment/lib/llm-step.js +0 -101
  140. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +0 -40
  141. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +0 -138
  142. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.d.ts +0 -17
  143. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.js +0 -99
  144. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.d.ts +0 -2
  145. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +0 -51
  146. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +0 -25
  147. package/dist/workflows/target-assessment/lib/regulatory-actions.js +0 -176
  148. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +0 -44
  149. package/dist/workflows/target-assessment/lib/safety-corroboration.js +0 -273
  150. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +0 -61
  151. package/dist/workflows/target-assessment/lib/structured-llm.js +0 -102
  152. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +0 -59
  153. package/dist/workflows/target-assessment/lib/target-identity-filter.js +0 -95
  154. package/dist/workflows/target-assessment/lib/therapeutic-programs.d.ts +0 -20
  155. package/dist/workflows/target-assessment/lib/therapeutic-programs.js +0 -68
  156. package/dist/workflows/target-assessment/metrics.d.ts +0 -16
  157. package/dist/workflows/target-assessment/metrics.js +0 -30
  158. package/dist/workflows/target-assessment/phase4-assemble.d.ts +0 -3182
  159. package/dist/workflows/target-assessment/phase4-assemble.js +0 -39
  160. package/dist/workflows/target-assessment/phase5-persist.d.ts +0 -59
  161. package/dist/workflows/target-assessment/phase5-persist.js +0 -349
  162. package/dist/workflows/target-assessment/progress-stream.d.ts +0 -75
  163. package/dist/workflows/target-assessment/progress-stream.js +0 -110
  164. package/dist/workflows/target-assessment/progress.d.ts +0 -39
  165. package/dist/workflows/target-assessment/progress.js +0 -77
  166. package/dist/workflows/target-assessment/schemas.d.ts +0 -970
  167. package/dist/workflows/target-assessment/schemas.js +0 -318
  168. package/dist/workflows/target-assessment/steps/collectors/pathways.d.ts +0 -15
  169. package/dist/workflows/target-assessment/steps/collectors/pathways.js +0 -58
  170. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.d.ts +0 -34
  171. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.js +0 -31
  172. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.d.ts +0 -57
  173. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.js +0 -54
  174. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.d.ts +0 -35
  175. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.js +0 -32
  176. package/dist/workflows/target-assessment/steps/fanout/index.d.ts +0 -14
  177. package/dist/workflows/target-assessment/steps/fanout/index.js +0 -10
  178. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +0 -29
  179. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +0 -29
  180. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +0 -657
  181. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -34
  182. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +0 -806
  183. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -16
  184. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -28
  185. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -27
  186. package/dist/workflows/target-assessment/sub-workflows/index.d.ts +0 -15
  187. package/dist/workflows/target-assessment/sub-workflows/index.js +0 -11
  188. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.d.ts +0 -41
  189. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.js +0 -12
  190. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.d.ts +0 -41
  191. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.js +0 -12
  192. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +0 -37
  193. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.js +0 -12
  194. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.d.ts +0 -66
  195. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.js +0 -12
  196. package/dist/workflows/target-assessment/synthesis/index.d.ts +0 -607
  197. package/dist/workflows/target-assessment/synthesis/index.js +0 -354
  198. package/dist/workflows/target-assessment/synthesis-probes.d.ts +0 -56
  199. package/dist/workflows/target-assessment/synthesis-probes.js +0 -320
@@ -1,152 +0,0 @@
1
- function normalize(text) {
2
- return text
3
- .toUpperCase()
4
- .replace(/[^A-Z0-9]+/g, " ")
5
- .trim();
6
- }
7
- function tokenMatch(haystack, needle) {
8
- const h = ` ${normalize(haystack)} `;
9
- const n = normalize(needle);
10
- if (!n)
11
- return false;
12
- return h.includes(` ${n} `);
13
- }
14
- function trialText(trial) {
15
- return [
16
- trial.title,
17
- trial.officialTitle,
18
- ...(trial.conditions ?? []),
19
- ...(trial.interventions ?? []),
20
- ...(trial.interventionDetails ?? []).flatMap((i) => [i.name, i.description, ...(i.otherNames ?? [])]),
21
- trial.briefSummary,
22
- trial.detailedDescription,
23
- ]
24
- .filter((v) => typeof v === "string" && v.length > 0)
25
- .join(" ");
26
- }
27
- function interventionDetails(trial) {
28
- if (trial.interventionDetails && trial.interventionDetails.length > 0) {
29
- return trial.interventionDetails;
30
- }
31
- return (trial.interventions ?? []).map((name) => ({
32
- name,
33
- type: null,
34
- description: null,
35
- otherNames: [],
36
- }));
37
- }
38
- function toResolvedIntervention(intervention, source, extras = {}) {
39
- return {
40
- name: intervention.name,
41
- intervention_type: intervention.type ?? null,
42
- target_uniprots: extras.target_uniprots ?? [],
43
- resolver_source: source,
44
- ...extras,
45
- };
46
- }
47
- function isDiagnosticOrBiomarkerOnly(trial) {
48
- const details = interventionDetails(trial);
49
- const hasOnlyDiagnostic = details.length > 0 && details.every((i) => ["DIAGNOSTIC_TEST", "OTHER", "PROCEDURE"].includes(String(i.type ?? "").toUpperCase()));
50
- const design = `${trial.studyType ?? ""} ${trial.primaryPurpose ?? ""}`.toUpperCase();
51
- return hasOnlyDiagnostic || /\bOBSERVATIONAL\b|\bBASIC_SCIENCE\b/.test(design);
52
- }
53
- function targetBiomarkerMention(trial, symbol) {
54
- const text = normalize(trialText(trial));
55
- const sym = normalize(symbol);
56
- if (!sym)
57
- return false;
58
- const wrapped = ` ${text} `;
59
- return wrapped.includes(` ${sym} `) || wrapped.includes(`S ${sym} `) || wrapped.includes(` ${sym} LEVEL`) || wrapped.includes(` ${sym} CONCENTRATION`);
60
- }
61
- function excluded(relationship, reason, basisKind, source, resolved_interventions) {
62
- return {
63
- eligible_for_toxicology_aggregation: false,
64
- attribution: {
65
- relationship,
66
- evidence_role: relationship === "target_biomarker" || relationship === "pathway_biomarker" ? "contextual" : "excluded",
67
- basis: [{ kind: basisKind, source }],
68
- resolved_interventions,
69
- exclusion_reason: reason,
70
- },
71
- };
72
- }
73
- export function classifyClinicalEvidenceTrial(trial, ctx) {
74
- const details = interventionDetails(trial);
75
- for (const program of ctx.therapeuticPrograms) {
76
- const matchesProgram = (program.nctIds ?? []).includes(trial.nctId) ||
77
- details.some((i) => tokenMatch(i.name, program.name) || (i.otherNames ?? []).some((alias) => tokenMatch(alias, program.name))) ||
78
- tokenMatch(trial.title, program.name);
79
- const hitsTarget = normalize(program.targetSymbol) === normalize(ctx.assessmentSymbol) || program.targetUniprot === ctx.assessmentUniprot;
80
- if (matchesProgram && hitsTarget) {
81
- const matched = details.find((i) => tokenMatch(i.name, program.name)) ??
82
- details[0] ?? {
83
- name: program.name,
84
- type: null,
85
- description: null,
86
- otherNames: [],
87
- };
88
- return {
89
- eligible_for_toxicology_aggregation: true,
90
- attribution: {
91
- relationship: "direct_modulator",
92
- evidence_role: "supports_target",
93
- basis: [
94
- {
95
- kind: "therapeutic_program_match",
96
- source: "therapeutic_programs",
97
- excerpt: `${program.name} targets ${program.targetSymbol}`,
98
- },
99
- ],
100
- resolved_interventions: [
101
- toResolvedIntervention(matched, "therapeutic_programs", {
102
- therapeutic_program_id: program.programId,
103
- target_uniprots: program.targetUniprot ? [program.targetUniprot] : [],
104
- }),
105
- ],
106
- },
107
- };
108
- }
109
- }
110
- for (const intervention of details) {
111
- const candidates = [intervention.name, ...(intervention.otherNames ?? [])];
112
- const matchedDrug = ctx.knownClassDrugs.find((drug) => candidates.some((candidate) => tokenMatch(candidate, drug.name)));
113
- if (!matchedDrug)
114
- continue;
115
- const targetUniprots = matchedDrug.targetUniprots ?? [];
116
- if (targetUniprots.includes(ctx.assessmentUniprot)) {
117
- return {
118
- eligible_for_toxicology_aggregation: true,
119
- attribution: {
120
- relationship: "class_modulator",
121
- evidence_role: "supports_target",
122
- basis: [{ kind: "known_class_drug", source: "chembl" }],
123
- resolved_interventions: [
124
- toResolvedIntervention(intervention, "chembl", {
125
- chembl_id: matchedDrug.moleculeChemblId ?? null,
126
- target_uniprots: targetUniprots,
127
- }),
128
- ],
129
- },
130
- };
131
- }
132
- if (targetUniprots.some((u) => ctx.familyUniprots.includes(u))) {
133
- return excluded("related_family_target", "Intervention targets a related family receptor, not the assessed target.", "related_family_target", "chembl", [
134
- toResolvedIntervention(intervention, "chembl", {
135
- chembl_id: matchedDrug.moleculeChemblId ?? null,
136
- target_uniprots: targetUniprots,
137
- }),
138
- ]);
139
- }
140
- }
141
- if (targetBiomarkerMention(trial, ctx.assessmentSymbol)) {
142
- const resolved = details.map((i) => toResolvedIntervention(i, "ctgov"));
143
- return excluded("target_biomarker", isDiagnosticOrBiomarkerOnly(trial)
144
- ? "Target is used as a biomarker or diagnostic endpoint, not as a therapeutic intervention."
145
- : "Target is mentioned in biomarker/outcome context without target-modulating intervention evidence.", "biomarker_endpoint", "ctgov", resolved);
146
- }
147
- const conditionOnly = (trial.conditions ?? []).some((c) => tokenMatch(c, ctx.assessmentSymbol));
148
- if (conditionOnly) {
149
- return excluded("condition_only", "Trial matched only by condition text and has no target-modulating intervention evidence.", "condition_only", "ctgov", details.map((i) => toResolvedIntervention(i, "ctgov")));
150
- }
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
- }
@@ -1,20 +0,0 @@
1
- import type { Derived } from "@inflexa-ai/harness/contracts/target-dossier.js";
2
- import { type OrganSystem } from "../../../contracts/organ-system.js";
3
- /**
4
- * Derive the set of organs that should appear in the organ rollup based on
5
- * available safety signals (FAERS top signals, serious trial AEs, and
6
- * off-target panel entries).
7
- *
8
- * Exported so the Phase-4 assembler can call the same logic when building
9
- * organ_rollup rows — ensuring the set of "expected" organs is identical at
10
- * assembly time and at derived-validation time.
11
- */
12
- export declare function expectedOrgansFromBody(bodyInput: unknown): OrganSystem[];
13
- /**
14
- * Build the derived sub-tree from a raw dossier body.
15
- *
16
- * Pure function: no I/O, no LLM calls, no side effects. Defensive against
17
- * missing/partial bodies — always returns a best-effort result and never throws.
18
- * Schema validation happens at the persist boundary, not here.
19
- */
20
- export declare function computeDerivedFields(bodyInput: unknown): Derived;
@@ -1,261 +0,0 @@
1
- import { ORGAN_SYSTEMS } from "../../../contracts/organ-system.js";
2
- import { classifyOrgan, classifyTrialAe } from "./meddra-organ-map.js";
3
- import { HIGH_EXPRESSION_TPM_THRESHOLD } from "./expression-constants.js";
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
- }
13
- /**
14
- * Derive the set of organs that should appear in the organ rollup based on
15
- * available safety signals (FAERS top signals, serious trial AEs, and
16
- * off-target panel entries).
17
- *
18
- * Exported so the Phase-4 assembler can call the same logic when building
19
- * organ_rollup rows — ensuring the set of "expected" organs is identical at
20
- * assembly time and at derived-validation time.
21
- */
22
- export function expectedOrgansFromBody(bodyInput) {
23
- const body = bodyInput;
24
- const expected = new Set();
25
- const top = body?.safety_profile?.faers?.data?.top_signals ?? [];
26
- for (const sig of top) {
27
- if (sig.report_count >= FAERS_TOP_SIGNAL_THRESHOLD) {
28
- const organ = classifyOrgan(sig.meddra_term);
29
- if (organ)
30
- expected.add(organ);
31
- }
32
- }
33
- const serious = body?.safety_profile?.trial_aes?.data?.serious ?? [];
34
- for (const ae of serious) {
35
- const organ = classifyTrialAe(ae);
36
- if (organ)
37
- expected.add(organ);
38
- }
39
- const offTargets = body?.safety_profile?.off_target_panel?.data?.rows ?? [];
40
- for (const r of offTargets) {
41
- if (r.organ_system && isOrganSystem(r.organ_system))
42
- expected.add(r.organ_system);
43
- }
44
- return [...expected].sort();
45
- }
46
- function presentOrgans(body) {
47
- const rows = body?.safety_profile?.organ_rollup?.data?.rows ?? [];
48
- return rows
49
- .map((r) => r.organ)
50
- .filter(isOrganSystem)
51
- .sort();
52
- }
53
- function countDistinctPapers(body) {
54
- const set = new Set();
55
- const keyPapers = body?.reference_biology?.key_papers?.data?.rows ?? [];
56
- for (const p of keyPapers)
57
- if (p.pmid)
58
- set.add(String(p.pmid));
59
- return set.size;
60
- }
61
- function countDistinctTrials(body) {
62
- const set = new Set();
63
- // Ineligible trials are partitioned into `excluded_rows`, so the distinct
64
- // count must union both buckets for trials, failed_trials, and
65
- // discovery_trials. Outcomes preserves every row in `rows` and has no
66
- // `excluded_rows` bucket.
67
- const partitioned = [body?.clinical_development?.trials?.data, body?.clinical_development?.failed_trials?.data, body?.analytics?.discovery_trials?.data];
68
- for (const data of partitioned) {
69
- for (const bucket of ["rows", "excluded_rows"]) {
70
- for (const r of data?.[bucket] ?? []) {
71
- if (r.nct_id)
72
- set.add(String(r.nct_id));
73
- }
74
- }
75
- }
76
- for (const r of body?.clinical_development?.outcomes?.data?.rows ?? []) {
77
- if (r.nct_id)
78
- set.add(String(r.nct_id));
79
- }
80
- return set.size;
81
- }
82
- // Walk the entire body tree counting all `evidence` array entries to produce
83
- // a total evidence item count. This is intentionally recursive and treats every
84
- // nested `evidence` array as contributing to the total.
85
- function countTotalEvidenceItems(body) {
86
- let total = 0;
87
- const walk = (v) => {
88
- if (Array.isArray(v)) {
89
- for (const item of v)
90
- walk(item);
91
- }
92
- else if (v && typeof v === "object") {
93
- const node = v;
94
- if (Array.isArray(node.evidence))
95
- total += node.evidence.length;
96
- for (const k of Object.keys(v))
97
- walk(node[k]);
98
- }
99
- };
100
- walk(body);
101
- return total;
102
- }
103
- function hasHumanEvidence(body) {
104
- let found = false;
105
- const walk = (v) => {
106
- if (found)
107
- return;
108
- if (Array.isArray(v)) {
109
- for (const item of v)
110
- walk(item);
111
- }
112
- else if (v && typeof v === "object") {
113
- const node = v;
114
- if (node.has_human_evidence === true || node.is_human === true) {
115
- found = true;
116
- return;
117
- }
118
- for (const k of Object.keys(v))
119
- walk(node[k]);
120
- }
121
- };
122
- walk(body);
123
- return found;
124
- }
125
- function hasClinicalEvidence(body) {
126
- let found = false;
127
- const walk = (v) => {
128
- if (found)
129
- return;
130
- if (Array.isArray(v)) {
131
- for (const item of v)
132
- walk(item);
133
- }
134
- else if (v && typeof v === "object") {
135
- const node = v;
136
- if (node.has_clinical_evidence === true || node.is_clinical === true) {
137
- found = true;
138
- return;
139
- }
140
- for (const k of Object.keys(v))
141
- walk(node[k]);
142
- }
143
- };
144
- walk(body);
145
- return found;
146
- }
147
- function highestRiskOrgan(body) {
148
- const rows = body?.safety_profile?.organ_rollup?.data?.rows ?? [];
149
- const high = rows.find((r) => r.risk_level === "high");
150
- if (high)
151
- return high.organ;
152
- const med = rows.find((r) => r.risk_level === "medium");
153
- if (med)
154
- return med.organ;
155
- return null;
156
- }
157
- function topIndicationSummary(body) {
158
- const rows = body?.indications?.coverage === "available" ? (body.indications?.data?.rows ?? []) : [];
159
- if (rows.length === 0) {
160
- return { highest_score: null, strongest_strength_label: null, highest_score_is_conflicted: false };
161
- }
162
- let top = null;
163
- for (const r of rows) {
164
- if (typeof r.composite_score !== "number")
165
- continue;
166
- if (!top || r.composite_score > top.composite_score)
167
- top = r;
168
- }
169
- if (!top) {
170
- return { highest_score: null, strongest_strength_label: null, highest_score_is_conflicted: false };
171
- }
172
- const conflicted = Array.isArray(top.evidence) ? top.evidence.some((e) => e?.predicate === "direction_mismatch") : false;
173
- return {
174
- highest_score: top.composite_score ?? null,
175
- strongest_strength_label: top.disease_name ?? null,
176
- highest_score_is_conflicted: conflicted,
177
- };
178
- }
179
- // Count distinct high-expression tissues across the human protein atlas consensus
180
- // (normal_tissue_expression, keyed as "human") and the cross-species expression
181
- // heatmap. Deduplication uses `${tissue}::${species}` pairs so the same tissue
182
- // in different species counts as separate entries.
183
- function countHighExpressionTissues(body) {
184
- const set = new Set();
185
- const consensusRows = body?.reference_biology?.normal_tissue_expression?.data?.rows ?? [];
186
- for (const r of consensusRows) {
187
- if ((r.value ?? 0) >= HIGH_EXPRESSION_TPM_THRESHOLD && r.tissue) {
188
- set.add(`${r.tissue}::human`);
189
- }
190
- }
191
- const heatmapCells = body?.reference_biology?.preclinical?.expression_heatmap?.data?.cells ?? [];
192
- for (const c of heatmapCells) {
193
- if (c.rank === "high" && c.tissue) {
194
- const species = c.species ?? "unknown";
195
- set.add(`${c.tissue}::${species}`);
196
- }
197
- }
198
- return set.size;
199
- }
200
- /**
201
- * Build the derived sub-tree from a raw dossier body.
202
- *
203
- * Pure function: no I/O, no LLM calls, no side effects. Defensive against
204
- * missing/partial bodies — always returns a best-effort result and never throws.
205
- * Schema validation happens at the persist boundary, not here.
206
- */
207
- export function computeDerivedFields(bodyInput) {
208
- const body = bodyInput;
209
- const classLiabilityRows = body?.safety_profile?.class_precedent?.coverage === "available"
210
- ? (body.safety_profile?.class_precedent?.data?.per_organ ?? []).filter((o) => o.is_class_liability)
211
- : [];
212
- const classLiabilityCount = classLiabilityRows.length;
213
- const sameClassDrugCount = body?.safety_profile?.class_precedent?.coverage === "available" ? (body.safety_profile?.class_precedent?.data?.drugs_in_class ?? []).length : 0;
214
- const offTargetRows = body?.safety_profile?.off_target_panel?.data?.rows ?? [];
215
- const safetyHits = offTargetRows.filter((r) => r.is_safety_panel_target === true).length;
216
- const offTissueRows = body?.off_tissue_risk?.data?.rows ?? [];
217
- const offTissueOrganCount = new Set(offTissueRows.map((r) => r.organ).filter(Boolean)).size;
218
- const expected = expectedOrgansFromBody(body);
219
- const present = presentOrgans(body);
220
- const missing = expected.filter((o) => !present.includes(o));
221
- const seriousness = body?.safety_profile?.faers?.data?.seriousness;
222
- const fatalBullet = (body?.liability_summary?.liability_bullets ?? []).some((b) => b?.category === "fatal_post_market");
223
- const anyFatal = seriousness?.coverage === "available" ? (seriousness.by_seriousness?.death ?? 0) > 0 || fatalBullet : fatalBullet ? true : "unknown";
224
- const tissueRows = body?.reference_biology?.normal_tissue_expression?.data?.rows ?? [];
225
- const highTissues = countHighExpressionTissues(body);
226
- const safetySources = ["organ_rollup", "faers", "trial_aes", "off_target_panel", "failed_trials_safety_lens", "class_precedent"].filter((k) => body?.safety_profile?.[k]?.coverage === "available").length;
227
- return {
228
- summary: {
229
- has_human_evidence: hasHumanEvidence(body),
230
- has_clinical_evidence: hasClinicalEvidence(body),
231
- total_evidence_items: countTotalEvidenceItems(body),
232
- total_distinct_papers: countDistinctPapers(body),
233
- total_distinct_clinical_trials: countDistinctTrials(body),
234
- ...topIndicationSummary(body),
235
- },
236
- risk_summary: {
237
- any_fatal_signal: anyFatal,
238
- highest_risk_organ: highestRiskOrgan(body),
239
- off_target_safety_target_hits: safetyHits,
240
- class_liability_count: classLiabilityCount,
241
- },
242
- liability_summary: {
243
- counts: {
244
- class_liability_count: classLiabilityCount,
245
- safety_target_off_target_count: safetyHits,
246
- off_tissue_risk_organ_count: offTissueOrganCount,
247
- },
248
- expression_breadth: {
249
- total_assessed_tissues: tissueRows.length,
250
- high_expression_tissue_count: highTissues,
251
- },
252
- same_class_drug_count: sameClassDrugCount,
253
- safety_data_sources_checked: safetySources,
254
- },
255
- organ_rollup_completeness: {
256
- expected_organs: expected,
257
- present_organs: present,
258
- missing_organs: missing,
259
- },
260
- };
261
- }
@@ -1,15 +0,0 @@
1
- export declare function computeSelectivity(input: {
2
- primary_pchembl: number | null;
3
- off_target_pchembl: number | null;
4
- primary_source?: "chembl_target_drug_indication" | "literature_curated";
5
- }): {
6
- vs_primary_potency: {
7
- primary_pchembl_used: number;
8
- primary_source: "chembl_target_drug_indication" | "literature_curated";
9
- fold: number;
10
- log_units: number;
11
- };
12
- } | {
13
- selectivity_unknown: true;
14
- reason: string;
15
- };
@@ -1,18 +0,0 @@
1
- export function computeSelectivity(input) {
2
- if (input.primary_pchembl == null) {
3
- return { selectivity_unknown: true, reason: "no primary pChEMBL available for this modulator in ChEMBL target-drug-indication table" };
4
- }
5
- if (input.off_target_pchembl == null) {
6
- return { selectivity_unknown: true, reason: "no off-target pChEMBL recorded" };
7
- }
8
- const logUnits = input.primary_pchembl - input.off_target_pchembl;
9
- const fold = Math.pow(10, logUnits);
10
- return {
11
- vs_primary_potency: {
12
- primary_pchembl_used: input.primary_pchembl,
13
- primary_source: input.primary_source ?? "chembl_target_drug_indication",
14
- fold,
15
- log_units: logUnits,
16
- },
17
- };
18
- }
@@ -1,37 +0,0 @@
1
- import { type ChemblModulator } from "../../../tools/lib/chembl-client.js";
2
- export type DroppedSynonym = {
3
- chemblId: string;
4
- reason: "same_active_substance" | "ambiguous_name_synonym";
5
- keptId: string | null;
6
- };
7
- export type DedupResult<T extends Pick<ChemblModulator, "moleculeChemblId" | "parentChemblId" | "maxPhase" | "firstApproval">> = {
8
- kept: T[];
9
- dropped: DroppedSynonym[];
10
- };
11
- /**
12
- * Group modulators by parent_molecule_chembl_id (or moleculeChemblId when
13
- * the parent is absent — singleton groups). Within each group keep the
14
- * "most informative" entry: prefer firstApproval populated, then highest
15
- * maxPhase, then earliest moleculeChemblId for deterministic tie-break.
16
- *
17
- * Reason: ChEMBL exposes the same active substance under multiple
18
- * molecule_chembl_ids (e.g., CALCITONIN SALMON vs generic CALCITONIN),
19
- * which causes downstream FAERS queries to double-count the same reports.
20
- *
21
- * A second pass handles name-prefix synonyms where ChEMBL does not populate
22
- * molecule_hierarchy (e.g., CALCITONIN → CALCITONIN HUMAN). Entries with a
23
- * null firstApproval whose preferredName (uppercased) is a strict
24
- * word-boundary prefix of another entry's name within the same moleculeType
25
- * are dropped as same_active_substance (one match) or ambiguous_name_synonym
26
- * (multiple matches).
27
- */
28
- export declare function dedupModulatorsByParent<T extends Pick<ChemblModulator, "moleculeChemblId" | "parentChemblId" | "maxPhase" | "firstApproval" | "preferredName" | "moleculeType">>(modulators: T[]): DedupResult<T>;
29
- /**
30
- * For each modulator whose ChEMBL `moleculeType` is empty or "Unknown",
31
- * fetch the molecule's standard InChI key from ChEMBL, look it up in
32
- * PubChem, and apply the heuristic classifier (MW + amide-bond count).
33
- * Returns a new array of modulators with `moleculeType` replaced when a
34
- * confident classification emerges; otherwise the original ChEMBL value is
35
- * preserved. Network failures are isolated per modulator.
36
- */
37
- export declare function resolveModulatorMoleculeType<T extends Pick<ChemblModulator, "moleculeChemblId" | "moleculeType">>(modulators: T[]): Promise<T[]>;
@@ -1,157 +0,0 @@
1
- import { getMoleculeInChIKey } from "../../../tools/lib/chembl-client.js";
2
- import { classifyMoleculeType, getCompoundPropertiesByInChIKey } from "../../../tools/lib/pubchem-client.js";
3
- import { withHost } from "../../../lib/host-concurrency.js";
4
- /**
5
- * Group modulators by parent_molecule_chembl_id (or moleculeChemblId when
6
- * the parent is absent — singleton groups). Within each group keep the
7
- * "most informative" entry: prefer firstApproval populated, then highest
8
- * maxPhase, then earliest moleculeChemblId for deterministic tie-break.
9
- *
10
- * Reason: ChEMBL exposes the same active substance under multiple
11
- * molecule_chembl_ids (e.g., CALCITONIN SALMON vs generic CALCITONIN),
12
- * which causes downstream FAERS queries to double-count the same reports.
13
- *
14
- * A second pass handles name-prefix synonyms where ChEMBL does not populate
15
- * molecule_hierarchy (e.g., CALCITONIN → CALCITONIN HUMAN). Entries with a
16
- * null firstApproval whose preferredName (uppercased) is a strict
17
- * word-boundary prefix of another entry's name within the same moleculeType
18
- * are dropped as same_active_substance (one match) or ambiguous_name_synonym
19
- * (multiple matches).
20
- */
21
- export function dedupModulatorsByParent(modulators) {
22
- const groups = new Map();
23
- for (const m of modulators) {
24
- const key = m.parentChemblId ?? m.moleculeChemblId;
25
- const arr = groups.get(key) ?? [];
26
- arr.push(m);
27
- groups.set(key, arr);
28
- }
29
- const kept = [];
30
- const dropped = [];
31
- for (const group of groups.values()) {
32
- if (group.length === 1) {
33
- kept.push(group[0]);
34
- continue;
35
- }
36
- const ranked = [...group].sort((a, b) => {
37
- const aHasApp = a.firstApproval != null ? 1 : 0;
38
- const bHasApp = b.firstApproval != null ? 1 : 0;
39
- if (aHasApp !== bHasApp)
40
- return bHasApp - aHasApp;
41
- const aPhase = a.maxPhase ?? -1;
42
- const bPhase = b.maxPhase ?? -1;
43
- if (aPhase !== bPhase)
44
- return bPhase - aPhase;
45
- return a.moleculeChemblId.localeCompare(b.moleculeChemblId);
46
- });
47
- const winner = ranked[0];
48
- kept.push(winner);
49
- for (const loser of ranked.slice(1)) {
50
- dropped.push({
51
- chemblId: loser.moleculeChemblId,
52
- reason: "same_active_substance",
53
- keptId: winner.moleculeChemblId,
54
- });
55
- }
56
- }
57
- // Second pass: name-prefix synonym detection within the same moleculeType.
58
- // Handles cases where ChEMBL does not populate molecule_hierarchy for
59
- // unqualified generic names (e.g., "CALCITONIN" as a prefix of
60
- // "CALCITONIN HUMAN" and "CALCITONIN SALMON"). Only drops entries with
61
- // null firstApproval — an approved generic name should never be silently
62
- // dropped.
63
- const byType = new Map();
64
- for (const m of kept) {
65
- const k = (m.moleculeType ?? "").toUpperCase();
66
- const arr = byType.get(k) ?? [];
67
- arr.push(m);
68
- byType.set(k, arr);
69
- }
70
- const dropIds = new Set();
71
- for (const group of byType.values()) {
72
- for (const shortEntry of group) {
73
- if (shortEntry.firstApproval != null)
74
- continue;
75
- const shortName = (shortEntry.preferredName ?? "").trim().toUpperCase();
76
- if (!shortName)
77
- continue;
78
- const longerMatches = group.filter((other) => {
79
- if (other === shortEntry)
80
- return false;
81
- const longName = (other.preferredName ?? "").trim().toUpperCase();
82
- if (longName.length <= shortName.length)
83
- return false;
84
- if (!longName.startsWith(shortName))
85
- return false;
86
- // Require a word-boundary character (whitespace or hyphen) immediately
87
- // after the prefix — prevents "CALC" from matching "CALCITONIN".
88
- const boundary = longName.charAt(shortName.length);
89
- return /[\s-]/.test(boundary);
90
- });
91
- if (longerMatches.length === 1) {
92
- dropIds.add(shortEntry.moleculeChemblId);
93
- dropped.push({
94
- chemblId: shortEntry.moleculeChemblId,
95
- reason: "same_active_substance",
96
- keptId: longerMatches[0].moleculeChemblId,
97
- });
98
- }
99
- else if (longerMatches.length > 1) {
100
- dropIds.add(shortEntry.moleculeChemblId);
101
- dropped.push({
102
- chemblId: shortEntry.moleculeChemblId,
103
- reason: "ambiguous_name_synonym",
104
- keptId: null,
105
- });
106
- }
107
- }
108
- }
109
- const finalKept = kept.filter((m) => !dropIds.has(m.moleculeChemblId));
110
- return { kept: finalKept, dropped };
111
- }
112
- // ── PubChem-backed molecule_type backfill ─────────────────────────────────────
113
- function looksUnknown(value) {
114
- if (!value)
115
- return true;
116
- const lowered = value.trim().toLowerCase();
117
- return lowered === "" || lowered === "unknown";
118
- }
119
- /**
120
- * For each modulator whose ChEMBL `moleculeType` is empty or "Unknown",
121
- * fetch the molecule's standard InChI key from ChEMBL, look it up in
122
- * PubChem, and apply the heuristic classifier (MW + amide-bond count).
123
- * Returns a new array of modulators with `moleculeType` replaced when a
124
- * confident classification emerges; otherwise the original ChEMBL value is
125
- * preserved. Network failures are isolated per modulator.
126
- */
127
- export async function resolveModulatorMoleculeType(modulators) {
128
- const candidates = modulators.filter((m) => looksUnknown(m.moleculeType));
129
- if (candidates.length === 0)
130
- return modulators;
131
- const refined = new Map();
132
- await Promise.all(candidates.map(async (m) => {
133
- try {
134
- const inchiKey = await withHost("chembl", () => getMoleculeInChIKey(m.moleculeChemblId));
135
- if (!inchiKey)
136
- return;
137
- const props = await withHost("pubchem", () => getCompoundPropertiesByInChIKey(inchiKey));
138
- if (!props)
139
- return;
140
- const classified = classifyMoleculeType(props);
141
- if (classified !== "Unknown")
142
- refined.set(m.moleculeChemblId, classified);
143
- }
144
- catch {
145
- // Per-modulator failures must not blank out the others — keep the
146
- // original ChEMBL annotation.
147
- }
148
- }));
149
- if (refined.size === 0)
150
- return modulators;
151
- return modulators.map((m) => {
152
- const next = refined.get(m.moleculeChemblId);
153
- if (!next)
154
- return m;
155
- return { ...m, moleculeType: next };
156
- });
157
- }
@@ -1,17 +0,0 @@
1
- /**
2
- * Deterministic structural conflict detector.
3
- *
4
- * Each rule is a pure function over the dossier body. A row is emitted
5
- * when the body internally contradicts itself in a way that should be
6
- * impossible post-pipeline-fix. The post-fix pipeline should produce an
7
- * empty array for any well-formed body; the rules exist to catch
8
- * regressions and to surface upstream pipeline bugs.
9
- */
10
- export type StructuralEvidenceConflict = {
11
- evidence_item_id: string;
12
- predicate: string;
13
- contradicting_predicates: string[];
14
- surfaced_in_section: string;
15
- evidence: never[];
16
- };
17
- export declare function detectStructuralEvidenceConflicts(bodyInput: unknown): StructuralEvidenceConflict[];