@inflexa-ai/harness 0.35.8 → 0.36.1

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