@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,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The IMPC top-level-phenotype vocabulary onto the canonical organ vocabulary.
|
|
3
|
-
*
|
|
4
|
-
* Buckets with no organ system to land in (hearing, growth, mortality,
|
|
5
|
-
* craniofacial) are absent: a phenotype is reported under the organ it affects
|
|
6
|
-
* or not at all, never under an approximate neighbour. The map's values are the
|
|
7
|
-
* canonical tokens, so it declares no enumeration of its own.
|
|
8
|
-
*
|
|
9
|
-
* The endocrine bucket is absent for the same reason: IMPC's
|
|
10
|
-
* "endocrine/exocrine gland phenotype" spans every endocrine organ and the
|
|
11
|
-
* exocrine glands besides, while the only endocrine token names the thyroid.
|
|
12
|
-
* A knockout in that bucket names no canonical organ, so it is dropped and
|
|
13
|
-
* counted rather than read as a thyroid finding.
|
|
14
|
-
*/
|
|
15
|
-
const IMPC_ORGAN_SYSTEMS = {
|
|
16
|
-
cardiovascular: "cardiac",
|
|
17
|
-
gastrointestinal: "gi",
|
|
18
|
-
hematologic: "hematologic",
|
|
19
|
-
metabolic: "metabolic",
|
|
20
|
-
immune: "immune",
|
|
21
|
-
skin: "dermatologic",
|
|
22
|
-
hepatic: "hepatic",
|
|
23
|
-
musculoskeletal: "musculoskeletal",
|
|
24
|
-
skeleton: "musculoskeletal",
|
|
25
|
-
cns: "cns",
|
|
26
|
-
renal: "renal",
|
|
27
|
-
reproductive: "reproductive",
|
|
28
|
-
respiratory: "respiratory",
|
|
29
|
-
vision: "ocular",
|
|
30
|
-
};
|
|
31
|
-
/** Resolve one IMPC bucket, or null when it denotes no canonical organ. */
|
|
32
|
-
export function resolveImpcOrgan(bucket) {
|
|
33
|
-
return IMPC_ORGAN_SYSTEMS[bucket] ?? null;
|
|
34
|
-
}
|
|
35
|
-
/** Resolve a set of IMPC buckets, dropping those with no canonical organ. */
|
|
36
|
-
export function toOrganSystems(impcOrganSystems) {
|
|
37
|
-
const resolved = impcOrganSystems.map((o) => resolveImpcOrgan(o)).filter((o) => o !== null);
|
|
38
|
-
return [...new Set(resolved)];
|
|
39
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hand-curated catalogue of multi-target peptide agonists. Conservative
|
|
3
|
-
* by design — only well-documented dual/triple agonists where the
|
|
4
|
-
* additional receptor IS the marketed mechanism. Ambiguous cases stay
|
|
5
|
-
* in the off-target panel.
|
|
6
|
-
*
|
|
7
|
-
* Source: ChEMBL triage_rationale strings + class-precedent literature.
|
|
8
|
-
* Extend as new dual/triple agonists appear in chembl-modulators output.
|
|
9
|
-
*/
|
|
10
|
-
const INTENDED_PAIRS = [
|
|
11
|
-
{ modulator: "CHEMBL4297839", coTarget: "CHEMBL4383", reason: "tirzepatide is a dual GLP-1R/GIPR agonist" },
|
|
12
|
-
{ modulator: "CHEMBL4297630", coTarget: "CHEMBL1985", reason: "cotadutide is a dual GLP-1R/GCGR agonist" },
|
|
13
|
-
{ modulator: "CHEMBL5095485", coTarget: "CHEMBL4383", reason: "retatrutide is a triple GLP-1R/GIPR/GCGR agonist" },
|
|
14
|
-
{ modulator: "CHEMBL5095485", coTarget: "CHEMBL1985", reason: "retatrutide is a triple GLP-1R/GIPR/GCGR agonist" },
|
|
15
|
-
{ modulator: "CHEMBL5314776", coTarget: "CHEMBL1985", reason: "survodutide is a dual GLP-1R/GCGR agonist" },
|
|
16
|
-
{ modulator: "CHEMBL4297576", coTarget: "CHEMBL1985", reason: "efinopegdutide is a dual GLP-1R/GCGR agonist" },
|
|
17
|
-
{ modulator: "CHEMBL3990012", coTarget: "CHEMBL1985", reason: "pegapamodutide is a dual GLP-1R/GCGR agonist" },
|
|
18
|
-
];
|
|
19
|
-
export function isIntendedCoTarget(modulatorChemblId, offTargetChemblId) {
|
|
20
|
-
const hit = INTENDED_PAIRS.find((p) => p.modulator === modulatorChemblId && p.coTarget === offTargetChemblId);
|
|
21
|
-
if (hit)
|
|
22
|
-
return { intended: true, reason: hit.reason };
|
|
23
|
-
return { intended: false };
|
|
24
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LLM step wrapper for the target-assessment DBOS workflow body.
|
|
3
|
-
*
|
|
4
|
-
* Each Phase-2 decision and Phase-5 synthesis LLM call goes through
|
|
5
|
-
* `runLlmStep` — it wraps `DBOS.runStep({name})` around the chat provider
|
|
6
|
-
* call and classifies caught errors:
|
|
7
|
-
*
|
|
8
|
-
* - `isBudgetExceeded(err) === true` (billing-gateway 402): self-send a
|
|
9
|
-
* `BUDGET_EXCEEDED_TOPIC` marker addressed to this workflow's own
|
|
10
|
-
* id, then return a sentinel marker. The wrapper does NOT call
|
|
11
|
-
* `DBOS.cancelWorkflow` itself — the workflow body's terminal block
|
|
12
|
-
* reads the marker, writes `status = "suspended_insufficient_funds"`
|
|
13
|
-
* via `DBOS.runStep({name: "ta-terminal-suspended"})`, then issues
|
|
14
|
-
* `DBOS.cancelWorkflow` + a trailing `runStep` to materialise the
|
|
15
|
-
* CANCELLED terminal state. Deferring the cancel keeps the terminal
|
|
16
|
-
* handler's DB writes inside DBOS step boundaries (replay-cached).
|
|
17
|
-
* - anything else: rethrow so the caller wraps the failure as
|
|
18
|
-
* `coverage: "queried_no_data"` with `error.kind: "synthesis-unavailable"`
|
|
19
|
-
* (or `"queried_no_data"` for decisions).
|
|
20
|
-
*
|
|
21
|
-
* The step name is attempt-numbered by the caller — e.g.
|
|
22
|
-
* `"ta-synth:liability-bullets:0"`. On 402 + top-up + `DBOS.resumeWorkflow`
|
|
23
|
-
* the caller bumps the attempt counter so the resumed call lands a fresh
|
|
24
|
-
* DBOS cache slot rather than replaying the cancelled prior attempt.
|
|
25
|
-
*/
|
|
26
|
-
import type { Logger } from "../../../lib/logger.js";
|
|
27
|
-
import type { AgentChat, ChatRequest, ChatResponse } from "../../../providers/types.js";
|
|
28
|
-
import type { AgentSession } from "../../../auth/types.js";
|
|
29
|
-
/**
|
|
30
|
-
* DBOS message topic the LLM step uses to mark the workflow as
|
|
31
|
-
* self-cancelled on a billing-gateway 402. The terminal handler drains the topic
|
|
32
|
-
* to dispatch to `markAssessmentSuspended` rather than the generic
|
|
33
|
-
* operator-cancel handler.
|
|
34
|
-
*/
|
|
35
|
-
export declare const BUDGET_EXCEEDED_TOPIC = "ta-budget-exceeded";
|
|
36
|
-
export interface BudgetExceededMarker {
|
|
37
|
-
readonly stepName: string;
|
|
38
|
-
readonly agentId: string;
|
|
39
|
-
readonly error: string;
|
|
40
|
-
}
|
|
41
|
-
export interface RunLlmStepOptions {
|
|
42
|
-
/** Operational logging seam; omitted falls back to no-op. */
|
|
43
|
-
readonly logger?: Logger;
|
|
44
|
-
/** Durable-step name — already attempt-numbered by the caller. */
|
|
45
|
-
readonly stepName: string;
|
|
46
|
-
/** Agent id stamped in the marker for telemetry. */
|
|
47
|
-
readonly agentId: string;
|
|
48
|
-
readonly provider: AgentChat;
|
|
49
|
-
readonly req: ChatRequest;
|
|
50
|
-
readonly session: AgentSession;
|
|
51
|
-
readonly signal?: AbortSignal;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Sentinel returned (NOT thrown) when the wrapper has self-cancelled the
|
|
55
|
-
* workflow. The next `DBOS.runStep` after this point raises
|
|
56
|
-
* `DBOSWorkflowCancelledError`, so call sites only see this value on the
|
|
57
|
-
* synchronous return path before the cancel materialises. Callers MUST NOT
|
|
58
|
-
* wrap this as `coverage: "queried_no_data"` — the workflow is unwinding.
|
|
59
|
-
*/
|
|
60
|
-
export declare const BUDGET_EXCEEDED_SENTINEL: unique symbol;
|
|
61
|
-
export type RunLlmStepResult = {
|
|
62
|
-
readonly kind: "ok";
|
|
63
|
-
readonly response: ChatResponse;
|
|
64
|
-
} | {
|
|
65
|
-
readonly kind: "budget-exceeded";
|
|
66
|
-
readonly sentinel: typeof BUDGET_EXCEEDED_SENTINEL;
|
|
67
|
-
};
|
|
68
|
-
/**
|
|
69
|
-
* Run one LLM call inside a `DBOS.runStep` boundary. On a billing-gateway 402 the
|
|
70
|
-
* workflow self-cancels and the caller receives a sentinel result; on any
|
|
71
|
-
* other throw the original error rethrows.
|
|
72
|
-
*/
|
|
73
|
-
export declare function runLlmStep(opts: RunLlmStepOptions): Promise<RunLlmStepResult>;
|
|
74
|
-
/**
|
|
75
|
-
* Drain the budget-exceeded topic for this workflow. Called from the
|
|
76
|
-
* terminal handler (after the body has unwound) to decide whether the
|
|
77
|
-
* workflow cancelled because of a 402 or because of an operator-initiated
|
|
78
|
-
* cancel. Returns the first marker (or `null` if none queued).
|
|
79
|
-
*/
|
|
80
|
-
export declare function readBudgetExceededMarker(): Promise<BudgetExceededMarker | null>;
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LLM step wrapper for the target-assessment DBOS workflow body.
|
|
3
|
-
*
|
|
4
|
-
* Each Phase-2 decision and Phase-5 synthesis LLM call goes through
|
|
5
|
-
* `runLlmStep` — it wraps `DBOS.runStep({name})` around the chat provider
|
|
6
|
-
* call and classifies caught errors:
|
|
7
|
-
*
|
|
8
|
-
* - `isBudgetExceeded(err) === true` (billing-gateway 402): self-send a
|
|
9
|
-
* `BUDGET_EXCEEDED_TOPIC` marker addressed to this workflow's own
|
|
10
|
-
* id, then return a sentinel marker. The wrapper does NOT call
|
|
11
|
-
* `DBOS.cancelWorkflow` itself — the workflow body's terminal block
|
|
12
|
-
* reads the marker, writes `status = "suspended_insufficient_funds"`
|
|
13
|
-
* via `DBOS.runStep({name: "ta-terminal-suspended"})`, then issues
|
|
14
|
-
* `DBOS.cancelWorkflow` + a trailing `runStep` to materialise the
|
|
15
|
-
* CANCELLED terminal state. Deferring the cancel keeps the terminal
|
|
16
|
-
* handler's DB writes inside DBOS step boundaries (replay-cached).
|
|
17
|
-
* - anything else: rethrow so the caller wraps the failure as
|
|
18
|
-
* `coverage: "queried_no_data"` with `error.kind: "synthesis-unavailable"`
|
|
19
|
-
* (or `"queried_no_data"` for decisions).
|
|
20
|
-
*
|
|
21
|
-
* The step name is attempt-numbered by the caller — e.g.
|
|
22
|
-
* `"ta-synth:liability-bullets:0"`. On 402 + top-up + `DBOS.resumeWorkflow`
|
|
23
|
-
* the caller bumps the attempt counter so the resumed call lands a fresh
|
|
24
|
-
* DBOS cache slot rather than replaying the cancelled prior attempt.
|
|
25
|
-
*/
|
|
26
|
-
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
27
|
-
import { createNoopLogger } from "../../../lib/console-logger.js";
|
|
28
|
-
import { unwrapOrThrow } from "../../../lib/result.js";
|
|
29
|
-
import { isBudgetExceeded } from "../../../loop/budget-exceeded.js";
|
|
30
|
-
/**
|
|
31
|
-
* DBOS message topic the LLM step uses to mark the workflow as
|
|
32
|
-
* self-cancelled on a billing-gateway 402. The terminal handler drains the topic
|
|
33
|
-
* to dispatch to `markAssessmentSuspended` rather than the generic
|
|
34
|
-
* operator-cancel handler.
|
|
35
|
-
*/
|
|
36
|
-
export const BUDGET_EXCEEDED_TOPIC = "ta-budget-exceeded";
|
|
37
|
-
/**
|
|
38
|
-
* Sentinel returned (NOT thrown) when the wrapper has self-cancelled the
|
|
39
|
-
* workflow. The next `DBOS.runStep` after this point raises
|
|
40
|
-
* `DBOSWorkflowCancelledError`, so call sites only see this value on the
|
|
41
|
-
* synchronous return path before the cancel materialises. Callers MUST NOT
|
|
42
|
-
* wrap this as `coverage: "queried_no_data"` — the workflow is unwinding.
|
|
43
|
-
*/
|
|
44
|
-
export const BUDGET_EXCEEDED_SENTINEL = Symbol.for("ta.budget-exceeded");
|
|
45
|
-
/**
|
|
46
|
-
* Run one LLM call inside a `DBOS.runStep` boundary. On a billing-gateway 402 the
|
|
47
|
-
* workflow self-cancels and the caller receives a sentinel result; on any
|
|
48
|
-
* other throw the original error rethrows.
|
|
49
|
-
*/
|
|
50
|
-
export async function runLlmStep(opts) {
|
|
51
|
-
const { stepName, agentId, provider, req, session, signal } = opts;
|
|
52
|
-
try {
|
|
53
|
-
const response = await DBOS.runStep(async () => unwrapOrThrow(await provider.chat(req, session, signal)), { name: stepName });
|
|
54
|
-
return { kind: "ok", response };
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
if (!isBudgetExceeded(err)) {
|
|
58
|
-
throw err;
|
|
59
|
-
}
|
|
60
|
-
const workflowId = DBOS.workflowID;
|
|
61
|
-
if (!workflowId) {
|
|
62
|
-
// Should not happen — the wrapper is only callable from inside a
|
|
63
|
-
// workflow body. Surface as a generic throw so the caller does not
|
|
64
|
-
// misclassify as coverage failure.
|
|
65
|
-
throw err;
|
|
66
|
-
}
|
|
67
|
-
const marker = {
|
|
68
|
-
stepName,
|
|
69
|
-
agentId,
|
|
70
|
-
error: err instanceof Error ? err.message : String(err),
|
|
71
|
-
};
|
|
72
|
-
// Self-send a marker so the terminal handler's `DBOS.recv(topic, 0)`
|
|
73
|
-
// drain sees it before the body unwinds. DBOS persists the send under
|
|
74
|
-
// (workflowID, function_id), so replay does not duplicate the
|
|
75
|
-
// marker. The send is wrapped in `DBOS.runStep` to own the slot.
|
|
76
|
-
//
|
|
77
|
-
// The workflow is NOT cancelled here — the body propagates the
|
|
78
|
-
// sentinel through phase outputs, the terminal block runs its DB
|
|
79
|
-
// writes inside DBOS step boundaries, and the cancel materialises at
|
|
80
|
-
// the very end via the terminal block's trailing
|
|
81
|
-
// `DBOS.cancelWorkflow` + `DBOS.runStep("self-cancel-budget-exceeded")`.
|
|
82
|
-
try {
|
|
83
|
-
await DBOS.runStep(() => DBOS.send(workflowId, marker, BUDGET_EXCEEDED_TOPIC), { name: `${stepName}:notify-budget-exceeded` });
|
|
84
|
-
}
|
|
85
|
-
catch (sendErr) {
|
|
86
|
-
const logger = (opts.logger ?? createNoopLogger()).named("ta-llm-step");
|
|
87
|
-
logger.warn("notify-budget-exceeded send failed (non-fatal)", { stepName, ...logger.errorFields(sendErr) });
|
|
88
|
-
}
|
|
89
|
-
return { kind: "budget-exceeded", sentinel: BUDGET_EXCEEDED_SENTINEL };
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Drain the budget-exceeded topic for this workflow. Called from the
|
|
94
|
-
* terminal handler (after the body has unwound) to decide whether the
|
|
95
|
-
* workflow cancelled because of a 402 or because of an operator-initiated
|
|
96
|
-
* cancel. Returns the first marker (or `null` if none queued).
|
|
97
|
-
*/
|
|
98
|
-
export async function readBudgetExceededMarker() {
|
|
99
|
-
const msg = await DBOS.recv(BUDGET_EXCEEDED_TOPIC, 0);
|
|
100
|
-
return msg ?? null;
|
|
101
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MedDRA terms and CT.gov system-organ classes onto the canonical organ
|
|
3
|
-
* vocabulary.
|
|
4
|
-
*
|
|
5
|
-
* The values are the canonical tokens, so this module declares no organ
|
|
6
|
-
* enumeration of its own. Every token in the vocabulary is reachable through
|
|
7
|
-
* the rules below, so nothing a producer may legitimately attribute is
|
|
8
|
-
* unreachable by accident.
|
|
9
|
-
*
|
|
10
|
-
* A term resolves onto the organ it names, or onto nothing. A term whose
|
|
11
|
-
* compartment is genuinely ambiguous — "Paraesthesia" and the other nervous
|
|
12
|
-
* system terms that name no compartment, "Adrenal insufficiency" and the other
|
|
13
|
-
* endocrine terms outside the thyroid — matches no rule, so the caller drops
|
|
14
|
-
* and counts it rather than filing it under a neighbour.
|
|
15
|
-
*/
|
|
16
|
-
import type { OrganSystem } from "../../../contracts/organ-system.js";
|
|
17
|
-
export declare function classifyOrgan(meddraTerm: string): OrganSystem | null;
|
|
18
|
-
/**
|
|
19
|
-
* MedDRA SOC (System Organ Class) names as they appear in CT.gov structured
|
|
20
|
-
* AE tables → canonical organ. SOC strings (e.g. "Gastrointestinal disorders")
|
|
21
|
-
* do not contain MedDRA PT stems, so `classifyOrgan` returns null for them —
|
|
22
|
-
* the SOC map is the primary path; `classifyOrgan(term)` is the fallback when
|
|
23
|
-
* the SOC string isn't in the map.
|
|
24
|
-
*
|
|
25
|
-
* A SOC that spans more than one canonical organ is absent: "Nervous system
|
|
26
|
-
* disorders" covers both nervous-system tokens and "Endocrine disorders" is
|
|
27
|
-
* broader than the thyroid, so a row under either resolves by its preferred
|
|
28
|
-
* term or not at all.
|
|
29
|
-
*/
|
|
30
|
-
export declare const TRIAL_AE_ORGAN_MAP: Record<string, OrganSystem>;
|
|
31
|
-
/**
|
|
32
|
-
* Classify a trial AE row by its SOC (preferred) or its individual MedDRA
|
|
33
|
-
* preferred term (fallback). Shared between the Phase-4 assembler's
|
|
34
|
-
* organ_rollup builder and the derived-completeness validator so both
|
|
35
|
-
* agree on which organ a trial AE belongs to.
|
|
36
|
-
*/
|
|
37
|
-
export declare function classifyTrialAe(ae: {
|
|
38
|
-
term?: string | null;
|
|
39
|
-
organ?: string | null;
|
|
40
|
-
}): OrganSystem | null;
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MedDRA terms and CT.gov system-organ classes onto the canonical organ
|
|
3
|
-
* vocabulary.
|
|
4
|
-
*
|
|
5
|
-
* The values are the canonical tokens, so this module declares no organ
|
|
6
|
-
* enumeration of its own. Every token in the vocabulary is reachable through
|
|
7
|
-
* the rules below, so nothing a producer may legitimately attribute is
|
|
8
|
-
* unreachable by accident.
|
|
9
|
-
*
|
|
10
|
-
* A term resolves onto the organ it names, or onto nothing. A term whose
|
|
11
|
-
* compartment is genuinely ambiguous — "Paraesthesia" and the other nervous
|
|
12
|
-
* system terms that name no compartment, "Adrenal insufficiency" and the other
|
|
13
|
-
* endocrine terms outside the thyroid — matches no rule, so the caller drops
|
|
14
|
-
* and counts it rather than filing it under a neighbour.
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* Order matters: more-specific patterns first. GI is matched before
|
|
18
|
-
* "metabolic" because terms like "blood glucose" should land in
|
|
19
|
-
* metabolic, but "nausea" / "vomiting" must not be reclassified by
|
|
20
|
-
* later metabolic glucose rules.
|
|
21
|
-
*
|
|
22
|
-
* Patterns are stem-based (e.g. "nause" matches "NAUSEA"), so trailing
|
|
23
|
-
* \b is omitted — the stem does not end at a word boundary inside the
|
|
24
|
-
* full MedDRA term. A leading \b is kept to prevent mid-word false matches.
|
|
25
|
-
*/
|
|
26
|
-
const RULES = [
|
|
27
|
-
// pancreas (specific) — must come before generic GI
|
|
28
|
-
{ rx: /\bpancreatit|\bamylase|\blipase/i, organ: "pancreas" },
|
|
29
|
-
// thyroid / C-cell. The token names the thyroid, not the endocrine system,
|
|
30
|
-
// so a non-thyroid endocrine term deliberately matches nothing here.
|
|
31
|
-
{ rx: /thyroid|\b(c[-\s]?cell|calcitonin|goit[re]+|medullar[a-z]*\s*carcinoma)/i, organ: "endocrine_thyroid" },
|
|
32
|
-
// gastrointestinal
|
|
33
|
-
{ rx: /\b(nause|vomit|diarrhoe|diarrhea|constipat|ileus|gastropares|abdominal\s*pain|cholecystit|cholelithias|gallbladder|colit|heartburn)/i, organ: "gi" },
|
|
34
|
-
// hepatic — liver/alt/ast forms added; alt/ast pinned to "increased" to avoid
|
|
35
|
-
// over-firing on terms like "ALTERED MENTAL STATUS"
|
|
36
|
-
{
|
|
37
|
-
rx: /\b(hepatit|hepato|jaundic|cholestas|transaminas|bilirubin|liver|alt\s+increased|ast\s+increased|alanine\s+aminotransferase|aspartate\s+aminotransferase)/i,
|
|
38
|
-
organ: "hepatic",
|
|
39
|
-
},
|
|
40
|
-
// renal — haematur/hematur explicitly placed here (above hematologic) to prevent
|
|
41
|
-
// the broad \bhaemat stem from misclassifying HAEMATURIA as hematologic
|
|
42
|
-
{ rx: /\b(renal|kidney|nephro|creatinin|glomerul|dialys|haematur|hematur)/i, organ: "renal" },
|
|
43
|
-
// cardiac — cardi(ac|al|o) covers CARDIOMYOPATHY/CARDIOVASCULAR/CARDIOTOXICITY;
|
|
44
|
-
// bare heart covers HEART FAILURE, HEART RATE INCREASED, etc.
|
|
45
|
-
{ rx: /\b(cardi(?:ac|al|o)|heart|myocard|tachycard|brady\w*card|arrhythm|atrial\s+fibrillat|qt\s+prolong|torsade)/i, organ: "cardiac" },
|
|
46
|
-
// vascular — placed after cardiac so a myocardial event stays cardiac.
|
|
47
|
-
// `thrombos` rather than the `thrombo` stem, which would swallow
|
|
48
|
-
// THROMBOCYTOPENIA; `\bvascul` never matches inside CEREBROVASCULAR.
|
|
49
|
-
{ rx: /\b(hypertens|hypotens|vasculit|vascular|thrombos|thromboemboli|thrombophleb|embolism|aneurysm|varicose|phlebit)/i, organ: "vascular" },
|
|
50
|
-
// metabolic (glucose, weight, lipid)
|
|
51
|
-
// triglyc has no leading \b because it appears mid-word in HYPERTRIGLYCERIDAEMIA
|
|
52
|
-
{ rx: /\b(glucose|glycaemi|hba1c|insulin|weight\s+(?:decreased|increased)|lipid|cholester|dehydration)|triglyc/i, organ: "metabolic" },
|
|
53
|
-
// respiratory — above the neurological rules so RESPIRATORY DEPRESSION is
|
|
54
|
-
// read as a respiratory event rather than by the bare "depression" stem
|
|
55
|
-
{ rx: /\b(nasopharyng|pharyng|bronch|pneumon|dyspnoe|dyspnea|cough|respiratory|sinusit|pulmonary)/i, organ: "respiratory" },
|
|
56
|
-
// hematologic — above the neurological rules for the same reason, so
|
|
57
|
-
// BONE MARROW DEPRESSION is read as haematologic
|
|
58
|
-
{ rx: /\b(haemoglobin|hemoglobin|haematocrit|leuko|neutro|thrombocyt|anaemi|haemat|platelet|marrow)/i, organ: "hematologic" },
|
|
59
|
-
// ocular — above the nerve rules so OPTIC NEUROPATHY stays ocular.
|
|
60
|
-
// `macular` is pinned to its ocular compounds so it never matches
|
|
61
|
-
// MACULOPAPULAR RASH.
|
|
62
|
-
{
|
|
63
|
-
rx: /\b(ocular|ophthalm|retinopath|retinal|glaucoma|cataract|uveit|conjunctivit|blindness|vision|visual|keratit|scleriti|blephariti|eye\s|macular\s+(?:oedema|edema|degenerat)|optic\s+(?:neuropath|neurit|atroph))/i,
|
|
64
|
-
organ: "ocular",
|
|
65
|
-
},
|
|
66
|
-
// peripheral nervous system — a neuropathy is a disease of the peripheral
|
|
67
|
-
// nerves; a central lesion is named by the terms in the cns rule below
|
|
68
|
-
// `neuropath` carries no leading \b so POLYNEUROPATHY and MONONEUROPATHY
|
|
69
|
-
// resolve as well as NEUROPATHY PERIPHERAL
|
|
70
|
-
{ rx: /neuropath|neurit|\b(radiculopath|neuralg|carpal\s+tunnel|guillain|plexopath|peripheral\s+(?:motor|sensory))/i, organ: "pns" },
|
|
71
|
-
// central nervous system
|
|
72
|
-
{
|
|
73
|
-
rx: /\b(headache|dizz|seizure|convuls|suici|depression|anxiet|encephalo|migrain|myelopath|meningit|cerebr|intracranial|somnolen|ataxi|tremor|stroke)/i,
|
|
74
|
-
organ: "cns",
|
|
75
|
-
},
|
|
76
|
-
// dermatologic
|
|
77
|
-
{ rx: /\b(rash|eczem|prurit|urticari|injection\s+site\s+(?:pain|haemorrhag|erythem))/i, organ: "dermatologic" },
|
|
78
|
-
// musculoskeletal — malignant hyperthermia is a skeletal-muscle crisis and
|
|
79
|
-
// is matched here so the malignancy stem below never claims it
|
|
80
|
-
{ rx: /\b(arthralg|myalg|musculoskel|fracture|bone\s+pain|rhabdomyol|malignant\s+hyperthermi)/i, organ: "musculoskeletal" },
|
|
81
|
-
// immune
|
|
82
|
-
{ rx: /\b(hypersensitiv|anaphyl|autoimmune|cytokine\s+release)/i, organ: "immune" },
|
|
83
|
-
// reproductive
|
|
84
|
-
{ rx: /\b(menstrual|menorrhag|ovari|endometr|prostat|erectile)/i, organ: "reproductive" },
|
|
85
|
-
// oncology — last, so a malignancy that names its site is filed under that
|
|
86
|
-
// site and only a term whose content is the malignancy itself lands here
|
|
87
|
-
{ rx: /\b(neoplasm|malignan|carcinoma|sarcoma|lymphoma|leukaemi|leukemi|myeloma|myelodysplas|melanoma|metastas|tumour|tumor|cancer)/i, organ: "oncology" },
|
|
88
|
-
];
|
|
89
|
-
const UNINFORMATIVE_RE = /\b(incorrect\s+dose|off\s+label|drug\s+ineffective|product\s+(?:use|storage|administration)|accidental\s+exposure|inappropriate\s+schedule|exposure\s+via\s+skin|extra\s+dose|wrong\s+technique)\b/i;
|
|
90
|
-
export function classifyOrgan(meddraTerm) {
|
|
91
|
-
if (UNINFORMATIVE_RE.test(meddraTerm))
|
|
92
|
-
return null;
|
|
93
|
-
for (const { rx, organ } of RULES) {
|
|
94
|
-
if (rx.test(meddraTerm))
|
|
95
|
-
return organ;
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* MedDRA SOC (System Organ Class) names as they appear in CT.gov structured
|
|
101
|
-
* AE tables → canonical organ. SOC strings (e.g. "Gastrointestinal disorders")
|
|
102
|
-
* do not contain MedDRA PT stems, so `classifyOrgan` returns null for them —
|
|
103
|
-
* the SOC map is the primary path; `classifyOrgan(term)` is the fallback when
|
|
104
|
-
* the SOC string isn't in the map.
|
|
105
|
-
*
|
|
106
|
-
* A SOC that spans more than one canonical organ is absent: "Nervous system
|
|
107
|
-
* disorders" covers both nervous-system tokens and "Endocrine disorders" is
|
|
108
|
-
* broader than the thyroid, so a row under either resolves by its preferred
|
|
109
|
-
* term or not at all.
|
|
110
|
-
*/
|
|
111
|
-
export const TRIAL_AE_ORGAN_MAP = {
|
|
112
|
-
"Gastrointestinal disorders": "gi",
|
|
113
|
-
"Cardiac disorders": "cardiac",
|
|
114
|
-
"Vascular disorders": "vascular",
|
|
115
|
-
"Hepatobiliary disorders": "hepatic",
|
|
116
|
-
"Renal and urinary disorders": "renal",
|
|
117
|
-
"Respiratory, thoracic and mediastinal disorders": "respiratory",
|
|
118
|
-
"Skin and subcutaneous tissue disorders": "dermatologic",
|
|
119
|
-
"Musculoskeletal and connective tissue disorders": "musculoskeletal",
|
|
120
|
-
"Metabolism and nutrition disorders": "metabolic",
|
|
121
|
-
"Blood and lymphatic system disorders": "hematologic",
|
|
122
|
-
"Reproductive system and breast disorders": "reproductive",
|
|
123
|
-
"Immune system disorders": "immune",
|
|
124
|
-
"Eye disorders": "ocular",
|
|
125
|
-
"Neoplasms benign, malignant and unspecified (incl cysts and polyps)": "oncology",
|
|
126
|
-
};
|
|
127
|
-
/**
|
|
128
|
-
* Classify a trial AE row by its SOC (preferred) or its individual MedDRA
|
|
129
|
-
* preferred term (fallback). Shared between the Phase-4 assembler's
|
|
130
|
-
* organ_rollup builder and the derived-completeness validator so both
|
|
131
|
-
* agree on which organ a trial AE belongs to.
|
|
132
|
-
*/
|
|
133
|
-
export function classifyTrialAe(ae) {
|
|
134
|
-
const fromSoc = TRIAL_AE_ORGAN_MAP[ae.organ ?? ""];
|
|
135
|
-
if (fromSoc)
|
|
136
|
-
return fromSoc;
|
|
137
|
-
return classifyOrgan(ae.term ?? "");
|
|
138
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ExecutiveRecommendation, QualityGateStatus, RecommendationAudit, SynthesisDiagnosticRow } from "@inflexa-ai/harness/contracts/target-dossier.js";
|
|
2
|
-
type ApplyRecommendationQualityGatesInput = {
|
|
3
|
-
executive_recommendation: ExecutiveRecommendation;
|
|
4
|
-
synthesis_diagnostics: Array<Partial<SynthesisDiagnosticRow> & {
|
|
5
|
-
step_id?: string;
|
|
6
|
-
}>;
|
|
7
|
-
recommendation_audit: Extract<RecommendationAudit, {
|
|
8
|
-
coverage: "available";
|
|
9
|
-
}>["data"] | null | undefined;
|
|
10
|
-
has_direct_clinical_evidence: boolean;
|
|
11
|
-
};
|
|
12
|
-
type ApplyRecommendationQualityGatesResult = {
|
|
13
|
-
executive_recommendation: ExecutiveRecommendation;
|
|
14
|
-
quality_gates: QualityGateStatus[];
|
|
15
|
-
};
|
|
16
|
-
export declare function applyRecommendationQualityGates(input: ApplyRecommendationQualityGatesInput): ApplyRecommendationQualityGatesResult;
|
|
17
|
-
export {};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
const ORGAN_CLAIM_RE = /\b(renal|kidney|nephro\w*|cns|brain|hypothalam\w*|cardiac|cardiovascular|hepatic|liver|pulmonary|respiratory|gastrointestinal|gi|oncology|malignan\w*)\b/i;
|
|
2
|
-
function appendNote(current, addition) {
|
|
3
|
-
const trimmed = current.trim();
|
|
4
|
-
return trimmed ? `${trimmed} ${addition}` : addition;
|
|
5
|
-
}
|
|
6
|
-
export function applyRecommendationQualityGates(input) {
|
|
7
|
-
const quality_gates = [];
|
|
8
|
-
const executive_recommendation = structuredClone(input.executive_recommendation);
|
|
9
|
-
const unresolved = input.recommendation_audit?.citations_unresolved ?? [];
|
|
10
|
-
const visibleRecommendationText = input.executive_recommendation.coverage === "available"
|
|
11
|
-
? [
|
|
12
|
-
input.executive_recommendation.data.rationale,
|
|
13
|
-
input.executive_recommendation.data.modality_choice.rationale,
|
|
14
|
-
...input.executive_recommendation.data.key_strengths,
|
|
15
|
-
...input.executive_recommendation.data.key_risks,
|
|
16
|
-
].join("\n")
|
|
17
|
-
: "";
|
|
18
|
-
const blockingCitationCount = unresolved.filter((u) => {
|
|
19
|
-
// The unresolved-citation entries are an untyped audit payload narrowed
|
|
20
|
-
// here to the fields these gates read; `excerpt` exists only on some
|
|
21
|
-
// surfaces, guarded by the `in` presence check.
|
|
22
|
-
if (u.surface === "organ_claim_without_probe_pass" && "excerpt" in u) {
|
|
23
|
-
const excerpt = String(u.excerpt ?? "");
|
|
24
|
-
return excerpt.length > 0 && visibleRecommendationText.includes(excerpt.slice(0, Math.min(80, excerpt.length)));
|
|
25
|
-
}
|
|
26
|
-
return ["external_missing", "nct_wrong_class", "organ_claim_without_probe_pass", "off_topic_citation"].includes(u.surface);
|
|
27
|
-
}).length;
|
|
28
|
-
if (blockingCitationCount > 0) {
|
|
29
|
-
quality_gates.push({
|
|
30
|
-
gate_id: "recommendation-citations",
|
|
31
|
-
status: "blocked",
|
|
32
|
-
message: "Recommendation contains unresolved or unsafe citation references.",
|
|
33
|
-
affected_sections: ["executive_recommendation"],
|
|
34
|
-
unresolved_count: blockingCitationCount,
|
|
35
|
-
});
|
|
36
|
-
return {
|
|
37
|
-
executive_recommendation: {
|
|
38
|
-
coverage: "queried_no_data",
|
|
39
|
-
error: {
|
|
40
|
-
message: "executive recommendation blocked because unresolved citation audit findings remain",
|
|
41
|
-
source: "recommendation-quality-gates",
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
quality_gates,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const safetyFlags = input.synthesis_diagnostics.find((row) => row.step_id === "safety-flags-trail");
|
|
48
|
-
const safetyFlagsAvailable = safetyFlags?.final_coverage === "available";
|
|
49
|
-
if (!safetyFlagsAvailable && executive_recommendation.coverage === "available") {
|
|
50
|
-
const rec = executive_recommendation.data;
|
|
51
|
-
const unverified = rec.key_risks
|
|
52
|
-
.filter((risk) => ORGAN_CLAIM_RE.test(risk))
|
|
53
|
-
.map((risk) => ({
|
|
54
|
-
text: risk,
|
|
55
|
-
bullet_category: "organ_claim_without_probe_pass",
|
|
56
|
-
surface: "organ_claim_without_probe_pass",
|
|
57
|
-
}));
|
|
58
|
-
if (unverified.length > 0) {
|
|
59
|
-
rec.key_risks = rec.key_risks.filter((risk) => !ORGAN_CLAIM_RE.test(risk));
|
|
60
|
-
rec.coverage_qualifier = {
|
|
61
|
-
...rec.coverage_qualifier,
|
|
62
|
-
unverified_bullets: [...(rec.coverage_qualifier.unverified_bullets ?? []), ...unverified],
|
|
63
|
-
note: appendNote(rec.coverage_qualifier.note, "Organ-level risk bullets were demoted because the safety-flags audit trail did not pass validation."),
|
|
64
|
-
};
|
|
65
|
-
quality_gates.push({
|
|
66
|
-
gate_id: "safety-flags-trail",
|
|
67
|
-
status: "blocked",
|
|
68
|
-
message: "Organ-level executive risk claims were demoted because safety-flags-trail was unavailable or failed validation.",
|
|
69
|
-
affected_sections: ["executive_recommendation.key_risks"],
|
|
70
|
-
unresolved_count: unverified.length,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!input.has_direct_clinical_evidence && executive_recommendation.coverage === "available") {
|
|
75
|
-
const rec = executive_recommendation.data;
|
|
76
|
-
if (rec.confidence !== "low") {
|
|
77
|
-
rec.confidence = "low";
|
|
78
|
-
}
|
|
79
|
-
rec.coverage_qualifier = {
|
|
80
|
-
...rec.coverage_qualifier,
|
|
81
|
-
note: appendNote(rec.coverage_qualifier.note, "No direct target-modulator clinical evidence was available; recommendation confidence is capped at low."),
|
|
82
|
-
};
|
|
83
|
-
quality_gates.push({
|
|
84
|
-
gate_id: "direct-clinical-evidence",
|
|
85
|
-
status: "warning",
|
|
86
|
-
message: "No direct target-modulator clinical evidence is available, so confidence is capped at low.",
|
|
87
|
-
affected_sections: ["executive_recommendation"],
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
if (!quality_gates.some((g) => g.status === "blocked")) {
|
|
91
|
-
quality_gates.push({
|
|
92
|
-
gate_id: "recommendation-quality",
|
|
93
|
-
status: "pass",
|
|
94
|
-
message: "No blocking recommendation quality issues detected.",
|
|
95
|
-
affected_sections: ["executive_recommendation"],
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return { executive_recommendation, quality_gates };
|
|
99
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strip pipeline scaffold tokens from user-visible recommendation prose.
|
|
3
|
-
*
|
|
4
|
-
* The Phase-5 dossier-recommendation agent occasionally echoes internal
|
|
5
|
-
* indexing paths it saw in its delegation prompt — strings like
|
|
6
|
-
* `per_section_synthesis.liability_bullets[0]` and parenthetical lists
|
|
7
|
-
* containing them. These are scaffolding for the build pipeline; a
|
|
8
|
-
* toxicologist reading the final dossier should never see them.
|
|
9
|
-
*
|
|
10
|
-
* The sanitizer removes the tokens and their parenthetical wrappers,
|
|
11
|
-
* collapses the resulting double whitespace, and preserves surrounding
|
|
12
|
-
* sentence punctuation.
|
|
13
|
-
*/
|
|
14
|
-
const SCAFFOLD_TOKEN_RE = /per_section_synthesis\.[A-Za-z_][\w.]*(?:\[[\d–-]+\])?(?:\s+row\s+\d+)?/g;
|
|
15
|
-
// Citations to dossier.per_section_synthesis.* — this namespace doesn't exist
|
|
16
|
-
// in the dossier schema. The synthesis prompt occasionally emits these paths;
|
|
17
|
-
// strip them to prevent non-resolvable citations in recommendation prose.
|
|
18
|
-
const INVALID_PATH_RE = /\(?dossier\.per_section_synthesis\.[a-z_.[\]0-9]*\)?/gi;
|
|
19
|
-
// Dangling "dossier." with no path suffix is leftover templating; strip it.
|
|
20
|
-
const DANGLING_DOSSIER_RE = /\bdossier\.(?=\s|$|[.,;])/g;
|
|
21
|
-
function stripScaffold(text) {
|
|
22
|
-
if (!text)
|
|
23
|
-
return text;
|
|
24
|
-
let out = text;
|
|
25
|
-
// First pass: remove a parenthetical that contains a scaffold token,
|
|
26
|
-
// along with its trailing punctuation if it directly precedes one.
|
|
27
|
-
out = out.replace(/\s*\([^()]*per_section_synthesis\.[^()]*\)/g, "");
|
|
28
|
-
// Second pass: any leftover scaffold tokens not inside parens.
|
|
29
|
-
out = out.replace(SCAFFOLD_TOKEN_RE, "");
|
|
30
|
-
// Strip dossier.per_section_synthesis.* invalid paths and dangling dossier. placeholders.
|
|
31
|
-
out = out.replace(INVALID_PATH_RE, "").replace(DANGLING_DOSSIER_RE, "");
|
|
32
|
-
// Collapse double whitespace.
|
|
33
|
-
out = out.replace(/\s{2,}/g, " ").replace(/\s+([,.;:])/g, "$1");
|
|
34
|
-
return out.trim();
|
|
35
|
-
}
|
|
36
|
-
export function sanitizeRecommendation(rec) {
|
|
37
|
-
return {
|
|
38
|
-
...rec,
|
|
39
|
-
rationale: stripScaffold(rec.rationale),
|
|
40
|
-
key_strengths: rec.key_strengths.map(stripScaffold),
|
|
41
|
-
key_risks: rec.key_risks.map(stripScaffold),
|
|
42
|
-
modality_choice: {
|
|
43
|
-
...rec.modality_choice,
|
|
44
|
-
rationale: stripScaffold(rec.modality_choice.rationale),
|
|
45
|
-
},
|
|
46
|
-
coverage_qualifier: {
|
|
47
|
-
...rec.coverage_qualifier,
|
|
48
|
-
note: stripScaffold(rec.coverage_qualifier.note),
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-drug regulatory-action collector. Queries openFDA Structured Product
|
|
3
|
-
* Labels (boxed warning, Section 5 warnings_and_cautions, REMS indicator)
|
|
4
|
-
* and the EMA referrals catalogue (Article 31 / Article 20 procedures)
|
|
5
|
-
* for each drug, then maps both into the dossier's RegulatoryActionRow
|
|
6
|
-
* shape.
|
|
7
|
-
*
|
|
8
|
-
* Coverage that legitimately has no
|
|
9
|
-
* machine-readable source (e.g., EMA assessment-report procedure codes like
|
|
10
|
-
* EMEA/H/A-31/1291) is omitted rather than backfilled with curation — the
|
|
11
|
-
* source_url points at the canonical document where the human reader can
|
|
12
|
-
* extract the additional context.
|
|
13
|
-
*/
|
|
14
|
-
import type { RegulatoryActionRow } from "@inflexa-ai/harness/contracts/target-dossier.js";
|
|
15
|
-
export interface DrugForRegulatoryActions {
|
|
16
|
-
chemblId: string;
|
|
17
|
-
name: string;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Fetch regulatory actions (FDA label + EMA referrals) for a set of drugs.
|
|
21
|
-
* Per-drug failures are isolated — one drug throwing does not blank out
|
|
22
|
-
* the rest. Returns an empty array when no drugs are provided or no source
|
|
23
|
-
* returned any actions.
|
|
24
|
-
*/
|
|
25
|
-
export declare function fetchRegulatoryActions(drugs: DrugForRegulatoryActions[]): Promise<RegulatoryActionRow[]>;
|