ml-time-graph 1.0.0 → 1.0.2

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.
@@ -1,5 +1,81 @@
1
- import { l as DataPoint, A as AggregatedPoint, d as AggregationThresholds, J as Gap, ae as TimeScale, W as LinearScale, _ as Point, p as DrawCommand } from '../scale-Cbr0KpPz.js';
2
- import { S as StatsAggregatedPoint } from '../aggregated_subtypes-DZNZyFTX.js';
1
+ /*!
2
+ * ml-time-analyze Copyright (c) 2026 Michael Lechner
3
+ * MIT with Attribution: free use incl. commercial requires visible credit to
4
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
5
+ */
6
+ /** A single measurement. `value: null` = gap (no reading at this time). */
7
+ interface DataPoint {
8
+ /** Wall-clock time as a millisecond epoch. */
9
+ time: number;
10
+ /** Numeric reading, or `null` to mark a gap (no measurement). */
11
+ value: number | null;
12
+ /** Optional free-text annotation attached to the point. */
13
+ annotation?: string;
14
+ /**
15
+ * Synthetic point — not a real measurement. Set by `SeriesProcessor`
16
+ * on interpolated points (threshold crossings, gap edges) or by users
17
+ * who want their own computed waypoints to be ignored for tooltip
18
+ * snapping and statistics.
19
+ */
20
+ synthetic?: boolean;
21
+ }
22
+ /** A bucketed aggregate (one slot of `aggregateBySlot`). */
23
+ interface AggregatedPoint {
24
+ time: number;
25
+ min: number | null;
26
+ max: number | null;
27
+ avg: number | null;
28
+ /** Number of raw samples that fell into this slot. */
29
+ count: number;
30
+ }
31
+ /** Aggregation grouping. */
32
+ type AggregationMode = "none" | "hourly" | "daily" | "custom";
33
+ interface AggregationConfig {
34
+ mode: AggregationMode;
35
+ /** Slot length in ms — required when `mode === 'custom'`. */
36
+ interval?: number;
37
+ }
38
+ /**
39
+ * A time-range gap detected by `detectGaps`. Minimal shape — start, end
40
+ * and an optional label. The chart package extends this with its own
41
+ * presentation fields (fill, hatch, style) for rendering.
42
+ */
43
+ interface DetectedGap {
44
+ startTime: number;
45
+ endTime: number;
46
+ label?: string;
47
+ }
48
+ /**
49
+ * Optional limits passed to `aggregateBySlot`. When set, the returned
50
+ * slots widen to {@link StatsAggregatedPoint} (MKT + σ + minutes-above /
51
+ * minutes-below).
52
+ */
53
+ interface AggregationThresholds {
54
+ limitLow: number;
55
+ limitHigh: number;
56
+ /** Activation energy for the Arrhenius MKT formula. Default 83 144 J/mol (USP <1079.2>). */
57
+ activationEnergy?: number;
58
+ }
59
+ /** Slot carrying a rolling Mean Kinetic Temperature value (USP <1079.2>). */
60
+ interface MktPoint extends AggregatedPoint {
61
+ mkt: number | null;
62
+ /** Delta vs the previous slot's MKT (optional). */
63
+ deltaMkt?: number | null;
64
+ }
65
+ /** Slot carrying the standard deviation of the samples in the window. */
66
+ interface StdDevPoint extends AggregatedPoint {
67
+ stdDev: number | null;
68
+ }
69
+ /** Slot carrying minutes-above-/below-limit statistics. */
70
+ interface LimitStatsPoint extends AggregatedPoint {
71
+ minutesAboveHigh?: number | null;
72
+ minutesBelowLow?: number | null;
73
+ }
74
+ /**
75
+ * Full-stats slot — what `aggregateBySlot(data, mode, thresholds)` returns
76
+ * when thresholds are provided. Carries MKT, σ, and minutes-out-of-bounds.
77
+ */
78
+ type StatsAggregatedPoint = MktPoint & StdDevPoint & LimitStatsPoint;
3
79
 
4
80
  /*!
5
81
  * MLTimeGraph — Copyright (c) 2026 Michael Lechner
@@ -73,7 +149,7 @@ declare function createAggr(time: number, values: number[]): AggregatedPoint;
73
149
  * Detect gaps in time-series data (autoDetect).
74
150
  * Gibt alle Zeitenrücken zurück wo das Zeitintervall > minGapMs ist.
75
151
  */
