@camstack/types 1.2.421 → 1.2.422
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/addon.js +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/capabilities/composer.cap.d.ts +129 -57
- package/dist/capabilities/core-blocks.cap.d.ts +936 -410
- package/dist/capabilities/index.d.ts +3 -1
- package/dist/capabilities/meter.cap.d.ts +53 -0
- package/dist/client.js +3 -0
- package/dist/client.mjs +3 -0
- package/dist/composition/composition-report.d.ts +45 -0
- package/dist/composition/composition.d.ts +196 -19
- package/dist/composition/helpers/compile-history.d.ts +13 -0
- package/dist/composition/helpers/compile.d.ts +2 -1
- package/dist/composition/helpers/energy.d.ts +8 -0
- package/dist/composition/helpers/history-math.d.ts +39 -0
- package/dist/composition/helpers/history-window.d.ts +71 -0
- package/dist/composition/helpers/index.d.ts +6 -2
- package/dist/composition/helpers/source-pick.d.ts +2 -0
- package/dist/composition/helpers/spec.d.ts +23 -10
- package/dist/{composition-B3Hkb6v7.js → composition-CPRAP41h.js} +792 -8
- package/dist/{composition-B2rw5L29.mjs → composition-CpETZ4HL.mjs} +595 -9
- package/dist/device/cap-vocabulary/entries/sensors.d.ts +1 -1
- package/dist/generated/addon-api.d.ts +14 -0
- package/dist/generated/addon-cap-facts.d.ts +1 -1
- package/dist/generated/cap-status-types.d.ts +3 -1
- package/dist/generated/capability-router-map.d.ts +5 -2
- package/dist/generated/device-local-state.d.ts +3 -0
- package/dist/generated/device-proxy.d.ts +3 -0
- package/dist/generated/device-scoped-caps.d.ts +1 -1
- package/dist/generated/history-catalog.d.ts +1 -1
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/method-device-selectors.d.ts +1 -1
- package/dist/generated/runtime-state-durability.d.ts +1 -1
- package/dist/index.js +747 -446
- package/dist/index.mjs +709 -447
- package/dist/node.js +1 -1
- package/dist/node.mjs +1 -1
- package/dist/{sleep-bykKrTgu.mjs → sleep-0sD_LHLI.mjs} +10 -1
- package/dist/{sleep-YJ6FVsUu.js → sleep-CEKGW7A5.js} +10 -1
- 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 = z.object({
|
|
2503
|
+
kind: z.literal("rolling"),
|
|
2504
|
+
span: z.enum(ROLLING_SPANS),
|
|
2505
|
+
customMinutes: 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 = z.object({
|
|
2522
|
+
kind: z.literal("calendar"),
|
|
2523
|
+
period: z.enum(CALENDAR_PERIODS)
|
|
2524
|
+
});
|
|
2525
|
+
var CalendarWindowSchema = calendarWindowObject;
|
|
2526
|
+
var StatisticWindowSchema = 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 = z.object({
|
|
|
2297
2835
|
cap: z.string().min(1),
|
|
2298
2836
|
fieldPath: 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 = z.object({
|
|
2305
2840
|
kind: z.literal("group"),
|
|
2306
2841
|
templateId: GroupTemplateIdSchema,
|
|
@@ -2318,10 +2853,35 @@ var ApplianceHelperSpecSchema = z.object({
|
|
|
2318
2853
|
stopDwellMs: z.number().int().nonnegative(),
|
|
2319
2854
|
member: HelperMemberSchema
|
|
2320
2855
|
});
|
|
2321
|
-
var
|
|
2322
|
-
|
|
2856
|
+
var statisticHelperObject = z.object({
|
|
2857
|
+
kind: z.literal("statistic"),
|
|
2858
|
+
stat: z.enum(STATISTIC_STATS),
|
|
2859
|
+
window: StatisticWindowSchema,
|
|
2860
|
+
source: HelperMemberSchema
|
|
2861
|
+
});
|
|
2862
|
+
var StatisticHelperSpecSchema = statisticHelperObject;
|
|
2863
|
+
var INTEGRAL_OUTPUTS = ["energy", "meter"];
|
|
2864
|
+
var integralHelperObject = z.object({
|
|
2865
|
+
kind: z.literal("integral"),
|
|
2866
|
+
reset: z.enum(RESET_PERIODS),
|
|
2867
|
+
output: z.enum(INTEGRAL_OUTPUTS),
|
|
2868
|
+
source: HelperMemberSchema
|
|
2869
|
+
});
|
|
2870
|
+
var IntegralHelperSpecSchema = integralHelperObject;
|
|
2871
|
+
var HelperSpecSchema = 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
|
-
|
|
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 = 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 = z.object({
|
|
3000
|
+
kind: z.literal("series"),
|
|
3001
|
+
read: CompositionFieldReadSchema,
|
|
3002
|
+
/** `value` is the statistic or the integral. The others are the gap counts. */
|
|
3003
|
+
publish: 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: z.enum([
|
|
3014
|
+
"null",
|
|
3015
|
+
"omit",
|
|
3016
|
+
"offline"
|
|
3017
|
+
])
|
|
3018
|
+
});
|
|
2435
3019
|
var CompositionFieldSourceSchema = 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 = 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
|
}
|
|
@@ -2582,4 +3168,4 @@ function featureSources(feature) {
|
|
|
2582
3168
|
return featureFieldEntries(feature).map((e) => e.source);
|
|
2583
3169
|
}
|
|
2584
3170
|
//#endregion
|
|
2585
|
-
export {
|
|
3171
|
+
export { NUMERIC_AGGREGATES as $, MAX_EXPRESSION_AST_NODES as $t, composedDeviceStableId as A, Fmp4BoxSplitter as An, KWH_PER_WATT_MS as At, AGGREGATE_REDUCERS as B, pickVideoEncoder as Bn, createStatefulExpressionMemory as Bt, CompositionSeriesSourceSchema as C, FFMPEG_PATH_ENV as Cn, planStatisticTier as Ct, MAX_COMPOSITION_FIELDS_PER_FEATURE as D, getFfmpegArtifact as Dn, STATISTIC_STATS as Dt, MAX_COMPOSITION_FEATURES as E, FFMPEG_RELEASE as En, SERIES_KINDS as Et, compositionSourceKey as F, buildInputArgs as Fn, applianceDeviceType as Ft, GROUP_TEMPLATE_IDS as G, createExpressionScope as Gt, AggregateReducerSchema as H, evaluateStatefulExpressionCall as Ht, featureFieldEntries as I, buildVideoArgs as In, isApplianceKind as It, HelperMemberSchema as J, compileExpressionSafe as Jt, GroupHelperSpecSchema as K, evaluateAst as Kt, featureSources as L, invocationFromEncodeProfile as Ln, sourcePickForAppliance as Lt, compositionItemFieldPath as M, audioPlanFromEncodeProfile as Mn, APPLIANCE_DEFAULTS as Mt, compositionReadKey as N, buildAudioArgs as Nn, APPLIANCE_KINDS as Nt, MAX_COMPOSITION_ITEMS as O, getFfmpegDownloadUrl as On, evaluateStatistic as Ot, compositionSliceKey as P, buildFfmpegArgs as Pn, APPLIANCE_TEMPLATE_ID as Pt, MAX_HELPER_MEMBERS as Q, EXPRESSION_IDENTIFIER_RE as Qt, fieldReads as R, isSoftwareDecode as Rn, EXPRESSION_INJECTED_NOW as Rt, CompositionSchema as S, sliceChangedAt as Sn, planResetTier as St, MAX_COMPOSITION_CODE_LENGTH as T, ffmpegBinaryName as Tn, statisticCadenceMs as Tt, ApplianceHelperSpecSchema as U, toExpressionValue as Ut, ActiveCriterionSchema as V, resolveStreamMaps as Vn, evaluateExpressionSource as Vt, BINARY_AGGREGATES as W, validateExpressionSource as Wt, INTEGRAL_OUTPUTS as X, tokenize as Xt, HelperSpecSchema as Y, parseExpression as Yt, IntegralHelperSpecSchema as Z, EXPRESSION_COMPILE_CACHE_CAPACITY as Zt, CompositionFromSourceSchema as _, SOURCE_DEVICE_TYPES as _n, integralCadenceMs as _t, CUSTOMIZATION_BLOCK_NAME_PREFIX as a, EXPRESSION_BUILTINS as an, DAY_MS as at, CompositionNewTargetSchema as b, isSourceCap as bn, openReset as bt, CompositionCodeSourceSchema as c, evaluateCount as cn, HISTORY_SLOW_MS as ct, CompositionExpressionSourceSchema as d, ExpressionEvalError as dn, MINUTE_MS as dt, MAX_EXPRESSION_BINDINGS as en, StatisticHelperSpecSchema as et, CompositionFeatureSchema as f, ExpressionParseError as fn, MIN_CUSTOM_MINUTES as ft, CompositionForwardCommandSchema as g, SOURCE_CAP_CHANGED_AT_FIELD as gn, StatisticWindowSchema as gt, CompositionFieldsFeatureSchema as h, SOURCE_CAP_ACTIVE_FIELD as hn, RollingWindowSchema as ht, COMPOSITION_ITEM_KEY_RE as i, RESERVED_BINDING_NAMES as in, CalendarWindowSchema as it, compositionFeatureEnabled as j, AUDIO_PRESETS as jn, wattMillisecondsToKwh as jt, composedDeviceRef as k, canonicalHash as kn, integrateReading as kt, CompositionCommandTargetSchema as l, evaluateRose as ln, HISTORY_TIERS as lt, CompositionFieldSourceSchema as m, SOURCE_CAPS as mn, ROLLING_SPANS as mt, COMPOSER_ADDON_ID as n, MAX_EXPRESSION_EVAL_STEPS as nn, CompositionSourceRefSchema as nt, CompositionAggregateSourceSchema as o, EXPRESSION_BUILTIN_NAMES as on, DEFAULT_HISTORY_RETENTION as ot, CompositionFieldReadSchema as p, DEFAULT_FIRST_SIGHTING_FRESHNESS_MS as pn, RESET_PERIODS as pt, GroupTemplateIdSchema as q, compileExpression as qt, COMPOSITION_ITEM_ARRAY_PATH as r, MAX_EXPRESSION_SOURCE_LENGTH as rn, CALENDAR_PERIODS as rt, CompositionBindingSchema as s, disarmRoseMemory as sn, HISTORY_FAST_MS as st, COMPOSED_DEVICE_STABLE_ID_PREFIX as t, MAX_EXPRESSION_CALL_ARGS as tn, helperMembers as tt, CompositionExistingTargetSchema as u, isStatefulBuiltin as un, HOUR_MS as ut, CompositionItemEntrySchema as v, evaluateSensorEdge as vn, maxDurationMs as vt, CompositionTargetSchema as w, chooseFfmpegBinary as wn, rollingDurationMs as wt, CompositionPassthroughFeatureSchema as x, sliceActiveValue as xn, openStatistic as xt, CompositionLiteralBindingSchema as y, evaluateSensorLevelEdge as yn, nextLocalMidnight as yt, ACTIVE_CRITERIA as z, logBannerArgs as zn, cloneStatefulExpressionMemory as zt };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CapVocabularySlice } from '../types.js';
|
|
2
|
-
type SensorCap = 'air-quality-sensor' | 'ambient-light-sensor' | 'binary' | 'carbon-monoxide' | 'connectivity' | 'contact' | 'enum-sensor' | 'flood' | 'gas' | 'group-commands' | 'group-status' | 'humidity-sensor' | 'motion' | 'numeric-sensor' | 'power-meter' | 'presence' | 'pressure-sensor' | 'smoke' | 'tamper' | 'temperature-sensor' | 'vibration' | 'weather' | 'battery' | 'network-link' | 'device-status' | 'doorbell' | 'event-emitter';
|
|
2
|
+
type SensorCap = 'air-quality-sensor' | 'ambient-light-sensor' | 'binary' | 'carbon-monoxide' | 'connectivity' | 'contact' | 'enum-sensor' | 'flood' | 'gas' | 'group-commands' | 'group-status' | 'humidity-sensor' | 'meter' | 'motion' | 'numeric-sensor' | 'power-meter' | 'presence' | 'pressure-sensor' | 'smoke' | 'tamper' | 'temperature-sensor' | 'vibration' | 'weather' | 'battery' | 'network-link' | 'device-status' | 'doorbell' | 'event-emitter';
|
|
3
3
|
/** The sensor family of the vocabulary. */
|
|
4
4
|
export declare const SENSOR_CAP_VOCABULARY: CapVocabularySlice<SensorCap>;
|
|
5
5
|
export {};
|
|
@@ -4940,6 +4940,20 @@ export type AppRouter = TrpcCoreRouter<{
|
|
|
4940
4940
|
meta: object;
|
|
4941
4941
|
}>;
|
|
4942
4942
|
}>>;
|
|
4943
|
+
meter: TRPCBuiltRouter<{
|
|
4944
|
+
ctx: TrpcContext;
|
|
4945
|
+
meta: object;
|
|
4946
|
+
errorShape: AugmentedErrorShape;
|
|
4947
|
+
transformer: true;
|
|
4948
|
+
}, TRPCDecorateCreateRouterOptions<{
|
|
4949
|
+
getStatus: TRPCQueryProcedure<{
|
|
4950
|
+
input: {
|
|
4951
|
+
[x: string]: unknown;
|
|
4952
|
+
} & z.input<typeof DEVICE_STATUS_METHOD.getStatus.input>;
|
|
4953
|
+
output: z.infer<typeof DEVICE_STATUS_METHOD.getStatus.output>;
|
|
4954
|
+
meta: object;
|
|
4955
|
+
}>;
|
|
4956
|
+
}>>;
|
|
4943
4957
|
metricsProvider: TRPCBuiltRouter<{
|
|
4944
4958
|
ctx: TrpcContext;
|
|
4945
4959
|
meta: object;
|