@kymoscope/sdk 0.4.0 → 0.5.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/importers-Y-PAIexV.js +5 -0
- package/dist/keys-DMRJGG6U.js +10 -0
- package/dist/kymoscope-embed.js +59 -58
- package/dist/kymoscope-sdk.js +84 -83
- package/dist/types/charts/useWorksheetData.d.ts +2 -0
- package/dist/types/engine/createEngine.d.ts +51 -1
- package/dist/types/engine/types.d.ts +28 -2
- package/dist/types/index.d.ts +5 -3
- package/dist/types/lib/cycles/cut.d.ts +48 -0
- package/dist/types/lib/cycles/evaluator.d.ts +127 -0
- package/dist/types/lib/live/crypto.d.ts +3 -0
- package/dist/types/lib/live/publisher.d.ts +6 -1
- package/dist/types/lib/live/types.d.ts +2 -2
- package/dist/types/lib/segments/liveAxis.d.ts +8 -1
- package/dist/types/lib/segments/rules.d.ts +14 -0
- package/dist/types/lib/workspaces/io.d.ts +6 -0
- package/dist/types/types/loadedDataset.d.ts +15 -0
- package/package.json +1 -1
- package/dist/importers-B6rkbTwQ.js +0 -5
- package/dist/keys-KE0GTKAF.js +0 -10
|
@@ -51,6 +51,8 @@ export declare function staticXExtent(parts: {
|
|
|
51
51
|
rows: Record<string, unknown>[];
|
|
52
52
|
offset: number;
|
|
53
53
|
}[], xKey: string): [number, number] | null;
|
|
54
|
+
/** Unit per column, plus each column's suffixed key (e.g. "FZ__0") per overlaid source. */
|
|
55
|
+
export declare function overlayColumnUnits(data: Pick<Dataset, 'columnNames' | 'units'>, sources: number): Record<string, string>;
|
|
54
56
|
/**
|
|
55
57
|
* Rows of the current lap on a wrapping X: from the last sample where X drops by
|
|
56
58
|
* more than half its range (lap distance at the finish line). A channel that
|
|
@@ -18,6 +18,8 @@ import type { ChannelChooser } from '../lib/importers/types';
|
|
|
18
18
|
import type { RecordingKey } from '../lib/recordings/keys';
|
|
19
19
|
import { DATASET_PALETTE } from '../lib/datasetColors';
|
|
20
20
|
import { type BeaconConfig } from '../lib/beacon';
|
|
21
|
+
import { type CycleDefinition, type CycleFound, type RuleDef } from '../lib/cycles/evaluator';
|
|
22
|
+
import type { KpiSpec } from '../lib/segments/rules';
|
|
21
23
|
import { type Dataset, type DatasetParseConfig } from '../lib/parseDataset';
|
|
22
24
|
import { parseKymoscopeFile, type WorkbookBody } from '../lib/workspaces/io';
|
|
23
25
|
import { type WorkbookFileOptions } from '../lib/workspaces/encrypted';
|
|
@@ -48,6 +50,8 @@ export interface InternalState {
|
|
|
48
50
|
colors: Record<string, string>;
|
|
49
51
|
liveWindowSeconds: number | null;
|
|
50
52
|
segmentContext: SegmentContext | null;
|
|
53
|
+
/** Cycle and phase rules (lib/cycles), saved with the workbook. */
|
|
54
|
+
cycleRules: CycleDefinition[];
|
|
51
55
|
live: LiveConnection[];
|
|
52
56
|
/** Video sources registered by the host (engine.video.register): recorded cameras. */
|
|
53
57
|
video: VideoSource[];
|
|
@@ -135,7 +139,10 @@ export declare function createEngine(options?: EngineOptions): {
|
|
|
135
139
|
* A cloud recording as a dataset: its encrypted chunks (KRC1, in order) decrypted with `keys` in the import
|
|
136
140
|
* worker, in memory or out of core like a file. Returns the id.
|
|
137
141
|
*/
|
|
138
|
-
openRecording(input: RecordingInput
|
|
142
|
+
openRecording(input: RecordingInput, o?: {
|
|
143
|
+
signal?: AbortSignal;
|
|
144
|
+
onProgress?: (fraction: number) => void;
|
|
145
|
+
}): Promise<string>;
|
|
139
146
|
/** New chunks of an open recording: the dataset keeps its id, selection, colour, offset and laps. */
|
|
140
147
|
reopenRecording(id: string, input: RecordingInput): Promise<void>;
|
|
141
148
|
/**
|
|
@@ -169,6 +176,40 @@ export declare function createEngine(options?: EngineOptions): {
|
|
|
169
176
|
writeColumns(id: string, names: string[], fill: (cols: Record<string, Float64Array>, current: Record<string, Float64Array | undefined>) => void): void;
|
|
170
177
|
/** Add delimited text (CSV/TSV/.dat), parsed like an imported file. */
|
|
171
178
|
addText(text: string, name?: string): Promise<string>;
|
|
179
|
+
/** The cycles and phases of a dataset under cycle rules v2 (lib/cycles): closed ones, then the open ones (tend
|
|
180
|
+
* null), each with its definition's KPIs (`kpis`, by column name: "TIC201 max"). `timeKey`: its time column
|
|
181
|
+
* (seconds). Any kind of dataset: read through its source. */
|
|
182
|
+
findCycles(id: string, defs: Array<RuleDef & {
|
|
183
|
+
kpis?: KpiSpec[];
|
|
184
|
+
}>, timeKey: string): Promise<CycleFound[]>;
|
|
185
|
+
/**
|
|
186
|
+
* "Add to datasets": the picked cycles or phases of a dataset (from findCycles), each as a dataset of its samples
|
|
187
|
+
* with `Cycle time` (s since its start), then the cycles table and phases table of `cycleId` (unless `tables`
|
|
188
|
+
* false). Returns the new ids, in that order.
|
|
189
|
+
*/
|
|
190
|
+
addCycles(id: string, o: {
|
|
191
|
+
timeKey: string;
|
|
192
|
+
defs: Array<RuleDef & {
|
|
193
|
+
name: string;
|
|
194
|
+
kpis?: KpiSpec[];
|
|
195
|
+
}>;
|
|
196
|
+
cycleId: string;
|
|
197
|
+
found: CycleFound[];
|
|
198
|
+
pick: Array<Pick<CycleFound, "definitionId" | "index" | "parentIndex">>;
|
|
199
|
+
tables?: boolean;
|
|
200
|
+
}): Promise<string[]>;
|
|
201
|
+
/**
|
|
202
|
+
* Cut a dataset into cycles (the dataset's Cycles button): the cycles of `cycleId` and its phases, found in the
|
|
203
|
+
* browser over any kind of dataset; each becomes a part with `Cycle time` (s since its start), and the dataset is
|
|
204
|
+
* shown and selected as them, as laps are (a selected dataset plots all its cycles). `null` undoes it. Nothing is
|
|
205
|
+
* stored: a workbook keeps the rules, never the cycles. Resolves to the number of cycles (phases not counted).
|
|
206
|
+
* ponytail: reads the dataset's numeric columns whole (as addCycles); a part is a view, not a copy.
|
|
207
|
+
*/
|
|
208
|
+
cut(id: string, o: {
|
|
209
|
+
defs: CycleDefinition[];
|
|
210
|
+
cycleId: string;
|
|
211
|
+
timeKey: string;
|
|
212
|
+
} | null): Promise<number>;
|
|
172
213
|
remove(id: string): void;
|
|
173
214
|
/** Remove every dataset. Worksheets and charts are kept. */
|
|
174
215
|
clear(): void;
|
|
@@ -315,6 +356,15 @@ export declare function createEngine(options?: EngineOptions): {
|
|
|
315
356
|
/** Host-owned store: push the authoritative list after it changes (an open workbook's channels stay). */
|
|
316
357
|
sync(channels: MathChannel[]): void;
|
|
317
358
|
};
|
|
359
|
+
/** Cycle and phase rules (lib/cycles), saved with the workbook: `getState().cycleRules`. */
|
|
360
|
+
cycles: {
|
|
361
|
+
/** Add or replace a cycle or phase rule (by id; a new one gets an id). Returns its id. Saved with the workbook. */
|
|
362
|
+
save(def: Omit<CycleDefinition, "id"> & {
|
|
363
|
+
id?: string;
|
|
364
|
+
}): string;
|
|
365
|
+
/** Remove a rule and the phases inside it. */
|
|
366
|
+
remove(id: string): void;
|
|
367
|
+
};
|
|
318
368
|
live: {
|
|
319
369
|
/** A stream the page feeds itself (no relay, no crypto). */
|
|
320
370
|
openLocal(name: string, options?: LiveStreamOptions): LocalLiveStream;
|
|
@@ -4,6 +4,7 @@ import type { DashboardPanelKind } from '../components/dashboardGridLayout';
|
|
|
4
4
|
import type { PanelChartSettings } from '../lib/panelChartSettings';
|
|
5
5
|
import type { ChartAnnotation } from '../lib/annotations';
|
|
6
6
|
import type { MathChannel, MathChannelPatch } from '../lib/mathChannels/types';
|
|
7
|
+
import type { CycleDefinition, CycleFlag } from '../lib/cycles/evaluator';
|
|
7
8
|
import type { LiveRelayConfig } from '../lib/live/stream';
|
|
8
9
|
import type { BeaconConfig } from '../lib/beacon';
|
|
9
10
|
import type { DatasetParseConfig } from '../lib/parseDataset';
|
|
@@ -76,6 +77,23 @@ export interface LapInfo {
|
|
|
76
77
|
color: string;
|
|
77
78
|
offset: number;
|
|
78
79
|
selected: boolean;
|
|
80
|
+
/** A cut's cycle or phase (datasets.cut); absent for a beacon lap. */
|
|
81
|
+
cycle?: LapCycle;
|
|
82
|
+
}
|
|
83
|
+
/** What a cut found for one lap row: a cycle (no parentIndex) or one of its phases. */
|
|
84
|
+
export interface LapCycle {
|
|
85
|
+
definitionId: string;
|
|
86
|
+
/** The rule's name ("Cycle", "Heating"). */
|
|
87
|
+
rule: string;
|
|
88
|
+
index: number;
|
|
89
|
+
/** A phase: the index of its cycle. */
|
|
90
|
+
parentIndex: number | null;
|
|
91
|
+
tstart: number;
|
|
92
|
+
tend: number | null;
|
|
93
|
+
flags: CycleFlag[];
|
|
94
|
+
kpis: Record<string, number | null>;
|
|
95
|
+
/** Cut short: by the end of the data, or of its cycle (a phase). */
|
|
96
|
+
incomplete: boolean;
|
|
79
97
|
}
|
|
80
98
|
export interface DatasetInfo {
|
|
81
99
|
id: string;
|
|
@@ -95,7 +113,13 @@ export interface DatasetInfo {
|
|
|
95
113
|
/** Binary files: format, start time, groups (with rates), events; null for text. */
|
|
96
114
|
fileInfo: DatasetFileInfo | null;
|
|
97
115
|
beacon: BeaconConfig | null;
|
|
98
|
-
/**
|
|
116
|
+
/** The rule a dataset is cut by (datasets.cut), or null. */
|
|
117
|
+
cut: {
|
|
118
|
+
cycleId: string;
|
|
119
|
+
timeKey: string;
|
|
120
|
+
defs: CycleDefinition[];
|
|
121
|
+
} | null;
|
|
122
|
+
/** Present when the dataset is split into laps (beacon) or cut into cycles (then each lap has `cycle`). */
|
|
99
123
|
laps: LapInfo[] | null;
|
|
100
124
|
/** Selection state: true, false, or 'partial' when only some laps are selected. */
|
|
101
125
|
selected: boolean | 'partial';
|
|
@@ -130,7 +154,7 @@ export interface LiveConnectionInfo {
|
|
|
130
154
|
id: string;
|
|
131
155
|
name: string;
|
|
132
156
|
ownerName: string;
|
|
133
|
-
visibility: 'private' | 'public' | 'org' | 'project';
|
|
157
|
+
visibility: 'private' | 'public' | 'org' | 'project' | 'open';
|
|
134
158
|
orgName: string | null;
|
|
135
159
|
/** A camera carries video (a camera panel shows it); a data stream, samples. */
|
|
136
160
|
kind: 'data' | 'camera';
|
|
@@ -170,6 +194,8 @@ export interface EngineState {
|
|
|
170
194
|
liveWindowSeconds: number | null;
|
|
171
195
|
/** The Segments panel's history, definition and reference, or null. */
|
|
172
196
|
segmentContext: SegmentContext | null;
|
|
197
|
+
/** Cycle and phase rules (engine.cycles), saved with the workbook. */
|
|
198
|
+
cycleRules: CycleDefinition[];
|
|
173
199
|
limits: KymoscopeLimits;
|
|
174
200
|
/** Colors given to plotted datasets in order (EngineOptions.palette). */
|
|
175
201
|
palette: string[];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type { KymoscopeEngine, Snapshot, ImportSource, ImportResult, ImportOptio
|
|
|
13
13
|
export { KymoscopeError } from './engine/errors';
|
|
14
14
|
export type { KymoscopeErrorCode } from './engine/errors';
|
|
15
15
|
export { UNLIMITED } from './engine/types';
|
|
16
|
-
export type { EngineOptions, EngineState, EngineEvents, KymoscopeLimits, LimitReason, MathChannelStoreAdapter, DatasetInfo, ColumnInfo, LapInfo, ChartKind, ChartModel, WorksheetState, LiveConnectionInfo, } from './engine/types';
|
|
16
|
+
export type { EngineOptions, EngineState, EngineEvents, KymoscopeLimits, LimitReason, MathChannelStoreAdapter, DatasetInfo, ColumnInfo, LapInfo, LapCycle, ChartKind, ChartModel, WorksheetState, LiveConnectionInfo, } from './engine/types';
|
|
17
17
|
export type { ChartMark, RuleStateInfo } from './engine/alerts';
|
|
18
18
|
export { CHART_KINDS, chartLabel } from './engine/kinds';
|
|
19
19
|
export type { ChartKindInfo } from './engine/kinds';
|
|
@@ -73,7 +73,7 @@ export { parseKrc, parseKrcHeader, KRC_HEADER_BYTES } from './lib/recordings/krc
|
|
|
73
73
|
export type { RecordingInput } from './engine/createEngine';
|
|
74
74
|
export { exportRecording, exportRecordingTo, EXPORT_TYPES, type ExportPart, type RecordingSource } from './lib/recordings/export';
|
|
75
75
|
export type { EncryptedEnvelope, DeviceWrap, SealedKey, PasswordSealed } from './lib/live/crypto';
|
|
76
|
-
export { generateApiToken, splitApiKey, generateDeviceKeyPair, unwrapStreamKey, unwrapForDevice, wrapForDevice, projectWrapAad, streamKeyAad, bindKeyAad, randomKey, importStreamKey, sealUnderProjectKey, openUnderProjectKey, senderBindKey, senderBinding, verifySenderBinding, deviceFingerprint, hashToken, generateSalt, deriveKey, deriveRawKeyBase64, makeKeyCheck, verifyKeyCheck, WORKBOOK_EXPORT_AAD, workbookBodyAad, workbookKeyAad, historyKeyAad, projectConfigKeyAad, alertRuleAad, recoveryKeyAad, recoveryAttestationAad, sealWithKey, openWithKey, sealWithPassword, openWithPassword, recoveryCode, parseRecoveryCode, generateRecoveryKeyPair, recoveryPrivateKey, } from './lib/live/crypto';
|
|
76
|
+
export { generateApiToken, splitApiKey, generateDeviceKeyPair, unwrapStreamKey, unwrapForDevice, wrapForDevice, projectWrapAad, streamKeyAad, bindKeyAad, randomKey, importStreamKey, sealUnderProjectKey, openUnderProjectKey, senderBindKey, senderBinding, verifySenderBinding, deviceFingerprint, hashToken, generateSalt, deriveKey, OPEN_STREAM_PASSWORD, deriveRawKeyBase64, makeKeyCheck, verifyKeyCheck, WORKBOOK_EXPORT_AAD, workbookBodyAad, workbookKeyAad, historyKeyAad, projectConfigKeyAad, alertRuleAad, recoveryKeyAad, recoveryAttestationAad, sealWithKey, openWithKey, sealWithPassword, openWithPassword, recoveryCode, parseRecoveryCode, generateRecoveryKeyPair, recoveryPrivateKey, } from './lib/live/crypto';
|
|
77
77
|
export * from './rules/types';
|
|
78
78
|
export { validateRule, type RuleProblem } from './rules/validate';
|
|
79
79
|
export { thresholdOf } from './rules/threshold';
|
|
@@ -86,7 +86,9 @@ export type { HistoryRangeView } from './engine/columns';
|
|
|
86
86
|
export { isClockAxis, formatClockTick, formatClockValue, setClockTimeZone, getClockTimeZone, subscribeClockTimeZone } from './lib/yAxisTicks';
|
|
87
87
|
export { readHistoryRange, readFooter, chooseLevel, pickSegments, coalesce, pool, RANGE_PARALLEL, HISTORY_PIXELS, type HistorySegmentRef, type HistoryReadOptions, type HistoryRange } from './lib/historian/reader';
|
|
88
88
|
export { exportHistoryTo, wideDay, f32text, isoMicros, MAX_WIDE_CELLS, MAX_PARQUET_VALUES, type HistoryExportFormat } from './lib/historian/export';
|
|
89
|
-
export {
|
|
89
|
+
export { CycleRunner, CycleSet, cycleRuleChannels, type CycleRule, type Trigger as CycleTrigger, type Condition as CycleCondition, type CycleLimits, type CycleFlag, type CycleInstance, type CycleFound, type RuleDef, type CycleDefinition, parseCycleDefinitions } from './lib/cycles/evaluator';
|
|
90
|
+
export { CYCLE_TIME, readSource, findCycles, cutCycle, cycleTables, definitionChannels, kpiColumn, kpisOf, rowsCsv, type SourceColumns } from './lib/cycles/cut';
|
|
91
|
+
export { RuleRunner, KpiAcc, resetStep, newResetState, compareOp, kpiKey, ruleChannels, localDay, zonedInstant, shiftDay, tzOffsetS, KPI_STATS, type SegmentRule, type CompareOp, type EdgeOn, type KpiStat, type KpiSpec, type Kpis, type DetectedSegment, type ResetState } from './lib/segments/rules';
|
|
90
92
|
export type { SegmentContext } from './engine/types';
|
|
91
93
|
export { referenceBands } from './charts/beacon';
|
|
92
94
|
export { alignSegment, AxisState, axisColumn, localSeconds, resample, gridAt, buildReference, bandEdges, compareToReference, encodeBand, decodeBand, MAX_GRID, MAX_BAND_CELLS, type AlignAxis, type AlignedSegment, type ReferenceCurve, type ReferenceChannel, type Comparison } from './lib/segments/align';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cutting any source into cycles (spec 2026-10-28-cycles-design, "Cutting any source"): read a source's time and the
|
|
3
|
+
* channels needed through DataSource.readColumns (every kind has it: memory, columnar, history range, recording, DuckDB),
|
|
4
|
+
* find the cycles with a CycleSet, and cut each one into columns with `Cycle time` (seconds since its start). The
|
|
5
|
+
* cycles table and phases table are rows (text flags), for the row-based charts.
|
|
6
|
+
* ponytail: the channels are read whole into memory; read per cycle (like segmentRun.loadAligned) if a source is too
|
|
7
|
+
* large for that.
|
|
8
|
+
*/
|
|
9
|
+
import type { DataSource } from '../data/dataSource';
|
|
10
|
+
import type { DatasetRow } from '../parseDataset';
|
|
11
|
+
import { type KpiSpec } from '../segments/rules';
|
|
12
|
+
import { type CycleFound, type RuleDef } from './evaluator';
|
|
13
|
+
export declare const CYCLE_TIME = "Cycle time";
|
|
14
|
+
export interface SourceColumns {
|
|
15
|
+
t: Float64Array;
|
|
16
|
+
cols: Map<string, Float64Array>;
|
|
17
|
+
}
|
|
18
|
+
/** `timeKey` and `channels` of a source, whole, in file order. Missing channels are left out. */
|
|
19
|
+
export declare function readSource(source: DataSource, timeKey: string, channels: string[]): Promise<SourceColumns>;
|
|
20
|
+
/** The cycles and phases of `defs` in the columns: closed ones, then the ones still open (tend null). */
|
|
21
|
+
export declare function findCycles(src: SourceColumns, defs: RuleDef[]): CycleFound[];
|
|
22
|
+
/** Every channel the definitions read (their rules and KPIs). */
|
|
23
|
+
export declare const definitionChannels: (defs: Array<RuleDef & {
|
|
24
|
+
kpis?: KpiSpec[];
|
|
25
|
+
}>) => string[];
|
|
26
|
+
/** A KPI's column name in the cycles and phases tables: "TIC201 max". */
|
|
27
|
+
export declare const kpiColumn: (k: KpiSpec) => string;
|
|
28
|
+
/** The KPIs of [tstart, tend) (an open one: to the end) from the source's columns; null where a channel has no value. */
|
|
29
|
+
export declare function kpisOf(src: SourceColumns, c: Pick<CycleFound, 'tstart' | 'tend'>, kpis: KpiSpec[]): Record<string, number | null>;
|
|
30
|
+
/** One cycle's samples, [tstart, tend) (an open cycle: to the end), as columns: `Cycle time`, the time and the channels
|
|
31
|
+
* (views of the source's arrays, not copies). */
|
|
32
|
+
export declare function cutCycle(src: SourceColumns, timeKey: string, c: Pick<CycleFound, 'tstart' | 'tend'>): Record<string, Float64Array>;
|
|
33
|
+
/**
|
|
34
|
+
* The cycles table (one row per cycle of `cycleId`: Cycle, Start, End, Duration, each phase's duration, Flags) and the
|
|
35
|
+
* phases table (one row per phase: Cycle, Phase, Start, End, Duration, Flags). `defs`: the definitions, named. A cycle's
|
|
36
|
+
* Flags also list its phases' flags ("Heating late"). Times in the source's seconds; an open cycle has no End.
|
|
37
|
+
*/
|
|
38
|
+
export declare function cycleTables(found: CycleFound[], cycleId: string, defs: Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
parentId: string | null;
|
|
42
|
+
kpis?: KpiSpec[];
|
|
43
|
+
}>, src?: SourceColumns): {
|
|
44
|
+
cycles: DatasetRow[];
|
|
45
|
+
phases: DatasetRow[];
|
|
46
|
+
};
|
|
47
|
+
/** Rows as CSV (the columns of the first row; text quoted when it needs it; null empty). */
|
|
48
|
+
export declare function rowsCsv(rows: DatasetRow[]): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cycle rules v2 (spec docs/superpowers/specs/2026-10-28-cycles-design.md, "Rules"): where a source is cut into cycles,
|
|
3
|
+
* and phases inside them. Boundaries and flags only; KPIs are the app's job. The collector runs the same semantics
|
|
4
|
+
* (packages/collector/src/kymoscope_collector/cycles.py), pinned by tests/vectors/cycle-rules-v2.json. Saved v1 rules
|
|
5
|
+
* (segment_definitions rule_v 1) keep running through ../segments/rules.ts RuleRunner.
|
|
6
|
+
*
|
|
7
|
+
* Semantics:
|
|
8
|
+
* - A start fires when its trigger becomes true (a condition rising, or an event: change, cross, reset, clock,
|
|
9
|
+
* parent-start). With minHold_s, a condition start must stay true that long; the cycle starts when it first held.
|
|
10
|
+
* - An end fires while its trigger is true, from the sample after the start on. With ignoreDips_s, a condition end must
|
|
11
|
+
* stay true that long; the cycle ends when it first held. `next-start`: each start closes the previous cycle.
|
|
12
|
+
* - A NaN value never changes a condition. A phase only sees samples inside its parent and closes with it (incomplete
|
|
13
|
+
* unless its end names parent-end).
|
|
14
|
+
* - Limits never drop a cycle; a missed one adds a flag: late (expectedWithin_s from the parent's or its own start),
|
|
15
|
+
* too-long (max_s), too-short (min_s).
|
|
16
|
+
*/
|
|
17
|
+
import { type CompareOp, type KpiSpec } from '../segments/rules';
|
|
18
|
+
export type Condition = {
|
|
19
|
+
kind: 'compare';
|
|
20
|
+
channel: string;
|
|
21
|
+
op: CompareOp;
|
|
22
|
+
value: number;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'change';
|
|
25
|
+
channel: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'cross';
|
|
28
|
+
channel: string;
|
|
29
|
+
dir: 'rise' | 'fall';
|
|
30
|
+
value: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'reset';
|
|
33
|
+
channel: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'elapsed';
|
|
36
|
+
from: 'parent-start' | 'start';
|
|
37
|
+
op: '>=' | '<=';
|
|
38
|
+
seconds: number;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'clock';
|
|
41
|
+
at: string[];
|
|
42
|
+
tz: string;
|
|
43
|
+
};
|
|
44
|
+
export type Trigger = {
|
|
45
|
+
kind: 'parent-start';
|
|
46
|
+
} | {
|
|
47
|
+
kind: 'parent-end';
|
|
48
|
+
} | {
|
|
49
|
+
kind: 'next-start';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'manual';
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'all' | 'any';
|
|
54
|
+
of: Trigger[];
|
|
55
|
+
} | Condition;
|
|
56
|
+
export interface CycleLimits {
|
|
57
|
+
expectedWithin_s?: number;
|
|
58
|
+
from?: 'parent-start' | 'start';
|
|
59
|
+
min_s?: number;
|
|
60
|
+
max_s?: number;
|
|
61
|
+
}
|
|
62
|
+
export interface CycleRule {
|
|
63
|
+
start: Trigger;
|
|
64
|
+
end: Trigger;
|
|
65
|
+
minHold_s?: number;
|
|
66
|
+
ignoreDips_s?: number;
|
|
67
|
+
limits?: CycleLimits;
|
|
68
|
+
}
|
|
69
|
+
export type CycleFlag = 'late' | 'too-long' | 'too-short';
|
|
70
|
+
export interface CycleInstance {
|
|
71
|
+
tstart: number;
|
|
72
|
+
tend: number | null;
|
|
73
|
+
flags: CycleFlag[];
|
|
74
|
+
incomplete?: true;
|
|
75
|
+
}
|
|
76
|
+
/** Every channel a rule reads. */
|
|
77
|
+
export declare function cycleRuleChannels(r: CycleRule): string[];
|
|
78
|
+
export declare class CycleRunner {
|
|
79
|
+
#private;
|
|
80
|
+
readonly rule: CycleRule;
|
|
81
|
+
constructor(rule: CycleRule);
|
|
82
|
+
/** A phase's parent opened: state starts over. Top-level rules never need it (the first sample is the start). */
|
|
83
|
+
parentStart(t: number): void;
|
|
84
|
+
/** The parent closed at t: an open phase closes with it. */
|
|
85
|
+
parentEnd(t: number, out: CycleInstance[]): void;
|
|
86
|
+
/** One sample, in time order; closed cycles are pushed to `out`. */
|
|
87
|
+
step(t: number, v: (channel: string) => number, out: CycleInstance[]): void;
|
|
88
|
+
/** The open cycle so far (flags up to the last sample), or null. */
|
|
89
|
+
open(): CycleInstance | null;
|
|
90
|
+
}
|
|
91
|
+
export interface RuleDef {
|
|
92
|
+
id: string;
|
|
93
|
+
rule: CycleRule;
|
|
94
|
+
parentId: string | null;
|
|
95
|
+
}
|
|
96
|
+
export interface CycleFound extends CycleInstance {
|
|
97
|
+
definitionId: string;
|
|
98
|
+
index: number;
|
|
99
|
+
parentIndex?: number;
|
|
100
|
+
/** Its definition's KPIs by column name ("TIC201 max"), when asked for (engine.datasets.findCycles). */
|
|
101
|
+
kpis?: Record<string, number | null>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Definitions evaluated together, phases inside their parents. Indexes count per definition from 1 (a phase's within
|
|
105
|
+
* its parent). ponytail: a parent whose start waits for minHold_s opens after the fact, so its phases only see samples
|
|
106
|
+
* from the moment it was confirmed; buffer the held samples if a phase ever needs them.
|
|
107
|
+
*/
|
|
108
|
+
export declare class CycleSet {
|
|
109
|
+
#private;
|
|
110
|
+
constructor(defs: RuleDef[]);
|
|
111
|
+
/** Samples in time order; returns the cycles and phases they closed. `col(name)`: a channel's column aligned with
|
|
112
|
+
* `t` (NaN where it has no value), or undefined. */
|
|
113
|
+
feed(t: ArrayLike<number>, col: (name: string) => ArrayLike<number> | undefined): CycleFound[];
|
|
114
|
+
/** The cycles and phases open now. */
|
|
115
|
+
open(): CycleFound[];
|
|
116
|
+
}
|
|
117
|
+
/** A saved rule: a cycle (parentId null) or a phase inside another definition. `kpis`: a column per KPI in the cycles
|
|
118
|
+
* and phases tables (duration is always there). */
|
|
119
|
+
export interface CycleDefinition extends RuleDef {
|
|
120
|
+
name: string;
|
|
121
|
+
kpis?: KpiSpec[];
|
|
122
|
+
/** The value beside each cycle (in a cut's list): a channel's statistic over the cycle; absent, its duration. */
|
|
123
|
+
show?: KpiSpec;
|
|
124
|
+
}
|
|
125
|
+
/** The definitions in `x` that are well formed (a workbook from anywhere: trust nothing); a phase whose parent is
|
|
126
|
+
* missing is dropped too. */
|
|
127
|
+
export declare function parseCycleDefinitions(x: unknown): CycleDefinition[];
|
|
@@ -15,6 +15,9 @@ export interface EncryptedEnvelope {
|
|
|
15
15
|
iv: string;
|
|
16
16
|
ciphertext: string;
|
|
17
17
|
}
|
|
18
|
+
/** An open stream's password (visibility 'open', spec 2026-10-31-stream-access-model): anyone watches with nothing
|
|
19
|
+
* typed, so its key derives from this published text. Not end-to-end encrypted, by design. Same in Python and plugins. */
|
|
20
|
+
export declare const OPEN_STREAM_PASSWORD = "kymoscope-open-stream-v1";
|
|
18
21
|
/** Random 16-byte KDF salt, base64-encoded. Stored server-side; not secret. */
|
|
19
22
|
export declare function generateSalt(): string;
|
|
20
23
|
/** Derive an AES-256-GCM key from a passphrase + base64 salt via PBKDF2. */
|
|
@@ -48,7 +48,10 @@ export interface PublisherOptions {
|
|
|
48
48
|
name: string;
|
|
49
49
|
kind: 'data' | 'camera';
|
|
50
50
|
/** Where the stream goes is the API key's: a project's key publishes into that project, a personal key a personal stream. */
|
|
51
|
-
visibility?: 'private' | 'public';
|
|
51
|
+
visibility?: 'private' | 'public' | 'open';
|
|
52
|
+
/** The stream password viewers type (spec 2026-10-31-stream-access-model); default: the API key's secret. 'open'
|
|
53
|
+
* streams take none: anyone watches them (a published password; not end-to-end encrypted). */
|
|
54
|
+
password?: string;
|
|
52
55
|
/** The caller's own data stream a camera belongs to (required for kind 'camera'); its placement comes from it. */
|
|
53
56
|
parentStreamId?: string;
|
|
54
57
|
/** A camera's low layer (360p, for small panels and camera walls): `layer: 'low'` with `lowOf`, its high camera's
|
|
@@ -75,6 +78,8 @@ export declare class LivePublisher {
|
|
|
75
78
|
private readonly deps;
|
|
76
79
|
private readonly auth;
|
|
77
80
|
private readonly secret;
|
|
81
|
+
/** What the stream key derives from: the stream password, the open streams' published one, or the API key's secret. */
|
|
82
|
+
private readonly password;
|
|
78
83
|
private token;
|
|
79
84
|
private ticket;
|
|
80
85
|
/** The current ticket says rec (the reply's `recording`); `authRec`: the one the open socket authenticated with. */
|
|
@@ -14,7 +14,7 @@ export interface LiveStreamDescriptor {
|
|
|
14
14
|
/** Encrypted marker proving the password is right before any frame arrives. */
|
|
15
15
|
key_check: EncryptedEnvelope;
|
|
16
16
|
owner_name?: string;
|
|
17
|
-
visibility?: 'private' | 'public' | 'org' | 'project';
|
|
17
|
+
visibility?: 'private' | 'public' | 'org' | 'project' | 'open';
|
|
18
18
|
org_name?: string | null;
|
|
19
19
|
/** Null when the viewer can't see the project's name. */
|
|
20
20
|
project_name?: string | null;
|
|
@@ -65,7 +65,7 @@ export interface LiveConnection {
|
|
|
65
65
|
id: string;
|
|
66
66
|
name: string;
|
|
67
67
|
ownerName: string;
|
|
68
|
-
visibility: 'private' | 'public' | 'org' | 'project';
|
|
68
|
+
visibility: 'private' | 'public' | 'org' | 'project' | 'open';
|
|
69
69
|
orgName: string | null;
|
|
70
70
|
projectName?: string | null;
|
|
71
71
|
kind: 'data' | 'camera';
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { type LiveBuffer } from '../live/buffer';
|
|
12
12
|
import { type AlignAxis } from './align';
|
|
13
13
|
import { type DetectedSegment, type KpiSpec, type SegmentRule } from './rules';
|
|
14
|
+
import { type CycleRule } from '../cycles/evaluator';
|
|
14
15
|
export declare const DELTA_SUFFIX = " \u0394";
|
|
15
16
|
export interface LiveReference {
|
|
16
17
|
grid: Float64Array;
|
|
@@ -39,7 +40,11 @@ export type LiveSegmentEvent = {
|
|
|
39
40
|
channels: string[];
|
|
40
41
|
};
|
|
41
42
|
export interface LiveSegmentAxis {
|
|
42
|
-
rule
|
|
43
|
+
/** A segment rule (v1); unused with `cycle`. */
|
|
44
|
+
rule?: SegmentRule;
|
|
45
|
+
/** A cycle rule (rules v2) instead of `rule` ("Follow the running cycle" on a cycle rule): its cycles open and close
|
|
46
|
+
* the axis; KPIs are not computed live for it. */
|
|
47
|
+
cycle?: CycleRule;
|
|
43
48
|
kpis?: KpiSpec[];
|
|
44
49
|
axis: AlignAxis;
|
|
45
50
|
/** The computed X column written into the ring, and its unit. */
|
|
@@ -48,6 +53,8 @@ export interface LiveSegmentAxis {
|
|
|
48
53
|
/** The open segment's start when the ring may no longer hold it (the collector's open segment), else null. */
|
|
49
54
|
start?: number | null;
|
|
50
55
|
reference?: LiveReference | null;
|
|
56
|
+
/** Also report the cycles already in the ring when it starts (open / close events for them); default: only new ones. */
|
|
57
|
+
replay?: boolean;
|
|
51
58
|
/** A deviation is reported once out of band this long (s); default 5. */
|
|
52
59
|
minDeviationS?: number;
|
|
53
60
|
onEvent?: (e: LiveSegmentEvent) => void;
|
|
@@ -38,6 +38,8 @@ export interface DetectedSegment {
|
|
|
38
38
|
tstart: number;
|
|
39
39
|
tend: number | null;
|
|
40
40
|
kpis: Kpis;
|
|
41
|
+
/** A cycle rule's (v2) missed limits. */
|
|
42
|
+
flags?: Array<'late' | 'too-long' | 'too-short'>;
|
|
41
43
|
}
|
|
42
44
|
export declare const kpiKey: (k: KpiSpec) => string;
|
|
43
45
|
export declare const ruleChannels: (r: SegmentRule) => string[];
|
|
@@ -59,6 +61,18 @@ export declare function localDay(t: number, tz: string): string;
|
|
|
59
61
|
/** The instant of a local wall-clock time (a DST gap resolves forward, an overlap to the first occurrence). */
|
|
60
62
|
export declare function zonedInstant(day: string, hhmm: string, tz: string): number;
|
|
61
63
|
export declare const shiftDay: (day: string, n: number) => string;
|
|
64
|
+
export declare class KpiAcc {
|
|
65
|
+
n: number;
|
|
66
|
+
sum: number;
|
|
67
|
+
min: number;
|
|
68
|
+
max: number;
|
|
69
|
+
first: number;
|
|
70
|
+
last: number;
|
|
71
|
+
integral: number;
|
|
72
|
+
lastT: number;
|
|
73
|
+
add(t: number, v: number): void;
|
|
74
|
+
value(stat: KpiStat): number | null;
|
|
75
|
+
}
|
|
62
76
|
export declare class RuleRunner {
|
|
63
77
|
#private;
|
|
64
78
|
readonly rule: SegmentRule;
|
|
@@ -5,6 +5,7 @@ import type { DashboardPanel, DashboardPanelKind } from '../../components/dashbo
|
|
|
5
5
|
import type { BeaconConfig } from '../beacon';
|
|
6
6
|
import type { MathChannel } from '../mathChannels/types';
|
|
7
7
|
import type { SegmentContext } from '../../engine/types';
|
|
8
|
+
import { type CycleDefinition } from '../cycles/evaluator';
|
|
8
9
|
type ExportedWorksheet = {
|
|
9
10
|
name: string;
|
|
10
11
|
xKey: string;
|
|
@@ -35,6 +36,8 @@ export interface WorkbookBody {
|
|
|
35
36
|
laps: BeaconConfig | null;
|
|
36
37
|
/** The Segments panel's ids (Historian phase 5); absent in older bodies. */
|
|
37
38
|
segments?: SegmentContext | null;
|
|
39
|
+
/** Cycle and phase rules (lib/cycles); absent in older bodies. */
|
|
40
|
+
cycles?: CycleDefinition[];
|
|
38
41
|
}
|
|
39
42
|
/** What `exportWorkspace()` returns (envelope v2 since cloud workbooks). */
|
|
40
43
|
export type WorkspaceExportEnvelope = WorkbookBody;
|
|
@@ -79,6 +82,8 @@ export interface ParsedWorkbook extends ImportResult {
|
|
|
79
82
|
laps: BeaconConfig | null | undefined;
|
|
80
83
|
/** Undefined: a body from before segments (keep the current context). */
|
|
81
84
|
segments: SegmentContext | null | undefined;
|
|
85
|
+
/** Cycle and phase rules; [] in bodies without them. */
|
|
86
|
+
cycles: CycleDefinition[];
|
|
82
87
|
}
|
|
83
88
|
export type ParsedKymoscopeFile = {
|
|
84
89
|
type: 'workspace';
|
|
@@ -106,6 +111,7 @@ export declare function buildWorkbookBody(s: {
|
|
|
106
111
|
liveWindowSeconds: number | null;
|
|
107
112
|
laps: BeaconConfig | null;
|
|
108
113
|
segments?: SegmentContext | null;
|
|
114
|
+
cycles?: CycleDefinition[];
|
|
109
115
|
}): WorkbookBody;
|
|
110
116
|
export declare function buildWorkspaceExportBlob(worksheets: Worksheet[]): Blob;
|
|
111
117
|
export declare function buildWorksheetEnvelope(worksheet: Worksheet): WorksheetExportEnvelope;
|
|
@@ -2,6 +2,7 @@ import type { Dataset, DatasetParseConfig } from '../lib/parseDataset';
|
|
|
2
2
|
import type { DataSource, ColumnSchema } from '../lib/data/dataSource';
|
|
3
3
|
import type { BeaconConfig } from '../lib/beacon';
|
|
4
4
|
import type { DatasetFileInfo } from '../lib/importers/types';
|
|
5
|
+
import type { CycleDefinition, CycleFound } from '../lib/cycles/evaluator';
|
|
5
6
|
export interface LoadedDataset {
|
|
6
7
|
id: string;
|
|
7
8
|
name: string;
|
|
@@ -37,11 +38,25 @@ export interface LoadedDataset {
|
|
|
37
38
|
columnSpares?: Map<string, Float64Array>;
|
|
38
39
|
/** Beacon lap split; when set, the dataset is shown/selected as its laps. */
|
|
39
40
|
beacon?: BeaconConfig;
|
|
41
|
+
/** Cut into cycles by a rule (datasets.cut): shown and selected as its cycles and their phases, as laps are. Wins over `beacon`. */
|
|
42
|
+
cut?: DatasetCut;
|
|
40
43
|
/** Shaded bands under a column's trace (a reference's envelope): per column, low and high edges aligned with the rows. */
|
|
41
44
|
bands?: Record<string, {
|
|
42
45
|
lo: Float64Array;
|
|
43
46
|
hi: Float64Array;
|
|
44
47
|
}>;
|
|
45
48
|
}
|
|
49
|
+
/** A dataset cut into cycles: the rule, what it found, and one dataset per cycle or phase. Never stored: a workbook keeps
|
|
50
|
+
* rules, not cycles (a workbook opens with other data). */
|
|
51
|
+
export interface DatasetCut {
|
|
52
|
+
/** The cycle's definition (`cycleId`) and its phases. */
|
|
53
|
+
defs: CycleDefinition[];
|
|
54
|
+
cycleId: string;
|
|
55
|
+
timeKey: string;
|
|
56
|
+
/** Cycles and phases by start time, with their definition's KPIs. */
|
|
57
|
+
found: CycleFound[];
|
|
58
|
+
/** One per `found` (same order): its samples with `Cycle time` (s since its start); id `<dataset id>::<key>`. */
|
|
59
|
+
parts: LoadedDataset[];
|
|
60
|
+
}
|
|
46
61
|
/** True when the dataset's rows are materialized in memory (small files). */
|
|
47
62
|
export declare function isInMemory(ld: LoadedDataset): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kymoscope/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The Kymoscope data engine and charts for the browser: import large measurement files, plot them, stream live data end-to-end encrypted.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"homepage": "https://kymoscope.com/docs/introduction",
|