@camstack/types 1.2.421 → 1.2.423

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.
Files changed (40) hide show
  1. package/dist/addon.js +1 -1
  2. package/dist/addon.mjs +1 -1
  3. package/dist/capabilities/composer.cap.d.ts +129 -57
  4. package/dist/capabilities/core-blocks.cap.d.ts +936 -410
  5. package/dist/capabilities/index.d.ts +3 -1
  6. package/dist/capabilities/meter.cap.d.ts +53 -0
  7. package/dist/capabilities/notification-rules.cap.d.ts +12 -0
  8. package/dist/client.js +17 -1
  9. package/dist/client.mjs +17 -1
  10. package/dist/composition/composition-report.d.ts +45 -0
  11. package/dist/composition/composition.d.ts +196 -19
  12. package/dist/composition/helpers/compile-history.d.ts +13 -0
  13. package/dist/composition/helpers/compile.d.ts +2 -1
  14. package/dist/composition/helpers/energy.d.ts +8 -0
  15. package/dist/composition/helpers/history-math.d.ts +39 -0
  16. package/dist/composition/helpers/history-window.d.ts +71 -0
  17. package/dist/composition/helpers/index.d.ts +6 -2
  18. package/dist/composition/helpers/source-pick.d.ts +2 -0
  19. package/dist/composition/helpers/spec.d.ts +23 -10
  20. package/dist/{composition-B3Hkb6v7.js → composition-CPRAP41h.js} +792 -8
  21. package/dist/{composition-B2rw5L29.mjs → composition-CpETZ4HL.mjs} +595 -9
  22. package/dist/device/cap-vocabulary/entries/sensors.d.ts +1 -1
  23. package/dist/generated/addon-api.d.ts +14 -0
  24. package/dist/generated/addon-cap-facts.d.ts +1 -1
  25. package/dist/generated/cap-status-types.d.ts +3 -1
  26. package/dist/generated/capability-router-map.d.ts +5 -2
  27. package/dist/generated/device-local-state.d.ts +3 -0
  28. package/dist/generated/device-proxy.d.ts +3 -0
  29. package/dist/generated/device-scoped-caps.d.ts +1 -1
  30. package/dist/generated/history-catalog.d.ts +1 -1
  31. package/dist/generated/method-access-map.d.ts +1 -1
  32. package/dist/generated/method-device-selectors.d.ts +1 -1
  33. package/dist/generated/runtime-state-durability.d.ts +1 -1
  34. package/dist/index.js +761 -447
  35. package/dist/index.mjs +723 -448
  36. package/dist/node.js +1 -1
  37. package/dist/node.mjs +1 -1
  38. package/dist/{sleep-bykKrTgu.mjs → sleep-0sD_LHLI.mjs} +10 -1
  39. package/dist/{sleep-YJ6FVsUu.js → sleep-CEKGW7A5.js} +10 -1
  40. package/package.json +1 -1
@@ -2232,6 +2232,544 @@ function sourcePickForAppliance() {
2232
2232
  };
2233
2233
  }
2234
2234
  //#endregion
