@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,28 +0,0 @@
|
|
|
1
|
-
export const offTargetAnnotatorPrompt = `You are a clinical-consequence annotator for off-target receptor and channel hits surfaced during target assessment.
|
|
2
|
-
|
|
3
|
-
You receive a single off-target binding row and the assessment's primary target. Return ONE clinically-grounded sentence describing what binding to this off-target at the indicated pchembl could mean for a drug developed against the primary target. Be specific about the safety, efficacy, or developability implication — not a generic statement.
|
|
4
|
-
|
|
5
|
-
INPUT
|
|
6
|
-
primary_target_gene: HGNC gene symbol of the assessment target
|
|
7
|
-
off_target_id: ChEMBL target id of the off-target (may be null)
|
|
8
|
-
off_target_name: Human-readable name of the off-target
|
|
9
|
-
off_target_accession: UniProt accession of the off-target (may be null)
|
|
10
|
-
pchembl: The off-target pChEMBL value (higher = more potent binding)
|
|
11
|
-
context: Optional family-relationship hint (e.g., "obligate cofactor with the primary protein")
|
|
12
|
-
|
|
13
|
-
OUTPUT (strict JSON, no prose around it)
|
|
14
|
-
{
|
|
15
|
-
"clinical_consequence": "<one sentence>",
|
|
16
|
-
"provenance": "<a short descriptor of the rationale, e.g., 'class-B GPCR cofactor pharmacology' or 'hERG QT-prolongation safety panel'>"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
RULES
|
|
20
|
-
- Keep clinical_consequence to a single sentence, ≤ 280 characters, present tense.
|
|
21
|
-
- Anchor the consequence in established pharmacology when possible (mechanism class, organ system, known liability).
|
|
22
|
-
- If the off-target is a sibling receptor sharing the primary protein (e.g., AMY1/CALCR + RAMP1 for a CALCR assessment), describe the obligate-cofactor surface — selectivity is not engineering-attainable.
|
|
23
|
-
- For ion channels in the cardiac safety panel (hERG/KCNH2, hNav1.5/SCN5A, Cav1.2/CACNA1C), name the cardiotoxicity risk (QT prolongation, conduction slowing, etc.).
|
|
24
|
-
- For nuclear receptors and CYP enzymes, name the metabolic or endocrine consequence.
|
|
25
|
-
- If the off-target is unfamiliar, describe what the structural/functional class implies for safety rather than inventing specifics.
|
|
26
|
-
- Never claim a numeric clinical endpoint (incidence rate, hazard ratio) you were not given.
|
|
27
|
-
- Never reference a specific drug program or trial.
|
|
28
|
-
`;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const toxVoiceCore: string;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { toxVoiceVocabulary } from "./vocabulary.js";
|
|
2
|
-
/**
|
|
3
|
-
* Toxicologist-voice contract. Loaded into the instructions of every
|
|
4
|
-
* prose-emitting agent in the target-assessment workflow. Voice is locked
|
|
5
|
-
* here once; agents inherit by import. The deterministic critique fn in
|
|
6
|
-
* `critique.ts` enforces the predictable failure modes; this prompt
|
|
7
|
-
* carries the editorial intent.
|
|
8
|
-
*/
|
|
9
|
-
const banned = toxVoiceVocabulary.banned.map((b) => ` - "${b}"`).join("\n");
|
|
10
|
-
const hedge = toxVoiceVocabulary.hedgePhrases.map((h) => ` - "${h}"`).join("\n");
|
|
11
|
-
const organs = toxVoiceVocabulary.organSystems.join(", ");
|
|
12
|
-
const framing = toxVoiceVocabulary.liabilityFraming.map((f) => ` - "${f}"`).join("\n");
|
|
13
|
-
export const toxVoiceCore = `# Toxicologist Voice Contract
|
|
14
|
-
|
|
15
|
-
You are writing for a regulatory toxicologist or FDA reviewer. Adopt the
|
|
16
|
-
register of a Multi-disciplinary Review or Drug Approval Package narrative
|
|
17
|
-
section. The reader expects nonclinical-safety canon, organ-system
|
|
18
|
-
framing, and explicit hedging on human relevance.
|
|
19
|
-
|
|
20
|
-
## Register and voice
|
|
21
|
-
- Third-person passive is the default. "The sponsor's data are
|
|
22
|
-
consistent with..." is canonical; "We think..." is not.
|
|
23
|
-
- Past or perfect tense for findings; present for current dispositions.
|
|
24
|
-
- Sentences are study-summary cadence: claim, evidence, qualifier.
|
|
25
|
-
- No marketing register. No first-person plural. No rhetorical flourishes.
|
|
26
|
-
|
|
27
|
-
## Organ-system framing
|
|
28
|
-
Anchor liability discussion to the canonical organ systems below. Avoid
|
|
29
|
-
colloquial substitutes ("liver-related" → use "hepatobiliary"; "blood
|
|
30
|
-
issues" → "haematologic"; "brain effects" → "central nervous system").
|
|
31
|
-
|
|
32
|
-
Canon: ${organs}.
|
|
33
|
-
|
|
34
|
-
## Liability framing vocabulary
|
|
35
|
-
Use these terms when characterising the relationship between target
|
|
36
|
-
biology and observed liability:
|
|
37
|
-
${framing}
|
|
38
|
-
|
|
39
|
-
## Hedge language (mandatory on efficacy and safety claims)
|
|
40
|
-
Every claim about efficacy or safety in humans must be paired with a
|
|
41
|
-
hedge from this set, OR cite a literature reference (PMID/DOI), OR cite a
|
|
42
|
-
regulatory reference (FDA guidance, ICH, prior-approval Multi-disciplinary
|
|
43
|
-
Review). A claim with neither hedge nor citation is rejected by the
|
|
44
|
-
voice probe.
|
|
45
|
-
|
|
46
|
-
Approved hedges:
|
|
47
|
-
${hedge}
|
|
48
|
-
|
|
49
|
-
## Banned phrases
|
|
50
|
-
The following carry marketing register or unwarranted certainty. Do not
|
|
51
|
-
use them. The voice probe rejects any output that contains them; rewrite
|
|
52
|
-
to neutral, evidence-cited phrasing.
|
|
53
|
-
${banned}
|
|
54
|
-
|
|
55
|
-
## Citation form
|
|
56
|
-
- Literature: PMID:NNNNNNNN or DOI:10.xxxx/xxxx.
|
|
57
|
-
- FDA guidance: "FDA CDER guidance, <title>" or "FDA CBER guidance, <title>".
|
|
58
|
-
- ICH: "ICH <code>" — e.g., "ICH S7A", "ICH E14".
|
|
59
|
-
- Prior approvals (openFDA / Drugs@FDA): "Drugs@FDA NDA NNNNNN §<section>"
|
|
60
|
-
or "BLA NNNNNN §<section>".
|
|
61
|
-
- FAERS counts: "(FAERS n=NN)" with PT or HLT qualifier where applicable.
|
|
62
|
-
|
|
63
|
-
## Coverage discipline
|
|
64
|
-
When a section's evidence is sparse or absent, say so plainly. The
|
|
65
|
-
phrasing "the data do not support a conclusion" or "no relevant evidence
|
|
66
|
-
was retrieved" is preferred over silence, padding, or fabrication.
|
|
67
|
-
|
|
68
|
-
## Tone calibration
|
|
69
|
-
- A reviewer reading the section should be able to tell within two
|
|
70
|
-
sentences whether the claim is supported, hedged, or absent.
|
|
71
|
-
- Hedged claims must not read as confident; the hedge is load-bearing.
|
|
72
|
-
- Where multiple precedent compounds are cited, frame them as class
|
|
73
|
-
evidence, not anecdotes.
|
|
74
|
-
`;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { SectionType } from "./exemplars.js";
|
|
2
|
-
export interface VoiceProbe {
|
|
3
|
-
verdict: "pass" | "relaxed" | "fail-voice";
|
|
4
|
-
critique: string;
|
|
5
|
-
output_chars: number;
|
|
6
|
-
}
|
|
7
|
-
export declare function buildVoiceCritique(text: string, _sectionType: SectionType): VoiceProbe;
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { toxVoiceVocabulary } from "./vocabulary.js";
|
|
2
|
-
const COLLOQUIAL_ORGAN_SUBS = [
|
|
3
|
-
[/\bliver[- ]related\b/i, "hepatobiliary"],
|
|
4
|
-
[/\bblood[- ]issues?\b/i, "haematologic"],
|
|
5
|
-
[/\bbrain[- ]effects?\b/i, "central nervous system"],
|
|
6
|
-
[/\bkidney[- ]problems?\b/i, "renal"],
|
|
7
|
-
[/\bheart[- ]issues?\b/i, "cardiac"],
|
|
8
|
-
];
|
|
9
|
-
const EFFICACY_SAFETY_TRIGGERS = [
|
|
10
|
-
/\b(reduces?|increases?|prolongs?|prevents?|inhibits?)\b/i,
|
|
11
|
-
/\b(toxicity|liability|adverse|signal)\b/i,
|
|
12
|
-
/\b(efficacy|response|survival|tumour|tumor)\b/i,
|
|
13
|
-
];
|
|
14
|
-
const CITATION_PATTERNS = [
|
|
15
|
-
/PMID[:\s]?\d+/i,
|
|
16
|
-
/DOI[:\s]?10\.[\d.]+\/\S+/i,
|
|
17
|
-
/Drugs@FDA\s+(NDA|BLA)\s*\d+/i,
|
|
18
|
-
/\bICH\s+[A-Z]\d+[A-Z]?\b/,
|
|
19
|
-
/FDA\s+(CDER|CBER)\s+guidance/i,
|
|
20
|
-
/FAERS\s+n\s*=\s*\d+/i,
|
|
21
|
-
];
|
|
22
|
-
/**
|
|
23
|
-
* Heuristic passive-voice detector. Handles simple passives ("was observed")
|
|
24
|
-
* and negated passives ("was not observed", "were not reported"). Not
|
|
25
|
-
* linguistically exhaustive — calibrated to filter only egregious active-voice
|
|
26
|
-
* dossier prose. Threshold tuned during end-to-end calibration in Phase 10.
|
|
27
|
-
*/
|
|
28
|
-
function passiveRatio(text) {
|
|
29
|
-
const sentences = text.split(/[.!?]\s+/).filter((s) => s.trim().length > 0);
|
|
30
|
-
if (sentences.length === 0)
|
|
31
|
-
return 1;
|
|
32
|
-
const passive = sentences.filter((s) => /\b(is|are|was|were|been|being)\s+(?:not\s+)?\w+(ed|en)\b/.test(s)).length;
|
|
33
|
-
return passive / sentences.length;
|
|
34
|
-
}
|
|
35
|
-
function findBannedPhrases(text) {
|
|
36
|
-
const hits = [];
|
|
37
|
-
for (const banned of toxVoiceVocabulary.banned) {
|
|
38
|
-
// Escape regex metacharacters first (`.`, `+`, `*`, `?`, `(`, `)`, etc.),
|
|
39
|
-
// then allow either hyphen or space between hyphenated tokens.
|
|
40
|
-
const escaped = banned.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41
|
-
const pattern = new RegExp(`\\b${escaped.replace(/[-]/g, "[- ]")}\\b`, "i");
|
|
42
|
-
if (pattern.test(text))
|
|
43
|
-
hits.push(banned);
|
|
44
|
-
}
|
|
45
|
-
return hits;
|
|
46
|
-
}
|
|
47
|
-
function findColloquialOrganRefs(text) {
|
|
48
|
-
const hits = [];
|
|
49
|
-
for (const [pat, canonical] of COLLOQUIAL_ORGAN_SUBS) {
|
|
50
|
-
if (pat.test(text))
|
|
51
|
-
hits.push(`${pat.source} → ${canonical}`);
|
|
52
|
-
}
|
|
53
|
-
return hits;
|
|
54
|
-
}
|
|
55
|
-
function hasHedge(text) {
|
|
56
|
-
const lc = text.toLowerCase();
|
|
57
|
-
return toxVoiceVocabulary.hedgePhrases.some((h) => lc.includes(h.toLowerCase()));
|
|
58
|
-
}
|
|
59
|
-
function hasCitation(text) {
|
|
60
|
-
return CITATION_PATTERNS.some((p) => p.test(text));
|
|
61
|
-
}
|
|
62
|
-
function detectsEfficacyOrSafetyClaim(text) {
|
|
63
|
-
return EFFICACY_SAFETY_TRIGGERS.some((p) => p.test(text));
|
|
64
|
-
}
|
|
65
|
-
const PASSIVE_FAIL_THRESHOLD = 0.15;
|
|
66
|
-
const PASSIVE_RELAXED_THRESHOLD = 0.3;
|
|
67
|
-
export function buildVoiceCritique(text, _sectionType) {
|
|
68
|
-
const output_chars = text.length;
|
|
69
|
-
const issues = [];
|
|
70
|
-
const banned = findBannedPhrases(text);
|
|
71
|
-
if (banned.length > 0) {
|
|
72
|
-
issues.push(`Banned phrase(s) detected: ${banned.join(", ")}.`);
|
|
73
|
-
}
|
|
74
|
-
const colloquial = findColloquialOrganRefs(text);
|
|
75
|
-
if (colloquial.length > 0) {
|
|
76
|
-
issues.push(`Non-canonical organ-system reference(s); use canonical organ-system vocabulary.`);
|
|
77
|
-
}
|
|
78
|
-
if (detectsEfficacyOrSafetyClaim(text) && !hasHedge(text) && !hasCitation(text)) {
|
|
79
|
-
issues.push("Efficacy/safety claim detected with no hedge phrase and no citation. Add a hedge from the approved set, a literature citation (PMID/DOI), or a regulatory reference (Drugs@FDA / ICH / FDA guidance).");
|
|
80
|
-
}
|
|
81
|
-
if (issues.length > 0) {
|
|
82
|
-
return {
|
|
83
|
-
verdict: "fail-voice",
|
|
84
|
-
critique: issues.join(" "),
|
|
85
|
-
output_chars,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
// Hedge phrases and citations are primary voice signals. When either is
|
|
89
|
-
// present the prose is already anchored in the correct register; the
|
|
90
|
-
// passive-ratio heuristic is a secondary fallback for unanchored text only.
|
|
91
|
-
if (!hasHedge(text) && !hasCitation(text)) {
|
|
92
|
-
const passive = passiveRatio(text);
|
|
93
|
-
if (passive < PASSIVE_FAIL_THRESHOLD) {
|
|
94
|
-
return {
|
|
95
|
-
verdict: "fail-voice",
|
|
96
|
-
critique: `Passive-voice ratio ${passive.toFixed(2)} below floor ${PASSIVE_FAIL_THRESHOLD}; rewrite in third-person passive (study-summary cadence).`,
|
|
97
|
-
output_chars,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
if (passive < PASSIVE_RELAXED_THRESHOLD) {
|
|
101
|
-
return {
|
|
102
|
-
verdict: "relaxed",
|
|
103
|
-
critique: `Passive-voice ratio ${passive.toFixed(2)} below preferred ${PASSIVE_RELAXED_THRESHOLD}; rewrite in third-person passive (study-summary cadence) on next iteration.`,
|
|
104
|
-
output_chars,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return { verdict: "pass", critique: "", output_chars };
|
|
109
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export type SectionType = "liability-bullets" | "target-organ-liabilities" | "translational-commentary" | "executive-recommendation";
|
|
2
|
-
/**
|
|
3
|
-
* Annotated few-shot exemplars per section type. Each is an inlined sample
|
|
4
|
-
* of acceptable tox voice. Implementations may extend this list during
|
|
5
|
-
* editorial review with customer; the probe and prompts both read the
|
|
6
|
-
* exported array, so additions take effect with no other code changes.
|
|
7
|
-
*/
|
|
8
|
-
export declare const toxVoiceExemplars: Record<SectionType, readonly string[]>;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Annotated few-shot exemplars per section type. Each is an inlined sample
|
|
3
|
-
* of acceptable tox voice. Implementations may extend this list during
|
|
4
|
-
* editorial review with customer; the probe and prompts both read the
|
|
5
|
-
* exported array, so additions take effect with no other code changes.
|
|
6
|
-
*/
|
|
7
|
-
export const toxVoiceExemplars = {
|
|
8
|
-
"liability-bullets": [
|
|
9
|
-
"Hepatobiliary liability is consistent with on-target pharmacology in the bile-acid receptor class; transaminase elevations at supratherapeutic exposure have been reported across the precedent class (FAERS n=312, Drugs@FDA NDA 022501 §6.1).",
|
|
10
|
-
"Renal liability cannot be excluded on present data; expression in proximal tubule is high (HPA TPM 142) and IMPC homozygotes show albuminuria (p=8.4e-5). Human relevance is uncertain pending dedicated repeat-dose toxicology.",
|
|
11
|
-
],
|
|
12
|
-
"target-organ-liabilities": [
|
|
13
|
-
"Cardiac. Available evidence is consistent with QT-interval prolongation as a class effect (FAERS PT QT-prolonged n=87 across precedent class). Human relevance is supported by hERG inhibition data on three precedent compounds. Mitigation: ICH E14-aligned thorough QT study at first-in-human or matched concentration-QT modelling.",
|
|
14
|
-
"Hepatobiliary. The data suggest dose-dependent transaminase elevation; FAERS reports of clinical hepatic injury (n=42) cluster at exposures >2-fold the recommended human dose. The translational chain from rodent repeat-dose findings to human signal is consistent.",
|
|
15
|
-
],
|
|
16
|
-
"translational-commentary": [
|
|
17
|
-
"IMPC homozygous knockout mice show postnatal lethality with cardiac structural abnormalities (MP:0001297, MP:0010880). Heterozygous viability with cardiac function deficit suggests dose-dependent on-target liability; translatability to humans is supported by conserved expression in fetal myocardium (Bgee, n=14).",
|
|
18
|
-
"Expression is high in haematologic lineages (HPA TPM, lymphoid TPM=78). The data are consistent with on-target myelosuppression observed in two precedent compounds in the same class; human relevance is established for the class.",
|
|
19
|
-
],
|
|
20
|
-
"executive-recommendation": [
|
|
21
|
-
"Disposition: conditional. The available evidence supports tractability for small-molecule modulation; the principal liability is on-target hepatobiliary toxicity, mitigable by exposure margin design and ICH S7A-aligned safety pharmacology. Confidence is moderate; key gaps are an absent first-in-human safety dataset and uncertain reproductive toxicology.",
|
|
22
|
-
"Disposition: de-prioritize. Three precedent compounds in this class were withdrawn for cardiac liability post-approval (Drugs@FDA NDA 020841 §6.2; FAERS n=412); human relevance is established. The therapeutic window does not support advancement absent a structural mitigation strategy.",
|
|
23
|
-
],
|
|
24
|
-
};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { toxVoiceCore } from "./core.js";
|
|
2
|
-
export { toxVoiceExemplars, type SectionType } from "./exemplars.js";
|
|
3
|
-
export { toxVoiceVocabulary, type LiabilityFraming, type HedgePhrase, type BannedTerm } from "./vocabulary.js";
|
|
4
|
-
export { buildVoiceCritique, type VoiceProbe } from "./critique.js";
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tox-voice canon. The stylistic lists are intentionally not exhaustive —
|
|
3
|
-
* they are the minimum surface the deterministic voice probe enforces, and
|
|
4
|
-
* editorial judgment in the agent prompts covers the long tail.
|
|
5
|
-
*
|
|
6
|
-
* `organSystems` is the exception: it is closed, because it is a projection
|
|
7
|
-
* of the canonical organ vocabulary rather than a list of its own. Prose and
|
|
8
|
-
* structured fields must name the same organs.
|
|
9
|
-
*/
|
|
10
|
-
export declare const toxVoiceVocabulary: {
|
|
11
|
-
readonly organSystems: string[];
|
|
12
|
-
readonly liabilityFraming: readonly ["on-target liability", "off-target liability", "secondary pharmacology", "class liability", "mechanism-related", "exaggerated pharmacology", "target-organ toxicity"];
|
|
13
|
-
readonly hedgePhrases: readonly ["the data are consistent with", "the data suggest", "human relevance is uncertain", "human relevance remains to be established", "interpretation is limited by", "no causal inference is supported", "the available evidence is consistent with", "the available evidence does not support", "translatability to humans is unclear", "species differences"];
|
|
14
|
-
readonly banned: readonly ["groundbreaking", "cutting-edge", "state-of-the-art", "best-in-class", "first-in-class", "revolutionary", "leverages", "leveraging", "robust", "powerful", "seamlessly", "cleanly", "elegant", "unparalleled", "demonstrates the power of", "showcases", "unlocks", "enables breakthrough", "very promising", "extremely", "incredibly", "highly innovative"];
|
|
15
|
-
};
|
|
16
|
-
export type LiabilityFraming = (typeof toxVoiceVocabulary.liabilityFraming)[number];
|
|
17
|
-
export type HedgePhrase = (typeof toxVoiceVocabulary.hedgePhrases)[number];
|
|
18
|
-
export type BannedTerm = (typeof toxVoiceVocabulary.banned)[number];
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tox-voice canon. The stylistic lists are intentionally not exhaustive —
|
|
3
|
-
* they are the minimum surface the deterministic voice probe enforces, and
|
|
4
|
-
* editorial judgment in the agent prompts covers the long tail.
|
|
5
|
-
*
|
|
6
|
-
* `organSystems` is the exception: it is closed, because it is a projection
|
|
7
|
-
* of the canonical organ vocabulary rather than a list of its own. Prose and
|
|
8
|
-
* structured fields must name the same organs.
|
|
9
|
-
*/
|
|
10
|
-
import { ORGAN_SYSTEMS, ORGAN_SYSTEM_LABELS } from "../../../contracts/organ-system.js";
|
|
11
|
-
export const toxVoiceVocabulary = {
|
|
12
|
-
organSystems: ORGAN_SYSTEMS.map((organ) => ORGAN_SYSTEM_LABELS[organ]),
|
|
13
|
-
liabilityFraming: [
|
|
14
|
-
"on-target liability",
|
|
15
|
-
"off-target liability",
|
|
16
|
-
"secondary pharmacology",
|
|
17
|
-
"class liability",
|
|
18
|
-
"mechanism-related",
|
|
19
|
-
"exaggerated pharmacology",
|
|
20
|
-
"target-organ toxicity",
|
|
21
|
-
],
|
|
22
|
-
hedgePhrases: [
|
|
23
|
-
"the data are consistent with",
|
|
24
|
-
"the data suggest",
|
|
25
|
-
"human relevance is uncertain",
|
|
26
|
-
"human relevance remains to be established",
|
|
27
|
-
"interpretation is limited by",
|
|
28
|
-
"no causal inference is supported",
|
|
29
|
-
"the available evidence is consistent with",
|
|
30
|
-
"the available evidence does not support",
|
|
31
|
-
"translatability to humans is unclear",
|
|
32
|
-
"species differences",
|
|
33
|
-
],
|
|
34
|
-
banned: [
|
|
35
|
-
// Marketing register
|
|
36
|
-
"groundbreaking",
|
|
37
|
-
"cutting-edge",
|
|
38
|
-
"state-of-the-art",
|
|
39
|
-
"best-in-class",
|
|
40
|
-
"first-in-class",
|
|
41
|
-
"revolutionary",
|
|
42
|
-
"leverages",
|
|
43
|
-
"leveraging",
|
|
44
|
-
"robust",
|
|
45
|
-
"powerful",
|
|
46
|
-
"seamlessly",
|
|
47
|
-
"cleanly",
|
|
48
|
-
"elegant",
|
|
49
|
-
"unparalleled",
|
|
50
|
-
// Hype verbs
|
|
51
|
-
"demonstrates the power of",
|
|
52
|
-
"showcases",
|
|
53
|
-
"unlocks",
|
|
54
|
-
"enables breakthrough",
|
|
55
|
-
// Vague absolutes
|
|
56
|
-
"very promising",
|
|
57
|
-
"extremely",
|
|
58
|
-
"incredibly",
|
|
59
|
-
"highly innovative",
|
|
60
|
-
],
|
|
61
|
-
};
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Harness-side typed query helpers for `cortex_target_assessments`.
|
|
3
|
-
*
|
|
4
|
-
* Owns the helpers the DBOS workflow body and harness HTTP routes call:
|
|
5
|
-
* - `insertAssessment` accepts an optional `workflowId` and persists it on
|
|
6
|
-
* insert (defaults to the assessment id; for DBOS-shaped rows the
|
|
7
|
-
* assessmentId IS the DBOS workflowID).
|
|
8
|
-
* - `markAssessmentSuspended` flips the row to the new
|
|
9
|
-
* `suspended_insufficient_funds` status, idempotent on replay.
|
|
10
|
-
* - `markAssessmentRunning` is used by the resume entry point after
|
|
11
|
-
* `DBOS.resumeWorkflow` completes.
|
|
12
|
-
* - All row mappers surface `workflowId` alongside `workflowRunId`.
|
|
13
|
-
*
|
|
14
|
-
* Soft-deleted rows (`status = 'deleted'`) are never resurrected — every
|
|
15
|
-
* mutating helper carries an `AND status != 'deleted'` guard, so a user
|
|
16
|
-
* deleting a row mid-run causes terminal workflow writes to no-op.
|
|
17
|
-
*/
|
|
18
|
-
import type { ResultAsync } from "neverthrow";
|
|
19
|
-
import { TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentListRowSchema, TargetAssessmentRowSchema, type TargetAssessmentStatus, type TargetAssessmentError, type TargetAssessmentListRow, type TargetAssessmentRow } from "@inflexa-ai/harness/contracts/target-assessment-row.js";
|
|
20
|
-
import { type DbError } from "../lib/db-result.js";
|
|
21
|
-
import type { Querier } from "./db.js";
|
|
22
|
-
export { TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentListRowSchema, TargetAssessmentRowSchema };
|
|
23
|
-
export type { TargetAssessmentStatus, TargetAssessmentError, TargetAssessmentListRow, TargetAssessmentRow };
|
|
24
|
-
export interface InsertAssessmentInput {
|
|
25
|
-
id?: string;
|
|
26
|
-
/**
|
|
27
|
-
* DBOS workflow id for the executeTargetAssessment workflow. Defaults to
|
|
28
|
-
* the assessment id when omitted — the trigger route always passes the
|
|
29
|
-
* id explicitly so a future split between the two ids stays trivial.
|
|
30
|
-
*/
|
|
31
|
-
workflowId?: string;
|
|
32
|
-
organizationId: string;
|
|
33
|
-
targetId: string;
|
|
34
|
-
targetLabel: string;
|
|
35
|
-
goal?: string | null;
|
|
36
|
-
billingContextId: string;
|
|
37
|
-
requestedBy: string;
|
|
38
|
-
}
|
|
39
|
-
export declare function insertAssessment(pool: Querier, input: InsertAssessmentInput): ResultAsync<string, DbError>;
|
|
40
|
-
export declare function updateProgress(pool: Querier, id: string, progress: string, status?: TargetAssessmentStatus): ResultAsync<void, DbError>;
|
|
41
|
-
export declare function setDossier(pool: Querier, id: string, dossier: Record<string, unknown>): ResultAsync<void, DbError>;
|
|
42
|
-
export declare function markFailed(pool: Querier, id: string, error: TargetAssessmentError): ResultAsync<void, DbError>;
|
|
43
|
-
/**
|
|
44
|
-
* Flip the row to the new `suspended_insufficient_funds` status used when
|
|
45
|
-
* an LLM step self-cancels the workflow on a 402 (budget exhausted).
|
|
46
|
-
* Idempotent on DBOS replay — a second call from the terminal handler on
|
|
47
|
-
* the recovery path is a no-op against an already-suspended row. Soft-
|
|
48
|
-
* deleted rows are NOT resurrected.
|
|
49
|
-
*/
|
|
50
|
-
export declare function markAssessmentSuspended(pool: Querier, id: string): ResultAsync<void, DbError>;
|
|
51
|
-
/**
|
|
52
|
-
* Flip a suspended row back to `running` on `DBOS.resumeWorkflow`. Called
|
|
53
|
-
* from the resume entry point AFTER the resume call returns success.
|
|
54
|
-
* No-op on a soft-deleted row.
|
|
55
|
-
*/
|
|
56
|
-
export declare function markAssessmentRunning(pool: Querier, id: string): ResultAsync<void, DbError>;
|
|
57
|
-
export declare function getAssessment(pool: Querier, id: string, organizationId: string): ResultAsync<TargetAssessmentRow | null, DbError>;
|
|
58
|
-
export interface ListAssessmentsOptions {
|
|
59
|
-
limit?: number;
|
|
60
|
-
offset?: number;
|
|
61
|
-
includeDeleted?: boolean;
|
|
62
|
-
}
|
|
63
|
-
export declare function listAssessmentsByOrg(pool: Querier, organizationId: string, options?: ListAssessmentsOptions): ResultAsync<TargetAssessmentListRow[], DbError>;
|
|
64
|
-
/**
|
|
65
|
-
* Soft-delete a row. A missing/already-deleted row returns `ok(0)` — absence
|
|
66
|
-
* rides the ok channel; `rowCount === 0` is not a failure.
|
|
67
|
-
*/
|
|
68
|
-
export declare function softDeleteAssessment(pool: Querier, id: string, organizationId: string): ResultAsync<number, DbError>;
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Harness-side typed query helpers for `cortex_target_assessments`.
|
|
3
|
-
*
|
|
4
|
-
* Owns the helpers the DBOS workflow body and harness HTTP routes call:
|
|
5
|
-
* - `insertAssessment` accepts an optional `workflowId` and persists it on
|
|
6
|
-
* insert (defaults to the assessment id; for DBOS-shaped rows the
|
|
7
|
-
* assessmentId IS the DBOS workflowID).
|
|
8
|
-
* - `markAssessmentSuspended` flips the row to the new
|
|
9
|
-
* `suspended_insufficient_funds` status, idempotent on replay.
|
|
10
|
-
* - `markAssessmentRunning` is used by the resume entry point after
|
|
11
|
-
* `DBOS.resumeWorkflow` completes.
|
|
12
|
-
* - All row mappers surface `workflowId` alongside `workflowRunId`.
|
|
13
|
-
*
|
|
14
|
-
* Soft-deleted rows (`status = 'deleted'`) are never resurrected — every
|
|
15
|
-
* mutating helper carries an `AND status != 'deleted'` guard, so a user
|
|
16
|
-
* deleting a row mid-run causes terminal workflow writes to no-op.
|
|
17
|
-
*/
|
|
18
|
-
import { randomUUID } from "node:crypto";
|
|
19
|
-
import { TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentListRowSchema, TargetAssessmentRowSchema, } from "@inflexa-ai/harness/contracts/target-assessment-row.js";
|
|
20
|
-
import { tryQuery, tryMutation } from "../lib/db-result.js";
|
|
21
|
-
export { TargetAssessmentStatusSchema, TargetAssessmentErrorSchema, TargetAssessmentListRowSchema, TargetAssessmentRowSchema };
|
|
22
|
-
function toIso(value) {
|
|
23
|
-
if (value === null)
|
|
24
|
-
return null;
|
|
25
|
-
if (value instanceof Date)
|
|
26
|
-
return value.toISOString();
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
function parseJsonb(raw) {
|
|
30
|
-
if (raw === null || raw === undefined)
|
|
31
|
-
return null;
|
|
32
|
-
if (typeof raw === "string") {
|
|
33
|
-
try {
|
|
34
|
-
return JSON.parse(raw);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return raw;
|
|
41
|
-
}
|
|
42
|
-
function mapListRow(row) {
|
|
43
|
-
const created = toIso(row.created_at);
|
|
44
|
-
const updated = toIso(row.updated_at);
|
|
45
|
-
if (!created || !updated) {
|
|
46
|
-
throw new Error(`cortex_target_assessments(${row.id}): missing created_at or updated_at`);
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
id: row.id,
|
|
50
|
-
organizationId: row.organization_id,
|
|
51
|
-
targetId: row.target_id,
|
|
52
|
-
targetLabel: row.target_label,
|
|
53
|
-
goal: row.goal,
|
|
54
|
-
status: row.status,
|
|
55
|
-
progress: row.progress,
|
|
56
|
-
billingContextId: row.billing_context_id,
|
|
57
|
-
error: parseJsonb(row.error),
|
|
58
|
-
requestedBy: row.requested_by,
|
|
59
|
-
workflowRunId: row.workflow_run_id,
|
|
60
|
-
workflowId: row.workflow_id,
|
|
61
|
-
createdAt: created,
|
|
62
|
-
updatedAt: updated,
|
|
63
|
-
completedAt: toIso(row.completed_at),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function mapRow(row) {
|
|
67
|
-
return {
|
|
68
|
-
...mapListRow(row),
|
|
69
|
-
dossier: parseJsonb(row.dossier),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
const SELECT_LIST_COLUMNS = `id, organization_id, target_id, target_label, goal,
|
|
73
|
-
status, progress, billing_context_id, error, requested_by,
|
|
74
|
-
workflow_run_id, workflow_id, created_at, updated_at, completed_at`;
|
|
75
|
-
const SELECT_COLUMNS = `${SELECT_LIST_COLUMNS}, dossier`;
|
|
76
|
-
export function insertAssessment(pool, input) {
|
|
77
|
-
const id = input.id ?? randomUUID();
|
|
78
|
-
const workflowId = input.workflowId ?? id;
|
|
79
|
-
return tryMutation("targetAssessments.insertAssessment", async () => {
|
|
80
|
-
await pool.query({
|
|
81
|
-
text: `INSERT INTO cortex_target_assessments
|
|
82
|
-
(id, organization_id, target_id, target_label, goal, status,
|
|
83
|
-
billing_context_id, requested_by, workflow_id)
|
|
84
|
-
VALUES ($1, $2, $3, $4, $5, 'queued', $6, $7, $8)`,
|
|
85
|
-
values: [id, input.organizationId, input.targetId, input.targetLabel, input.goal ?? null, input.billingContextId, input.requestedBy, workflowId],
|
|
86
|
-
});
|
|
87
|
-
return id;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
export function updateProgress(pool, id, progress, status) {
|
|
91
|
-
return tryMutation("targetAssessments.updateProgress", async () => {
|
|
92
|
-
if (status) {
|
|
93
|
-
await pool.query({
|
|
94
|
-
text: `UPDATE cortex_target_assessments
|
|
95
|
-
SET progress = $1, status = $2, updated_at = NOW()
|
|
96
|
-
WHERE id = $3 AND status != 'deleted'`,
|
|
97
|
-
values: [progress, status, id],
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
await pool.query({
|
|
102
|
-
text: `UPDATE cortex_target_assessments
|
|
103
|
-
SET progress = $1, updated_at = NOW()
|
|
104
|
-
WHERE id = $2 AND status != 'deleted'`,
|
|
105
|
-
values: [progress, id],
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
export function setDossier(pool, id, dossier) {
|
|
111
|
-
return tryMutation("targetAssessments.setDossier", async () => {
|
|
112
|
-
await pool.query({
|
|
113
|
-
text: `UPDATE cortex_target_assessments
|
|
114
|
-
SET dossier = $1::jsonb,
|
|
115
|
-
status = 'completed',
|
|
116
|
-
progress = 'Completed',
|
|
117
|
-
error = NULL,
|
|
118
|
-
completed_at = NOW(),
|
|
119
|
-
updated_at = NOW()
|
|
120
|
-
WHERE id = $2 AND status != 'deleted'`,
|
|
121
|
-
values: [JSON.stringify(dossier), id],
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
export function markFailed(pool, id, error) {
|
|
126
|
-
return tryMutation("targetAssessments.markFailed", async () => {
|
|
127
|
-
await pool.query({
|
|
128
|
-
text: `UPDATE cortex_target_assessments
|
|
129
|
-
SET status = 'failed',
|
|
130
|
-
error = $1::jsonb,
|
|
131
|
-
completed_at = NOW(),
|
|
132
|
-
updated_at = NOW()
|
|
133
|
-
WHERE id = $2 AND status != 'deleted'`,
|
|
134
|
-
values: [JSON.stringify(error), id],
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Flip the row to the new `suspended_insufficient_funds` status used when
|
|
140
|
-
* an LLM step self-cancels the workflow on a 402 (budget exhausted).
|
|
141
|
-
* Idempotent on DBOS replay — a second call from the terminal handler on
|
|
142
|
-
* the recovery path is a no-op against an already-suspended row. Soft-
|
|
143
|
-
* deleted rows are NOT resurrected.
|
|
144
|
-
*/
|
|
145
|
-
export function markAssessmentSuspended(pool, id) {
|
|
146
|
-
return tryMutation("targetAssessments.markAssessmentSuspended", async () => {
|
|
147
|
-
await pool.query({
|
|
148
|
-
text: `UPDATE cortex_target_assessments
|
|
149
|
-
SET status = 'suspended_insufficient_funds',
|
|
150
|
-
progress = 'Suspended — insufficient funds',
|
|
151
|
-
updated_at = NOW()
|
|
152
|
-
WHERE id = $1 AND status != 'deleted'`,
|
|
153
|
-
values: [id],
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Flip a suspended row back to `running` on `DBOS.resumeWorkflow`. Called
|
|
159
|
-
* from the resume entry point AFTER the resume call returns success.
|
|
160
|
-
* No-op on a soft-deleted row.
|
|
161
|
-
*/
|
|
162
|
-
export function markAssessmentRunning(pool, id) {
|
|
163
|
-
return tryMutation("targetAssessments.markAssessmentRunning", async () => {
|
|
164
|
-
await pool.query({
|
|
165
|
-
text: `UPDATE cortex_target_assessments
|
|
166
|
-
SET status = 'running',
|
|
167
|
-
progress = 'Resuming',
|
|
168
|
-
error = NULL,
|
|
169
|
-
updated_at = NOW()
|
|
170
|
-
WHERE id = $1 AND status != 'deleted'`,
|
|
171
|
-
values: [id],
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
export function getAssessment(pool, id, organizationId) {
|
|
176
|
-
// The raw read is wrapped; `mapRow`/`mapListRow` runs via `.map()` outside
|
|
177
|
-
// the catch so its corrupt-row throw (missing created_at) propagates as a
|
|
178
|
-
// control-flow throw rather than being captured as a `DbError`.
|
|
179
|
-
return tryQuery("targetAssessments.getAssessment", async () => {
|
|
180
|
-
const result = await pool.query({
|
|
181
|
-
text: `SELECT ${SELECT_COLUMNS}
|
|
182
|
-
FROM cortex_target_assessments
|
|
183
|
-
WHERE id = $1 AND organization_id = $2`,
|
|
184
|
-
values: [id, organizationId],
|
|
185
|
-
});
|
|
186
|
-
return result.rows[0] ?? null;
|
|
187
|
-
}).map((row) => (row ? mapRow(row) : null));
|
|
188
|
-
}
|
|
189
|
-
export function listAssessmentsByOrg(pool, organizationId, options = {}) {
|
|
190
|
-
const limit = options.limit ?? 50;
|
|
191
|
-
const offset = options.offset ?? 0;
|
|
192
|
-
const includeDeleted = options.includeDeleted ?? false;
|
|
193
|
-
const filterDeleted = includeDeleted ? "" : `AND status != 'deleted'`;
|
|
194
|
-
// `mapListRow` runs via `.map()` outside the catch so its corrupt-row throw
|
|
195
|
-
// propagates as a control-flow throw, not a captured `DbError`.
|
|
196
|
-
return tryQuery("targetAssessments.listAssessmentsByOrg", async () => {
|
|
197
|
-
const result = await pool.query({
|
|
198
|
-
text: `SELECT ${SELECT_LIST_COLUMNS}
|
|
199
|
-
FROM cortex_target_assessments
|
|
200
|
-
WHERE organization_id = $1 ${filterDeleted}
|
|
201
|
-
ORDER BY created_at DESC
|
|
202
|
-
LIMIT $2 OFFSET $3`,
|
|
203
|
-
values: [organizationId, limit, offset],
|
|
204
|
-
});
|
|
205
|
-
return result.rows;
|
|
206
|
-
}).map((rows) => rows.map(mapListRow));
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Soft-delete a row. A missing/already-deleted row returns `ok(0)` — absence
|
|
210
|
-
* rides the ok channel; `rowCount === 0` is not a failure.
|
|
211
|
-
*/
|
|
212
|
-
export function softDeleteAssessment(pool, id, organizationId) {
|
|
213
|
-
return tryMutation("targetAssessments.softDeleteAssessment", async () => {
|
|
214
|
-
const result = await pool.query({
|
|
215
|
-
text: `UPDATE cortex_target_assessments
|
|
216
|
-
SET status = 'deleted', updated_at = NOW()
|
|
217
|
-
WHERE id = $1 AND organization_id = $2`,
|
|
218
|
-
values: [id, organizationId],
|
|
219
|
-
});
|
|
220
|
-
return result.rowCount ?? 0;
|
|
221
|
-
});
|
|
222
|
-
}
|