@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,176 +0,0 @@
1
- /**
2
- * Per-drug regulatory-action collector. Queries openFDA Structured Product
3
- * Labels (boxed warning, Section 5 warnings_and_cautions, REMS indicator)
4
- * and the EMA referrals catalogue (Article 31 / Article 20 procedures)
5
- * for each drug, then maps both into the dossier's RegulatoryActionRow
6
- * shape.
7
- *
8
- * Coverage that legitimately has no
9
- * machine-readable source (e.g., EMA assessment-report procedure codes like
10
- * EMEA/H/A-31/1291) is omitted rather than backfilled with curation — the
11
- * source_url points at the canonical document where the human reader can
12
- * extract the additional context.
13
- */
14
- import { withHost } from "../../../lib/host-concurrency.js";
15
- import { getReferralsByDrug } from "../../../tools/lib/ema-client.js";
16
- import { getDrugLabelActions } from "../../../tools/lib/openfda-client.js";
17
- import { segmentLabelSafety } from "./fda-label-safety.js";
18
- function formatEffectiveTime(yyyymmdd) {
19
- if (!yyyymmdd || yyyymmdd.length < 6)
20
- return "";
21
- return `${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}`;
22
- }
23
- function formatEmaDate(ddmmyyyy) {
24
- if (!ddmmyyyy)
25
- return "";
26
- const parts = ddmmyyyy.split("/");
27
- if (parts.length !== 3)
28
- return "";
29
- return `${parts[2]}-${parts[1]}`;
30
- }
31
- const FINDING_CHAR_LIMIT = 2000;
32
- function clipFinding(text) {
33
- const cleaned = text.replace(/\s+/g, " ").trim();
34
- if (cleaned.length <= FINDING_CHAR_LIMIT)
35
- return cleaned;
36
- return `${cleaned.slice(0, FINDING_CHAR_LIMIT - 1).trimEnd()}…`;
37
- }
38
- /**
39
- * Project a fetched label onto the shared typed safety form. Returns null when
40
- * the label carries no application number: its warnings could not then be cited
41
- * back to a document, and an uncitable warning is not evidence.
42
- */
43
- function labelSafetyOf(drug, label) {
44
- if (!label.applicationNumber)
45
- return null;
46
- const sections = [];
47
- if (label.boxedWarning)
48
- sections.push({ section: "boxed_warning", text: label.boxedWarning });
49
- if (label.warningsAndCautions)
50
- sections.push({ section: "warnings_and_precautions", text: label.warningsAndCautions });
51
- if (sections.length === 0)
52
- return null;
53
- return {
54
- application_number: label.applicationNumber,
55
- drug_name: label.genericName ?? label.brandName ?? drug.name,
56
- effective_time: label.effectiveTime ?? undefined,
57
- source_url: label.sourceUrl || undefined,
58
- sections,
59
- };
60
- }
61
- function fdaRowsFor(drug, labels) {
62
- if (labels.length === 0)
63
- return [];
64
- const sorted = [...labels].sort((a, b) => (b.effectiveTime ?? "").localeCompare(a.effectiveTime ?? ""));
65
- const latest = sorted[0];
66
- const rows = [];
67
- const actionDate = formatEffectiveTime(latest.effectiveTime);
68
- // The label's own prose, segmented once and attributed to organs, is what
69
- // lets each row cite the section a reader can open rather than only the
70
- // agency and the date.
71
- const labelSafety = labelSafetyOf(drug, latest);
72
- const signals = labelSafety ? segmentLabelSafety([labelSafety]).signals : [];
73
- const evidenceFrom = (section) => signals.filter((s) => s.source_section === section).map((s) => s.evidence);
74
- if (latest.boxedWarning) {
75
- rows.push({
76
- drug_chembl_id: drug.chemblId,
77
- drug_name: drug.name,
78
- agency: "FDA",
79
- action_kind: "black_box",
80
- source_kind: "boxed_warning",
81
- action_date: actionDate,
82
- source_date: actionDate,
83
- application_number: latest.applicationNumber ?? undefined,
84
- finding: clipFinding(latest.boxedWarning),
85
- source_url: latest.sourceUrl || undefined,
86
- evidence: evidenceFrom("boxed_warning"),
87
- });
88
- }
89
- if (latest.warningsAndCautions) {
90
- rows.push({
91
- drug_chembl_id: drug.chemblId,
92
- drug_name: drug.name,
93
- agency: "FDA",
94
- action_kind: "label_warning",
95
- source_kind: "label_warning",
96
- action_date: actionDate,
97
- source_date: actionDate,
98
- application_number: latest.applicationNumber ?? undefined,
99
- label_section: "5",
100
- finding: clipFinding(latest.warningsAndCautions),
101
- source_url: latest.sourceUrl || undefined,
102
- evidence: evidenceFrom("warnings_and_precautions"),
103
- });
104
- }
105
- if (latest.hasRems) {
106
- rows.push({
107
- drug_chembl_id: drug.chemblId,
108
- drug_name: drug.name,
109
- agency: "FDA",
110
- action_kind: "REMS",
111
- source_kind: "rems",
112
- action_date: actionDate,
113
- source_date: actionDate,
114
- application_number: latest.applicationNumber ?? undefined,
115
- finding: `Risk Evaluation and Mitigation Strategy referenced in current label (${latest.brandName ?? latest.genericName ?? drug.name}).`,
116
- source_url: latest.sourceUrl || undefined,
117
- evidence: [],
118
- });
119
- }
120
- return rows;
121
- }
122
- function classifyEmaActionKind(referral) {
123
- const status = referral.currentStatus.toLowerCase();
124
- if (status.includes("withdraw"))
125
- return "withdrawal";
126
- return "referral";
127
- }
128
- function emaRowsFor(drug, referrals) {
129
- return referrals.map((r) => {
130
- const date = formatEmaDate(r.europeanCommissionDecisionDate) ||
131
- formatEmaDate(r.chmpOpinionDate) ||
132
- formatEmaDate(r.cmdhPositionDate) ||
133
- formatEmaDate(r.pracRecommendationDate) ||
134
- formatEmaDate(r.procedureStartDate);
135
- const findingParts = [];
136
- if (r.referralType)
137
- findingParts.push(r.referralType);
138
- if (r.referenceNumber)
139
- findingParts.push(`(${r.referenceNumber})`);
140
- if (r.currentStatus)
141
- findingParts.push(`— ${r.currentStatus}`);
142
- if (r.referralName && r.referralName.toLowerCase() !== drug.name.toLowerCase()) {
143
- findingParts.push(`re: ${r.referralName}`);
144
- }
145
- return {
146
- drug_chembl_id: drug.chemblId,
147
- drug_name: drug.name,
148
- agency: "EMA",
149
- action_kind: classifyEmaActionKind(r),
150
- source_kind: classifyEmaActionKind(r) === "withdrawal" ? "withdrawal" : "referral",
151
- action_date: date,
152
- source_date: date,
153
- finding: clipFinding(findingParts.join(" ")),
154
- source_url: r.referralUrl || undefined,
155
- evidence: [],
156
- };
157
- });
158
- }
159
- /**
160
- * Fetch regulatory actions (FDA label + EMA referrals) for a set of drugs.
161
- * Per-drug failures are isolated — one drug throwing does not blank out
162
- * the rest. Returns an empty array when no drugs are provided or no source
163
- * returned any actions.
164
- */
165
- export async function fetchRegulatoryActions(drugs) {
166
- if (drugs.length === 0)
167
- return [];
168
- const perDrug = await Promise.all(drugs.map(async (drug) => {
169
- const [fdaLabels, emaReferrals] = await Promise.all([
170
- withHost("openfda", () => getDrugLabelActions(drug.name)).catch(() => []),
171
- withHost("ema", () => getReferralsByDrug(drug.name)).catch(() => []),
172
- ]);
173
- return [...fdaRowsFor(drug, fdaLabels), ...emaRowsFor(drug, emaReferrals)];
174
- }));
175
- return perDrug.flat();
176
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * The safety-evidence corroboration spine.
3
- *
4
- * A run queries a dozen-plus independent sources and files each one's output in
5
- * its own dossier section. This fold asks the question none of those sections
6
- * can: for a given organ, do the sources agree? Agreement across independent
7
- * sources is itself evidence, and it is what separates a real organ liability
8
- * from a single-source artefact.
9
- *
10
- * The fold re-fetches nothing. Every signal it reads is already in hand when it
11
- * runs, and a source that also populates a section of its own is referenced
12
- * here as a contribution rather than restated — there is one evidence path, not
13
- * two.
14
- *
15
- * **The source list is open.** A source is one entry in `SIGNAL_SOURCES`
16
- * declaring its id and how to read its signals out of the run's inputs.
17
- * Everything downstream — grouping, counting, thresholding, evidence assembly —
18
- * is written against the id-bearing record, so a fifteenth collector joins the
19
- * spine by adding an entry and changes neither the fold nor the schema.
20
- */
21
- import type { SafetyCorroboration } from "@inflexa-ai/harness/contracts/target-dossier.js";
22
- import type { Phase1Bundle } from "../schemas.js";
23
- import type { OrganSignalProjection } from "./fda-label-safety.js";
24
- /** Distinct sources an organ needs before the spine calls it corroborated. */
25
- export declare const MIN_INDEPENDENT_SOURCES = 2;
26
- /** What the section reports as having emptied it. */
27
- export declare const CORROBORATION_FILTER: string;
28
- /** Everything the fold reads. Already collected — nothing here is fetched. */
29
- export interface CorroborationInput {
30
- readonly phase1: Phase1Bundle;
31
- /** Per-organ FDA label warnings segmented at the approval-precedent step. */
32
- readonly regulatoryOrganSignals: OrganSignalProjection | null;
33
- }
34
- /**
35
- * Fold the run's per-organ safety signals into one corroboration record per
36
- * organ.
37
- *
38
- * `dropped_count` counts signals — the fold's input unit — discarded because
39
- * their organ did not resolve, because their source produced no locator, or
40
- * because their organ drew fewer than `MIN_INDEPENDENT_SOURCES` distinct
41
- * sources. A fold that ends with no record reports `filtered` with that count
42
- * rather than an empty `available`.
43
- */
44
- export declare function assembleSafetyCorroboration(input: CorroborationInput): SafetyCorroboration;
@@ -1,273 +0,0 @@
1
- /**
2
- * The safety-evidence corroboration spine.
3
- *
4
- * A run queries a dozen-plus independent sources and files each one's output in
5
- * its own dossier section. This fold asks the question none of those sections
6
- * can: for a given organ, do the sources agree? Agreement across independent
7
- * sources is itself evidence, and it is what separates a real organ liability
8
- * from a single-source artefact.
9
- *
10
- * The fold re-fetches nothing. Every signal it reads is already in hand when it
11
- * runs, and a source that also populates a section of its own is referenced
12
- * here as a contribution rather than restated — there is one evidence path, not
13
- * two.
14
- *
15
- * **The source list is open.** A source is one entry in `SIGNAL_SOURCES`
16
- * declaring its id and how to read its signals out of the run's inputs.
17
- * Everything downstream — grouping, counting, thresholding, evidence assembly —
18
- * is written against the id-bearing record, so a fifteenth collector joins the
19
- * spine by adding an entry and changes neither the fold nor the schema.
20
- */
21
- import { isSafetyRelevant, meetsTpmFloor, resolveTissueOrganSystem } from "../assemblers/safety.js";
22
- import { resolveHpoOrgan } from "./hpo-organ-map.js";
23
- import { resolveImpcOrgan } from "./impc-organ-map.js";
24
- import { classifyOrgan } from "./meddra-organ-map.js";
25
- /** Distinct sources an organ needs before the spine calls it corroborated. */
26
- export const MIN_INDEPENDENT_SOURCES = 2;
27
- /** What the section reports as having emptied it. */
28
- export const CORROBORATION_FILTER = `organ signals with no resolvable organ or locator, or whose organ drew fewer than ` + `${MIN_INDEPENDENT_SOURCES} independent sources`;
29
- /**
30
- * Curated human phenotypes ranked per organ. A well-annotated gene carries
31
- * hundreds of HPO terms under one organ system; beyond the ranked head they
32
- * restate the same organ-level signal, so the source raises it once at the
33
- * granularity a reader can act on.
34
- */
35
- const PHENOTYPES_PER_ORGAN = 3;
36
- // ── Source extractors ────────────────────────────────────────────────
37
- /** Warning fragments from FDA prescribing information, already organ-keyed. */
38
- function extractRegulatoryLabels(input) {
39
- const projection = input.regulatoryOrganSignals;
40
- if (!projection)
41
- return [];
42
- return projection.signals.map((s) => ({
43
- organ: s.organ,
44
- signal: `${s.drug_name} label §${s.label_section}: ${s.excerpt}`,
45
- evidence: s.evidence.regulatory_reference ? s.evidence : null,
46
- }));
47
- }
48
- /** Mouse knockout phenotype systems, keyed by the MGI accession for the gene. */
49
- function extractKnockoutPhenotypes(input) {
50
- const impc = input.phase1.collectors.impc;
51
- if (impc.coverage !== "available")
52
- return [];
53
- const accession = impc.data.mgiAccessionId;
54
- return impc.data.organSystems.map((bucket) => ({
55
- organ: resolveImpcOrgan(bucket),
56
- signal: `mouse knockout phenotype in the ${bucket} system`,
57
- evidence: accession
58
- ? {
59
- source: "impc",
60
- accession,
61
- predicate: "knockout_phenotype",
62
- excerpt: `${impc.data.mouseMarkerSymbol ?? ""} knockout phenotypes in the ${bucket} system`.trim(),
63
- metadata: { bucket, phenotype_count: impc.data.phenotypeCount },
64
- }
65
- : null,
66
- }));
67
- }
68
- /** Curated human phenotypes, resolved by HPO ancestry rather than by label. */
69
- function extractHumanPhenotypes(input) {
70
- const monarch = input.phase1.collectors.monarch;
71
- if (monarch.coverage !== "available")
72
- return [];
73
- const ranked = [...monarch.data.phenotypes].sort((a, b) => b.publications.length - a.publications.length || (b.frequencyPercent ?? 0) - (a.frequencyPercent ?? 0) || a.label.localeCompare(b.label));
74
- const perOrgan = new Map();
75
- const out = [];
76
- for (const p of ranked) {
77
- const organ = resolveHpoOrgan(p.hpoId, p.ancestorIds);
78
- if (organ === null) {
79
- out.push({ organ: null, signal: p.label, evidence: null });
80
- continue;
81
- }
82
- const seen = perOrgan.get(organ) ?? 0;
83
- if (seen >= PHENOTYPES_PER_ORGAN)
84
- continue;
85
- perOrgan.set(organ, seen + 1);
86
- const pmid = p.publications.find((pub) => pub.startsWith("PMID:"))?.slice("PMID:".length);
87
- out.push({
88
- organ,
89
- signal: `curated human phenotype: ${p.label}`,
90
- evidence: {
91
- source: "monarch",
92
- accession: p.hpoId,
93
- ...(pmid ? { pmid } : {}),
94
- predicate: "human_phenotype",
95
- is_human: true,
96
- excerpt: p.label,
97
- metadata: {
98
- ...(p.diseaseContext ? { disease_context: p.diseaseContext } : {}),
99
- ...(p.frequencyPercent != null ? { frequency_percent: p.frequencyPercent } : {}),
100
- ...(p.primaryKnowledgeSource ? { knowledge_source: p.primaryKnowledgeSource } : {}),
101
- },
102
- },
103
- });
104
- }
105
- return out;
106
- }
107
- /** Open Targets' curated safety liabilities, keyed by the Ensembl accession. */
108
- function extractCuratedSafetyLiabilities(input) {
109
- const ot = input.phase1.collectors.opentargets;
110
- if (ot.coverage !== "available")
111
- return [];
112
- return ot.data.safetyLiabilities.map((liability) => ({
113
- organ: classifyOrgan(liability.event) ?? classifyOrgan(liability.biosamples.join(" ")),
114
- signal: `curated safety liability: ${liability.event}`,
115
- evidence: {
116
- source: "opentargets:safety",
117
- accession: ot.data.ensemblId,
118
- predicate: "known_safety_liability",
119
- excerpt: liability.effects ? `${liability.event} — ${liability.effects}` : liability.event,
120
- metadata: { biosamples: liability.biosamples, curator: liability.source },
121
- },
122
- }));
123
- }
124
- /**
125
- * Baseline expression in safety-relevant tissues.
126
- *
127
- * The atlas reaches the dossier through the Open Targets `baselineExpression`
128
- * surface, narrowed to the GTEx bulk-tissue rows — the same path
129
- * `reference_biology.normal_tissue_expression` and the off-tissue risk rows
130
- * read, with the same safety-relevance and floor rules applied. Only the
131
- * highest-expressing tissue per organ is raised: several tissues mapping to one
132
- * organ are one expression signal about that organ.
133
- */
134
- function extractExpressionAtlas(input) {
135
- const expression = input.phase1.collectors.expressionHuman;
136
- if (expression.coverage !== "available")
137
- return [];
138
- const accession = input.phase1.resolved.ids.ensembl;
139
- const best = new Map();
140
- const unresolved = [];
141
- for (const t of expression.data.tissues) {
142
- const value = t.value ?? 0;
143
- if (!isSafetyRelevant(t.tissueLabel, t.organSystem) || !meetsTpmFloor(t.tissueLabel, value))
144
- continue;
145
- const organ = resolveTissueOrganSystem(t.tissueLabel, t.organSystem);
146
- if (organ === null) {
147
- unresolved.push({ organ: null, signal: `${t.tissueLabel} expression`, evidence: null });
148
- continue;
149
- }
150
- const current = best.get(organ);
151
- if (!current || value > current.value)
152
- best.set(organ, { tissue: t.tissueLabel, value });
153
- }
154
- const resolved = [...best.entries()].map(([organ, hit]) => {
155
- const signal = `${expression.data.unit} expression ${hit.value} in ${hit.tissue}`;
156
- return {
157
- organ,
158
- signal,
159
- evidence: accession
160
- ? {
161
- source: `expression:${expression.data.source}`,
162
- accession,
163
- predicate: "expressed_in_safety_tissue",
164
- score: hit.value,
165
- excerpt: `${hit.tissue}: ${hit.value} (${expression.data.unit})`,
166
- metadata: {
167
- tissue: hit.tissue,
168
- unit: expression.data.unit,
169
- normalization_notes: expression.data.normalization_notes,
170
- },
171
- }
172
- : null,
173
- };
174
- });
175
- return [...resolved, ...unresolved];
176
- }
177
- /**
178
- * The registered sources.
179
- *
180
- * This array is the whole integration surface: an id, and how to read that
181
- * source's organ-bearing signals out of what the run already collected.
182
- */
183
- const SIGNAL_SOURCES = [
184
- { id: "fda_label", extract: extractRegulatoryLabels },
185
- { id: "impc", extract: extractKnockoutPhenotypes },
186
- { id: "monarch", extract: extractHumanPhenotypes },
187
- { id: "opentargets_safety", extract: extractCuratedSafetyLiabilities },
188
- { id: "expression_atlas", extract: extractExpressionAtlas },
189
- ];
190
- /**
191
- * Fold the run's per-organ safety signals into one corroboration record per
192
- * organ.
193
- *
194
- * `dropped_count` counts signals — the fold's input unit — discarded because
195
- * their organ did not resolve, because their source produced no locator, or
196
- * because their organ drew fewer than `MIN_INDEPENDENT_SOURCES` distinct
197
- * sources. A fold that ends with no record reports `filtered` with that count
198
- * rather than an empty `available`.
199
- */
200
- export function assembleSafetyCorroboration(input) {
201
- const sourcesConsidered = [];
202
- const admitted = [];
203
- let dropped = 0;
204
- for (const source of SIGNAL_SOURCES) {
205
- const signals = source.extract(input);
206
- if (signals.length === 0)
207
- continue;
208
- sourcesConsidered.push(source.id);
209
- const seen = new Set();
210
- for (const raw of signals) {
211
- if (raw.organ === null || raw.evidence === null) {
212
- dropped += 1;
213
- continue;
214
- }
215
- const key = `${raw.organ}\u0000${raw.signal}`;
216
- if (seen.has(key))
217
- continue;
218
- seen.add(key);
219
- admitted.push({
220
- source: source.id,
221
- organ: raw.organ,
222
- contribution: { source: source.id, signal: raw.signal, evidence: raw.evidence },
223
- });
224
- }
225
- }
226
- if (sourcesConsidered.length === 0) {
227
- return {
228
- coverage: "queried_no_data",
229
- error: { message: "no source produced an organ-bearing safety signal" },
230
- };
231
- }
232
- const byOrgan = new Map();
233
- for (const item of admitted) {
234
- const bucket = byOrgan.get(item.organ);
235
- if (bucket)
236
- bucket.push(item);
237
- else
238
- byOrgan.set(item.organ, [item]);
239
- }
240
- const rows = [];
241
- for (const [organ, items] of byOrgan) {
242
- const corroborating = [...new Set(items.map((i) => i.source))].sort();
243
- if (corroborating.length < MIN_INDEPENDENT_SOURCES) {
244
- dropped += items.length;
245
- continue;
246
- }
247
- const contributions = items.map((i) => i.contribution);
248
- rows.push({
249
- organ,
250
- contributions,
251
- corroborating_sources: corroborating,
252
- independent_source_count: corroborating.length,
253
- support: { state: "scored", evidence: contributions.map((c) => c.evidence) },
254
- });
255
- }
256
- rows.sort((a, b) => b.independent_source_count - a.independent_source_count || a.organ.localeCompare(b.organ));
257
- // Reaching here means at least one source emitted a signal — that is what put
258
- // it in `sourcesConsidered` — and every emitted signal was either admitted or
259
- // counted as dropped. So no record left standing means the fold's own
260
- // thresholds took them all, and `dropped` is necessarily positive.
261
- if (rows.length === 0) {
262
- return { coverage: "filtered", filter: CORROBORATION_FILTER, dropped_count: dropped };
263
- }
264
- return {
265
- coverage: "available",
266
- data: {
267
- rows,
268
- sources_considered: [...sourcesConsidered].sort(),
269
- min_independent_sources: MIN_INDEPENDENT_SOURCES,
270
- },
271
- ...(dropped > 0 ? { dropped_count: dropped } : {}),
272
- };
273
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * Structured-output LLM helper for the target-assessment DBOS workflow.
3
- *
4
- * Uses a tool-choice forcing pattern: a single Anthropic tool named `submit`
5
- * carries the Zod schema as its `input_schema`, and
6
- * `tool_choice: { type: "tool", name: "submit" }` forces the model to fill
7
- * it. The tool's `input` IS the structured output.
8
- *
9
- * This intentionally does NOT pull in the harness `runAgent` loop — Phase-2
10
- * decisions are zero-tool single-shot calls. Phase-5 syntheses today use
11
- * regulatory-guidance + approval-precedent retrieval tools; the harness
12
- * port drops those grounding tools (PR #4 ships single-shot synthesis; a
13
- * follow-up may reintroduce the retrieval tools through the agent loop).
14
- *
15
- * Calls are routed through `runLlmStep`, so each LLM call lives in its own
16
- * `DBOS.runStep({name})` cache slot and a billing-gateway 402 self-cancels the
17
- * workflow rather than returning a coverage envelope.
18
- */
19
- import { z } from "zod";
20
- import type { AgentSession } from "../../../auth/types.js";
21
- import type { Logger } from "../../../lib/logger.js";
22
- import type { AgentChat } from "../../../providers/types.js";
23
- import { BUDGET_EXCEEDED_SENTINEL } from "./llm-step.js";
24
- export interface StructuredLlmCallOptions<TSchema extends z.ZodType> {
25
- /** Operational logging seam, forwarded to `runLlmStep`; omitted falls back to no-op. */
26
- readonly logger?: Logger;
27
- /** Attempt-numbered durable-step name (e.g. `"ta-decision:modulator-triage:0"`). */
28
- readonly stepName: string;
29
- /** Agent id stamped on telemetry and billing-gateway provenance. */
30
- readonly agentId: string;
31
- readonly provider: AgentChat;
32
- readonly session: AgentSession;
33
- readonly system: string;
34
- readonly prompt: string;
35
- readonly schema: TSchema;
36
- readonly model: string;
37
- readonly signal?: AbortSignal;
38
- }
39
- export type StructuredLlmResult<TSchema extends z.ZodType> = {
40
- readonly kind: "ok";
41
- readonly value: z.infer<TSchema>;
42
- } | {
43
- readonly kind: "budget-exceeded";
44
- readonly sentinel: typeof BUDGET_EXCEEDED_SENTINEL;
45
- };
46
- /**
47
- * Thrown when the model returned a structured-output payload that does
48
- * not parse against the declared Zod schema. The caller decides whether
49
- * to wrap as `coverage: "queried_no_data"` or rethrow as a hard failure.
50
- */
51
- export declare class StructuredOutputParseError extends Error {
52
- readonly cause?: unknown | undefined;
53
- constructor(message: string, cause?: unknown | undefined);
54
- }
55
- /**
56
- * Run one structured-output LLM call. Forces the model to call the
57
- * `submit` tool whose input schema is `opts.schema`; parses the tool
58
- * input via `safeParse` and returns the typed value. A billing-gateway 402 returns
59
- * the budget-exceeded sentinel; non-402 throws + parse errors throw.
60
- */
61
- export declare function structuredLlmCall<TSchema extends z.ZodType>(opts: StructuredLlmCallOptions<TSchema>): Promise<StructuredLlmResult<TSchema>>;
@@ -1,102 +0,0 @@
1
- /**
2
- * Structured-output LLM helper for the target-assessment DBOS workflow.
3
- *
4
- * Uses a tool-choice forcing pattern: a single Anthropic tool named `submit`
5
- * carries the Zod schema as its `input_schema`, and
6
- * `tool_choice: { type: "tool", name: "submit" }` forces the model to fill
7
- * it. The tool's `input` IS the structured output.
8
- *
9
- * This intentionally does NOT pull in the harness `runAgent` loop — Phase-2
10
- * decisions are zero-tool single-shot calls. Phase-5 syntheses today use
11
- * regulatory-guidance + approval-precedent retrieval tools; the harness
12
- * port drops those grounding tools (PR #4 ships single-shot synthesis; a
13
- * follow-up may reintroduce the retrieval tools through the agent loop).
14
- *
15
- * Calls are routed through `runLlmStep`, so each LLM call lives in its own
16
- * `DBOS.runStep({name})` cache slot and a billing-gateway 402 self-cancels the
17
- * workflow rather than returning a coverage envelope.
18
- */
19
- import { jsonSchema, tool as aiTool } from "ai";
20
- import { z } from "zod";
21
- import { runLlmStep } from "./llm-step.js";
22
- const SUBMIT_TOOL_NAME = "submit";
23
- /**
24
- * Thrown when the model returned a structured-output payload that does
25
- * not parse against the declared Zod schema. The caller decides whether
26
- * to wrap as `coverage: "queried_no_data"` or rethrow as a hard failure.
27
- */
28
- export class StructuredOutputParseError extends Error {
29
- cause;
30
- constructor(message, cause) {
31
- super(message);
32
- this.cause = cause;
33
- this.name = "StructuredOutputParseError";
34
- }
35
- }
36
- /**
37
- * Convert a Zod schema to the JSON Schema shape Anthropic accepts as
38
- * `tool.input_schema`. Strips `$schema` (Anthropic rejects it) and asserts
39
- * a top-level `"type": "object"` (Anthropic rejects oneOf at the root).
40
- */
41
- function schemaToJsonSchema(schema, stepName) {
42
- const jsonSchema = z.toJSONSchema(schema);
43
- delete jsonSchema.$schema;
44
- if (jsonSchema.type !== "object") {
45
- throw new Error(`structuredLlmCall(${stepName}): schema must emit top-level "type":"object", ` + `got ${JSON.stringify(jsonSchema.type)}`);
46
- }
47
- // `z.toJSONSchema()` returns an untyped JSON-schema object; it is structurally
48
- // Anthropic's `input_schema` once the top-level `type:"object"` check above passes
49
- // (the one shape Anthropic requires), which is the invariant making this cast safe.
50
- return jsonSchema;
51
- }
52
- function extractToolUseInput(blocks) {
53
- for (const block of blocks) {
54
- const part = block;
55
- if (part.type === "tool-call" && part.toolName === SUBMIT_TOOL_NAME) {
56
- return part.input;
57
- }
58
- }
59
- return undefined;
60
- }
61
- /**
62
- * Run one structured-output LLM call. Forces the model to call the
63
- * `submit` tool whose input schema is `opts.schema`; parses the tool
64
- * input via `safeParse` and returns the typed value. A billing-gateway 402 returns
65
- * the budget-exceeded sentinel; non-402 throws + parse errors throw.
66
- */
67
- export async function structuredLlmCall(opts) {
68
- const inputSchema = schemaToJsonSchema(opts.schema, opts.stepName);
69
- const req = {
70
- system: opts.system,
71
- messages: [{ role: "user", content: opts.prompt }],
72
- tools: {
73
- [SUBMIT_TOOL_NAME]: aiTool({
74
- description: `Submit the structured output. Call this tool exactly once with the final answer.`,
75
- inputSchema: jsonSchema(inputSchema),
76
- }),
77
- },
78
- toolChoice: { type: "tool", toolName: SUBMIT_TOOL_NAME },
79
- };
80
- const result = await runLlmStep({
81
- ...(opts.logger ? { logger: opts.logger } : {}),
82
- stepName: opts.stepName,
83
- agentId: opts.agentId,
84
- provider: opts.provider,
85
- req,
86
- session: opts.session,
87
- signal: opts.signal,
88
- });
89
- if (result.kind === "budget-exceeded") {
90
- return result;
91
- }
92
- const content = result.response.message.content;
93
- const raw = extractToolUseInput(Array.isArray(content) ? content : []);
94
- if (raw === undefined) {
95
- throw new StructuredOutputParseError(`structuredLlmCall(${opts.stepName}): model returned no submit tool_use block`);
96
- }
97
- const parsed = opts.schema.safeParse(raw);
98
- if (!parsed.success) {
99
- throw new StructuredOutputParseError(`structuredLlmCall(${opts.stepName}): submit payload failed schema validation: ${parsed.error.message}`, parsed.error);
100
- }
101
- return { kind: "ok", value: parsed.data };
102
- }