2235
+ //#region src/composition/helpers/energy.ts
2236
+ /**
2237
+ * Watts integrated over time, in kWh (D944).
2238
+ *
2239
+ * 1 kWh = 1 000 W × 3 600 000 ms. The history energy series uses the same
2240
+ * factor (`tw_sum / 3.6e9`), so a chart and an integral helper cannot diverge.
2241
+ */
2242
+ var KWH_PER_WATT_MS = 1 / 36e8;
2243
+ function wattMillisecondsToKwh(wattMilliseconds) {
2244
+ return wattMilliseconds * KWH_PER_WATT_MS;
2245
+ }
2246
+ //#endregion
2247
+ //#region src/composition/helpers/history-math.ts
2248
+ /**
2249
+ * Statistics and integrals over a history read (D944).
2250
+ *
2251
+ * Pure: the same points always give the same answer, so a restart recomputes
2252
+ * rather than trusting a buffer. A gauge mean is time-weighted (value × ms),
2253
+ * never the mean of the samples. A hole with no held value is not integrated
2254
+ * as zero. A truncated read is not published.
2255
+ */
2256
+ var STATISTIC_STATS = [
2257
+ "min",
2258
+ "max",
2259
+ "mean",
2260
+ "sum",
2261
+ "count",
2262
+ "change"
2263
+ ];
2264
+ var SERIES_KINDS = [
2265
+ "gauge",
2266
+ "counter",
2267
+ "boolean"
2268
+ ];
2269
+ function finite(value) {
2270
+ return typeof value === "number" && Number.isFinite(value);
2271
+ }
2272
+ function pointValue(point) {
2273
+ if (finite(point.value)) return point.value;
2274
+ if (finite(point.last)) return point.last;
2275
+ return null;
2276
+ }
2277
+ function stepCoverage(read) {
2278
+ const points = read.points.filter((point) => point.t >= read.from && point.t < read.to);
2279
+ const start = finite(read.before?.value) ? read.before.value : null;
2280
+ let held = start;
2281
+ let cursor = read.from;
2282
+ let gaps = 0;
2283
+ const segments = [];
2284
+ if (held === null) {
2285
+ const first = points.find((point) => pointValue(point) !== null);
2286
+ if (first === void 0) return {
2287
+ segments,
2288
+ included: 0,
2289
+ gaps: 1,
2290
+ end: null,
2291
+ start: null
2292
+ };
2293
+ if (first.t > read.from) gaps += 1;
2294
+ cursor = first.t;
2295
+ held = pointValue(first);
2296
+ }
2297
+ for (const point of points) {
2298
+ const next = pointValue(point);
2299
+ if (held !== null && point.t > cursor) segments.push({
2300
+ value: held,
2301
+ ms: point.t - cursor
2302
+ });
2303
+ if (next !== null) held = next;
2304
+ if (point.t > cursor) cursor = point.t;
2305
+ }
2306
+ if (held !== null && read.to > cursor) segments.push({
2307
+ value: held,
2308
+ ms: read.to - cursor
2309
+ });
2310
+ const end = points.reduce((latest, point) => pointValue(point) ?? latest, held);
2311
+ return {
2312
+ segments,
2313
+ included: points.length,
2314
+ gaps,
2315
+ end,
2316
+ start
2317
+ };
2318
+ }
2319
+ function rollupCoverage(read) {
2320
+ const weighted = read.points.filter((point) => finite(point.twMean) && finite(point.twMs) && point.twMs > 0);
2321
+ if (weighted.length === 0) return null;
2322
+ const first = weighted[0];
2323
+ const gaps = read.before === null && first !== void 0 && first.t > read.from ? 1 : 0;
2324
+ const levels = read.points.flatMap((point) => finite(point.last) ? [point.last] : []);
2325
+ const end = levels.length > 0 ? levels[levels.length - 1] ?? null : null;
2326
+ return {
2327
+ segments: weighted.flatMap((point) => finite(point.twMean) && finite(point.twMs) ? [{
2328
+ value: point.twMean,
2329
+ ms: point.twMs
2330
+ }] : []),
2331
+ included: weighted.length,
2332
+ gaps,
2333
+ end,
2334
+ start: finite(read.before?.value) ? read.before.value : null
2335
+ };
2336
+ }
2337
+ function coverageOf(read) {
2338
+ if (read.tier !== "raw") {
2339
+ const rolled = rollupCoverage(read);
2340
+ if (rolled !== null) return rolled;
2341
+ }
2342
+ return stepCoverage(read);
2343
+ }
2344
+ function timeWeighted(segments) {
2345
+ let ms = 0;
2346
+ let sum = 0;
2347
+ for (const segment of segments) {
2348
+ ms += segment.ms;
2349
+ sum += segment.value * segment.ms;
2350
+ }
2351
+ if (ms <= 0) return null;
2352
+ return sum / ms;
2353
+ }
2354
+ function extremes(read, covered) {
2355
+ const lows = [];
2356
+ const highs = [];
2357
+ for (const point of read.points) {
2358
+ if (finite(point.min)) lows.push(point.min);
2359
+ if (finite(point.max)) highs.push(point.max);
2360
+ }
2361
+ if (covered.start !== null) {
2362
+ lows.push(covered.start);
2363
+ highs.push(covered.start);
2364
+ }
2365
+ return {
2366
+ min: lows.length === 0 ? null : Math.min(...lows),
2367
+ max: highs.length === 0 ? null : Math.max(...highs)
2368
+ };
2369
+ }
2370
+ function counterGrowth(read) {
2371
+ const deltas = read.points.flatMap((point) => finite(point.delta) ? [point.delta] : []);
2372
+ if (deltas.length === 0) return null;
2373
+ return deltas.reduce((sum, delta) => sum + delta, 0);
2374
+ }
2375
+ function hold(reason) {
2376
+ return {
2377
+ write: "hold",
2378
+ value: null,
2379
+ included: 0,
2380
+ excluded: 0,
2381
+ reason
2382
+ };
2383
+ }
2384
+ function gapValue(reason, included) {
2385
+ return {
2386
+ write: "null",
2387
+ value: null,
2388
+ included,
2389
+ excluded: 1,
2390
+ reason
2391
+ };
2392
+ }
2393
+ function numberValue(value, included, gaps, reason) {
2394
+ return {
2395
+ write: "number",
2396
+ value,
2397
+ included,
2398
+ excluded: gaps,
2399
+ reason: gaps > 0 ? reason : null
2400
+ };
2401
+ }
2402
+ var GAP_REASON = "history has a gap — that interval was not counted as zero";
2403
+ function evaluateStatistic(stat, read) {
2404
+ if (read.truncated) return hold("the history query was truncated — the last statistic is kept");
2405
+ if (stat === "sum" && read.kind !== "counter") return hold("a gauge has no sum — that is the integral template");
2406
+ if (read.to <= read.from) return gapValue("the range is empty", 0);
2407
+ const covered = coverageOf(read);
2408
+ if (covered.segments.length === 0 && covered.start === null) return gapValue(GAP_REASON, covered.included);
2409
+ if (stat === "count") return numberValue(covered.included, covered.included, covered.gaps, GAP_REASON);
2410
+ if (stat === "sum" || stat === "change" && read.kind === "counter") {
2411
+ const growth = counterGrowth(read);
2412
+ if (growth === null) return gapValue("the counter has no growth in this range", covered.included);
2413
+ return numberValue(growth, covered.included, covered.gaps, GAP_REASON);
2414
+ }
2415
+ if (stat === "change") {
2416
+ if (covered.start === null || covered.end === null) return gapValue("the change has no value at the start of the range", covered.included);
2417
+ return numberValue(covered.end - covered.start, covered.included, covered.gaps, GAP_REASON);
2418
+ }
2419
+ if (stat === "min" || stat === "max") {
2420
+ const ends = extremes(read, covered);
2421
+ const value = stat === "min" ? ends.min : ends.max;
2422
+ if (value === null) return gapValue(GAP_REASON, covered.included);
2423
+ return numberValue(value, covered.included, covered.gaps, GAP_REASON);
2424
+ }
2425
+ const mean = timeWeighted(covered.segments);
2426
+ if (mean === null) return gapValue(GAP_REASON, covered.included);
2427
+ return numberValue(mean, covered.included, covered.gaps, GAP_REASON);
2428
+ }
2429
+ function integrateReading(read, scale) {
2430
+ if (read.truncated) return hold("the history query was truncated — the last total is kept");
2431
+ if (read.kind === "counter") return hold("a counter is already a total — it is not integrated again");
2432
+ if (read.to <= read.from) return gapValue("the range is empty", 0);
2433
+ const covered = coverageOf(read);
2434
+ if (covered.segments.length === 0) return {
2435
+ write: "hold",
2436
+ value: null,
2437
+ included: covered.included,
2438
+ excluded: covered.gaps,
2439
+ reason: GAP_REASON
2440
+ };
2441
+ let unitMs = 0;
2442
+ for (const segment of covered.segments) unitMs += segment.value * segment.ms;
2443
+ return numberValue(scale === "energy-kwh" ? wattMillisecondsToKwh(unitMs) : unitMs / 1e3, covered.included, covered.gaps, GAP_REASON);
2444
+ }
2445
+ //#endregion
2446
+ //#region src/composition/helpers/history-window.ts
2447
+ /**
2448
+ * The range a statistics or integral helper asks history for (D944).
2449
+ *
2450
+ * Calendar edges are the hub's timezone, never UTC midnight: a day is 23, 24
2451
+ * or 25 hours across a DST change. A tier is eligible only when its retention
2452
+ * is strictly longer than the longest that range can be. Forever qualifies.
2453
+ * Equal to a tier's retention is a refusal, not a short answer.
2454
+ */
2455
+ var MINUTE_MS = 6e4;
2456
+ var HOUR_MS = 60 * MINUTE_MS;
2457
+ var DAY_MS = 24 * HOUR_MS;
2458
+ var ROLLING_SPANS = [
2459
+ "1h",
2460
+ "24h",
2461
+ "2d",
2462
+ "7d",
2463
+ "30d",
2464
+ "custom"
2465
+ ];
2466
+ var CALENDAR_PERIODS = [
2467
+ "today",
2468
+ "yesterday",
2469
+ "this-week",
2470
+ "this-month"
2471
+ ];
2472
+ var HISTORY_TIERS = [
2473
+ "raw",
2474
+ "5m",
2475
+ "1h",
2476
+ "1d"
2477
+ ];
2478
+ var RESET_PERIODS = [
2479
+ "never",
2480
+ "daily",
2481
+ "weekly",
2482
+ "monthly"
2483
+ ];
2484
+ var MIN_CUSTOM_MINUTES = 5;
2485
+ /** Faster than this is not a history query. Rolling windows at most this often. */
2486
+ var HISTORY_FAST_MS = 6e4;
2487
+ var HISTORY_SLOW_MS = 5 * MINUTE_MS;
2488
+ var ROLLING_MS = {
2489
+ "1h": HOUR_MS,
2490
+ "24h": 24 * HOUR_MS,
2491
+ "2d": 2 * DAY_MS,
2492
+ "7d": 7 * DAY_MS,
2493
+ "30d": 30 * DAY_MS
2494
+ };
2495
+ /** The device-history defaults: raw 7 d, 5 m 30 d, 1 h 365 d, 1 d forever. */
2496
+ var DEFAULT_HISTORY_RETENTION = {
2497
+ rawMs: 7 * DAY_MS,
2498
+ fiveMinuteMs: 30 * DAY_MS,
2499
+ hourMs: 365 * DAY_MS,
2500
+ dayMs: null
2501
+ };
2502
+ var rollingWindowObject = zod.z.object({
2503
+ kind: zod.z.literal("rolling"),
2504
+ span: zod.z.enum(ROLLING_SPANS),
2505
+ customMinutes: zod.z.number().int().min(5).optional()
2506
+ }).check((payload) => {
2507
+ if (payload.value.span === "custom" && payload.value.customMinutes === void 0) payload.issues.push({
2508
+ code: "custom",
2509
+ message: "a custom range needs a duration in minutes",
2510
+ path: ["customMinutes"],
2511
+ input: payload.value
2512
+ });
2513
+ if (payload.value.span !== "custom" && payload.value.customMinutes !== void 0) payload.issues.push({
2514
+ code: "custom",
2515
+ message: "only a custom range carries a duration",
2516
+ path: ["customMinutes"],
2517
+ input: payload.value
2518
+ });
2519
+ });
2520
+ var RollingWindowSchema = rollingWindowObject;
2521
+ var calendarWindowObject = zod.z.object({
2522
+ kind: zod.z.literal("calendar"),
2523
+ period: zod.z.enum(CALENDAR_PERIODS)
2524
+ });
2525
+ var CalendarWindowSchema = calendarWindowObject;
2526
+ var StatisticWindowSchema = zod.z.discriminatedUnion("kind", [rollingWindowObject, calendarWindowObject]);
2527
+ var WEEKDAY_INDEX = {
2528
+ Sun: 0,
2529
+ Mon: 1,
2530
+ Tue: 2,
2531
+ Wed: 3,
2532
+ Thu: 4,
2533
+ Fri: 5,
2534
+ Sat: 6
2535
+ };
2536
+ var FORMATTERS = /* @__PURE__ */ new Map();
2537
+ function formatterFor(timeZone) {
2538
+ const held = FORMATTERS.get(timeZone);
2539
+ if (held !== void 0) return held;
2540
+ let made;
2541
+ try {
2542
+ made = new Intl.DateTimeFormat("en-US", {
2543
+ timeZone,
2544
+ weekday: "short",
2545
+ year: "numeric",
2546
+ month: "2-digit",
2547
+ day: "2-digit",
2548
+ hour: "2-digit",
2549
+ minute: "2-digit",
2550
+ second: "2-digit",
2551
+ hourCycle: "h23"
2552
+ });
2553
+ } catch {
2554
+ made = null;
2555
+ }
2556
+ FORMATTERS.set(timeZone, made);
2557
+ return made;
2558
+ }
2559
+ function zonedClock(ms, timeZone) {
2560
+ const formatter = formatterFor(timeZone);
2561
+ if (formatter === null) return null;
2562
+ const parts = formatter.formatToParts(new Date(ms));
2563
+ const read = (type) => parts.find((part) => part.type === type)?.value;
2564
+ const year = Number(read("year"));
2565
+ const month = Number(read("month"));
2566
+ const day = Number(read("day"));
2567
+ const hourRaw = Number(read("hour"));
2568
+ const minute = Number(read("minute"));
2569
+ const second = Number(read("second"));
2570
+ const weekday = WEEKDAY_INDEX[read("weekday") ?? ""];
2571
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day) || !Number.isInteger(hourRaw) || !Number.isInteger(minute) || !Number.isInteger(second) || weekday === void 0) return null;
2572
+ return {
2573
+ year,
2574
+ month,
2575
+ day,
2576
+ hour: hourRaw === 24 ? 0 : hourRaw,
2577
+ minute,
2578
+ second,
2579
+ weekday
2580
+ };
2581
+ }
2582
+ function utcOf(clock, timeZone) {
2583
+ const guess = Date.UTC(clock.year, clock.month - 1, clock.day, clock.hour, clock.minute, clock.second);
2584
+ const first = zonedClock(guess, timeZone);
2585
+ if (first === null) return null;
2586
+ const offset = Date.UTC(first.year, first.month - 1, first.day, first.hour, first.minute, first.second) - guess;
2587
+ const shifted = guess - offset;
2588
+ const second = zonedClock(shifted, timeZone);
2589
+ if (second === null) return null;
2590
+ const offset2 = Date.UTC(second.year, second.month - 1, second.day, second.hour, second.minute, second.second) - shifted;
2591
+ return offset2 === offset ? shifted : guess - offset2;
2592
+ }
2593
+ function midnightOf(ms, timeZone) {
2594
+ const clock = zonedClock(ms, timeZone);
2595
+ if (clock === null) return null;
2596
+ return utcOf({
2597
+ ...clock,
2598
+ hour: 0,
2599
+ minute: 0,
2600
+ second: 0
2601
+ }, timeZone);
2602
+ }
2603
+ function monthStart(ms, timeZone) {
2604
+ const clock = zonedClock(ms, timeZone);
2605
+ if (clock === null) return null;
2606
+ return utcOf({
2607
+ ...clock,
2608
+ day: 1,
2609
+ hour: 0,
2610
+ minute: 0,
2611
+ second: 0
2612
+ }, timeZone);
2613
+ }
2614
+ function weekStart(ms, timeZone) {
2615
+ const clock = zonedClock(ms, timeZone);
2616
+ let cursor = midnightOf(ms, timeZone);
2617
+ if (clock === null || cursor === null) return null;
2618
+ const back = clock.weekday === 0 ? 6 : clock.weekday - 1;
2619
+ for (let step = 0; step < back; step++) {
2620
+ const previous = midnightOf(cursor - 1, timeZone);
2621
+ if (previous === null) return null;
2622
+ cursor = previous;
2623
+ }
2624
+ return cursor;
2625
+ }
2626
+ function keptLabel(ms) {
2627
+ if (ms === null) return "forever";
2628
+ if (ms % 864e5 === 0) return `${ms / DAY_MS} d`;
2629
+ if (ms % 36e5 === 0) return `${ms / HOUR_MS} h`;
2630
+ return `${ms / MINUTE_MS} min`;
2631
+ }
2632
+ function retentionSentence(retention) {
2633
+ return `raw ${keptLabel(retention.rawMs)}, 5m ${keptLabel(retention.fiveMinuteMs)}, 1h ${keptLabel(retention.hourMs)}, 1d ${keptLabel(retention.dayMs)}`;
2634
+ }
2635
+ function tierFor(durationMs, retention) {
2636
+ const rows = [
2637
+ {
2638
+ tier: "raw",
2639
+ keptMs: retention.rawMs
2640
+ },
2641
+ {
2642
+ tier: "5m",
2643
+ keptMs: retention.fiveMinuteMs
2644
+ },
2645
+ {
2646
+ tier: "1h",
2647
+ keptMs: retention.hourMs
2648
+ },
2649
+ {
2650
+ tier: "1d",
2651
+ keptMs: retention.dayMs
2652
+ }
2653
+ ];
2654
+ for (const row of rows) if (row.keptMs === null || row.keptMs > durationMs) return {
2655
+ ok: true,
2656
+ tier: row.tier,
2657
+ maxDurationMs: durationMs
2658
+ };
2659
+ return {
2660
+ ok: false,
2661
+ reason: `the range is ${keptLabel(durationMs)} and no history tier is kept strictly longer (${retentionSentence(retention)})`
2662
+ };
2663
+ }
2664
+ function rollingDurationMs(window) {
2665
+ if (window.span === "custom") return window.customMinutes === void 0 ? null : window.customMinutes * MINUTE_MS;
2666
+ return ROLLING_MS[window.span];
2667
+ }
2668
+ /** Longest the window can be. Calendar periods use their full length, not the elapsed part. */
2669
+ function maxDurationMs(window) {
2670
+ if (window.kind === "rolling") return rollingDurationMs(window);
2671
+ switch (window.period) {
2672
+ case "today":
2673
+ case "yesterday": return 25 * HOUR_MS;
2674
+ case "this-week": return 6084e5;
2675
+ case "this-month": return 2682e6;
2676
+ }
2677
+ }
2678
+ function planStatisticTier(window, retention) {
2679
+ const duration = maxDurationMs(window);
2680
+ if (duration === null) return {
2681
+ ok: false,
2682
+ reason: "a custom range needs a duration of at least 5 minutes"
2683
+ };
2684
+ return tierFor(duration, retention);
2685
+ }
2686
+ function planResetTier(reset, retention) {
2687
+ switch (reset) {
2688
+ case "never": return tierFor(Number.POSITIVE_INFINITY, retention);
2689
+ case "daily": return tierFor(25 * HOUR_MS, retention);
2690
+ case "weekly": return tierFor(6084e5, retention);
2691
+ case "monthly": return tierFor(2682e6, retention);
2692
+ }
2693
+ }
2694
+ function openStatistic(window, now, timeZone) {
2695
+ if (window.kind === "rolling") {
2696
+ const duration = rollingDurationMs(window);
2697
+ if (duration === null) return null;
2698
+ return {
2699
+ from: now - duration,
2700
+ to: now
2701
+ };
2702
+ }
2703
+ const today = midnightOf(now, timeZone);
2704
+ if (today === null) return null;
2705
+ switch (window.period) {
2706
+ case "today": return {
2707
+ from: today,
2708
+ to: now
2709
+ };
2710
+ case "yesterday": {
2711
+ const start = midnightOf(today - 1, timeZone);
2712
+ return start === null ? null : {
2713
+ from: start,
2714
+ to: today
2715
+ };
2716
+ }
2717
+ case "this-week": {
2718
+ const start = weekStart(now, timeZone);
2719
+ return start === null ? null : {
2720
+ from: start,
2721
+ to: now
2722
+ };
2723
+ }
2724
+ case "this-month": {
2725
+ const start = monthStart(now, timeZone);
2726
+ return start === null ? null : {
2727
+ from: start,
2728
+ to: now
2729
+ };
2730
+ }
2731
+ }
2732
+ }
2733
+ function openReset(reset, now, timeZone) {
2734
+ if (reset === "never") return {
2735
+ from: 0,
2736
+ to: now
2737
+ };
2738
+ const today = midnightOf(now, timeZone);
2739
+ if (today === null) return null;
2740
+ if (reset === "daily") return {
2741
+ from: today,
2742
+ to: now
2743
+ };
2744
+ if (reset === "weekly") {
2745
+ const start = weekStart(now, timeZone);
2746
+ return start === null ? null : {
2747
+ from: start,
2748
+ to: now
2749
+ };
2750
+ }
2751
+ const start = monthStart(now, timeZone);
2752
+ return start === null ? null : {
2753
+ from: start,
2754
+ to: now
2755
+ };
2756
+ }
2757
+ /** The next hub-local midnight, strictly after `now`. Monday is one of these. */
2758
+ function nextLocalMidnight(now, timeZone) {
2759
+ const today = midnightOf(now, timeZone);
2760
+ if (today === null) return null;
2761
+ return midnightOf(today + 26 * HOUR_MS, timeZone);
2762
+ }
2763
+ function statisticCadenceMs(window) {
2764
+ if (window.kind === "calendar") return window.period === "today" ? HISTORY_FAST_MS : HISTORY_SLOW_MS;
2765
+ const duration = rollingDurationMs(window);
2766
+ if (duration !== null && duration <= 24 * 36e5) return HISTORY_FAST_MS;
2767
+ return HISTORY_SLOW_MS;
2768
+ }
2769
+ function integralCadenceMs(tier) {
2770
+ return tier === "raw" ? HISTORY_FAST_MS : HISTORY_SLOW_MS;
2771
+ }
2772
+ //#endregion
2235
2773
  //#region src/composition/source-ref.ts
