@inflexa-ai/harness 0.35.8 → 0.36.1

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