@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
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chart derivation of the report page.
|
|
3
|
+
*
|
|
4
|
+
* A chart block binds one whole-table artifact and a channel mapping. `deriveChartOption` turns the
|
|
5
|
+
* chart type, the encoding, and the resolved rows into one ECharts option object. The chart container
|
|
6
|
+
* markup lives beside this file, and it wraps the option that this derivation gives.
|
|
7
|
+
*
|
|
8
|
+
* The renderer computes no aggregate. Each plotted number stays traceable to one cell of the evidence
|
|
9
|
+
* artifact. A pie or a heatmap arrives one row per category or per cell, thus a repeated category or a
|
|
10
|
+
* repeated pair is a refusal and not a silent sum.
|
|
11
|
+
*
|
|
12
|
+
* The derivation is deterministic. Every object builds in one fixed key order, and the code reads no
|
|
13
|
+
* clock, no random value, and no locale. A string comparison uses the code-unit order (`<`) and never
|
|
14
|
+
* `localeCompare`, thus the same rows give the same bytes on every host.
|
|
15
|
+
*/
|
|
16
|
+
import { err, ok } from "neverthrow";
|
|
17
|
+
import { normalizeEchartSpec } from "../tools/display/normalize-echart-spec.js";
|
|
18
|
+
/**
|
|
19
|
+
* The ten-stop viridis ramp for a heatmap `visualMap`. The stops run dark to light, thus a continuous
|
|
20
|
+
* scale maps the low value to `#440154` and the high value to `#fde725`.
|
|
21
|
+
*/
|
|
22
|
+
const VIRIDIS = ["#440154", "#482777", "#3e4989", "#31688e", "#26828e", "#1f9e89", "#35b779", "#6ece58", "#b5de2b", "#fde725"];
|
|
23
|
+
/**
|
|
24
|
+
* Derive the ECharts option for a chart block, then pass it through `normalizeEchartSpec`.
|
|
25
|
+
*
|
|
26
|
+
* The normalizer owns the bottom legend, the save action, and the axis-label discipline. Thus the
|
|
27
|
+
* derivation sets no `title`, no `legend`, no `toolbox`, and no explicit series color. The theme palette
|
|
28
|
+
* assigns the series colors by their order.
|
|
29
|
+
*/
|
|
30
|
+
export function deriveChartOption(block, rows, columns) {
|
|
31
|
+
return deriveRaw(block, rows, columns).map((option) => normalizeEchartSpec(option, { title: block.title }));
|
|
32
|
+
}
|
|
33
|
+
/** Dispatch to the per-type derivation. Each type holds one fixed rule. */
|
|
34
|
+
function deriveRaw(block, rows, columns) {
|
|
35
|
+
switch (block.chartType) {
|
|
36
|
+
case "bar":
|
|
37
|
+
return deriveBar(block, rows, columns);
|
|
38
|
+
case "line":
|
|
39
|
+
return deriveLine(block, rows, columns);
|
|
40
|
+
case "scatter":
|
|
41
|
+
return deriveScatter(block, rows, columns);
|
|
42
|
+
case "histogram":
|
|
43
|
+
return deriveHistogram(block, rows, columns);
|
|
44
|
+
case "box":
|
|
45
|
+
return deriveBox(block, rows, columns);
|
|
46
|
+
case "heatmap":
|
|
47
|
+
return deriveHeatmap(block, rows, columns);
|
|
48
|
+
case "pie":
|
|
49
|
+
return derivePie(block, rows, columns);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// ── The per-type derivations ────────────────────────────────────────────────
|
|
53
|
+
/** A category x axis, a value y axis, and one bar series per group with the `[x, y]` pairs. */
|
|
54
|
+
function deriveBar(block, rows, columns) {
|
|
55
|
+
const xResult = requireColumn(block, rows, columns, "x");
|
|
56
|
+
if (xResult.isErr())
|
|
57
|
+
return err(xResult.error);
|
|
58
|
+
const yResult = requireColumn(block, rows, columns, "y");
|
|
59
|
+
if (yResult.isErr())
|
|
60
|
+
return err(yResult.error);
|
|
61
|
+
const x = xResult.value;
|
|
62
|
+
const y = yResult.value;
|
|
63
|
+
const categories = firstAppearance(rows.map((row) => row[x]));
|
|
64
|
+
const series = groupedSeries(rows, block.encoding.group, (groupRows, name) => ({
|
|
65
|
+
type: "bar",
|
|
66
|
+
...(name !== undefined ? { name: String(name) } : {}),
|
|
67
|
+
barGap: 0,
|
|
68
|
+
data: groupRows.map((row) => [row[x], row[y]]),
|
|
69
|
+
}));
|
|
70
|
+
return ok({
|
|
71
|
+
xAxis: { type: "category", data: categories, name: x },
|
|
72
|
+
yAxis: { type: "value", name: y },
|
|
73
|
+
series,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Per-column axis inference, and one line series per group whose data sorts by x. */
|
|
77
|
+
function deriveLine(block, rows, columns) {
|
|
78
|
+
const xResult = requireColumn(block, rows, columns, "x");
|
|
79
|
+
if (xResult.isErr())
|
|
80
|
+
return err(xResult.error);
|
|
81
|
+
const yResult = requireColumn(block, rows, columns, "y");
|
|
82
|
+
if (yResult.isErr())
|
|
83
|
+
return err(yResult.error);
|
|
84
|
+
const x = xResult.value;
|
|
85
|
+
const y = yResult.value;
|
|
86
|
+
const series = groupedSeries(rows, block.encoding.group, (groupRows, name) => ({
|
|
87
|
+
type: "line",
|
|
88
|
+
...(name !== undefined ? { name: String(name) } : {}),
|
|
89
|
+
showSymbol: false,
|
|
90
|
+
data: sortByX(groupRows.map((row) => [row[x], row[y]])),
|
|
91
|
+
}));
|
|
92
|
+
return ok({
|
|
93
|
+
xAxis: inferAxis(rows, x),
|
|
94
|
+
yAxis: inferAxis(rows, y),
|
|
95
|
+
series,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** The same axis inference as a line chart, with no sort and the large-render flags. */
|
|
99
|
+
function deriveScatter(block, rows, columns) {
|
|
100
|
+
const xResult = requireColumn(block, rows, columns, "x");
|
|
101
|
+
if (xResult.isErr())
|
|
102
|
+
return err(xResult.error);
|
|
103
|
+
const yResult = requireColumn(block, rows, columns, "y");
|
|
104
|
+
if (yResult.isErr())
|
|
105
|
+
return err(yResult.error);
|
|
106
|
+
const x = xResult.value;
|
|
107
|
+
const y = yResult.value;
|
|
108
|
+
const series = groupedSeries(rows, block.encoding.group, (groupRows, name) => ({
|
|
109
|
+
type: "scatter",
|
|
110
|
+
...(name !== undefined ? { name: String(name) } : {}),
|
|
111
|
+
large: true,
|
|
112
|
+
largeThreshold: 2000,
|
|
113
|
+
data: groupRows.map((row) => [row[x], row[y]]),
|
|
114
|
+
}));
|
|
115
|
+
return ok({
|
|
116
|
+
xAxis: inferAxis(rows, x),
|
|
117
|
+
yAxis: inferAxis(rows, y),
|
|
118
|
+
series,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** Equal-width bins over the global range. Each group shares the same edges. */
|
|
122
|
+
function deriveHistogram(block, rows, columns) {
|
|
123
|
+
const xResult = requireColumn(block, rows, columns, "x");
|
|
124
|
+
if (xResult.isErr())
|
|
125
|
+
return err(xResult.error);
|
|
126
|
+
const x = xResult.value;
|
|
127
|
+
const groupCol = block.encoding.group;
|
|
128
|
+
if (rows.length === 0) {
|
|
129
|
+
return ok({
|
|
130
|
+
xAxis: { type: "value", scale: true },
|
|
131
|
+
yAxis: { type: "value", name: "Count" },
|
|
132
|
+
series: [{ type: "bar", barWidth: "99%", data: [] }],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const values = numericColumn(rows, x);
|
|
136
|
+
if (values.length === 0) {
|
|
137
|
+
return err(problem(block.id, `The histogram column "${x}" holds no numeric value.`));
|
|
138
|
+
}
|
|
139
|
+
const edges = histogramEdges(values);
|
|
140
|
+
let series;
|
|
141
|
+
if (groupCol === undefined) {
|
|
142
|
+
series = [histogramSeries(values, edges, undefined)];
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
series = [];
|
|
146
|
+
for (const name of firstAppearance(rows.map((row) => row[groupCol]))) {
|
|
147
|
+
const groupValues = numericColumn(rows.filter((row) => row[groupCol] === name), x);
|
|
148
|
+
series.push(histogramSeries(groupValues, edges, name));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return ok({
|
|
152
|
+
xAxis: { type: "value", scale: true },
|
|
153
|
+
yAxis: { type: "value", name: "Count" },
|
|
154
|
+
series,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** A five-number summary per category, plus a paired scatter series for the outliers. */
|
|
158
|
+
function deriveBox(block, rows, columns) {
|
|
159
|
+
const xResult = requireColumn(block, rows, columns, "x");
|
|
160
|
+
if (xResult.isErr())
|
|
161
|
+
return err(xResult.error);
|
|
162
|
+
const yResult = requireColumn(block, rows, columns, "y");
|
|
163
|
+
if (yResult.isErr())
|
|
164
|
+
return err(yResult.error);
|
|
165
|
+
const x = xResult.value;
|
|
166
|
+
const y = yResult.value;
|
|
167
|
+
const categories = firstAppearance(rows.map((row) => row[x]));
|
|
168
|
+
const groups = splitGroups(rows, block.encoding.group);
|
|
169
|
+
const series = [];
|
|
170
|
+
for (const group of groups) {
|
|
171
|
+
const boxData = [];
|
|
172
|
+
const outliers = [];
|
|
173
|
+
for (let index = 0; index < categories.length; index++) {
|
|
174
|
+
const values = numericColumn(group.rows.filter((row) => row[x] === categories[index]), y);
|
|
175
|
+
// A category with fewer than five values renders as an empty box.
|
|
176
|
+
if (values.length < 5) {
|
|
177
|
+
boxData.push("-");
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const summary = boxSummary(values);
|
|
181
|
+
boxData.push(summary.box);
|
|
182
|
+
for (const outlier of summary.outliers) {
|
|
183
|
+
outliers.push([index, outlier]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
series.push({
|
|
187
|
+
type: "boxplot",
|
|
188
|
+
...(group.name !== undefined ? { name: String(group.name) } : {}),
|
|
189
|
+
data: boxData,
|
|
190
|
+
});
|
|
191
|
+
// The outlier scatter pairs with its box series, thus it only appears when an outlier exists.
|
|
192
|
+
if (outliers.length > 0) {
|
|
193
|
+
series.push({
|
|
194
|
+
type: "scatter",
|
|
195
|
+
...(group.name !== undefined ? { name: String(group.name) } : {}),
|
|
196
|
+
symbolSize: 4,
|
|
197
|
+
data: outliers,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return ok({
|
|
202
|
+
xAxis: { type: "category", data: categories, name: x },
|
|
203
|
+
yAxis: { type: "value", name: y },
|
|
204
|
+
series,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/** A dense grid over every pair of x and y categories, with a viridis `visualMap`. */
|
|
208
|
+
function deriveHeatmap(block, rows, columns) {
|
|
209
|
+
const xResult = requireColumn(block, rows, columns, "x");
|
|
210
|
+
if (xResult.isErr())
|
|
211
|
+
return err(xResult.error);
|
|
212
|
+
const yResult = requireColumn(block, rows, columns, "y");
|
|
213
|
+
if (yResult.isErr())
|
|
214
|
+
return err(yResult.error);
|
|
215
|
+
const valueResult = requireColumn(block, rows, columns, "value");
|
|
216
|
+
if (valueResult.isErr())
|
|
217
|
+
return err(valueResult.error);
|
|
218
|
+
const x = xResult.value;
|
|
219
|
+
const y = yResult.value;
|
|
220
|
+
const valueColumn = valueResult.value;
|
|
221
|
+
const xCategories = firstAppearance(rows.map((row) => row[x]));
|
|
222
|
+
const yCategories = firstAppearance(rows.map((row) => row[y]));
|
|
223
|
+
const cells = new Map();
|
|
224
|
+
for (const row of rows) {
|
|
225
|
+
const key = pairKey(row[x], row[y]);
|
|
226
|
+
if (cells.has(key)) {
|
|
227
|
+
return err(problem(block.id, `The heatmap holds the pair (${String(row[x])}, ${String(row[y])}) more than one time.`));
|
|
228
|
+
}
|
|
229
|
+
cells.set(key, toNumber(row[valueColumn]));
|
|
230
|
+
}
|
|
231
|
+
const data = [];
|
|
232
|
+
const finite = [];
|
|
233
|
+
for (let xi = 0; xi < xCategories.length; xi++) {
|
|
234
|
+
for (let yi = 0; yi < yCategories.length; yi++) {
|
|
235
|
+
const stored = cells.get(pairKey(xCategories[xi], yCategories[yi]));
|
|
236
|
+
const value = stored === undefined ? null : stored;
|
|
237
|
+
data.push([xi, yi, value]);
|
|
238
|
+
if (value !== null)
|
|
239
|
+
finite.push(value);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const min = finite.length > 0 ? Math.min(...finite) : 0;
|
|
243
|
+
const max = finite.length > 0 ? Math.max(...finite) : 1;
|
|
244
|
+
return ok({
|
|
245
|
+
xAxis: { type: "category", data: xCategories.map(String), name: x, splitArea: { show: true } },
|
|
246
|
+
yAxis: { type: "category", data: yCategories.map(String), name: y, splitArea: { show: true } },
|
|
247
|
+
visualMap: {
|
|
248
|
+
type: "continuous",
|
|
249
|
+
min,
|
|
250
|
+
max,
|
|
251
|
+
calculable: true,
|
|
252
|
+
orient: "horizontal",
|
|
253
|
+
left: "center",
|
|
254
|
+
bottom: 0,
|
|
255
|
+
inRange: { color: [...VIRIDIS] },
|
|
256
|
+
},
|
|
257
|
+
series: [{ type: "heatmap", data }],
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/** One slice per group category, in first-appearance order. A repeated category refuses. */
|
|
261
|
+
function derivePie(block, rows, columns) {
|
|
262
|
+
const groupResult = requireColumn(block, rows, columns, "group");
|
|
263
|
+
if (groupResult.isErr())
|
|
264
|
+
return err(groupResult.error);
|
|
265
|
+
const valueResult = requireColumn(block, rows, columns, "value");
|
|
266
|
+
if (valueResult.isErr())
|
|
267
|
+
return err(valueResult.error);
|
|
268
|
+
const groupColumn = groupResult.value;
|
|
269
|
+
const valueColumn = valueResult.value;
|
|
270
|
+
const seen = new Set();
|
|
271
|
+
const data = [];
|
|
272
|
+
for (const row of rows) {
|
|
273
|
+
const name = String(row[groupColumn]);
|
|
274
|
+
if (seen.has(name)) {
|
|
275
|
+
return err(problem(block.id, `The pie holds the category "${name}" more than one time.`));
|
|
276
|
+
}
|
|
277
|
+
seen.add(name);
|
|
278
|
+
const numeric = toNumber(row[valueColumn]);
|
|
279
|
+
data.push({ name, value: numeric === null ? row[valueColumn] : numeric });
|
|
280
|
+
}
|
|
281
|
+
return ok({
|
|
282
|
+
series: [{ type: "pie", radius: "55%", data }],
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// ── The shared building blocks ──────────────────────────────────────────────
|
|
286
|
+
/** A typed `invalid-chart-input` problem that names the block and the cause. */
|
|
287
|
+
function problem(blockId, detail) {
|
|
288
|
+
return { blockId, kind: "invalid-chart-input", detail };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Resolve one demanded channel to its column name.
|
|
292
|
+
*
|
|
293
|
+
* A channel that the encoding omits is a refusal. A named column that no row holds is a refusal too.
|
|
294
|
+
* A zero-row chart skips the row check, thus it renders an empty container.
|
|
295
|
+
*/
|
|
296
|
+
function requireColumn(block, rows, columns, channel) {
|
|
297
|
+
const column = block.encoding[channel];
|
|
298
|
+
if (column === undefined) {
|
|
299
|
+
return err(problem(block.id, `The ${block.chartType} chart needs a column for the "${channel}" channel.`));
|
|
300
|
+
}
|
|
301
|
+
if (rows.length > 0 && !columnPresent(column, rows, columns)) {
|
|
302
|
+
return err(problem(block.id, `The column "${column}" is absent from every row.`));
|
|
303
|
+
}
|
|
304
|
+
return ok(column);
|
|
305
|
+
}
|
|
306
|
+
/** True when the column exists in the declared header, or in one row at least. */
|
|
307
|
+
function columnPresent(column, rows, columns) {
|
|
308
|
+
if (columns !== undefined && columns.includes(column))
|
|
309
|
+
return true;
|
|
310
|
+
for (const row of rows) {
|
|
311
|
+
if (Object.prototype.hasOwnProperty.call(row, column))
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
/** Convert one cell to a finite number, or `null` when it is not numeric. */
|
|
317
|
+
function toNumber(cell) {
|
|
318
|
+
if (typeof cell === "number")
|
|
319
|
+
return Number.isFinite(cell) ? cell : null;
|
|
320
|
+
if (typeof cell === "string") {
|
|
321
|
+
const trimmed = cell.trim();
|
|
322
|
+
if (trimmed === "")
|
|
323
|
+
return null;
|
|
324
|
+
const parsed = Number(trimmed);
|
|
325
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
/** True when the cell is a string that does not represent a finite number. */
|
|
330
|
+
function isNonNumericString(cell) {
|
|
331
|
+
return typeof cell === "string" && toNumber(cell) === null;
|
|
332
|
+
}
|
|
333
|
+
/** The distinct values in first-appearance order. */
|
|
334
|
+
function firstAppearance(values) {
|
|
335
|
+
const seen = new Set();
|
|
336
|
+
const order = [];
|
|
337
|
+
for (const value of values) {
|
|
338
|
+
if (!seen.has(value)) {
|
|
339
|
+
seen.add(value);
|
|
340
|
+
order.push(value);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return order;
|
|
344
|
+
}
|
|
345
|
+
/** The numeric cells of one column, in row order. A non-numeric cell drops. */
|
|
346
|
+
function numericColumn(rows, column) {
|
|
347
|
+
const values = [];
|
|
348
|
+
for (const row of rows) {
|
|
349
|
+
const value = toNumber(row[column]);
|
|
350
|
+
if (value !== null)
|
|
351
|
+
values.push(value);
|
|
352
|
+
}
|
|
353
|
+
return values;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* The axis for a line or a scatter column. A column with any non-numeric string cell is a category axis
|
|
357
|
+
* with its distinct values as strings. Any other column is a value axis with `scale: true`.
|
|
358
|
+
*/
|
|
359
|
+
function inferAxis(rows, column) {
|
|
360
|
+
if (rows.some((row) => isNonNumericString(row[column]))) {
|
|
361
|
+
return { type: "category", data: firstAppearance(rows.map((row) => row[column])).map(String), name: column };
|
|
362
|
+
}
|
|
363
|
+
return { type: "value", scale: true, name: column };
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Build one series per group, in first-appearance order of the group values. A chart with no group
|
|
367
|
+
* column gets one series over every row.
|
|
368
|
+
*/
|
|
369
|
+
function groupedSeries(rows, groupCol, make) {
|
|
370
|
+
if (groupCol === undefined) {
|
|
371
|
+
return [make(rows, undefined)];
|
|
372
|
+
}
|
|
373
|
+
const series = [];
|
|
374
|
+
for (const name of firstAppearance(rows.map((row) => row[groupCol]))) {
|
|
375
|
+
series.push(make(rows.filter((row) => row[groupCol] === name), name));
|
|
376
|
+
}
|
|
377
|
+
return series;
|
|
378
|
+
}
|
|
379
|
+
/** The row groups in first-appearance order, or one unnamed group when there is no group column. */
|
|
380
|
+
function splitGroups(rows, groupCol) {
|
|
381
|
+
if (groupCol === undefined) {
|
|
382
|
+
return [{ name: undefined, rows }];
|
|
383
|
+
}
|
|
384
|
+
return firstAppearance(rows.map((row) => row[groupCol])).map((name) => ({
|
|
385
|
+
name,
|
|
386
|
+
rows: rows.filter((row) => row[groupCol] === name),
|
|
387
|
+
}));
|
|
388
|
+
}
|
|
389
|
+
/** Sort `[x, y]` pairs by x. Two numbers compare numerically, and any other pair compares as text. */
|
|
390
|
+
function sortByX(pairs) {
|
|
391
|
+
return [...pairs].sort((a, b) => compareCell(a[0], b[0]));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Compare two cells for a sort by x. Two numbers compare by their difference. Any other pair compares
|
|
395
|
+
* by the code-unit order of the string form. The comparison never calls `localeCompare`, thus the order
|
|
396
|
+
* stays the same on every host.
|
|
397
|
+
*/
|
|
398
|
+
function compareCell(a, b) {
|
|
399
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
400
|
+
return a - b;
|
|
401
|
+
}
|
|
402
|
+
const left = String(a);
|
|
403
|
+
const right = String(b);
|
|
404
|
+
if (left < right)
|
|
405
|
+
return -1;
|
|
406
|
+
if (left > right)
|
|
407
|
+
return 1;
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* A stable key for one `(x, y)` pair. The key carries the type of each cell, thus the number `1` and the
|
|
412
|
+
* string `"1"` are different pairs and a false collision is impossible.
|
|
413
|
+
*/
|
|
414
|
+
function pairKey(xCell, yCell) {
|
|
415
|
+
return `${typeof xCell}:${String(xCell)}${typeof yCell}:${String(yCell)}`;
|
|
416
|
+
}
|
|
417
|
+
// ── The histogram math ──────────────────────────────────────────────────────
|
|
418
|
+
/**
|
|
419
|
+
* The bin edges over the global range. The bin count is the larger of the Sturges count and the
|
|
420
|
+
* Freedman-Diaconis count. One bin applies when the range is zero.
|
|
421
|
+
*/
|
|
422
|
+
function histogramEdges(values) {
|
|
423
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
424
|
+
const min = sorted[0];
|
|
425
|
+
const max = sorted[sorted.length - 1];
|
|
426
|
+
if (max === min) {
|
|
427
|
+
return [min, max];
|
|
428
|
+
}
|
|
429
|
+
const count = autoBinCount(sorted, min, max);
|
|
430
|
+
const width = (max - min) / count;
|
|
431
|
+
const edges = [];
|
|
432
|
+
for (let i = 0; i <= count; i++) {
|
|
433
|
+
edges.push(min + i * width);
|
|
434
|
+
}
|
|
435
|
+
// Pin the last edge to the exact max, thus a float drift cannot drop the top value out of the range.
|
|
436
|
+
edges[count] = max;
|
|
437
|
+
return edges;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* The automatic bin count: `max(Sturges, Freedman-Diaconis)`. Sturges is `ceil(log2(n)) + 1`.
|
|
441
|
+
* Freedman-Diaconis derives the width `2 * IQR / n^(1/3)` and the count `ceil((max - min) / width)`. When
|
|
442
|
+
* the IQR is zero, the Sturges count applies alone.
|
|
443
|
+
*/
|
|
444
|
+
function autoBinCount(sorted, min, max) {
|
|
445
|
+
const n = sorted.length;
|
|
446
|
+
const sturges = Math.ceil(Math.log2(n)) + 1;
|
|
447
|
+
const iqr = quantileType7(sorted, 0.75) - quantileType7(sorted, 0.25);
|
|
448
|
+
if (iqr === 0) {
|
|
449
|
+
return Math.max(1, sturges);
|
|
450
|
+
}
|
|
451
|
+
const width = (2 * iqr) / Math.cbrt(n);
|
|
452
|
+
const fd = Math.ceil((max - min) / width);
|
|
453
|
+
return Math.max(1, sturges, fd);
|
|
454
|
+
}
|
|
455
|
+
/** The bin counts for the values against the edges. The max value falls in the last bin. */
|
|
456
|
+
function binCounts(values, edges) {
|
|
457
|
+
const bins = edges.length - 1;
|
|
458
|
+
const counts = new Array(bins).fill(0);
|
|
459
|
+
const min = edges[0];
|
|
460
|
+
const width = (edges[bins] - min) / bins;
|
|
461
|
+
for (const value of values) {
|
|
462
|
+
let index = width === 0 ? 0 : Math.floor((value - min) / width);
|
|
463
|
+
if (index < 0)
|
|
464
|
+
index = 0;
|
|
465
|
+
if (index >= bins)
|
|
466
|
+
index = bins - 1;
|
|
467
|
+
counts[index] += 1;
|
|
468
|
+
}
|
|
469
|
+
return counts;
|
|
470
|
+
}
|
|
471
|
+
/** One histogram series. Each bar sits at the bin midpoint. A grouped series overlaps its siblings. */
|
|
472
|
+
function histogramSeries(values, edges, name) {
|
|
473
|
+
const counts = binCounts(values, edges);
|
|
474
|
+
const data = [];
|
|
475
|
+
for (let i = 0; i < counts.length; i++) {
|
|
476
|
+
data.push([(edges[i] + edges[i + 1]) / 2, counts[i]]);
|
|
477
|
+
}
|
|
478
|
+
if (name === undefined) {
|
|
479
|
+
return { type: "bar", barWidth: "99%", data };
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
type: "bar",
|
|
483
|
+
name: String(name),
|
|
484
|
+
barWidth: "99%",
|
|
485
|
+
barGap: "-100%",
|
|
486
|
+
itemStyle: { opacity: 0.7 },
|
|
487
|
+
data,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
// ── The box math ────────────────────────────────────────────────────────────
|
|
491
|
+
/**
|
|
492
|
+
* The five-number summary of one category.
|
|
493
|
+
*
|
|
494
|
+
* The quartiles are type-7 quantiles. The whiskers are the Tukey fences at `1.5 * IQR`, clamped to the
|
|
495
|
+
* nearest data value inside each fence. A value outside a fence is an outlier.
|
|
496
|
+
*/
|
|
497
|
+
function boxSummary(values) {
|
|
498
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
499
|
+
const q1 = quantileType7(sorted, 0.25);
|
|
500
|
+
const median = quantileType7(sorted, 0.5);
|
|
501
|
+
const q3 = quantileType7(sorted, 0.75);
|
|
502
|
+
const iqr = q3 - q1;
|
|
503
|
+
const lowerFence = q1 - 1.5 * iqr;
|
|
504
|
+
const upperFence = q3 + 1.5 * iqr;
|
|
505
|
+
let whiskerLow = q1;
|
|
506
|
+
let whiskerHigh = q3;
|
|
507
|
+
let lowSet = false;
|
|
508
|
+
const outliers = [];
|
|
509
|
+
for (const value of sorted) {
|
|
510
|
+
if (value < lowerFence || value > upperFence) {
|
|
511
|
+
outliers.push(value);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
// The values arrive in ascending order. Thus the first in-fence value is the low whisker, and the
|
|
515
|
+
// last in-fence value is the high whisker.
|
|
516
|
+
if (!lowSet) {
|
|
517
|
+
whiskerLow = value;
|
|
518
|
+
lowSet = true;
|
|
519
|
+
}
|
|
520
|
+
whiskerHigh = value;
|
|
521
|
+
}
|
|
522
|
+
return { box: [whiskerLow, q1, median, q3, whiskerHigh], outliers };
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* The type-7 quantile of a sorted array. The index is `p * (n - 1)`, and the value interpolates
|
|
526
|
+
* linearly between the `floor` and the `ceil` of that index.
|
|
527
|
+
*/
|
|
528
|
+
function quantileType7(sorted, p) {
|
|
529
|
+
const n = sorted.length;
|
|
530
|
+
if (n === 1)
|
|
531
|
+
return sorted[0];
|
|
532
|
+
const index = p * (n - 1);
|
|
533
|
+
const low = Math.floor(index);
|
|
534
|
+
const high = Math.ceil(index);
|
|
535
|
+
if (low === high)
|
|
536
|
+
return sorted[low];
|
|
537
|
+
return sorted[low] + (index - low) * (sorted[high] - sorted[low]);
|
|
538
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page constants: the CDN pins, the inline style, the ECharts theme, and the chart bootstrap.
|
|
3
|
+
*
|
|
4
|
+
* These constants are copies. The old template pack at `templates/report-html` holds the source, and the
|
|
5
|
+
* old render path owns that directory. The renderer never reads that directory at run time, thus the
|
|
6
|
+
* output stays a pure function of its inputs. A copy that drifts is a maintenance cost, and it is the
|
|
7
|
+
* price of a page with no local asset and no template engine.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The head references: the Tailwind runtime, the ECharts runtime, the two fonts, and the preconnect. Each
|
|
11
|
+
* tag carries the pinned version and, where the source pins one, the integrity hash. The bytes are exact
|
|
12
|
+
* copies, thus the page stays deterministic and the hashes stay valid.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CDN_HEAD = "<script src=\"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.2/dist/index.global.min.js\" integrity=\"sha384-shaHAtPgz0ulP7R/YmFe0nZtC8FxdhJPi73vwJQcADVttxvHLJBJt/pjzkLSbIvL\" crossorigin=\"anonymous\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js\" integrity=\"sha384-Mx5lkUEQPM1pOJCwFtUICyX45KNojXbkWdYhkKUKsbv391mavbfoAmONbzkgYPzR\" crossorigin=\"anonymous\"></script>\n<link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\" crossorigin>\n<link href=\"https://cdn.jsdelivr.net/npm/@fontsource-variable/space-grotesk@5.1.1/index.css\" rel=\"stylesheet\" integrity=\"sha384-b3Dtcpab/ltw+oDnVHstCCa9ENEb2hKouCSRAfb8o0ROkWOb2uNWYG6+l7HGrHmG\" crossorigin=\"anonymous\">\n<link href=\"https://cdn.jsdelivr.net/npm/@fontsource-variable/ibm-plex-mono@5.1.1/index.css\" rel=\"stylesheet\">";
|
|
15
|
+
/**
|
|
16
|
+
* The style rules of the page. The renderer inlines them in one `<style>` block, thus the page holds the
|
|
17
|
+
* whole design token set and every component rule with no external stylesheet.
|
|
18
|
+
*/
|
|
19
|
+
export declare const PAGE_CSS = "/* \u2500\u2500 Color Palette \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n:root {\n /* Primary Scale */\n --color-primary-50: #f0f1fe; /* Tag bg, CTA section bg */\n --color-primary-100: #dde0fc;\n --color-primary-200: #bcc2f9; /* Badge borders, corner accent default */\n --color-primary-300: #9ba5f5; /* Corner button accent default */\n --color-primary-400: #7987f0; /* Section illustration icons */\n --color-primary-500: #576dea; /* Primary action, section labels, links */\n --color-primary-600: #4458d4; /* Button hover */\n --color-primary-700: #3545b0; /* Tag text on primary-50 bg */\n\n /* Text */\n --color-heading: #0f172a; /* slate-900 \u2014 all headings */\n --color-text: #475569; /* slate-600 \u2014 body text */\n --color-text-secondary: #64748b; /* slate-500 \u2014 captions, metadata */\n --color-text-muted: #94a3b8; /* slate-400 \u2014 timestamps, footnotes */\n\n /* Surface */\n --color-bg: #ffffff; /* Page background (white) */\n --color-bg-alt: #f8fafc; /* Alternating section bg (slate-50) */\n --color-card: #ffffff; /* Cards (white) */\n --color-border: #e2e8f0; /* slate-200 \u2014 card borders, dividers */\n --color-border-hover: #cbd5e1; /* slate-300 \u2014 hover borders */\n\n /* Data Visualization */\n --color-up: #ef4444; /* Red 500 \u2014 upregulated, positive */\n --color-down: #576dea; /* Primary 500 \u2014 downregulated, negative */\n --color-ns: #94a3b8; /* Slate 400 \u2014 not significant */\n\n /* Semantic (Tags \u2014 solid bg + colored text) */\n --color-high-bg: #f0fdf4; /* green-50 */\n --color-high-text: #15803d; /* green-700 */\n --color-medium-bg: #fffbeb; /* amber-50 */\n --color-medium-text:#b45309; /* amber-700 */\n --color-low-bg: #fef2f2; /* red-50 */\n --color-low-text: #b91c1c; /* red-700 */\n\n /* Stat card accent colors */\n --color-stat-primary: #576dea;\n --color-stat-green: #22c55e;\n --color-stat-red: #ef4444;\n --color-stat-purple: #a78bfa;\n --color-stat-amber: #f59e0b;\n}\n\n/* \u2500\u2500 Typography \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\nbody {\n font-family: 'Space Grotesk Variable', system-ui, sans-serif;\n font-size: 16px;\n line-height: 1.6;\n color: var(--color-text);\n}\n\n.font-mono, code, .gene {\n font-family: 'IBM Plex Mono Variable', ui-monospace, monospace;\n}\n\n/* Rule: Headings use font-sans (Space Grotesk), never font-mono.\n Labels, tags, badges, data, product names always use font-mono (IBM Plex Mono). */\n\n/* \u2500\u2500 Corner Accent Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.corner-accents {\n position: relative;\n border: 1px solid var(--color-border);\n background: var(--color-card);\n /* No border-radius \u2014 square corners */\n}\n.corner-accents::before,\n.corner-accents::after {\n content: '';\n position: absolute;\n width: 20px;\n height: 20px;\n transition: all 0.3s ease;\n pointer-events: none;\n}\n.corner-accents::before {\n top: -1px; left: -1px;\n border-top: 2px solid var(--color-primary-200);\n border-left: 2px solid var(--color-primary-200);\n}\n.corner-accents::after {\n bottom: -1px; right: -1px;\n border-bottom: 2px solid var(--color-primary-200);\n border-right: 2px solid var(--color-primary-200);\n}\n.corner-accents:hover::before,\n.corner-accents:hover::after {\n width: 28px;\n height: 28px;\n border-color: var(--color-primary-500);\n}\n.corner-accents:hover {\n box-shadow: 0 4px 12px -4px rgba(15, 23, 42, 0.08);\n}\n\n/* \u2500\u2500 Window Chrome Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Exception: uses rounded-xl (only rounded component) */\n.window-chrome {\n border-radius: 12px;\n border: 1px solid var(--color-border);\n background: var(--color-card);\n box-shadow: 0 8px 30px -8px rgba(15,23,42,0.08), 0 4px 12px -4px rgba(15,23,42,0.04);\n transition: all 0.5s ease;\n}\n.window-chrome:hover {\n transform: translateY(-4px);\n box-shadow: 0 25px 50px -12px rgba(15,23,42,0.18), 0 12px 24px -8px rgba(15,23,42,0.1);\n border-color: var(--color-border-hover);\n}\n.window-chrome .chrome-dots span { transition: background-color 0.3s ease; }\n.window-chrome:hover .chrome-dots .dot-1 { background-color: #fb7185; }\n.window-chrome:hover .chrome-dots .dot-2 { background-color: #fbbf24; }\n.window-chrome:hover .chrome-dots .dot-3 { background-color: #4ade80; }\n\n/* \u2500\u2500 Terminal Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.terminal-card {\n position: relative;\n overflow: hidden;\n}\n.terminal-card .terminal-header {\n background: #1e293b; /* slate-800 */\n}\n.terminal-card .terminal-body-dark {\n background: #0f172a; /* slate-900 */\n color: #e2e8f0;\n}\n.terminal-card::after {\n content: '';\n position: absolute;\n inset: 0;\n pointer-events: none;\n opacity: 0;\n background: repeating-linear-gradient(\n 0deg, transparent, transparent 2px,\n rgba(148, 163, 184, 0.03) 2px, rgba(148, 163, 184, 0.03) 4px\n );\n transition: opacity 0.3s ease;\n}\n.terminal-card:hover::after { opacity: 1; }\n\n/* \u2500\u2500 Block Progress Bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Usage: <span class=\"block-progress\"><span class=\"filled-high\">\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588</span><span class=\"empty\">\u2591\u2591</span> <span class=\"score\">0.82</span></span> */\n.block-progress {\n font-family: 'IBM Plex Mono Variable', ui-monospace, monospace;\n font-size: 14px;\n}\n.block-progress .filled-high { color: var(--color-primary-500); } /* \u2265 0.8 */\n.block-progress .filled-mid { color: #f59e0b; } /* \u2265 0.6 */\n.block-progress .filled-low { color: #f43f5e; } /* < 0.6 */\n.block-progress .empty { color: #e2e8f0; }\n.block-progress .score { font-size: 12px; color: #94a3b8; margin-left: 8px; }\n\n/* \u2500\u2500 Sidebar Navigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* When a sidebar is rendered (#report-sidebar), the body shifts right on lg+\n to make room. Styling matches the corner-accent / mono-label language. */\n@media (min-width: 1024px) {\n body:has(#report-sidebar) { padding-left: 15rem; }\n}\n.sidebar-link {\n border-left: 2px solid transparent;\n}\n.sidebar-link.active {\n color: var(--color-primary-500);\n background-color: var(--color-primary-50);\n border-left-color: var(--color-primary-500);\n font-weight: 600;\n}\n\n/* \u2500\u2500 Insight Box \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.insight-box {\n background: var(--color-card);\n border: 1px solid var(--color-border);\n /* No border-radius \u2014 uses corner-accents class */\n}\n\n/* \u2500\u2500 Section Textures \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n/* Apply to <section>: combine one pattern + noise, e.g. class=\"texture-dots texture-noise\" */\n/* Textures should be felt, not seen \u2014 low opacity, faded edges */\n\n/* \u2500\u2500 Fade-In \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n.fade-in {\n opacity: 0;\n transform: translateY(20px);\n transition: opacity 0.7s ease-out, transform 0.7s ease-out;\n}\n.fade-in-visible {\n opacity: 1;\n transform: translateY(0);\n}\n/* Stagger with data-delay attribute: <div class=\"fade-in\" data-delay=\"100\"> */";
|
|
20
|
+
/**
|
|
21
|
+
* The registered name of the ECharts theme. The skeleton registers the theme under this name, and the
|
|
22
|
+
* bootstrap reads the same name. One source prevents a silent mismatch between the two sites.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ECHARTS_THEME_NAME = "inflexa";
|
|
25
|
+
/**
|
|
26
|
+
* The ECharts theme as a plain object. The renderer serializes this object and registers it, thus the
|
|
27
|
+
* theme never rides a JSON file and no run-time read is necessary. The palette and the axis styles mirror
|
|
28
|
+
* the light design tokens of the page.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ECHARTS_THEME: {
|
|
31
|
+
color: string[];
|
|
32
|
+
backgroundColor: string;
|
|
33
|
+
textStyle: {
|
|
34
|
+
fontFamily: string;
|
|
35
|
+
fontSize: number;
|
|
36
|
+
color: string;
|
|
37
|
+
};
|
|
38
|
+
title: {
|
|
39
|
+
show: boolean;
|
|
40
|
+
};
|
|
41
|
+
legend: {
|
|
42
|
+
bottom: number;
|
|
43
|
+
textStyle: {
|
|
44
|
+
fontSize: number;
|
|
45
|
+
color: string;
|
|
46
|
+
};
|
|
47
|
+
itemGap: number;
|
|
48
|
+
itemWidth: number;
|
|
49
|
+
itemHeight: number;
|
|
50
|
+
};
|
|
51
|
+
grid: {
|
|
52
|
+
left: number;
|
|
53
|
+
right: number;
|
|
54
|
+
top: number;
|
|
55
|
+
bottom: number;
|
|
56
|
+
containLabel: boolean;
|
|
57
|
+
};
|
|
58
|
+
tooltip: {
|
|
59
|
+
backgroundColor: string;
|
|
60
|
+
borderColor: string;
|
|
61
|
+
textStyle: {
|
|
62
|
+
color: string;
|
|
63
|
+
fontSize: number;
|
|
64
|
+
};
|
|
65
|
+
extraCssText: string;
|
|
66
|
+
};
|
|
67
|
+
toolbox: {
|
|
68
|
+
feature: {
|
|
69
|
+
saveAsImage: {
|
|
70
|
+
title: string;
|
|
71
|
+
pixelRatio: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
iconStyle: {
|
|
75
|
+
borderColor: string;
|
|
76
|
+
};
|
|
77
|
+
right: number;
|
|
78
|
+
top: number;
|
|
79
|
+
};
|
|
80
|
+
xAxis: {
|
|
81
|
+
axisLine: {
|
|
82
|
+
lineStyle: {
|
|
83
|
+
color: string;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
axisTick: {
|
|
87
|
+
lineStyle: {
|
|
88
|
+
color: string;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
axisLabel: {
|
|
92
|
+
color: string;
|
|
93
|
+
fontSize: number;
|
|
94
|
+
};
|
|
95
|
+
splitLine: {
|
|
96
|
+
lineStyle: {
|
|
97
|
+
color: string;
|
|
98
|
+
type: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
yAxis: {
|
|
103
|
+
axisLine: {
|
|
104
|
+
show: boolean;
|
|
105
|
+
};
|
|
106
|
+
axisTick: {
|
|
107
|
+
show: boolean;
|
|
108
|
+
};
|
|
109
|
+
axisLabel: {
|
|
110
|
+
color: string;
|
|
111
|
+
fontSize: number;
|
|
112
|
+
};
|
|
113
|
+
splitLine: {
|
|
114
|
+
lineStyle: {
|
|
115
|
+
color: string;
|
|
116
|
+
type: string;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* The page-side script that wires each chart. It finds every chart container, reads the option JSON from
|
|
123
|
+
* the sibling `<script type="application/json">` element, and initializes ECharts with the registered
|
|
124
|
+
* theme. The skeleton registers the theme before this script runs. A resize handler keeps each chart
|
|
125
|
+
* fit to the window.
|
|
126
|
+
*/
|
|
127
|
+
export declare const CHART_BOOTSTRAP: string;
|