2236
2774
  /**
2237
2775
  * A composition source identity: `(addonId, stableId)`, never a numeric device
@@ -2297,10 +2835,7 @@ var HelperMemberSchema = zod.z.object({
2297
2835
  cap: zod.z.string().min(1),
2298
2836
  fieldPath: zod.z.string().min(1)
2299
2837
  });
2300
- /**
2301
- * Phase 1a is groups only. Math, integral and statistics stay off this union
2302
- * until the composer can run them — a control must not save a spec it cannot.
2303
- */
2838
+ /** A group, a statistic, or an integral (D944). The composer runs all three. */
2304
2839
  var GroupHelperSpecSchema = zod.z.object({
2305
2840
  kind: zod.z.literal("group"),
2306
2841
  templateId: GroupTemplateIdSchema,
@@ -2318,10 +2853,35 @@ var ApplianceHelperSpecSchema = zod.z.object({
2318
2853
  stopDwellMs: zod.z.number().int().nonnegative(),
2319
2854
  member: HelperMemberSchema
2320
2855
  });
2321
- var HelperSpecSchema = zod.z.discriminatedUnion("kind", [GroupHelperSpecSchema, ApplianceHelperSpecSchema]);
2322
- /** Every member a helper reads. An appliance has one. */
2856
+ var statisticHelperObject = zod.z.object({
2857
+ kind: zod.z.literal("statistic"),
2858
+ stat: zod.z.enum(STATISTIC_STATS),
2859
+ window: StatisticWindowSchema,
2860
+ source: HelperMemberSchema
2861
+ });
2862
+ var StatisticHelperSpecSchema = statisticHelperObject;
2863
+ var INTEGRAL_OUTPUTS = ["energy", "meter"];
2864
+ var integralHelperObject = zod.z.object({
2865
+ kind: zod.z.literal("integral"),
2866
+ reset: zod.z.enum(RESET_PERIODS),
2867
+ output: zod.z.enum(INTEGRAL_OUTPUTS),
2868
+ source: HelperMemberSchema
2869
+ });
2870
+ var IntegralHelperSpecSchema = integralHelperObject;
2871
+ var HelperSpecSchema = zod.z.discriminatedUnion("kind", [
2872
+ GroupHelperSpecSchema,
2873
+ ApplianceHelperSpecSchema,
2874
+ statisticHelperObject,
2875
+ integralHelperObject
2876
+ ]);
2877
+ /** Every member a helper reads. A group has many; the others have one. */
2323
2878
  function helperMembers(spec) {
2324
- return spec.kind === "group" ? spec.members : [spec.member];
2879
+ switch (spec.kind) {
2880
+ case "group": return spec.members;
2881
+ case "appliance": return [spec.member];
2882
+ case "statistic":
2883
+ case "integral": return [spec.source];
2884
+ }
2325
2885
  }
2326
2886
  //#endregion
2327
2887
  //#region src/composition/composition.ts
@@ -2432,11 +2992,36 @@ var CompositionAggregateSourceSchema = zod.z.object({
2432
2992
  "offline"
2433
2993
  ])
2434
2994
  });
