@inflexa-ai/harness 0.17.0 → 0.18.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/agents/report-builder.d.ts +2 -0
- package/dist/agents/report-builder.js +2 -0
- package/dist/app/spawn-report-session.d.ts +85 -0
- package/dist/app/spawn-report-session.js +83 -0
- package/dist/contracts/chat-events.d.ts +9 -0
- package/dist/contracts/index.d.ts +6 -2
- package/dist/contracts/index.js +3 -1
- package/dist/contracts/organ-system.d.ts +49 -0
- package/dist/contracts/organ-system.js +67 -0
- package/dist/contracts/report-blocks.d.ts +564 -0
- package/dist/contracts/report-blocks.js +117 -0
- package/dist/contracts/report-reference.d.ts +459 -0
- package/dist/contracts/report-reference.js +256 -0
- package/dist/contracts/schemas/chat-events.d.ts +2 -0
- package/dist/contracts/schemas/chat-events.js +2 -0
- package/dist/contracts/severity.d.ts +15 -0
- package/dist/contracts/severity.js +10 -0
- package/dist/contracts/target-dossier.d.ts +8675 -12972
- package/dist/contracts/target-dossier.js +534 -591
- package/dist/data/safety-panel-schema.d.ts +18 -5
- package/dist/data/safety-panel-schema.js +4 -4
- package/dist/execution/artifact-metadata.js +1 -0
- package/dist/execution/report-runner.js +1 -3
- package/dist/execution/run-synthesis.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +19 -0
- package/dist/lib/async-utils.d.ts +16 -4
- package/dist/lib/async-utils.js +20 -15
- package/dist/lib/host-concurrency.js +1 -0
- package/dist/loop/__fixtures__/scripted-provider.js +23 -2
- package/dist/loop/run-agent.js +58 -10
- package/dist/loop/tool-detail.d.ts +40 -0
- package/dist/loop/tool-detail.js +35 -3
- package/dist/loop/types.d.ts +13 -0
- package/dist/memory/thread-history.d.ts +10 -0
- package/dist/memory/thread-history.js +12 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
- package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
- package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
- package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
- package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
- package/dist/prompts/target-assessment/investigation/index.js +3 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
- package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
- package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
- package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
- package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
- package/dist/providers/__fixtures__/session.d.ts +5 -4
- package/dist/providers/__fixtures__/session.js +4 -4
- package/dist/report-model/block-walk.d.ts +56 -0
- package/dist/report-model/block-walk.js +89 -0
- package/dist/report-model/draft-finish.d.ts +60 -0
- package/dist/report-model/draft-finish.js +56 -0
- package/dist/report-model/draft-operations.d.ts +125 -0
- package/dist/report-model/draft-operations.js +341 -0
- package/dist/report-model/draft-read.d.ts +59 -0
- package/dist/report-model/draft-read.js +109 -0
- package/dist/report-model/draft.d.ts +51 -0
- package/dist/report-model/draft.js +32 -0
- package/dist/report-model/fixture-resolver.d.ts +8 -0
- package/dist/report-model/fixture-resolver.js +252 -0
- package/dist/report-model/mint-snapshot.d.ts +30 -0
- package/dist/report-model/mint-snapshot.js +40 -0
- package/dist/report-model/reference-resolver.d.ts +113 -0
- package/dist/report-model/reference-resolver.js +60 -0
- package/dist/report-model/structural-validation.d.ts +22 -0
- package/dist/report-model/structural-validation.js +68 -0
- package/dist/report-model/validate.d.ts +40 -0
- package/dist/report-model/validate.js +80 -0
- package/dist/report-render/chart.d.ts +33 -0
- package/dist/report-render/chart.js +538 -0
- package/dist/report-render/page.d.ts +127 -0
- package/dist/report-render/page.js +307 -0
- package/dist/report-render/references.d.ts +32 -0
- package/dist/report-render/references.js +44 -0
- package/dist/report-render/render.d.ts +25 -0
- package/dist/report-render/render.js +125 -0
- package/dist/report-render/script-json.d.ts +13 -0
- package/dist/report-render/script-json.js +15 -0
- package/dist/report-render/types.d.ts +39 -0
- package/dist/report-render/types.js +10 -0
- package/dist/report-render/views/chart-view.d.ts +11 -0
- package/dist/report-render/views/chart-view.js +23 -0
- package/dist/report-render/views/page-view.d.ts +20 -0
- package/dist/report-render/views/page-view.js +49 -0
- package/dist/report-render/views/prose.d.ts +28 -0
- package/dist/report-render/views/prose.js +75 -0
- package/dist/report-render/views/references-view.d.ts +20 -0
- package/dist/report-render/views/references-view.js +64 -0
- package/dist/report-render/views/values.d.ts +42 -0
- package/dist/report-render/views/values.js +43 -0
- package/dist/runtime/boot.d.ts +3 -3
- package/dist/runtime/boot.js +15 -4
- package/dist/runtime/dbos.d.ts +4 -4
- package/dist/state/artifacts.d.ts +16 -0
- package/dist/state/artifacts.js +18 -0
- package/dist/state/init.js +31 -0
- package/dist/state/purge-analysis.js +1 -0
- package/dist/state/report-versions.d.ts +132 -0
- package/dist/state/report-versions.js +194 -0
- package/dist/tasks/data-profile.js +1 -0
- package/dist/tools/bio/chembl.js +1 -0
- package/dist/tools/bio/comptox.js +1 -0
- package/dist/tools/bio/drug-gene-interactions.js +1 -0
- package/dist/tools/bio/gene-disease-evidence.js +1 -0
- package/dist/tools/bio/gene-preclinical-profile.js +1 -0
- package/dist/tools/bio/keys.d.ts +3 -2
- package/dist/tools/bio/keys.js +3 -2
- package/dist/tools/bio/lookup-annotation.js +1 -0
- package/dist/tools/bio/opentargets.d.ts +3 -3
- package/dist/tools/bio/opentargets.js +4 -3
- package/dist/tools/bio/pubchem.js +1 -0
- package/dist/tools/bio/search-clinical-trials.js +1 -0
- package/dist/tools/bio/search-faers.js +1 -0
- package/dist/tools/bio/search-geo-datasets.js +1 -0
- package/dist/tools/bio/search-interactions.js +1 -0
- package/dist/tools/bio/target-safety.d.ts +4 -6
- package/dist/tools/bio/target-safety.js +7 -6
- package/dist/tools/define-tool.d.ts +19 -12
- package/dist/tools/define-tool.js +4 -2
- package/dist/tools/display/show-user.js +1 -0
- package/dist/tools/iterate-report.js +2 -0
- package/dist/tools/lib/chembl-client.d.ts +2 -0
- package/dist/tools/lib/chembl-client.js +1 -0
- package/dist/tools/lib/monarch-client.d.ts +42 -0
- package/dist/tools/lib/monarch-client.js +100 -0
- package/dist/tools/report/build-report.js +1 -0
- package/dist/tools/report/mint-preview-url.js +1 -0
- package/dist/tools/report/preview-snapshot.js +1 -0
- package/dist/tools/report/submit-report.js +1 -0
- package/dist/tools/report/version-fs.js +4 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +134 -0
- package/dist/tools/report-authoring/authoring-tools.js +352 -0
- package/dist/tools/research/context7-docs.js +2 -0
- package/dist/tools/research/generate-analogy-report.js +1 -0
- package/dist/tools/research/generate-plan.js +3 -0
- package/dist/tools/research/inspect-data-profile.js +4 -0
- package/dist/tools/research/literature-reviewer.js +1 -0
- package/dist/tools/research/search-arxiv.js +1 -0
- package/dist/tools/research/search-github-repos.js +1 -0
- package/dist/tools/research/search-semantic-scholar.js +1 -0
- package/dist/tools/sandbox/list-available-packages.js +23 -0
- package/dist/tools/sandbox/list-available-refs.js +36 -0
- package/dist/tools/sandbox/report-blocker.js +1 -0
- package/dist/tools/sandbox/skills.js +2 -0
- package/dist/tools/workspace/file-stat.js +1 -0
- package/dist/tools/workspace/show-file.js +2 -0
- package/dist/tools/workspace/show-plan.js +1 -0
- package/dist/workflows/execute-analysis.d.ts +5 -1
- package/dist/workflows/execute-analysis.js +5 -5
- package/dist/workflows/execute-target-assessment.d.ts +13 -2
- package/dist/workflows/execute-target-assessment.js +78 -16
- package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
- package/dist/workflows/target-assessment/assemblers/index.js +1 -1
- package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
- package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
- package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
- package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
- package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
- package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
- package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
- package/dist/workflows/target-assessment/collectors/index.js +48 -5
- package/dist/workflows/target-assessment/coverage.d.ts +43 -27
- package/dist/workflows/target-assessment/coverage.js +33 -29
- package/dist/workflows/target-assessment/fanout/index.js +1 -0
- package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
- package/dist/workflows/target-assessment/investigation/index.js +419 -0
- package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
- package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
- package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
- package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
- package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
- package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
- package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
- package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
- package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
- package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
- package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
- package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
- package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
- package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
- package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
- package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
- package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
- package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
- package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
- package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
- package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
- package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
- package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
- package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
- package/dist/workflows/target-assessment/metrics.d.ts +1 -6
- package/dist/workflows/target-assessment/metrics.js +1 -13
- package/dist/workflows/target-assessment/phase4-assemble.d.ts +2147 -807
- package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
- package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
- package/dist/workflows/target-assessment/phase5-persist.js +56 -173
- package/dist/workflows/target-assessment/schemas.d.ts +46 -1
- package/dist/workflows/target-assessment/schemas.js +19 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
- package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
- package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +26 -62
- package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
- package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +27 -154
- package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
- package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
- package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
- package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
- package/dist/workflows/target-assessment/synthesis/index.js +4 -1
- package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
- package/package.json +4 -1
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import type { Pool } from "pg";
|
|
20
20
|
import type { AgentSession } from "../../../auth/types.js";
|
|
21
|
-
import type {
|
|
21
|
+
import type { AgentChat } from "../../../providers/types.js";
|
|
22
22
|
import type { UsageRecorder } from "../../../billing/usage-recorder.js";
|
|
23
23
|
export declare const OffTargetAnnotationSchema: z.ZodObject<{
|
|
24
24
|
clinical_consequence: z.ZodString;
|
|
@@ -26,7 +26,7 @@ export declare const OffTargetAnnotationSchema: z.ZodObject<{
|
|
|
26
26
|
}, z.core.$strip>;
|
|
27
27
|
export type OffTargetAnnotation = z.infer<typeof OffTargetAnnotationSchema>;
|
|
28
28
|
export interface ClinicalConsequenceAnnotatorDeps {
|
|
29
|
-
readonly provider:
|
|
29
|
+
readonly provider: AgentChat;
|
|
30
30
|
readonly session: AgentSession;
|
|
31
31
|
readonly model: string;
|
|
32
32
|
/** LLM usage-accounting seam for the annotator loop; omitted falls back to the no-op recorder. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClinicalTrialAttribution } from "@inflexa-ai/harness/contracts/target-dossier.js";
|
|
2
2
|
type TrialLike = {
|
|
3
3
|
nctId: string;
|
|
4
4
|
title: string;
|
|
@@ -37,9 +37,8 @@ export type ClinicalEvidenceAttributionContext = {
|
|
|
37
37
|
therapeuticPrograms: TherapeuticProgram[];
|
|
38
38
|
};
|
|
39
39
|
export type ClinicalEvidenceAttributionResult = {
|
|
40
|
-
attribution:
|
|
40
|
+
attribution: ClinicalTrialAttribution;
|
|
41
41
|
eligible_for_toxicology_aggregation: boolean;
|
|
42
42
|
};
|
|
43
43
|
export declare function classifyClinicalEvidenceTrial(trial: TrialLike, ctx: ClinicalEvidenceAttributionContext): ClinicalEvidenceAttributionResult;
|
|
44
|
-
export declare function attachClinicalEvidenceAttribution<T extends TrialLike>(trial: T, ctx: ClinicalEvidenceAttributionContext): T & Pick<ClinicalTrialRowV5, "attribution" | "eligible_for_toxicology_aggregation">;
|
|
45
44
|
export {};
|
|
@@ -150,7 +150,3 @@ export function classifyClinicalEvidenceTrial(trial, ctx) {
|
|
|
150
150
|
}
|
|
151
151
|
return excluded("unrelated", "No intervention or mechanism evidence links this trial to the assessed target.", "text_match", "ctgov", details.map((i) => toResolvedIntervention(i, "ctgov")));
|
|
152
152
|
}
|
|
153
|
-
export function attachClinicalEvidenceAttribution(trial, ctx) {
|
|
154
|
-
const result = classifyClinicalEvidenceTrial(trial, ctx);
|
|
155
|
-
return { ...trial, ...result };
|
|
156
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Derived } from "@inflexa-ai/harness/contracts/target-dossier.js";
|
|
2
|
+
import { type OrganSystem } from "../../../contracts/organ-system.js";
|
|
2
3
|
/**
|
|
3
4
|
* Derive the set of organs that should appear in the organ rollup based on
|
|
4
5
|
* available safety signals (FAERS top signals, serious trial AEs, and
|
|
@@ -8,12 +9,12 @@ import type { DerivedV4 } from "@inflexa-ai/harness/contracts/target-dossier.js"
|
|
|
8
9
|
* organ_rollup rows — ensuring the set of "expected" organs is identical at
|
|
9
10
|
* assembly time and at derived-validation time.
|
|
10
11
|
*/
|
|
11
|
-
export declare function expectedOrgansFromBody(bodyInput: unknown):
|
|
12
|
+
export declare function expectedOrgansFromBody(bodyInput: unknown): OrganSystem[];
|
|
12
13
|
/**
|
|
13
|
-
* Build the
|
|
14
|
+
* Build the derived sub-tree from a raw dossier body.
|
|
14
15
|
*
|
|
15
16
|
* Pure function: no I/O, no LLM calls, no side effects. Defensive against
|
|
16
17
|
* missing/partial bodies — always returns a best-effort result and never throws.
|
|
17
|
-
* Schema validation happens at the persist boundary
|
|
18
|
+
* Schema validation happens at the persist boundary, not here.
|
|
18
19
|
*/
|
|
19
|
-
export declare function computeDerivedFields(bodyInput: unknown):
|
|
20
|
+
export declare function computeDerivedFields(bodyInput: unknown): Derived;
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import { ORGAN_SYSTEMS } from "../../../contracts/organ-system.js";
|
|
1
2
|
import { classifyOrgan, classifyTrialAe } from "./meddra-organ-map.js";
|
|
2
3
|
import { HIGH_EXPRESSION_TPM_THRESHOLD } from "./expression-constants.js";
|
|
3
4
|
const FAERS_TOP_SIGNAL_THRESHOLD = 1000;
|
|
5
|
+
const ORGAN_SYSTEM_SET = new Set(ORGAN_SYSTEMS);
|
|
6
|
+
/**
|
|
7
|
+
* The body arrives here unvalidated, so an organ token is checked against the
|
|
8
|
+
* vocabulary before it is carried into the derived sub-tree.
|
|
9
|
+
*/
|
|
10
|
+
function isOrganSystem(value) {
|
|
11
|
+
return typeof value === "string" && ORGAN_SYSTEM_SET.has(value);
|
|
12
|
+
}
|
|
4
13
|
/**
|
|
5
14
|
* Derive the set of organs that should appear in the organ rollup based on
|
|
6
15
|
* available safety signals (FAERS top signals, serious trial AEs, and
|
|
@@ -29,7 +38,7 @@ export function expectedOrgansFromBody(bodyInput) {
|
|
|
29
38
|
}
|
|
30
39
|
const offTargets = body?.safety_profile?.off_target_panel?.data?.rows ?? [];
|
|
31
40
|
for (const r of offTargets) {
|
|
32
|
-
if (r.organ_system &&
|
|
41
|
+
if (r.organ_system && isOrganSystem(r.organ_system))
|
|
33
42
|
expected.add(r.organ_system);
|
|
34
43
|
}
|
|
35
44
|
return [...expected].sort();
|
|
@@ -38,7 +47,7 @@ function presentOrgans(body) {
|
|
|
38
47
|
const rows = body?.safety_profile?.organ_rollup?.data?.rows ?? [];
|
|
39
48
|
return rows
|
|
40
49
|
.map((r) => r.organ)
|
|
41
|
-
.filter(
|
|
50
|
+
.filter(isOrganSystem)
|
|
42
51
|
.sort();
|
|
43
52
|
}
|
|
44
53
|
function countDistinctPapers(body) {
|
|
@@ -51,7 +60,7 @@ function countDistinctPapers(body) {
|
|
|
51
60
|
}
|
|
52
61
|
function countDistinctTrials(body) {
|
|
53
62
|
const set = new Set();
|
|
54
|
-
//
|
|
63
|
+
// Ineligible trials are partitioned into `excluded_rows`, so the distinct
|
|
55
64
|
// count must union both buckets for trials, failed_trials, and
|
|
56
65
|
// discovery_trials. Outcomes preserves every row in `rows` and has no
|
|
57
66
|
// `excluded_rows` bucket.
|
|
@@ -189,11 +198,11 @@ function countHighExpressionTissues(body) {
|
|
|
189
198
|
return set.size;
|
|
190
199
|
}
|
|
191
200
|
/**
|
|
192
|
-
* Build the
|
|
201
|
+
* Build the derived sub-tree from a raw dossier body.
|
|
193
202
|
*
|
|
194
203
|
* Pure function: no I/O, no LLM calls, no side effects. Defensive against
|
|
195
204
|
* missing/partial bodies — always returns a best-effort result and never throws.
|
|
196
|
-
* Schema validation happens at the persist boundary
|
|
205
|
+
* Schema validation happens at the persist boundary, not here.
|
|
197
206
|
*/
|
|
198
207
|
export function computeDerivedFields(bodyInput) {
|
|
199
208
|
const body = bodyInput;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build the obligate-cofactor supplement rows shown in
|
|
3
|
-
*
|
|
4
|
-
* bundle.
|
|
5
|
-
* previously lived next to it.
|
|
3
|
+
* OffTargetPanel.excluded_rows from the IUPHAR family-complexes collector
|
|
4
|
+
* bundle.
|
|
6
5
|
*
|
|
7
6
|
* pchembl is set to 0 because IUPHAR exposes complex structure, not
|
|
8
7
|
* ligand affinity for the complex; the row is marked selectivity_unknown
|
|
@@ -11,11 +10,11 @@
|
|
|
11
10
|
* clinical_consequence annotator populates `clinical_consequence` once
|
|
12
11
|
* the per-row annotation step runs.
|
|
13
12
|
*/
|
|
14
|
-
import type {
|
|
13
|
+
import type { ExcludedOffTargetRowSchema } from "@inflexa-ai/harness/contracts/target-dossier.js";
|
|
15
14
|
import type { z } from "zod";
|
|
16
15
|
import type { FamilyComplexesBundle } from "../schemas.js";
|
|
17
|
-
type
|
|
18
|
-
export declare function buildFamilyComplexSupplement(bundle: FamilyComplexesBundle): (Omit<
|
|
16
|
+
type ExcludedOffTargetRow = z.infer<typeof ExcludedOffTargetRowSchema>;
|
|
17
|
+
export declare function buildFamilyComplexSupplement(bundle: FamilyComplexesBundle): (Omit<ExcludedOffTargetRow, "evidence"> & {
|
|
19
18
|
evidence: [];
|
|
20
19
|
})[];
|
|
21
20
|
export {};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build the obligate-cofactor supplement rows shown in
|
|
3
|
-
*
|
|
4
|
-
* bundle.
|
|
5
|
-
* previously lived next to it.
|
|
3
|
+
* OffTargetPanel.excluded_rows from the IUPHAR family-complexes collector
|
|
4
|
+
* bundle.
|
|
6
5
|
*
|
|
7
6
|
* pchembl is set to 0 because IUPHAR exposes complex structure, not
|
|
8
7
|
* ligand affinity for the complex; the row is marked selectivity_unknown
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed form of an FDA Structured Product Label's safety prose, and the
|
|
3
|
+
* per-organ projection over it.
|
|
4
|
+
*
|
|
5
|
+
* A label arrives as prose in a handful of named sections. Ingesting it once
|
|
6
|
+
* into this shape is what lets the same warning reach two places without being
|
|
7
|
+
* fetched twice: the synthesis markdown renders the sections, and the dossier's
|
|
8
|
+
* per-organ regulatory signals are segmented out of them. Everything a reader
|
|
9
|
+
* needs to find a signal again — the application number and the label section —
|
|
10
|
+
* rides on the signal, so it satisfies the claim-evidence locator requirement.
|
|
11
|
+
*/
|
|
12
|
+
import type { OrganSystem } from "../../../contracts/organ-system.js";
|
|
13
|
+
import type { EvidenceItem } from "../../../contracts/target-dossier.js";
|
|
14
|
+
/** The label sections whose prose carries a safety finding. */
|
|
15
|
+
export declare const LABEL_SAFETY_SECTIONS: readonly ["boxed_warning", "warnings_and_precautions", "warnings", "contraindications"];
|
|
16
|
+
export type LabelSafetySection = (typeof LABEL_SAFETY_SECTIONS)[number];
|
|
17
|
+
/** One block of prose, keyed to the label section it was published under. */
|
|
18
|
+
export interface LabelSafetyText {
|
|
19
|
+
readonly section: LabelSafetySection;
|
|
20
|
+
readonly text: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* One label's identity plus its safety prose.
|
|
24
|
+
*
|
|
25
|
+
* `application_number` is required rather than nullable because it is half of
|
|
26
|
+
* what makes a signal resolvable; a producer holding a label without one has
|
|
27
|
+
* nothing to cite and does not construct this shape.
|
|
28
|
+
*/
|
|
29
|
+
export interface FdaLabelSafety {
|
|
30
|
+
readonly application_number: string;
|
|
31
|
+
readonly drug_name: string;
|
|
32
|
+
readonly effective_time?: string;
|
|
33
|
+
readonly source_url?: string;
|
|
34
|
+
readonly sections: readonly LabelSafetyText[];
|
|
35
|
+
}
|
|
36
|
+
/** A warning fragment attributed to one canonical organ, with its locator. */
|
|
37
|
+
export interface RegulatoryOrganSignal {
|
|
38
|
+
readonly organ: OrganSystem;
|
|
39
|
+
readonly drug_name: string;
|
|
40
|
+
readonly application_number: string;
|
|
41
|
+
/** The label section the prose was published under. */
|
|
42
|
+
readonly source_section: LabelSafetySection;
|
|
43
|
+
/** What a reader opens to find the excerpt: a numbered marker when the prose carried one. */
|
|
44
|
+
readonly label_section: string;
|
|
45
|
+
readonly excerpt: string;
|
|
46
|
+
readonly evidence: EvidenceItem;
|
|
47
|
+
}
|
|
48
|
+
export interface OrganSignalProjection {
|
|
49
|
+
readonly signals: readonly RegulatoryOrganSignal[];
|
|
50
|
+
/** Fragments whose organ did not resolve and were therefore not emitted. */
|
|
51
|
+
readonly dropped_count: number;
|
|
52
|
+
}
|
|
53
|
+
/** What the section reports as having emptied it when nothing resolved. */
|
|
54
|
+
export declare const ORGAN_RESOLUTION_FILTER = "label warning fragments with no resolvable organ";
|
|
55
|
+
interface WarningFragment {
|
|
56
|
+
readonly marker: string | null;
|
|
57
|
+
readonly text: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Split one section's prose into the fragments an organ can be attributed to:
|
|
61
|
+
* numbered label-section markers first, then bullets inside each marked block.
|
|
62
|
+
*/
|
|
63
|
+
export declare function splitWarningProse(text: string): WarningFragment[];
|
|
64
|
+
/**
|
|
65
|
+
* Project a set of ingested labels onto per-organ regulatory signals.
|
|
66
|
+
*
|
|
67
|
+
* A fragment that resolves to no canonical organ is dropped and counted rather
|
|
68
|
+
* than filed under a neighbouring organ. A fragment the highlights section
|
|
69
|
+
* repeats from the full section is emitted once.
|
|
70
|
+
*/
|
|
71
|
+
export declare function segmentLabelSafety(labels: readonly FdaLabelSafety[]): OrganSignalProjection;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The typed form of an FDA Structured Product Label's safety prose, and the
|
|
3
|
+
* per-organ projection over it.
|
|
4
|
+
*
|
|
5
|
+
* A label arrives as prose in a handful of named sections. Ingesting it once
|
|
6
|
+
* into this shape is what lets the same warning reach two places without being
|
|
7
|
+
* fetched twice: the synthesis markdown renders the sections, and the dossier's
|
|
8
|
+
* per-organ regulatory signals are segmented out of them. Everything a reader
|
|
9
|
+
* needs to find a signal again — the application number and the label section —
|
|
10
|
+
* rides on the signal, so it satisfies the claim-evidence locator requirement.
|
|
11
|
+
*/
|
|
12
|
+
import { classifyOrgan } from "./meddra-organ-map.js";
|
|
13
|
+
/** The label sections whose prose carries a safety finding. */
|
|
14
|
+
export const LABEL_SAFETY_SECTIONS = ["boxed_warning", "warnings_and_precautions", "warnings", "contraindications"];
|
|
15
|
+
/** What the section reports as having emptied it when nothing resolved. */
|
|
16
|
+
export const ORGAN_RESOLUTION_FILTER = "label warning fragments with no resolvable organ";
|
|
17
|
+
/**
|
|
18
|
+
* Numbered label-section markers ("5.1 Hepatotoxicity"). Split as a lookahead
|
|
19
|
+
* so the marker stays with the prose it heads. The trailing uppercase-or-paren
|
|
20
|
+
* requirement is what keeps a dose ("2.5 mg") from reading as a marker.
|
|
21
|
+
*/
|
|
22
|
+
const SECTION_MARKER_SPLIT = /(?=\b\d{1,2}\.\d{1,2}\s+[A-Z(])/;
|
|
23
|
+
const SECTION_MARKER_HEAD = /^(\d{1,2}\.\d{1,2})\s+/;
|
|
24
|
+
/**
|
|
25
|
+
* Bullet boundaries. Single newlines are NOT boundaries — labels wrap prose
|
|
26
|
+
* mid-sentence, and splitting on the wrap would sever a finding from the organ
|
|
27
|
+
* named in its own heading.
|
|
28
|
+
*/
|
|
29
|
+
const FRAGMENT_SPLIT = /[•·▪‣]+|\r?\n\s*\r?\n+|\r?\n[ \t]*[-*]\s+/;
|
|
30
|
+
/** Below this a fragment is a stray heading remnant, not a finding. */
|
|
31
|
+
const MIN_FRAGMENT_CHARS = 16;
|
|
32
|
+
/** Excerpt budget per signal — enough to read the finding, bounded for storage. */
|
|
33
|
+
const MAX_EXCERPT_CHARS = 400;
|
|
34
|
+
function clipExcerpt(text) {
|
|
35
|
+
if (text.length <= MAX_EXCERPT_CHARS)
|
|
36
|
+
return text;
|
|
37
|
+
return `${text.slice(0, MAX_EXCERPT_CHARS - 1).trimEnd()}…`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Split one section's prose into the fragments an organ can be attributed to:
|
|
41
|
+
* numbered label-section markers first, then bullets inside each marked block.
|
|
42
|
+
*/
|
|
43
|
+
export function splitWarningProse(text) {
|
|
44
|
+
const fragments = [];
|
|
45
|
+
for (const block of text.split(SECTION_MARKER_SPLIT)) {
|
|
46
|
+
const head = SECTION_MARKER_HEAD.exec(block);
|
|
47
|
+
const marker = head?.[1] ?? null;
|
|
48
|
+
const body = head ? block.slice(head[0].length) : block;
|
|
49
|
+
for (const piece of body.split(FRAGMENT_SPLIT)) {
|
|
50
|
+
const collapsed = piece.replace(/\s+/g, " ").trim();
|
|
51
|
+
if (collapsed.length < MIN_FRAGMENT_CHARS)
|
|
52
|
+
continue;
|
|
53
|
+
fragments.push({ marker, text: collapsed });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return fragments;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a fragment onto the canonical organ vocabulary at its own boundary.
|
|
60
|
+
* The heading — the prose up to its first colon — is tried first, because a
|
|
61
|
+
* label states the organ there and then discusses it; scanning the whole
|
|
62
|
+
* fragment first would let an incidental mention outrank the heading.
|
|
63
|
+
*/
|
|
64
|
+
function resolveFragmentOrgan(fragment) {
|
|
65
|
+
const colon = fragment.indexOf(":");
|
|
66
|
+
const heading = colon > 0 ? fragment.slice(0, colon) : "";
|
|
67
|
+
return (heading ? classifyOrgan(heading) : null) ?? classifyOrgan(fragment);
|
|
68
|
+
}
|
|
69
|
+
function buildEvidence(label, labelSection, excerpt, organ) {
|
|
70
|
+
return {
|
|
71
|
+
source: "openfda:label",
|
|
72
|
+
predicate: "label_warning",
|
|
73
|
+
excerpt,
|
|
74
|
+
regulatory_reference: {
|
|
75
|
+
document: `FDA prescribing information — ${label.drug_name}`,
|
|
76
|
+
section: labelSection,
|
|
77
|
+
doc_id: label.application_number,
|
|
78
|
+
...(label.source_url ? { doc_url: label.source_url } : {}),
|
|
79
|
+
},
|
|
80
|
+
metadata: {
|
|
81
|
+
organ,
|
|
82
|
+
drug_name: label.drug_name,
|
|
83
|
+
...(label.effective_time ? { effective_time: label.effective_time } : {}),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A short fragment can be a substring of a longer unrelated one, so containment
|
|
89
|
+
* only settles a repeat once the contained text is long enough to be prose
|
|
90
|
+
* rather than a phrase.
|
|
91
|
+
*/
|
|
92
|
+
const MIN_CONTAINMENT_CHARS = 60;
|
|
93
|
+
function isRepeatOf(seen, candidate) {
|
|
94
|
+
if (seen === candidate)
|
|
95
|
+
return true;
|
|
96
|
+
if (seen.includes(candidate))
|
|
97
|
+
return candidate.length >= MIN_CONTAINMENT_CHARS;
|
|
98
|
+
if (candidate.includes(seen))
|
|
99
|
+
return seen.length >= MIN_CONTAINMENT_CHARS;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Order a label's sections for segmentation: published section order, then
|
|
104
|
+
* longest prose first within a section. openFDA returns the highlights summary
|
|
105
|
+
* and the full section under the same key, so reading the longer one first is
|
|
106
|
+
* what makes the shorter repeat recognisable as a repeat rather than the other
|
|
107
|
+
* way round.
|
|
108
|
+
*/
|
|
109
|
+
function segmentationOrder(sections) {
|
|
110
|
+
const firstSeenAt = new Map();
|
|
111
|
+
for (const [index, s] of sections.entries()) {
|
|
112
|
+
if (!firstSeenAt.has(s.section))
|
|
113
|
+
firstSeenAt.set(s.section, index);
|
|
114
|
+
}
|
|
115
|
+
return [...sections].sort((a, b) => firstSeenAt.get(a.section) - firstSeenAt.get(b.section) || b.text.length - a.text.length);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Project a set of ingested labels onto per-organ regulatory signals.
|
|
119
|
+
*
|
|
120
|
+
* A fragment that resolves to no canonical organ is dropped and counted rather
|
|
121
|
+
* than filed under a neighbouring organ. A fragment the highlights section
|
|
122
|
+
* repeats from the full section is emitted once.
|
|
123
|
+
*/
|
|
124
|
+
export function segmentLabelSafety(labels) {
|
|
125
|
+
const signals = [];
|
|
126
|
+
let droppedCount = 0;
|
|
127
|
+
for (const label of labels) {
|
|
128
|
+
const seenMarkers = new Set();
|
|
129
|
+
const seenText = [];
|
|
130
|
+
for (const section of segmentationOrder(label.sections)) {
|
|
131
|
+
// A numbered marker settles a repeat only across blocks: one block
|
|
132
|
+
// legitimately carries several fragments under its own `5.1`, while
|
|
133
|
+
// a second block reprising `5.1` is the highlights summary of it.
|
|
134
|
+
const blockMarkers = new Set();
|
|
135
|
+
for (const fragment of splitWarningProse(section.text)) {
|
|
136
|
+
const normalized = fragment.text.toLowerCase();
|
|
137
|
+
const markerKey = fragment.marker ? `${section.section}:${fragment.marker}` : null;
|
|
138
|
+
const repeated = (markerKey !== null && seenMarkers.has(markerKey)) || seenText.some((seen) => isRepeatOf(seen, normalized));
|
|
139
|
+
if (repeated)
|
|
140
|
+
continue;
|
|
141
|
+
if (markerKey !== null)
|
|
142
|
+
blockMarkers.add(markerKey);
|
|
143
|
+
seenText.push(normalized);
|
|
144
|
+
const organ = resolveFragmentOrgan(fragment.text);
|
|
145
|
+
if (organ === null) {
|
|
146
|
+
droppedCount += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const labelSection = fragment.marker ?? section.section;
|
|
150
|
+
const excerpt = clipExcerpt(fragment.text);
|
|
151
|
+
signals.push({
|
|
152
|
+
organ,
|
|
153
|
+
drug_name: label.drug_name,
|
|
154
|
+
application_number: label.application_number,
|
|
155
|
+
source_section: section.section,
|
|
156
|
+
label_section: labelSection,
|
|
157
|
+
excerpt,
|
|
158
|
+
evidence: buildEvidence(label, labelSection, excerpt, organ),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
for (const marker of blockMarkers)
|
|
162
|
+
seenMarkers.add(marker);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { signals, dropped_count: droppedCount };
|
|
166
|
+
}
|
|
@@ -28,12 +28,3 @@ export interface HeterodimerFilterInput {
|
|
|
28
28
|
* "<primary>-<accessory>" (some sources use a hyphen)
|
|
29
29
|
*/
|
|
30
30
|
export declare function makeHeterodimerOfAssessmentFilter(input: HeterodimerFilterInput): (offTargetName: string) => boolean;
|
|
31
|
-
/**
|
|
32
|
-
* One-shot helper for callers that already have an accessory list in hand.
|
|
33
|
-
* Equivalent to `makeHeterodimerOfAssessmentFilter(input)(offTargetName)`.
|
|
34
|
-
*/
|
|
35
|
-
export declare function isHeterodimerOfAssessment(input: {
|
|
36
|
-
assessmentGeneSymbol: string;
|
|
37
|
-
accessoryProteinNames: string[];
|
|
38
|
-
offTargetName: string;
|
|
39
|
-
}): boolean;
|
|
@@ -42,13 +42,3 @@ export function makeHeterodimerOfAssessmentFilter(input) {
|
|
|
42
42
|
return m[1].toUpperCase() === target;
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
46
|
-
* One-shot helper for callers that already have an accessory list in hand.
|
|
47
|
-
* Equivalent to `makeHeterodimerOfAssessmentFilter(input)(offTargetName)`.
|
|
48
|
-
*/
|
|
49
|
-
export function isHeterodimerOfAssessment(input) {
|
|
50
|
-
return makeHeterodimerOfAssessmentFilter({
|
|
51
|
-
assessmentGeneSymbol: input.assessmentGeneSymbol,
|
|
52
|
-
accessoryProteinNames: input.accessoryProteinNames,
|
|
53
|
-
})(input.offTargetName);
|
|
54
|
-
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human Phenotype Ontology terms onto the canonical organ vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* HPO organises every phenotypic abnormality under organ-system roots, and a
|
|
5
|
+
* phenotype association carries its full ancestor closure. Resolution is
|
|
6
|
+
* therefore membership of an ancestor id in this table — equality on
|
|
7
|
+
* identifiers, not matching on prose, so a renamed term keeps resolving and an
|
|
8
|
+
* incidental word in a label never decides an organ.
|
|
9
|
+
*
|
|
10
|
+
* The table is ordered most specific first, because a term sits under several
|
|
11
|
+
* roots at once: a liver phenotype's closure also contains the digestive-system
|
|
12
|
+
* root, and reading the broad root first would file every hepatic finding under
|
|
13
|
+
* `gi`. Roots with no canonical organ to land in (ear, head and neck, growth,
|
|
14
|
+
* prenatal) are absent — a phenotype is reported under the organ it affects or
|
|
15
|
+
* not at all.
|
|
16
|
+
*
|
|
17
|
+
* A root broader than any single token is absent for that same reason, even
|
|
18
|
+
* where a token's name is close to it: the endocrine-system root spans every
|
|
19
|
+
* endocrine organ while the only endocrine token names the thyroid, so an
|
|
20
|
+
* adrenal phenotype resolves through no root and is dropped. The neoplasm root
|
|
21
|
+
* says a phenotype is malignant, not where it sits, so a neoplasm resolves
|
|
22
|
+
* through the organ root it also carries — a lung neoplasm is respiratory, and
|
|
23
|
+
* a neoplasm with no organ root at all resolves to nothing.
|
|
24
|
+
*
|
|
25
|
+
* The values are the canonical tokens, so this module declares no organ
|
|
26
|
+
* enumeration of its own.
|
|
27
|
+
*/
|
|
28
|
+
import type { OrganSystem } from "../../../contracts/organ-system.js";
|
|
29
|
+
/**
|
|
30
|
+
* Resolve an HPO term onto a canonical organ from its own id plus its ancestor
|
|
31
|
+
* closure. Returns null when nothing in the ancestry denotes a canonical organ
|
|
32
|
+
* system — the caller drops the phenotype and counts it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveHpoOrgan(termId: string, ancestorIds: readonly string[]): OrganSystem | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human Phenotype Ontology terms onto the canonical organ vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* HPO organises every phenotypic abnormality under organ-system roots, and a
|
|
5
|
+
* phenotype association carries its full ancestor closure. Resolution is
|
|
6
|
+
* therefore membership of an ancestor id in this table — equality on
|
|
7
|
+
* identifiers, not matching on prose, so a renamed term keeps resolving and an
|
|
8
|
+
* incidental word in a label never decides an organ.
|
|
9
|
+
*
|
|
10
|
+
* The table is ordered most specific first, because a term sits under several
|
|
11
|
+
* roots at once: a liver phenotype's closure also contains the digestive-system
|
|
12
|
+
* root, and reading the broad root first would file every hepatic finding under
|
|
13
|
+
* `gi`. Roots with no canonical organ to land in (ear, head and neck, growth,
|
|
14
|
+
* prenatal) are absent — a phenotype is reported under the organ it affects or
|
|
15
|
+
* not at all.
|
|
16
|
+
*
|
|
17
|
+
* A root broader than any single token is absent for that same reason, even
|
|
18
|
+
* where a token's name is close to it: the endocrine-system root spans every
|
|
19
|
+
* endocrine organ while the only endocrine token names the thyroid, so an
|
|
20
|
+
* adrenal phenotype resolves through no root and is dropped. The neoplasm root
|
|
21
|
+
* says a phenotype is malignant, not where it sits, so a neoplasm resolves
|
|
22
|
+
* through the organ root it also carries — a lung neoplasm is respiratory, and
|
|
23
|
+
* a neoplasm with no organ root at all resolves to nothing.
|
|
24
|
+
*
|
|
25
|
+
* The values are the canonical tokens, so this module declares no organ
|
|
26
|
+
* enumeration of its own.
|
|
27
|
+
*/
|
|
28
|
+
/** HPO ancestor id → canonical organ, most specific ancestor first. */
|
|
29
|
+
const HPO_ORGAN_ANCESTORS = [
|
|
30
|
+
["HP:0001392", "hepatic"], // Abnormality of the liver
|
|
31
|
+
["HP:0001733", "pancreas"], // Abnormality of the pancreas
|
|
32
|
+
["HP:0000820", "endocrine_thyroid"], // Abnormality of the thyroid gland
|
|
33
|
+
["HP:0000079", "renal"], // Abnormality of the urinary system
|
|
34
|
+
["HP:0000078", "reproductive"], // Abnormality of the genital system
|
|
35
|
+
["HP:0002597", "vascular"], // Abnormality of the vasculature
|
|
36
|
+
["HP:0001627", "cardiac"], // Abnormal heart morphology
|
|
37
|
+
["HP:0001626", "cardiac"], // Abnormality of the cardiovascular system
|
|
38
|
+
["HP:0000759", "pns"], // Abnormal peripheral nervous system morphology
|
|
39
|
+
["HP:0012639", "cns"], // Abnormal nervous system morphology
|
|
40
|
+
["HP:0000707", "cns"], // Abnormality of the nervous system
|
|
41
|
+
["HP:0002715", "immune"], // Abnormality of the immune system
|
|
42
|
+
["HP:0001871", "hematologic"], // Abnormality of blood and blood-forming tissues
|
|
43
|
+
["HP:0001939", "metabolic"], // Abnormality of metabolism/homeostasis
|
|
44
|
+
["HP:0002086", "respiratory"], // Abnormality of the respiratory system
|
|
45
|
+
["HP:0025031", "gi"], // Abnormality of the digestive system
|
|
46
|
+
["HP:0000478", "ocular"], // Abnormality of the eye
|
|
47
|
+
["HP:0001574", "dermatologic"], // Abnormality of the integument
|
|
48
|
+
["HP:0003011", "musculoskeletal"], // Abnormality of the musculature
|
|
49
|
+
["HP:0000924", "musculoskeletal"], // Abnormality of the skeletal system
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* Resolve an HPO term onto a canonical organ from its own id plus its ancestor
|
|
53
|
+
* closure. Returns null when nothing in the ancestry denotes a canonical organ
|
|
54
|
+
* system — the caller drops the phenotype and counts it.
|
|
55
|
+
*/
|
|
56
|
+
export function resolveHpoOrgan(termId, ancestorIds) {
|
|
57
|
+
const ancestry = new Set([termId, ...ancestorIds]);
|
|
58
|
+
for (const [id, organ] of HPO_ORGAN_ANCESTORS) {
|
|
59
|
+
if (ancestry.has(id))
|
|
60
|
+
return organ;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IMPC top-level-phenotype vocabulary onto the canonical organ vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Buckets with no organ system to land in (hearing, growth, mortality,
|
|
5
|
+
* craniofacial) are absent: a phenotype is reported under the organ it affects
|
|
6
|
+
* or not at all, never under an approximate neighbour. The map's values are the
|
|
7
|
+
* canonical tokens, so it declares no enumeration of its own.
|
|
8
|
+
*
|
|
9
|
+
* The endocrine bucket is absent for the same reason: IMPC's
|
|
10
|
+
* "endocrine/exocrine gland phenotype" spans every endocrine organ and the
|
|
11
|
+
* exocrine glands besides, while the only endocrine token names the thyroid.
|
|
12
|
+
* A knockout in that bucket names no canonical organ, so it is dropped and
|
|
13
|
+
* counted rather than read as a thyroid finding.
|
|
14
|
+
*/
|
|
15
|
+
import type { OrganSystem } from "../../../contracts/organ-system.js";
|
|
16
|
+
/** Resolve one IMPC bucket, or null when it denotes no canonical organ. */
|
|
17
|
+
export declare function resolveImpcOrgan(bucket: string): OrganSystem | null;
|
|
18
|
+
/** Resolve a set of IMPC buckets, dropping those with no canonical organ. */
|
|
19
|
+
export declare function toOrganSystems(impcOrganSystems: readonly string[]): OrganSystem[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IMPC top-level-phenotype vocabulary onto the canonical organ vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Buckets with no organ system to land in (hearing, growth, mortality,
|
|
5
|
+
* craniofacial) are absent: a phenotype is reported under the organ it affects
|
|
6
|
+
* or not at all, never under an approximate neighbour. The map's values are the
|
|
7
|
+
* canonical tokens, so it declares no enumeration of its own.
|
|
8
|
+
*
|
|
9
|
+
* The endocrine bucket is absent for the same reason: IMPC's
|
|
10
|
+
* "endocrine/exocrine gland phenotype" spans every endocrine organ and the
|
|
11
|
+
* exocrine glands besides, while the only endocrine token names the thyroid.
|
|
12
|
+
* A knockout in that bucket names no canonical organ, so it is dropped and
|
|
13
|
+
* counted rather than read as a thyroid finding.
|
|
14
|
+
*/
|
|
15
|
+
const IMPC_ORGAN_SYSTEMS = {
|
|
16
|
+
cardiovascular: "cardiac",
|
|
17
|
+
gastrointestinal: "gi",
|
|
18
|
+
hematologic: "hematologic",
|
|
19
|
+
metabolic: "metabolic",
|
|
20
|
+
immune: "immune",
|
|
21
|
+
skin: "dermatologic",
|
|
22
|
+
hepatic: "hepatic",
|
|
23
|
+
musculoskeletal: "musculoskeletal",
|
|
24
|
+
skeleton: "musculoskeletal",
|
|
25
|
+
cns: "cns",
|
|
26
|
+
renal: "renal",
|
|
27
|
+
reproductive: "reproductive",
|
|
28
|
+
respiratory: "respiratory",
|
|
29
|
+
vision: "ocular",
|
|
30
|
+
};
|
|
31
|
+
/** Resolve one IMPC bucket, or null when it denotes no canonical organ. */
|
|
32
|
+
export function resolveImpcOrgan(bucket) {
|
|
33
|
+
return IMPC_ORGAN_SYSTEMS[bucket] ?? null;
|
|
34
|
+
}
|
|
35
|
+
/** Resolve a set of IMPC buckets, dropping those with no canonical organ. */
|
|
36
|
+
export function toOrganSystems(impcOrganSystems) {
|
|
37
|
+
const resolved = impcOrganSystems.map((o) => resolveImpcOrgan(o)).filter((o) => o !== null);
|
|
38
|
+
return [...new Set(resolved)];
|
|
39
|
+
}
|
|
@@ -1,13 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* MedDRA terms and CT.gov system-organ classes onto the canonical organ
|
|
3
|
+
* vocabulary.
|
|
4
|
+
*
|
|
5
|
+
* The values are the canonical tokens, so this module declares no organ
|
|
6
|
+
* enumeration of its own. Every token in the vocabulary is reachable through
|
|
7
|
+
* the rules below, so nothing a producer may legitimately attribute is
|
|
8
|
+
* unreachable by accident.
|
|
9
|
+
*
|
|
10
|
+
* A term resolves onto the organ it names, or onto nothing. A term whose
|
|
11
|
+
* compartment is genuinely ambiguous — "Paraesthesia" and the other nervous
|
|
12
|
+
* system terms that name no compartment, "Adrenal insufficiency" and the other
|
|
13
|
+
* endocrine terms outside the thyroid — matches no rule, so the caller drops
|
|
14
|
+
* and counts it rather than filing it under a neighbour.
|
|
15
|
+
*/
|
|
16
|
+
import type { OrganSystem } from "../../../contracts/organ-system.js";
|
|
17
|
+
export declare function classifyOrgan(meddraTerm: string): OrganSystem | null;
|
|
3
18
|
/**
|
|
4
19
|
* MedDRA SOC (System Organ Class) names as they appear in CT.gov structured
|
|
5
20
|
* AE tables → canonical organ. SOC strings (e.g. "Gastrointestinal disorders")
|
|
6
21
|
* do not contain MedDRA PT stems, so `classifyOrgan` returns null for them —
|
|
7
22
|
* the SOC map is the primary path; `classifyOrgan(term)` is the fallback when
|
|
8
23
|
* the SOC string isn't in the map.
|
|
24
|
+
*
|
|
25
|
+
* A SOC that spans more than one canonical organ is absent: "Nervous system
|
|
26
|
+
* disorders" covers both nervous-system tokens and "Endocrine disorders" is
|
|
27
|
+
* broader than the thyroid, so a row under either resolves by its preferred
|
|
28
|
+
* term or not at all.
|
|
9
29
|
*/
|
|
10
|
-
export declare const TRIAL_AE_ORGAN_MAP: Record<string,
|
|
30
|
+
export declare const TRIAL_AE_ORGAN_MAP: Record<string, OrganSystem>;
|
|
11
31
|
/**
|
|
12
32
|
* Classify a trial AE row by its SOC (preferred) or its individual MedDRA
|
|
13
33
|
* preferred term (fallback). Shared between the Phase-4 assembler's
|
|
@@ -17,14 +37,4 @@ export declare const TRIAL_AE_ORGAN_MAP: Record<string, CanonicalOrgan>;
|
|
|
17
37
|
export declare function classifyTrialAe(ae: {
|
|
18
38
|
term?: string | null;
|
|
19
39
|
organ?: string | null;
|
|
20
|
-
}):
|
|
21
|
-
/**
|
|
22
|
-
* Classify a polypharm off-target panel row by its canonical `organ_system`
|
|
23
|
-
* string (set by the curated safety panel — `cardiac` | `hepatic` | `cns` |
|
|
24
|
-
* `renal` | `gi` | `hematologic` | `immune` | `metabolic` | `respiratory`).
|
|
25
|
-
*
|
|
26
|
-
* Falls back to `classifyOrgan` if the upstream string is non-canonical;
|
|
27
|
-
* this preserves coverage if a future collector emits MedDRA terms instead
|
|
28
|
-
* of canonical organ names.
|
|
29
|
-
*/
|
|
30
|
-
export declare function classifyPolypharmOrgan(organSystem: string | null | undefined): CanonicalOrgan | null;
|
|
40
|
+
}): OrganSystem | null;
|