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