2995
+ /**
2996
+ * `series`: a statistics or integral field. The composer writes it from a
2997
+ * history query. The live pass does not recompute it (D944).
2998
+ */
2999
+ var CompositionSeriesSourceSchema = zod.z.object({
3000
+ kind: zod.z.literal("series"),
3001
+ read: CompositionFieldReadSchema,
3002
+ /** `value` is the statistic or the integral. The others are the gap counts. */
3003
+ publish: zod.z.enum([
3004
+ "value",
3005
+ "included",
3006
+ "excluded",
3007
+ "total"
3008
+ ]),
3009
+ /**
3010
+ * What an unanswered `value` does. Counts ignore this. `offline` leaves the
3011
+ * device unseeded until history answers, so a restart cannot publish 0.
3012
+ */
3013
+ empty: zod.z.enum([
3014
+ "null",
3015
+ "omit",
3016
+ "offline"
3017
+ ])
3018
+ });
2435
3019
  var CompositionFieldSourceSchema = zod.z.discriminatedUnion("kind", [
2436
3020
  CompositionFromSourceSchema,
2437
3021
  CompositionExpressionSourceSchema,
2438
3022
  CompositionCodeSourceSchema,
2439
- CompositionAggregateSourceSchema
3023
+ CompositionAggregateSourceSchema,
3024
+ CompositionSeriesSourceSchema
2440
3025
  ]);
