edfcore 0.2.14 → 0.2.15
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/CHANGELOG.md +12 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/diagnostics/summary.d.ts +43 -0
- package/dist/diagnostics/summary.d.ts.map +1 -0
- package/dist/diagnostics/summary.js +61 -0
- package/dist/diagnostics/summary.js.map +1 -0
- package/dist/format-report.d.ts.map +1 -1
- package/dist/format-report.js +15 -11
- package/dist/format-report.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +1 -1
- package/src/diagnostics/summary.ts +82 -0
- package/src/format-report.ts +17 -11
- package/src/index.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ alone does not tell you whether you were affected.
|
|
|
6
6
|
edfcore is pre-1.0. Patch releases have carried behaviour changes where the old behaviour was a
|
|
7
7
|
defect; those are called out below.
|
|
8
8
|
|
|
9
|
+
## 0.2.15
|
|
10
|
+
|
|
11
|
+
- **Added** `summarizeDiagnostics(diagnostics)` — counts by severity and by code, plus `worst`.
|
|
12
|
+
`formatDiagnostics` produces text for a person; there was nothing that produced numbers for a
|
|
13
|
+
program, and `validateRecording`'s `report.ok` needs a full scan. `worst` is `undefined` for an
|
|
14
|
+
empty list rather than `'info'`, so `worst !== undefined` means "anything to report at all".
|
|
15
|
+
Documented with the warning that `errors > 0` does NOT mean the file failed to read: a deferred
|
|
16
|
+
code carries `error` severity while every signal but one decodes perfectly.
|
|
17
|
+
- **Changed** `formatValidationReport` to print severity counts in a fixed error-warning-info
|
|
18
|
+
order rather than in whichever order they first appeared, and to share one counting
|
|
19
|
+
implementation with `summarizeDiagnostics` instead of keeping a second copy.
|
|
20
|
+
|
|
9
21
|
## 0.2.14
|
|
10
22
|
|
|
11
23
|
- **Added** `segmentAt(index, seconds)` — the pure, synchronous form of `index.locate()`. A viewer
|
package/dist/constants.d.ts
CHANGED
|
@@ -109,5 +109,5 @@ export declare const SIGNAL_FIELD_BLOCK_OFFSETS: {
|
|
|
109
109
|
readonly reserved: 224;
|
|
110
110
|
};
|
|
111
111
|
/** Published package version. Kept in sync with package.json by a test. */
|
|
112
|
-
export declare const VERSION = "0.2.
|
|
112
|
+
export declare const VERSION = "0.2.15";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics, counted.
|
|
3
|
+
*
|
|
4
|
+
* `formatDiagnostics` turns a list into text for a human. This turns the same list into numbers
|
|
5
|
+
* for a program: the question "is anything wrong with this header, and how wrong" has no answer
|
|
6
|
+
* on `EdfHeader` — `validateRecording` produces a `report.ok`, but that needs a full scan, and a
|
|
7
|
+
* caller who has only just parsed the header has nothing to test but `diagnostics.length`.
|
|
8
|
+
*
|
|
9
|
+
* The one thing worth knowing before acting on `errors > 0`: an `error` here does not mean the
|
|
10
|
+
* file failed to read. It means at least one diagnostic has `error` severity, and the `deferred`
|
|
11
|
+
* codes — a signal whose gain cannot be computed — carry that severity while the file parses,
|
|
12
|
+
* reads and decodes perfectly. `signal.scale` is `undefined` for that one signal and every other
|
|
13
|
+
* signal is fine. Refusing the whole recording on that count throws away good data.
|
|
14
|
+
*/
|
|
15
|
+
import type { EdfDiagnostic, EdfDiagnosticCode, EdfSeverity } from '../types.js';
|
|
16
|
+
export interface EdfCodeCount {
|
|
17
|
+
readonly code: EdfDiagnosticCode;
|
|
18
|
+
readonly severity: EdfSeverity;
|
|
19
|
+
readonly count: number;
|
|
20
|
+
}
|
|
21
|
+
export interface EdfDiagnosticSummary {
|
|
22
|
+
readonly total: number;
|
|
23
|
+
readonly errors: number;
|
|
24
|
+
readonly warnings: number;
|
|
25
|
+
readonly infos: number;
|
|
26
|
+
/** The highest severity present, or `undefined` when there are no diagnostics at all. */
|
|
27
|
+
readonly worst: EdfSeverity | undefined;
|
|
28
|
+
/** Distinct codes, most frequent first. Ties keep first-seen order. */
|
|
29
|
+
readonly byCode: readonly EdfCodeCount[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Counts by severity and by code.
|
|
33
|
+
*
|
|
34
|
+
* `worst` is by severity rank, not by insertion order and not alphabetical — `error` beats
|
|
35
|
+
* `warning` beats `info`. It is `undefined` for an empty list rather than `'info'`, which would
|
|
36
|
+
* claim a note exists when none does.
|
|
37
|
+
*
|
|
38
|
+
* `byCode` is ordered by count, descending: on a damaged file one code usually accounts for most
|
|
39
|
+
* of the list — `TIMEKEEPING_TAL_MISSING` is reported per record — and that is the one to look at
|
|
40
|
+
* first. `Array.prototype.sort` is stable, so equal counts keep the order they were first seen.
|
|
41
|
+
*/
|
|
42
|
+
export declare function summarizeDiagnostics(diagnostics: readonly EdfDiagnostic[]): EdfDiagnosticSummary;
|
|
43
|
+
//# sourceMappingURL=summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/diagnostics/summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEjF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yFAAyF;IACzF,QAAQ,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IACxC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAC;CAC1C;AAKD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,SAAS,aAAa,EAAE,GAAG,oBAAoB,CAiChG"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics, counted.
|
|
3
|
+
*
|
|
4
|
+
* `formatDiagnostics` turns a list into text for a human. This turns the same list into numbers
|
|
5
|
+
* for a program: the question "is anything wrong with this header, and how wrong" has no answer
|
|
6
|
+
* on `EdfHeader` — `validateRecording` produces a `report.ok`, but that needs a full scan, and a
|
|
7
|
+
* caller who has only just parsed the header has nothing to test but `diagnostics.length`.
|
|
8
|
+
*
|
|
9
|
+
* The one thing worth knowing before acting on `errors > 0`: an `error` here does not mean the
|
|
10
|
+
* file failed to read. It means at least one diagnostic has `error` severity, and the `deferred`
|
|
11
|
+
* codes — a signal whose gain cannot be computed — carry that severity while the file parses,
|
|
12
|
+
* reads and decodes perfectly. `signal.scale` is `undefined` for that one signal and every other
|
|
13
|
+
* signal is fine. Refusing the whole recording on that count throws away good data.
|
|
14
|
+
*/
|
|
15
|
+
/** Descending, so the first entry is the worst thing in the list. */
|
|
16
|
+
const SEVERITY_RANK = { error: 3, warning: 2, info: 1 };
|
|
17
|
+
/**
|
|
18
|
+
* Counts by severity and by code.
|
|
19
|
+
*
|
|
20
|
+
* `worst` is by severity rank, not by insertion order and not alphabetical — `error` beats
|
|
21
|
+
* `warning` beats `info`. It is `undefined` for an empty list rather than `'info'`, which would
|
|
22
|
+
* claim a note exists when none does.
|
|
23
|
+
*
|
|
24
|
+
* `byCode` is ordered by count, descending: on a damaged file one code usually accounts for most
|
|
25
|
+
* of the list — `TIMEKEEPING_TAL_MISSING` is reported per record — and that is the one to look at
|
|
26
|
+
* first. `Array.prototype.sort` is stable, so equal counts keep the order they were first seen.
|
|
27
|
+
*/
|
|
28
|
+
export function summarizeDiagnostics(diagnostics) {
|
|
29
|
+
let errors = 0;
|
|
30
|
+
let warnings = 0;
|
|
31
|
+
let infos = 0;
|
|
32
|
+
let worst;
|
|
33
|
+
const counts = new Map();
|
|
34
|
+
for (const diagnostic of diagnostics) {
|
|
35
|
+
if (diagnostic.severity === 'error')
|
|
36
|
+
errors += 1;
|
|
37
|
+
else if (diagnostic.severity === 'warning')
|
|
38
|
+
warnings += 1;
|
|
39
|
+
else
|
|
40
|
+
infos += 1;
|
|
41
|
+
if (worst === undefined || SEVERITY_RANK[diagnostic.severity] > SEVERITY_RANK[worst]) {
|
|
42
|
+
worst = diagnostic.severity;
|
|
43
|
+
}
|
|
44
|
+
const existing = counts.get(diagnostic.code);
|
|
45
|
+
counts.set(diagnostic.code, {
|
|
46
|
+
code: diagnostic.code,
|
|
47
|
+
// The severity of a code is fixed by `severityOf`, so the first one seen is the one.
|
|
48
|
+
severity: existing?.severity ?? diagnostic.severity,
|
|
49
|
+
count: (existing?.count ?? 0) + 1,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
total: diagnostics.length,
|
|
54
|
+
errors,
|
|
55
|
+
warnings,
|
|
56
|
+
infos,
|
|
57
|
+
worst,
|
|
58
|
+
byCode: Object.freeze([...counts.values()].sort((a, b) => b.count - a.count)),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/diagnostics/summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAqBH,qEAAqE;AACrE,MAAM,aAAa,GAAgC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAErF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAqC;IACxE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAA8B,CAAC;IAEnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmC,CAAC;IAC1D,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,QAAQ,KAAK,OAAO;YAAE,MAAM,IAAI,CAAC,CAAC;aAC5C,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS;YAAE,QAAQ,IAAI,CAAC,CAAC;;YACrD,KAAK,IAAI,CAAC,CAAC;QAEhB,IAAI,KAAK,KAAK,SAAS,IAAI,aAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACrF,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC;QAC9B,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE;YAC1B,IAAI,EAAE,UAAU,CAAC,IAAI;YACrB,qFAAqF;YACrF,QAAQ,EAAE,QAAQ,EAAE,QAAQ,IAAI,UAAU,CAAC,QAAQ;YACnD,KAAK,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,KAAK,EAAE,WAAW,CAAC,MAAM;QACzB,MAAM;QACN,QAAQ;QACR,KAAK;QACL,KAAK;QACL,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;KAC9E,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-report.d.ts","sourceRoot":"","sources":["../src/format-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"format-report.d.ts","sourceRoot":"","sources":["../src/format-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AASnF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,gBAAgB,EACxB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CA4DR;AAED,qFAAqF;AACrF,YAAY,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/format-report.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* thing are wrong and how much of the file is affected.
|
|
12
12
|
*/
|
|
13
13
|
import { formatDiagnostics } from './diagnostics/format.js';
|
|
14
|
+
import { summarizeDiagnostics } from './diagnostics/summary.js';
|
|
14
15
|
/** Enough to see the pattern, few enough to read. Override with `maxItems`. */
|
|
15
16
|
const DEFAULT_MAX_ITEMS = 20;
|
|
16
17
|
function pluralise(count, noun) {
|
|
@@ -25,24 +26,27 @@ function pluralise(count, noun) {
|
|
|
25
26
|
export function formatValidationReport(report, options) {
|
|
26
27
|
const lines = [];
|
|
27
28
|
const header = options?.header;
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
for (const diagnostic of report.diagnostics) {
|
|
31
|
-
bySeverity.set(diagnostic.severity, (bySeverity.get(diagnostic.severity) ?? 0) + 1);
|
|
32
|
-
byCode.set(diagnostic.code, (byCode.get(diagnostic.code) ?? 0) + 1);
|
|
33
|
-
}
|
|
29
|
+
// One counting implementation, shared with the public `summarizeDiagnostics`.
|
|
30
|
+
const summary = summarizeDiagnostics(report.diagnostics);
|
|
34
31
|
const verdict = report.ok ? 'PASS' : 'FAIL';
|
|
35
|
-
const severities =
|
|
32
|
+
const severities = summary.total === 0
|
|
36
33
|
? 'no diagnostics'
|
|
37
|
-
: [
|
|
34
|
+
: [
|
|
35
|
+
[summary.errors, 'error'],
|
|
36
|
+
[summary.warnings, 'warning'],
|
|
37
|
+
[summary.infos, 'info'],
|
|
38
|
+
]
|
|
39
|
+
.filter(([count]) => count > 0)
|
|
40
|
+
.map(([count, severity]) => `${count} ${severity}`)
|
|
41
|
+
.join(', ');
|
|
38
42
|
lines.push(`${verdict} — ${severities}`);
|
|
39
43
|
lines.push(`scanned ${pluralise(report.recordsScanned, 'record')}, ` +
|
|
40
44
|
`read ${report.bytesRead.toLocaleString('en-US')} bytes`);
|
|
41
|
-
if (byCode.
|
|
45
|
+
if (summary.byCode.length > 0) {
|
|
42
46
|
lines.push('');
|
|
43
47
|
lines.push('by code:');
|
|
44
|
-
//
|
|
45
|
-
for (const
|
|
48
|
+
// Already descending by count: the code affecting most of the file comes first.
|
|
49
|
+
for (const { code, count } of summary.byCode) {
|
|
46
50
|
lines.push(` ${String(count).padStart(7)} ${code}`);
|
|
47
51
|
}
|
|
48
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-report.js","sourceRoot":"","sources":["../src/format-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"format-report.js","sourceRoot":"","sources":["../src/format-report.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
/** The trailing `options` argument shared by every primitive that can allocate. */
|
|
23
23
|
export type { MaterializeOptions } from './decode/digital.js';
|
|
24
24
|
export type { FormatDiagnosticsOptions } from './diagnostics/format.js';
|
|
25
|
+
export type { EdfCodeCount, EdfDiagnosticSummary } from './diagnostics/summary.js';
|
|
25
26
|
export type { AbortSignalLike, BlobLike, BuildIndexOptions, ByteSource, CacheOptions, DecodeAnnotationsOptions, EdfAnnotation, EdfAnnotationsResult, EdfAnnotationWindow, EdfCalendarDate, EdfChunk, EdfChunkSignal, EdfClockTime, EdfDiagnostic, EdfDiagnosticCode, EdfEnvelopeChunk, EdfEnvelopeSignal, EdfGap, EdfHeader, EdfInspection, EdfKnownDiagnosticCode, EdfLocation, EdfPatientId, EdfPhysicalEnvelope, EdfRawHeaderFields, EdfRawSignalFields, EdfRecordIndex, EdfRecording, EdfRecordingId, EdfSampleLocation, EdfScale, EdfSegment, EdfSeverity, EdfSignal, EdfStartTime, EdfStatusWord, EdfTimeline, EdfTriggerEvent, EdfVariant, EnvelopeSelection, FetchLike, FormatHeaderOptions, HttpResponseLike, HttpSourceOptions, OpenOptions, ParseOptions, ReadOptions, RecordRange, RecordSelection, StreamSelection, TriggerSelection, WindowSelection, } from './types.js';
|
|
26
27
|
export type { AnyEdfError, EdfErrorKind, EdfFormatErrorInit } from './errors.js';
|
|
27
28
|
export { EdfAmbiguousChannelError, EdfBudgetError, EdfChannelNotFoundError, EdfError, EdfFormatError, EdfRangeError, EdfScalingError, EdfSourceError, isEdfError, } from './errors.js';
|
|
@@ -30,6 +31,7 @@ export { decodeHeaderLatin1 } from './bytes/latin1.js';
|
|
|
30
31
|
export { decodeDigital } from './decode/digital.js';
|
|
31
32
|
export { clampToDigitalRange, physicalRangeOf, toPhysical } from './decode/physical.js';
|
|
32
33
|
export { formatDiagnostics } from './diagnostics/format.js';
|
|
34
|
+
export { summarizeDiagnostics } from './diagnostics/summary.js';
|
|
33
35
|
export { formatStartTimeNaive } from './header/dates.js';
|
|
34
36
|
export { declaredDurationSeconds, findSignals, getSignal, isAnnotationLabel, matchSignals, } from './header/lookup.js';
|
|
35
37
|
export { parseHeader } from './header/parse.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,mFAAmF;AACnF,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,YAAY,EACV,eAAe,EACf,QAAQ,EACR,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,wBAAwB,EACxB,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,MAAM,EACN,SAAS,EACT,aAAa,EACb,sBAAsB,EACtB,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,UAAU,EACV,WAAW,EACX,SAAS,EACT,YAAY,EACZ,aAAa,EACb,WAAW,EACX,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,SAAS,EACT,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,WAAW,EACX,WAAW,EACX,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,YAAY,CAAC;AAUpB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAMrB,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAOxB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQnE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM1C,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAM7F,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ export { decodeHeaderLatin1 } from './bytes/latin1.js';
|
|
|
32
32
|
export { decodeDigital } from './decode/digital.js';
|
|
33
33
|
export { clampToDigitalRange, physicalRangeOf, toPhysical } from './decode/physical.js';
|
|
34
34
|
export { formatDiagnostics } from './diagnostics/format.js';
|
|
35
|
+
export { summarizeDiagnostics } from './diagnostics/summary.js';
|
|
35
36
|
export { formatStartTimeNaive } from './header/dates.js';
|
|
36
37
|
export { declaredDurationSeconds, findSignals, getSignal, isAnnotationLabel, matchSignals, } from './header/lookup.js';
|
|
37
38
|
export { parseHeader } from './header/parse.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA2EH,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAExB,8EAA8E;AAC9E,2EAA2E;AAC3E,2CAA2C;AAC3C,8EAA8E;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,8EAA8E;AAC9E,yEAAyE;AACzE,2EAA2E;AAC3E,6CAA6C;AAC7C,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE7F,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics, counted.
|
|
3
|
+
*
|
|
4
|
+
* `formatDiagnostics` turns a list into text for a human. This turns the same list into numbers
|
|
5
|
+
* for a program: the question "is anything wrong with this header, and how wrong" has no answer
|
|
6
|
+
* on `EdfHeader` — `validateRecording` produces a `report.ok`, but that needs a full scan, and a
|
|
7
|
+
* caller who has only just parsed the header has nothing to test but `diagnostics.length`.
|
|
8
|
+
*
|
|
9
|
+
* The one thing worth knowing before acting on `errors > 0`: an `error` here does not mean the
|
|
10
|
+
* file failed to read. It means at least one diagnostic has `error` severity, and the `deferred`
|
|
11
|
+
* codes — a signal whose gain cannot be computed — carry that severity while the file parses,
|
|
12
|
+
* reads and decodes perfectly. `signal.scale` is `undefined` for that one signal and every other
|
|
13
|
+
* signal is fine. Refusing the whole recording on that count throws away good data.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { EdfDiagnostic, EdfDiagnosticCode, EdfSeverity } from '../types.js';
|
|
17
|
+
|
|
18
|
+
export interface EdfCodeCount {
|
|
19
|
+
readonly code: EdfDiagnosticCode;
|
|
20
|
+
readonly severity: EdfSeverity;
|
|
21
|
+
readonly count: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EdfDiagnosticSummary {
|
|
25
|
+
readonly total: number;
|
|
26
|
+
readonly errors: number;
|
|
27
|
+
readonly warnings: number;
|
|
28
|
+
readonly infos: number;
|
|
29
|
+
/** The highest severity present, or `undefined` when there are no diagnostics at all. */
|
|
30
|
+
readonly worst: EdfSeverity | undefined;
|
|
31
|
+
/** Distinct codes, most frequent first. Ties keep first-seen order. */
|
|
32
|
+
readonly byCode: readonly EdfCodeCount[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Descending, so the first entry is the worst thing in the list. */
|
|
36
|
+
const SEVERITY_RANK: Record<EdfSeverity, number> = { error: 3, warning: 2, info: 1 };
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Counts by severity and by code.
|
|
40
|
+
*
|
|
41
|
+
* `worst` is by severity rank, not by insertion order and not alphabetical — `error` beats
|
|
42
|
+
* `warning` beats `info`. It is `undefined` for an empty list rather than `'info'`, which would
|
|
43
|
+
* claim a note exists when none does.
|
|
44
|
+
*
|
|
45
|
+
* `byCode` is ordered by count, descending: on a damaged file one code usually accounts for most
|
|
46
|
+
* of the list — `TIMEKEEPING_TAL_MISSING` is reported per record — and that is the one to look at
|
|
47
|
+
* first. `Array.prototype.sort` is stable, so equal counts keep the order they were first seen.
|
|
48
|
+
*/
|
|
49
|
+
export function summarizeDiagnostics(diagnostics: readonly EdfDiagnostic[]): EdfDiagnosticSummary {
|
|
50
|
+
let errors = 0;
|
|
51
|
+
let warnings = 0;
|
|
52
|
+
let infos = 0;
|
|
53
|
+
let worst: EdfSeverity | undefined;
|
|
54
|
+
|
|
55
|
+
const counts = new Map<EdfDiagnosticCode, EdfCodeCount>();
|
|
56
|
+
for (const diagnostic of diagnostics) {
|
|
57
|
+
if (diagnostic.severity === 'error') errors += 1;
|
|
58
|
+
else if (diagnostic.severity === 'warning') warnings += 1;
|
|
59
|
+
else infos += 1;
|
|
60
|
+
|
|
61
|
+
if (worst === undefined || SEVERITY_RANK[diagnostic.severity] > SEVERITY_RANK[worst]) {
|
|
62
|
+
worst = diagnostic.severity;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const existing = counts.get(diagnostic.code);
|
|
66
|
+
counts.set(diagnostic.code, {
|
|
67
|
+
code: diagnostic.code,
|
|
68
|
+
// The severity of a code is fixed by `severityOf`, so the first one seen is the one.
|
|
69
|
+
severity: existing?.severity ?? diagnostic.severity,
|
|
70
|
+
count: (existing?.count ?? 0) + 1,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
total: diagnostics.length,
|
|
76
|
+
errors,
|
|
77
|
+
warnings,
|
|
78
|
+
infos,
|
|
79
|
+
worst,
|
|
80
|
+
byCode: Object.freeze([...counts.values()].sort((a, b) => b.count - a.count)),
|
|
81
|
+
};
|
|
82
|
+
}
|
package/src/format-report.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { formatDiagnostics } from './diagnostics/format.js';
|
|
15
|
+
import { summarizeDiagnostics } from './diagnostics/summary.js';
|
|
15
16
|
import type { EdfHeader, FormatReportOptions, ValidationReport } from './types.js';
|
|
16
17
|
|
|
17
18
|
/** Enough to see the pattern, few enough to read. Override with `maxItems`. */
|
|
@@ -34,29 +35,34 @@ export function formatValidationReport(
|
|
|
34
35
|
const lines: string[] = [];
|
|
35
36
|
const header = options?.header;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
for (const diagnostic of report.diagnostics) {
|
|
40
|
-
bySeverity.set(diagnostic.severity, (bySeverity.get(diagnostic.severity) ?? 0) + 1);
|
|
41
|
-
byCode.set(diagnostic.code, (byCode.get(diagnostic.code) ?? 0) + 1);
|
|
42
|
-
}
|
|
38
|
+
// One counting implementation, shared with the public `summarizeDiagnostics`.
|
|
39
|
+
const summary = summarizeDiagnostics(report.diagnostics);
|
|
43
40
|
|
|
44
41
|
const verdict = report.ok ? 'PASS' : 'FAIL';
|
|
45
42
|
const severities =
|
|
46
|
-
|
|
43
|
+
summary.total === 0
|
|
47
44
|
? 'no diagnostics'
|
|
48
|
-
:
|
|
45
|
+
: (
|
|
46
|
+
[
|
|
47
|
+
[summary.errors, 'error'],
|
|
48
|
+
[summary.warnings, 'warning'],
|
|
49
|
+
[summary.infos, 'info'],
|
|
50
|
+
] as const
|
|
51
|
+
)
|
|
52
|
+
.filter(([count]) => count > 0)
|
|
53
|
+
.map(([count, severity]) => `${count} ${severity}`)
|
|
54
|
+
.join(', ');
|
|
49
55
|
lines.push(`${verdict} — ${severities}`);
|
|
50
56
|
lines.push(
|
|
51
57
|
`scanned ${pluralise(report.recordsScanned, 'record')}, ` +
|
|
52
58
|
`read ${report.bytesRead.toLocaleString('en-US')} bytes`,
|
|
53
59
|
);
|
|
54
60
|
|
|
55
|
-
if (byCode.
|
|
61
|
+
if (summary.byCode.length > 0) {
|
|
56
62
|
lines.push('');
|
|
57
63
|
lines.push('by code:');
|
|
58
|
-
//
|
|
59
|
-
for (const
|
|
64
|
+
// Already descending by count: the code affecting most of the file comes first.
|
|
65
|
+
for (const { code, count } of summary.byCode) {
|
|
60
66
|
lines.push(` ${String(count).padStart(7)} ${code}`);
|
|
61
67
|
}
|
|
62
68
|
}
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
/** The trailing `options` argument shared by every primitive that can allocate. */
|
|
29
29
|
export type { MaterializeOptions } from './decode/digital.js';
|
|
30
30
|
export type { FormatDiagnosticsOptions } from './diagnostics/format.js';
|
|
31
|
+
export type { EdfCodeCount, EdfDiagnosticSummary } from './diagnostics/summary.js';
|
|
31
32
|
export type {
|
|
32
33
|
AbortSignalLike,
|
|
33
34
|
BlobLike,
|
|
@@ -130,6 +131,7 @@ export { decodeHeaderLatin1 } from './bytes/latin1.js';
|
|
|
130
131
|
export { decodeDigital } from './decode/digital.js';
|
|
131
132
|
export { clampToDigitalRange, physicalRangeOf, toPhysical } from './decode/physical.js';
|
|
132
133
|
export { formatDiagnostics } from './diagnostics/format.js';
|
|
134
|
+
export { summarizeDiagnostics } from './diagnostics/summary.js';
|
|
133
135
|
export { formatStartTimeNaive } from './header/dates.js';
|
|
134
136
|
export {
|
|
135
137
|
declaredDurationSeconds,
|