@camstack/types 1.2.416 → 1.2.418

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 (79) hide show
  1. package/dist/addon.js +1 -1
  2. package/dist/addon.mjs +1 -1
  3. package/dist/capabilities/air-quality-sensor.cap.d.ts +2 -8
  4. package/dist/capabilities/ambient-light-sensor.cap.d.ts +3 -14
  5. package/dist/capabilities/battery.cap.d.ts +9 -2
  6. package/dist/capabilities/binary.cap.d.ts +0 -5
  7. package/dist/capabilities/capability-definition.d.ts +11 -6
  8. package/dist/capabilities/composer.cap.d.ts +198 -0
  9. package/dist/capabilities/consumables.cap.d.ts +30 -41
  10. package/dist/capabilities/control.cap.d.ts +3 -25
  11. package/dist/capabilities/core-blocks.cap.d.ts +1421 -0
  12. package/dist/capabilities/customize-catalog.d.ts +0 -8
  13. package/dist/capabilities/customize-spec.d.ts +2 -3
  14. package/dist/capabilities/device-manager.cap.d.ts +79 -0
  15. package/dist/capabilities/device-provider.cap.d.ts +36 -0
  16. package/dist/capabilities/enum-sensor.cap.d.ts +5 -17
  17. package/dist/capabilities/group-status.cap.d.ts +46 -0
  18. package/dist/capabilities/humidity-sensor.cap.d.ts +3 -14
  19. package/dist/capabilities/index.d.ts +4 -2
  20. package/dist/capabilities/motion.cap.d.ts +13 -5
  21. package/dist/capabilities/numeric-sensor.cap.d.ts +8 -16
  22. package/dist/capabilities/pet-feeder.cap.d.ts +1 -1
  23. package/dist/capabilities/power-meter.cap.d.ts +0 -11
  24. package/dist/capabilities/pressure-sensor.cap.d.ts +3 -9
  25. package/dist/capabilities/running-state.cap.d.ts +1 -2
  26. package/dist/capabilities/runtime-field-unit.d.ts +1 -0
  27. package/dist/capabilities/runtime-field-units.d.ts +8 -0
  28. package/dist/capabilities/switch.cap.d.ts +0 -5
  29. package/dist/capabilities/temperature-sensor.cap.d.ts +5 -20
  30. package/dist/capabilities/weather.cap.d.ts +4 -13
  31. package/dist/client.d.ts +2 -1
  32. package/dist/client.js +57 -5
  33. package/dist/client.mjs +55 -6
  34. package/dist/composition/composition-report.d.ts +80 -3
  35. package/dist/composition/composition.d.ts +285 -5
  36. package/dist/composition/helpers/aggregate.d.ts +41 -0
  37. package/dist/composition/helpers/catalog.d.ts +42 -0
  38. package/dist/composition/helpers/compile.d.ts +22 -0
  39. package/dist/composition/helpers/index.d.ts +6 -0
  40. package/dist/composition/helpers/save.d.ts +18 -0
  41. package/dist/composition/helpers/source-pick.d.ts +40 -0
  42. package/dist/composition/helpers/spec.d.ts +38 -0
  43. package/dist/composition/index.d.ts +1 -0
  44. package/dist/composition/source-ref.d.ts +11 -0
  45. package/dist/composition/state-fields.d.ts +1 -1
  46. package/dist/composition/validate-composition.d.ts +8 -2
  47. package/dist/{composition-kEl9mevu.mjs → composition-B85l9j1M.mjs} +114 -8
  48. package/dist/{composition-DsmkwP9-.js → composition-OJKOJJD-.js} +179 -7
  49. package/dist/device/cap-vocabulary/entries/sensors.d.ts +1 -1
  50. package/dist/device/cap-vocabulary/types.d.ts +2 -2
  51. package/dist/device/schema-fields.d.ts +3 -0
  52. package/dist/device/source-info.d.ts +12 -6
  53. package/dist/generated/addon-api.d.ts +14 -0
  54. package/dist/generated/addon-cap-facts.d.ts +1 -1
  55. package/dist/generated/cap-status-types.d.ts +3 -1
  56. package/dist/generated/capability-router-map.d.ts +5 -2
  57. package/dist/generated/customize-catalog.d.ts +11 -87
  58. package/dist/generated/device-local-state.d.ts +3 -0
  59. package/dist/generated/device-proxy.d.ts +3 -0
  60. package/dist/generated/history-catalog.d.ts +1 -1
  61. package/dist/generated/method-access-map.d.ts +1 -1
  62. package/dist/generated/method-device-selectors.d.ts +1 -1
  63. package/dist/generated/runtime-state-durability.d.ts +1 -1
  64. package/dist/index.d.ts +3 -1
  65. package/dist/index.js +1458 -499
  66. package/dist/index.mjs +1420 -499
  67. package/dist/node.js +1 -1
  68. package/dist/node.mjs +1 -1
  69. package/dist/{sleep-BPQ2ZyaI.js → sleep-7Gw3GJ4n.js} +232 -127
  70. package/dist/{sleep-BErAxhul.mjs → sleep-B81gwQuR.mjs} +179 -128
  71. package/dist/units/convert.d.ts +5 -0
  72. package/dist/units/format-reading.d.ts +15 -0
  73. package/dist/units/index.d.ts +8 -1
  74. package/dist/units/ingest-reading.d.ts +21 -0
  75. package/dist/units/quantity.d.ts +47 -0
  76. package/dist/units/threshold.d.ts +6 -0
  77. package/dist/units/unit-table.d.ts +2 -228
  78. package/package.json +1 -1
  79. package/dist/capabilities/advanced-parameters.d.ts +0 -17
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-BPQ2ZyaI.js");
2
+ const require_sleep = require("./sleep-7Gw3GJ4n.js");
3
3
  const require_event_category = require("./event-category-DXZ3f9Q5.js");
4
- const require_composition = require("./composition-DsmkwP9-.js");
4
+ const require_composition = require("./composition-OJKOJJD-.js");
5
5
  const require_node_color = require("./node-color.js");
6
6
  const require_err_msg = require("./err-msg-BhVbr0NX.js");
7
7
  const require_enums = require("./enums.js");
@@ -12398,6 +12398,17 @@ function unwrapSchema(schema) {
12398
12398
  while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
12399
12399
  return s;
12400
12400
  }
12401
+ function isMetaBag(value) {
12402
+ return typeof value === "object" && value !== null;
12403
+ }
12404
+ function metaQuantity(schema) {
12405
+ const bag = zod.z.globalRegistry.get(schema);
12406
+ if (isMetaBag(bag) && require_sleep.isReadingQuantity(bag.quantity)) return bag.quantity;
12407
+ }
12408
+ /** Quantity declared with `readingMeta` on the field or on the unwrapped leaf. */
12409
+ function quantityOfField(schema) {
12410
+ return metaQuantity(schema) ?? metaQuantity(unwrapSchema(schema));
12411
+ }
12401
12412
  function leafKind(schema) {
12402
12413
  const s = unwrapSchema(schema);
12403
12414
  if (s instanceof zod.z.ZodEnum) return {
@@ -12426,11 +12437,15 @@ function enumerateSchemaFields(schema, prefix = "") {
12426
12437
  continue;
12427
12438
  }
12428
12439
  const leaf = leafKind(field);
12429
- if (leaf) out.push({
12430
- path,
12431
- kind: leaf.kind,
12432
- ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
12433
- });
12440
+ if (leaf) {
12441
+ const quantity = quantityOfField(field);
12442
+ out.push({
12443
+ path,
12444
+ kind: leaf.kind,
12445
+ ...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
12446
+ ...quantity !== void 0 ? { quantity } : {}
12447
+ });
12448
+ }
12434
12449
  }
12435
12450
  return out;
12436
12451
  }
