@inflexa-ai/harness 0.35.7 → 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/genai-spans.d.ts +20 -0
- package/dist/loop/genai-spans.js +53 -0
- package/dist/loop/run-agent.d.ts +1 -1
- package/dist/loop/run-agent.js +9 -4
- package/dist/providers/ai-sdk.js +8 -0
- 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 +9 -8
- 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,1282 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Target Dossier — schema-driven document produced by the
|
|
3
|
-
* `executeTargetAssessment` workflow and rendered by the frontend.
|
|
4
|
-
*
|
|
5
|
-
* Single source of truth for the persisted JSONB shape and the rendered
|
|
6
|
-
* UI shape. Both Cortex and the frontend import from this module.
|
|
7
|
-
*
|
|
8
|
-
* Coverage discipline: every section that depends on enrichment data
|
|
9
|
-
* carries a `coverage` discriminator (`available | queried_no_data |
|
|
10
|
-
* not_loaded | filtered`), taken from the shared envelope rather than
|
|
11
|
-
* declared per section. `filtered` says a threshold of ours emptied the
|
|
12
|
-
* section and carries the filter and the count it discarded; a partially
|
|
13
|
-
* filtered `available` section carries `dropped_count`. Sections that fall
|
|
14
|
-
* back to inference also carry an `inference_path`. Aggregate rows preserve
|
|
15
|
-
* their contributing evidence under `evidence: [...]` arrays; rows that
|
|
16
|
-
* assert a liability carry it under `support`.
|
|
17
|
-
*
|
|
18
|
-
* See `TARGET_DOSSIER.md` for the full editorial specification.
|
|
19
|
-
*/
|
|
20
|
-
import { z } from "zod";
|
|
21
|
-
import { OrganSystemSchema } from "./organ-system.js";
|
|
22
|
-
import { SeveritySchema } from "./severity.js";
|
|
23
|
-
// ── Coverage primitives ─────────────────────────────────────────────
|
|
24
|
-
export const CoverageSchema = z.enum(["available", "queried_no_data", "not_loaded", "filtered"]);
|
|
25
|
-
/**
|
|
26
|
-
* Coverage states a per-row marker can take.
|
|
27
|
-
*
|
|
28
|
-
* `filtered` is a section-level outcome only: a filter that removes a row
|
|
29
|
-
* removes the row, so a row that survived to be read can never report it.
|
|
30
|
-
* Derived from the section vocabulary rather than restated, so the two
|
|
31
|
-
* cannot drift into separate lists.
|
|
32
|
-
*/
|
|
33
|
-
export const RowCoverageSchema = CoverageSchema.exclude(["filtered"]);
|
|
34
|
-
const CoverageError = z.object({
|
|
35
|
-
message: z.string(),
|
|
36
|
-
source: z.string().optional(),
|
|
37
|
-
});
|
|
38
|
-
/**
|
|
39
|
-
* Build a discriminated section schema: when `coverage` is `available`,
|
|
40
|
-
* `data` is required and matches the inner schema. Otherwise `data` is
|
|
41
|
-
* absent and an optional `error` payload may be present.
|
|
42
|
-
*
|
|
43
|
-
* `dropped_count` rides on the `available` branch as well as the `filtered`
|
|
44
|
-
* one because a filter that removes some rows still leaves a section whose
|
|
45
|
-
* list is partial — without it, a section that discarded most of its rows
|
|
46
|
-
* reports a clean `available` and overstates its own completeness.
|
|
47
|
-
*/
|
|
48
|
-
function withCoverage(data) {
|
|
49
|
-
return z.discriminatedUnion("coverage", [
|
|
50
|
-
z.object({
|
|
51
|
-
coverage: z.literal("available"),
|
|
52
|
-
data,
|
|
53
|
-
dropped_count: z.number().int().nonnegative().optional(),
|
|
54
|
-
inference_path: z.string().optional(),
|
|
55
|
-
truncated: z.boolean().optional(),
|
|
56
|
-
}),
|
|
57
|
-
z.object({
|
|
58
|
-
coverage: z.literal("queried_no_data"),
|
|
59
|
-
error: CoverageError.optional(),
|
|
60
|
-
inference_path: z.string().optional(),
|
|
61
|
-
}),
|
|
62
|
-
z.object({
|
|
63
|
-
coverage: z.literal("filtered"),
|
|
64
|
-
filter: z.string(),
|
|
65
|
-
dropped_count: z.number().int().nonnegative(),
|
|
66
|
-
inference_path: z.string().optional(),
|
|
67
|
-
}),
|
|
68
|
-
z.object({
|
|
69
|
-
coverage: z.literal("not_loaded"),
|
|
70
|
-
reason: z.string().optional(),
|
|
71
|
-
}),
|
|
72
|
-
]);
|
|
73
|
-
}
|
|
74
|
-
// ── Section blurbs (renderer source of truth) ───────────────────────
|
|
75
|
-
export const SECTION_BLURBS = {
|
|
76
|
-
entity: "Canonical identity for the assessed target, including ontology, identifiers, and synonyms.",
|
|
77
|
-
safety_profile: "Per-organ liabilities derived from genetic, expression, FAERS, and class-precedent evidence. Each row carries an audit trail and a mechanism hypothesis where supportable.",
|
|
78
|
-
therapeutic_area_associations: "Disease and therapeutic-area associations from genetics, literature, and Open Targets evidence rows; intended as orientation for indication selection.",
|
|
79
|
-
indications: "Candidate indications ranked by combined evidence weight; each row carries the contributing evidence sources.",
|
|
80
|
-
clinical_development: "Active and completed trials of modulators of this target, indexed from ClinicalTrials.gov.",
|
|
81
|
-
reference_biology: "Preclinical biology, expression, KO phenotype, and PPI context supporting the target's mechanistic basis.",
|
|
82
|
-
executive_recommendation: "Disposition (pursue / conditional / de-prioritize / insufficient evidence) with cited rationale, key strengths, and key risks.",
|
|
83
|
-
};
|
|
84
|
-
// ── Shared evidence primitives ──────────────────────────────────────
|
|
85
|
-
export const EvidenceItemSchema = z.object({
|
|
86
|
-
pmid: z.string().optional(),
|
|
87
|
-
doi: z.string().optional(),
|
|
88
|
-
accession: z.string().optional(),
|
|
89
|
-
source: z.string(),
|
|
90
|
-
predicate: z.string().optional(),
|
|
91
|
-
score: z.number().optional(),
|
|
92
|
-
strength: z.string().optional(),
|
|
93
|
-
is_human: z.boolean().optional(),
|
|
94
|
-
is_clinical: z.boolean().optional(),
|
|
95
|
-
excerpt: z.string().optional(),
|
|
96
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
97
|
-
regulatory_reference: z
|
|
98
|
-
.object({
|
|
99
|
-
document: z.string(),
|
|
100
|
-
section: z.string().optional(),
|
|
101
|
-
doc_id: z.string().optional(),
|
|
102
|
-
doc_url: z.string().url().optional(),
|
|
103
|
-
})
|
|
104
|
-
.optional(),
|
|
105
|
-
});
|
|
106
|
-
const EvidenceList = z.array(EvidenceItemSchema);
|
|
107
|
-
/**
|
|
108
|
-
* Evidence attached to a claim, which must resolve to something a reader can
|
|
109
|
-
* actually go and check. `source` alone names a provenance bucket, not a
|
|
110
|
-
* record, so it does not qualify on its own.
|
|
111
|
-
*
|
|
112
|
-
* The constraint lives here rather than on `EvidenceItemSchema` because that
|
|
113
|
-
* shape is shared with sections whose producers legitimately carry looser
|
|
114
|
-
* evidence; tightening it globally would break them, and loosening the claim
|
|
115
|
-
* to match them would defeat the point.
|
|
116
|
-
*/
|
|
117
|
-
export const ClaimEvidenceSchema = EvidenceItemSchema.refine((e) => Boolean(e.pmid ?? e.doi ?? e.accession ?? e.regulatory_reference), {
|
|
118
|
-
message: "claim evidence needs a locator: pmid, doi, accession, or regulatory_reference",
|
|
119
|
-
});
|
|
120
|
-
/**
|
|
121
|
-
* What backs a claim: either evidence, or a stated reason there is none.
|
|
122
|
-
*
|
|
123
|
-
* Discriminating on `state` — rather than making `evidence` nullable and
|
|
124
|
-
* checking it at runtime — is what makes a scored claim with no evidence
|
|
125
|
-
* unconstructable rather than merely invalid. `unknown` is a complete,
|
|
126
|
-
* first-class outcome: an invariant that is harder to satisfy honestly than
|
|
127
|
-
* dishonestly makes the data worse, not better.
|
|
128
|
-
*/
|
|
129
|
-
export const ClaimSupportSchema = z.discriminatedUnion("state", [
|
|
130
|
-
z.object({
|
|
131
|
-
state: z.literal("scored"),
|
|
132
|
-
evidence: z.array(ClaimEvidenceSchema).min(1),
|
|
133
|
-
}),
|
|
134
|
-
z.object({
|
|
135
|
-
state: z.literal("unknown"),
|
|
136
|
-
reason: z.string().min(1),
|
|
137
|
-
}),
|
|
138
|
-
]);
|
|
139
|
-
/**
|
|
140
|
-
* Attach support to a claim body.
|
|
141
|
-
*
|
|
142
|
-
* The support rides as a field rather than wrapping the body, because these
|
|
143
|
-
* rows *are* the assertion: a reader of an unevidenced liability still needs
|
|
144
|
-
* the liability. Swapping the body out on the unknown branch would leave
|
|
145
|
-
* nothing to read.
|
|
146
|
-
*/
|
|
147
|
-
function withEvidence(body) {
|
|
148
|
-
return body.extend({ support: ClaimSupportSchema });
|
|
149
|
-
}
|
|
150
|
-
// ── §1.1 Entity ─────────────────────────────────────────────────────
|
|
151
|
-
export const EntitySchema = z.object({
|
|
152
|
-
canonicalId: z.string(),
|
|
153
|
-
symbol: z.string(),
|
|
154
|
-
displayName: z.string(),
|
|
155
|
-
/**
|
|
156
|
-
* Cortex's Phase-0 resolver only emits `"gene"`. The schema keeps the
|
|
157
|
-
* broader enum so protein-, rna- and complex-level entities are
|
|
158
|
-
* expressible once the resolver supports them.
|
|
159
|
-
*/
|
|
160
|
-
entityType: z.enum(["gene", "protein", "rna", "complex"]),
|
|
161
|
-
ontology: z.string(),
|
|
162
|
-
identifiers: z.object({
|
|
163
|
-
hgnc: z.string().optional(),
|
|
164
|
-
ensembl: z.string().optional(),
|
|
165
|
-
uniprot: z.string().optional(),
|
|
166
|
-
chembl: z.string().optional(),
|
|
167
|
-
ncbiGene: z.string().optional(),
|
|
168
|
-
}),
|
|
169
|
-
synonyms: z.array(z.string()).default([]),
|
|
170
|
-
proteinFamily: z.string().optional(),
|
|
171
|
-
});
|
|
172
|
-
// ── §2.1 Liability summary ──────────────────────────────────────────
|
|
173
|
-
/**
|
|
174
|
-
* The assertion a liability bullet makes, without its support.
|
|
175
|
-
*
|
|
176
|
-
* Named separately because a dropped bullet records the same assertion
|
|
177
|
-
* plus the reason it was dropped, and carries no support of its own.
|
|
178
|
-
*/
|
|
179
|
-
export const LiabilityBulletBodySchema = z.object({
|
|
180
|
-
text: z.string(),
|
|
181
|
-
rationale: z.string(),
|
|
182
|
-
category: z.enum(["fatal_post_market", "class_liability", "off_target_safety", "high_safety_organ_expression", "broad_expression", "other"]),
|
|
183
|
-
});
|
|
184
|
-
export const LiabilityBulletSchema = withEvidence(LiabilityBulletBodySchema);
|
|
185
|
-
/**
|
|
186
|
-
* Liability bullets removed post-synthesis because they cited a PMID the
|
|
187
|
-
* direction-of-effect auditor found contradicts the bullet's framing.
|
|
188
|
-
* Mirrors `executive_recommendation.key_risks_dropped` — the dropped
|
|
189
|
-
* content is preserved with the reason for an audit trail.
|
|
190
|
-
*/
|
|
191
|
-
export const LiabilityBulletDroppedSchema = LiabilityBulletBodySchema.extend({
|
|
192
|
-
reason: z.string(),
|
|
193
|
-
cited_pmid: z.string().optional(),
|
|
194
|
-
});
|
|
195
|
-
/** `.strict()` rejects the derived counters, which live in the derived sub-tree. */
|
|
196
|
-
export const LiabilitySummarySchema = z
|
|
197
|
-
.object({
|
|
198
|
-
liability_bullets: z.array(LiabilityBulletSchema),
|
|
199
|
-
liability_bullets_dropped: z.array(LiabilityBulletDroppedSchema).optional(),
|
|
200
|
-
modality_recommendation: z.string().nullable(),
|
|
201
|
-
same_class_drug_count: z.number().int().nonnegative(),
|
|
202
|
-
inferred_therapeutic_area: z.string().nullable(),
|
|
203
|
-
no_liabilities_disclosure: z.string().optional(),
|
|
204
|
-
})
|
|
205
|
-
.strict();
|
|
206
|
-
// ── §2.2 Tractability ───────────────────────────────────────────────
|
|
207
|
-
export const TractabilityModalitySchema = z.object({
|
|
208
|
-
/**
|
|
209
|
-
* Phase-4 emits `small_molecule`, `antibody`, and `other_clinical`. The
|
|
210
|
-
* remaining variants belong to the Phase-2 modality fallback (PROTAC,
|
|
211
|
-
* oligonucleotide, peptide libraries).
|
|
212
|
-
*/
|
|
213
|
-
modality: z.enum(["small_molecule", "antibody", "protac", "other_clinical", "oligonucleotide", "peptide"]),
|
|
214
|
-
levels: z.array(z.string()),
|
|
215
|
-
has_approved_drug: z.boolean(),
|
|
216
|
-
has_clinical_stage: z.boolean(),
|
|
217
|
-
is_inferred_from_family: z.boolean(),
|
|
218
|
-
approved_drug_ids: z.array(z.string()).optional(),
|
|
219
|
-
note: z.string().optional(),
|
|
220
|
-
});
|
|
221
|
-
const MOLECULE_TYPE_TO_MODALITY = {
|
|
222
|
-
Protein: ["peptide", "antibody"],
|
|
223
|
-
"Small molecule": ["small_molecule"],
|
|
224
|
-
Oligonucleotide: ["oligonucleotide"],
|
|
225
|
-
};
|
|
226
|
-
export const TractabilitySchema = withCoverage(z
|
|
227
|
-
.object({
|
|
228
|
-
modalities: z.array(TractabilityModalitySchema),
|
|
229
|
-
preferred_modality: z.string().nullable(),
|
|
230
|
-
drug_molecule_types_present: z.array(z.string()).default([]),
|
|
231
|
-
})
|
|
232
|
-
.superRefine((v, ctx) => {
|
|
233
|
-
for (const moleculeType of v.drug_molecule_types_present) {
|
|
234
|
-
const allowed = MOLECULE_TYPE_TO_MODALITY[moleculeType];
|
|
235
|
-
if (!allowed)
|
|
236
|
-
continue; // unknown molecule_type — skip rather than block
|
|
237
|
-
const hasMatching = v.modalities.some((m) => allowed.includes(m.modality) && !m.is_inferred_from_family);
|
|
238
|
-
if (!hasMatching) {
|
|
239
|
-
ctx.addIssue({
|
|
240
|
-
code: "custom",
|
|
241
|
-
message: `drug molecule_type "${moleculeType}" has no matching enumerated modality row`,
|
|
242
|
-
path: ["modalities"],
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}));
|
|
247
|
-
// ── §2.3 Indications ────────────────────────────────────────────────
|
|
248
|
-
export const IndicationRowSchema = z.object({
|
|
249
|
-
disease_id: z.string(),
|
|
250
|
-
disease_name: z.string(),
|
|
251
|
-
composite_score: z.number(),
|
|
252
|
-
composite_score_breakdown: z
|
|
253
|
-
.object({
|
|
254
|
-
base: z.number(),
|
|
255
|
-
source_bonuses: z.record(z.string(), z.number()),
|
|
256
|
-
paper_depth: z.number(),
|
|
257
|
-
})
|
|
258
|
-
.optional(),
|
|
259
|
-
evidence_score: z.number(),
|
|
260
|
-
source_count: z.number().int().nonnegative(),
|
|
261
|
-
unique_paper_count: z.number().int().nonnegative(),
|
|
262
|
-
sources: z.array(z.string()),
|
|
263
|
-
evidence: EvidenceList,
|
|
264
|
-
});
|
|
265
|
-
export const IndicationsSchema = withCoverage(z.object({
|
|
266
|
-
rows: z.array(IndicationRowSchema),
|
|
267
|
-
excluded_unsupported_count: z.number().int().nonnegative().optional(),
|
|
268
|
-
unsupported_associations: z
|
|
269
|
-
.array(z.object({
|
|
270
|
-
disease_id: z.string(),
|
|
271
|
-
disease_name: z.string(),
|
|
272
|
-
}))
|
|
273
|
-
.optional(),
|
|
274
|
-
}));
|
|
275
|
-
// ── §2.4 Drug interactions ──────────────────────────────────────────
|
|
276
|
-
export const DrugInteractionRowSchema = z.object({
|
|
277
|
-
drug_id: z.string().nullable(),
|
|
278
|
-
drug_name: z.string(),
|
|
279
|
-
best_score: z.number(),
|
|
280
|
-
predicates: z.array(z.string()),
|
|
281
|
-
sources: z.array(z.string()),
|
|
282
|
-
paper_count: z.number().int().nonnegative(),
|
|
283
|
-
dominant_direction: z.enum(["positive", "negative", "mixed", "unknown"]),
|
|
284
|
-
has_human_evidence: z.boolean(),
|
|
285
|
-
has_clinical_evidence: z.boolean(),
|
|
286
|
-
evidence: EvidenceList,
|
|
287
|
-
});
|
|
288
|
-
export const DrugInteractionsSchema = withCoverage(z.object({ rows: z.array(DrugInteractionRowSchema) }));
|
|
289
|
-
// ── §2.5 Clinical development ───────────────────────────────────────
|
|
290
|
-
export const ClinicalTrialAttributionBasisSchema = z.object({
|
|
291
|
-
kind: z.enum([
|
|
292
|
-
"mechanism_target_match",
|
|
293
|
-
"known_class_drug",
|
|
294
|
-
"therapeutic_program_match",
|
|
295
|
-
"related_family_target",
|
|
296
|
-
"biomarker_endpoint",
|
|
297
|
-
"condition_only",
|
|
298
|
-
"text_match",
|
|
299
|
-
"manual",
|
|
300
|
-
]),
|
|
301
|
-
source: z.string(),
|
|
302
|
-
excerpt: z.string().optional(),
|
|
303
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
304
|
-
});
|
|
305
|
-
export const ResolvedTrialInterventionSchema = z.object({
|
|
306
|
-
name: z.string(),
|
|
307
|
-
intervention_type: z.string().nullable().optional(),
|
|
308
|
-
chembl_id: z.string().nullable().optional(),
|
|
309
|
-
therapeutic_program_id: z.string().nullable().optional(),
|
|
310
|
-
target_uniprots: z.array(z.string()).default([]),
|
|
311
|
-
resolver_source: z.string(),
|
|
312
|
-
});
|
|
313
|
-
export const ClinicalTrialAttributionSchema = z
|
|
314
|
-
.object({
|
|
315
|
-
relationship: z.enum([
|
|
316
|
-
"direct_modulator",
|
|
317
|
-
"class_modulator",
|
|
318
|
-
"related_family_target",
|
|
319
|
-
"target_biomarker",
|
|
320
|
-
"pathway_biomarker",
|
|
321
|
-
"condition_only",
|
|
322
|
-
"unrelated",
|
|
323
|
-
"unknown",
|
|
324
|
-
]),
|
|
325
|
-
evidence_role: z.enum(["supports_target", "contextual", "excluded"]),
|
|
326
|
-
basis: z.array(ClinicalTrialAttributionBasisSchema).min(1),
|
|
327
|
-
resolved_interventions: z.array(ResolvedTrialInterventionSchema).default([]),
|
|
328
|
-
exclusion_reason: z.string().optional(),
|
|
329
|
-
})
|
|
330
|
-
.superRefine((v, ctx) => {
|
|
331
|
-
if (v.evidence_role !== "supports_target" && !v.exclusion_reason) {
|
|
332
|
-
ctx.addIssue({
|
|
333
|
-
code: "custom",
|
|
334
|
-
message: "contextual/excluded trial evidence requires exclusion_reason",
|
|
335
|
-
path: ["exclusion_reason"],
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
if (v.evidence_role === "supports_target" && v.relationship !== "direct_modulator" && v.relationship !== "class_modulator") {
|
|
339
|
-
ctx.addIssue({
|
|
340
|
-
code: "custom",
|
|
341
|
-
message: "supports_target evidence must be direct_modulator or class_modulator",
|
|
342
|
-
path: ["relationship"],
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
});
|
|
346
|
-
export const ClinicalTrialRowSchema = z.object({
|
|
347
|
-
nct_id: z.string(),
|
|
348
|
-
title: z.string(),
|
|
349
|
-
phase: z.string().nullable(),
|
|
350
|
-
status: z.string(),
|
|
351
|
-
conditions: z.array(z.string()),
|
|
352
|
-
start_date: z.string().nullable(),
|
|
353
|
-
completion_date: z.string().nullable(),
|
|
354
|
-
match_confidence: z.enum(["high", "medium", "low", "off_target"]),
|
|
355
|
-
attribution: ClinicalTrialAttributionSchema,
|
|
356
|
-
eligible_for_toxicology_aggregation: z.boolean(),
|
|
357
|
-
});
|
|
358
|
-
export const TrialOutcomeRowSchema = z.object({
|
|
359
|
-
nct_id: z.string(),
|
|
360
|
-
measure: z.string(),
|
|
361
|
-
outcome_type: z.enum(["primary", "secondary", "other"]),
|
|
362
|
-
description: z.string().optional(),
|
|
363
|
-
time_frame: z.string().optional(),
|
|
364
|
-
effect: z.discriminatedUnion("kind", [
|
|
365
|
-
z.object({
|
|
366
|
-
kind: z.literal("quantitative"),
|
|
367
|
-
value: z.number(),
|
|
368
|
-
ci_low: z.number().optional(),
|
|
369
|
-
ci_high: z.number().optional(),
|
|
370
|
-
units: z.string(),
|
|
371
|
-
}),
|
|
372
|
-
z.object({
|
|
373
|
-
kind: z.literal("qualitative"),
|
|
374
|
-
direction: z.enum(["increase", "decrease", "no_change"]),
|
|
375
|
-
magnitude_label: z.string(),
|
|
376
|
-
}),
|
|
377
|
-
z.object({
|
|
378
|
-
kind: z.literal("not_extracted"),
|
|
379
|
-
reason: z.enum(["ctgov_no_numeric_result", "ctgov_no_result_groups", "parse_failure"]),
|
|
380
|
-
}),
|
|
381
|
-
]),
|
|
382
|
-
attribution: ClinicalTrialAttributionSchema.optional(),
|
|
383
|
-
eligible_for_toxicology_aggregation: z.boolean().default(false),
|
|
384
|
-
});
|
|
385
|
-
const FailureCategorySchema = z.discriminatedUnion("category", [
|
|
386
|
-
z.object({ category: z.literal("safety"), safety_evidence_excerpt: z.string() }),
|
|
387
|
-
z.object({ category: z.literal("strategic"), category_evidence_excerpt: z.string() }),
|
|
388
|
-
z.object({ category: z.literal("operational"), category_evidence_excerpt: z.string() }),
|
|
389
|
-
z.object({ category: z.literal("efficacy"), category_evidence_excerpt: z.string() }),
|
|
390
|
-
]);
|
|
391
|
-
export const FailedTrialRowSchema = z
|
|
392
|
-
.object({
|
|
393
|
-
nct_id: z.string(),
|
|
394
|
-
title: z.string(),
|
|
395
|
-
why_stopped: z.string(),
|
|
396
|
-
classifier: z.enum(["rules", "llm"]),
|
|
397
|
-
failure_category: FailureCategorySchema,
|
|
398
|
-
attribution: ClinicalTrialAttributionSchema,
|
|
399
|
-
eligible_for_toxicology_aggregation: z.boolean(),
|
|
400
|
-
})
|
|
401
|
-
.superRefine((v, ctx) => {
|
|
402
|
-
const excerpt = "safety_evidence_excerpt" in v.failure_category ? v.failure_category.safety_evidence_excerpt : v.failure_category.category_evidence_excerpt;
|
|
403
|
-
// CT.gov sometimes terminates trials without recording a `whyStopped`
|
|
404
|
-
// reason. In that case the assembler emits `why_stopped: ""` with
|
|
405
|
-
// `category: "operational"` and `excerpt: ""` — there's nothing to
|
|
406
|
-
// classify and nothing to excerpt. Both being empty is the valid
|
|
407
|
-
// "no recorded reason" shape; mismatched emptiness is an error.
|
|
408
|
-
const whyEmpty = v.why_stopped.length === 0;
|
|
409
|
-
const excerptEmpty = excerpt.length === 0;
|
|
410
|
-
if (whyEmpty !== excerptEmpty) {
|
|
411
|
-
ctx.addIssue({
|
|
412
|
-
code: "custom",
|
|
413
|
-
message: whyEmpty
|
|
414
|
-
? `failure_category evidence_excerpt is non-empty but why_stopped is empty`
|
|
415
|
-
: `failure_category evidence_excerpt is empty but why_stopped is non-empty`,
|
|
416
|
-
path: ["failure_category"],
|
|
417
|
-
});
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
if (whyEmpty)
|
|
421
|
-
return;
|
|
422
|
-
if (!v.why_stopped.toLowerCase().includes(excerpt.toLowerCase())) {
|
|
423
|
-
ctx.addIssue({
|
|
424
|
-
code: "custom",
|
|
425
|
-
message: `failure_category evidence_excerpt is not a substring of why_stopped`,
|
|
426
|
-
path: ["failure_category"],
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
export const ClinicalBenchmarksSchema = z.object({
|
|
431
|
-
therapeutic_area: z.string().nullable(),
|
|
432
|
-
fallback_to_all_areas: z.boolean(),
|
|
433
|
-
phase_transitions: z.record(z.string(), z.number()),
|
|
434
|
-
likelihood_of_approval: z.number().nullable(),
|
|
435
|
-
source_attribution: z.string(),
|
|
436
|
-
});
|
|
437
|
-
export const ClinicalDevelopmentSchema = z.object({
|
|
438
|
-
trials: withCoverage(z.object({
|
|
439
|
-
rows: z.array(ClinicalTrialRowSchema),
|
|
440
|
-
excluded_rows: z.array(ClinicalTrialRowSchema).default([]),
|
|
441
|
-
selection_criteria: z
|
|
442
|
-
.object({
|
|
443
|
-
derived_from: z.string().optional(),
|
|
444
|
-
min_confidence: z.string().optional(),
|
|
445
|
-
excluded_off_target_count: z.number().int().nonnegative().optional(),
|
|
446
|
-
})
|
|
447
|
-
.optional(),
|
|
448
|
-
})),
|
|
449
|
-
outcomes: withCoverage(z.object({ rows: z.array(TrialOutcomeRowSchema) })),
|
|
450
|
-
failed_trials: withCoverage(z.object({
|
|
451
|
-
rows: z.array(FailedTrialRowSchema),
|
|
452
|
-
excluded_rows: z.array(FailedTrialRowSchema).default([]),
|
|
453
|
-
})),
|
|
454
|
-
benchmarks: ClinicalBenchmarksSchema,
|
|
455
|
-
});
|
|
456
|
-
// ── §2.6 Safety profile ─────────────────────────────────────────────
|
|
457
|
-
export const OrganRiskRowSchema = z.object({
|
|
458
|
-
organ: OrganSystemSchema,
|
|
459
|
-
risk_level: z.enum(["high", "medium", "low"]),
|
|
460
|
-
signal_type_count: z.number().int().nonnegative(),
|
|
461
|
-
signals: z.object({
|
|
462
|
-
chembl_polypharm_count: z.number().int().nonnegative(),
|
|
463
|
-
faers_count: z.number().int().nonnegative(),
|
|
464
|
-
trial_ae_count: z.number().int().nonnegative(),
|
|
465
|
-
class_liability_present: z.boolean(),
|
|
466
|
-
}),
|
|
467
|
-
evidence: EvidenceList,
|
|
468
|
-
});
|
|
469
|
-
const SeriousnessAvailableSchema = z
|
|
470
|
-
.object({
|
|
471
|
-
coverage: z.literal("available"),
|
|
472
|
-
total_reports: z.number().int().nonnegative(),
|
|
473
|
-
by_seriousness: z.object({
|
|
474
|
-
death: z.number().int().nonnegative(),
|
|
475
|
-
life_threatening: z.number().int().nonnegative(),
|
|
476
|
-
hospitalization: z.number().int().nonnegative(),
|
|
477
|
-
disabling: z.number().int().nonnegative(),
|
|
478
|
-
other_serious: z.number().int().nonnegative(),
|
|
479
|
-
congenital_anomaly: z.number().int().nonnegative(),
|
|
480
|
-
}),
|
|
481
|
-
fatal_report_count: z.number().int().nonnegative(),
|
|
482
|
-
})
|
|
483
|
-
.superRefine((v, ctx) => {
|
|
484
|
-
// NOTE: by_seriousness categories are independent binary flags on
|
|
485
|
-
// each FAERS report — a single fatal hospitalisation increments
|
|
486
|
-
// both `death` and `hospitalization` — so sum(by_seriousness) can
|
|
487
|
-
// legitimately exceed total_reports. Do not invariant on the sum.
|
|
488
|
-
if (v.fatal_report_count > v.by_seriousness.death) {
|
|
489
|
-
ctx.addIssue({
|
|
490
|
-
code: "custom",
|
|
491
|
-
message: `fatal_report_count=${v.fatal_report_count} exceeds by_seriousness.death=${v.by_seriousness.death}`,
|
|
492
|
-
path: ["fatal_report_count"],
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
const sum = v.by_seriousness.death +
|
|
496
|
-
v.by_seriousness.life_threatening +
|
|
497
|
-
v.by_seriousness.hospitalization +
|
|
498
|
-
v.by_seriousness.disabling +
|
|
499
|
-
v.by_seriousness.other_serious +
|
|
500
|
-
v.by_seriousness.congenital_anomaly;
|
|
501
|
-
if (v.total_reports > 1000 && sum === 0) {
|
|
502
|
-
ctx.addIssue({
|
|
503
|
-
code: "custom",
|
|
504
|
-
message: `seriousness is all-zero across ${v.total_reports} reports; emit coverage:"queried_no_data" instead of fabricated zeros`,
|
|
505
|
-
path: ["by_seriousness"],
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
const SeriousnessQueriedNoDataSchema = z.object({
|
|
510
|
-
coverage: z.literal("queried_no_data"),
|
|
511
|
-
total_reports: z.number().int().nonnegative(),
|
|
512
|
-
});
|
|
513
|
-
export const FaersSummarySchema = z.object({
|
|
514
|
-
total_reports: z.number().int().nonnegative(),
|
|
515
|
-
seriousness: z.discriminatedUnion("coverage", [SeriousnessAvailableSchema, SeriousnessQueriedNoDataSchema]),
|
|
516
|
-
top_signals: z.array(z.object({
|
|
517
|
-
meddra_term: z.string(),
|
|
518
|
-
organ: z.string().optional(),
|
|
519
|
-
report_count: z.number().int().nonnegative(),
|
|
520
|
-
proportional_reporting_ratio: z.number().optional(),
|
|
521
|
-
})),
|
|
522
|
-
per_modulator: z.array(z.object({
|
|
523
|
-
modulator: z.string(),
|
|
524
|
-
modulator_id: z.string().nullable(),
|
|
525
|
-
report_count: z.number().int().nonnegative(),
|
|
526
|
-
coverage: RowCoverageSchema,
|
|
527
|
-
})),
|
|
528
|
-
});
|
|
529
|
-
export const TrialAesSchema = z.object({
|
|
530
|
-
serious: z.array(z.object({
|
|
531
|
-
term: z.string(),
|
|
532
|
-
incidence_pct: z.number(),
|
|
533
|
-
organ: z.string().optional(),
|
|
534
|
-
nct_ids: z.array(z.string()),
|
|
535
|
-
})),
|
|
536
|
-
non_serious: z.array(z.object({
|
|
537
|
-
term: z.string(),
|
|
538
|
-
incidence_pct: z.number(),
|
|
539
|
-
organ: z.string().optional(),
|
|
540
|
-
nct_ids: z.array(z.string()),
|
|
541
|
-
})),
|
|
542
|
-
/** Floor pct for non-serious AEs, low enough to keep real low-incidence AEs. */
|
|
543
|
-
non_serious_floor_pct: z.number().nonnegative().default(2),
|
|
544
|
-
/**
|
|
545
|
-
* Count of rows dropped by the catch-all-bucket filter (e.g., "Other"
|
|
546
|
-
* term or term equal to organ system). Surfaced for auditability.
|
|
547
|
-
*/
|
|
548
|
-
dropped_uninformative_count: z.number().int().nonnegative().optional(),
|
|
549
|
-
coverage_qualifier: z
|
|
550
|
-
.object({
|
|
551
|
-
trials_queried: z.number().int().nonnegative(),
|
|
552
|
-
trials_with_ae_data: z.number().int().nonnegative(),
|
|
553
|
-
serious_floor_applied: z.number().optional(),
|
|
554
|
-
non_serious_floor_pct_applied: z.number().optional(),
|
|
555
|
-
})
|
|
556
|
-
.optional(),
|
|
557
|
-
});
|
|
558
|
-
const SelectivitySchema = z.union([
|
|
559
|
-
z.object({
|
|
560
|
-
vs_primary_potency: z.object({
|
|
561
|
-
primary_pchembl_used: z.number(),
|
|
562
|
-
primary_source: z.enum(["chembl_target_drug_indication", "literature_curated"]),
|
|
563
|
-
fold: z.number().positive().finite(),
|
|
564
|
-
log_units: z.number(),
|
|
565
|
-
}),
|
|
566
|
-
}),
|
|
567
|
-
z.object({
|
|
568
|
-
selectivity_unknown: z.literal(true),
|
|
569
|
-
reason: z.string().min(1),
|
|
570
|
-
}),
|
|
571
|
-
]);
|
|
572
|
-
/**
|
|
573
|
-
* The measured facts about an off-target hit, shared by the rows that
|
|
574
|
-
* assert a liability and the rows that record why one was set aside.
|
|
575
|
-
*/
|
|
576
|
-
export const OffTargetRowBodySchema = z.object({
|
|
577
|
-
off_target_id: z.string().nullable(),
|
|
578
|
-
off_target_name: z.string(),
|
|
579
|
-
target_class: z.string().optional(),
|
|
580
|
-
pchembl: z.number(),
|
|
581
|
-
is_safety_panel_target: z.boolean(),
|
|
582
|
-
organ_system: OrganSystemSchema.nullable(),
|
|
583
|
-
clinical_consequence: z.string().nullable(),
|
|
584
|
-
selectivity: SelectivitySchema,
|
|
585
|
-
selectivity_window_below_threshold: z.boolean(),
|
|
586
|
-
metadata: z.object({ merged_chembl_ids: z.array(z.string()).optional() }).optional(),
|
|
587
|
-
});
|
|
588
|
-
/** The claim's own evidence rides in `support`, so the row carries no second evidence list. */
|
|
589
|
-
export const OffTargetRowSchema = withEvidence(OffTargetRowBodySchema.extend({ relationship: z.literal("off_target") }));
|
|
590
|
-
/**
|
|
591
|
-
* Excluded rows record why something was set aside rather than asserting a
|
|
592
|
-
* liability, so they carry evidence without the claim contract.
|
|
593
|
-
*/
|
|
594
|
-
export const ExcludedOffTargetRowSchema = OffTargetRowBodySchema.extend({
|
|
595
|
-
relationship: z.enum(["intended_co_target", "on_target_self_hit", "obligate_cofactor"]),
|
|
596
|
-
reason: z.string().min(1),
|
|
597
|
-
evidence: EvidenceList,
|
|
598
|
-
});
|
|
599
|
-
export const OffTargetPanelSchema = z.object({
|
|
600
|
-
rows: z.array(OffTargetRowSchema),
|
|
601
|
-
excluded_rows: z.array(ExcludedOffTargetRowSchema),
|
|
602
|
-
});
|
|
603
|
-
export const ClassLiabilityOrganSchema = z.object({
|
|
604
|
-
organ: OrganSystemSchema,
|
|
605
|
-
drug_count_in_class: z.number().int().nonnegative(),
|
|
606
|
-
drugs_with_signal: z.number().int().nonnegative(),
|
|
607
|
-
signal_fraction: z.number(),
|
|
608
|
-
is_class_liability: z.boolean(),
|
|
609
|
-
suppressed_reason: z.string().nullable(),
|
|
610
|
-
top_aes: z.array(z.object({
|
|
611
|
-
term: z.string(),
|
|
612
|
-
report_count: z.number().int().nonnegative(),
|
|
613
|
-
})),
|
|
614
|
-
});
|
|
615
|
-
export const ClassPrecedentSchema = z.object({
|
|
616
|
-
drugs_in_class: z.array(z.object({
|
|
617
|
-
drug_id: z.string().nullable(),
|
|
618
|
-
drug_name: z.string(),
|
|
619
|
-
max_phase: z.number().int(),
|
|
620
|
-
mechanism: z.string().optional(),
|
|
621
|
-
})),
|
|
622
|
-
per_organ: z.array(ClassLiabilityOrganSchema),
|
|
623
|
-
});
|
|
624
|
-
export const SafetyFlagSchema = withEvidence(z.object({
|
|
625
|
-
organ: OrganSystemSchema,
|
|
626
|
-
trail: z.string(),
|
|
627
|
-
mechanism_hypothesis: z.string().nullable().optional(),
|
|
628
|
-
severity: SeveritySchema,
|
|
629
|
-
}));
|
|
630
|
-
export const RegulatoryActionRowSchema = z
|
|
631
|
-
.object({
|
|
632
|
-
drug_chembl_id: z.string(),
|
|
633
|
-
drug_name: z.string(),
|
|
634
|
-
agency: z.enum(["FDA", "EMA", "MHRA", "PMDA", "Health Canada", "TGA"]),
|
|
635
|
-
action_kind: z.enum(["referral", "withdrawal", "indication_restriction", "REMS", "black_box", "DHCP", "safety_communication", "label_warning"]),
|
|
636
|
-
action_date: z.string(),
|
|
637
|
-
finding: z.string(),
|
|
638
|
-
source_url: z.string().url().optional(),
|
|
639
|
-
source_kind: z.enum(["label_warning", "referral", "rems", "withdrawal", "boxed_warning", "safety_communication"]),
|
|
640
|
-
application_number: z.string().optional(),
|
|
641
|
-
label_section: z.string().optional(),
|
|
642
|
-
source_date: z.string().optional(),
|
|
643
|
-
evidence: EvidenceList,
|
|
644
|
-
})
|
|
645
|
-
.superRefine((v, ctx) => {
|
|
646
|
-
if (v.source_kind === "label_warning" && v.action_kind === "safety_communication") {
|
|
647
|
-
ctx.addIssue({
|
|
648
|
-
code: "custom",
|
|
649
|
-
message: "FDA label warnings must not be represented as action_kind:safety_communication",
|
|
650
|
-
path: ["action_kind"],
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
/**
|
|
655
|
-
* One warning fragment from an FDA label, attributed to a canonical organ.
|
|
656
|
-
*
|
|
657
|
-
* The application number plus the label section are what let a reader open the
|
|
658
|
-
* label and find the excerpt again, which is why the row carries both rather
|
|
659
|
-
* than a bare drug name.
|
|
660
|
-
*/
|
|
661
|
-
export const RegulatoryOrganSignalRowSchema = withEvidence(z.object({
|
|
662
|
-
organ: OrganSystemSchema,
|
|
663
|
-
drug_name: z.string(),
|
|
664
|
-
agency: z.literal("FDA"),
|
|
665
|
-
application_number: z.string(),
|
|
666
|
-
label_section: z.string(),
|
|
667
|
-
excerpt: z.string(),
|
|
668
|
-
}));
|
|
669
|
-
export const RegulatoryOrganSignalsSchema = withCoverage(z.object({ rows: z.array(RegulatoryOrganSignalRowSchema) }));
|
|
670
|
-
export const SafetyProfileSchema = z.object({
|
|
671
|
-
organ_rollup: withCoverage(z.object({ rows: z.array(OrganRiskRowSchema) })),
|
|
672
|
-
faers: withCoverage(FaersSummarySchema),
|
|
673
|
-
trial_aes: withCoverage(TrialAesSchema),
|
|
674
|
-
off_target_panel: withCoverage(OffTargetPanelSchema),
|
|
675
|
-
failed_trials_safety_lens: withCoverage(z.object({
|
|
676
|
-
rows: z.array(FailedTrialRowSchema),
|
|
677
|
-
excluded_rows: z.array(FailedTrialRowSchema).default([]),
|
|
678
|
-
})),
|
|
679
|
-
class_precedent: withCoverage(ClassPrecedentSchema),
|
|
680
|
-
target_organ_liabilities: withCoverage(z.object({ rows: z.array(SafetyFlagSchema) })),
|
|
681
|
-
regulatory_actions: withCoverage(z.object({ rows: z.array(RegulatoryActionRowSchema) })).optional(),
|
|
682
|
-
regulatory_organ_signals: RegulatoryOrganSignalsSchema.optional(),
|
|
683
|
-
});
|
|
684
|
-
// ── §2.6a Safety corroboration spine ────────────────────────────────
|
|
685
|
-
/**
|
|
686
|
-
* One source's signal for one organ.
|
|
687
|
-
*
|
|
688
|
-
* The source names itself in a field rather than occupying a field of its own.
|
|
689
|
-
* That is what keeps the contributing set open: a source that did not exist
|
|
690
|
-
* when this shape was written contributes by emitting a record, with no schema
|
|
691
|
-
* edit, no new branch, and no consumer change. A struct with one field per
|
|
692
|
-
* source would instead force a schema change per source — and, because a new
|
|
693
|
-
* source arriving before its field exists has nowhere to land, would invite a
|
|
694
|
-
* second, narrower evidence path alongside this one.
|
|
695
|
-
*
|
|
696
|
-
* `evidence` is claim evidence, so a contribution always resolves to a record a
|
|
697
|
-
* reader can go and check.
|
|
698
|
-
*/
|
|
699
|
-
export const OrganSignalContributionSchema = z.object({
|
|
700
|
-
/** Stable id of the source that produced the signal. */
|
|
701
|
-
source: z.string().min(1),
|
|
702
|
-
/** What this source says about the organ, in the source's own terms. */
|
|
703
|
-
signal: z.string().min(1),
|
|
704
|
-
evidence: ClaimEvidenceSchema,
|
|
705
|
-
});
|
|
706
|
-
/**
|
|
707
|
-
* The corroboration record for one organ: which sources carry a signal for it,
|
|
708
|
-
* how many of them are independent, and what each contributed.
|
|
709
|
-
*
|
|
710
|
-
* `independent_source_count` counts distinct source ids, not contributions — a
|
|
711
|
-
* source carrying three label warnings for one organ is still one source. The
|
|
712
|
-
* claim's evidence is the contributions' evidence, which is why a signal with
|
|
713
|
-
* no locator is never admitted as a contribution in the first place.
|
|
714
|
-
*/
|
|
715
|
-
export const OrganCorroborationRowSchema = withEvidence(z.object({
|
|
716
|
-
organ: OrganSystemSchema,
|
|
717
|
-
contributions: z.array(OrganSignalContributionSchema).min(1),
|
|
718
|
-
corroborating_sources: z.array(z.string().min(1)).min(1),
|
|
719
|
-
independent_source_count: z.number().int().positive(),
|
|
720
|
-
}));
|
|
721
|
-
/**
|
|
722
|
-
* `dropped_count` on this section counts *signals* — the fold's input unit —
|
|
723
|
-
* discarded because their organ did not resolve, because they carried no
|
|
724
|
-
* locator, or because their organ drew fewer than `min_independent_sources`
|
|
725
|
-
* distinct sources.
|
|
726
|
-
*/
|
|
727
|
-
export const SafetyCorroborationSchema = withCoverage(z.object({
|
|
728
|
-
rows: z.array(OrganCorroborationRowSchema),
|
|
729
|
-
/** Every source id that offered at least one signal on this run. */
|
|
730
|
-
sources_considered: z.array(z.string().min(1)),
|
|
731
|
-
/** Distinct sources an organ needed to be reported as corroborated. */
|
|
732
|
-
min_independent_sources: z.number().int().positive(),
|
|
733
|
-
}));
|
|
734
|
-
// ── §2.6b Claim investigation ───────────────────────────────────────
|
|
735
|
-
/**
|
|
736
|
-
* What became of a claim once it was argued against.
|
|
737
|
-
*
|
|
738
|
-
* Four words, no number. A numeric soundness would need a threshold to mean
|
|
739
|
-
* anything, and a threshold on this axis deletes real liabilities: the organs
|
|
740
|
-
* with the thinnest published mechanism are routinely the ones worth the most
|
|
741
|
-
* attention. `undetermined` is a real outcome — the argument ran and settled
|
|
742
|
-
* nothing — not a failure to produce one of the other three.
|
|
743
|
-
*/
|
|
744
|
-
export const ClaimVerdictSchema = z.enum(["upheld", "weakened", "overturned", "undetermined"]);
|
|
745
|
-
/** Which stopping condition ended a claim's investigation. */
|
|
746
|
-
export const InvestigationConvergenceSchema = z.enum(["verdict_terminal", "verdict_settled", "round_bound_reached"]);
|
|
747
|
-
/** How the target is proposed to produce the liability in an organ. */
|
|
748
|
-
export const MechanismProposalSchema = withEvidence(z.object({ statement: z.string().min(1) }));
|
|
749
|
-
/**
|
|
750
|
-
* The case against the claim.
|
|
751
|
-
*
|
|
752
|
-
* Its `support` is the counter-evidence, held to the same contract as the claim
|
|
753
|
-
* it attacks: an objection that cannot cite a record is `unknown` with a reason,
|
|
754
|
-
* which is what stops "no supporting literature was found" being dressed up as a
|
|
755
|
-
* finding of its own.
|
|
756
|
-
*/
|
|
757
|
-
export const ClaimCritiqueSchema = withEvidence(z.object({ objection: z.string().min(1) }));
|
|
758
|
-
/**
|
|
759
|
-
* One organ claim, after interrogation.
|
|
760
|
-
*
|
|
761
|
-
* The row's own `support` backs the **verdict** — the assertion the row exists
|
|
762
|
-
* to make. The mechanism and the critique are nested claims carrying their own
|
|
763
|
-
* support, because they are separate assertions that can independently fail to
|
|
764
|
-
* be evidenced; flattening all three onto one `support` would make it
|
|
765
|
-
* unreadable which of them it was for.
|
|
766
|
-
*
|
|
767
|
-
* `critique` is singular: objections are deduplicated by organ, so a later
|
|
768
|
-
* round's critique replaces the earlier one it was written against, and
|
|
769
|
-
* `rounds_run` says how many passes it took.
|
|
770
|
-
*/
|
|
771
|
-
export const InvestigatedClaimRowSchema = withEvidence(z.object({
|
|
772
|
-
organ: OrganSystemSchema,
|
|
773
|
-
/** Null when no mechanism was proposable from the evidence collected. */
|
|
774
|
-
mechanism: MechanismProposalSchema.nullable(),
|
|
775
|
-
/** Null when the critique step produced no objection worth recording. */
|
|
776
|
-
critique: ClaimCritiqueSchema.nullable(),
|
|
777
|
-
verdict: ClaimVerdictSchema,
|
|
778
|
-
rounds_run: z.number().int().positive(),
|
|
779
|
-
convergence: InvestigationConvergenceSchema,
|
|
780
|
-
}));
|
|
781
|
-
/** Why a candidate organ claim produced no investigated row. */
|
|
782
|
-
export const UninvestigatedClaimReasonSchema = z.enum(["not_corroborated", "exceeded_claim_budget", "investigation_unavailable"]);
|
|
783
|
-
export const UninvestigatedClaimSchema = z.object({
|
|
784
|
-
organ: OrganSystemSchema,
|
|
785
|
-
reason: UninvestigatedClaimReasonSchema,
|
|
786
|
-
detail: z.string().min(1),
|
|
787
|
-
});
|
|
788
|
-
/**
|
|
789
|
-
* The investigation record.
|
|
790
|
-
*
|
|
791
|
-
* `not_investigated` is part of the data rather than a diagnostic beside it: a
|
|
792
|
-
* run that investigated nothing still has something to say, and it is the only
|
|
793
|
-
* thing that case has to say. That is why `rows` may be empty on an `available`
|
|
794
|
-
* section — every candidate that produced no row is named here with its reason.
|
|
795
|
-
*
|
|
796
|
-
* The two bounds ride in the section because they are what a reader needs to
|
|
797
|
-
* know how hard each claim was pushed, and they are configuration, so the value
|
|
798
|
-
* in force is not derivable from the code a reader happens to be looking at.
|
|
799
|
-
*/
|
|
800
|
-
export const ClaimInvestigationSchema = withCoverage(z.object({
|
|
801
|
-
rows: z.array(InvestigatedClaimRowSchema),
|
|
802
|
-
not_investigated: z.array(UninvestigatedClaimSchema),
|
|
803
|
-
/** Propose → critique → re-verify rounds a single claim was allowed. */
|
|
804
|
-
round_bound: z.number().int().positive(),
|
|
805
|
-
/** Corroborated claims this run was allowed to investigate. */
|
|
806
|
-
claim_budget: z.number().int().positive(),
|
|
807
|
-
}));
|
|
808
|
-
// ── §2.7 Off-tissue risk ────────────────────────────────────────────
|
|
809
|
-
export const OffTissueRowSchema = withEvidence(z.object({
|
|
810
|
-
tissue: z.string(),
|
|
811
|
-
organ: OrganSystemSchema,
|
|
812
|
-
tpm: z.number(),
|
|
813
|
-
}));
|
|
814
|
-
export const OffTissueRiskSchema = withCoverage(z.object({ rows: z.array(OffTissueRowSchema) }));
|
|
815
|
-
// ── §3.1–3.6 Reference biology — keyed evidence sections ────────────
|
|
816
|
-
export const KeyedEvidenceRowSchema = z.object({
|
|
817
|
-
partner_id: z.string().nullable(),
|
|
818
|
-
partner_name: z.string(),
|
|
819
|
-
predicate: z.string(),
|
|
820
|
-
best_score: z.number(),
|
|
821
|
-
source_count: z.number().int().nonnegative(),
|
|
822
|
-
paper_count: z.number().int().nonnegative(),
|
|
823
|
-
evidence: EvidenceList,
|
|
824
|
-
});
|
|
825
|
-
export const DiseaseAssociationsSchema = withCoverage(z.object({ rows: z.array(KeyedEvidenceRowSchema) }));
|
|
826
|
-
export const MolecularInteractionsSchema = withCoverage(z.object({ rows: z.array(KeyedEvidenceRowSchema) }));
|
|
827
|
-
export const BiomarkerEvidenceRowSchema = KeyedEvidenceRowSchema.extend({
|
|
828
|
-
metrics: z.object({
|
|
829
|
-
sensitivity: z.number().optional(),
|
|
830
|
-
specificity: z.number().optional(),
|
|
831
|
-
auc: z.number().optional(),
|
|
832
|
-
hazard_ratio: z.number().optional(),
|
|
833
|
-
}),
|
|
834
|
-
});
|
|
835
|
-
export const BiomarkerPotentialSchema = withCoverage(z.object({ rows: z.array(BiomarkerEvidenceRowSchema) }));
|
|
836
|
-
export const ResistanceEvidenceSchema = withCoverage(z.object({ rows: z.array(KeyedEvidenceRowSchema) }));
|
|
837
|
-
export const CombinationEvidenceSchema = withCoverage(z.object({ rows: z.array(KeyedEvidenceRowSchema) }));
|
|
838
|
-
// ── §3.4 Genetic alterations ────────────────────────────────────────
|
|
839
|
-
export const SomaticMutationRowSchema = z.object({
|
|
840
|
-
cancer_type: z.string(),
|
|
841
|
-
cohort: z.string(),
|
|
842
|
-
mutation_count: z.number().int().nonnegative(),
|
|
843
|
-
total_samples: z.number().int().nonnegative(),
|
|
844
|
-
frequency: z.number(),
|
|
845
|
-
source: z.string(),
|
|
846
|
-
});
|
|
847
|
-
export const ClinvarVariantRowSchema = z.object({
|
|
848
|
-
variant_id: z.string(),
|
|
849
|
-
hgvs: z.string(),
|
|
850
|
-
classification: z.string(),
|
|
851
|
-
condition: z.string(),
|
|
852
|
-
review_status: z.string(),
|
|
853
|
-
});
|
|
854
|
-
export const GeneticAlterationsSchema = z.object({
|
|
855
|
-
somatic: withCoverage(z.object({ rows: z.array(SomaticMutationRowSchema) })),
|
|
856
|
-
clinvar: withCoverage(z.object({ rows: z.array(ClinvarVariantRowSchema) })),
|
|
857
|
-
});
|
|
858
|
-
// ── §3.7 Pathway context ────────────────────────────────────────────
|
|
859
|
-
export const PathwayRowSchema = z.object({
|
|
860
|
-
pathway_id: z.string(),
|
|
861
|
-
pathway_name: z.string(),
|
|
862
|
-
database: z.enum(["reactome", "kegg", "wikipathways", "msigdb"]),
|
|
863
|
-
evidence_score: z.number(),
|
|
864
|
-
entity_uniprots: z.array(z.string()).optional(),
|
|
865
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
866
|
-
});
|
|
867
|
-
export const PathwayContextSchema = withCoverage(z.object({
|
|
868
|
-
rows: z.array(PathwayRowSchema),
|
|
869
|
-
databases_queried: z.array(z.string()),
|
|
870
|
-
databases_skipped: z.array(z.string()),
|
|
871
|
-
}));
|
|
872
|
-
// ── §3.8 PPI network ────────────────────────────────────────────────
|
|
873
|
-
export const PpiPartnerRowSchema = z.object({
|
|
874
|
-
partner_id: z.string().nullable(),
|
|
875
|
-
partner_name: z.string(),
|
|
876
|
-
string_score: z.number().nullable(),
|
|
877
|
-
literature_score: z.number().nullable(),
|
|
878
|
-
combined_score: z.number(),
|
|
879
|
-
sources: z.array(z.string()),
|
|
880
|
-
has_human_evidence: z.boolean(),
|
|
881
|
-
has_clinical_evidence: z.boolean(),
|
|
882
|
-
});
|
|
883
|
-
export const PpiNetworkSchema = withCoverage(z.object({ partners: z.array(PpiPartnerRowSchema) }));
|
|
884
|
-
// ── §3.9 Normal tissue expression ───────────────────────────────────
|
|
885
|
-
/** Source and unit travel together, and the normalization note is mandatory. */
|
|
886
|
-
export const NormalTissueExpressionSchema = z
|
|
887
|
-
.object({
|
|
888
|
-
source: z.enum(["gtex", "hpa_consensus", "hpa_rna_tissue"]),
|
|
889
|
-
unit: z.enum(["tpm", "ntpm", "consensus_normalized"]),
|
|
890
|
-
normalization_notes: z.string().min(1),
|
|
891
|
-
rows: z.array(z.object({
|
|
892
|
-
tissue: z.string(),
|
|
893
|
-
value: z.number(),
|
|
894
|
-
percentile_rank: z.number().optional(),
|
|
895
|
-
})),
|
|
896
|
-
})
|
|
897
|
-
.superRefine((v, ctx) => {
|
|
898
|
-
if (v.unit === "tpm" && v.source !== "gtex") {
|
|
899
|
-
ctx.addIssue({
|
|
900
|
-
code: "custom",
|
|
901
|
-
message: `unit:"tpm" is only valid with source:"gtex" (got source:"${v.source}")`,
|
|
902
|
-
path: ["unit"],
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
if (v.unit === "ntpm" && !(v.source === "hpa_rna_tissue" || v.source === "hpa_consensus")) {
|
|
906
|
-
ctx.addIssue({
|
|
907
|
-
code: "custom",
|
|
908
|
-
message: `unit:"ntpm" is only valid with HPA sources`,
|
|
909
|
-
path: ["unit"],
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
// ── §3.10 Preclinical ───────────────────────────────────────────────
|
|
914
|
-
export const KoPhenotypeSchema = z.object({
|
|
915
|
-
marker_symbol: z.string().nullable(),
|
|
916
|
-
viability: z.string().nullable(),
|
|
917
|
-
sex_dimorphism: z.boolean(),
|
|
918
|
-
organ_systems_with_phenotype: z.array(OrganSystemSchema),
|
|
919
|
-
top_mp_terms: z.array(z.string()),
|
|
920
|
-
total_phenotype_count: z.number().int().nonnegative(),
|
|
921
|
-
pre_weaning_lethal: z.boolean(),
|
|
922
|
-
supporting_literature: EvidenceList,
|
|
923
|
-
});
|
|
924
|
-
const ExpressionRank = z.enum(["absent", "low", "medium", "high", "no_data", "not_loaded"]);
|
|
925
|
-
export const ExpressionHeatmapCellSchema = z.object({
|
|
926
|
-
tissue: z.string(),
|
|
927
|
-
species: z.enum(["human", "mouse", "rat", "macaque", "dog"]),
|
|
928
|
-
rank: ExpressionRank,
|
|
929
|
-
});
|
|
930
|
-
export const ExpressionHeatmapSchema = z.object({
|
|
931
|
-
cells: z.array(ExpressionHeatmapCellSchema),
|
|
932
|
-
per_species_coverage: z.record(z.string(), CoverageSchema),
|
|
933
|
-
});
|
|
934
|
-
export const TranslationalCommentaryRowSchema = z.object({
|
|
935
|
-
severity: z.enum(["ok", "caution", "gap"]),
|
|
936
|
-
organ: z.string().optional(),
|
|
937
|
-
species: z.string().optional(),
|
|
938
|
-
text: z.string(),
|
|
939
|
-
});
|
|
940
|
-
export const PreclinicalLiteratureRowSchema = z.object({
|
|
941
|
-
pmid: z.string().nullable(),
|
|
942
|
-
claim: z.string(),
|
|
943
|
-
excerpt: z.string().optional(),
|
|
944
|
-
model_system: z.string(),
|
|
945
|
-
species: z.string(),
|
|
946
|
-
});
|
|
947
|
-
export const PreclinicalDataCoverageSchema = z.object({
|
|
948
|
-
ko: z.enum(["available", "no_phenotypes", "none", "queried_no_data", "not_loaded"]),
|
|
949
|
-
expression: CoverageSchema,
|
|
950
|
-
literature: CoverageSchema,
|
|
951
|
-
per_species: z.record(z.string(), z.boolean()),
|
|
952
|
-
});
|
|
953
|
-
export const PreclinicalProfileSchema = z.object({
|
|
954
|
-
ko_phenotype: withCoverage(KoPhenotypeSchema),
|
|
955
|
-
expression_heatmap: withCoverage(ExpressionHeatmapSchema),
|
|
956
|
-
translational_commentary: withCoverage(z.object({ rows: z.array(TranslationalCommentaryRowSchema) })),
|
|
957
|
-
preclinical_literature: withCoverage(z.object({
|
|
958
|
-
rows: z.array(PreclinicalLiteratureRowSchema),
|
|
959
|
-
total_claim_count: z.number().int().nonnegative(),
|
|
960
|
-
truncated: z.boolean(),
|
|
961
|
-
})),
|
|
962
|
-
data_coverage: PreclinicalDataCoverageSchema,
|
|
963
|
-
});
|
|
964
|
-
// ── §3.11 Key papers ────────────────────────────────────────────────
|
|
965
|
-
export const KeyPaperRowSchema = z.object({
|
|
966
|
-
pmid: z.string(),
|
|
967
|
-
title: z.string(),
|
|
968
|
-
internal_reference_count: z.number().int().nonnegative(),
|
|
969
|
-
year: z.number().int().optional(),
|
|
970
|
-
});
|
|
971
|
-
export const KeyPapersSchema = withCoverage(z.object({ rows: z.array(KeyPaperRowSchema) }));
|
|
972
|
-
// ── §4.1 Evidence conflicts ─────────────────────────────────────────
|
|
973
|
-
export const EvidenceConflictRowSchema = z.object({
|
|
974
|
-
evidence_item_id: z.string(),
|
|
975
|
-
predicate: z.string(),
|
|
976
|
-
contradicting_predicates: z.array(z.string()),
|
|
977
|
-
surfaced_in_section: z.string(),
|
|
978
|
-
evidence: EvidenceList,
|
|
979
|
-
});
|
|
980
|
-
export const EvidenceConflictsSchema = withCoverage(z.object({ rows: z.array(EvidenceConflictRowSchema) }));
|
|
981
|
-
// ── §4.2 Evidence timeline ──────────────────────────────────────────
|
|
982
|
-
export const EvidenceTimelineSchema = withCoverage(z.object({
|
|
983
|
-
histogram: z.record(z.string(), z.number().int().nonnegative()),
|
|
984
|
-
first_reported_year: z.number().int().nullable(),
|
|
985
|
-
last_reported_year: z.number().int().nullable(),
|
|
986
|
-
trend_labels: z.array(z.string()),
|
|
987
|
-
}));
|
|
988
|
-
// ── §4.3 Translational chain ────────────────────────────────────────
|
|
989
|
-
const TranslationalTier = z.enum(["computational", "basic_in_vitro", "complex_in_vitro", "in_vivo_animal", "ex_vivo_human", "clinical"]);
|
|
990
|
-
export const TranslationalChainSchema = withCoverage(z.object({
|
|
991
|
-
tiers: z.array(z.object({
|
|
992
|
-
tier: TranslationalTier,
|
|
993
|
-
claim_count: z.number().int().nonnegative(),
|
|
994
|
-
paper_count: z.number().int().nonnegative(),
|
|
995
|
-
})),
|
|
996
|
-
peak_evidence_tier: TranslationalTier.nullable(),
|
|
997
|
-
progression_complete: z.boolean(),
|
|
998
|
-
weakest_progression_gap: TranslationalTier.nullable().optional(),
|
|
999
|
-
}));
|
|
1000
|
-
// ── §4.4 Additional evidence ────────────────────────────────────────
|
|
1001
|
-
export const AdditionalEvidenceSchema = withCoverage(z.object({
|
|
1002
|
-
rows: z.array(z.object({
|
|
1003
|
-
predicate: z.string(),
|
|
1004
|
-
evidence: EvidenceList,
|
|
1005
|
-
})),
|
|
1006
|
-
score_floor: z.number(),
|
|
1007
|
-
}));
|
|
1008
|
-
// ── §4.5 Discovery trials ───────────────────────────────────────────
|
|
1009
|
-
export const DiscoveryTrialRowSchema = z
|
|
1010
|
-
.object({
|
|
1011
|
-
nct_id: z.string(),
|
|
1012
|
-
title: z.string(),
|
|
1013
|
-
phase: z.string().nullable().optional(),
|
|
1014
|
-
status: z.string().optional(),
|
|
1015
|
-
conditions: z.array(z.string()),
|
|
1016
|
-
start_date: z.string().optional(),
|
|
1017
|
-
completion_date: z.string().optional(),
|
|
1018
|
-
match_confidence: z.enum(["high", "medium", "low"]),
|
|
1019
|
-
relevance_basis: z.discriminatedUnion("kind", [
|
|
1020
|
-
z.object({
|
|
1021
|
-
kind: z.literal("drug_in_class_match"),
|
|
1022
|
-
drug_id: z.string(),
|
|
1023
|
-
matched_term: z.string().optional(),
|
|
1024
|
-
}),
|
|
1025
|
-
z.object({ kind: z.literal("title_keyword"), matched_term: z.string() }),
|
|
1026
|
-
z.object({ kind: z.literal("condition_match"), matched_term: z.string().optional() }),
|
|
1027
|
-
z.object({ kind: z.literal("manual") }),
|
|
1028
|
-
]),
|
|
1029
|
-
attribution: ClinicalTrialAttributionSchema,
|
|
1030
|
-
eligible_for_toxicology_aggregation: z.boolean(),
|
|
1031
|
-
})
|
|
1032
|
-
.superRefine((v, ctx) => {
|
|
1033
|
-
// Low-confidence rows that only match by condition (no drug link) are unreliable;
|
|
1034
|
-
// collector must drop them rather than emitting them.
|
|
1035
|
-
if (v.match_confidence === "low" && v.relevance_basis.kind === "condition_match") {
|
|
1036
|
-
ctx.addIssue({
|
|
1037
|
-
code: "custom",
|
|
1038
|
-
message: `low-confidence rows must not have relevance_basis.kind="condition_match" without an intervention drug match (drop at collector)`,
|
|
1039
|
-
path: ["relevance_basis"],
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
export const DiscoveryTrialsSchema = withCoverage(z.object({
|
|
1044
|
-
rows: z.array(DiscoveryTrialRowSchema),
|
|
1045
|
-
excluded_rows: z.array(DiscoveryTrialRowSchema).default([]),
|
|
1046
|
-
}));
|
|
1047
|
-
// ── §1.0 Executive recommendation ───────────────────────────────────
|
|
1048
|
-
export const ExecutiveRecommendationDataSchema = z.object({
|
|
1049
|
-
disposition: z.enum(["pursue", "conditional", "de_prioritize", "insufficient_evidence"]),
|
|
1050
|
-
confidence: z.enum(["high", "medium", "low"]),
|
|
1051
|
-
rationale: z.string(),
|
|
1052
|
-
key_strengths: z.array(z.string()).max(8).default([]),
|
|
1053
|
-
key_risks: z.array(z.string()).max(8).default([]),
|
|
1054
|
-
key_risks_dropped: z
|
|
1055
|
-
.array(z.object({
|
|
1056
|
-
text: z.string(),
|
|
1057
|
-
reason: z.string(),
|
|
1058
|
-
cited_pmid: z.string().optional(),
|
|
1059
|
-
}))
|
|
1060
|
-
.optional(),
|
|
1061
|
-
modality_choice: z.object({
|
|
1062
|
-
modality: z.string(),
|
|
1063
|
-
rationale: z.string(),
|
|
1064
|
-
}),
|
|
1065
|
-
coverage_qualifier: z.object({
|
|
1066
|
-
sections_consulted: z.array(z.string()).default([]),
|
|
1067
|
-
sections_unavailable: z.array(z.string()).default([]),
|
|
1068
|
-
note: z.string(),
|
|
1069
|
-
unverified_bullets: z
|
|
1070
|
-
.array(z.object({
|
|
1071
|
-
text: z.string(),
|
|
1072
|
-
bullet_category: z.string(),
|
|
1073
|
-
surface: z.literal("organ_claim_without_probe_pass"),
|
|
1074
|
-
}))
|
|
1075
|
-
.optional(),
|
|
1076
|
-
}),
|
|
1077
|
-
});
|
|
1078
|
-
export const ExecutiveRecommendationSchema = withCoverage(ExecutiveRecommendationDataSchema);
|
|
1079
|
-
// ── §4.6 Synthesis diagnostics ──────────────────────────────────────
|
|
1080
|
-
export const SynthesisDiagnosticRowSchema = z.object({
|
|
1081
|
-
step_id: z.enum(["liability-bullets", "safety-flags-trail", "translational-commentary", "dossier-recommendation"]),
|
|
1082
|
-
model_id: z.string(),
|
|
1083
|
-
attempt_count: z.number().int().min(1).max(2),
|
|
1084
|
-
retry_critique: z.string().nullable(),
|
|
1085
|
-
output_chars: z.number().int().nonnegative(),
|
|
1086
|
-
probe_verdict: z.enum([
|
|
1087
|
-
"pass",
|
|
1088
|
-
"fail-length",
|
|
1089
|
-
"fail-section-references",
|
|
1090
|
-
"fail-key-strengths-risks",
|
|
1091
|
-
"fail-source-references",
|
|
1092
|
-
"fail-numeric-claim",
|
|
1093
|
-
"fail-voice",
|
|
1094
|
-
"relaxed",
|
|
1095
|
-
"skipped",
|
|
1096
|
-
]),
|
|
1097
|
-
final_coverage: CoverageSchema.extract(["available", "queried_no_data"]),
|
|
1098
|
-
error_kind: z.enum(["synthesis-too-shallow", "synthesis-unavailable", "voice-violation"]).nullable().optional(),
|
|
1099
|
-
error_message: z.string().nullable().optional(),
|
|
1100
|
-
});
|
|
1101
|
-
export const SynthesisDiagnosticsSchema = withCoverage(z.object({ rows: z.array(SynthesisDiagnosticRowSchema) }));
|
|
1102
|
-
// ── §4.7 Recommendation citation audit ──────────────────────────────
|
|
1103
|
-
export const RecommendationAuditEntrySchema = z.union([
|
|
1104
|
-
z.object({
|
|
1105
|
-
surface: z.enum(["rationale", "key_strengths", "key_risks", "modality_choice"]),
|
|
1106
|
-
path: z.string(),
|
|
1107
|
-
excerpt: z.string(),
|
|
1108
|
-
}),
|
|
1109
|
-
z.object({
|
|
1110
|
-
surface: z.literal("external_missing"),
|
|
1111
|
-
id: z.string(),
|
|
1112
|
-
excerpt: z.string(),
|
|
1113
|
-
}),
|
|
1114
|
-
z.object({
|
|
1115
|
-
surface: z.literal("organ_claim_without_probe_pass"),
|
|
1116
|
-
excerpt: z.string(),
|
|
1117
|
-
bullet_category: z.string(),
|
|
1118
|
-
}),
|
|
1119
|
-
z.object({
|
|
1120
|
-
surface: z.literal("nct_wrong_class"),
|
|
1121
|
-
id: z.string(),
|
|
1122
|
-
excerpt: z.string(),
|
|
1123
|
-
}),
|
|
1124
|
-
z.object({
|
|
1125
|
-
surface: z.literal("pmid_not_in_key_papers"),
|
|
1126
|
-
id: z.string(),
|
|
1127
|
-
excerpt: z.string(),
|
|
1128
|
-
}),
|
|
1129
|
-
z.object({
|
|
1130
|
-
surface: z.literal("direction_mismatch"),
|
|
1131
|
-
pmid: z.string(),
|
|
1132
|
-
excerpt: z.string(),
|
|
1133
|
-
paper_conclusion_direction: z.enum(["supports", "contradicts", "ambiguous"]).optional(),
|
|
1134
|
-
}),
|
|
1135
|
-
z.object({
|
|
1136
|
-
surface: z.literal("abstract_unavailable"),
|
|
1137
|
-
pmid: z.string(),
|
|
1138
|
-
excerpt: z.string(),
|
|
1139
|
-
}),
|
|
1140
|
-
z.object({
|
|
1141
|
-
surface: z.literal("off_topic_citation"),
|
|
1142
|
-
pmid: z.string(),
|
|
1143
|
-
excerpt: z.string(),
|
|
1144
|
-
}),
|
|
1145
|
-
]);
|
|
1146
|
-
export const RecommendationAuditSchema = withCoverage(z.object({
|
|
1147
|
-
citations_total: z.number().int().nonnegative(),
|
|
1148
|
-
citations_unresolved: z.array(RecommendationAuditEntrySchema),
|
|
1149
|
-
non_dossier_citations: z.array(z.object({ token: z.string(), source: z.string() })),
|
|
1150
|
-
}));
|
|
1151
|
-
// ── §4.8 Quality gates ──────────────────────────────────────────────
|
|
1152
|
-
export const QualityGateStatusSchema = z.object({
|
|
1153
|
-
gate_id: z.string(),
|
|
1154
|
-
status: z.enum(["pass", "warning", "blocked"]),
|
|
1155
|
-
message: z.string(),
|
|
1156
|
-
affected_sections: z.array(z.string()).default([]),
|
|
1157
|
-
unresolved_count: z.number().int().nonnegative().optional(),
|
|
1158
|
-
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
1159
|
-
});
|
|
1160
|
-
export const QualityGatesSchema = withCoverage(z.object({ rows: z.array(QualityGateStatusSchema) }));
|
|
1161
|
-
// ── Composite shapes ────────────────────────────────────────────────
|
|
1162
|
-
export const ReferenceBiologyShape = z.object({
|
|
1163
|
-
therapeutic_area_associations: DiseaseAssociationsSchema,
|
|
1164
|
-
molecular_interactions: MolecularInteractionsSchema,
|
|
1165
|
-
biomarker_potential: BiomarkerPotentialSchema,
|
|
1166
|
-
genetic_alterations: GeneticAlterationsSchema,
|
|
1167
|
-
resistance_evidence: ResistanceEvidenceSchema,
|
|
1168
|
-
combination_evidence: CombinationEvidenceSchema,
|
|
1169
|
-
pathway_context: PathwayContextSchema,
|
|
1170
|
-
ppi_network: PpiNetworkSchema,
|
|
1171
|
-
normal_tissue_expression: withCoverage(NormalTissueExpressionSchema),
|
|
1172
|
-
preclinical: PreclinicalProfileSchema,
|
|
1173
|
-
key_papers: KeyPapersSchema,
|
|
1174
|
-
});
|
|
1175
|
-
const AnalyticsShape = z.object({
|
|
1176
|
-
evidence_conflicts: EvidenceConflictsSchema,
|
|
1177
|
-
evidence_timeline: EvidenceTimelineSchema,
|
|
1178
|
-
translational_chain: TranslationalChainSchema,
|
|
1179
|
-
additional_evidence: AdditionalEvidenceSchema,
|
|
1180
|
-
discovery_trials: DiscoveryTrialsSchema,
|
|
1181
|
-
synthesis_diagnostics: SynthesisDiagnosticsSchema,
|
|
1182
|
-
quality_gates: QualityGatesSchema,
|
|
1183
|
-
recommendation_audit: RecommendationAuditSchema.optional(),
|
|
1184
|
-
});
|
|
1185
|
-
// ── Derived sub-tree ────────────────────────────────────────────────
|
|
1186
|
-
//
|
|
1187
|
-
// Shape produced by computeDerivedFields(body) and validated at phase-5
|
|
1188
|
-
// persist. `.strict()` rejects unknown keys to prevent accidental extras.
|
|
1189
|
-
export const DerivedSchema = z
|
|
1190
|
-
.object({
|
|
1191
|
-
summary: z.object({
|
|
1192
|
-
has_human_evidence: z.boolean(),
|
|
1193
|
-
has_clinical_evidence: z.boolean(),
|
|
1194
|
-
total_evidence_items: z.number().int().nonnegative(),
|
|
1195
|
-
total_distinct_papers: z.number().int().nonnegative(),
|
|
1196
|
-
total_distinct_clinical_trials: z.number().int().nonnegative(),
|
|
1197
|
-
strongest_strength_label: z.string().nullable(),
|
|
1198
|
-
highest_score: z.number().nullable(),
|
|
1199
|
-
highest_score_is_conflicted: z.boolean(),
|
|
1200
|
-
}),
|
|
1201
|
-
risk_summary: z.object({
|
|
1202
|
-
any_fatal_signal: z.union([z.boolean(), z.literal("unknown")]),
|
|
1203
|
-
highest_risk_organ: OrganSystemSchema.nullable(),
|
|
1204
|
-
off_target_safety_target_hits: z.number().int().nonnegative(),
|
|
1205
|
-
class_liability_count: z.number().int().nonnegative(),
|
|
1206
|
-
}),
|
|
1207
|
-
liability_summary: z.object({
|
|
1208
|
-
counts: z.object({
|
|
1209
|
-
class_liability_count: z.number().int().nonnegative(),
|
|
1210
|
-
safety_target_off_target_count: z.number().int().nonnegative(),
|
|
1211
|
-
off_tissue_risk_organ_count: z.number().int().nonnegative(),
|
|
1212
|
-
}),
|
|
1213
|
-
expression_breadth: z.object({
|
|
1214
|
-
total_assessed_tissues: z.number().int().nonnegative(),
|
|
1215
|
-
high_expression_tissue_count: z.number().int().nonnegative(),
|
|
1216
|
-
}),
|
|
1217
|
-
same_class_drug_count: z.number().int().nonnegative(),
|
|
1218
|
-
safety_data_sources_checked: z.number().int().nonnegative(),
|
|
1219
|
-
}),
|
|
1220
|
-
organ_rollup_completeness: z
|
|
1221
|
-
.object({
|
|
1222
|
-
expected_organs: z.array(OrganSystemSchema),
|
|
1223
|
-
present_organs: z.array(OrganSystemSchema),
|
|
1224
|
-
missing_organs: z.array(OrganSystemSchema),
|
|
1225
|
-
})
|
|
1226
|
-
.refine((v) => v.missing_organs.length === 0, {
|
|
1227
|
-
message: "missing_organs must be empty for the dossier to ship",
|
|
1228
|
-
path: ["missing_organs"],
|
|
1229
|
-
}),
|
|
1230
|
-
})
|
|
1231
|
-
.strict();
|
|
1232
|
-
// ── Top-level Dossier ───────────────────────────────────────────────
|
|
1233
|
-
export const DossierSchema = z.object({
|
|
1234
|
-
entity: EntitySchema,
|
|
1235
|
-
generated_at: z.string(),
|
|
1236
|
-
liability_summary: LiabilitySummarySchema,
|
|
1237
|
-
tractability: TractabilitySchema,
|
|
1238
|
-
indications: IndicationsSchema,
|
|
1239
|
-
drug_interactions: DrugInteractionsSchema,
|
|
1240
|
-
clinical_development: ClinicalDevelopmentSchema,
|
|
1241
|
-
safety_profile: SafetyProfileSchema,
|
|
1242
|
-
safety_corroboration: SafetyCorroborationSchema,
|
|
1243
|
-
claim_investigation: ClaimInvestigationSchema,
|
|
1244
|
-
off_tissue_risk: OffTissueRiskSchema,
|
|
1245
|
-
off_target_panel: withCoverage(OffTargetPanelSchema),
|
|
1246
|
-
reference_biology: ReferenceBiologyShape,
|
|
1247
|
-
analytics: AnalyticsShape,
|
|
1248
|
-
executive_recommendation: ExecutiveRecommendationSchema,
|
|
1249
|
-
derived: DerivedSchema,
|
|
1250
|
-
});
|
|
1251
|
-
/**
|
|
1252
|
-
* Everything the Phase-4 assembler is responsible for building, without the
|
|
1253
|
-
* derived sub-tree. Phase-5 persist computes derived from the stamped body
|
|
1254
|
-
* and validates the complete dossier against `DossierSchema`.
|
|
1255
|
-
*/
|
|
1256
|
-
export const DossierBodySchema = DossierSchema.omit({ derived: true });
|
|
1257
|
-
// ── Type guards ─────────────────────────────────────────────────────
|
|
1258
|
-
export function isDossier(value) {
|
|
1259
|
-
return DossierSchema.safeParse(value).success;
|
|
1260
|
-
}
|
|
1261
|
-
/** JSON Schema export for runtime validation in non-TS environments. */
|
|
1262
|
-
export function dossierJsonSchema() {
|
|
1263
|
-
return z.toJSONSchema(DossierSchema);
|
|
1264
|
-
}
|
|
1265
|
-
// ── Progress events ─────────────────────────────────────────────────
|
|
1266
|
-
export const TargetAssessmentPhaseSchema = z.enum([
|
|
1267
|
-
"resolving",
|
|
1268
|
-
"collecting",
|
|
1269
|
-
"deciding",
|
|
1270
|
-
"fanning_out",
|
|
1271
|
-
"assembling",
|
|
1272
|
-
"synthesizing",
|
|
1273
|
-
"completed",
|
|
1274
|
-
"failed",
|
|
1275
|
-
"suspended",
|
|
1276
|
-
]);
|
|
1277
|
-
export const TargetAssessmentProgressEventSchema = z.object({
|
|
1278
|
-
phase: TargetAssessmentPhaseSchema,
|
|
1279
|
-
message: z.string(),
|
|
1280
|
-
percent: z.number().min(0).max(100),
|
|
1281
|
-
at: z.string(),
|
|
1282
|
-
});
|