@inflexa-ai/harness 0.35.8 → 0.36.1

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 (198) 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 +14 -0
  15. package/dist/loop/genai-spans.js +35 -0
  16. package/dist/loop/run-agent.d.ts +1 -1
  17. package/dist/loop/run-agent.js +37 -4
  18. package/dist/runtime/assemble.d.ts +0 -3
  19. package/dist/runtime/assemble.js +0 -3
  20. package/dist/state/index.d.ts +0 -2
  21. package/dist/state/index.js +0 -1
  22. package/dist/state/init.js +1 -60
  23. package/dist/state/purge-analysis.d.ts +1 -2
  24. package/dist/state/purge-analysis.js +1 -2
  25. package/dist/tasks/regulatory-corpus-boot-refresh.js +1 -1
  26. package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -3
  27. package/dist/tools/bio/gene-preclinical-profile.js +1 -3
  28. package/dist/tools/lib/chembl-client.d.ts +1 -4
  29. package/dist/tools/lib/chembl-client.js +1 -4
  30. package/dist/tools/lib/clinical-benchmarks-client.d.ts +1 -1
  31. package/dist/tools/lib/clinical-benchmarks-client.js +1 -1
  32. package/dist/tools/lib/clinical-trials-client.d.ts +0 -3
  33. package/dist/tools/lib/clinical-trials-client.js +0 -3
  34. package/dist/tools/lib/identifier-resolver.d.ts +5 -7
  35. package/dist/tools/lib/identifier-resolver.js +5 -7
  36. package/dist/tools/lib/iuphar-client.d.ts +1 -3
  37. package/dist/tools/lib/iuphar-client.js +1 -3
  38. package/dist/tools/lib/opentargets-client.d.ts +1 -1
  39. package/dist/tools/lib/opentargets-client.js +1 -1
  40. package/dist/tools/lib/protein-family-normalize.d.ts +5 -7
  41. package/dist/tools/lib/protein-family-normalize.js +5 -7
  42. package/dist/tools/lib/uniprot-client.d.ts +0 -7
  43. package/dist/tools/lib/uniprot-client.js +2 -9
  44. package/package.json +1 -1
  45. package/dist/contracts/target-assessment-row.d.ts +0 -85
  46. package/dist/contracts/target-assessment-row.js +0 -37
  47. package/dist/contracts/target-dossier.d.ts +0 -12423
  48. package/dist/contracts/target-dossier.js +0 -1282
  49. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +0 -1
  50. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +0 -85
  51. package/dist/prompts/target-assessment/briefs/index.d.ts +0 -4
  52. package/dist/prompts/target-assessment/briefs/index.js +0 -4
  53. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +0 -1
  54. package/dist/prompts/target-assessment/briefs/liability-bullets.js +0 -114
  55. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +0 -1
  56. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +0 -37
  57. package/dist/prompts/target-assessment/briefs/translational-commentary.d.ts +0 -1
  58. package/dist/prompts/target-assessment/briefs/translational-commentary.js +0 -23
  59. package/dist/prompts/target-assessment/drugs-in-class.d.ts +0 -1
  60. package/dist/prompts/target-assessment/drugs-in-class.js +0 -29
  61. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +0 -1
  62. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +0 -67
  63. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +0 -1
  64. package/dist/prompts/target-assessment/investigation/claim-reverification.js +0 -49
  65. package/dist/prompts/target-assessment/investigation/index.d.ts +0 -3
  66. package/dist/prompts/target-assessment/investigation/index.js +0 -3
  67. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +0 -1
  68. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +0 -49
  69. package/dist/prompts/target-assessment/modulator-triage.d.ts +0 -1
  70. package/dist/prompts/target-assessment/modulator-triage.js +0 -31
  71. package/dist/prompts/target-assessment/off-target-annotator.d.ts +0 -1
  72. package/dist/prompts/target-assessment/off-target-annotator.js +0 -28
  73. package/dist/prompts/target-assessment/tox-voice/core.d.ts +0 -1
  74. package/dist/prompts/target-assessment/tox-voice/core.js +0 -74
  75. package/dist/prompts/target-assessment/tox-voice/critique.d.ts +0 -7
  76. package/dist/prompts/target-assessment/tox-voice/critique.js +0 -109
  77. package/dist/prompts/target-assessment/tox-voice/exemplars.d.ts +0 -8
  78. package/dist/prompts/target-assessment/tox-voice/exemplars.js +0 -24
  79. package/dist/prompts/target-assessment/tox-voice/index.d.ts +0 -4
  80. package/dist/prompts/target-assessment/tox-voice/index.js +0 -4
  81. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +0 -18
  82. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +0 -61
  83. package/dist/state/target-assessments.d.ts +0 -68
  84. package/dist/state/target-assessments.js +0 -222
  85. package/dist/workflows/execute-target-assessment.d.ts +0 -118
  86. package/dist/workflows/execute-target-assessment.js +0 -631
  87. package/dist/workflows/target-assessment/assemblers/index.d.ts +0 -5
  88. package/dist/workflows/target-assessment/assemblers/index.js +0 -4
  89. package/dist/workflows/target-assessment/assemblers/literature.d.ts +0 -281
  90. package/dist/workflows/target-assessment/assemblers/literature.js +0 -971
  91. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +0 -118
  92. package/dist/workflows/target-assessment/assemblers/orchestrator.js +0 -945
  93. package/dist/workflows/target-assessment/assemblers/safety.d.ts +0 -374
  94. package/dist/workflows/target-assessment/assemblers/safety.js +0 -1068
  95. package/dist/workflows/target-assessment/assemblers/trials.d.ts +0 -30
  96. package/dist/workflows/target-assessment/assemblers/trials.js +0 -75
  97. package/dist/workflows/target-assessment/collectors/index.d.ts +0 -114
  98. package/dist/workflows/target-assessment/collectors/index.js +0 -538
  99. package/dist/workflows/target-assessment/coverage.d.ts +0 -97
  100. package/dist/workflows/target-assessment/coverage.js +0 -86
  101. package/dist/workflows/target-assessment/decisions/index.d.ts +0 -86
  102. package/dist/workflows/target-assessment/decisions/index.js +0 -127
  103. package/dist/workflows/target-assessment/fanout/index.d.ts +0 -32
  104. package/dist/workflows/target-assessment/fanout/index.js +0 -156
  105. package/dist/workflows/target-assessment/investigation/index.d.ts +0 -97
  106. package/dist/workflows/target-assessment/investigation/index.js +0 -419
  107. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +0 -73
  108. package/dist/workflows/target-assessment/lib/approval-precedents.js +0 -185
  109. package/dist/workflows/target-assessment/lib/citation-validator.d.ts +0 -46
  110. package/dist/workflows/target-assessment/lib/citation-validator.js +0 -316
  111. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +0 -65
  112. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +0 -174
  113. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +0 -44
  114. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -152
  115. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +0 -20
  116. package/dist/workflows/target-assessment/lib/compute-derived.js +0 -261
  117. package/dist/workflows/target-assessment/lib/compute-selectivity.d.ts +0 -15
  118. package/dist/workflows/target-assessment/lib/compute-selectivity.js +0 -18
  119. package/dist/workflows/target-assessment/lib/dedup-modulators.d.ts +0 -37
  120. package/dist/workflows/target-assessment/lib/dedup-modulators.js +0 -157
  121. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.d.ts +0 -17
  122. package/dist/workflows/target-assessment/lib/evidence-conflict-detector.js +0 -100
  123. package/dist/workflows/target-assessment/lib/expression-constants.d.ts +0 -21
  124. package/dist/workflows/target-assessment/lib/expression-constants.js +0 -21
  125. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +0 -20
  126. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +0 -36
  127. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +0 -72
  128. package/dist/workflows/target-assessment/lib/fda-label-safety.js +0 -166
  129. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -30
  130. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -44
  131. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +0 -34
  132. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +0 -63
  133. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +0 -19
  134. package/dist/workflows/target-assessment/lib/impc-organ-map.js +0 -39
  135. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.d.ts +0 -4
  136. package/dist/workflows/target-assessment/lib/intended-polypharm-filter.js +0 -24
  137. package/dist/workflows/target-assessment/lib/llm-step.d.ts +0 -80
  138. package/dist/workflows/target-assessment/lib/llm-step.js +0 -101
  139. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +0 -40
  140. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +0 -138
  141. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.d.ts +0 -17
  142. package/dist/workflows/target-assessment/lib/recommendation-quality-gates.js +0 -99
  143. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.d.ts +0 -2
  144. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +0 -51
  145. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +0 -25
  146. package/dist/workflows/target-assessment/lib/regulatory-actions.js +0 -176
  147. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +0 -44
  148. package/dist/workflows/target-assessment/lib/safety-corroboration.js +0 -273
  149. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +0 -61
  150. package/dist/workflows/target-assessment/lib/structured-llm.js +0 -102
  151. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +0 -59
  152. package/dist/workflows/target-assessment/lib/target-identity-filter.js +0 -95
  153. package/dist/workflows/target-assessment/lib/therapeutic-programs.d.ts +0 -20
  154. package/dist/workflows/target-assessment/lib/therapeutic-programs.js +0 -68
  155. package/dist/workflows/target-assessment/metrics.d.ts +0 -16
  156. package/dist/workflows/target-assessment/metrics.js +0 -30
  157. package/dist/workflows/target-assessment/phase4-assemble.d.ts +0 -3182
  158. package/dist/workflows/target-assessment/phase4-assemble.js +0 -39
  159. package/dist/workflows/target-assessment/phase5-persist.d.ts +0 -59
  160. package/dist/workflows/target-assessment/phase5-persist.js +0 -349
  161. package/dist/workflows/target-assessment/progress-stream.d.ts +0 -75
  162. package/dist/workflows/target-assessment/progress-stream.js +0 -110
  163. package/dist/workflows/target-assessment/progress.d.ts +0 -39
  164. package/dist/workflows/target-assessment/progress.js +0 -77
  165. package/dist/workflows/target-assessment/schemas.d.ts +0 -970
  166. package/dist/workflows/target-assessment/schemas.js +0 -318
  167. package/dist/workflows/target-assessment/steps/collectors/pathways.d.ts +0 -15
  168. package/dist/workflows/target-assessment/steps/collectors/pathways.js +0 -58
  169. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.d.ts +0 -34
  170. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.js +0 -31
  171. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.d.ts +0 -57
  172. package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.js +0 -54
  173. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.d.ts +0 -35
  174. package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.js +0 -32
  175. package/dist/workflows/target-assessment/steps/fanout/index.d.ts +0 -14
  176. package/dist/workflows/target-assessment/steps/fanout/index.js +0 -10
  177. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +0 -29
  178. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +0 -29
  179. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +0 -657
  180. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -34
  181. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +0 -806
  182. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -16
  183. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -28
  184. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -27
  185. package/dist/workflows/target-assessment/sub-workflows/index.d.ts +0 -15
  186. package/dist/workflows/target-assessment/sub-workflows/index.js +0 -11
  187. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.d.ts +0 -41
  188. package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.js +0 -12
  189. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.d.ts +0 -41
  190. package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.js +0 -12
  191. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +0 -37
  192. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.js +0 -12
  193. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.d.ts +0 -66
  194. package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.js +0 -12
  195. package/dist/workflows/target-assessment/synthesis/index.d.ts +0 -607
  196. package/dist/workflows/target-assessment/synthesis/index.js +0 -354
  197. package/dist/workflows/target-assessment/synthesis-probes.d.ts +0 -56
  198. 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[];