@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,320 +0,0 @@
1
- /**
2
- * Synthesis content probes — deterministic floor on Phase-5 LLM output
3
- * depth.
4
- *
5
- * Each per-section synthesis step runs its agent's structured output
6
- * through the relevant probe before accepting it. A failed probe
7
- * triggers one bounded retry with a critique appended; a second failure
8
- * marks the section `coverage: "queried_no_data"` with
9
- * `error.kind: "synthesis-too-shallow"` (the workflow proceeds — the
10
- * dossier still persists with the rest of the data intact).
11
- *
12
- * Probes are deliberately deterministic regex/length checks — not
13
- * LLM-as-judge. The prompts already require evidence citation; the
14
- * probes are a regression-prevention floor for the rare LLM regression
15
- * that produces fragment-length output anyway.
16
- *
17
- * Threshold tuning: the constants block at the top of this file is the
18
- * single source of truth. Adjust there, never inline.
19
- */
20
- import { buildVoiceCritique } from "../../prompts/target-assessment/tox-voice/index.js";
21
- // ── Tunable thresholds ───────────────────────────────────────────────
22
- export const PROBE_THRESHOLDS = {
23
- recommendation: {
24
- rationaleMinChars: 600,
25
- rationaleMinCharsRelaxed: 200,
26
- requiredSectionRefs: 3,
27
- requiredKeyItems: 1,
28
- },
29
- liabilityBullet: {
30
- rationaleMinChars: 80,
31
- requiresSourceOrCounter: true,
32
- },
33
- safetyFlag: {
34
- trailMinChars: 100,
35
- requiredSourceRefs: 2,
36
- },
37
- translationalCommentary: {
38
- commentaryMinChars: 120,
39
- },
40
- };
41
- // ── Helpers: substring matching ──────────────────────────────────────
42
- const KNOWN_SECTION_NAMES = [
43
- "safety_profile",
44
- "tractability",
45
- "indications",
46
- "clinical_development",
47
- "reference_biology.preclinical",
48
- "reference_biology",
49
- "analytics.translational_chain",
50
- "off_target_panel",
51
- "off_tissue_risk",
52
- "drug_interactions",
53
- "liability_summary",
54
- "summary",
55
- ];
56
- const KNOWN_SOURCE_NAMES = ["FAERS", "trial AEs", "off-target panel", "class precedent"];
57
- export function countSectionReferences(text) {
58
- const lower = text.toLowerCase();
59
- const matched = new Set();
60
- for (const name of KNOWN_SECTION_NAMES) {
61
- if (lower.includes(name.toLowerCase())) {
62
- matched.add(name);
63
- }
64
- }
65
- // Treat `reference_biology.preclinical` as more specific than
66
- // `reference_biology`; if both match, only count the more specific.
67
- if (matched.has("reference_biology.preclinical")) {
68
- matched.delete("reference_biology");
69
- }
70
- return { matchedSections: [...matched] };
71
- }
72
- export function countSourceReferences(text) {
73
- const lower = text.toLowerCase();
74
- const matched = new Set();
75
- for (const name of KNOWN_SOURCE_NAMES) {
76
- if (lower.includes(name.toLowerCase()))
77
- matched.add(name);
78
- }
79
- return { matchedSources: [...matched] };
80
- }
81
- const NUMERIC_COUNTER_RE = /\b\d+(?:\.\d+)?(?:\s*(?:%|µm|um|fold|drugs?|reports?|signals?|trials?|tissues?|sources?|years?))?/i;
82
- function hasNumericCounter(text) {
83
- return NUMERIC_COUNTER_RE.test(text);
84
- }
85
- // ── Numeric-claim verifier ───────────────────────────────────────────
86
- // Matches a "≥/>= N nTPM in M (distinct) tissues" count claim. The GLP1R
87
- // dossier review found a bullet asserting "≥10 nTPM in 14 distinct tissues"
88
- // when normal_tissue_expression actually held 23 — a class of miscount the
89
- // length/source probes cannot catch.
90
- const TISSUE_COUNT_CLAIM_RE = /(?:≥|>=)\s*(\d+(?:\.\d+)?)\s*ntpm\s+in\s+(\d+)\s+(?:distinct\s+)?tissues/i;
91
- /**
92
- * Verify a bullet's "≥N nTPM in M tissues" claim against
93
- * `reference_biology.normal_tissue_expression`. Returns a critique string
94
- * on mismatch, or `null` when there is no such claim, no dossier, or the
95
- * expression section is not available (nothing to verify against).
96
- */
97
- function verifyTissueCountClaim(text, dossier) {
98
- if (!dossier)
99
- return null;
100
- const nte = dossier.reference_biology?.normal_tissue_expression;
101
- if (!nte || nte.coverage !== "available")
102
- return null;
103
- // Raw GTEx `tpm` values are not on the normalized nTPM scale a "≥N nTPM"
104
- // bullet claim refers to — comparing them would be meaningless. `ntpm` and
105
- // `consensus_normalized` are both nTPM-scale and ARE verified.
106
- if (nte.data.unit === "tpm")
107
- return null;
108
- const m = text.match(TISSUE_COUNT_CLAIM_RE);
109
- if (!m)
110
- return null;
111
- const threshold = parseFloat(m[1]);
112
- const claimed = parseInt(m[2], 10);
113
- const actual = nte.data.rows.filter((r) => typeof r.value === "number" && r.value >= threshold).length;
114
- if (claimed === actual)
115
- return null;
116
- return (`Bullet claims "${m[0]}" but normal_tissue_expression has ${actual} ` +
117
- `tissue(s) with value ≥ ${threshold} nTPM, not ${claimed}. ` +
118
- `Correct the count to ${actual}.`);
119
- }
120
- // ── Voice composition helper ─────────────────────────────────────────
121
- /**
122
- * Compose a base probe result with a voice check.
123
- *
124
- * Length fails are hard — a too-short output cannot be redeemed by
125
- * good voice. All other fails (source, section-refs, key-items) are
126
- * overridable: a banned-phrase violation is reported in preference to
127
- * an incomplete source citation, because voice is the narrower and
128
- * more actionable failure for the retry prompt.
129
- *
130
- * When the base is a clean pass, voice can still flip the verdict to
131
- * "fail-voice" (banned phrases / wrong register) or downgrade to
132
- * "relaxed" (borderline passive ratio).
133
- */
134
- function composeWithVoice(base, text, section) {
135
- if (base.verdict === "fail-length") {
136
- // A too-short output cannot be redeemed by good voice, so the verdict
137
- // stays fail-length. But surface any voice issues in the critique too —
138
- // the single bounded retry should be able to fix length AND voice in
139
- // one pass, not discover the voice problem only on its final attempt.
140
- const voice = buildVoiceCritique(text, section);
141
- if (voice.verdict === "fail-voice" && voice.critique) {
142
- return {
143
- ...base,
144
- critique: `${base.critique ?? ""}\n\nAdditionally: ${voice.critique}`,
145
- };
146
- }
147
- return base;
148
- }
149
- const voice = buildVoiceCritique(text, section);
150
- if (voice.verdict === "fail-voice") {
151
- return {
152
- verdict: "fail-voice",
153
- critique: voice.critique,
154
- output_chars: base.output_chars,
155
- };
156
- }
157
- if (voice.verdict === "relaxed" && base.verdict === "pass") {
158
- return {
159
- verdict: "relaxed",
160
- critique: voice.critique,
161
- output_chars: base.output_chars,
162
- };
163
- }
164
- return base;
165
- }
166
- // ── Recommendation probe ─────────────────────────────────────────────
167
- function recommendationCoverageRelaxed(dossier) {
168
- // Relax probe rules when the dossier is genuinely thin — at least two
169
- // of the three "primary" evidence streams must be unavailable.
170
- const probes = [
171
- dossier.safety_profile.organ_rollup.coverage !== "available",
172
- dossier.clinical_development.trials.coverage !== "available",
173
- dossier.reference_biology.preclinical.data_coverage.ko !== "available",
174
- ];
175
- return probes.filter(Boolean).length >= 2;
176
- }
177
- export function probeRecommendation(out, dossier) {
178
- const t = PROBE_THRESHOLDS.recommendation;
179
- const len = out.rationale.length;
180
- const relaxed = recommendationCoverageRelaxed(dossier);
181
- // Build the prose payload up-front so the voice probe runs on every
182
- // path — including the fail-length branch. Banned phrases and
183
- // colloquial organ-system references have no data-coverage dependency
184
- // and must be caught regardless of how thin the upstream evidence is.
185
- const recommendationText = [out.rationale, out.key_strengths.join(" "), out.key_risks.join(" "), out.modality_choice.rationale].join("\n");
186
- // Length floor (minimum applies even when relaxed).
187
- const minLen = relaxed ? t.rationaleMinCharsRelaxed : t.rationaleMinChars;
188
- if (len < minLen) {
189
- return composeWithVoice({
190
- verdict: "fail-length",
191
- critique: `Your prior rationale was ${len} characters; rewrite to ≥ ${minLen} characters integrating the dossier's evidence.`,
192
- output_chars: len,
193
- }, recommendationText, "executive-recommendation");
194
- }
195
- if (relaxed) {
196
- // Coverage is thin: do not require section-reference count or
197
- // key-strengths/risks. Voice probe still applies. Returns "relaxed"
198
- // when voice is clean so diagnostics reflect that the strict rules
199
- // were not applied.
200
- return composeWithVoice({ verdict: "relaxed", critique: null, output_chars: len }, recommendationText, "executive-recommendation");
201
- }
202
- // Section-reference and key-item content fails are routed through
203
- // composeWithVoice so a banned-phrase / colloquial-organ violation can
204
- // override them. Voice fails are narrower and more actionable for the
205
- // single retry budget — getting both issues into one critique is the
206
- // difference between a recoverable section and a permanent
207
- // queried_no_data drop.
208
- const { matchedSections } = countSectionReferences(out.rationale);
209
- if (matchedSections.length < t.requiredSectionRefs) {
210
- return composeWithVoice({
211
- verdict: "fail-section-references",
212
- critique: `Your rationale referenced ${matchedSections.length} dossier section(s) (${matchedSections.join(", ") || "none"}); rewrite to cite ≥ ${t.requiredSectionRefs} of: safety_profile, tractability, indications, clinical_development, reference_biology.preclinical, analytics.translational_chain, off_target_panel.`,
213
- output_chars: len,
214
- }, recommendationText, "executive-recommendation");
215
- }
216
- if (out.key_strengths.length < t.requiredKeyItems || out.key_risks.length < t.requiredKeyItems) {
217
- return composeWithVoice({
218
- verdict: "fail-key-strengths-risks",
219
- critique: `key_strengths (${out.key_strengths.length}) and key_risks (${out.key_risks.length}) must each have ≥ ${t.requiredKeyItems} entry when overall data coverage allows it.`,
220
- output_chars: len,
221
- }, recommendationText, "executive-recommendation");
222
- }
223
- return composeWithVoice({ verdict: "pass", critique: null, output_chars: len }, recommendationText, "executive-recommendation");
224
- }
225
- // ── Liability bullet probe ───────────────────────────────────────────
226
- export function probeLiabilityBullets(out, dossier) {
227
- const t = PROBE_THRESHOLDS.liabilityBullet;
228
- if (out.bullets.length === 0) {
229
- // An empty bullets array is acceptable when `notes` discloses why.
230
- if (out.notes.length >= 60) {
231
- return { verdict: "pass", critique: null, output_chars: out.notes.length };
232
- }
233
- return {
234
- verdict: "fail-length",
235
- critique: `Empty bullets array with insufficient disclosure note (${out.notes.length} chars). Either populate at least one bullet or expand notes to ≥ 60 chars explaining the data picture.`,
236
- output_chars: out.notes.length,
237
- };
238
- }
239
- const totalChars = out.bullets.reduce((acc, b) => acc + b.rationale.length, 0);
240
- const bulletText = out.bullets.map((b) => `${b.text}\n${b.rationale}`).join("\n");
241
- for (const b of out.bullets) {
242
- if (b.rationale.length < t.rationaleMinChars) {
243
- return composeWithVoice({
244
- verdict: "fail-length",
245
- critique: `Bullet "${b.organ_or_axis}" rationale is ${b.rationale.length} chars; rewrite to ≥ ${t.rationaleMinChars} chars citing a numeric counter or a source by name.`,
246
- output_chars: totalChars,
247
- }, bulletText, "liability-bullets");
248
- }
249
- if (t.requiresSourceOrCounter) {
250
- const { matchedSources } = countSourceReferences(b.rationale);
251
- const counter = hasNumericCounter(b.rationale);
252
- if (matchedSources.length === 0 && !counter) {
253
- const sourceFailBase = {
254
- verdict: "fail-source-references",
255
- critique: `Bullet "${b.organ_or_axis}" rationale cites no source (FAERS, trial AEs, off-target panel, class precedent) and no numeric counter; rewrite citing one or both.`,
256
- output_chars: totalChars,
257
- };
258
- return composeWithVoice(sourceFailBase, bulletText, "liability-bullets");
259
- }
260
- }
261
- }
262
- for (const b of out.bullets) {
263
- const numericCritique = verifyTissueCountClaim(b.text, dossier);
264
- if (numericCritique) {
265
- return {
266
- verdict: "fail-numeric-claim",
267
- critique: numericCritique,
268
- output_chars: totalChars,
269
- };
270
- }
271
- }
272
- return composeWithVoice({ verdict: "pass", critique: null, output_chars: totalChars }, bulletText, "liability-bullets");
273
- }
274
- // ── Safety flag probe ────────────────────────────────────────────────
275
- export function probeSafetyFlags(out) {
276
- const t = PROBE_THRESHOLDS.safetyFlag;
277
- if (out.flags.length === 0) {
278
- return { verdict: "pass", critique: null, output_chars: 0 };
279
- }
280
- const totalChars = out.flags.reduce((acc, f) => acc + f.trail.length, 0);
281
- const flagText = out.flags.map((f) => `${f.organ}: ${f.trail} ${f.mechanism_hypothesis ?? ""}`).join("\n");
282
- for (const f of out.flags) {
283
- if (f.trail.length < t.trailMinChars) {
284
- return composeWithVoice({
285
- verdict: "fail-length",
286
- critique: `Flag for "${f.organ}" trail is ${f.trail.length} chars; rewrite to ≥ ${t.trailMinChars} chars citing specific source counts.`,
287
- output_chars: totalChars,
288
- }, flagText, "target-organ-liabilities");
289
- }
290
- const { matchedSources } = countSourceReferences(f.trail);
291
- if (matchedSources.length < t.requiredSourceRefs) {
292
- const sourceFailBase = {
293
- verdict: "fail-source-references",
294
- critique: `Flag for "${f.organ}" cites ${matchedSources.length} source(s) (${matchedSources.join(", ") || "none"}); rewrite citing ≥ ${t.requiredSourceRefs} of: FAERS, trial AEs, off-target panel, class precedent.`,
295
- output_chars: totalChars,
296
- };
297
- return composeWithVoice(sourceFailBase, flagText, "target-organ-liabilities");
298
- }
299
- }
300
- return composeWithVoice({ verdict: "pass", critique: null, output_chars: totalChars }, flagText, "target-organ-liabilities");
301
- }
302
- // ── Translational commentary probe ───────────────────────────────────
303
- export function probeTranslationalCommentary(out) {
304
- const t = PROBE_THRESHOLDS.translationalCommentary;
305
- if (out.rows.length === 0) {
306
- return { verdict: "pass", critique: null, output_chars: 0 };
307
- }
308
- const totalChars = out.rows.reduce((acc, r) => acc + r.commentary.length, 0);
309
- const commentaryText = out.rows.map((r) => `${r.predicate} ${r.commentary}`).join("\n");
310
- for (const r of out.rows) {
311
- if (r.commentary.length < t.commentaryMinChars) {
312
- return composeWithVoice({
313
- verdict: "fail-length",
314
- critique: `Row "${r.topic}" commentary is ${r.commentary.length} chars; rewrite to ≥ ${t.commentaryMinChars} chars bridging preclinical observation to human prediction.`,
315
- output_chars: totalChars,
316
- }, commentaryText, "translational-commentary");
317
- }
318
- }
319
- return composeWithVoice({ verdict: "pass", critique: null, output_chars: totalChars }, commentaryText, "translational-commentary");
320
- }