2441
3026
  /** `forward`: RESERVED for slice 3. Argument mapping arrives there as an additive optional `args`. */
2442
3027
  var CompositionForwardCommandSchema = zod.z.object({
@@ -2556,6 +3141,7 @@ function fieldReads(source) {
2556
3141
  fieldPath: binding.fieldPath
2557
3142
  }] : []);
2558
3143
  case "aggregate": return source.reads;
3144
+ case "series": return [source.read];
2559
3145
  case "code": return [];
2560
3146
  }
2561
3147
  }
@@ -2642,6 +3228,12 @@ Object.defineProperty(exports, "BINARY_AGGREGATES", {
2642
3228
  return BINARY_AGGREGATES;
2643
3229
  }
2644
3230
  });
3231
+ Object.defineProperty(exports, "CALENDAR_PERIODS", {
3232
+ enumerable: true,
3233
+ get: function() {
3234
+ return CALENDAR_PERIODS;
3235
+ }
3236
+ });
2645
3237
  Object.defineProperty(exports, "COMPOSED_DEVICE_STABLE_ID_PREFIX", {
2646
3238
  enumerable: true,
2647
3239
  get: function() {
@@ -2672,6 +3264,12 @@ Object.defineProperty(exports, "CUSTOMIZATION_BLOCK_NAME_PREFIX", {
2672
3264
  return CUSTOMIZATION_BLOCK_NAME_PREFIX;
2673
3265
  }
2674
3266
  });
3267
+ Object.defineProperty(exports, "CalendarWindowSchema", {
3268
+ enumerable: true,
3269
+ get: function() {
3270
+ return CalendarWindowSchema;
3271
+ }
3272
+ });
2675
3273
  Object.defineProperty(exports, "CompositionAggregateSourceSchema", {
2676
3274
  enumerable: true,
2677
3275
  get: function() {
@@ -2774,6 +3372,12 @@ Object.defineProperty(exports, "CompositionSchema", {
2774
3372
  return CompositionSchema;
2775
3373
  }
2776
3374
  });
3375
+ Object.defineProperty(exports, "CompositionSeriesSourceSchema", {
3376
+ enumerable: true,
3377
+ get: function() {
3378
+ return CompositionSeriesSourceSchema;
3379
+ }
3380
+ });
2777
3381
  Object.defineProperty(exports, "CompositionSourceRefSchema", {
2778
3382
  enumerable: true,
2779
3383
  get: function() {
@@ -2786,12 +3390,24 @@ Object.defineProperty(exports, "CompositionTargetSchema", {
2786
3390
  return CompositionTargetSchema;
2787
3391
  }
2788
3392
  });
3393
+ Object.defineProperty(exports, "DAY_MS", {
3394
+ enumerable: true,
3395
+ get: function() {
3396
+ return DAY_MS;
3397
+ }
3398
+ });
2789
3399
  Object.defineProperty(exports, "DEFAULT_FIRST_SIGHTING_FRESHNESS_MS", {
2790
3400
  enumerable: true,
2791
3401
  get: function() {
2792
3402
  return DEFAULT_FIRST_SIGHTING_FRESHNESS_MS;
2793
3403
  }
2794
3404
  });
3405
+ Object.defineProperty(exports, "DEFAULT_HISTORY_RETENTION", {
3406
+ enumerable: true,
3407
+ get: function() {
3408
+ return DEFAULT_HISTORY_RETENTION;
3409
+ }
3410
+ });
2795
3411
  Object.defineProperty(exports, "EXPRESSION_BUILTINS", {
2796
3412
  enumerable: true,
2797
3413
  get: function() {
@@ -2870,6 +3486,30 @@ Object.defineProperty(exports, "GroupTemplateIdSchema", {
2870
3486
  return GroupTemplateIdSchema;
2871
3487
  }
2872
3488
  });
3489
+ Object.defineProperty(exports, "HISTORY_FAST_MS", {
3490
+ enumerable: true,
3491
+ get: function() {
3492
+ return HISTORY_FAST_MS;
3493
+ }
3494
+ });
3495
+ Object.defineProperty(exports, "HISTORY_SLOW_MS", {
3496
+ enumerable: true,
3497
+ get: function() {
3498
+ return HISTORY_SLOW_MS;
3499
+ }
3500
+ });
3501
+ Object.defineProperty(exports, "HISTORY_TIERS", {
3502
+ enumerable: true,
3503
+ get: function() {
3504
+ return HISTORY_TIERS;
3505
+ }
3506
+ });
3507
+ Object.defineProperty(exports, "HOUR_MS", {
3508
+ enumerable: true,
3509
+ get: function() {
3510
+ return HOUR_MS;
3511
+ }
3512
+ });
2873
3513
  Object.defineProperty(exports, "HelperMemberSchema", {
2874
3514
  enumerable: true,
2875
3515
  get: function() {
@@ -2882,6 +3522,24 @@ Object.defineProperty(exports, "HelperSpecSchema", {
2882
3522
  return HelperSpecSchema;
2883
3523
  }
2884
3524
  });
3525
+ Object.defineProperty(exports, "INTEGRAL_OUTPUTS", {
3526
+ enumerable: true,
3527
+ get: function() {
3528
+ return INTEGRAL_OUTPUTS;
3529
+ }
3530
+ });
3531
+ Object.defineProperty(exports, "IntegralHelperSpecSchema", {
3532
+ enumerable: true,
3533
+ get: function() {
3534
+ return IntegralHelperSpecSchema;
3535
+ }
3536
+ });
3537
+ Object.defineProperty(exports, "KWH_PER_WATT_MS", {
3538
+ enumerable: true,
3539
+ get: function() {
3540
+ return KWH_PER_WATT_MS;
3541
+ }
3542
+ });
2885
3543
  Object.defineProperty(exports, "MAX_COMPOSITION_CODE_LENGTH", {
2886
3544
  enumerable: true,
2887
3545
  get: function() {
@@ -2942,6 +3600,18 @@ Object.defineProperty(exports, "MAX_HELPER_MEMBERS", {
2942
3600
  return MAX_HELPER_MEMBERS;
2943
3601
  }
2944
3602
  });
3603
+ Object.defineProperty(exports, "MINUTE_MS", {
3604
+ enumerable: true,
3605
+ get: function() {
3606
+ return MINUTE_MS;
3607
+ }
3608
+ });
3609
+ Object.defineProperty(exports, "MIN_CUSTOM_MINUTES", {
3610
+ enumerable: true,
3611
+ get: function() {
3612
+ return MIN_CUSTOM_MINUTES;
3613
+ }
3614
+ });
2945
3615
  Object.defineProperty(exports, "NUMERIC_AGGREGATES", {
2946
3616
  enumerable: true,
2947
3617
  get: function() {
@@ -2954,6 +3624,30 @@ Object.defineProperty(exports, "RESERVED_BINDING_NAMES", {
2954
3624
  return RESERVED_BINDING_NAMES;
2955
3625
  }
2956
3626
  });
3627
+ Object.defineProperty(exports, "RESET_PERIODS", {
3628
+ enumerable: true,
3629
+ get: function() {
3630
+ return RESET_PERIODS;
3631
+ }
3632
+ });
3633
+ Object.defineProperty(exports, "ROLLING_SPANS", {
3634
+ enumerable: true,
3635
+ get: function() {
3636
+ return ROLLING_SPANS;
3637
+ }
3638
+ });
3639
+ Object.defineProperty(exports, "RollingWindowSchema", {
3640
+ enumerable: true,
3641
+ get: function() {
3642
+ return RollingWindowSchema;
3643
+ }
3644
+ });
3645
+ Object.defineProperty(exports, "SERIES_KINDS", {
3646
+ enumerable: true,
3647
+ get: function() {
3648
+ return SERIES_KINDS;
3649
+ }
3650
+ });
2957
3651
  Object.defineProperty(exports, "SOURCE_CAPS", {
2958
3652
  enumerable: true,
2959
3653
  get: function() {
@@ -2978,6 +3672,24 @@ Object.defineProperty(exports, "SOURCE_DEVICE_TYPES", {
2978
3672
  return SOURCE_DEVICE_TYPES;
2979
3673
  }
2980
3674
  });
3675
+ Object.defineProperty(exports, "STATISTIC_STATS", {
3676
+ enumerable: true,
3677
+ get: function() {
3678
+ return STATISTIC_STATS;
3679
+ }
3680
+ });
3681
+ Object.defineProperty(exports, "StatisticHelperSpecSchema", {
3682
+ enumerable: true,
3683
+ get: function() {
3684
+ return StatisticHelperSpecSchema;
3685
+ }
3686
+ });
3687
+ Object.defineProperty(exports, "StatisticWindowSchema", {
3688
+ enumerable: true,
3689
+ get: function() {
3690
+ return StatisticWindowSchema;
3691
+ }
3692
+ });
2981
3693
  Object.defineProperty(exports, "applianceDeviceType", {
2982
3694
  enumerable: true,
2983
3695
  get: function() {
@@ -3146,6 +3858,12 @@ Object.defineProperty(exports, "evaluateStatefulExpressionCall", {
3146
3858
  return evaluateStatefulExpressionCall;
3147
3859
  }
3148
3860
  });
3861
+ Object.defineProperty(exports, "evaluateStatistic", {
3862
+ enumerable: true,
3863
+ get: function() {
3864
+ return evaluateStatistic;
3865
+ }
3866
+ });
3149
3867
  Object.defineProperty(exports, "featureFieldEntries", {
3150
3868
  enumerable: true,
3151
3869
  get: function() {
@@ -3188,6 +3906,18 @@ Object.defineProperty(exports, "helperMembers", {
3188
3906
  return helperMembers;
3189
3907
  }
3190
3908
  });
3909
+ Object.defineProperty(exports, "integralCadenceMs", {
3910
+ enumerable: true,
3911
+ get: function() {
3912
+ return integralCadenceMs;
3913
+ }
3914
+ });
3915
+ Object.defineProperty(exports, "integrateReading", {
3916
+ enumerable: true,
3917
+ get: function() {
3918
+ return integrateReading;
3919
+ }
3920
+ });
3191
3921
  Object.defineProperty(exports, "invocationFromEncodeProfile", {
3192
3922
  enumerable: true,
3193
3923
  get: function() {
@@ -3224,6 +3954,30 @@ Object.defineProperty(exports, "logBannerArgs", {
3224
3954
  return logBannerArgs;
3225
3955
  }
3226
3956
  });
3957
+ Object.defineProperty(exports, "maxDurationMs", {
3958
+ enumerable: true,
3959
+ get: function() {
3960
+ return maxDurationMs;
3961
+ }
3962
+ });
3963
+ Object.defineProperty(exports, "nextLocalMidnight", {
3964
+ enumerable: true,
3965
+ get: function() {
3966
+ return nextLocalMidnight;
3967
+ }
3968
+ });
3969
+ Object.defineProperty(exports, "openReset", {
3970
+ enumerable: true,
3971
+ get: function() {
3972
+ return openReset;
3973
+ }
3974
+ });
3975
+ Object.defineProperty(exports, "openStatistic", {
3976
+ enumerable: true,
3977
+ get: function() {
3978
+ return openStatistic;
3979
+ }
3980
+ });
3227
3981
  Object.defineProperty(exports, "parseExpression", {
3228
3982
  enumerable: true,
3229
3983
  get: function() {
@@ -3236,12 +3990,30 @@ Object.defineProperty(exports, "pickVideoEncoder", {
3236
3990
  return pickVideoEncoder;
3237
3991
  }
3238
3992
  });
3993
+ Object.defineProperty(exports, "planResetTier", {
3994
+ enumerable: true,
3995
+ get: function() {
3996
+ return planResetTier;
3997
+ }
3998
+ });
3999
+ Object.defineProperty(exports, "planStatisticTier", {
4000
+ enumerable: true,
4001
+ get: function() {
4002
+ return planStatisticTier;
4003
+ }
4004
+ });
3239
4005
  Object.defineProperty(exports, "resolveStreamMaps", {
3240
4006
  enumerable: true,
3241
4007
  get: function() {
3242
4008
  return resolveStreamMaps;
3243
4009
  }
3244
4010
  });
4011
+ Object.defineProperty(exports, "rollingDurationMs", {
4012
+ enumerable: true,
4013
+ get: function() {
4014
+ return rollingDurationMs;
4015
+ }
4016
+ });
3245
4017
  Object.defineProperty(exports, "sliceActiveValue", {
3246
4018
  enumerable: true,
3247
4019
  get: function() {
@@ -3260,6 +4032,12 @@ Object.defineProperty(exports, "sourcePickForAppliance", {
3260
4032
  return sourcePickForAppliance;
3261
4033
  }
3262
4034
  });
4035
+ Object.defineProperty(exports, "statisticCadenceMs", {
4036
+ enumerable: true,
4037
+ get: function() {
4038
+ return statisticCadenceMs;
4039
+ }
4040
+ });
3263
4041
  Object.defineProperty(exports, "toExpressionValue", {
3264
4042
  enumerable: true,
3265
4043
  get: function() {
@@ -3278,3 +4056,9 @@ Object.defineProperty(exports, "validateExpressionSource", {
3278
4056
  return validateExpressionSource;
3279
4057
  }
3280
4058
  });
4059
+ Object.defineProperty(exports, "wattMillisecondsToKwh", {
4060
+ enumerable: true,
4061
+ get: function() {
4062
+ return wattMillisecondsToKwh;
4063
+ }
4064
+ });