@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.
- package/dist/auth/types.d.ts +5 -14
- package/dist/auth/types.js +3 -10
- package/dist/billing/usage-recorder.d.ts +1 -1
- package/dist/contracts/analogy-report.d.ts +1 -1
- package/dist/contracts/analogy-report.js +1 -1
- package/dist/contracts/chat-parts.d.ts +2 -3
- package/dist/contracts/index.d.ts +0 -4
- package/dist/contracts/index.js +0 -2
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -21
- package/dist/lib/metrics.d.ts +1 -1
- package/dist/lib/otel-spans.d.ts +0 -2
- package/dist/lib/otel-spans.js +0 -2
- package/dist/loop/run-agent.d.ts +1 -1
- package/dist/runtime/assemble.d.ts +0 -3
- package/dist/runtime/assemble.js +0 -3
- package/dist/state/index.d.ts +0 -2
- package/dist/state/index.js +0 -1
- package/dist/state/init.js +1 -60
- package/dist/state/purge-analysis.d.ts +1 -2
- package/dist/state/purge-analysis.js +1 -2
- package/dist/tasks/regulatory-corpus-boot-refresh.js +1 -1
- package/dist/tools/bio/gene-preclinical-profile.d.ts +1 -3
- package/dist/tools/bio/gene-preclinical-profile.js +1 -3
- package/dist/tools/lib/chembl-client.d.ts +1 -4
- package/dist/tools/lib/chembl-client.js +1 -4
- package/dist/tools/lib/clinical-benchmarks-client.d.ts +1 -1
- package/dist/tools/lib/clinical-benchmarks-client.js +1 -1
- package/dist/tools/lib/clinical-trials-client.d.ts +0 -3
- package/dist/tools/lib/clinical-trials-client.js +0 -3
- package/dist/tools/lib/identifier-resolver.d.ts +5 -7
- package/dist/tools/lib/identifier-resolver.js +5 -7
- package/dist/tools/lib/iuphar-client.d.ts +1 -3
- package/dist/tools/lib/iuphar-client.js +1 -3
- package/dist/tools/lib/opentargets-client.d.ts +1 -1
- package/dist/tools/lib/opentargets-client.js +1 -1
- package/dist/tools/lib/protein-family-normalize.d.ts +5 -7
- package/dist/tools/lib/protein-family-normalize.js +5 -7
- package/dist/tools/lib/uniprot-client.d.ts +0 -7
- package/dist/tools/lib/uniprot-client.js +2 -9
- package/package.json +1 -1
- package/dist/contracts/target-assessment-row.d.ts +0 -85
- package/dist/contracts/target-assessment-row.js +0 -37
- package/dist/contracts/target-dossier.d.ts +0 -12423
- package/dist/contracts/target-dossier.js +0 -1282
- package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.js +0 -85
- package/dist/prompts/target-assessment/briefs/index.d.ts +0 -4
- package/dist/prompts/target-assessment/briefs/index.js +0 -4
- package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/liability-bullets.js +0 -114
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +0 -37
- package/dist/prompts/target-assessment/briefs/translational-commentary.d.ts +0 -1
- package/dist/prompts/target-assessment/briefs/translational-commentary.js +0 -23
- package/dist/prompts/target-assessment/drugs-in-class.d.ts +0 -1
- package/dist/prompts/target-assessment/drugs-in-class.js +0 -29
- package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +0 -1
- package/dist/prompts/target-assessment/investigation/adversarial-critique.js +0 -67
- package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +0 -1
- package/dist/prompts/target-assessment/investigation/claim-reverification.js +0 -49
- package/dist/prompts/target-assessment/investigation/index.d.ts +0 -3
- package/dist/prompts/target-assessment/investigation/index.js +0 -3
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +0 -1
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +0 -49
- package/dist/prompts/target-assessment/modulator-triage.d.ts +0 -1
- package/dist/prompts/target-assessment/modulator-triage.js +0 -31
- package/dist/prompts/target-assessment/off-target-annotator.d.ts +0 -1
- package/dist/prompts/target-assessment/off-target-annotator.js +0 -28
- package/dist/prompts/target-assessment/tox-voice/core.d.ts +0 -1
- package/dist/prompts/target-assessment/tox-voice/core.js +0 -74
- package/dist/prompts/target-assessment/tox-voice/critique.d.ts +0 -7
- package/dist/prompts/target-assessment/tox-voice/critique.js +0 -109
- package/dist/prompts/target-assessment/tox-voice/exemplars.d.ts +0 -8
- package/dist/prompts/target-assessment/tox-voice/exemplars.js +0 -24
- package/dist/prompts/target-assessment/tox-voice/index.d.ts +0 -4
- package/dist/prompts/target-assessment/tox-voice/index.js +0 -4
- package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +0 -18
- package/dist/prompts/target-assessment/tox-voice/vocabulary.js +0 -61
- package/dist/state/target-assessments.d.ts +0 -68
- package/dist/state/target-assessments.js +0 -222
- package/dist/workflows/execute-target-assessment.d.ts +0 -118
- package/dist/workflows/execute-target-assessment.js +0 -631
- package/dist/workflows/target-assessment/assemblers/index.d.ts +0 -5
- package/dist/workflows/target-assessment/assemblers/index.js +0 -4
- package/dist/workflows/target-assessment/assemblers/literature.d.ts +0 -281
- package/dist/workflows/target-assessment/assemblers/literature.js +0 -971
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +0 -118
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +0 -945
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +0 -374
- package/dist/workflows/target-assessment/assemblers/safety.js +0 -1068
- package/dist/workflows/target-assessment/assemblers/trials.d.ts +0 -30
- package/dist/workflows/target-assessment/assemblers/trials.js +0 -75
- package/dist/workflows/target-assessment/collectors/index.d.ts +0 -114
- package/dist/workflows/target-assessment/collectors/index.js +0 -538
- package/dist/workflows/target-assessment/coverage.d.ts +0 -97
- package/dist/workflows/target-assessment/coverage.js +0 -86
- package/dist/workflows/target-assessment/decisions/index.d.ts +0 -86
- package/dist/workflows/target-assessment/decisions/index.js +0 -127
- package/dist/workflows/target-assessment/fanout/index.d.ts +0 -32
- package/dist/workflows/target-assessment/fanout/index.js +0 -156
- package/dist/workflows/target-assessment/investigation/index.d.ts +0 -97
- package/dist/workflows/target-assessment/investigation/index.js +0 -419
- package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +0 -73
- package/dist/workflows/target-assessment/lib/approval-precedents.js +0 -185
- package/dist/workflows/target-assessment/lib/citation-validator.d.ts +0 -46
- package/dist/workflows/target-assessment/lib/citation-validator.js +0 -316
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +0 -65
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +0 -174
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +0 -44
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -152
- package/dist/workflows/target-assessment/lib/compute-derived.d.ts +0 -20
- package/dist/workflows/target-assessment/lib/compute-derived.js +0 -261
- package/dist/workflows/target-assessment/lib/compute-selectivity.d.ts +0 -15
- package/dist/workflows/target-assessment/lib/compute-selectivity.js +0 -18
- package/dist/workflows/target-assessment/lib/dedup-modulators.d.ts +0 -37
- package/dist/workflows/target-assessment/lib/dedup-modulators.js +0 -157
- package/dist/workflows/target-assessment/lib/evidence-conflict-detector.d.ts +0 -17
- package/dist/workflows/target-assessment/lib/evidence-conflict-detector.js +0 -100
- package/dist/workflows/target-assessment/lib/expression-constants.d.ts +0 -21
- package/dist/workflows/target-assessment/lib/expression-constants.js +0 -21
- package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +0 -20
- package/dist/workflows/target-assessment/lib/family-complex-supplement.js +0 -36
- package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +0 -72
- package/dist/workflows/target-assessment/lib/fda-label-safety.js +0 -166
- package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -30
- package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -44
- package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +0 -34
- package/dist/workflows/target-assessment/lib/hpo-organ-map.js +0 -63
- package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +0 -19
- package/dist/workflows/target-assessment/lib/impc-organ-map.js +0 -39
- package/dist/workflows/target-assessment/lib/intended-polypharm-filter.d.ts +0 -4
- package/dist/workflows/target-assessment/lib/intended-polypharm-filter.js +0 -24
- package/dist/workflows/target-assessment/lib/llm-step.d.ts +0 -80
- package/dist/workflows/target-assessment/lib/llm-step.js +0 -101
- package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +0 -40
- package/dist/workflows/target-assessment/lib/meddra-organ-map.js +0 -138
- package/dist/workflows/target-assessment/lib/recommendation-quality-gates.d.ts +0 -17
- package/dist/workflows/target-assessment/lib/recommendation-quality-gates.js +0 -99
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.d.ts +0 -2
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +0 -51
- package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +0 -25
- package/dist/workflows/target-assessment/lib/regulatory-actions.js +0 -176
- package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +0 -44
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +0 -273
- package/dist/workflows/target-assessment/lib/structured-llm.d.ts +0 -61
- package/dist/workflows/target-assessment/lib/structured-llm.js +0 -102
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +0 -59
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +0 -95
- package/dist/workflows/target-assessment/lib/therapeutic-programs.d.ts +0 -20
- package/dist/workflows/target-assessment/lib/therapeutic-programs.js +0 -68
- package/dist/workflows/target-assessment/metrics.d.ts +0 -16
- package/dist/workflows/target-assessment/metrics.js +0 -30
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +0 -3182
- package/dist/workflows/target-assessment/phase4-assemble.js +0 -39
- package/dist/workflows/target-assessment/phase5-persist.d.ts +0 -59
- package/dist/workflows/target-assessment/phase5-persist.js +0 -349
- package/dist/workflows/target-assessment/progress-stream.d.ts +0 -75
- package/dist/workflows/target-assessment/progress-stream.js +0 -110
- package/dist/workflows/target-assessment/progress.d.ts +0 -39
- package/dist/workflows/target-assessment/progress.js +0 -77
- package/dist/workflows/target-assessment/schemas.d.ts +0 -970
- package/dist/workflows/target-assessment/schemas.js +0 -318
- package/dist/workflows/target-assessment/steps/collectors/pathways.d.ts +0 -15
- package/dist/workflows/target-assessment/steps/collectors/pathways.js +0 -58
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.d.ts +0 -34
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-class-drug-step.js +0 -31
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.d.ts +0 -57
- package/dist/workflows/target-assessment/steps/fanout/aes-for-one-trial-step.js +0 -54
- package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.d.ts +0 -35
- package/dist/workflows/target-assessment/steps/fanout/faers-for-one-modulator-step.js +0 -32
- package/dist/workflows/target-assessment/steps/fanout/index.d.ts +0 -14
- package/dist/workflows/target-assessment/steps/fanout/index.js +0 -10
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +0 -29
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +0 -29
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +0 -657
- package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -34
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +0 -806
- package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -16
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -28
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -27
- package/dist/workflows/target-assessment/sub-workflows/index.d.ts +0 -15
- package/dist/workflows/target-assessment/sub-workflows/index.js +0 -11
- package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.d.ts +0 -41
- package/dist/workflows/target-assessment/sub-workflows/per-class-drug-aes.js +0 -12
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.d.ts +0 -41
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-faers.js +0 -12
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +0 -37
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.js +0 -12
- package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.d.ts +0 -66
- package/dist/workflows/target-assessment/sub-workflows/per-trial-aes.js +0 -12
- package/dist/workflows/target-assessment/synthesis/index.d.ts +0 -607
- package/dist/workflows/target-assessment/synthesis/index.js +0 -354
- package/dist/workflows/target-assessment/synthesis-probes.d.ts +0 -56
- 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
|
-
}
|