76
- declare function detectGaps(data: DataPoint[], minGapMs?: number): Gap[];
152
+ declare function detectGaps(data: DataPoint[], minGapMs?: number): DetectedGap[];
77
153
  interface LongTermInsight {
78
154
  type: "warning" | "info" | "critical";
79
155
  message: string;
@@ -132,49 +208,17 @@ declare class StatsAggregator {
132
208
  /** Compute stats for a specific time range (viewport-scoped) */
133
209
  static computeInRange(data: DataPoint[], startTime: number, endTime: number): StatsResult;
134
210
  }
135
-
136
- /*!
137
- * MLTimeGraph Copyright (c) 2026 Michael Lechner
138
- * MIT with Attribution: free use incl. commercial requires visible credit to
139
- * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
211
+ /**
212
+ * Coefficient of variation (Cv = σ / μ × 100 %). Dimensionless — comparable
213
+ * across devices that operate at very different temperature levels
214
+ * (fridge at 4 °C vs incubator at 37 °C vs room at 20 °C). A ±1 °C drift
215
+ * is huge for the fridge but tiny for the incubator, and Cv captures that.
216
+ *
217
+ * Returns null when `mean` is zero or non-finite (the metric is undefined).
218
+ * The result is the absolute value, so negative means (e.g. freezer
219
+ * profiles like −30 °C) still give a positive percentage.
140
220
  */
141
-
142
- /** Moving average type */
143
- type MovingAvgType = "simple" | "exponential";
144
- interface MovingAvgConfig {
145
- /** Data points */
146
- data: DataPoint[];
147
- /** Window size (number of data points) */
148
- windowSize: number;
149
- /** MA type */
150
- type?: MovingAvgType;
151
- /** Time scale for X-axis */
152
- timeScale: TimeScale;
153
- /** Value scale for Y-axis */
154
- valueScale: LinearScale;
155
- /** Stroke color */
156
- stroke?: string;
157
- /** Stroke width */
158
- strokeWidth?: number;
159
- }
160
- declare class MovingAvg {
161
- #private;
162
- constructor(config: MovingAvgConfig);
163
- /** Compute simple moving average points */
164
- static simple(data: DataPoint[], windowSize: number): {
165
- time: number;
166
- value: number;
167
- }[];
168
- /** Compute exponential moving average (EMA) */
169
- static exponential(data: DataPoint[], windowSize: number): {
170
- time: number;
171
- value: number;
172
- }[];
173
- /** Get computed moving average as pixel points */
174
- points(): Point[];
175
- /** Render MA as a path command */
176
- render(): DrawCommand[];
177
- }
221
+ declare function varianceCoefficient(meanOrStats: number | StatsResult, stdDev?: number): number | null;
178
222
 
179
223
  /*!
180
224
  * MLTimeGraph — Copyright (c) 2026 Michael Lechner
@@ -293,4 +337,186 @@ interface ComputeLimitsOpts {
293
337
  */
294
338
  declare function computeLimitExcursions(data: DataPoint[], opts: ComputeLimitsOpts): LimitStats;
295
339
 
296
- export { type ComputeLimitsOpts, DEFAULT_ACTIVATION_ENERGY, type LimitExcursion, type LimitStats, type LongTermInsight, MovingAvg, type MovingAvgConfig, type MovingAvgType, PRODUCT_PROFILES, type ProductConfig, type ProductType, SeriesProcessor, StatsAggregator, type StatsResult, aggregateBySlot, aggregateWithStats, analyzeLongTermTrends, computeLimitExcursions, createAggr, detectGaps, downsample, mkt, rollingMkt, rollingStdDev, sampleStdDev, stdDev };
340
+ /*!
341
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
342
+ * MIT with Attribution: free use incl. commercial requires visible credit to
343
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
344
+ */
345
+
346
+ /** A named series for spatial analysis — a single sensor in the room. */
347
+ interface NamedSeries {
348
+ /** Stable identifier (e.g. "S_FLOOR", "S_CEILING"). */
349
+ name: string;
350
+ /** Time-ordered samples. `value: null` is treated as a gap and ignored. */
351
+ data: DataPoint[];
352
+ }
353
+ /**
354
+ * Per-timestamp summary of a multi-sensor recording.
355
+ * `delta = max - min` is the room's "spatial spread" at that instant.
356
+ */
357
+ interface SpatialPoint {
358
+ time: number;
359
+ /** Lowest reading across all sensors at this timestamp. */
360
+ min: number | null;
361
+ /** Highest reading across all sensors at this timestamp. */
362
+ max: number | null;
363
+ /** `max - min`; null if fewer than two valid readings at this timestamp. */
364
+ delta: number | null;
365
+ /** Name of the sensor that supplied `min`. */
366
+ minSensor: string | null;
367
+ /** Name of the sensor that supplied `max`. */
368
+ maxSensor: string | null;
369
+ }
370
+ /**
371
+ * Compute the spatial spread per timestamp across a set of sensor series.
372
+ * Returns one {@link SpatialPoint} per unique timestamp found across all
373
+ * inputs, in ascending time order.
374
+ *
375
+ * Sensors are matched by timestamp (no interpolation). For each timestamp
376
+ * we collect every non-null sample at that exact time and emit min, max
377
+ * and their delta — plus which sensor produced each extremum (handy for
378
+ * UI badges like "Hotspot: S_CEILING at 14:32").
379
+ *
380
+ * The caller is responsible for upstream alignment (same sample cadence
381
+ * across sensors). If a sensor has no sample at a given timestamp it is
382
+ * silently skipped for that point.
383
+ */
384
+ declare function spatialDelta(sensors: NamedSeries[]): SpatialPoint[];
385
+ /** Aggregate statistics for one sensor across the full observation window. */
386
+ interface SensorStat {
387
+ name: string;
388
+ mean: number;
389
+ min: number;
390
+ max: number;
391
+ /** Sample count used (null values skipped). */
392
+ count: number;
393
+ }
394
+ /** Result of {@link hotColdSpots}. */
395
+ interface HotColdSpotsResult {
396
+ /** Per-sensor stats in input order. Sensors with no valid samples are omitted. */
397
+ sensors: SensorStat[];
398
+ /** Sensor with the highest mean. `null` if no sensor had any samples. */
399
+ hottest: SensorStat | null;
400
+ /** Sensor with the lowest mean. */
401
+ coldest: SensorStat | null;
402
+ /** `hottest.mean - coldest.mean` — the long-term stratification of the room. */
403
+ meanDelta: number | null;
404
+ }
405
+ /**
406
+ * Find the hottest and coldest sensor over an extended period — the
407
+ * "stratification" of a room. Each sensor's mean is computed across its
408
+ * own samples (null values skipped); the function then picks the highest-
409
+ * and lowest-mean sensor.
410
+ *
411
+ * Use for long-window (days / weeks) reports to identify positions where
412
+ * sensitive products should NOT be stored. For short-term spread use
413
+ * {@link spatialDelta} instead.
414
+ */
415
+ declare function hotColdSpots(sensors: NamedSeries[]): HotColdSpotsResult;
416
+
417
+ /*!
418
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
419
+ * MIT with Attribution: free use incl. commercial requires visible credit to
420
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
421
+ */
422
+
423
+ /** Result of a single linear-regression fit. */
424
+ interface TrendFit {
425
+ /** Slope in value-units per **millisecond** (multiply by 60000 for per-minute). */
426
+ slope: number;
427
+ /** Y-intercept of the fit line (value at time=0 epoch). */
428
+ intercept: number;
429
+ /** Number of (non-null) points used. */
430
+ n: number;
431
+ }
432
+ /** One rolling-trend output sample. */
433
+ interface TrendPoint {
434
+ /** Center time of the window — the input point's own timestamp. */
435
+ time: number;
436
+ /** Trend fit for the window ending at `time`, or null if too few points. */
437
+ fit: TrendFit | null;
438
+ }
439
+ /**
440
+ * Linear regression (`y = m·x + b`) over an arbitrary point set.
441
+ * `value: null` is skipped. Returns null when fewer than 2 valid points
442
+ * remain or when all timestamps are identical (vertical line).
443
+ */
444
+ declare function linearFit(points: DataPoint[]): TrendFit | null;
445
+ /**
446
+ * Trend over the most recent `lookbackMs` of the series — useful as a
447
+ * "current rate of change" snapshot. Returns null when the last sample is
448
+ * null or fewer than 2 valid points fall inside the lookback window.
449
+ */
450
+ declare function currentTrend(data: DataPoint[], lookbackMs: number): TrendFit | null;
451
+ /**
452
+ * For each input point, emit a {@link TrendPoint} containing the linear fit
453
+ * of all points in `[time - windowMs, time]`. Useful for plotting "rate of
454
+ * change" as a secondary series, or for detecting when the slope itself
455
+ * starts to climb.
456
+ *
457
+ * `data` should be sorted by time ascending. Output length equals input
458
+ * length.
459
+ */
460
+ declare function rollingTrend(data: DataPoint[], windowMs: number): TrendPoint[];
461
+ /** Options for {@link predictTimeToThreshold}. */
462
+ interface PredictOpts {
463
+ /** Window for the trend fit. Default 15 min. */
464
+ lookbackMs?: number;
465
+ /**
466
+ * Which crossing to predict:
467
+ * - 'above' = predict when value will rise above `threshold`
468
+ * - 'below' = predict when value will fall below `threshold`
469
+ * - 'either' (default) = either direction, whichever the trend points to.
470
+ */
471
+ side?: "above" | "below" | "either";
472
+ }
473
+ /** Result of {@link predictTimeToThreshold}. */
474
+ interface PredictResult {
475
+ /** Milliseconds from `data[last].time` until the threshold is crossed. */
476
+ msUntil: number;
477
+ /** Predicted crossing wall-clock time (ms epoch). */
478
+ eta: number;
479
+ /** Trend fit used for the extrapolation. */
480
+ fit: TrendFit;
481
+ /** Current value (the last non-null sample). */
482
+ currentValue: number;
483
+ }
484
+ /**
485
+ * Predict — assuming the current trend continues unchanged — how long until
486
+ * the series crosses `threshold`. Returns null when the trend is flat,
487
+ * heading the wrong way, or no usable data exists.
488
+ *
489
+ * Designed for the predictive-alarm pattern: fire a warning while the value
490
+ * is still in spec but trending toward a hard limit, e.g. compressor
491
+ * weakness on a fridge or a slow-leak excursion in a pharma room.
492
+ */
493
+ declare function predictTimeToThreshold(data: DataPoint[], threshold: number, opts?: PredictOpts): PredictResult | null;
494
+
495
+ /*!
496
+ * MLTimeGraph — Copyright (c) 2026 Michael Lechner
497
+ * MIT with Attribution: free use incl. commercial requires visible credit to
498
+ * "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
499
+ */
500
+
501
+ /** Options for {@link f0Sterilization}. */
502
+ interface F0Opts {
503
+ /** Reference temperature in °C. Default 121.11 °C (USP <1229>). */
504
+ refTempCelsius?: number;
505
+ /** z-value (resistance constant) in Kelvin. Default 10 K (B. stearothermophilus). */
506
+ zValueKelvin?: number;
507
+ }
508
+ /**
509
+ * Compute the F₀ value (in minutes) of a heat-treatment temperature record
510
+ * via trapezoidal integration of the lethality rate
511
+ * `L(T) = 10^((T − T_ref) / z)`.
512
+ *
513
+ * `data` must be sorted by `time` ascending. `value: null` breaks the
514
+ * integration — adjacent valid points around the gap are skipped (the gap
515
+ * contributes nothing to F₀, which is conservative).
516
+ *
517
+ * Returns 0 for empty input or a single point. Negative or non-finite
518
+ * temperatures produce a negligible contribution (L → 0).
519
+ */
520
+ declare function f0Sterilization(data: DataPoint[], opts?: F0Opts): number;
521
+
522
+ export { type AggregatedPoint, type AggregationConfig, type AggregationMode, type AggregationThresholds, type ComputeLimitsOpts, DEFAULT_ACTIVATION_ENERGY, type DataPoint, type F0Opts, type HotColdSpotsResult, type LimitExcursion, type LimitStats, type LimitStatsPoint, type LongTermInsight, type MktPoint, type NamedSeries, PRODUCT_PROFILES, type PredictOpts, type PredictResult, type ProductConfig, type ProductType, type SensorStat, SeriesProcessor, type SpatialPoint, type StatsAggregatedPoint, StatsAggregator, type StatsResult, type StdDevPoint, type TrendFit, type TrendPoint, aggregateBySlot, aggregateWithStats, analyzeLongTermTrends, computeLimitExcursions, createAggr, currentTrend, detectGaps, downsample, f0Sterilization, hotColdSpots, linearFit, mkt, predictTimeToThreshold, rollingMkt, rollingStdDev, rollingTrend, sampleStdDev, spatialDelta, stdDev, varianceCoefficient };