@@ -12491,7 +12506,7 @@ function requiredTopLevelKeys(schema) {
12491
12506
  /**
12492
12507
  * The proper prefixes of `path` whose schema accepts `undefined`: a required
12493
12508
  * leaf under one of them is required only once something writes that parent
12494
- * (`remaining.unit` is required only when `remaining` is present).
12509
+ * (`remaining.kind` is required only when `remaining` is present).
12495
12510
  */
12496
12511
  function optionalAncestors(schema, path) {
12497
12512
  const segments = path.split(".");
@@ -12608,7 +12623,11 @@ var CompositionProblemCodeSchema = zod.z.enum([
12608
12623
  "item-field-reserved",
12609
12624
  "target-missing",
12610
12625
  "target-unreadable",
12611
- "not-replaceable"
12626
+ "not-replaceable",
12627
+ "native-capability-replace",
12628
+ "mixed-quantity",
12629
+ "helper-mismatch",
12630
+ "member-constraint"
12612
12631
  ]);
12613
12632
  /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
12614
12633
  var CompositionProblemSchema = zod.z.object({
@@ -12632,7 +12651,8 @@ var CompositionUnavailableModeSchema = zod.z.enum([
12632
12651
  "null",
12633
12652
  "offline",
12634
12653
  "unknown",
12635
- "release"
12654
+ "release",
12655
+ "omit"
12636
12656
  ]);
12637
12657
  /**
12638
12658
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -12674,9 +12694,11 @@ function compositionFieldKey(plan) {
12674
12694
  return `${plan.cap}.${path}`;
12675
12695
  }
12676
12696
  /**
12677
- * What a feature does to its target (D663): `new` builds a capability on a new
12678
- * device; on an existing device it `add`s a capability the device lacks, or
12679
- * `replace`s named fields of one it has natively.
12697
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
12698
+ * new device; on an existing device it `add`s a capability the device lacks.
12699
+ * Replacing a capability the device already serves is refused
12700
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
12701
+ * wire for a claim the hub already holds.
12680
12702
  */
12681
12703
  var CompositionFeatureModeSchema = zod.z.enum([
12682
12704
  "new",
@@ -13001,6 +13023,24 @@ function inferExpressionKinds(input) {
13001
13023
  * planner (`composition-items.ts`). Internal to the composition module — NOT
13002
13024
  * in the barrel: these are planner internals, not a published contract.
13003
13025
  */
13026
+ function aggregateKinds(source) {
13027
+ if (source.publish !== "value") return {
13028
+ ok: true,
13029
+ kinds: new Set(["number"])
13030
+ };
13031
+ if (source.reducer === "any" || source.reducer === "all") return {
13032
+ ok: true,
13033
+ kinds: new Set(["boolean"])
13034
+ };
13035
+ if (source.empty === "null") return {
13036
+ ok: true,
13037
+ kinds: new Set(["number", "null"])
13038
+ };
13039
+ return {
13040
+ ok: true,
13041
+ kinds: new Set(["number"])
13042
+ };
13043
+ }
13004
13044
  function readKinds(read, lookupCap) {
13005
13045
  const def = lookupCap(read.cap);
13006
13046
  const schema = def?.runtimeState ?? def?.status?.schema;
@@ -13028,6 +13068,7 @@ function sourceKinds(source, lookupCap) {
13028
13068
  code: "reserved-variant",
13029
13069
  message: "code fields arrive in slice 4"
13030
13070
  };
13071
+ if (source.kind === "aggregate") return aggregateKinds(source);
13031
13072
  const bindingKinds = /* @__PURE__ */ new Map();
13032
13073
  for (const [name, binding] of Object.entries(source.bindings)) {
13033
13074
  if (binding.kind === "literal") {
@@ -13224,6 +13265,388 @@ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
13224
13265
  };
13225
13266
  }
13226
13267
  //#endregion
13268
+ //#region src/composition/helpers/catalog.ts
13269
+ /**
13270
+ * One catalog row per base reading a group helper can build (D944 §3).
13271
+ * Data, not a form default written into the block.
13272
+ */
13273
+ var NOW = {
13274
+ fieldPath: "lastFetchedAt",
13275
+ expr: "now"
13276
+ };
13277
+ var CHANGED = {
13278
+ fieldPath: "lastChangedAt",
13279
+ expr: "now"
13280
+ };
13281
+ function numeric(input) {
13282
+ return {
13283
+ ...input,
13284
+ family: "numeric",
13285
+ deviceType: require_err_msg.DeviceType.Sensor,
13286
+ memberDeviceType: require_err_msg.DeviceType.Sensor,
13287
+ baseUnit: require_sleep.baseUnitOf(input.quantity),
13288
+ aggregates: require_composition.NUMERIC_AGGREGATES,
13289
+ accept: [{
13290
+ cap: input.outputCap,
13291
+ fieldPath: input.outputField
13292
+ }]
13293
+ };
13294
+ }
13295
+ function binary(input) {
13296
+ return {
13297
+ ...input,
13298
+ family: "binary",
13299
+ deviceType: require_err_msg.DeviceType.Sensor,
13300
+ memberDeviceType: require_err_msg.DeviceType.Sensor,
13301
+ quantity: "none",
13302
+ baseUnit: void 0,
13303
+ aggregates: require_composition.BINARY_AGGREGATES,
13304
+ empty: "offline"
13305
+ };
13306
+ }
13307
+ var GROUP_TEMPLATES = [
13308
+ numeric({
13309
+ id: "temperature",
13310
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
13311
+ role: require_err_msg.DeviceRole.TemperatureSensor,
13312
+ outputCap: "temperature-sensor",
13313
+ outputField: "celsius",
13314
+ quantity: "temperature",
13315
+ defaultAggregate: "mean",
13316
+ empty: "null",
13317
+ companions: [NOW]
13318
+ }),
13319
+ numeric({
13320
+ id: "humidity",
13321
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
13322
+ role: require_err_msg.DeviceRole.HumiditySensor,
13323
+ outputCap: "humidity-sensor",
13324
+ outputField: "percent",
13325
+ quantity: "humidity",
13326
+ defaultAggregate: "mean",
13327
+ empty: "null",
13328
+ companions: [NOW]
13329
+ }),
13330
+ numeric({
13331
+ id: "pressure",
13332
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
13333
+ role: require_err_msg.DeviceRole.PressureSensor,
13334
+ outputCap: "pressure-sensor",
13335
+ outputField: "hpa",
13336
+ quantity: "pressure",
13337
+ defaultAggregate: "mean",
13338
+ empty: "null",
13339
+ companions: [NOW]
13340
+ }),
13341
+ numeric({
13342
+ id: "illuminance",
13343
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
13344
+ role: require_err_msg.DeviceRole.AmbientLightSensor,
13345
+ outputCap: "ambient-light-sensor",
13346
+ outputField: "lux",
13347
+ quantity: "illuminance",
13348
+ defaultAggregate: "mean",
13349
+ empty: "null",
13350
+ companions: [NOW]
13351
+ }),
13352
+ numeric({
13353
+ id: "power",
13354
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
13355
+ role: require_err_msg.DeviceRole.PowerSensor,
13356
+ outputCap: "power-meter",
13357
+ outputField: "watts",
13358
+ quantity: "power",
13359
+ defaultAggregate: "sum",
13360
+ empty: "omit",
13361
+ companions: [NOW]
13362
+ }),
13363
+ numeric({
13364
+ id: "energy",
13365
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
13366
+ role: require_err_msg.DeviceRole.EnergySensor,
13367
+ outputCap: "power-meter",
13368
+ outputField: "kwhTotal",
13369
+ quantity: "energy",
13370
+ defaultAggregate: "sum",
13371
+ empty: "omit",
13372
+ companions: [NOW]
13373
+ }),
13374
+ numeric({
13375
+ id: "battery",
13376
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
13377
+ role: require_err_msg.DeviceRole.BatterySensor,
13378
+ outputCap: "battery",
13379
+ outputField: "percentage",
13380
+ quantity: "percentage",
13381
+ defaultAggregate: "mean",
13382
+ empty: "null",
13383
+ companions: [
13384
+ {
13385
+ fieldPath: "charging",
13386
+ expr: "\"unknown\""
13387
+ },
13388
+ {
13389
+ fieldPath: "sleeping",
13390
+ expr: "false"
13391
+ },
13392
+ {
13393
+ fieldPath: "lastUpdated",
13394
+ expr: "now"
13395
+ }
13396
+ ]
13397
+ }),
13398
+ numeric({
13399
+ id: "numeric",
13400
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
13401
+ role: require_err_msg.DeviceRole.NumericSensor,
13402
+ outputCap: "numeric-sensor",
13403
+ outputField: "value",
13404
+ quantity: "device",
13405
+ defaultAggregate: "mean",
13406
+ empty: "null",
13407
+ companions: [NOW]
13408
+ }),
13409
+ binary({
13410
+ id: "motion",
13411
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
13412
+ role: require_err_msg.DeviceRole.MotionSensor,
13413
+ outputCap: "motion",
13414
+ outputField: "detected",
13415
+ defaultAggregate: "any",
13416
+ accept: [{
13417
+ cap: "motion",
13418
+ fieldPath: "detected"
13419
+ }],
13420
+ companions: [{
13421
+ fieldPath: "lastDetectedAt",
13422
+ expr: "null"
13423
+ }, {
13424
+ fieldPath: "autoClearAfterMs",
13425
+ expr: "null"
13426
+ }]
13427
+ }),
13428
+ binary({
13429
+ id: "contact",
13430
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
13431
+ role: require_err_msg.DeviceRole.ContactSensor,
13432
+ outputCap: "contact",
13433
+ outputField: "entryOpen",
13434
+ defaultAggregate: "any",
13435
+ accept: [{
13436
+ cap: "contact",
13437
+ fieldPath: "entryOpen"
13438
+ }],
13439
+ companions: [CHANGED]
13440
+ }),
13441
+ binary({
13442
+ id: "leak",
13443
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
13444
+ role: require_err_msg.DeviceRole.LeakSensor,
13445
+ outputCap: "flood",
13446
+ outputField: "flooded",
13447
+ defaultAggregate: "any",
13448
+ accept: [{
13449
+ cap: "flood",
13450
+ fieldPath: "flooded"
13451
+ }],
13452
+ companions: [CHANGED]
13453
+ }),
13454
+ binary({
13455
+ id: "smoke",
13456
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
13457
+ role: require_err_msg.DeviceRole.SmokeSensor,
13458
+ outputCap: "smoke",
13459
+ outputField: "detected",
13460
+ defaultAggregate: "any",
13461
+ accept: [{
13462
+ cap: "smoke",
13463
+ fieldPath: "detected"
13464
+ }],
13465
+ companions: [CHANGED]
13466
+ }),
13467
+ binary({
13468
+ id: "occupancy",
13469
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
13470
+ role: require_err_msg.DeviceRole.BinarySensor,
13471
+ outputCap: "binary",
13472
+ outputField: "on",
13473
+ defaultAggregate: "any",
13474
+ accept: [{
13475
+ cap: "motion",
13476
+ fieldPath: "detected"
13477
+ }, {
13478
+ cap: "binary",
13479
+ fieldPath: "on"
13480
+ }],
13481
+ companions: [CHANGED]
13482
+ }),
13483
+ binary({
13484
+ id: "binary",
13485
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
13486
+ role: require_err_msg.DeviceRole.BinarySensor,
13487
+ outputCap: "binary",
13488
+ outputField: "on",
13489
+ defaultAggregate: "any",
13490
+ accept: [{
13491
+ cap: "binary",
13492
+ fieldPath: "on"
13493
+ }],
13494
+ companions: [CHANGED]
13495
+ })
13496
+ ];
13497
+ var BY_ID = new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
13498
+ function groupTemplate(id) {
13499
+ return BY_ID.get(id) ?? null;
13500
+ }
13501
+ //#endregion
13502
+ //#region src/composition/helpers/compile.ts
13503
+ function sameSource(a, b) {
13504
+ return a.addonId === b.addonId && a.stableId === b.stableId;
13505
+ }
13506
+ function sameMember(a, b) {
13507
+ return sameSource(a.source, b.source) && a.cap === b.cap && a.fieldPath === b.fieldPath;
13508
+ }
13509
+ function accepted(template, member) {
13510
+ return template.accept.some((field) => field.cap === member.cap && field.fieldPath === member.fieldPath);
13511
+ }
13512
+ function memberProblems(template, spec) {
13513
+ return spec.members.flatMap((member, index) => {
13514
+ const at = `helper.members[${index}]`;
13515
+ const problems = [];
13516
+ if (!accepted(template, member)) {
13517
+ const allowed = template.accept.map((field) => `${field.cap}.${field.fieldPath}`).join(" or ");
13518
+ problems.push(problem("member-constraint", at, `a ${template.id} group reads ${allowed}; this member is ${member.cap}.${member.fieldPath}`));
13519
+ }
13520
+ if (spec.members.slice(0, index).some((other) => sameMember(other, member))) problems.push(problem("member-constraint", at, "this member is already in the group"));
13521
+ return problems;
13522
+ });
13523
+ }
13524
+ function aggregateSource(template, spec, reads, publish) {
13525
+ return {
13526
+ kind: "aggregate",
13527
+ reducer: spec.aggregate,
13528
+ reads: [...reads],
13529
+ publish,
13530
+ empty: template.empty
13531
+ };
13532
+ }
13533
+ function expressionSource(expr) {
13534
+ return {
13535
+ kind: "expression",
13536
+ expr,
13537
+ bindings: {}
13538
+ };
13539
+ }
13540
+ function draftOf$1(template, spec) {
13541
+ const reads = spec.members.map((member) => ({
13542
+ source: member.source,
13543
+ cap: member.cap,
13544
+ fieldPath: member.fieldPath
13545
+ }));
13546
+ const companions = Object.fromEntries(template.companions.map((companion) => [companion.fieldPath, expressionSource(companion.expr)]));
13547
+ return {
13548
+ target: {
13549
+ kind: "new",
13550
+ type: template.deviceType,
13551
+ role: template.role
13552
+ },
13553
+ helper: spec,
13554
+ features: [{
13555
+ kind: "fields",
13556
+ cap: template.outputCap,
13557
+ fields: {
13558
+ [template.outputField]: aggregateSource(template, spec, reads, "value"),
13559
+ ...companions
13560
+ }
13561
+ }, {
13562
+ kind: "fields",
13563
+ cap: "group-status",
13564
+ fields: {
13565
+ included: aggregateSource(template, spec, reads, "included"),
13566
+ excluded: aggregateSource(template, spec, reads, "excluded"),
13567
+ total: aggregateSource(template, spec, reads, "total")
13568
+ }
13569
+ }]
13570
+ };
13571
+ }
13572
+ function parseDraft(draft) {
13573
+ const parsed = require_composition.CompositionSchema.safeParse(draft);
13574
+ if (parsed.success) return {
13575
+ ok: true,
13576
+ composition: parsed.data
13577
+ };
13578
+ return {
13579
+ ok: false,
13580
+ problems: [problem("helper-mismatch", "helper", `the template did not compile: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`)]
13581
+ };
13582
+ }
13583
+ /** Compile a group template to a new-device composition. Refusals are named. */
13584
+ function compileHelper(spec) {
13585
+ const template = groupTemplate(spec.templateId);
13586
+ if (template === null) return {
13587
+ ok: false,
13588
+ problems: [problem("member-constraint", "helper.templateId", `unknown template \`${spec.templateId}\``)]
13589
+ };
13590
+ if (!template.aggregates.includes(spec.aggregate)) {
13591
+ const allowed = template.aggregates.join(", ");
13592
+ return {
13593
+ ok: false,
13594
+ problems: [problem("kind-mismatch", "helper.aggregate", `a ${template.id} group aggregates by ${allowed}, not \`${spec.aggregate}\``)]
13595
+ };
13596
+ }
13597
+ const members = memberProblems(template, spec);
13598
+ if (members.length > 0) return {
13599
+ ok: false,
13600
+ problems: members
13601
+ };
13602
+ return parseDraft(draftOf$1(template, spec));
13603
+ }
13604
+ /** The built body, ignoring `helper`, so a save can demand one writer. */
13605
+ function sameHelperBody(stored, compiled) {
13606
+ return JSON.stringify(stored.target) === JSON.stringify(compiled.target) && JSON.stringify(stored.features) === JSON.stringify(compiled.features);
13607
+ }
13608
+ function templateDeviceType(spec) {
13609
+ return groupTemplate(spec.templateId)?.memberDeviceType ?? null;
13610
+ }
13611
+ //#endregion
13612
+ //#region src/composition/helpers/save.ts
13613
+ function quantityProblems(spec, facts) {
13614
+ const template = groupTemplate(spec.templateId);
13615
+ if (template === null || template.family !== "numeric") return [];
13616
+ const known = spec.members.flatMap((member, index) => {
13617
+ const quantity = facts.get(require_composition.compositionSourceKey(member.source))?.quantity;
13618
+ return quantity === void 0 ? [] : [{
13619
+ index,
13620
+ quantity
13621
+ }];
13622
+ });
13623
+ if (template.quantity !== "device") return known.flatMap((member) => member.quantity === template.quantity ? [] : [problem("mixed-quantity", `helper.members[${member.index}]`, `a ${template.id} group is ${template.quantity}${template.baseUnit === void 0 ? "" : ` (${template.baseUnit})`}; this member is ${member.quantity}`)]);
13624
+ const distinct = [...new Set(known.map((member) => member.quantity))];
13625
+ if (distinct.length <= 1) return [];
13626
+ return [problem("mixed-quantity", "helper.members", `a numeric group requires one quantity; members report ${distinct.join(" and ")}`)];
13627
+ }
13628
+ function deviceTypeProblems(spec, facts) {
13629
+ const template = groupTemplate(spec.templateId);
13630
+ if (template === null) return [];
13631
+ return spec.members.flatMap((member, index) => {
13632
+ const deviceType = facts.get(require_composition.compositionSourceKey(member.source))?.deviceType;
13633
+ if (deviceType === void 0 || deviceType === template.memberDeviceType) return [];
13634
+ return [problem("member-constraint", `helper.members[${index}]`, `a ${template.id} group takes ${template.memberDeviceType} devices; this member is a ${deviceType}`)];
13635
+ });
13636
+ }
13637
+ /**
13638
+ * Problems that refuse a save. No `helper` key means a free composition: this
13639
+ * returns nothing, and expression semantics stay as they are.
13640
+ */
13641
+ function helperSaveProblems(composition, facts) {
13642
+ const spec = composition.helper;
13643
+ if (spec === void 0) return [];
13644
+ const compiled = compileHelper(spec);
13645
+ if (!compiled.ok) return compiled.problems;
13646
+ if (!sameHelperBody(composition, compiled.composition)) return [problem("helper-mismatch", "helper", "the stored features are not this template — the helper spec is the only writer")];
13647
+ return [...quantityProblems(spec, facts), ...deviceTypeProblems(spec, facts)];
13648
+ }
13649
+ //#endregion
13227
13650
  //#region src/composition/composition-graph.ts
13228
13651
  /**
13229
13652
  * The field graph across every composition, and the cycles in it (D659).
@@ -13454,6 +13877,7 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
13454
13877
  const role = roleOf(source);
13455
13878
  const unavailable = unavailableFor(tf, role, mode, def.name, fieldAt);
13456
13879
  if (!unavailable.ok) return refused(unavailable.problem);
13880
+ const omitEmpty = source.kind === "aggregate" && source.publish === "value" && source.empty === "omit";
13457
13881
  const plan = {
13458
13882
  cap: def.name,
13459
13883
  path,
@@ -13461,7 +13885,7 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
13461
13885
  kind: tf.kind,
13462
13886
  nullable: tf.nullable,
13463
13887
  required: tf.required,
13464
- unavailable: unavailable.unavailable,
13888
+ unavailable: omitEmpty ? "omit" : unavailable.unavailable,
13465
13889
  role,
13466
13890
  source,
13467
13891
  reads: [...require_composition.fieldReads(source)]
@@ -13510,6 +13934,9 @@ function isNativeCap(target, cap) {
13510
13934
  const owner = target.nativeCaps.get(cap);
13511
13935
  return owner !== void 0 && owner !== "composer";
13512
13936
  }
13937
+ function isPlanProblem(value) {
13938
+ return "code" in value;
13939
+ }
13513
13940
  /** The target read, or the refusal that replaces the whole plan when it could not be (D391). */
13514
13941
  function planTargetOf(composition, target) {
13515
13942
  if (composition.target.kind === "new") return {
@@ -13525,26 +13952,59 @@ function planTargetOf(composition, target) {
13525
13952
  found: target
13526
13953
  };
13527
13954
  }
13528
- function featureTargetOf(target, cap) {
13955
+ var NATIVE_REPLACE_KEPT = new Set([
13956
+ "not-composable",
13957
+ "unknown-capability",
13958
+ "reserved-variant"
13959
+ ]);
13960
+ /** A native override is refused, unless the cap cannot be composed at all — that reason stays. */
13961
+ function nativeReplaceOrBlock(refusal, probed) {
13962
+ const kept = probed.problems.filter((item) => NATIVE_REPLACE_KEPT.has(item.code));
13963
+ if (kept.length > 0) return {
13964
+ problems: kept,
13965
+ fields: [],
13966
+ features: [],
13967
+ events: []
13968
+ };
13969
+ return refused(refusal);
13970
+ }
13971
+ function featureTargetOf(target, cap, at) {
13529
13972
  if (target.kind === "new") return {
13530
13973
  mode: "new",
13531
13974
  deviceType: target.type
13532
13975
  };
13976
+ if (isNativeCap(target.found, cap)) return problem("native-capability-replace", at, `native-capability-replace: stored override replaces native capability \`${cap}\` on device ${target.found.deviceId} — refused (D944)`);
13533
13977
  return {
13534
- mode: isNativeCap(target.found, cap) ? "replace" : "add",
13978
+ mode: "add",
13535
13979
  deviceType: target.found.type
13536
13980
  };
13537
13981
  }
13538
13982
  /**
13539
13983
  * The static plan. `target` describes an EXISTING target device (one read);
13540
- * it is ignored for a new one. On an existing target each feature REPLACES a
13541
- * cap the device has natively, else ADDS it (D663).
13984
+ * it is ignored for a new one. On an existing target each feature ADDS a cap
13985
+ * the device lacks. A feature that would replace a native capability is
13986
+ * refused by name (D944). A new device is unchanged.
13542
13987
  */
13543
13988
  function planComposition(composition, lookupCap, target) {
13544
13989
  const planTarget = planTargetOf(composition, target);
13545
13990
  if ("code" in planTarget) return refused(planTarget);
13546
13991
  const results = composition.features.map((feature, index) => {
13547
- const result = feature.kind === "passthrough" ? refused(problem("reserved-variant", `features[${index}]`, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`)) : planFeature(feature, index, featureTargetOf(planTarget, feature.cap), lookupCap);
13992
+ const at = `features[${index}]`;
13993
+ if (feature.kind === "passthrough") return refused(problem("reserved-variant", at, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`));
13994
+ const featureTarget = featureTargetOf(planTarget, feature.cap, at);
13995
+ if (isPlanProblem(featureTarget)) {
13996
+ if (!require_composition.compositionFeatureEnabled(feature) || planTarget.kind !== "existing") return {
13997
+ problems: [],
13998
+ fields: [],
13999
+ features: [],
14000
+ events: []
14001
+ };
14002
+ return nativeReplaceOrBlock(featureTarget, planFeature(feature, index, {
14003
+ mode: "add",
14004
+ deviceType: planTarget.found.type
14005
+ }, lookupCap));
14006
+ }
14007
+ const result = planFeature(feature, index, featureTarget, lookupCap);
13548
14008
  return require_composition.compositionFeatureEnabled(feature) ? result : {
13549
14009
  problems: result.problems,
13550
14010
  fields: [],
@@ -13685,11 +14145,20 @@ function cycleProblems(composition, ctx, fields, self) {
13685
14145
  function validateComposition(composition, ctx) {
13686
14146
  const plan = planComposition(composition, ctx.lookupCap, ctx.target ?? void 0);
13687
14147
  const self = candidateSelf(composition, ctx, plan);
13688
- const cycles = cycleProblems(composition, ctx, plan.fields, self);
14148
+ const refusedFeatures = plan.problems.filter((item) => item.code === "native-capability-replace").map((item) => item.path);
14149
+ const cycles = cycleProblems(composition, ctx, plan.fields, self).filter((item) => {
14150
+ if (refusedFeatures.some((at) => item.path === at || item.path.startsWith(`${at}.`))) return false;
14151
+ return refusedFeatures.length === 0 || item.path !== "features";
14152
+ });
14153
+ const facts = new Map([...ctx.sources].flatMap(([key, source]) => source.kind === "found" ? [[key, {
14154
+ deviceType: source.deviceType,
14155
+ quantity: source.quantity
14156
+ }]] : []));
13689
14157
  const problems = [
13690
14158
  ...plan.problems,
13691
14159
  ...sourceProblems(plan.fields, ctx, self),
13692
- ...cycles
14160
+ ...cycles,
14161
+ ...helperSaveProblems(composition, facts)
13693
14162
  ];
13694
14163
  return {
13695
14164
  ok: problems.length === 0,
@@ -16432,12 +16901,12 @@ function parseAdminBase(baseUrl) {
16432
16901
  * providers detect renames by matching `uniqueId` and patch `id`
16433
16902
  * in-place so dispatch keeps working.
16434
16903
  *
16435
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
16436
- * `precision`) live in the cap STATUS SLICE so they flow live with every
16437
- * state push and are the single source of truth. The `device_class` tag is
16438
- * an HA-specific input that is consumed at the discovery boundary
16439
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
16440
- * stored after that point.
16904
+ * **Display unit and precision are NOT stored here.** They are a read-time
16905
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
16906
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
16907
+ * neither is repeated on each sample. The `device_class` tag is an
16908
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
16909
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
16441
16910
  *
16442
16911
  * The `raw` escape hatch carries provider-specific attributes that don't
16443
16912
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -16451,6 +16920,14 @@ var SourceInfoSchema = zod.z.object({
16451
16920
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
16452
16921
  * Used to detect rename when `id` changes. */
16453
16922
  uniqueId: zod.z.string().optional(),
16923
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
16924
+ quantity: require_sleep.ReadingQuantitySchema.optional(),
16925
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
16926
+ dateTimeShape: zod.z.enum([
16927
+ "date",
16928
+ "time",
16929
+ "datetime"
16930
+ ]).optional(),
16454
16931
  /** Provider-specific extras that don't fit the structured slots.
16455
16932
  * Whitelist what goes here per provider — do NOT dump entire HA
16456
16933
  * attribute blobs (would explode the meta JSON column). */
@@ -16535,9 +17012,38 @@ function normalizeUnit(raw) {
16535
17012
  if (lower === "mph") return "mph";
16536
17013
  if (lower === "m/s") return "m/s";
16537
17014
  if (lower === "m") return "m";
17015
+ if (lower === "m²" || lower === "m2") return "m²";
17016
+ if (lower === "mm") return "mm";
17017
+ if (lower === "cm") return "cm";
16538
17018
  if (lower === "km") return "km";
17019
+ if (lower === "in" || lower === "inch" || lower === "inches") return "in";
17020
+ if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
16539
17021
  if (lower === "kg") return "kg";
16540
17022
  if (lower === "g") return "g";
17023
+ if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
17024
+ if (lower === "ml") return "mL";
17025
+ if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
17026
+ if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
17027
+ if (lower === "ms") return "ms";
17028
+ if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
17029
+ if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
17030
+ if (lower === "d" || lower === "day" || lower === "days") return "d";
17031
+ if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
17032
+ if (lower === "kib") return "KiB";
17033
+ if (lower === "mib") return "MiB";
17034
+ if (lower === "gib") return "GiB";
17035
+ if (lower === "tib") return "TiB";
17036
+ if (lower === "kb") return "kB";
17037
+ if (lower === "mb") return "MB";
17038
+ if (lower === "gb") return "GB";
17039
+ if (lower === "dbm") return "dBm";
17040
+ if (lower === "dbfs") return "dBFS";
17041
+ if (lower === "inhg") return "inHg";
17042
+ if (lower === "mm/h") return "mm/h";
17043
+ if (lower === "w/m²" || lower === "w/m2") return "W/m²";
17044
+ if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
17045
+ if (lower === "kbps") return "kbps";
17046
+ if (lower === "fps") return "fps";
16541
17047
  return trimmed;
16542
17048
  }
16543
17049
  /**
@@ -37969,7 +38475,8 @@ var deviceManagerCapability = {
37969
38475
  "enum"
37970
38476
  ]),
37971
38477
  enumValues: zod.z.array(zod.z.string()).optional(),
37972
- item: zod.z.boolean().optional()
38478
+ item: zod.z.boolean().optional(),
38479
+ quantity: require_sleep.ReadingQuantitySchema.optional()
37973
38480
  })).readonly(),
37974
38481
  itemArray: zod.z.object({
37975
38482
  path: zod.z.string(),
@@ -44293,8 +44800,8 @@ var MaskPolygonShapeSchema = zod.z.object({
44293
44800
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
44294
44801
  var MaskGridShapeSchema = zod.z.object({
44295
44802
  kind: zod.z.literal("grid"),
44296
- gridWidth: zod.z.number(),
44297
- gridHeight: zod.z.number(),
44803
+ gridWidth: zod.z.number().meta(require_sleep.readingMeta("none")),
44804
+ gridHeight: zod.z.number().meta(require_sleep.readingMeta("none")),
44298
44805
  cells: zod.z.array(zod.z.boolean())
44299
44806
  });
44300
44807
  /** Directed line / tripwire — an ordered list of normalized points (≥2). */
@@ -60558,27 +61065,17 @@ var addonsCapability = {
60558
61065
  */
60559
61066
  var AirQualitySensorStatusSchema = zod.z.object({
60560
61067
  /** Carbon dioxide concentration in ppm. */
60561
- co2Ppm: zod.z.number().min(0).optional(),
61068
+ co2Ppm: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-volume")),
60562
61069
  /** Total volatile organic compounds in ppb. */
60563
- vocPpb: zod.z.number().min(0).optional(),
61070
+ vocPpb: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-ppb")),
60564
61071
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
60565
- pm25: zod.z.number().min(0).optional(),
61072
+ pm25: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-mass")),
60566
61073
  /** Particulate matter ≤ 10 μm in µg/m³. */
60567
- pm10: zod.z.number().min(0).optional(),
61074
+ pm10: zod.z.number().min(0).optional().meta(require_sleep.readingMeta("concentration-mass")),
60568
61075
  /** Composite AQI value (typically 0..500). */
60569
- aqi: zod.z.number().optional(),
61076
+ aqi: zod.z.number().optional().meta(require_sleep.readingMeta("index")),
60570
61077
  /** Ms epoch when the slice was last updated. */
60571
- lastFetchedAt: zod.z.number(),
60572
- /** Live display unit of the single metric this slice carries (e.g. HA
60573
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
60574
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
60575
- * per slice is unambiguous. */
60576
- unit: zod.z.string().optional(),
60577
- /** Suggested decimal places for numeric display.
60578
- * Populated live from the upstream source when provided (e.g. HA
60579
- * `attributes.suggested_display_precision`). Falls back to
60580
- * auto-formatting when absent. */
60581
- precision: zod.z.number().int().min(0).max(10).optional()
61078
+ lastFetchedAt: zod.z.number()
60582
61079
  });
60583
61080
  var airQualitySensorCapability = {
60584
61081
  name: "air-quality-sensor",
@@ -60615,12 +61112,12 @@ var airQualitySensorCapability = {
60615
61112
  },
60616
61113
  pm25: {
60617
61114
  kind: "gauge",
60618
- unit: "ug/m3",
61115
+ unit: "µg/m³",
60619
61116
  deadband: 1
60620
61117
  },
60621
61118
  pm10: {
60622
61119
  kind: "gauge",
60623
- unit: "ug/m3",
61120
+ unit: "µg/m³",
60624
61121
  deadband: 1
60625
61122
  },
60626
61123
  aqi: {
@@ -60640,22 +61137,13 @@ var airQualitySensorCapability = {
60640
61137
  * entries with `device_class: illuminance`.
60641
61138
  */
60642
61139
  var AmbientLightSensorStatusSchema = zod.z.object({
60643
- /** Current illuminance in lux (lx). */
60644
- lux: zod.z.number().min(0).meta({
60645
- unit: "lx",
61140
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
61141
+ lux: zod.z.number().min(0).nullable().meta(require_sleep.readingMeta("illuminance", {
61142
+ precision: 0,
60646
61143
  label: "Illuminance"
60647
- }),
61144
+ })),
60648
61145
  /** Ms epoch when the slice was last updated. */
60649
- lastFetchedAt: zod.z.number(),
60650
- /** Live display unit from the upstream source (e.g. HA
60651
- * `attributes.unit_of_measurement`). The UI prefers this over the
60652
- * role's canonical unit. Absent → fall back to the canonical unit. */
60653
- unit: zod.z.string().optional(),
60654
- /** Suggested decimal places for numeric display.
60655
- * Populated live from the upstream source when provided (e.g. HA
60656
- * `attributes.suggested_display_precision`). Falls back to
60657
- * auto-formatting when absent. */
60658
- precision: zod.z.number().int().min(0).max(10).optional()
61146
+ lastFetchedAt: zod.z.number()
60659
61147
  });
60660
61148
  var ambientLightSensorCapability = {
60661
61149
  name: "ambient-light-sensor",
@@ -60687,12 +61175,7 @@ var ambientLightSensorCapability = {
60687
61175
  } } },
60688
61176
  /** Clock fields: written, but excluded from the compare that decides
60689
61177
  * whether persisting is worth a SQLite commit. */
60690
- volatileStateFields: ["lastFetchedAt"],
60691
- customize: {
60692
- version: 1,
60693
- advanced: "derive",
60694
- targets: []
60695
- }
61178
+ volatileStateFields: ["lastFetchedAt"]
60696
61179
  };
60697
61180
  //#endregion
60698
61181
  //#region src/capabilities/audio-metrics.cap.ts
@@ -60934,10 +61417,7 @@ var BatteryStatusSchema = zod.z.object({
60934
61417
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
60935
61418
  * declines to band a non-finite reading, which is the correct shape.
60936
61419
  */
60937
- percentage: zod.z.number().min(0).max(100).nullable().meta({
60938
- unit: "%",
60939
- label: "Charge"
60940
- }),
61420
+ percentage: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage", { label: "Charge" })),
60941
61421
  /**
60942
61422
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
60943
61423
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -60964,7 +61444,14 @@ var BatteryStatusSchema = zod.z.object({
60964
61444
  */
60965
61445
  sleeping: zod.z.boolean().meta({ label: "Sleeping" }),
60966
61446
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
60967
- lastUpdated: zod.z.number(),
61447
+ lastUpdated: zod.z.number().meta(require_sleep.readingMeta("none")),
61448
+ /**
61449
+ * Battery voltage in volts, for a cell the firmware reports as voltage
61450
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
61451
+ * that only report charge. A second reading on this cap, not a second
61452
+ * `numeric-sensor` on the same device.
61453
+ */
61454
+ volts: zod.z.number().nullable().optional().meta(require_sleep.readingMeta("voltage", { label: "Voltage" })),
60968
61455
  /**
60969
61456
  * Ms epoch of the last time the device PROVED it was reachable — a
60970
61457
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -61016,7 +61503,6 @@ var BATTERY_DEVICE_TYPES = [
61016
61503
  /** Generic Customize target for the percentage reading (D936). */
61017
61504
  var batteryCustomize = {
61018
61505
  version: 1,
61019
- advanced: "derive",
61020
61506
  targets: [{
61021
61507
  id: "percentage",
61022
61508
  cardinality: "scalar",
@@ -61025,7 +61511,7 @@ var batteryCustomize = {
61025
61511
  stateField: "percentage"
61026
61512
  },
61027
61513
  deviceTypes: BATTERY_DEVICE_TYPES,
61028
- apply: "add-or-replace",
61514
+ apply: "add",
61029
61515
  path: "percentage",
61030
61516
  sources: [{
61031
61517
  kind: "field",
@@ -61146,10 +61632,16 @@ onStatusChanged: { data: zod.z.object({
61146
61632
  /**
61147
61633
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
61148
61634
  */
61149
- history: { fields: { percentage: {
61150
- kind: "gauge",
61151
- unit: "%"
61152
- } } },
61635
+ history: { fields: {
61636
+ percentage: {
61637
+ kind: "gauge",
61638
+ unit: "%"
61639
+ },
61640
+ volts: {
61641
+ kind: "gauge",
61642
+ unit: "V"
61643
+ }
61644
+ } },
61153
61645
  customize: batteryCustomize,
61154
61646
  /** Clock fields: written, but excluded from the compare that decides
61155
61647
  * whether persisting is worth a SQLite commit. */
@@ -61198,12 +61690,7 @@ var binaryCapability = {
61198
61690
  history: { fields: { on: { kind: "boolean" } } },
61199
61691
  /** Clock fields: written, but excluded from the compare that decides
61200
61692
  * whether persisting is worth a SQLite commit. */
61201
- volatileStateFields: ["lastChangedAt"],
61202
- customize: {
61203
- version: 1,
61204
- advanced: "derive",
61205
- targets: []
61206
- }
61693
+ volatileStateFields: ["lastChangedAt"]
61207
61694
  };
61208
61695
  //#endregion
61209
61696
  //#region src/capabilities/brightness.cap.ts
@@ -61221,7 +61708,7 @@ var binaryCapability = {
61221
61708
  */
61222
61709
  var BrightnessStatusSchema = zod.z.object({
61223
61710
  /** Current level as 0..100 inclusive. Firmware-reported. */
61224
- percentage: zod.z.number().min(0).max(100),
61711
+ percentage: zod.z.number().min(0).max(100).meta(require_sleep.readingMeta("percentage")),
61225
61712
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
61226
61713
  lastChangedAt: zod.z.number()
61227
61714
  });
@@ -61820,21 +62307,21 @@ var ClimateControlStatusSchema = zod.z.object({
61820
62307
  /** Single setpoint in Celsius. Used by single-target modes
61821
62308
  * (heat / cool / dry). Null when the device is in a range mode
61822
62309
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
61823
- target: zod.z.number().nullable(),
62310
+ target: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61824
62311
  /** Upper bound of the dual setpoint in Celsius. Populated only
61825
62312
  * in `heat_cool` mode. */
61826
- targetHigh: zod.z.number().nullable(),
62313
+ targetHigh: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61827
62314
  /** Lower bound of the dual setpoint in Celsius. Populated only
61828
62315
  * in `heat_cool` mode. */
61829
- targetLow: zod.z.number().nullable(),
62316
+ targetLow: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61830
62317
  /** Target relative humidity (0..100). Null when the device has no
61831
62318
  * humidity control. */
61832
- targetHumidity: zod.z.number().min(0).max(100).nullable(),
62319
+ targetHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
61833
62320
  /** Current measured temperature in Celsius. Read-only — pushed by
61834
62321
  * the device's internal sensor. */
61835
- currentTemp: zod.z.number().nullable(),
62322
+ currentTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
61836
62323
  /** Current measured relative humidity (0..100). Read-only. */
61837
- currentHumidity: zod.z.number().min(0).max(100).nullable(),
62324
+ currentHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
61838
62325
  /** Vertical louver swing on/off. Null when the device has no vertical
61839
62326
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
61840
62327
  swingVertical: zod.z.boolean().nullable(),
@@ -61842,11 +62329,11 @@ var ClimateControlStatusSchema = zod.z.object({
61842
62329
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
61843
62330
  swingHorizontal: zod.z.boolean().nullable(),
61844
62331
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
61845
- minTemp: zod.z.number().nullable().optional(),
62332
+ minTemp: zod.z.number().nullable().optional().meta(require_sleep.readingMeta("temperature")),
61846
62333
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
61847
- maxTemp: zod.z.number().nullable().optional(),
62334
+ maxTemp: zod.z.number().nullable().optional().meta(require_sleep.readingMeta("temperature")),
61848
62335
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
61849
- tempStep: zod.z.number().positive().nullable().optional(),
62336
+ tempStep: zod.z.number().positive().nullable().optional().meta(require_sleep.readingMeta("temperature")),
61850
62337
  /** Ms epoch when the slice was last updated (push or command). */
61851
62338
  lastFetchedAt: zod.z.number()
61852
62339
  });
@@ -61990,17 +62477,17 @@ var climateControlCapability = {
61990
62477
  * modes to expose in the picker without a slice fetch.
61991
62478
  */
61992
62479
  var RgbTripletSchema = zod.z.object({
61993
- r: zod.z.number().int().min(0).max(255),
61994
- g: zod.z.number().int().min(0).max(255),
61995
- b: zod.z.number().int().min(0).max(255)
62480
+ r: zod.z.number().int().min(0).max(255).meta(require_sleep.readingMeta("none")),
62481
+ g: zod.z.number().int().min(0).max(255).meta(require_sleep.readingMeta("none")),
62482
+ b: zod.z.number().int().min(0).max(255).meta(require_sleep.readingMeta("none"))
61996
62483
  });
61997
62484
  var HsvTripletSchema = zod.z.object({
61998
62485
  /** Hue in degrees, 0..360. */
61999
- h: zod.z.number().min(0).max(360),
62486
+ h: zod.z.number().min(0).max(360).meta(require_sleep.readingMeta("none")),
62000
62487
  /** Saturation as 0..100 inclusive. */
62001
- s: zod.z.number().min(0).max(100),
62488
+ s: zod.z.number().min(0).max(100).meta(require_sleep.readingMeta("none")),
62002
62489
  /** Value/brightness as 0..100 inclusive. */
62003
- v: zod.z.number().min(0).max(100)
62490
+ v: zod.z.number().min(0).max(100).meta(require_sleep.readingMeta("none"))
62004
62491
  });
62005
62492
  var ColorInputSchema = zod.z.discriminatedUnion("mode", [
62006
62493
  zod.z.object({
@@ -62029,7 +62516,7 @@ var ColorStatusSchema = zod.z.object({
62029
62516
  /** Populated when `mode === 'hsv'`. */
62030
62517
  hsv: HsvTripletSchema.optional(),
62031
62518
  /** Populated when `mode === 'mired'`. */
62032
- mireds: zod.z.number().int().optional(),
62519
+ mireds: zod.z.number().int().optional().meta(require_sleep.readingMeta("colour-temperature")),
62033
62520
  /** Ms epoch of the last operator-driven change. */
62034
62521
  lastChangedAt: zod.z.number()
62035
62522
  });
@@ -62311,18 +62798,12 @@ var FORM_OF_KIND = {
62311
62798
  function consumableFormOf(item) {
62312
62799
  return item.form ?? FORM_OF_KIND[consumableKindOf(item)];
62313
62800
  }
62314
- /** Units a remaining-life counter is reported in. Closed: the panel renders
62315
- * each. */
62316
- var ConsumableRemainingUnitSchema = zod.z.enum([
62317
- "days",
62318
- "hours",
62319
- "cycles"
62320
- ]);
62321
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
62322
- * days). `value` null = unknown (D393). */
62801
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
62802
+ var ConsumableRemainingKindSchema = zod.z.enum(["duration", "count"]);
62803
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
62323
62804
  var ConsumableRemainingSchema = zod.z.object({
62324
- value: zod.z.number().min(0).nullable(),
62325
- unit: ConsumableRemainingUnitSchema
62805
+ value: zod.z.number().min(0).nullable().meta(require_sleep.readingMeta("device")),
62806
+ kind: ConsumableRemainingKindSchema
62326
62807
  });
62327
62808
  /** A single consumable item. Either a continuous `level` (remaining
62328
62809
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -62335,7 +62816,7 @@ var ConsumableItemSchema = zod.z.object({
62335
62816
  /** Display name. */
62336
62817
  label: zod.z.string().min(1),
62337
62818
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
62338
- level: zod.z.number().min(0).max(100).nullable(),
62819
+ level: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
62339
62820
  /** The provider's discrete verdict when known (binary mode). */
62340
62821
  status: ConsumableStatusSchema.nullable(),
62341
62822
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -62417,8 +62898,8 @@ var consumablesCustomize = {
62417
62898
  transforms: [{ kind: "identity" }]
62418
62899
  }],
62419
62900
  literals: [{
62420
- path: "remaining.unit",
62421
- values: [...ConsumableRemainingUnitSchema.options]
62901
+ path: "remaining.kind",
62902
+ values: [...ConsumableRemainingKindSchema.options]
62422
62903
  }]
62423
62904
  },
62424
62905
  {
@@ -62547,7 +63028,6 @@ reset: require_sleep.method(zod.z.object({
62547
63028
  },
62548
63029
  "remaining.value": {
62549
63030
  kind: "gauge",
62550
- unitFrom: "remaining.unit",
62551
63031
  deadband: 1,
62552
63032
  displayName: "Remaining"
62553
63033
  }
@@ -62650,49 +63130,26 @@ var ControlKindSchema = zod.z.enum([
62650
63130
  * (empty array for the other kinds). It lives in the slice because
62651
63131
  * HA `select.*` entities may update their options at runtime.
62652
63132
  *
62653
- * The `unit`/`min`/`max`/`step` fields are meaningful for
62654
- * `kind === 'numeric'` and are absent (undefined) for all other
62655
- * kinds. They are populated live from HA attributes on each state
62656
- * push — consistent with `options` and the sensor-unit approach.
63133
+ * Numeric `min` / `max` / `step` live in the slice and share the device
63134
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
63135
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
62657
63136
  */
62658
63137
  var ControlStatusSchema = zod.z.object({
62659
63138
  kind: ControlKindSchema,
62660
63139
  /** Current value. Numeric kind → number; select/text/datetime → string.
62661
63140
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
62662
63141
  * or time-only — HA accepts all three forms). */
62663
- value: zod.z.union([zod.z.number(), zod.z.string()]),
63142
+ value: zod.z.union([zod.z.number().meta(require_sleep.readingMeta("device")), zod.z.string()]),
62664
63143
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
62665
63144
  options: zod.z.array(zod.z.string()),
62666
63145
  /** Ms epoch when the slice was last updated. */
62667
63146
  lastChangedAt: zod.z.number(),
62668
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
62669
- * Populated live from HA `attributes.unit_of_measurement` on each push.
62670
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62671
- unit: zod.z.string().optional(),
62672
- /** Minimum allowed value. Populated live from HA `attributes.min`.
62673
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62674
- min: zod.z.number().optional(),
62675
- /** Maximum allowed value. Populated live from HA `attributes.max`.
62676
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62677
- max: zod.z.number().optional(),
62678
- /** Step increment. Populated live from HA `attributes.step`.
62679
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
62680
- step: zod.z.number().optional(),
62681
- /** Suggested decimal places for numeric display. Populated live from HA
62682
- * `attributes.suggested_display_precision`, or derived from `step` when the
62683
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
62684
- * other kinds. Falls back to auto-formatting when absent. */
62685
- precision: zod.z.number().int().min(0).max(10).optional(),
62686
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
62687
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
62688
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
62689
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
62690
- * `has_date`/`has_time` attributes on each push. */
62691
- format: zod.z.enum([
62692
- "date",
62693
- "time",
62694
- "datetime"
62695
- ]).optional()
63147
+ /** Minimum allowed value, in the device quantity's base unit. */
63148
+ min: zod.z.number().optional().meta(require_sleep.readingMeta("device")),
63149
+ /** Maximum allowed value, in the device quantity's base unit. */
63150
+ max: zod.z.number().optional().meta(require_sleep.readingMeta("device")),
63151
+ /** Step increment, in the device quantity's base unit. */
63152
+ step: zod.z.number().optional().meta(require_sleep.readingMeta("device"))
62696
63153
  });
62697
63154
  var ControlSetValueInputSchema = zod.z.discriminatedUnion("kind", [
62698
63155
  zod.z.object({
@@ -62782,9 +63239,9 @@ var CoverStatusSchema = zod.z.object({
62782
63239
  state: CoverStateSchema,
62783
63240
  /** 0 = fully closed, 100 = fully open. Null when the device has
62784
63241
  * no intermediate position surface. */
62785
- position: zod.z.number().min(0).max(100).nullable(),
63242
+ position: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
62786
63243
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
62787
- tiltPosition: zod.z.number().min(0).max(100).nullable(),
63244
+ tiltPosition: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
62788
63245
  /** Ms epoch when the slice was last updated. */
62789
63246
  lastChangedAt: zod.z.number()
62790
63247
  });
@@ -62889,9 +63346,9 @@ var NormalizedRangeSchema$1 = zod.z.object({
62889
63346
  var DayNightStatusSchema = zod.z.object({
62890
63347
  mode: DayNightModeSchema,
62891
63348
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
62892
- sensitivity: zod.z.number().optional(),
63349
+ sensitivity: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
62893
63350
  /** Delay before the IR-cut filter flips, in seconds. */
62894
- switchDelaySec: zod.z.number().optional(),
63351
+ switchDelaySec: zod.z.number().optional().meta(require_sleep.readingMeta("duration")),
62895
63352
  lastFetchedAt: zod.z.number()
62896
63353
  });
62897
63354
  /**
@@ -63124,11 +63581,11 @@ var DishwasherStatusSchema = zod.z.object({
63124
63581
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
63125
63582
  availablePrograms: zod.z.array(zod.z.string()),
63126
63583
  /** Seconds remaining on the active cycle, or null when not running / not known. */
63127
- remainingSeconds: zod.z.number().int().nonnegative().nullable(),
63584
+ remainingSeconds: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration")),
63128
63585
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
63129
- delayedStartSeconds: zod.z.number().int().nonnegative().nullable(),
63586
+ delayedStartSeconds: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration")),
63130
63587
  /** Cycle completion 0..100, or null when not applicable. */
63131
- progressPercent: zod.z.number().min(0).max(100).nullable(),
63588
+ progressPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
63132
63589
  /** True when the door is reported open; null when the door state is not known. */
63133
63590
  doorOpen: zod.z.boolean().nullable(),
63134
63591
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -63152,7 +63609,7 @@ var DishwasherStatusSchema = zod.z.object({
63152
63609
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
63153
63610
  machineCareDue: zod.z.boolean().nullable(),
63154
63611
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
63155
- machineCareRemainingRuns: zod.z.number().int().nonnegative().nullable(),
63612
+ machineCareRemainingRuns: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("count")),
63156
63613
  /** Option states: null when the appliance does not expose the option or has not said. */
63157
63614
  halfLoad: zod.z.boolean().nullable(),
63158
63615
  hygienePlus: zod.z.boolean().nullable(),
@@ -63167,7 +63624,7 @@ var DishwasherStatusSchema = zod.z.object({
63167
63624
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
63168
63625
  activeEvents: zod.z.array(zod.z.string()),
63169
63626
  /** Numeric error code when the appliance reports one; null otherwise. */
63170
- errorCode: zod.z.number().nullable(),
63627
+ errorCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
63171
63628
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
63172
63629
  errorLabel: zod.z.string().nullable(),
63173
63630
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -63402,7 +63859,7 @@ var DoorbellStatusSchema = zod.z.object({
63402
63859
  /** Ms epoch of the last press. null = never observed since this provider started. */
63403
63860
  lastPressedAt: zod.z.number().nullable(),
63404
63861
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
63405
- pressCountSinceStart: zod.z.number()
63862
+ pressCountSinceStart: zod.z.number().meta(require_sleep.readingMeta("count"))
63406
63863
  });
63407
63864
  var DoorbellPressEventSchema = zod.z.object({
63408
63865
  deviceId: zod.z.number(),
@@ -63487,12 +63944,11 @@ var EnumSensorDateTimeFormatSchema = zod.z.enum([
63487
63944
  var EnumSensorStatusSchema = zod.z.object({
63488
63945
  value: zod.z.string(),
63489
63946
  /**
63490
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
63491
- * as a locale date/time/datetime; absent for plain enum sensors. HA
63492
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
63493
- * a time-only sensor → `'time'`.
63947
+ * Set when `value` is a timestamp rather than an enum state, so history
63948
+ * does not learn ISO strings as states. The display shape (date / time /
63949
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
63494
63950
  */
63495
- format: EnumSensorDateTimeFormatSchema.optional(),
63951
+ timestamp: zod.z.boolean().optional(),
63496
63952
  /** Ms epoch when the slice was last updated. */
63497
63953
  lastFetchedAt: zod.z.number()
63498
63954
  });
@@ -63519,11 +63975,11 @@ var enumSensorCapability = {
63519
63975
  * History (D850): the value as an `enum` series (an appliance's program or
63520
63976
  * operation state, an HVAC action). The option set is per device, so the
63521
63977
  * value list is LEARNED as values are first seen (bounded). A sensor with a
63522
- * `format` carries a timestamp, not a state, and is not recorded.
63978
+ * `timestamp` carries a clock, not a state, and is not recorded.
63523
63979
  */
63524
63980
  history: { fields: { value: {
63525
63981
  kind: "enum",
63526
- skipWhenSet: "format"
63982
+ skipWhenSet: "timestamp"
63527
63983
  } } },
63528
63984
  /** Clock fields: written, but excluded from the compare that decides
63529
63985
  * whether persisting is worth a SQLite commit. */
@@ -63545,13 +64001,13 @@ var EventFireSchema = zod.z.object({
63545
64001
  deviceId: zod.z.number(),
63546
64002
  eventType: zod.z.string(),
63547
64003
  data: zod.z.record(zod.z.string(), zod.z.unknown()).nullable(),
63548
- timestamp: zod.z.number(),
63549
- seq: zod.z.number()
64004
+ timestamp: zod.z.number().meta(require_sleep.readingMeta("none")),
64005
+ seq: zod.z.number().meta(require_sleep.readingMeta("count"))
63550
64006
  });
63551
64007
  var EventEmitterStatusSchema = zod.z.object({
63552
64008
  eventTypes: zod.z.array(zod.z.string()),
63553
64009
  lastEvent: EventFireSchema.nullable(),
63554
- eventCountSinceStart: zod.z.number()
64010
+ eventCountSinceStart: zod.z.number().meta(require_sleep.readingMeta("count"))
63555
64011
  });
63556
64012
  var eventEmitterCapability = {
63557
64013
  name: "event-emitter",
@@ -64008,10 +64464,10 @@ var FanDirectionSchema = zod.z.enum(["forward", "reverse"]);
64008
64464
  var FanControlStatusSchema = zod.z.object({
64009
64465
  /** Active speed as 0..100 inclusive. Null when the device has no
64010
64466
  * speed surface (single-speed fan). */
64011
- percentage: zod.z.number().min(0).max(100).nullable(),
64467
+ percentage: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
64012
64468
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
64013
64469
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
64014
- percentageStep: zod.z.number().positive().optional(),
64470
+ percentageStep: zod.z.number().positive().optional().meta(require_sleep.readingMeta("percentage")),
64015
64471
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
64016
64472
  * — empty string when no preset is active or supported. */
64017
64473
  preset: zod.z.string(),
@@ -64134,7 +64590,7 @@ var FeatureProbeStatusSchema = zod.z.object({
64134
64590
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
64135
64591
  model: zod.z.string().nullable(),
64136
64592
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
64137
- channelCount: zod.z.number().nullable(),
64593
+ channelCount: zod.z.number().nullable().meta(require_sleep.readingMeta("count")),
64138
64594
  /**
64139
64595
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
64140
64596
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -64262,10 +64718,57 @@ var gasCapability = {
64262
64718
  volatileStateFields: ["lastChangedAt"]
64263
64719
  };
64264
64720
  //#endregion
64721
+ //#region src/capabilities/group-status.cap.ts
64722
+ /**
64723
+ * How many members of a helper group answered (D944). Counts are integers,
64724
+ * not measurements: zero included is a real count, never a stand-in reading.
64725
+ * The reading itself stays on the output capability. History is none — the
64726
+ * series that later statistics read is the output cap's own history block.
64727
+ */
64728
+ var GroupStatusSchema = zod.z.object({
64729
+ /** Members whose current value entered the aggregate. */
64730
+ included: zod.z.number().int().nonnegative().meta(require_sleep.readingMeta("count", {
64731
+ precision: 0,
64732
+ label: "Reporting"
64733
+ })),
64734
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
64735
+ excluded: zod.z.number().int().nonnegative().meta(require_sleep.readingMeta("count", {
64736
+ precision: 0,
64737
+ label: "Excluded"
64738
+ })),
64739
+ /** `included` + `excluded`. */
64740
+ total: zod.z.number().int().nonnegative().meta(require_sleep.readingMeta("count", {
64741
+ precision: 0,
64742
+ label: "Members"
64743
+ }))
64744
+ });
64745
+ var groupStatusCapability = {
64746
+ name: "group-status",
64747
+ scope: "device",
64748
+ deviceNative: true,
64749
+ mode: "singleton",
64750
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
64751
+ methods: {},
64752
+ status: {
64753
+ schema: GroupStatusSchema,
64754
+ kind: "push"
64755
+ },
64756
+ runtimeState: GroupStatusSchema,
64757
+ /**
64758
+ * Runtime-state durability: **session** — recomputed from members on the
64759
+ * next source event; a restored count would outlive the readings it named.
64760
+ *
64761
+ * See `RuntimeStateDurability`. Enforced by
64762
+ * `scripts/check-runtime-state-durability.ts`.
64763
+ */
64764
+ durability: "session",
64765
+ history: "none"
64766
+ };
64767
+ //#endregion
64265
64768
  //#region src/capabilities/heating-valve.cap.ts
64266
64769
  var HeatingValveStatusSchema = zod.z.object({
64267
64770
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
64268
- position: zod.z.number().min(0).max(100).nullable(),
64771
+ position: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
64269
64772
  /** Ms epoch when the slice was last updated (push or poll). */
64270
64773
  lastFetchedAt: zod.z.number()
64271
64774
  });
@@ -64310,9 +64813,9 @@ var HumidifierStatusSchema = zod.z.object({
64310
64813
  /** Whether the humidifier is currently on. */
64311
64814
  on: zod.z.boolean(),
64312
64815
  /** Current measured relative humidity (0..100). Null when not reported. */
64313
- currentHumidity: zod.z.number().min(0).max(100).nullable(),
64816
+ currentHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
64314
64817
  /** Target relative humidity (0..100). Null when no setpoint surface. */
64315
- targetHumidity: zod.z.number().min(0).max(100).nullable(),
64818
+ targetHumidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
64316
64819
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
64317
64820
  * device has no mode surface. */
64318
64821
  mode: zod.z.string().nullable(),
@@ -64322,9 +64825,9 @@ var HumidifierStatusSchema = zod.z.object({
64322
64825
  * `idle` / `off`). Null when the device doesn't report it. */
64323
64826
  action: zod.z.string().nullable(),
64324
64827
  /** HA `min_humidity` attribute. Null → UI uses 0. */
64325
- minHumidity: zod.z.number().nullable(),
64828
+ minHumidity: zod.z.number().nullable().meta(require_sleep.readingMeta("humidity")),
64326
64829
  /** HA `max_humidity` attribute. Null → UI uses 100. */
64327
- maxHumidity: zod.z.number().nullable(),
64830
+ maxHumidity: zod.z.number().nullable().meta(require_sleep.readingMeta("humidity")),
64328
64831
  /** Ms epoch when the slice was last updated. */
64329
64832
  lastChangedAt: zod.z.number()
64330
64833
  });
@@ -64396,22 +64899,13 @@ var humidifierCapability = {
64396
64899
  * `sourceInfo`.
64397
64900
  */
64398
64901
  var HumiditySensorStatusSchema = zod.z.object({
64399
- /** Current relative humidity, 0..100. */
64400
- percent: zod.z.number().min(0).max(100).meta({
64401
- unit: "%",
64902
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
64903
+ percent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity", {
64904
+ precision: 0,
64402
64905
  label: "Humidity"
64403
- }),
64906
+ })),
64404
64907
  /** Ms epoch when the slice was last updated. */
64405
- lastFetchedAt: zod.z.number(),
64406
- /** Live display unit from the upstream source (e.g. HA
64407
- * `attributes.unit_of_measurement`). The UI prefers this over the
64408
- * role's canonical unit. Absent → fall back to the canonical unit. */
64409
- unit: zod.z.string().optional(),
64410
- /** Suggested decimal places for numeric display.
64411
- * Populated live from the upstream source when provided (e.g. HA
64412
- * `attributes.suggested_display_precision`). Falls back to
64413
- * auto-formatting when absent. */
64414
- precision: zod.z.number().int().min(0).max(10).optional()
64908
+ lastFetchedAt: zod.z.number()
64415
64909
  });
64416
64910
  var humiditySensorCapability = {
64417
64911
  name: "humidity-sensor",
@@ -64442,12 +64936,7 @@ var humiditySensorCapability = {
64442
64936
  } } },
64443
64937
  /** Clock fields: written, but excluded from the compare that decides
64444
64938
  * whether persisting is worth a SQLite commit. */
64445
- volatileStateFields: ["lastFetchedAt"],
64446
- customize: {
64447
- version: 1,
64448
- advanced: "derive",
64449
- targets: []
64450
- }
64939
+ volatileStateFields: ["lastFetchedAt"]
64451
64940
  };
64452
64941
  //#endregion
64453
64942
  //#region src/capabilities/image.cap.ts
@@ -64470,7 +64959,7 @@ var ImageStatusSchema = zod.z.object({
64470
64959
  * entity exposes no `entity_picture` (yet). */
64471
64960
  url: zod.z.string().nullable(),
64472
64961
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
64473
- lastUpdated: zod.z.number().nullable()
64962
+ lastUpdated: zod.z.number().nullable().meta(require_sleep.readingMeta("none"))
64474
64963
  });
64475
64964
  var imageCapability = {
64476
64965
  name: "image",
@@ -64560,19 +65049,19 @@ var NormalizedRangeSchema = zod.z.object({
64560
65049
  */
64561
65050
  var ImageSettingsStatusSchema = zod.z.object({
64562
65051
  /** Normalized 0–100. */
64563
- brightness: zod.z.number().optional(),
65052
+ brightness: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64564
65053
  /** Normalized 0–100. */
64565
- contrast: zod.z.number().optional(),
65054
+ contrast: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64566
65055
  /** Normalized 0–100. */
64567
- saturation: zod.z.number().optional(),
65056
+ saturation: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64568
65057
  /** Normalized 0–100. */
64569
- sharpness: zod.z.number().optional(),
65058
+ sharpness: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64570
65059
  mirror: zod.z.boolean().optional(),
64571
65060
  flip: zod.z.boolean().optional(),
64572
65061
  rotate: ImageRotateSchema.optional(),
64573
65062
  whiteBalance: WhiteBalanceModeSchema.optional(),
64574
65063
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
64575
- warmth: zod.z.number().optional(),
65064
+ warmth: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
64576
65065
  exposureMode: ExposureModeSchema.optional(),
64577
65066
  backlightMode: BacklightModeSchema.optional(),
64578
65067
  lastFetchedAt: zod.z.number()
@@ -64873,10 +65362,10 @@ var IntercomAbilitySchema = zod.z.object({
64873
65362
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
64874
65363
  codecs: zod.z.array(zod.z.string()),
64875
65364
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
64876
- sampleRate: zod.z.number(),
65365
+ sampleRate: zod.z.number().meta(require_sleep.readingMeta("none")),
64877
65366
  duplex: zod.z.enum(["half", "full"]),
64878
65367
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
64879
- maxBacklogMs: zod.z.number()
65368
+ maxBacklogMs: zod.z.number().meta(require_sleep.readingMeta("duration-ms"))
64880
65369
  });
64881
65370
  var IntercomStatusSchema = zod.z.object({
64882
65371
  /** True while a talk session is open. */
@@ -65105,13 +65594,13 @@ var LawnMowerControlStatusSchema = zod.z.object({
65105
65594
  activity: LawnMowerActivitySchema,
65106
65595
  /** 0..100 battery percentage. Null when the device has no battery
65107
65596
  * reading. */
65108
- batteryLevel: zod.z.number().min(0).max(100).nullable(),
65597
+ batteryLevel: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
65109
65598
  /** 0..100 mowing-completion percentage of the current task, or null when no
65110
65599
  * task is active / progress is unavailable. */
65111
- progressPercent: zod.z.number().min(0).max(100).nullable(),
65600
+ progressPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
65112
65601
  /** Current device/event code (dynamic — mostly status, sometimes an error),
65113
65602
  * or null when unknown. */
65114
- currentCode: zod.z.number().nullable(),
65603
+ currentCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
65115
65604
  /** Human label for {@link currentCode}, or null when undecodable. */
65116
65605
  currentCodeLabel: zod.z.string().nullable(),
65117
65606
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -65841,7 +66330,7 @@ var MediaPlayerStatusSchema = zod.z.object({
65841
66330
  state: MediaPlayerStateSchema,
65842
66331
  /** Volume as 0..100 inclusive. Null when the device has no volume
65843
66332
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
65844
- volumeLevel: zod.z.number().min(0).max(100).nullable(),
66333
+ volumeLevel: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
65845
66334
  /** Mute toggle distinct from volume=0. Null when no mute surface.
65846
66335
  * Pair with `DeviceFeature.MediaPlayerMute`. */
65847
66336
  isMuted: zod.z.boolean().nullable(),
@@ -65854,10 +66343,10 @@ var MediaPlayerStatusSchema = zod.z.object({
65854
66343
  currentMedia: MediaInfoSchema.nullable(),
65855
66344
  /** Current playback position in ms. Null when not seekable or
65856
66345
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
65857
- positionMs: zod.z.number().int().nonnegative().nullable(),
66346
+ positionMs: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration-ms")),
65858
66347
  /** Total duration of the current media in ms. Null when unknown
65859
66348
  * (live stream) or nothing is playing. */
65860
- durationMs: zod.z.number().int().nonnegative().nullable(),
66349
+ durationMs: zod.z.number().int().nonnegative().nullable().meta(require_sleep.readingMeta("duration-ms")),
65861
66350
  /** Shuffle toggle. Null when no shuffle surface.
65862
66351
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
65863
66352
  shuffle: zod.z.boolean().nullable(),
@@ -66237,7 +66726,7 @@ var motionCustomize = {
66237
66726
  eventName: "onMotionChanged"
66238
66727
  },
66239
66728
  deviceTypes: [require_err_msg.DeviceType.Camera],
66240
- apply: "replace",
66729
+ apply: "add",
66241
66730
  applyAuthority: {
66242
66731
  kind: "camera-sensors",
66243
66732
  timeoutField: "motionCooldownMs"
@@ -66334,7 +66823,7 @@ var MotionStatusSchema = zod.z.object({
66334
66823
  * it, every provider seeded it at boot, and every session left it
66335
66824
  * `detected: true` after its detach.
66336
66825
  */
66337
- autoClearAfterMs: zod.z.number().nullable()
66826
+ autoClearAfterMs: zod.z.number().nullable().meta(require_sleep.readingMeta("duration-ms"))
66338
66827
  });
66339
66828
  /**
66340
66829
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -66371,12 +66860,18 @@ var motionCapability = {
66371
66860
  methods: {
66372
66861
  /**
66373
66862
  * Pull the current motion state synchronously. Convenience shortcut
66374
- * for consumers that don't need the full status object; equivalent
66375
- * to `getStatus()?.detected ?? false`. Will likely be folded into
66376
- * `getStatus` once the auto-injected status surface lands in every
66377
- * consumer.
66863
+ * for consumers that don't need the full status object. A composed
66864
+ * group answers from its slice (D944): unknown throws, it is not off.
66378
66865
  */
66379
66866
  isDetected: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.boolean()) },
66867
+ /**
66868
+ * A composed motion group serves `isDetected` from `detected` (D944).
66869
+ * Without this the cap stays uncomposable (`has-commands`).
66870
+ */
66871
+ composedMethods: { isDetected: {
66872
+ kind: "slice",
66873
+ field: "detected"
66874
+ } },
66380
66875
  events: {
66381
66876
  /**
66382
66877
  * Fires every time the runner transitions a camera between
@@ -66520,7 +67015,7 @@ var MotionZoneRegionSchema = zod.z.object({
66520
67015
  * the grid region(s). */
66521
67016
  var MotionZoneStatusSchema = zod.z.object({
66522
67017
  enabled: zod.z.boolean(),
66523
- sensitivity: zod.z.number(),
67018
+ sensitivity: zod.z.number().meta(require_sleep.readingMeta("none")),
66524
67019
  /** Grid region(s). Today exactly one `grid` shape. */
66525
67020
  regions: zod.z.array(MotionZoneRegionSchema),
66526
67021
  lastFetchedAt: zod.z.number()
@@ -67036,7 +67531,7 @@ var NavigationStatusSchema = zod.z.object({
67036
67531
  following: zod.z.boolean(),
67037
67532
  flash: zod.z.boolean(),
67038
67533
  lightMode: NavigationLightModeSchema,
67039
- lightLevel: zod.z.number().min(40).max(100),
67534
+ lightLevel: zod.z.number().min(40).max(100).meta(require_sleep.readingMeta("none")),
67040
67535
  /** Ms epoch when the slice was last updated. */
67041
67536
  lastChangedAt: zod.z.number()
67042
67537
  });
@@ -67187,13 +67682,13 @@ var NetworkLinkStatusSchema = zod.z.object({
67187
67682
  * one whose reading has not landed must not be drawn at 0 %. Consumers
67188
67683
  * SKIP a null rather than coerce it.
67189
67684
  */
67190
- signalPercent: zod.z.number().min(0).max(100).nullable(),
67685
+ signalPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
67191
67686
  /** Raw received signal strength in dBm, when the firmware reports one. */
67192
- rssiDbm: zod.z.number().optional(),
67687
+ rssiDbm: zod.z.number().optional().meta(require_sleep.readingMeta("signal-level")),
67193
67688
  /** Network name of a wireless link, when the firmware reports it. */
67194
67689
  ssid: zod.z.string().optional(),
67195
67690
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
67196
- lastUpdated: zod.z.number()
67691
+ lastUpdated: zod.z.number().meta(require_sleep.readingMeta("none"))
67197
67692
  });
67198
67693
  /** The slice a provider seeds before its first read: nothing is known yet. */
67199
67694
  var NETWORK_LINK_UNKNOWN = {
@@ -67669,7 +68164,7 @@ var NotifierStatusSchema = zod.z.object({
67669
68164
  lastError: zod.z.string().nullable(),
67670
68165
  /** Number of deliveries currently buffered server-side (rate-limited
67671
68166
  * notifiers may queue). 0 when the provider sends synchronously. */
67672
- queueDepth: zod.z.number().int().nonnegative(),
68167
+ queueDepth: zod.z.number().int().nonnegative().meta(require_sleep.readingMeta("count")),
67673
68168
  /** Per-feature capability matrix — authoritative source for the
67674
68169
  * compose-form field gating. */
67675
68170
  supports: NotifierSupportsSchema
@@ -67763,11 +68258,9 @@ onSent: { data: zod.z.object({
67763
68258
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
67764
68259
  * for: water flow, distance, weight, frequency, signal strength, …).
67765
68260
  *
67766
- * The `unit` and `precision` fields carry the live display metadata that
67767
- * the upstream source provides with each state push (HA
67768
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
67769
- * The UI reads them directly from the slice — they are the single source
67770
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
68261
+ * The quantity is not on this slice. It is chosen when the device is
68262
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
68263
+ * that quantity's base unit. `null` is a refused sample (D393).
67771
68264
  *
67772
68265
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
67773
68266
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -67776,15 +68269,8 @@ onSent: { data: zod.z.object({
67776
68269
  * numeric values land here.
67777
68270
  */
67778
68271
  var NumericSensorStatusSchema = zod.z.object({
67779
- value: zod.z.number(),
67780
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
67781
- * Populated live from the upstream source on each state push.
67782
- * Absent for unitless sensors. */
67783
- unit: zod.z.string().optional(),
67784
- /** Suggested decimal places for numeric display.
67785
- * Populated live from the upstream source when provided.
67786
- * Falls back to auto-formatting when absent. */
67787
- precision: zod.z.number().int().min(0).max(10).optional(),
68272
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
68273
+ value: zod.z.number().nullable().meta(require_sleep.readingMeta("device")),
67788
68274
  /** Ms epoch when the slice was last updated. */
67789
68275
  lastFetchedAt: zod.z.number()
67790
68276
  });
@@ -67808,11 +68294,11 @@ var numericSensorCapability = {
67808
68294
  */
67809
68295
  durability: "restored",
67810
68296
  /**
67811
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
68297
+ * History (D850): recorded series. The unit is the device quantity's base,
68298
+ * resolved when the series is read — the cap has no single unit.
67812
68299
  */
67813
68300
  history: { fields: { value: {
67814
68301
  kind: "gauge",
67815
- unitFrom: "unit",
67816
68302
  minIntervalMs: 3e4
67817
68303
  } } },
67818
68304
  /** Clock fields: written, but excluded from the compare that decides
@@ -68269,23 +68755,26 @@ var PetFeederStatusSchema = zod.z.object({
68269
68755
  /** Food currently in the bowl (grams). Null when the device has not
68270
68756
  * reported a reading yet. On dual-hopper models this is the combined
68271
68757
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
68272
- foodLevel: zod.z.number().nullable(),
68758
+ foodLevel: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68273
68759
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
68274
68760
  * single-hopper models. */
68275
- food1: zod.z.number().nullable(),
68761
+ food1: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68276
68762
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
68277
68763
  * single-hopper models. */
68278
- food2: zod.z.number().nullable(),
68764
+ food2: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68279
68765
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
68280
68766
  * (`device_class: problem`, on = low). True when the bowl is empty /
68281
68767
  * below the feeder's low threshold. */
68282
68768
  lowFood: zod.z.boolean(),
68283
68769
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
68284
68770
  * device has no battery reading. */
68285
- batteryPower: zod.z.number().min(0).max(100).nullable(),
68286
- /** Days of desiccant life remaining. Null when the model has no
68287
- * desiccant sensor. */
68288
- desiccantLeftDays: zod.z.number().nullable(),
68771
+ batteryPower: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
68772
+ /**
68773
+ * Desiccant life remaining, stored in seconds (duration base, D944).
68774
+ * Providers convert from the days the feeder reports. Null when the
68775
+ * model has no desiccant sensor.
68776
+ */
68777
+ desiccantLeftDays: zod.z.number().nullable().meta(require_sleep.readingMeta("duration")),
68289
68778
  /** True while a feed is in progress. */
68290
68779
  feeding: zod.z.boolean(),
68291
68780
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -68296,7 +68785,7 @@ var PetFeederStatusSchema = zod.z.object({
68296
68785
  * with `errorCode` for consumers that want the raw integer. */
68297
68786
  error: zod.z.string().nullable(),
68298
68787
  /** Raw device error code (0 / null = no error). */
68299
- errorCode: zod.z.number().nullable(),
68788
+ errorCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
68300
68789
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
68301
68790
  isDualHopper: zod.z.boolean(),
68302
68791
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -68306,7 +68795,7 @@ var PetFeederStatusSchema = zod.z.object({
68306
68795
  /** Play a chime when dispensing. */
68307
68796
  feedSound: zod.z.boolean(),
68308
68797
  /** Speaker / prompt volume level (device-scaled integer). */
68309
- volume: zod.z.number(),
68798
+ volume: zod.z.number().meta(require_sleep.readingMeta("none")),
68310
68799
  /** Ms epoch when the slice was last refreshed from the cloud. */
68311
68800
  lastFetchedAt: zod.z.number()
68312
68801
  });
@@ -68425,7 +68914,7 @@ var petFeederCapability = {
68425
68914
  },
68426
68915
  desiccantLeftDays: {
68427
68916
  kind: "gauge",
68428
- unit: "days"
68917
+ unit: "s"
68429
68918
  }
68430
68919
  } }
68431
68920
  };
@@ -69631,36 +70120,19 @@ var platformProbeCapability = {
69631
70120
  */
69632
70121
  var PowerMeterStatusSchema = zod.z.object({
69633
70122
  /** Instantaneous power draw in watts. */
69634
- watts: zod.z.number().optional().meta({
69635
- unit: "W",
69636
- label: "Power"
69637
- }),
70123
+ watts: zod.z.number().optional().meta(require_sleep.readingMeta("power", { label: "Power" })),
69638
70124
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
69639
- kwhTotal: zod.z.number().optional().meta({
69640
- unit: "kWh",
69641
- label: "Energy"
69642
- }),
70125
+ kwhTotal: zod.z.number().optional().meta(require_sleep.readingMeta("energy", { label: "Energy" })),
69643
70126
  /** Voltage in volts. */
69644
- volts: zod.z.number().optional(),
70127
+ volts: zod.z.number().optional().meta(require_sleep.readingMeta("voltage")),
69645
70128
  /** Current in amperes. */
69646
- amps: zod.z.number().optional(),
69647
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
69648
- va: zod.z.number().optional(),
70129
+ amps: zod.z.number().optional().meta(require_sleep.readingMeta("current")),
70130
+ /** Apparent power in volt-amperes. */
70131
+ va: zod.z.number().optional().meta(require_sleep.readingMeta("apparent-power")),
69649
70132
  /** Power factor (0..1) if reported. */
69650
- powerFactor: zod.z.number().min(0).max(1).optional(),
70133
+ powerFactor: zod.z.number().min(0).max(1).optional().meta(require_sleep.readingMeta("ratio")),
69651
70134
  /** Ms epoch when the slice was last updated. */
69652
- lastFetchedAt: zod.z.number(),
69653
- /** Live display unit of the single metric this slice carries (e.g. HA
69654
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
69655
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
69656
- * unit per slice is unambiguous. The UI prefers this over the role's
69657
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
69658
- unit: zod.z.string().optional(),
69659
- /** Suggested decimal places for numeric display.
69660
- * Populated live from the upstream source when provided (e.g. HA
69661
- * `attributes.suggested_display_precision`). Falls back to
69662
- * auto-formatting when absent. */
69663
- precision: zod.z.number().int().min(0).max(10).optional()
70135
+ lastFetchedAt: zod.z.number()
69664
70136
  });
69665
70137
  var powerMeterCapability = {
69666
70138
  name: "power-meter",
@@ -69724,12 +70196,7 @@ var powerMeterCapability = {
69724
70196
  } },
69725
70197
  /** Clock fields: written, but excluded from the compare that decides
69726
70198
  * whether persisting is worth a SQLite commit. */
69727
- volatileStateFields: ["lastFetchedAt"],
69728
- customize: {
69729
- version: 1,
69730
- advanced: "derive",
69731
- targets: []
69732
- }
70199
+ volatileStateFields: ["lastFetchedAt"]
69733
70200
  };
69734
70201
  //#endregion
69735
70202
  //#region src/capabilities/presence.cap.ts
@@ -69751,11 +70218,11 @@ var powerMeterCapability = {
69751
70218
  */
69752
70219
  var GpsLocationSchema = zod.z.object({
69753
70220
  /** Latitude in decimal degrees, -90..90. */
69754
- latitude: zod.z.number().min(-90).max(90),
70221
+ latitude: zod.z.number().min(-90).max(90).meta(require_sleep.readingMeta("angle")),
69755
70222
  /** Longitude in decimal degrees, -180..180. */
69756
- longitude: zod.z.number().min(-180).max(180),
70223
+ longitude: zod.z.number().min(-180).max(180).meta(require_sleep.readingMeta("angle")),
69757
70224
  /** Reported accuracy in meters (lower = better). */
69758
- accuracyMeters: zod.z.number().nonnegative()
70225
+ accuracyMeters: zod.z.number().nonnegative().meta(require_sleep.readingMeta("length"))
69759
70226
  });
69760
70227
  var PresenceStatusSchema = zod.z.object({
69761
70228
  /** `home` / `not_home` / any user-defined zone name. */
@@ -69767,7 +70234,7 @@ var PresenceStatusSchema = zod.z.object({
69767
70234
  gps: GpsLocationSchema.nullable(),
69768
70235
  /** Optional battery level of the tracking device (0..100). Null
69769
70236
  * when not reported. */
69770
- batteryPercent: zod.z.number().min(0).max(100).nullable(),
70237
+ batteryPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
69771
70238
  /** Ms epoch when the slice was last updated. */
69772
70239
  lastChangedAt: zod.z.number()
69773
70240
  });
@@ -69819,19 +70286,10 @@ var presenceCapability = {
69819
70286
  * `sourceInfo`.
69820
70287
  */
69821
70288
  var PressureSensorStatusSchema = zod.z.object({
69822
- /** Current pressure in hPa. */
69823
- hpa: zod.z.number(),
70289
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
70290
+ hpa: zod.z.number().nullable().meta(require_sleep.readingMeta("pressure", { precision: 0 })),
69824
70291
  /** Ms epoch when the slice was last updated. */
69825
- lastFetchedAt: zod.z.number(),
69826
- /** Live display unit from the upstream source (e.g. HA
69827
- * `attributes.unit_of_measurement`). The UI prefers this over the
69828
- * role's canonical unit. Absent → fall back to the canonical unit. */
69829
- unit: zod.z.string().optional(),
69830
- /** Suggested decimal places for numeric display.
69831
- * Populated live from the upstream source when provided (e.g. HA
69832
- * `attributes.suggested_display_precision`). Falls back to
69833
- * auto-formatting when absent. */
69834
- precision: zod.z.number().int().min(0).max(10).optional()
70292
+ lastFetchedAt: zod.z.number()
69835
70293
  });
69836
70294
  var pressureSensorCapability = {
69837
70295
  name: "pressure-sensor",
@@ -70398,8 +70856,8 @@ var ptzCapability = {
70398
70856
  var AutotrackTargetTypeSchema = zod.z.string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
70399
70857
  var PtzAutotrackSettingsSchema = zod.z.object({
70400
70858
  targetType: AutotrackTargetTypeSchema,
70401
- stopDelaySeconds: zod.z.number().int().min(0).max(300),
70402
- disappearDelaySeconds: zod.z.number().int().min(0).max(300)
70859
+ stopDelaySeconds: zod.z.number().int().min(0).max(300).meta(require_sleep.readingMeta("duration")),
70860
+ disappearDelaySeconds: zod.z.number().int().min(0).max(300).meta(require_sleep.readingMeta("duration"))
70403
70861
  });
70404
70862
  /**
70405
70863
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -70566,7 +71024,7 @@ var RunningStateStatusSchema = zod.z.object({
70566
71024
  /** null = unknown — never coerced to false (D393). */
70567
71025
  running: zod.z.boolean().nullable(),
70568
71026
  /** Epoch ms the current boolean began; null while `running` is null. */
70569
- since: zod.z.number().nullable(),
71027
+ since: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
70570
71028
  source: RunningStateSourceSchema.meta({ label: "Source" }),
70571
71029
  phase: zod.z.string().nullable(),
70572
71030
  /** null until a reading has been published (D393). */
@@ -70581,7 +71039,6 @@ var RUNNING_DEVICE_TYPES = Object.values(require_err_msg.DeviceType);
70581
71039
  /** Generic Customize target for the running boolean (D936). */
70582
71040
  var runningCustomize = {
70583
71041
  version: 1,
70584
- advanced: "derive",
70585
71042
  targets: [{
70586
71043
  id: "running",
70587
71044
  cardinality: "scalar",
@@ -70601,7 +71058,7 @@ var runningCustomize = {
70601
71058
  require_err_msg.DeviceType.Thermostat,
70602
71059
  require_err_msg.DeviceType.PetFeeder
70603
71060
  ],
70604
- apply: "add-or-replace",
71061
+ apply: "add",
70605
71062
  path: "running",
70606
71063
  sources: [
70607
71064
  {
@@ -72449,9 +72906,9 @@ var RecordTriggerSchema = zod.z.enum([
72449
72906
  */
72450
72907
  var RecordWindowSchema = zod.z.object({
72451
72908
  trigger: RecordTriggerSchema,
72452
- day: zod.z.number().int().min(0).max(6),
72453
- startMinute: zod.z.number().int().min(0).max(1439),
72454
- endMinute: zod.z.number().int().min(1).max(1440)
72909
+ day: zod.z.number().int().min(0).max(6).meta(require_sleep.readingMeta("none")),
72910
+ startMinute: zod.z.number().int().min(0).max(1439).meta(require_sleep.readingMeta("none")),
72911
+ endMinute: zod.z.number().int().min(1).max(1440).meta(require_sleep.readingMeta("none"))
72455
72912
  });
72456
72913
  /** Status of one physical volume, as the camera itself describes it. */
72457
72914
  var OnboardStorageVolumeSchema = zod.z.object({
@@ -72550,10 +73007,10 @@ var RecordingOnboardStatusSchema = zod.z.object({
72550
73007
  /** Master "record to the card at all" switch. */
72551
73008
  enabled: zod.z.boolean().nullable(),
72552
73009
  overwriteWhenFull: zod.z.boolean().nullable(),
72553
- preRecordSec: zod.z.number().nullable(),
72554
- postRecordSec: zod.z.number().nullable(),
73010
+ preRecordSec: zod.z.number().nullable().meta(require_sleep.readingMeta("duration")),
73011
+ postRecordSec: zod.z.number().nullable().meta(require_sleep.readingMeta("duration")),
72555
73012
  /** Length of one recorded file, in minutes. */
72556
- segmentMinutes: zod.z.number().nullable(),
73013
+ segmentMinutes: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
72557
73014
  /** The primary track's weekly windows, flattened. */
72558
73015
  windows: zod.z.array(RecordWindowSchema),
72559
73016
  /**
@@ -72569,7 +73026,7 @@ var RecordingOnboardStatusSchema = zod.z.object({
72569
73026
  * provider reporting a non-zero count also reports the schedule as not
72570
73027
  * writable.
72571
73028
  */
72572
- unreadableWindows: zod.z.number(),
73029
+ unreadableWindows: zod.z.number().meta(require_sleep.readingMeta("count")),
72573
73030
  /**
72574
73031
  * The camera is scheduled to record and has NO usable storage.
72575
73032
  *
@@ -73741,7 +74198,7 @@ var CamStreamDescriptorSchema = zod.z.object({
73741
74198
  url: zod.z.string().optional(),
73742
74199
  codec: zod.z.string().optional(),
73743
74200
  resolution: require_sleep.CamStreamResolutionSchema.optional(),
73744
- fps: zod.z.number().positive().optional(),
74201
+ fps: zod.z.number().positive().optional().meta(require_sleep.readingMeta("rate")),
73745
74202
  label: zod.z.string().optional(),
73746
74203
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
73747
74204
  deviceFeatures: zod.z.array(zod.z.string()).optional(),
@@ -73830,18 +74287,18 @@ var StreamProfileSchema = zod.z.enum([
73830
74287
  ]);
73831
74288
  /** The current encoding config of one stream profile. */
73832
74289
  var StreamProfileConfigSchema = zod.z.object({
73833
- width: zod.z.number(),
73834
- height: zod.z.number(),
74290
+ width: zod.z.number().meta(require_sleep.readingMeta("none")),
74291
+ height: zod.z.number().meta(require_sleep.readingMeta("none")),
73835
74292
  codec: zod.z.enum(["h264", "h265"]),
73836
- framerate: zod.z.number(),
73837
- bitrate: zod.z.number(),
74293
+ framerate: zod.z.number().meta(require_sleep.readingMeta("rate")),
74294
+ bitrate: zod.z.number().meta(require_sleep.readingMeta("bitrate")),
73838
74295
  bitrateMode: zod.z.enum(["vbr", "cbr"]).optional(),
73839
74296
  encoderProfile: zod.z.enum([
73840
74297
  "high",
73841
74298
  "main",
73842
74299
  "baseline"
73843
74300
  ]).optional(),
73844
- gop: zod.z.number().optional(),
74301
+ gop: zod.z.number().optional().meta(require_sleep.readingMeta("none")),
73845
74302
  /**
73846
74303
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
73847
74304
  *
@@ -74323,12 +74780,7 @@ var switchCapability = {
74323
74780
  label: "On",
74324
74781
  kind: "boolean"
74325
74782
  }
74326
- }] },
74327
- customize: {
74328
- version: 1,
74329
- advanced: "derive",
74330
- targets: []
74331
- }
74783
+ }] }
74332
74784
  };
74333
74785
  //#endregion
74334
74786
  //#region src/system/server-name.ts
@@ -75076,35 +75528,21 @@ var tamperCapability = {
75076
75528
  * entries with `device_class: temperature` and any future native
75077
75529
  * thermometer.
75078
75530
  *
75079
- * Unit normalisation: providers convert to Celsius before storing.
75080
- * The slice value is always Celsius so cross-cap aggregators
75081
- * (climate-control's `currentTemp`, energy analytics) can compose
75082
- * without per-source unit fixups. The canonical display unit (`°C`) is
75083
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
75084
- * `sourceInfo`.
75531
+ * The number is Celsius (D944). Providers convert on ingest. A failed
75532
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
75085
75533
  *
75086
75534
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
75087
75535
  * frozen feed (provider hung) distinct from a "temperature hasn't
75088
75536
  * changed" steady state.
75089
75537
  */
75090
75538
  var TemperatureSensorStatusSchema = zod.z.object({
75091
- /** Current temperature in Celsius. */
75092
- celsius: zod.z.number().meta({
75093
- unit: "°C",
75539
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
75540
+ celsius: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature", {
75541
+ precision: 1,
75094
75542
  label: "Temperature"
75095
- }),
75543
+ })),
75096
75544
  /** Ms epoch when the slice was last updated (push or poll). */
75097
- lastFetchedAt: zod.z.number(),
75098
- /** Live display unit from the upstream source (e.g. HA
75099
- * `attributes.unit_of_measurement`). The UI prefers this over the
75100
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
75101
- * Absent → the UI falls back to the role's canonical unit. */
75102
- unit: zod.z.string().optional(),
75103
- /** Suggested decimal places for numeric display.
75104
- * Populated live from the upstream source when provided (e.g. HA
75105
- * `attributes.suggested_display_precision`). Falls back to
75106
- * auto-formatting when absent. */
75107
- precision: zod.z.number().int().min(0).max(10).optional()
75545
+ lastFetchedAt: zod.z.number()
75108
75546
  });
75109
75547
  var temperatureSensorCapability = {
75110
75548
  name: "temperature-sensor",
@@ -75135,12 +75573,7 @@ var temperatureSensorCapability = {
75135
75573
  } } },
75136
75574
  /** Clock fields: written, but excluded from the compare that decides
75137
75575
  * whether persisting is worth a SQLite commit. */
75138
- volatileStateFields: ["lastFetchedAt"],
75139
- customize: {
75140
- version: 1,
75141
- advanced: "derive",
75142
- targets: []
75143
- }
75576
+ volatileStateFields: ["lastFetchedAt"]
75144
75577
  };
75145
75578
  //#endregion
75146
75579
  //#region src/capabilities/toast.cap.ts
@@ -75494,16 +75927,16 @@ var VacuumControlStatusSchema = zod.z.object({
75494
75927
  state: VacuumStateSchema,
75495
75928
  /** 0..100 battery percentage. Null when the device has no battery
75496
75929
  * reading. */
75497
- batteryLevel: zod.z.number().min(0).max(100).nullable(),
75930
+ batteryLevel: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
75498
75931
  /** Current fan-speed token (provider-verbatim). Null when unknown or
75499
75932
  * the vacuum has no speed control. */
75500
75933
  fanSpeed: zod.z.string().nullable(),
75501
75934
  /** Speed tokens the hardware accepts — drives the UI selector. */
75502
75935
  availableFanSpeeds: zod.z.array(zod.z.string()),
75503
75936
  /** 0..100 cleaning-completion percentage of the current task, or null. */
75504
- progressPercent: zod.z.number().min(0).max(100).nullable(),
75937
+ progressPercent: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
75505
75938
  /** Current error code (0 / null = no error). */
75506
- errorCode: zod.z.number().nullable(),
75939
+ errorCode: zod.z.number().nullable().meta(require_sleep.readingMeta("none")),
75507
75940
  /** Human label for {@link errorCode}, or null when none / undecodable. */
75508
75941
  errorLabel: zod.z.string().nullable(),
75509
75942
  /** Ms epoch when the slice was last updated. */
@@ -75601,7 +76034,7 @@ var ValveStatusSchema = zod.z.object({
75601
76034
  state: ValveStateSchema,
75602
76035
  /** 0 = fully closed, 100 = fully open. Null when the device has no
75603
76036
  * intermediate position surface. */
75604
- position: zod.z.number().min(0).max(100).nullable(),
76037
+ position: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("percentage")),
75605
76038
  /** Ms epoch when the slice was last updated. */
75606
76039
  lastChangedAt: zod.z.number()
75607
76040
  });
@@ -75713,9 +76146,9 @@ var vibrationCapability = {
75713
76146
  */
75714
76147
  var WaterHeaterStatusSchema = zod.z.object({
75715
76148
  /** Current measured temperature. Null when not reported. */
75716
- currentTemp: zod.z.number().nullable(),
76149
+ currentTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75717
76150
  /** Target temperature setpoint. Null when no setpoint surface. */
75718
- targetTemp: zod.z.number().nullable(),
76151
+ targetTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75719
76152
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
75720
76153
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
75721
76154
  * device reports an unknown state. */
@@ -75726,9 +76159,9 @@ var WaterHeaterStatusSchema = zod.z.object({
75726
76159
  * has no away surface. */
75727
76160
  away: zod.z.boolean().nullable(),
75728
76161
  /** HA `min_temp` attribute. Null when not reported. */
75729
- minTemp: zod.z.number().nullable(),
76162
+ minTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75730
76163
  /** HA `max_temp` attribute. Null when not reported. */
75731
- maxTemp: zod.z.number().nullable(),
76164
+ maxTemp: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature")),
75732
76165
  /** Ms epoch when the slice was last updated. */
75733
76166
  lastChangedAt: zod.z.number()
75734
76167
  });
@@ -75815,25 +76248,18 @@ var waterHeaterCapability = {
75815
76248
  * Forecast deferred — current conditions only for v1.
75816
76249
  */
75817
76250
  var WeatherStatusSchema = zod.z.object({
75818
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
75819
- * when no condition has been reported yet. */
76251
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
75820
76252
  condition: zod.z.string().nullable(),
75821
- /** Current temperature in the reported unit. Null when not provided. */
75822
- temperature: zod.z.number().nullable(),
75823
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
75824
- temperatureUnit: zod.z.string().nullable(),
76253
+ /** Current temperature in Celsius. Null when not provided or refused. */
76254
+ temperature: zod.z.number().nullable().meta(require_sleep.readingMeta("temperature", { precision: 1 })),
75825
76255
  /** Relative humidity (0..100). Null when not provided. */
75826
- humidity: zod.z.number().min(0).max(100).nullable(),
75827
- /** Barometric pressure in the reported unit. Null when not provided. */
75828
- pressure: zod.z.number().nullable(),
75829
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
75830
- pressureUnit: zod.z.string().nullable(),
75831
- /** Wind speed in the reported unit. Null when not provided. */
75832
- windSpeed: zod.z.number().nullable(),
75833
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
75834
- windSpeedUnit: zod.z.string().nullable(),
76256
+ humidity: zod.z.number().min(0).max(100).nullable().meta(require_sleep.readingMeta("humidity")),
76257
+ /** Barometric pressure in hPa. Null when not provided or refused. */
76258
+ pressure: zod.z.number().nullable().meta(require_sleep.readingMeta("pressure")),
76259
+ /** Wind speed in metres per second. Null when not provided or refused. */
76260
+ windSpeed: zod.z.number().nullable().meta(require_sleep.readingMeta("speed")),
75835
76261
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
75836
- windBearing: zod.z.number().nullable(),
76262
+ windBearing: zod.z.number().nullable().meta(require_sleep.readingMeta("angle")),
75837
76263
  /** Ms epoch when the slice was last updated. */
75838
76264
  lastFetchedAt: zod.z.number()
75839
76265
  });
@@ -75861,12 +76287,12 @@ var weatherCapability = {
75861
76287
  */
75862
76288
  durability: "session",
75863
76289
  /**
75864
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
76290
+ * History (D850): recorded series. Numbers are the quantity base (D944).
75865
76291
  */
75866
76292
  history: { fields: {
75867
76293
  temperature: {
75868
76294
  kind: "gauge",
75869
- unitFrom: "temperatureUnit",
76295
+ unit: "°C",
75870
76296
  deadband: .2
75871
76297
  },
75872
76298
  humidity: {
@@ -75876,12 +76302,12 @@ var weatherCapability = {
75876
76302
  },
75877
76303
  pressure: {
75878
76304
  kind: "gauge",
75879
- unitFrom: "pressureUnit",
76305
+ unit: "hPa",
75880
76306
  deadband: .5
75881
76307
  },
75882
76308
  windSpeed: {
75883
76309
  kind: "gauge",
75884
- unitFrom: "windSpeedUnit",
76310
+ unit: "m/s",
75885
76311
  deadband: .5
75886
76312
  }
75887
76313
  } }
@@ -77192,6 +77618,127 @@ function createDeviceExtensionProvider(options) {
77192
77618
  };
77193
77619
  }
77194
77620
  //#endregion
77621
+ //#region src/composition/helpers/aggregate.ts
77622
+ function isBinaryReducer(reducer) {
77623
+ return reducer === "any" || reducer === "all";
77624
+ }
77625
+ function includedValue(reducer, member) {
77626
+ if (member.state !== "fresh") return null;
77627
+ if (isBinaryReducer(reducer)) return typeof member.value === "boolean" ? member.value : null;
77628
+ return typeof member.value === "number" && Number.isFinite(member.value) ? member.value : null;
77629
+ }
77630
+ function median$1(values) {
77631
+ const sorted = [...values].toSorted((a, b) => a - b);
77632
+ const mid = Math.floor(sorted.length / 2);
77633
+ const right = sorted[mid];
77634
+ const left = sorted[mid - 1];
77635
+ if (right === void 0) throw new Error("median requires at least one finite number");
77636
+ if (sorted.length % 2 === 1) return right;
77637
+ if (left === void 0) throw new Error("median requires at least one finite number");
77638
+ return (left + right) / 2;
77639
+ }
77640
+ function reduceNumbers(reducer, values) {
77641
+ if (reducer === "min") return Math.min(...values);
77642
+ if (reducer === "max") return Math.max(...values);
77643
+ if (reducer === "sum") return values.reduce((sum, value) => sum + value, 0);
77644
+ if (reducer === "median") return median$1(values);
77645
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
77646
+ }
77647
+ function reduceBooleans(reducer, values) {
77648
+ return reducer === "all" ? values.every((value) => value) : values.some((value) => value);
77649
+ }
77650
+ function countsOf(included, total) {
77651
+ return {
77652
+ included,
77653
+ excluded: total - included,
77654
+ total
77655
+ };
77656
+ }
77657
+ function fatalReason(members) {
77658
+ const reasons = members.flatMap((member) => member.state === "unavailable" && member.fatal === true ? [member.reason ?? "source gone"] : []);
77659
+ return reasons.length === 0 ? null : reasons.join("; ");
77660
+ }
77661
+ function emptyOutcome(empty, counts) {
77662
+ if (empty === "null") return {
77663
+ kind: "value",
77664
+ value: null,
77665
+ counts
77666
+ };
77667
+ return {
77668
+ kind: "unavailable",
77669
+ fatal: false,
77670
+ reason: "no member included — unknown is not 0 or off",
77671
+ counts
77672
+ };
77673
+ }
77674
+ /** Pure. `members` is not mutated. An empty included set is null or unavailable, never 0 or false. */
77675
+ function evaluateAggregate(input) {
77676
+ const included = input.members.flatMap((member) => {
77677
+ const value = includedValue(input.reducer, member);
77678
+ return value === null ? [] : [value];
77679
+ });
77680
+ const counts = countsOf(included.length, input.members.length);
77681
+ const gone = fatalReason(input.members);
77682
+ if (gone !== null) return {
77683
+ kind: "unavailable",
77684
+ fatal: true,
77685
+ reason: gone,
77686
+ counts
77687
+ };
77688
+ if (input.publish === "included") return {
77689
+ kind: "value",
77690
+ value: counts.included,
77691
+ counts
77692
+ };
77693
+ if (input.publish === "excluded") return {
77694
+ kind: "value",
77695
+ value: counts.excluded,
77696
+ counts
77697
+ };
77698
+ if (input.publish === "total") return {
77699
+ kind: "value",
77700
+ value: counts.total,
77701
+ counts
77702
+ };
77703
+ if (included.length === 0) return emptyOutcome(input.empty, counts);
77704
+ if (isBinaryReducer(input.reducer)) {
77705
+ const flags = included.filter((value) => typeof value === "boolean");
77706
+ return {
77707
+ kind: "value",
77708
+ value: reduceBooleans(input.reducer, flags),
77709
+ counts
77710
+ };
77711
+ }
77712
+ const numbers = included.filter((value) => typeof value === "number");
77713
+ return {
77714
+ kind: "value",
77715
+ value: reduceNumbers(input.reducer, numbers),
77716
+ counts
77717
+ };
77718
+ }
77719
+ //#endregion
77720
+ //#region src/composition/helpers/source-pick.ts
77721
+ /** What the member step asks the selector for this template. */
77722
+ function sourcePickForTemplate(template) {
77723
+ const valueKind = template.family === "numeric" ? "number" : "boolean";
77724
+ return {
77725
+ purpose: "member",
77726
+ multiple: true,
77727
+ max: 64,
77728
+ deviceTypes: [template.memberDeviceType],
77729
+ accept: template.accept.map((field) => ({
77730
+ ...field,
77731
+ valueKind
77732
+ })),
77733
+ history: {
77734
+ required: false,
77735
+ minRetentionMs: null
77736
+ },
77737
+ mustBindCaps: [],
77738
+ exclude: []
77739
+ };
77740
+ }
77741
+ //#endregion
77195
77742
  //#region src/units/unit-table.ts
77196
77743
  /**
77197
77744
  * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
@@ -77320,6 +77867,11 @@ var UNIT_TABLE = {
77320
77867
  factor: 1609.344,
77321
77868
  offset: 0
77322
77869
  },
77870
+ "m²": {
77871
+ dimension: "area",
77872
+ factor: 1,
77873
+ offset: 0
77874
+ },
77323
77875
  lx: {
77324
77876
  dimension: "illuminance",
77325
77877
  factor: 1,
@@ -77424,6 +77976,121 @@ var UNIT_TABLE = {
77424
77976
  dimension: "percentage",
77425
77977
  factor: 1,
77426
77978
  offset: 0
77979
+ },
77980
+ L: {
77981
+ dimension: "volume",
77982
+ factor: 1,
77983
+ offset: 0
77984
+ },
77985
+ mL: {
77986
+ dimension: "volume",
77987
+ factor: .001,
77988
+ offset: 0
77989
+ },
77990
+ gal: {
77991
+ dimension: "volume",
77992
+ factor: 3.785411784,
77993
+ offset: 0
77994
+ },
77995
+ s: {
77996
+ dimension: "duration",
77997
+ factor: 1,
77998
+ offset: 0
77999
+ },
78000
+ ms: {
78001
+ dimension: "duration",
78002
+ factor: .001,
78003
+ offset: 0
78004
+ },
78005
+ min: {
78006
+ dimension: "duration",
78007
+ factor: 60,
78008
+ offset: 0
78009
+ },
78010
+ h: {
78011
+ dimension: "duration",
78012
+ factor: 3600,
78013
+ offset: 0
78014
+ },
78015
+ d: {
78016
+ dimension: "duration",
78017
+ factor: 86400,
78018
+ offset: 0
78019
+ },
78020
+ B: {
78021
+ dimension: "information",
78022
+ factor: 1,
78023
+ offset: 0
78024
+ },
78025
+ KiB: {
78026
+ dimension: "information",
78027
+ factor: 1024,
78028
+ offset: 0
78029
+ },
78030
+ MiB: {
78031
+ dimension: "information",
78032
+ factor: 1024 ** 2,
78033
+ offset: 0
78034
+ },
78035
+ GiB: {
78036
+ dimension: "information",
78037
+ factor: 1024 ** 3,
78038
+ offset: 0
78039
+ },
78040
+ TiB: {
78041
+ dimension: "information",
78042
+ factor: 1024 ** 4,
78043
+ offset: 0
78044
+ },
78045
+ kB: {
78046
+ dimension: "information",
78047
+ factor: 1e3,
78048
+ offset: 0
78049
+ },
78050
+ MB: {
78051
+ dimension: "information",
78052
+ factor: 1e6,
78053
+ offset: 0
78054
+ },
78055
+ GB: {
78056
+ dimension: "information",
78057
+ factor: 1e9,
78058
+ offset: 0
78059
+ },
78060
+ "°": {
78061
+ dimension: "angle",
78062
+ factor: 1,
78063
+ offset: 0
78064
+ },
78065
+ dBFS: {
78066
+ dimension: "sound-level",
78067
+ factor: 1,
78068
+ offset: 0
78069
+ },
78070
+ dBm: {
78071
+ dimension: "signal-level",
78072
+ factor: 1,
78073
+ offset: 0
78074
+ },
78075
+ mired: {
78076
+ dimension: "colour-temperature",
78077
+ factor: 1,
78078
+ offset: 0
78079
+ },
78080
+ kbps: {
78081
+ dimension: "bitrate",
78082
+ factor: 1,
78083
+ offset: 0
78084
+ },
78085
+ Mbps: {
78086
+ dimension: "bitrate",
78087
+ factor: 1e3,
78088
+ offset: 0
78089
+ },
78090
+ fps: {
78091
+ dimension: "rate",
78092
+ factor: 1,
78093
+ offset: 0
77427
78094
  }
77428
78095
  };
77429
78096
  //#endregion
@@ -77514,6 +78181,17 @@ function canConvertUnit(from, to) {
77514
78181
  * Every known unit spelling in a dimension (table insertion order — canonical
77515
78182
  * unit first). Powers the UI's same-dimension unit picker.
77516
78183
  */
78184
+ /**
78185
+ * Convert a difference, not a reading. Affine units (°F) shift an absolute
78186
+ * value by their offset; a step of 1°F is 5/9°C, so the offset is cancelled.
78187
+ */
78188
+ function tryConvertDelta(value, from, to) {
78189
+ if (from === to) return value;
78190
+ const origin = tryConvertUnit(0, from, to);
78191
+ const shifted = tryConvertUnit(value, from, to);
78192
+ if (origin === void 0 || shifted === void 0) return void 0;
78193
+ return shifted - origin;
78194
+ }
77517
78195
  function unitsForDimension(dimension) {
77518
78196
  const out = [];
77519
78197
  for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
@@ -77647,11 +78325,45 @@ function expressionOutcome(source, read, now, stateful) {
77647
78325
  deadline: deadlines.length > 0 ? Math.min(...deadlines) : null
77648
78326
  };
77649
78327
  }
78328
+ function aggregateRead(reading) {
78329
+ if (reading.state === "unavailable") return {
78330
+ state: "unavailable",
78331
+ fatal: reading.fatal,
78332
+ reason: reading.reason
78333
+ };
78334
+ return {
78335
+ state: reading.state,
78336
+ value: reading.value,
78337
+ ...reading.state === "stale" ? { reason: reading.reason } : {}
78338
+ };
78339
+ }
78340
+ function aggregateField(source, read) {
78341
+ const outcome = evaluateAggregate({
78342
+ reducer: source.reducer,
78343
+ members: source.reads.map((item) => aggregateRead(read(item))),
78344
+ publish: source.publish,
78345
+ empty: source.empty
78346
+ });
78347
+ if (outcome.kind === "value") return {
78348
+ state: "fresh",
78349
+ value: outcome.value,
78350
+ reason: null,
78351
+ deadline: null
78352
+ };
78353
+ return {
78354
+ state: "unavailable",
78355
+ value: null,
78356
+ reason: outcome.reason,
78357
+ fatal: outcome.fatal,
78358
+ confirmed: true
78359
+ };
78360
+ }
77650
78361
  function evaluateCompositionField(input) {
77651
78362
  const { source } = input;
77652
78363
  switch (source.kind) {
77653
78364
  case "from": return fromReading(input.read(source), source);
77654
78365
  case "expression": return expressionOutcome(source, input.read, input.now, input.stateful);
78366
+ case "aggregate": return aggregateField(source, input.read);
77655
78367
  case "code": return {
77656
78368
  state: "unavailable",
77657
78369
  value: null,
@@ -77817,9 +78529,9 @@ function feederCustomizationExample(sources) {
77817
78529
  expr: "number(d)",
77818
78530
  bindings: days
77819
78531
  },
77820
- "remaining.unit": {
78532
+ "remaining.kind": {
77821
78533
  kind: "expression",
77822
- expr: "\"days\"",
78534
+ expr: "\"duration\"",
77823
78535
  bindings: {}
77824
78536
  },
77825
78537
  status: {
@@ -78085,19 +78797,9 @@ function convertUnitTargets(from) {
78085
78797
  //#region src/generated/customize-catalog.ts
78086
78798
  /**
78087
78799
  * Customize catalog, projected from every `*.cap.ts` `customize` block.
78088
- * 11 caps. Field keys are `runtimeState` paths. No Zod.
78800
+ * 5 caps. Field keys are `runtimeState` paths. No Zod.
78089
78801
  */
78090
78802
  var CAP_CUSTOMIZE_CATALOG = {
78091
- "ambient-light-sensor": {
78092
- version: 1,
78093
- targets: [],
78094
- advancedParameters: [{
78095
- path: "lux",
78096
- kind: "number",
78097
- label: "Illuminance",
78098
- unit: "lx"
78099
- }]
78100
- },
78101
78803
  "battery": {
78102
78804
  version: 1,
78103
78805
  targets: [{
@@ -78112,7 +78814,7 @@ var CAP_CUSTOMIZE_CATALOG = {
78112
78814
  require_err_msg.DeviceType.Container,
78113
78815
  require_err_msg.DeviceType.Thermostat
78114
78816
  ],
78115
- apply: "add-or-replace",
78817
+ apply: "add",
78116
78818
  vocabularyCap: "battery",
78117
78819
  vocabularyStateField: "percentage",
78118
78820
  sources: [{
@@ -78151,38 +78853,6 @@ var CAP_CUSTOMIZE_CATALOG = {
78151
78853
  when: "add"
78152
78854
  }
78153
78855
  ]
78154
- }],
78155
- advancedParameters: [
78156
- {
78157
- path: "binary",
78158
- kind: "boolean",
78159
- label: "Binary indicator"
78160
- },
78161
- {
78162
- path: "charging",
78163
- kind: "enum",
78164
- label: "Charging source",
78165
- enumValues: [
78166
- "dc",
78167
- "solar",
78168
- "none",
78169
- "unknown"
78170
- ]
78171
- },
78172
- {
78173
- path: "sleeping",
78174
- kind: "boolean",
78175
- label: "Sleeping"
78176
- }
78177
- ]
78178
- },
78179
- "binary": {
78180
- version: 1,
78181
- targets: [],
78182
- advancedParameters: [{
78183
- path: "on",
78184
- kind: "boolean",
78185
- label: "On"
78186
78856
  }]
78187
78857
  },
78188
78858
  "consumables": {
@@ -78260,12 +78930,8 @@ var CAP_CUSTOMIZE_CATALOG = {
78260
78930
  transforms: [{ kind: "identity" }]
78261
78931
  }],
78262
78932
  literals: [{
78263
- path: "remaining.unit",
78264
- values: [
78265
- "days",
78266
- "hours",
78267
- "cycles"
78268
- ]
78933
+ path: "remaining.kind",
78934
+ values: ["duration", "count"]
78269
78935
  }]
78270
78936
  },
78271
78937
  {
@@ -78323,8 +78989,7 @@ var CAP_CUSTOMIZE_CATALOG = {
78323
78989
  path: "lastFetchedAt",
78324
78990
  when: "add"
78325
78991
  }]
78326
- }],
78327
- advancedParameters: []
78992
+ }]
78328
78993
  },
78329
78994
  "doorbell": {
78330
78995
  version: 1,
@@ -78356,17 +79021,6 @@ var CAP_CUSTOMIZE_CATALOG = {
78356
79021
  counterPath: "pressCountSinceStart",
78357
79022
  highWater: "composer-advance",
78358
79023
  debounceMs: 2e3
78359
- }],
78360
- advancedParameters: []
78361
- },
78362
- "humidity-sensor": {
78363
- version: 1,
78364
- targets: [],
78365
- advancedParameters: [{
78366
- path: "percent",
78367
- kind: "number",
78368
- label: "Humidity",
78369
- unit: "%"
78370
79024
  }]
78371
79025
  },
78372
79026
  "motion": {
@@ -78375,7 +79029,7 @@ var CAP_CUSTOMIZE_CATALOG = {
78375
79029
  id: "detected",
78376
79030
  cardinality: "event",
78377
79031
  deviceTypes: [require_err_msg.DeviceType.Camera],
78378
- apply: "replace",
79032
+ apply: "add",
78379
79033
  vocabularyCap: "motion",
78380
79034
  vocabularyEvent: "onMotionChanged",
78381
79035
  sources: [{
@@ -78404,22 +79058,6 @@ var CAP_CUSTOMIZE_CATALOG = {
78404
79058
  "confirm",
78405
79059
  "replace"
78406
79060
  ]
78407
- }],
78408
- advancedParameters: []
78409
- },
78410
- "power-meter": {
78411
- version: 1,
78412
- targets: [],
78413
- advancedParameters: [{
78414
- path: "kwhTotal",
78415
- kind: "number",
78416
- label: "Energy",
78417
- unit: "kWh"
78418
- }, {
78419
- path: "watts",
78420
- kind: "number",
78421
- label: "Power",
78422
- unit: "W"
78423
79061
  }]
78424
79062
  },
78425
79063
  "running-state": {
@@ -78439,7 +79077,7 @@ var CAP_CUSTOMIZE_CATALOG = {
78439
79077
  require_err_msg.DeviceType.Thermostat,
78440
79078
  require_err_msg.DeviceType.PetFeeder
78441
79079
  ],
78442
- apply: "add-or-replace",
79080
+ apply: "add",
78443
79081
  vocabularyCap: "running-state",
78444
79082
  vocabularyStateField: "running",
78445
79083
  sources: [
@@ -78502,29 +79140,26 @@ var CAP_CUSTOMIZE_CATALOG = {
78502
79140
  sourceChangedAt: "prefer"
78503
79141
  }
78504
79142
  ]
78505
- }],
78506
- advancedParameters: []
78507
- },
78508
- "switch": {
78509
- version: 1,
78510
- targets: [],
78511
- advancedParameters: [{
78512
- path: "on",
78513
- kind: "boolean",
78514
- label: "On"
78515
- }]
78516
- },
78517
- "temperature-sensor": {
78518
- version: 1,
78519
- targets: [],
78520
- advancedParameters: [{
78521
- path: "celsius",
78522
- kind: "number",
78523
- label: "Temperature",
78524
- unit: "°C"
78525
79143
  }]
78526
79144
  }
78527
79145
  };
79146
+ /**
79147
+ * Unit meta on runtime-state fields. The composed-device row reads this;
79148
+ * it is not an override list (D944).
79149
+ */
79150
+ var CAP_RUNTIME_FIELD_UNITS = {
79151
+ "ambient-light-sensor": { lux: "lx" },
79152
+ "battery": {
79153
+ percentage: "%",
79154
+ volts: "V"
79155
+ },
79156
+ "humidity-sensor": { percent: "%" },
79157
+ "power-meter": {
79158
+ watts: "W",
79159
+ kwhTotal: "kWh"
79160
+ },
79161
+ "temperature-sensor": { celsius: "°C" }
79162
+ };
78528
79163
  //#endregion
78529
79164
  //#region src/composition/customize/field-source.ts
78530
79165
  function fromRead(source, cap, fieldPath) {
@@ -80584,6 +81219,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
80584
81219
  featureProbe: featureProbeCapability,
80585
81220
  flood: floodCapability,
80586
81221
  gas: gasCapability,
81222
+ groupStatus: groupStatusCapability,
80587
81223
  heatingValve: heatingValveCapability,
80588
81224
  humidifier: humidifierCapability,
80589
81225
  humiditySensor: humiditySensorCapability,
@@ -85909,6 +86545,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
85909
86545
  access: "view",
85910
86546
  role: "viewer"
85911
86547
  },
86548
+ "groupStatus.getStatus": {
86549
+ capName: "group-status",
86550
+ capScope: "device",
86551
+ addonId: null,
86552
+ access: "view",
86553
+ role: "viewer"
86554
+ },
85912
86555
  "heatingValve.getStatus": {
85913
86556
  capName: "heating-valve",
85914
86557
  capScope: "device",
@@ -92447,6 +93090,7 @@ var KNOWN_CAP_NAMES = [
92447
93090
  "filesystem-browse",
92448
93091
  "flood",
92449
93092
  "gas",
93093
+ "group-status",
92450
93094
  "heating-valve",
92451
93095
  "humidifier",
92452
93096
  "humidity-sensor",
@@ -92595,6 +93239,7 @@ var DEVICE_CAP_NAMES = [
92595
93239
  "feature-probe",
92596
93240
  "flood",
92597
93241
  "gas",
93242
+ "group-status",
92598
93243
  "heating-valve",
92599
93244
  "humidifier",
92600
93245
  "humidity-sensor",
@@ -96112,6 +96757,33 @@ var SENSOR_CAP_VOCABULARY = {
96112
96757
  icon: "flame"
96113
96758
  }]
96114
96759
  },
96760
+ "group-status": {
96761
+ scope: "device",
96762
+ noun: t("Group", "Gruppo"),
96763
+ state: [
96764
+ {
96765
+ kind: "numeric",
96766
+ field: "included",
96767
+ quantity: t("the members reporting", "i membri che rispondono"),
96768
+ label: t("Reporting", "Rispondono"),
96769
+ icon: "users"
96770
+ },
96771
+ {
96772
+ kind: "numeric",
96773
+ field: "excluded",
96774
+ quantity: t("the members left out", "i membri esclusi"),
96775
+ label: t("Excluded", "Esclusi"),
96776
+ icon: "users"
96777
+ },
96778
+ {
96779
+ kind: "numeric",
96780
+ field: "total",
96781
+ quantity: t("the member count", "il numero di membri"),
96782
+ label: t("Members", "Membri"),
96783
+ icon: "sigma"
96784
+ }
96785
+ ]
96786
+ },
96115
96787
  "humidity-sensor": {
96116
96788
  scope: "device",
96117
96789
  noun: t("Humidity", "Umidità"),
@@ -96145,7 +96817,6 @@ var SENSOR_CAP_VOCABULARY = {
96145
96817
  field: "value",
96146
96818
  quantity: t("the reading", "il valore"),
96147
96819
  label: t("Reading", "Valore"),
96148
- unitField: "unit",
96149
96820
  icon: "gauge"
96150
96821
  }]
96151
96822
  },
@@ -96281,7 +96952,7 @@ var SENSOR_CAP_VOCABULARY = {
96281
96952
  field: "temperature",
96282
96953
  quantity: t("the outdoor temperature", "la temperatura esterna"),
96283
96954
  label: t("Temperature", "Temperatura"),
96284
- unitField: "temperatureUnit",
96955
+ unit: "°C",
96285
96956
  icon: "thermometer"
96286
96957
  },
96287
96958
  {
@@ -96297,7 +96968,7 @@ var SENSOR_CAP_VOCABULARY = {
96297
96968
  field: "windSpeed",
96298
96969
  quantity: t("the wind speed", "la velocità del vento"),
96299
96970
  label: t("Wind", "Vento"),
96300
- unitField: "windSpeedUnit",
96971
+ unit: "m/s",
96301
96972
  icon: "wind"
96302
96973
  },
96303
96974
  {
@@ -96305,7 +96976,7 @@ var SENSOR_CAP_VOCABULARY = {
96305
96976
  field: "pressure",
96306
96977
  quantity: t("the air pressure", "la pressione atmosferica"),
96307
96978
  label: t("Pressure", "Pressione"),
96308
- unitField: "pressureUnit",
96979
+ unit: "hPa",
96309
96980
  icon: "gauge"
96310
96981
  }
96311
96982
  ]
@@ -98250,6 +98921,7 @@ var CAP_NAMES_WITH_STATUS = [
98250
98921
  "feature-probe",
98251
98922
  "flood",
98252
98923
  "gas",
98924
+ "group-status",
98253
98925
  "heating-valve",
98254
98926
  "humidifier",
98255
98927
  "humidity-sensor",
@@ -98372,6 +99044,7 @@ var CAPABILITY_NAMES = {
98372
99044
  filesystemBrowse: "filesystem-browse",
98373
99045
  flood: "flood",
98374
99046
  gas: "gas",
99047
+ groupStatus: "group-status",
98375
99048
  heatingValve: "heating-valve",
98376
99049
  humidifier: "humidifier",
98377
99050
  humiditySensor: "humidity-sensor",
@@ -98778,6 +99451,10 @@ var CAPABILITY_ROUTER_KEYS = [
98778
99451
  key: "gas",
98779
99452
  name: "gas"
98780
99453
  },
99454
+ {
99455
+ key: "groupStatus",
99456
+ name: "group-status"
99457
+ },
98781
99458
  {
98782
99459
  key: "heatingValve",
98783
99460
  name: "heating-valve"
@@ -99291,6 +99968,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
99291
99968
  filesystemBrowseCapability,
99292
99969
  floodCapability,
99293
99970
  gasCapability,
99971
+ groupStatusCapability,
99294
99972
  heatingValveCapability,
99295
99973
  humidifierCapability,
99296
99974
  humiditySensorCapability,
@@ -100367,6 +101045,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
100367
101045
  form: "single",
100368
101046
  optional: false
100369
101047
  }],
101048
+ "groupStatus.getStatus": [{
101049
+ name: "deviceId",
101050
+ form: "single",
101051
+ optional: false
101052
+ }],
100370
101053
  "heatingValve.getStatus": [{
100371
101054
  name: "deviceId",
100372
101055
  form: "single",
@@ -102378,7 +103061,7 @@ function getCapsByProviderKind(kind) {
102378
103061
  //#region src/generated/runtime-state-durability.ts
102379
103062
  /**
102380
103063
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
102381
- * `runtimeState`. 72 caps: 38 `restored`, 34 `session`.
103064
+ * `runtimeState`. 73 caps: 38 `restored`, 35 `session`.
102382
103065
  *
102383
103066
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
102384
103067
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -102496,6 +103179,10 @@ var RUNTIME_STATE_POLICY = {
102496
103179
  durability: "restored",
102497
103180
  volatileFields: ["lastChangedAt"]
102498
103181
  },
103182
+ "group-status": {
103183
+ durability: "session",
103184
+ volatileFields: []
103185
+ },
102499
103186
  "heating-valve": {
102500
103187
  durability: "session",
102501
103188
  volatileFields: ["lastFetchedAt"]
@@ -102838,7 +103525,7 @@ var BucketAccumulator = class {
102838
103525
  //#region src/generated/history-catalog.ts
102839
103526
  /**
102840
103527
  * Cap history catalog, projected from every `*.cap.ts` `history` block.
102841
- * 41 caps with series (74 series), 30 caps answered `'none'`.
103528
+ * 41 caps with series (75 series), 31 caps answered `'none'`.
102842
103529
  * Field keys are `runtimeState` paths (`a.b` into fixed objects only).
102843
103530
  */
102844
103531
  var HISTORY_CATALOG = {
@@ -102855,12 +103542,12 @@ var HISTORY_CATALOG = {
102855
103542
  },
102856
103543
  "pm25": {
102857
103544
  kind: "gauge",
102858
- unit: "ug/m3",
103545
+ unit: "µg/m³",
102859
103546
  deadband: 1
102860
103547
  },
102861
103548
  "pm10": {
102862
103549
  kind: "gauge",
102863
- unit: "ug/m3",
103550
+ unit: "µg/m³",
102864
103551
  deadband: 1
102865
103552
  },
102866
103553
  "aqi": {
@@ -102890,10 +103577,16 @@ var HISTORY_CATALOG = {
102890
103577
  deadbandPct: 10,
102891
103578
  minIntervalMs: 6e4
102892
103579
  } },
102893
- "battery": { "percentage": {
102894
- kind: "gauge",
102895
- unit: "%"
102896
- } },
103580
+ "battery": {
103581
+ "percentage": {
103582
+ kind: "gauge",
103583
+ unit: "%"
103584
+ },
103585
+ "volts": {
103586
+ kind: "gauge",
103587
+ unit: "V"
103588
+ }
103589
+ },
102897
103590
  "binary": { "on": { kind: "boolean" } },
102898
103591
  "brightness": { "percentage": {
102899
103592
  kind: "gauge",
@@ -102977,7 +103670,7 @@ var HISTORY_CATALOG = {
102977
103670
  },
102978
103671
  "enum-sensor": { "value": {
102979
103672
  kind: "enum",
102980
- skipWhenSet: "format"
103673
+ skipWhenSet: "timestamp"
102981
103674
  } },
102982
103675
  "fan-control": {
102983
103676
  "percentage": {
@@ -103050,7 +103743,6 @@ var HISTORY_CATALOG = {
103050
103743
  },
103051
103744
  "numeric-sensor": { "value": {
103052
103745
  kind: "gauge",
103053
- unitFrom: "unit",
103054
103746
  minIntervalMs: 3e4
103055
103747
  } },
103056
103748
  "pet-feeder": {
@@ -103064,7 +103756,7 @@ var HISTORY_CATALOG = {
103064
103756
  },
103065
103757
  "desiccantLeftDays": {
103066
103758
  kind: "gauge",
103067
- unit: "days"
103759
+ unit: "s"
103068
103760
  }
103069
103761
  },
103070
103762
  "power-meter": {
@@ -103168,7 +103860,7 @@ var HISTORY_CATALOG = {
103168
103860
  "weather": {
103169
103861
  "temperature": {
103170
103862
  kind: "gauge",
103171
- unitFrom: "temperatureUnit",
103863
+ unit: "°C",
103172
103864
  deadband: .2
103173
103865
  },
103174
103866
  "humidity": {
@@ -103178,12 +103870,12 @@ var HISTORY_CATALOG = {
103178
103870
  },
103179
103871
  "pressure": {
103180
103872
  kind: "gauge",
103181
- unitFrom: "pressureUnit",
103873
+ unit: "hPa",
103182
103874
  deadband: .5
103183
103875
  },
103184
103876
  "windSpeed": {
103185
103877
  kind: "gauge",
103186
- unitFrom: "windSpeedUnit",
103878
+ unit: "m/s",
103187
103879
  deadband: .5
103188
103880
  }
103189
103881
  }
@@ -103217,7 +103909,6 @@ var HISTORY_ITEM_CATALOG = { "consumables": {
103217
103909
  },
103218
103910
  "remaining.value": {
103219
103911
  kind: "gauge",
103220
- unitFrom: "remaining.unit",
103221
103912
  deadband: 1,
103222
103913
  displayName: "Remaining"
103223
103914
  }
@@ -103235,6 +103926,7 @@ var HISTORY_NONE_CAPS = [
103235
103926
  "doorbell",
103236
103927
  "event-emitter",
103237
103928
  "feature-probe",
103929
+ "group-status",
103238
103930
  "image",
103239
103931
  "image-settings",
103240
103932
  "intercom",
@@ -103261,6 +103953,14 @@ function historyFieldsFor(capName) {
103261
103953
  return HISTORY_CATALOG[capName] ?? {};
103262
103954
  }
103263
103955
  //#endregion
103956
+ //#region src/capabilities/runtime-field-unit.ts
103957
+ /**
103958
+ * The unit a composed-device field row offers convert-unit from (D941, D944).
103959
+ */
103960
+ function runtimeFieldUnit(cap, path) {
103961
+ return CAP_RUNTIME_FIELD_UNITS[cap]?.[path];
103962
+ }
103963
+ //#endregion
103264
103964
  //#region src/composition/customize/override-draft.ts
103265
103965
  /**
103266
103966
  * A saved Customize assignment (D936). The schema is the type: callers use
@@ -112817,6 +113517,227 @@ var DEVICE_TYPE_INFO = { ["camera"]: {
112817
113517
  icon: "camera"
112818
113518
  } };
112819
113519
  //#endregion
113520
+ //#region src/units/threshold.ts
113521
+ /**
113522
+ * Move a threshold between the unit the operator sees and the stored base.
113523
+ * A failed conversion leaves the number unchanged — the recipe stays serialisable.
113524
+ */
113525
+ function thresholdToBase(value, displayUnit, baseUnit) {
113526
+ if (!Number.isFinite(value) || displayUnit === baseUnit) return value;
113527
+ return tryConvertUnit(value, displayUnit, baseUnit) ?? value;
113528
+ }
113529
+ function thresholdToDisplay(value, baseUnit, displayUnit) {
113530
+ if (!Number.isFinite(value) || baseUnit === displayUnit) return value;
113531
+ return tryConvertUnit(value, baseUnit, displayUnit) ?? value;
113532
+ }
113533
+ //#endregion
113534
+ //#region src/units/format-reading.ts
113535
+ /**
113536
+ * Render a stored base-unit reading (D944 §4). The preference defaults to
113537
+ * metric. An operator override is applied by the caller via `convertUnit`
113538
+ * before this, or by passing the already-chosen display unit through
113539
+ * `resolveSensorDisplay` — this function is the quantity → string step.
113540
+ */
113541
+ var DURATION_STEPS = [
113542
+ {
113543
+ value: 86400,
113544
+ unit: "d"
113545
+ },
113546
+ {
113547
+ value: 3600,
113548
+ unit: "h"
113549
+ },
113550
+ {
113551
+ value: 60,
113552
+ unit: "min"
113553
+ }
113554
+ ];
113555
+ var INFO_STEPS = [
113556
+ {
113557
+ factor: 1024 ** 4,
113558
+ unit: "TiB"
113559
+ },
113560
+ {
113561
+ factor: 1024 ** 3,
113562
+ unit: "GiB"
113563
+ },
113564
+ {
113565
+ factor: 1024 ** 2,
113566
+ unit: "MiB"
113567
+ },
113568
+ {
113569
+ factor: 1024,
113570
+ unit: "KiB"
113571
+ }
113572
+ ];
113573
+ function formatMagnitude(value, precision) {
113574
+ if (!Number.isFinite(value)) return "";
113575
+ if (precision !== void 0) return value.toFixed(precision);
113576
+ return Number.isInteger(value) ? String(value) : value.toFixed(1);
113577
+ }
113578
+ function scaleDuration(seconds) {
113579
+ const abs = Math.abs(seconds);
113580
+ for (const step of DURATION_STEPS) if (abs >= step.value) return {
113581
+ value: seconds / step.value,
113582
+ unit: step.unit
113583
+ };
113584
+ return {
113585
+ value: seconds,
113586
+ unit: "s"
113587
+ };
113588
+ }
113589
+ function scaleInformation(bytes) {
113590
+ const abs = Math.abs(bytes);
113591
+ for (const step of INFO_STEPS) if (abs >= step.factor) return {
113592
+ value: bytes / step.factor,
113593
+ unit: step.unit
113594
+ };
113595
+ return {
113596
+ value: bytes,
113597
+ unit: "B"
113598
+ };
113599
+ }
113600
+ function resolvedQuantity(input) {
113601
+ if (input.quantity === "device") return input.deviceQuantity ?? "none";
113602
+ return input.quantity;
113603
+ }
113604
+ function formatReading(input) {
113605
+ const quantity = resolvedQuantity(input);
113606
+ const precision = input.precision;
113607
+ if (!Number.isFinite(input.value)) return {
113608
+ text: "",
113609
+ unit: "",
113610
+ value: input.value
113611
+ };
113612
+ if (quantity === "duration" || quantity === "duration-ms") {
113613
+ const scaled = scaleDuration(quantity === "duration-ms" ? input.value / 1e3 : input.value);
113614
+ return {
113615
+ text: formatMagnitude(scaled.value, precision),
113616
+ unit: scaled.unit,
113617
+ value: scaled.value
113618
+ };
113619
+ }
113620
+ if (quantity === "information") {
113621
+ const scaled = scaleInformation(input.value);
113622
+ return {
113623
+ text: formatMagnitude(scaled.value, precision),
113624
+ unit: scaled.unit,
113625
+ value: scaled.value
113626
+ };
113627
+ }
113628
+ const preference = input.preference ?? "metric";
113629
+ const base = require_sleep.baseUnitOf(quantity);
113630
+ const display = require_sleep.displayUnitFor(quantity, preference);
113631
+ if (base === void 0 || display === void 0) return {
113632
+ text: formatMagnitude(input.value, precision),
113633
+ unit: "",
113634
+ value: input.value
113635
+ };
113636
+ const converted = tryConvertUnit(input.value, base, display);
113637
+ if (converted === void 0) return {
113638
+ text: formatMagnitude(input.value, precision),
113639
+ unit: base,
113640
+ value: input.value
113641
+ };
113642
+ return {
113643
+ text: formatMagnitude(converted, precision),
113644
+ unit: display,
113645
+ value: converted
113646
+ };
113647
+ }
113648
+ //#endregion
113649
+ //#region src/units/ingest-reading.ts
113650
+ /**
113651
+ * Convert an upstream number into the stored base of its quantity (D944 §4).
113652
+ * An unknown unit or a unit from another dimension is a refusal: the caller
113653
+ * logs it and publishes null (D393). This function never guesses.
113654
+ */
113655
+ var DIMENSION_QUANTITY = {
113656
+ temperature: "temperature",
113657
+ pressure: "pressure",
113658
+ speed: "speed",
113659
+ "precipitation-rate": "precipitation-rate",
113660
+ length: "length",
113661
+ area: "area",
113662
+ illuminance: "illuminance",
113663
+ irradiance: "irradiance",
113664
+ power: "power",
113665
+ "apparent-power": "apparent-power",
113666
+ energy: "energy",
113667
+ voltage: "voltage",
113668
+ current: "current",
113669
+ "concentration-volume": "concentration-volume",
113670
+ "concentration-mass": "concentration-mass",
113671
+ mass: "mass",
113672
+ percentage: "percentage",
113673
+ volume: "volume",
113674
+ duration: "duration",
113675
+ information: "information",
113676
+ angle: "angle",
113677
+ "sound-level": "sound-level",
113678
+ "signal-level": "signal-level",
113679
+ "colour-temperature": "colour-temperature",
113680
+ bitrate: "bitrate",
113681
+ rate: "rate"
113682
+ };
113683
+ /**
113684
+ * Quantity implied by a unit symbol. `ppb` and `ms` are the two symbols
113685
+ * whose stored base is not the dimension's canonical unit. `mm` is length;
113686
+ * precipitation is chosen by the caller (a device class), not by the symbol.
113687
+ */
113688
+ function quantityFromUnit(rawUnit) {
113689
+ const unit = normalizeUnit(rawUnit);
113690
+ if (unit === void 0) return void 0;
113691
+ if (unit === "ppb") return "concentration-ppb";
113692
+ if (unit === "ms") return "duration-ms";
113693
+ const dimension = unitDimension(unit);
113694
+ if (dimension === void 0) return void 0;
113695
+ return DIMENSION_QUANTITY[dimension];
113696
+ }
113697
+ function ingestReading(input) {
113698
+ if (!Number.isFinite(input.value)) return {
113699
+ ok: false,
113700
+ reason: "non-finite"
113701
+ };
113702
+ if (input.quantity === "device") return {
113703
+ ok: false,
113704
+ reason: "unknown-unit"
113705
+ };
113706
+ const raw = input.rawUnit?.trim();
113707
+ if (raw === void 0 || raw.length === 0) return {
113708
+ ok: true,
113709
+ value: input.value
113710
+ };
113711
+ const unit = normalizeUnit(raw);
113712
+ if (unit === void 0) return {
113713
+ ok: false,
113714
+ reason: "unknown-unit"
113715
+ };
113716
+ const base = require_sleep.baseUnitOf(input.quantity);
113717
+ if (base === void 0) {
113718
+ if (unitDimension(unit) !== void 0) return {
113719
+ ok: false,
113720
+ reason: "cross-dimension"
113721
+ };
113722
+ return {
113723
+ ok: false,
113724
+ reason: "unknown-unit"
113725
+ };
113726
+ }
113727
+ const converted = tryConvertUnit(input.value, unit, base);
113728
+ if (converted === void 0) return unitDimension(unit) === void 0 ? {
113729
+ ok: false,
113730
+ reason: "unknown-unit"
113731
+ } : {
113732
+ ok: false,
113733
+ reason: "cross-dimension"
113734
+ };
113735
+ return {
113736
+ ok: true,
113737
+ value: converted
113738
+ };
113739
+ }
113740
+ //#endregion
112820
113741
  //#region src/util/location-match.ts
112821
113742
  /**
112822
113743
  * Pure fuzzy matcher for adoption location import. Normalized
@@ -114261,6 +115182,7 @@ exports.ADMIN_SCOPE = ADMIN_SCOPE;
114261
115182
  exports.AGENT_MDNS_QUERY_NAME = AGENT_MDNS_QUERY_NAME;
114262
115183
  exports.AGENT_MDNS_SERVICE_TYPE = AGENT_MDNS_SERVICE_TYPE;
114263
115184
  exports.AGENT_MDNS_TXT_KEYS = AGENT_MDNS_TXT_KEYS;
115185
+ exports.AGGREGATE_REDUCERS = require_composition.AGGREGATE_REDUCERS;
114264
115186
  exports.AGGREGATE_THROUGHPUT_BY_KIND = AGGREGATE_THROUGHPUT_BY_KIND;
114265
115187
  exports.ALEXA_EGRESS_PROFILE = ALEXA_EGRESS_PROFILE;
114266
115188
  exports.ALL_CAPABILITY_DEFINITIONS = ALL_CAPABILITY_DEFINITIONS;
@@ -114315,6 +115237,7 @@ exports.AdoptionOutcomeSchema = AdoptionOutcomeSchema;
114315
115237
  exports.AdoptionReleaseInputSchema = ReleaseInputSchema;
114316
115238
  exports.AdoptionStatusSchema = AdoptionStatusSchema;
114317
115239
  exports.AgentLoadSummarySchema = AgentLoadSummarySchema;
115240
+ exports.AggregateReducerSchema = require_composition.AggregateReducerSchema;
114318
115241
  exports.AirQualitySensorStatusSchema = AirQualitySensorStatusSchema;
114319
115242
  exports.AlarmArmModeSchema = AlarmArmModeSchema;
114320
115243
  exports.AlarmPanelStatusSchema = AlarmPanelStatusSchema;
@@ -114381,6 +115304,7 @@ exports.BASE_LIVE_EGRESS_PROFILE = BASE_LIVE_EGRESS_PROFILE;
114381
115304
  exports.BATTERY_DEVICE_PROFILE = BATTERY_DEVICE_PROFILE;
114382
115305
  exports.BATTERY_UNREACHABLE_AFTER_MS = BATTERY_UNREACHABLE_AFTER_MS;
114383
115306
  exports.BENCH_ROOT_STEP_ID = BENCH_ROOT_STEP_ID;
115307
+ exports.BINARY_AGGREGATES = require_composition.BINARY_AGGREGATES;
114384
115308
  exports.BIRTH_DECISION_DEFAULT_LIMIT = BIRTH_DECISION_DEFAULT_LIMIT;
114385
115309
  exports.BOOT_RECOVERY_BACKOFF_MS = require_sleep.BOOT_RECOVERY_BACKOFF_MS;
114386
115310
  exports.BUILTIN_RESULT_KINDS = BUILTIN_RESULT_KINDS;
@@ -114443,6 +115367,7 @@ exports.CAP_CUSTOMIZE_CATALOG = CAP_CUSTOMIZE_CATALOG;
114443
115367
  exports.CAP_NAMES_WITH_STATUS = CAP_NAMES_WITH_STATUS;
114444
115368
  exports.CAP_NODE_PIN_CONTEXT_KEY = require_sleep.CAP_NODE_PIN_CONTEXT_KEY;
114445
115369
  exports.CAP_PROVIDER_KIND_MAP = CAP_PROVIDER_KIND_MAP;
115370
+ exports.CAP_RUNTIME_FIELD_UNITS = CAP_RUNTIME_FIELD_UNITS;
114446
115371
  exports.CAP_VOCABULARY = CAP_VOCABULARY;
114447
115372
  exports.CAP_VOCABULARY_BY_NAME = CAP_VOCABULARY_BY_NAME;
114448
115373
  exports.CATALOG_LANGS = CATALOG_LANGS;
@@ -114590,6 +115515,7 @@ exports.ComposerApplyResultSchema = ComposerApplyResultSchema;
114590
115515
  exports.ComposerBlockSchema = ComposerBlockSchema;
114591
115516
  exports.ComposerBlockVerdictSchema = ComposerBlockVerdictSchema;
114592
115517
  exports.ComposerUnreadableBlockSchema = ComposerUnreadableBlockSchema;
115518
+ exports.CompositionAggregateSourceSchema = require_composition.CompositionAggregateSourceSchema;
114593
115519
  exports.CompositionBindingSchema = require_composition.CompositionBindingSchema;
114594
115520
  exports.CompositionBlockInputSchema = CompositionBlockInputSchema;
114595
115521
  exports.CompositionBlockStateSchema = CompositionBlockStateSchema;
@@ -114641,8 +115567,8 @@ exports.ConsumableFormSchema = ConsumableFormSchema;
114641
115567
  exports.ConsumableItemSchema = ConsumableItemSchema;
114642
115568
  exports.ConsumableKindSchema = ConsumableKindSchema;
114643
115569
  exports.ConsumableLevelSchema = ConsumableLevelSchema;
115570
+ exports.ConsumableRemainingKindSchema = ConsumableRemainingKindSchema;
114644
115571
  exports.ConsumableRemainingSchema = ConsumableRemainingSchema;
114645
- exports.ConsumableRemainingUnitSchema = ConsumableRemainingUnitSchema;
114646
115572
  exports.ConsumableStatusSchema = ConsumableStatusSchema;
114647
115573
  exports.ConsumablesStatusSchema = ConsumablesStatusSchema;
114648
115574
  exports.ContactStatusSchema = ContactStatusSchema;
@@ -114688,6 +115614,7 @@ exports.DEFAULT_CLUSTER_STEP_SETTINGS = DEFAULT_CLUSTER_STEP_SETTINGS;
114688
115614
  exports.DEFAULT_CORE_BLOCK_ENTRY = DEFAULT_CORE_BLOCK_ENTRY;
114689
115615
  exports.DEFAULT_DECODER_HWACCEL_CONFIG = DEFAULT_DECODER_HWACCEL_CONFIG;
114690
115616
  exports.DEFAULT_DETAIL_CROP_CONVENTION = DEFAULT_DETAIL_CROP_CONVENTION;
115617
+ exports.DEFAULT_DISPLAY_PREFERENCE = require_sleep.DEFAULT_DISPLAY_PREFERENCE;
114691
115618
  exports.DEFAULT_EVENTS_BAND_BUFFER_SEC = DEFAULT_EVENTS_BAND_BUFFER_SEC;
114692
115619
  exports.DEFAULT_EVENT_COLOR = DEFAULT_EVENT_COLOR;
114693
115620
  exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
@@ -114746,6 +115673,7 @@ exports.DEVICE_TOMBSTONE_RETENTION_MS = DEVICE_TOMBSTONE_RETENTION_MS;
114746
115673
  exports.DEVICE_TYPE_CONTROL_KIND = DEVICE_TYPE_CONTROL_KIND;
114747
115674
  exports.DEVICE_TYPE_DO_CAPS = DEVICE_TYPE_DO_CAPS;
114748
115675
  exports.DEVICE_TYPE_INFO = DEVICE_TYPE_INFO;
115676
+ exports.DISPLAY_PREFERENCES = require_sleep.DISPLAY_PREFERENCES;
114749
115677
  exports.DISPLAY_PRESETS = DISPLAY_PRESETS;
114750
115678
  exports.DOORBELL_RISE_DEBOUNCE_MS = DOORBELL_RISE_DEBOUNCE_MS;
114751
115679
  exports.DataStoreDatabaseSchema = DataStoreDatabaseSchema;
@@ -114964,12 +115892,17 @@ exports.FrameInputSchema = FrameInputSchema;
114964
115892
  exports.FrameLazyCountersSchema = FrameLazyCountersSchema;
114965
115893
  exports.FrameLazyMetricsSchema = FrameLazyMetricsSchema;
114966
115894
  exports.G711_SCALE_CORRECTION_DB = G711_SCALE_CORRECTION_DB;
115895
+ exports.GROUP_TEMPLATES = GROUP_TEMPLATES;
115896
+ exports.GROUP_TEMPLATE_IDS = require_composition.GROUP_TEMPLATE_IDS;
114967
115897
  exports.GasStatusSchema = GasStatusSchema;
114968
115898
  exports.GetLoggingSettingsInputSchema = GetLoggingSettingsInputSchema;
114969
115899
  exports.GetStreamWithCodecInputSchema = GetStreamWithCodecInputSchema;
114970
115900
  exports.GlobalMetricsSchema = GlobalMetricsSchema;
114971
115901
  exports.GridLayoutPatchSchema = GridLayoutPatchSchema;
114972
115902
  exports.GridLayoutViewSchema = GridLayoutViewSchema;
115903
+ exports.GroupHelperSpecSchema = require_composition.GroupHelperSpecSchema;
115904
+ exports.GroupStatusSchema = GroupStatusSchema;
115905
+ exports.GroupTemplateIdSchema = require_composition.GroupTemplateIdSchema;
114973
115906
  exports.HAP_AUDIO_BASE = HAP_AUDIO_BASE;
114974
115907
  exports.HAP_AUDIO_BITRATE_KBPS = HAP_AUDIO_BITRATE_KBPS;
114975
115908
  exports.HAP_AUDIO_VBV_KBITS = HAP_AUDIO_VBV_KBITS;
@@ -114993,6 +115926,8 @@ exports.HWACCEL_OPTIONS = HWACCEL_OPTIONS;
114993
115926
  exports.HardwareClassIdSchema = HardwareClassIdSchema;
114994
115927
  exports.HealthStatusSchema = HealthStatusSchema;
114995
115928
  exports.HeatingValveStatusSchema = HeatingValveStatusSchema;
115929
+ exports.HelperMemberSchema = require_composition.HelperMemberSchema;
115930
+ exports.HelperSpecSchema = require_composition.HelperSpecSchema;
114996
115931
  exports.HfModelResolutionSchema = HfModelResolutionSchema;
114997
115932
  exports.HistoryPointSchema = HistoryPointSchema;
114998
115933
  exports.HistoryResolutionEnum = HistoryResolutionEnum;
@@ -115167,6 +116102,7 @@ exports.MAX_EXPRESSION_BINDINGS = require_composition.MAX_EXPRESSION_BINDINGS;
115167
116102
  exports.MAX_EXPRESSION_CALL_ARGS = require_composition.MAX_EXPRESSION_CALL_ARGS;
115168
116103
  exports.MAX_EXPRESSION_EVAL_STEPS = require_composition.MAX_EXPRESSION_EVAL_STEPS;
115169
116104
  exports.MAX_EXPRESSION_SOURCE_LENGTH = require_composition.MAX_EXPRESSION_SOURCE_LENGTH;
116105
+ exports.MAX_HELPER_MEMBERS = require_composition.MAX_HELPER_MEMBERS;
115170
116106
  exports.MAX_KEYS = MAX_KEYS;
115171
116107
  exports.MAX_REASONS_PER_KEY = MAX_REASONS_PER_KEY;
115172
116108
  exports.MAX_REQUEUED_BUCKETS_PER_SERIES = MAX_REQUEUED_BUCKETS_PER_SERIES;
@@ -115358,6 +116294,7 @@ exports.NETWORK_LINK_UNKNOWN = NETWORK_LINK_UNKNOWN;
115358
116294
  exports.NODE_BENCHMARK_DEFAULTS = NODE_BENCHMARK_DEFAULTS;
115359
116295
  exports.NODE_COLOR_IDS = require_node_color.NODE_COLOR_IDS;
115360
116296
  exports.NO_MEASURED_CAPACITY = NO_MEASURED_CAPACITY;
116297
+ exports.NUMERIC_AGGREGATES = require_composition.NUMERIC_AGGREGATES;
115361
116298
  exports.NativeCropBboxSchema = NativeCropBboxSchema;
115362
116299
  exports.NativeCropRefSchema = NativeCropRefSchema;
115363
116300
  exports.NativeCropResultSchema = NativeCropResultSchema;
@@ -115676,6 +116613,7 @@ exports.PtzPositionSchema = PtzPositionSchema;
115676
116613
  exports.PtzPresetFunctionSchema = PtzPresetFunctionSchema;
115677
116614
  exports.PtzPresetSchema = PtzPresetSchema;
115678
116615
  exports.PtzStatusSchema = PtzStatusSchema;
116616
+ exports.QUANTITY_BASE = require_sleep.QUANTITY_BASE;
115679
116617
  exports.QueryFilterSchema = QueryFilterSchema;
115680
116618
  exports.RATE_CONTROL_RELAXED = RATE_CONTROL_RELAXED;
115681
116619
  exports.RATE_CONTROL_TIGHT = RATE_CONTROL_TIGHT;
@@ -115754,6 +116692,7 @@ exports.ReadSegmentBytesResultSchema = ReadSegmentBytesResultSchema;
115754
116692
  exports.ReadWindowBytesResultSchema = ReadWindowBytesResultSchema;
115755
116693
  exports.ReadinessRegistry = require_sleep.ReadinessRegistry;
115756
116694
  exports.ReadinessTimeoutError = require_sleep.ReadinessTimeoutError;
116695
+ exports.ReadingQuantitySchema = require_sleep.ReadingQuantitySchema;
115757
116696
  exports.RebuildObjectEmbeddingsRefusalSchema = RebuildObjectEmbeddingsRefusalSchema;
115758
116697
  exports.RecentTracksPageSchema = RecentTracksPageSchema;
115759
116698
  exports.RecentTracksQueryInput = RecentTracksQueryInput;
@@ -116320,6 +117259,7 @@ exports.autoAssignProfiles = autoAssignProfiles;
116320
117259
  exports.automationControlCapability = automationControlCapability;
116321
117260
  exports.backupCapability = backupCapability;
116322
117261
  exports.bareAddonId = bareAddonId;
117262
+ exports.baseUnitOf = require_sleep.baseUnitOf;
116323
117263
  exports.batteryCapability = batteryCapability;
116324
117264
  exports.bestLocationMatch = bestLocationMatch;
116325
117265
  exports.binaryCapability = binaryCapability;
@@ -116409,6 +117349,7 @@ exports.commitWatchdogRestart = commitWatchdogRestart;
116409
117349
  exports.compareToTarget = compareToTarget;
116410
117350
  exports.compileExpression = require_composition.compileExpression;
116411
117351
  exports.compileExpressionSafe = require_composition.compileExpressionSafe;
117352
+ exports.compileHelper = compileHelper;
116412
117353
  exports.compileKeyedArrayOverride = compileKeyedArrayOverride;
116413
117354
  exports.compilePulseOverride = compilePulseOverride;
116414
117355
  exports.compileScalarOverride = compileScalarOverride;
@@ -116539,6 +117480,7 @@ exports.diffStepSettings = diffStepSettings;
116539
117480
  exports.disarmRoseMemory = require_composition.disarmRoseMemory;
116540
117481
  exports.dishwasherCapability = dishwasherCapability;
116541
117482
  exports.displayPaletteCapability = displayPaletteCapability;
117483
+ exports.displayUnitFor = require_sleep.displayUnitFor;
116542
117484
  exports.doActionSelectValue = doActionSelectValue;
116543
117485
  exports.dominantClipLabels = dominantClipLabels;
116544
117486
  exports.doorbellCapability = doorbellCapability;
@@ -116570,6 +117512,7 @@ exports.enumerateItemArrayFields = enumerateItemArrayFields;
116570
117512
  exports.enumerateSchemaFields = enumerateSchemaFields;
116571
117513
  exports.errMsg = require_err_msg.errMsg;
116572
117514
  exports.estimateNodeBenchmarkDurationMs = estimateNodeBenchmarkDurationMs;
117515
+ exports.evaluateAggregate = evaluateAggregate;
116573
117516
  exports.evaluateAst = require_composition.evaluateAst;
116574
117517
  exports.evaluateCompositionField = evaluateCompositionField;
116575
117518
  exports.evaluateCount = require_composition.evaluateCount;
@@ -116625,6 +117568,7 @@ exports.formatMediaOwnerKey = formatMediaOwnerKey;
116625
117568
  exports.formatMinutes = formatMinutes;
116626
117569
  exports.formatPaletteViolations = formatPaletteViolations;
116627
117570
  exports.formatParkedFor = formatParkedFor;
117571
+ exports.formatReading = formatReading;
116628
117572
  exports.formatSecretScope = require_sleep.formatSecretScope;
116629
117573
  exports.formulaProblem = formulaProblem;
116630
117574
  exports.freshResults = freshResults;
@@ -116642,13 +117586,17 @@ exports.gifFilterComplex = gifFilterComplex;
116642
117586
  exports.gifFromMp4Args = gifFromMp4Args;
116643
117587
  exports.globalSecretScope = require_sleep.globalSecretScope;
116644
117588
  exports.grantRowsOf = grantRowsOf;
117589
+ exports.groupStatusCapability = groupStatusCapability;
117590
+ exports.groupTemplate = groupTemplate;
116645
117591
  exports.hasModelBoundOverrides = hasModelBoundOverrides;
116646
117592
  exports.hasMotionTrigger = hasMotionTrigger;
116647
117593
  exports.hasUrlScheme = hasUrlScheme;
116648
117594
  exports.heatingValveCapability = heatingValveCapability;
117595
+ exports.helperSaveProblems = helperSaveProblems;
116649
117596
  exports.hfModelUrl = require_sleep.hfModelUrl;
116650
117597
  exports.historyFieldsFor = historyFieldsFor;
116651
117598
  exports.historyItemFieldPath = require_sleep.historyItemFieldPath;
117599
+ exports.historyUnitFor = require_sleep.historyUnitFor;
116652
117600
  exports.htmlToText = htmlToText;
116653
117601
  exports.hubAddressHostname = hubAddressHostname;
116654
117602
  exports.hubMotionNativeProvider = hubMotionNativeProvider;
@@ -116663,6 +117611,7 @@ exports.imageModesForKind = imageModesForKind;
116663
117611
  exports.imageSettingsCapability = imageSettingsCapability;
116664
117612
  exports.inferExpressionKinds = inferExpressionKinds;
116665
117613
  exports.inferModelProvider = inferModelProvider;
117614
+ exports.ingestReading = ingestReading;
116666
117615
  exports.initialPoolMemoryState = initialPoolMemoryState;
116667
117616
  exports.integrationsCapability = integrationsCapability;
116668
117617
  exports.intercomCapability = intercomCapability;
@@ -116717,6 +117666,7 @@ exports.isOauthLandingReason = isOauthLandingReason;
116717
117666
  exports.isObjectInput = isObjectInput;
116718
117667
  exports.isOccupancyRule = isOccupancyRule;
116719
117668
  exports.isOwnerOnlyEventCategory = require_event_category.isOwnerOnlyEventCategory;
117669
+ exports.isReadingQuantity = require_sleep.isReadingQuantity;
116720
117670
  exports.isRemovedModelId = isRemovedModelId;
116721
117671
  exports.isRestoredCap = isRestoredCap;
116722
117672
  exports.isRetiredClearKey = isRetiredClearKey;
@@ -116947,6 +117897,7 @@ exports.ptzAutotrackCapability = ptzAutotrackCapability;
116947
117897
  exports.ptzCapability = ptzCapability;
116948
117898
  exports.publicEventCategories = require_event_category.publicEventCategories;
116949
117899
  exports.pythonScriptForBackend = pythonScriptForBackend;
117900
+ exports.quantityFromUnit = quantityFromUnit;
116950
117901
  exports.rankStreams = rankStreams;
116951
117902
  exports.readClusterStepModels = readClusterStepModels;
116952
117903
  exports.readClusterStepSettings = readClusterStepSettings;
@@ -116965,6 +117916,7 @@ exports.readRecoveryRecipeForSteps = readRecoveryRecipeForSteps;
116965
117916
  exports.readTimelapseGeneratedAt = readTimelapseGeneratedAt;
116966
117917
  exports.readUseRootModel = readUseRootModel;
116967
117918
  exports.readinessKey = require_sleep.readinessKey;
117919
+ exports.readingMeta = require_sleep.readingMeta;
116968
117920
  exports.rebootCapability = rebootCapability;
116969
117921
  exports.recommendNode = recommendNode;
116970
117922
  exports.reconcileBlockDevices = reconcileBlockDevices;
@@ -117065,8 +118017,10 @@ exports.runningFromMediaPlayer = runningFromMediaPlayer;
117065
118017
  exports.runningFromVacuumState = runningFromVacuumState;
117066
118018
  exports.runningStateCapability = runningStateCapability;
117067
118019
  exports.runtimeDevices = runtimeDevices;
118020
+ exports.runtimeFieldUnit = runtimeFieldUnit;
117068
118021
  exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
117069
118022
  exports.sameDoFamily = sameDoFamily;
118023
+ exports.sameHelperBody = sameHelperBody;
117070
118024
  exports.sameRead = sameRead;
117071
118025
  exports.sceneMonitorCapability = sceneMonitorCapability;
117072
118026
  exports.scheduleForSendTimes = scheduleForSendTimes;
@@ -117116,6 +118070,7 @@ exports.sliceChangedAt = require_composition.sliceChangedAt;
117116
118070
  exports.smokeCapability = smokeCapability;
117117
118071
  exports.smtpProviderCapability = smtpProviderCapability;
117118
118072
  exports.snapshotCapability = snapshotCapability;
118073
+ exports.sourcePickForTemplate = sourcePickForTemplate;
117119
118074
  exports.splitTranslationRef = splitTranslationRef;
117120
118075
  exports.squareAround = squareAround;
117121
118076
  exports.ssoBridgeCapability = ssoBridgeCapability;
@@ -117178,12 +118133,15 @@ exports.taskPhaseSchema = taskPhaseSchema;
117178
118133
  exports.taskTargetSchema = taskTargetSchema;
117179
118134
  exports.temperatureSensorCapability = temperatureSensorCapability;
117180
118135
  exports.templateCarriesSummary = templateCarriesSummary;
118136
+ exports.templateDeviceType = templateDeviceType;
117181
118137
  exports.templateVarDeliveryApplies = templateVarDeliveryApplies;
117182
118138
  exports.templateVarKindApplies = templateVarKindApplies;
117183
118139
  exports.templateVarSystemEventApplies = templateVarSystemEventApplies;
117184
118140
  exports.templateVarsFor = templateVarsFor;
117185
118141
  exports.terminalSessionCapability = terminalSessionCapability;
117186
118142
  exports.textToHtml = textToHtml;
118143
+ exports.thresholdToBase = thresholdToBase;
118144
+ exports.thresholdToDisplay = thresholdToDisplay;
117187
118145
  exports.timeOfDayToMinutes = timeOfDayToMinutes;
117188
118146
  exports.toAppLinkHubOrigin = toAppLinkHubOrigin;
117189
118147
  exports.toDeviceSummary = toDeviceSummary;
@@ -117199,6 +118157,7 @@ exports.trainingCapability = trainingCapability;
117199
118157
  exports.trainingProviderCapability = trainingProviderCapability;
117200
118158
  exports.transcodeBody = transcodeBody;
117201
118159
  exports.transformSecretWrite = require_sleep.transformSecretWrite;
118160
+ exports.tryConvertDelta = tryConvertDelta;
117202
118161
  exports.tryConvertUnit = tryConvertUnit;
117203
118162
  exports.turnProviderCapability = turnProviderCapability;
117204
118163
  exports.unitCompanionField = unitCompanionField;