@inflexa-ai/harness 0.20.0 → 0.21.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/conversation-agent.d.ts +26 -14
- package/dist/agents/conversation-agent.js +12 -23
- package/dist/agents/report-session-agent.d.ts +31 -5
- package/dist/agents/report-session-agent.js +31 -6
- package/dist/app/chat-turn.js +3 -1
- package/dist/app/message-assembly.d.ts +15 -1
- package/dist/app/message-assembly.js +22 -8
- package/dist/app/report-session-runtime.d.ts +21 -4
- package/dist/app/report-session-runtime.js +53 -6
- package/dist/app/spawn-report-session.d.ts +174 -17
- package/dist/app/spawn-report-session.js +233 -21
- package/dist/contracts/chat-events.d.ts +7 -2
- package/dist/contracts/message.d.ts +10 -0
- package/dist/contracts/report-blocks.d.ts +743 -19
- package/dist/contracts/report-blocks.js +186 -11
- package/dist/contracts/report-reference.d.ts +88 -0
- package/dist/contracts/report-reference.js +48 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +20 -0
- package/dist/lib/chrome.d.ts +26 -6
- package/dist/lib/chrome.js +99 -32
- package/dist/lib/eyes.d.ts +57 -0
- package/dist/lib/eyes.js +45 -0
- package/dist/lib/page-capture.d.ts +14 -0
- package/dist/lib/page-capture.js +56 -2
- package/dist/loop/run-agent.js +58 -23
- package/dist/loop/tool-detail.d.ts +19 -2
- package/dist/loop/tool-detail.js +35 -2
- package/dist/memory/content-to-cortex.d.ts +3 -2
- package/dist/memory/content-to-cortex.js +21 -12
- package/dist/memory/conversation-display-replay.js +14 -7
- package/dist/memory/thread-history.d.ts +60 -3
- package/dist/memory/thread-history.js +55 -16
- package/dist/memory/thread-store.d.ts +11 -1
- package/dist/memory/thread-store.js +12 -2
- package/dist/prompts/conversation.d.ts +1 -1
- package/dist/prompts/conversation.js +23 -6
- package/dist/prompts/report-session.d.ts +1 -1
- package/dist/prompts/report-session.js +183 -9
- package/dist/report-model/authoring-grammar.d.ts +463 -0
- package/dist/report-model/authoring-grammar.js +77 -0
- package/dist/report-model/block-walk.d.ts +57 -14
- package/dist/report-model/block-walk.js +116 -11
- package/dist/report-model/draft-finish.d.ts +22 -4
- package/dist/report-model/draft-finish.js +39 -7
- package/dist/report-model/draft-operations.d.ts +3 -0
- package/dist/report-model/draft-operations.js +19 -5
- package/dist/report-model/draft-read.d.ts +8 -5
- package/dist/report-model/draft-read.js +21 -5
- package/dist/report-model/fixture-resolver.js +15 -4
- package/dist/report-model/pin-snapshot.d.ts +24 -6
- package/dist/report-model/pin-snapshot.js +204 -6
- package/dist/report-model/production-resolver.js +15 -4
- package/dist/report-model/reference-resolver.d.ts +50 -1
- package/dist/report-model/reference-resolver.js +94 -1
- package/dist/report-model/reference-stamp.d.ts +45 -0
- package/dist/report-model/reference-stamp.js +173 -0
- package/dist/report-model/structural-validation.d.ts +4 -1
- package/dist/report-model/structural-validation.js +48 -3
- package/dist/report-model/validate.d.ts +1 -1
- package/dist/report-model/validate.js +1 -1
- package/dist/report-render/asset-lookup.d.ts +19 -0
- package/dist/report-render/asset-lookup.js +37 -0
- package/dist/report-render/assets.d.ts +45 -1
- package/dist/report-render/assets.js +65 -6
- package/dist/report-render/chart-presets.d.ts +102 -0
- package/dist/report-render/chart-presets.js +205 -0
- package/dist/report-render/chart.d.ts +112 -10
- package/dist/report-render/chart.js +1164 -41
- package/dist/report-render/design.d.ts +99 -0
- package/dist/report-render/design.js +289 -107
- package/dist/report-render/number-format.d.ts +117 -0
- package/dist/report-render/number-format.js +479 -0
- package/dist/report-render/page.d.ts +121 -5
- package/dist/report-render/page.js +689 -7
- package/dist/report-render/references.d.ts +70 -16
- package/dist/report-render/references.js +54 -22
- package/dist/report-render/render.d.ts +23 -5
- package/dist/report-render/render.js +81 -22
- package/dist/report-render/table-data.d.ts +92 -0
- package/dist/report-render/table-data.js +129 -0
- package/dist/report-render/types.d.ts +15 -0
- package/dist/report-render/value-bridge.js +6 -2
- package/dist/report-render/views/chart-view.d.ts +12 -5
- package/dist/report-render/views/chart-view.js +14 -8
- package/dist/report-render/views/page-view.d.ts +36 -15
- package/dist/report-render/views/page-view.js +45 -23
- package/dist/report-render/views/prose.d.ts +12 -2
- package/dist/report-render/views/prose.js +29 -7
- package/dist/report-render/views/references-view.d.ts +22 -8
- package/dist/report-render/views/references-view.js +101 -13
- package/dist/report-render/views/values.d.ts +128 -13
- package/dist/report-render/views/values.js +199 -18
- package/dist/runtime/assemble.d.ts +39 -5
- package/dist/runtime/assemble.js +47 -6
- package/dist/sandbox/client.d.ts +10 -1
- package/dist/sandbox/create-sandbox.d.ts +9 -4
- package/dist/sandbox/create-sandbox.js +22 -16
- package/dist/sandbox/docker-client.js +10 -7
- package/dist/sandbox/mount-plan.d.ts +45 -7
- package/dist/sandbox/mount-plan.js +43 -5
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/state/init.js +38 -9
- package/dist/state/report-session-state.d.ts +42 -1
- package/dist/state/report-session-state.js +70 -2
- package/dist/state/report-versions.d.ts +22 -17
- package/dist/state/report-versions.js +48 -35
- package/dist/state/snapshot-parse.js +16 -1
- package/dist/tools/define-tool.d.ts +32 -0
- package/dist/tools/define-tool.js +4 -0
- package/dist/tools/detail-resolver.d.ts +7 -2
- package/dist/tools/detail-resolver.js +7 -2
- package/dist/tools/iterate-report.d.ts +1 -1
- package/dist/tools/report/preview-snapshot.js +1 -0
- package/dist/tools/report-authoring/authoring-tools.d.ts +15 -6
- package/dist/tools/report-authoring/authoring-tools.js +99 -18
- package/dist/tools/report-session/derive-table.d.ts +162 -0
- package/dist/tools/report-session/derive-table.js +459 -0
- package/dist/tools/report-session/examine-page.d.ts +48 -18
- package/dist/tools/report-session/examine-page.js +204 -24
- package/dist/tools/report-session/index.d.ts +2 -0
- package/dist/tools/report-session/index.js +2 -0
- package/dist/tools/report-session/list-artifacts.d.ts +97 -0
- package/dist/tools/report-session/list-artifacts.js +197 -0
- package/dist/tools/report-session/preview-report.d.ts +6 -0
- package/dist/tools/report-session/preview-report.js +180 -28
- package/dist/tools/report-session/record-version.d.ts +22 -10
- package/dist/tools/report-session/record-version.js +99 -14
- package/dist/tools/start-report-session.d.ts +115 -0
- package/dist/tools/start-report-session.js +179 -0
- package/dist/workspace/paths.d.ts +50 -0
- package/dist/workspace/paths.js +63 -0
- package/package.json +2 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The number format of a resolved value.
|
|
3
|
+
*
|
|
4
|
+
* The renderer shows a number in one of five kinds. `scientific` gives a coefficient of two significant
|
|
5
|
+
* digits and an exponent, for example `4.3e-5`. `compact` gives an integer with comma grouping, for example
|
|
6
|
+
* `14,201`. `compact-scientific` gives three significant digits, for example `−3.09`. It gives a grouped
|
|
7
|
+
* whole number for a magnitude from `1e3` to `1e15`, for example `15,235`, and it falls to the scientific
|
|
8
|
+
* form under one thousandth and from `1e15` up. `identifier` gives the source text. `below-resolution`
|
|
9
|
+
* gives a bound over a stored zero, for example `<4e-4`, or `<0.02` where the column is coarse.
|
|
10
|
+
*
|
|
11
|
+
* The module reads no locale. `toLocaleString` gives different text on a different host, thus the render
|
|
12
|
+
* function would stop being a pure function of its inputs. The comma grouping is written here for that
|
|
13
|
+
* reason.
|
|
14
|
+
*
|
|
15
|
+
* A shown form hides digits when it no longer parses back to the value, or when it no longer matches the
|
|
16
|
+
* source text of a string cell. The caller puts the full digits in the `title` attribute at that time, and
|
|
17
|
+
* it emits no attribute at any other time.
|
|
18
|
+
*
|
|
19
|
+
* The column meaning that a binding declares replaces the name of the column in the kind decision. The
|
|
20
|
+
* magnitude of the cell decides after it, the same for a declared column and for an undeclared one.
|
|
21
|
+
*
|
|
22
|
+
* A table resolves one kind for each whole column with `selectColumnKind`, and the data asset ships that
|
|
23
|
+
* kind. `formatTableCell` then reads the cell under it. The page script holds a twin of that one function,
|
|
24
|
+
* and a shared test vector pins the two together.
|
|
25
|
+
*
|
|
26
|
+
* `holdsADriftedExponent` reads one written form against the same rules. A prose sentence can state an
|
|
27
|
+
* exponent in a notation that no shown form gives. Such a sentence drifts from the page, and the advisory
|
|
28
|
+
* scan of a draft reports it.
|
|
29
|
+
*/
|
|
30
|
+
import type { ColumnMeaning } from "../contracts/report-reference.js";
|
|
31
|
+
/** The five number kinds that the renderer shows. */
|
|
32
|
+
export type NumberKind = "scientific" | "compact" | "compact-scientific" | "identifier" | "below-resolution";
|
|
33
|
+
/** One formatted cell. `full` holds the full digits, and it is present only when `text` hides one. */
|
|
34
|
+
export interface FormattedNumber {
|
|
35
|
+
readonly text: string;
|
|
36
|
+
readonly full?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format one cell in one kind.
|
|
40
|
+
*
|
|
41
|
+
* A cell that holds no finite number passes through as its own text, and it carries no full form. A finite
|
|
42
|
+
* number formats in the kind, and it carries the full digits only when the shown text loses one.
|
|
43
|
+
*
|
|
44
|
+
* `bound` is the smallest positive value of the column that the cell sits in. It answers the
|
|
45
|
+
* below-resolution kind alone, and the caller reads the column one time to get it. Thus this function stays
|
|
46
|
+
* a pure function of its arguments, and it never reads a column.
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatNumberCell(cell: string | number, kind: NumberKind, bound?: number): FormattedNumber;
|
|
49
|
+
/**
|
|
50
|
+
* Select the number kind for one cell of one column.
|
|
51
|
+
*
|
|
52
|
+
* A declaration replaces the name of the column, and the magnitude decides after it exactly as it does for
|
|
53
|
+
* a name match. Thus a declared p-value column gives the same bytes as a p-value column that the tokens
|
|
54
|
+
* match: `3.8e-7` takes the scientific form, and `0.536` stays `0.536`.
|
|
55
|
+
*
|
|
56
|
+
* An identifier column selects the identifier kind. A zero of a p-value column selects the below-resolution
|
|
57
|
+
* kind. A p-value column selects the scientific kind for a value between zero and one hundredth. A safe
|
|
58
|
+
* integer selects the compact kind. Every other finite number selects the compact-scientific kind. A cell
|
|
59
|
+
* that holds no finite number selects the compact-scientific kind too, because the format passes such a
|
|
60
|
+
* cell through unchanged.
|
|
61
|
+
*/
|
|
62
|
+
export declare function selectNumberKind(column: string, cell: string | number, meaning?: ColumnMeaning): NumberKind;
|
|
63
|
+
/**
|
|
64
|
+
* The number kind of one whole column.
|
|
65
|
+
*
|
|
66
|
+
* A column carries one kind, and the cell decides the arms under it. `identifier` names a column of names.
|
|
67
|
+
* `scientific` names a column of probabilities, where a small value reads as an exponent and a stored zero
|
|
68
|
+
* reads as a bound. `compact-scientific` names every other column, where a whole number groups and a float
|
|
69
|
+
* rounds.
|
|
70
|
+
*
|
|
71
|
+
* The renderer ships this kind with the table data, and the page formats each cell under it. Thus the
|
|
72
|
+
* declaration and the token guess stay here, on the server, where a test reads them.
|
|
73
|
+
*/
|
|
74
|
+
export declare function selectColumnKind(column: string, meaning?: ColumnMeaning): NumberKind;
|
|
75
|
+
/**
|
|
76
|
+
* The shown text of one table cell, under the kind of its column.
|
|
77
|
+
*
|
|
78
|
+
* The page script holds a twin of this function, because the rows reach the reader through the data asset
|
|
79
|
+
* and the page formats them there. A shared test vector runs both over the same entries, thus the two
|
|
80
|
+
* cannot give different text in silence.
|
|
81
|
+
*
|
|
82
|
+
* `bound` is the smallest positive value of the column. A stored zero of a probability column reads as a
|
|
83
|
+
* bound under it, thus the page never prints a probability of zero.
|
|
84
|
+
*
|
|
85
|
+
* A delimited name shows its first segment. Such a name holds no finite number, thus the number format
|
|
86
|
+
* passes it through and this trim reads the text that the format gave.
|
|
87
|
+
*/
|
|
88
|
+
export declare function formatTableCell(cell: string | number, kind: NumberKind, bound?: number): string;
|
|
89
|
+
/**
|
|
90
|
+
* True when one cell of a column parses as a finite number.
|
|
91
|
+
*
|
|
92
|
+
* The filter of a column reads this. One value that parses keeps the column numeric, thus a sentinel such
|
|
93
|
+
* as `NA` beside the numbers cannot drop the column to the text filter. A column where no cell parses holds
|
|
94
|
+
* names, and a name filters as text.
|
|
95
|
+
*/
|
|
96
|
+
export declare function holdsANumber(cells: readonly (string | number | undefined)[]): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* The smallest positive value of one column, or `undefined` when the column holds none.
|
|
99
|
+
*
|
|
100
|
+
* The value bounds a stored zero of the same column from above. A cell that holds no finite number, and a
|
|
101
|
+
* cell that is not positive, both give no bound.
|
|
102
|
+
*/
|
|
103
|
+
export declare function smallestPositiveValue(cells: readonly (string | number | undefined)[]): number | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* True when one written form states an exponent that no shown form of this module gives.
|
|
106
|
+
*
|
|
107
|
+
* A shown exponent comes out of `tidy` in every kind that prints one, thus it carries no plus sign and no
|
|
108
|
+
* leading zero. A written form such as `4.3e-05` states the same value in a notation that the page never
|
|
109
|
+
* prints. Thus the prose and the page read differently for one number.
|
|
110
|
+
*
|
|
111
|
+
* The test reads the shown form of the same value and compares the two exponents. Thus it follows the
|
|
112
|
+
* printer, and it repeats none of the rules of the printer. The mantissa stays out of the comparison,
|
|
113
|
+
* because the digits of a written value are a matter of rounding and not of notation.
|
|
114
|
+
*
|
|
115
|
+
* A text that carries no exponent, and a text that holds no finite number, both give false.
|
|
116
|
+
*/
|
|
117
|
+
export declare function holdsADriftedExponent(text: string): boolean;
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The number format of a resolved value.
|
|
3
|
+
*
|
|
4
|
+
* The renderer shows a number in one of five kinds. `scientific` gives a coefficient of two significant
|
|
5
|
+
* digits and an exponent, for example `4.3e-5`. `compact` gives an integer with comma grouping, for example
|
|
6
|
+
* `14,201`. `compact-scientific` gives three significant digits, for example `−3.09`. It gives a grouped
|
|
7
|
+
* whole number for a magnitude from `1e3` to `1e15`, for example `15,235`, and it falls to the scientific
|
|
8
|
+
* form under one thousandth and from `1e15` up. `identifier` gives the source text. `below-resolution`
|
|
9
|
+
* gives a bound over a stored zero, for example `<4e-4`, or `<0.02` where the column is coarse.
|
|
10
|
+
*
|
|
11
|
+
* The module reads no locale. `toLocaleString` gives different text on a different host, thus the render
|
|
12
|
+
* function would stop being a pure function of its inputs. The comma grouping is written here for that
|
|
13
|
+
* reason.
|
|
14
|
+
*
|
|
15
|
+
* A shown form hides digits when it no longer parses back to the value, or when it no longer matches the
|
|
16
|
+
* source text of a string cell. The caller puts the full digits in the `title` attribute at that time, and
|
|
17
|
+
* it emits no attribute at any other time.
|
|
18
|
+
*
|
|
19
|
+
* The column meaning that a binding declares replaces the name of the column in the kind decision. The
|
|
20
|
+
* magnitude of the cell decides after it, the same for a declared column and for an undeclared one.
|
|
21
|
+
*
|
|
22
|
+
* A table resolves one kind for each whole column with `selectColumnKind`, and the data asset ships that
|
|
23
|
+
* kind. `formatTableCell` then reads the cell under it. The page script holds a twin of that one function,
|
|
24
|
+
* and a shared test vector pins the two together.
|
|
25
|
+
*
|
|
26
|
+
* `holdsADriftedExponent` reads one written form against the same rules. A prose sentence can state an
|
|
27
|
+
* exponent in a notation that no shown form gives. Such a sentence drifts from the page, and the advisory
|
|
28
|
+
* scan of a draft reports it.
|
|
29
|
+
*/
|
|
30
|
+
/** The significant digits of the scientific coefficient, for example the two digits of `4.3e-5`. */
|
|
31
|
+
const SCIENTIFIC_DIGITS = 2;
|
|
32
|
+
/** The significant digits of the compact-scientific form, for example the three digits of `-3.09`. */
|
|
33
|
+
const COMPACT_DIGITS = 3;
|
|
34
|
+
/** The magnitude under which a p-value column selects the scientific kind. */
|
|
35
|
+
const SCIENTIFIC_FLOOR = 1e-2;
|
|
36
|
+
/**
|
|
37
|
+
* The exponent from which the bound of a stored zero reads as a plain decimal. It is the exponent of
|
|
38
|
+
* `SCIENTIFIC_FLOOR`, thus a bound and the values around it read in one notation.
|
|
39
|
+
*/
|
|
40
|
+
const PLAIN_BOUND_EXPONENT = -2;
|
|
41
|
+
/** The magnitude under which the compact-scientific form falls to the scientific form. */
|
|
42
|
+
const COMPACT_SCIENTIFIC_FLOOR = 1e-3;
|
|
43
|
+
/** The rounded magnitude from which the compact-scientific form gives a grouped whole number. */
|
|
44
|
+
const GROUPED_FLOOR = 1e3;
|
|
45
|
+
/** The rounded magnitude from which the grouped whole number gives way to the scientific form. */
|
|
46
|
+
const GROUPED_CEILING = 1e15;
|
|
47
|
+
/** The size of one grouped digit run, for example the three digits of `14,201`. */
|
|
48
|
+
const GROUP_SIZE = 3;
|
|
49
|
+
/**
|
|
50
|
+
* The whole tokens of a column name that name a p-value. Such a value reads better in the scientific form.
|
|
51
|
+
*
|
|
52
|
+
* The tokens are the fallback for a column that declares no meaning. A declaration states what the column
|
|
53
|
+
* is, and a name only suggests it. Thus a token match answers where the author declared nothing.
|
|
54
|
+
*/
|
|
55
|
+
const P_VALUE_TOKENS = new Set(["p", "pval", "pvalue", "padj", "fdr", "q", "qval"]);
|
|
56
|
+
/** The whole tokens of a column name that name an identifier. Such a value is a name, not a magnitude. */
|
|
57
|
+
const IDENTIFIER_TOKENS = new Set(["id", "pmid", "doi", "entrez", "taxid", "year", "accession"]);
|
|
58
|
+
/** The boundary between a lowercase or numeric character and an uppercase character. */
|
|
59
|
+
const CASE_BOUNDARY = /([a-z0-9])([A-Z])/g;
|
|
60
|
+
/** The separators of a column name: whitespace, an underscore, a point, and a hyphen. */
|
|
61
|
+
const SEPARATORS = /[\s_.-]+/;
|
|
62
|
+
/** A text that holds decimal digits alone. */
|
|
63
|
+
const DIGIT_RUN = /^[0-9]+$/;
|
|
64
|
+
/** Each group comma of a compact form. */
|
|
65
|
+
const GROUP_COMMAS = /,/g;
|
|
66
|
+
/** The exponent of one written or shown form, for example the `-05` of `4.3e-05`. */
|
|
67
|
+
const EXPONENT_PART = /[eE]([+-]?\d+)$/;
|
|
68
|
+
/**
|
|
69
|
+
* The form of a stored zero that no positive neighbor bounds. It claims nearness, and it claims no bound,
|
|
70
|
+
* because the column gives nothing better.
|
|
71
|
+
*/
|
|
72
|
+
const NEAR_ZERO_FORM = "≈0";
|
|
73
|
+
/**
|
|
74
|
+
* The minus of a shown form. It is the character that a preset axis title carries, thus a card, a cell, and
|
|
75
|
+
* an axis of one page read one glyph.
|
|
76
|
+
*/
|
|
77
|
+
const TYPOGRAPHIC_MINUS = "−";
|
|
78
|
+
/** The delimiter of an encoded set name, for example `HALLMARK_HYPOXIA%MSigDB%M5891`. */
|
|
79
|
+
const NAME_DELIMITER = "%";
|
|
80
|
+
/** The count of segments that an encoded set name holds: the name, the collection, and the accession. */
|
|
81
|
+
const NAME_SEGMENTS = 3;
|
|
82
|
+
/** One whitespace character. A segment of an encoded name holds none. */
|
|
83
|
+
const WHITESPACE = /\s/;
|
|
84
|
+
/**
|
|
85
|
+
* Format one cell in one kind.
|
|
86
|
+
*
|
|
87
|
+
* A cell that holds no finite number passes through as its own text, and it carries no full form. A finite
|
|
88
|
+
* number formats in the kind, and it carries the full digits only when the shown text loses one.
|
|
89
|
+
*
|
|
90
|
+
* `bound` is the smallest positive value of the column that the cell sits in. It answers the
|
|
91
|
+
* below-resolution kind alone, and the caller reads the column one time to get it. Thus this function stays
|
|
92
|
+
* a pure function of its arguments, and it never reads a column.
|
|
93
|
+
*/
|
|
94
|
+
export function formatNumberCell(cell, kind, bound) {
|
|
95
|
+
if (kind === "identifier") {
|
|
96
|
+
// An identifier is a name that is written with digits. A group comma and a rounded digit both break
|
|
97
|
+
// the name, thus each character of the source reaches the page.
|
|
98
|
+
return { text: String(cell).trim() };
|
|
99
|
+
}
|
|
100
|
+
if (kind === "below-resolution") {
|
|
101
|
+
// The shown text carries no digit of the cell, thus the raw cell rides the full form in both arms.
|
|
102
|
+
const text = bound !== undefined && bound > 0 ? `<${boundForm(bound)}` : NEAR_ZERO_FORM;
|
|
103
|
+
return { text, full: String(cell).trim() };
|
|
104
|
+
}
|
|
105
|
+
const value = finiteValue(cell);
|
|
106
|
+
if (value === null) {
|
|
107
|
+
return { text: String(cell) };
|
|
108
|
+
}
|
|
109
|
+
const text = shownMinus(formatValue(value, kind));
|
|
110
|
+
if (typeof cell === "string") {
|
|
111
|
+
// A string cell carries its own digits already, thus the source text is the truer full form. A
|
|
112
|
+
// comparison of the two texts also catches a loss that a comparison of two numbers cannot see, for
|
|
113
|
+
// example the leading zero of `007` and the trailing zero of `1.50`.
|
|
114
|
+
const source = cell.trim();
|
|
115
|
+
return ungrouped(text) === source ? { text } : { text, full: source };
|
|
116
|
+
}
|
|
117
|
+
return hidesDigits(text, value) ? { text, full: String(cell) } : { text };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Select the number kind for one cell of one column.
|
|
121
|
+
*
|
|
122
|
+
* A declaration replaces the name of the column, and the magnitude decides after it exactly as it does for
|
|
123
|
+
* a name match. Thus a declared p-value column gives the same bytes as a p-value column that the tokens
|
|
124
|
+
* match: `3.8e-7` takes the scientific form, and `0.536` stays `0.536`.
|
|
125
|
+
*
|
|
126
|
+
* An identifier column selects the identifier kind. A zero of a p-value column selects the below-resolution
|
|
127
|
+
* kind. A p-value column selects the scientific kind for a value between zero and one hundredth. A safe
|
|
128
|
+
* integer selects the compact kind. Every other finite number selects the compact-scientific kind. A cell
|
|
129
|
+
* that holds no finite number selects the compact-scientific kind too, because the format passes such a
|
|
130
|
+
* cell through unchanged.
|
|
131
|
+
*/
|
|
132
|
+
export function selectNumberKind(column, cell, meaning) {
|
|
133
|
+
if (holdsAName(column, meaning)) {
|
|
134
|
+
return "identifier";
|
|
135
|
+
}
|
|
136
|
+
const value = finiteValue(cell);
|
|
137
|
+
if (value === null) {
|
|
138
|
+
return "compact-scientific";
|
|
139
|
+
}
|
|
140
|
+
// A stored zero in a p-value column claims no zero probability. It states that the estimator bottomed
|
|
141
|
+
// out, for example a permutation count that bounds the smallest value that it can report. Thus the page
|
|
142
|
+
// shows a bound, and a bare `0` would read as a result.
|
|
143
|
+
if (value === 0 && holdsAPValue(column, meaning)) {
|
|
144
|
+
return "below-resolution";
|
|
145
|
+
}
|
|
146
|
+
// From one hundredth up, the plain decimal is as short as the exponent and it is easier to read. Thus
|
|
147
|
+
// `0.05` stays `0.05` and it does not become `5e-2`.
|
|
148
|
+
if (value > 0 && value < SCIENTIFIC_FLOOR && holdsAPValue(column, meaning)) {
|
|
149
|
+
return "scientific";
|
|
150
|
+
}
|
|
151
|
+
// An integer above the safe range is no longer exact, thus it reads as a general float and not as a count.
|
|
152
|
+
if (Number.isSafeInteger(value)) {
|
|
153
|
+
return "compact";
|
|
154
|
+
}
|
|
155
|
+
return "compact-scientific";
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The number kind of one whole column.
|
|
159
|
+
*
|
|
160
|
+
* A column carries one kind, and the cell decides the arms under it. `identifier` names a column of names.
|
|
161
|
+
* `scientific` names a column of probabilities, where a small value reads as an exponent and a stored zero
|
|
162
|
+
* reads as a bound. `compact-scientific` names every other column, where a whole number groups and a float
|
|
163
|
+
* rounds.
|
|
164
|
+
*
|
|
165
|
+
* The renderer ships this kind with the table data, and the page formats each cell under it. Thus the
|
|
166
|
+
* declaration and the token guess stay here, on the server, where a test reads them.
|
|
167
|
+
*/
|
|
168
|
+
export function selectColumnKind(column, meaning) {
|
|
169
|
+
if (holdsAName(column, meaning)) {
|
|
170
|
+
return "identifier";
|
|
171
|
+
}
|
|
172
|
+
return holdsAPValue(column, meaning) ? "scientific" : "compact-scientific";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The shown text of one table cell, under the kind of its column.
|
|
176
|
+
*
|
|
177
|
+
* The page script holds a twin of this function, because the rows reach the reader through the data asset
|
|
178
|
+
* and the page formats them there. A shared test vector runs both over the same entries, thus the two
|
|
179
|
+
* cannot give different text in silence.
|
|
180
|
+
*
|
|
181
|
+
* `bound` is the smallest positive value of the column. A stored zero of a probability column reads as a
|
|
182
|
+
* bound under it, thus the page never prints a probability of zero.
|
|
183
|
+
*
|
|
184
|
+
* A delimited name shows its first segment. Such a name holds no finite number, thus the number format
|
|
185
|
+
* passes it through and this trim reads the text that the format gave.
|
|
186
|
+
*/
|
|
187
|
+
export function formatTableCell(cell, kind, bound) {
|
|
188
|
+
const shown = formatNumberCell(cell, cellKind(cell, kind), bound).text;
|
|
189
|
+
return firstNameSegment(shown) ?? shown;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The kind of one cell under the kind of its column.
|
|
193
|
+
*
|
|
194
|
+
* A column of names gives one kind to each of its cells. A column of magnitudes reads the cell: a zero of a
|
|
195
|
+
* probability column takes the bound, a small probability takes the exponent, a safe integer takes the
|
|
196
|
+
* grouped form, and every other value takes the rounded form. A cell that holds no finite number takes the
|
|
197
|
+
* rounded form too, because the format passes such a cell through unchanged.
|
|
198
|
+
*/
|
|
199
|
+
function cellKind(cell, column) {
|
|
200
|
+
if (column === "identifier") {
|
|
201
|
+
return "identifier";
|
|
202
|
+
}
|
|
203
|
+
const value = finiteValue(cell);
|
|
204
|
+
if (value === null) {
|
|
205
|
+
return "compact-scientific";
|
|
206
|
+
}
|
|
207
|
+
if (column === "scientific") {
|
|
208
|
+
// A stored zero in a probability column claims no zero probability. It states that the estimator
|
|
209
|
+
// bottomed out, thus the page shows a bound and a bare `0` would read as a result.
|
|
210
|
+
if (value === 0) {
|
|
211
|
+
return "below-resolution";
|
|
212
|
+
}
|
|
213
|
+
// From one hundredth up, the plain decimal is as short as the exponent and it is easier to read.
|
|
214
|
+
if (value > 0 && value < SCIENTIFIC_FLOOR) {
|
|
215
|
+
return "scientific";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// An integer above the safe range is no longer exact, thus it reads as a general float and not as a count.
|
|
219
|
+
return Number.isSafeInteger(value) ? "compact" : "compact-scientific";
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The first segment of a delimited name, or `undefined` when the text carries no delimited name.
|
|
223
|
+
*
|
|
224
|
+
* An enrichment tool joins the name of a set, its collection, and its accession with a percent sign. The
|
|
225
|
+
* name alone identifies the row, thus the rest is noise inside a narrow column.
|
|
226
|
+
*
|
|
227
|
+
* Such a name holds three or more segments, each segment holds a character, and no segment holds a
|
|
228
|
+
* whitespace character. Every other text keeps its whole form. Thus `95%` stays whole, and a sentence such
|
|
229
|
+
* as `up 20% vs control` stays whole.
|
|
230
|
+
*/
|
|
231
|
+
function firstNameSegment(text) {
|
|
232
|
+
const segments = text.split(NAME_DELIMITER);
|
|
233
|
+
if (segments.length < NAME_SEGMENTS) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const encoded = segments.every((segment) => segment.length > 0 && !WHITESPACE.test(segment));
|
|
237
|
+
return encoded ? segments[0] : undefined;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* True when the column holds a name and not a magnitude.
|
|
241
|
+
*
|
|
242
|
+
* A declared `identifier` and a declared `category` both hold one. The identifier kind is the one kind that
|
|
243
|
+
* gives the source text unchanged, thus a category that reads as a number keeps its own text, for example
|
|
244
|
+
* the cluster `01`. A column that declares no meaning falls to the name tokens.
|
|
245
|
+
*/
|
|
246
|
+
function holdsAName(column, meaning) {
|
|
247
|
+
if (meaning !== undefined) {
|
|
248
|
+
return meaning === "identifier" || meaning === "category";
|
|
249
|
+
}
|
|
250
|
+
return isIdentifierColumn(column);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* True when the column holds a probability. A column that declares no meaning falls to the name tokens.
|
|
254
|
+
*
|
|
255
|
+
* A declared `effect` and a declared `count` both hold a magnitude, thus each one takes the magnitude arms
|
|
256
|
+
* below this test. An effect is a float, and it reads there in the compact-scientific kind. A count is a
|
|
257
|
+
* whole number, and it reads there in the compact kind.
|
|
258
|
+
*
|
|
259
|
+
* The kind of a cell and the kind of a whole column both read this, thus a probability column takes the
|
|
260
|
+
* scientific kind and the zeros of that column take a bound.
|
|
261
|
+
*/
|
|
262
|
+
function holdsAPValue(column, meaning) {
|
|
263
|
+
return meaning !== undefined ? meaning === "p-value" : isPValueColumn(column);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* True when one cell of a column parses as a finite number.
|
|
267
|
+
*
|
|
268
|
+
* The filter of a column reads this. One value that parses keeps the column numeric, thus a sentinel such
|
|
269
|
+
* as `NA` beside the numbers cannot drop the column to the text filter. A column where no cell parses holds
|
|
270
|
+
* names, and a name filters as text.
|
|
271
|
+
*/
|
|
272
|
+
export function holdsANumber(cells) {
|
|
273
|
+
for (const cell of cells) {
|
|
274
|
+
if (cell !== undefined && finiteValue(cell) !== null) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* The smallest positive value of one column, or `undefined` when the column holds none.
|
|
282
|
+
*
|
|
283
|
+
* The value bounds a stored zero of the same column from above. A cell that holds no finite number, and a
|
|
284
|
+
* cell that is not positive, both give no bound.
|
|
285
|
+
*/
|
|
286
|
+
export function smallestPositiveValue(cells) {
|
|
287
|
+
let smallest;
|
|
288
|
+
for (const cell of cells) {
|
|
289
|
+
if (cell === undefined)
|
|
290
|
+
continue;
|
|
291
|
+
const value = finiteValue(cell);
|
|
292
|
+
if (value === null || value <= 0)
|
|
293
|
+
continue;
|
|
294
|
+
if (smallest === undefined || value < smallest)
|
|
295
|
+
smallest = value;
|
|
296
|
+
}
|
|
297
|
+
return smallest;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* True when one written form states an exponent that no shown form of this module gives.
|
|
301
|
+
*
|
|
302
|
+
* A shown exponent comes out of `tidy` in every kind that prints one, thus it carries no plus sign and no
|
|
303
|
+
* leading zero. A written form such as `4.3e-05` states the same value in a notation that the page never
|
|
304
|
+
* prints. Thus the prose and the page read differently for one number.
|
|
305
|
+
*
|
|
306
|
+
* The test reads the shown form of the same value and compares the two exponents. Thus it follows the
|
|
307
|
+
* printer, and it repeats none of the rules of the printer. The mantissa stays out of the comparison,
|
|
308
|
+
* because the digits of a written value are a matter of rounding and not of notation.
|
|
309
|
+
*
|
|
310
|
+
* A text that carries no exponent, and a text that holds no finite number, both give false.
|
|
311
|
+
*/
|
|
312
|
+
export function holdsADriftedExponent(text) {
|
|
313
|
+
const written = EXPONENT_PART.exec(text.trim());
|
|
314
|
+
if (written === null) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
const value = finiteValue(text);
|
|
318
|
+
if (value === null) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
const shown = EXPONENT_PART.exec(formatNumberCell(value, "scientific").text);
|
|
322
|
+
return shown !== null && shown[1] !== written[1];
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* The bound of a stored zero, as one significant digit that rounds up, for example `4e-4` from `0.00036`.
|
|
326
|
+
*
|
|
327
|
+
* The rounding reads the decimal text and never the binary value. `toExponential` gives the shortest text
|
|
328
|
+
* that names the value again, thus a mantissa of one digit names the neighbor exactly, and any other
|
|
329
|
+
* mantissa holds something under the first digit. A round to the nearest digit would fall under the
|
|
330
|
+
* neighbor and state a bound that is false. Thus the first digit goes up by one wherever a digit follows
|
|
331
|
+
* it, and a digit that reaches ten carries into the exponent.
|
|
332
|
+
*
|
|
333
|
+
* From `PLAIN_BOUND_EXPONENT` up the bound reads as a plain decimal. A coarse estimator gives a bound
|
|
334
|
+
* beside values that the scientific floor leaves as plain decimals, and one column must read in one
|
|
335
|
+
* notation. Thus `<0.02` stands beside `0.04`, and no bound gives an exponent of zero.
|
|
336
|
+
*/
|
|
337
|
+
function boundForm(bound) {
|
|
338
|
+
const [mantissa, exponent] = bound.toExponential().split("e");
|
|
339
|
+
const digits = mantissa.replace(".", "");
|
|
340
|
+
const raised = Number(digits[0]) + (digits.length > 1 ? 1 : 0);
|
|
341
|
+
const carries = raised === 10;
|
|
342
|
+
const digit = carries ? 1 : raised;
|
|
343
|
+
const power = Number(exponent) + (carries ? 1 : 0);
|
|
344
|
+
if (power < PLAIN_BOUND_EXPONENT) {
|
|
345
|
+
return `${digit}e${power}`;
|
|
346
|
+
}
|
|
347
|
+
// The decimal text builds from the digit and the exponent, thus no float arithmetic can shift it.
|
|
348
|
+
return power >= 0 ? `${digit}${"0".repeat(power)}` : `0.${"0".repeat(-power - 1)}${digit}`;
|
|
349
|
+
}
|
|
350
|
+
/** The finite number of one cell, or `null` when the cell holds no finite number. */
|
|
351
|
+
function finiteValue(cell) {
|
|
352
|
+
if (typeof cell === "number") {
|
|
353
|
+
return Number.isFinite(cell) ? cell : null;
|
|
354
|
+
}
|
|
355
|
+
const trimmed = cell.trim();
|
|
356
|
+
if (trimmed === "") {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
const parsed = Number(trimmed);
|
|
360
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* The text of one finite value in one kind. The identifier kind and the below-resolution kind both give a
|
|
364
|
+
* text that carries no digit of the value, thus both are absent here.
|
|
365
|
+
*/
|
|
366
|
+
function formatValue(value, kind) {
|
|
367
|
+
switch (kind) {
|
|
368
|
+
case "scientific":
|
|
369
|
+
return scientificForm(value);
|
|
370
|
+
case "compact":
|
|
371
|
+
return compactForm(value);
|
|
372
|
+
case "compact-scientific": {
|
|
373
|
+
const magnitude = Math.abs(value);
|
|
374
|
+
if (value !== 0 && magnitude < COMPACT_SCIENTIFIC_FLOOR) {
|
|
375
|
+
return scientificForm(value);
|
|
376
|
+
}
|
|
377
|
+
// Above three significant digits `toPrecision` crosses to the exponential form, and `1.52e4`
|
|
378
|
+
// reads worse than `15,235`. The grouped form holds up to the width where a group run stops
|
|
379
|
+
// being easy to count.
|
|
380
|
+
const rounded = Math.round(magnitude);
|
|
381
|
+
if (rounded >= GROUPED_FLOOR && rounded < GROUPED_CEILING) {
|
|
382
|
+
return compactForm(value);
|
|
383
|
+
}
|
|
384
|
+
return tidy(value.toPrecision(COMPACT_DIGITS));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* One shown form with the sign of its value as the typographic minus.
|
|
390
|
+
*
|
|
391
|
+
* The substitution reads the leading character alone. Thus the exponent of a form such as `-1.2e-4` keeps
|
|
392
|
+
* its own hyphen, and a reader of that exponent reads the notation that the module prints.
|
|
393
|
+
*
|
|
394
|
+
* The shown form then no longer parses back to the value, thus the caller carries the raw cell in the full
|
|
395
|
+
* form. A negative always names its stored digits beside the glyph.
|
|
396
|
+
*/
|
|
397
|
+
function shownMinus(text) {
|
|
398
|
+
return text.startsWith("-") ? TYPOGRAPHIC_MINUS + text.slice(1) : text;
|
|
399
|
+
}
|
|
400
|
+
/** The scientific form, for example `4.3e-5`. */
|
|
401
|
+
function scientificForm(value) {
|
|
402
|
+
return tidy(value.toExponential(SCIENTIFIC_DIGITS - 1));
|
|
403
|
+
}
|
|
404
|
+
/** The integer form with comma grouping, for example `14,201`. The sign stays in front of the groups. */
|
|
405
|
+
function compactForm(value) {
|
|
406
|
+
const sign = value < 0 ? "-" : "";
|
|
407
|
+
return sign + groupDigits(Math.abs(value).toFixed(0));
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Insert one comma before each group of three digits. A text that is not a plain digit run passes through,
|
|
411
|
+
* thus a magnitude that `toFixed` gives in the exponential form stays whole.
|
|
412
|
+
*/
|
|
413
|
+
function groupDigits(digits) {
|
|
414
|
+
if (!DIGIT_RUN.test(digits)) {
|
|
415
|
+
return digits;
|
|
416
|
+
}
|
|
417
|
+
let grouped = "";
|
|
418
|
+
for (let index = 0; index < digits.length; index += 1) {
|
|
419
|
+
if (index > 0 && (digits.length - index) % GROUP_SIZE === 0) {
|
|
420
|
+
grouped += ",";
|
|
421
|
+
}
|
|
422
|
+
grouped += digits[index];
|
|
423
|
+
}
|
|
424
|
+
return grouped;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Trim the noise from one numeric form: the trailing zeros of the fraction, the decimal point that they
|
|
428
|
+
* leave bare, and the plus sign of a positive exponent.
|
|
429
|
+
*/
|
|
430
|
+
function tidy(text) {
|
|
431
|
+
const marker = text.indexOf("e");
|
|
432
|
+
if (marker < 0) {
|
|
433
|
+
return trimTrailingZeros(text);
|
|
434
|
+
}
|
|
435
|
+
const mantissa = trimTrailingZeros(text.slice(0, marker));
|
|
436
|
+
const exponent = text.slice(marker + 1).replace("+", "");
|
|
437
|
+
return `${mantissa}e${exponent}`;
|
|
438
|
+
}
|
|
439
|
+
/** Drop the trailing zeros of a decimal fraction, and the decimal point that they leave bare. */
|
|
440
|
+
function trimTrailingZeros(text) {
|
|
441
|
+
if (!text.includes(".")) {
|
|
442
|
+
return text;
|
|
443
|
+
}
|
|
444
|
+
let end = text.length;
|
|
445
|
+
while (end > 0 && text[end - 1] === "0") {
|
|
446
|
+
end -= 1;
|
|
447
|
+
}
|
|
448
|
+
if (text[end - 1] === ".") {
|
|
449
|
+
end -= 1;
|
|
450
|
+
}
|
|
451
|
+
return text.slice(0, end);
|
|
452
|
+
}
|
|
453
|
+
/** One shown form without its group commas, thus the grouping alone never counts as a hidden digit. */
|
|
454
|
+
function ungrouped(text) {
|
|
455
|
+
return text.replace(GROUP_COMMAS, "");
|
|
456
|
+
}
|
|
457
|
+
/** True when the shown text no longer parses back to the value. */
|
|
458
|
+
function hidesDigits(text, value) {
|
|
459
|
+
return Number(ungrouped(text)) !== value;
|
|
460
|
+
}
|
|
461
|
+
/** True when one whole token of the column name names a p-value. A bare substring never matches. */
|
|
462
|
+
function isPValueColumn(column) {
|
|
463
|
+
return columnTokens(column).some((token) => P_VALUE_TOKENS.has(token));
|
|
464
|
+
}
|
|
465
|
+
/** True when one whole token of the column name names an identifier. A bare substring never matches. */
|
|
466
|
+
function isIdentifierColumn(column) {
|
|
467
|
+
return columnTokens(column).some((token) => IDENTIFIER_TOKENS.has(token));
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* The lowercase tokens of a column name. A separator and a case boundary both end a token, thus `padjBH`
|
|
471
|
+
* gives `padj` and `bh`, and `p_value` gives `p` and `value`.
|
|
472
|
+
*/
|
|
473
|
+
function columnTokens(column) {
|
|
474
|
+
return column
|
|
475
|
+
.replace(CASE_BOUNDARY, "$1 $2")
|
|
476
|
+
.split(SEPARATORS)
|
|
477
|
+
.filter((token) => token.length > 0)
|
|
478
|
+
.map((token) => token.toLowerCase());
|
|
479
|
+
}
|