@camstack/types 1.2.403 → 1.2.405

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 (47) hide show
  1. package/dist/addon.js +2 -2
  2. package/dist/addon.mjs +2 -2
  3. package/dist/capabilities/index.d.ts +4 -3
  4. package/dist/capabilities/pipeline-analytics.cap.d.ts +27 -0
  5. package/dist/capabilities/pipeline-executor.cap.d.ts +0 -64
  6. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +68 -124
  7. package/dist/capabilities/platform-probe.cap.d.ts +34 -0
  8. package/dist/capabilities/schemas/package-llm-verdict.d.ts +14 -0
  9. package/dist/capabilities/schemas/zone-rule.d.ts +4 -0
  10. package/dist/capabilities/training.cap.d.ts +91 -71
  11. package/dist/capabilities/zone-rules.cap.d.ts +10 -0
  12. package/dist/client.js +2 -8
  13. package/dist/client.mjs +2 -8
  14. package/dist/enums/event-category.d.ts +2 -3
  15. package/dist/enums.js +1 -1
  16. package/dist/enums.mjs +1 -1
  17. package/dist/{event-category-7AwNbPjm.js → event-category-CXrwjC3e.js} +2 -3
  18. package/dist/{event-category-BfJwUd2S.mjs → event-category-fDPduoeC.mjs} +2 -3
  19. package/dist/generated/addon-api.d.ts +14 -56
  20. package/dist/generated/device-proxy.d.ts +1 -1
  21. package/dist/generated/method-access-map.d.ts +1 -1
  22. package/dist/generated/method-device-selectors.d.ts +2 -2
  23. package/dist/generated/system-proxy.d.ts +3 -3
  24. package/dist/index.d.ts +3 -1
  25. package/dist/index.js +2250 -1529
  26. package/dist/index.mjs +2197 -1528
  27. package/dist/interfaces/birth-decision-ledger.d.ts +3 -0
  28. package/dist/interfaces/pipeline-executor-capability.d.ts +2 -8
  29. package/dist/interfaces/platform.d.ts +7 -0
  30. package/dist/node-color.d.ts +1 -1
  31. package/dist/pipeline/auto-tune/accelerator-compute.d.ts +116 -0
  32. package/dist/pipeline/auto-tune/detector-performance.d.ts +5 -0
  33. package/dist/pipeline/auto-tune/hardware-class.d.ts +5 -4
  34. package/dist/pipeline/auto-tune/index.d.ts +1 -0
  35. package/dist/pipeline/auto-tune/plan-schemas.d.ts +108 -80
  36. package/dist/pipeline/auto-tune/recommend.d.ts +31 -9
  37. package/dist/pipeline/second-pass/index.d.ts +2 -0
  38. package/dist/pipeline/second-pass/motion-crop-plan.d.ts +183 -0
  39. package/dist/pipeline/second-pass/small-subject-recovery.d.ts +216 -0
  40. package/dist/pipeline/step-settings-layers.d.ts +7 -8
  41. package/dist/{sleep-Ch67uumJ.js → sleep-BKS4OPMo.js} +1 -4
  42. package/dist/{sleep-6AqUnQIT.mjs → sleep-CHIEEJkY.mjs} +1 -4
  43. package/dist/types/agent-pipeline-settings.d.ts +37 -70
  44. package/dist/types/camera-pipeline.d.ts +2 -2
  45. package/dist/types/detection.d.ts +15 -5
  46. package/dist/types/pipeline-step.d.ts +0 -16
  47. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-Ch67uumJ.js");
3
- const require_event_category = require("./event-category-7AwNbPjm.js");
2
+ const require_sleep = require("./sleep-BKS4OPMo.js");
3
+ const require_event_category = require("./event-category-CXrwjC3e.js");
4
4
  const require_composition = require("./composition-eoYYVfOp.js");
5
5
  const require_node_color = require("./node-color.js");
6
6
  const require_err_msg = require("./err-msg-BhVbr0NX.js");
@@ -2288,13 +2288,22 @@ var BirthDecisionRecordSchema = zod.z.object({
2288
2288
  decidedByBirthEvidence: zod.z.boolean(),
2289
2289
  /** Best class-compatible crop score, or `null` when nothing compatible was
2290
2290
  * found — deliberately not 0, which would be a measurement that never was.
2291
- * Includes a box the detector's own per-class floor refused (reason
2292
- * `below-detector-floor`, D922): that box never confirms, but it is the
2293
- * score the crop really held. */
2291
+ * Includes a box the detector's own per-class floor refused (D922): since
2292
+ * D925 the gate's bar, not that floor, decides whether it confirms. */
2294
2293
  bestScore: zod.z.number().nullable(),
2295
2294
  /** The bar this candidate actually faced (the phantom-cell hook may raise it). */
2296
2295
  appliedMinConfidence: zod.z.number().nullable(),
2297
2296
  /**
2297
+ * The detector the gate re-judged the birth WITH (D925): the session's own
2298
+ * root model, inference-device pool and node — never a node default. `null`
2299
+ * when the frame named no session detector or no gate ran, and on rows
2300
+ * written before D925. A `session-device-unavailable` row names the session
2301
+ * detector that could not be reached.
2302
+ */
2303
+ detectorModelId: zod.z.string().nullable().default(null),
2304
+ detectorDeviceKey: zod.z.string().nullable().default(null),
2305
+ detectorNodeId: zod.z.string().nullable().default(null),
2306
+ /**
2298
2307
  * The track's own `firstSeen` — the candidate's first frame, which is where
2299
2308
  * the timeline starts. `null` for a suppressed birth, which has no track.
2300
2309
  */
@@ -5416,6 +5425,27 @@ function pickClosestResolution(entries, target) {
5416
5425
  }])].sort((a, b) => compareToTarget(a.pixels, b.pixels, targetPixels))[0]?.entry;
5417
5426
  }
5418
5427
  //#endregion
5428
+ //#region src/types/agent-pipeline-settings.ts
5429
+ /**
5430
+ * Is `key` a step setting that only the accelerator running the model may
5431
+ * carry (D927)? A model id, a model binding (`<key>ModelId`, D738) or a
5432
+ * confidence floor (`minConfidence*` — every `bindsToModel` field). A camera
5433
+ * layer never stores one: the floor of one model is wrong on the next, and a
5434
+ * camera does not know which accelerator — or which model — it lands on.
5435
+ * (Per-camera thresholds are a possible future extension, see D927.)
5436
+ */
5437
+ function isModelScopedStepSettingKey(key) {
5438
+ return key === "modelId" || key.endsWith("ModelId") || key.startsWith("minConfidence");
5439
+ }
5440
+ /** The keys of `bag` a camera layer may not carry ({@link isModelScopedStepSettingKey}), sorted. */
5441
+ function modelScopedStepSettingKeys(bag) {
5442
+ return Object.keys(bag).filter(isModelScopedStepSettingKey).toSorted();
5443
+ }
5444
+ /** `bag` without its model-scoped keys — what a camera layer contributes at dispatch. Fresh object. */
5445
+ function cameraScopedStepSettings(bag) {
5446
+ return Object.fromEntries(Object.entries(bag).filter(([key]) => !isModelScopedStepSettingKey(key)));
5447
+ }
5448
+ //#endregion
5419
5449
  //#region src/types/appearance.ts
5420
5450
  /**
5421
5451
  * A model-free appearance descriptor of one detection box (D683 §8).
@@ -12853,7 +12883,7 @@ function kindOfValue(value) {
12853
12883
  function only(kind) {
12854
12884
  return new Set([kind]);
12855
12885
  }
12856
- function union(...sets) {
12886
+ function union$1(...sets) {
12857
12887
  return new Set(sets.flatMap((s) => [...s]));
12858
12888
  }
12859
12889
  function withoutNull(set) {
@@ -12881,8 +12911,8 @@ function callKinds(callee, args) {
12881
12911
  const rule = BUILTIN_RESULT_KINDS[callee];
12882
12912
  if (rule === void 0) throw new KindInferenceError(`no result-kind rule for builtin '${callee}'`);
12883
12913
  if (rule.kind === "fixed") return new Set(rule.result);
12884
- const nonNull = union(...args.map(withoutNull));
12885
- return args.every((a) => a.has("null")) ? union(nonNull, only("null")) : nonNull;
12914
+ const nonNull = union$1(...args.map(withoutNull));
12915
+ return args.every((a) => a.has("null")) ? union$1(nonNull, only("null")) : nonNull;
12886
12916
  }
12887
12917
  function kindsOf(node, bindings) {
12888
12918
  switch (node.kind) {
@@ -12895,8 +12925,8 @@ function kindsOf(node, bindings) {
12895
12925
  }
12896
12926
  case "unary": return only(node.op === "!" ? "boolean" : "number");
12897
12927
  case "binary": return binaryKinds(node.op, kindsOf(node.left, bindings), kindsOf(node.right, bindings));
12898
- case "logical": return union(kindsOf(node.left, bindings), kindsOf(node.right, bindings));
12899
- case "conditional": return union(kindsOf(node.consequent, bindings), kindsOf(node.alternate, bindings));
12928
+ case "logical": return union$1(kindsOf(node.left, bindings), kindsOf(node.right, bindings));
12929
+ case "conditional": return union$1(kindsOf(node.consequent, bindings), kindsOf(node.alternate, bindings));
12900
12930
  case "call": return callKinds(node.callee, node.args.map((a) => kindsOf(a, bindings)));
12901
12931
  }
12902
12932
  }
@@ -47259,6 +47289,20 @@ var CAMERA_SENSORS_NOT_LOADED = Object.freeze({
47259
47289
  reason: "camera sensor mappings not loaded"
47260
47290
  });
47261
47291
  //#endregion
47292
+ //#region src/capabilities/schemas/package-llm-verdict.ts
47293
+ /**
47294
+ * What the vision model must answer before a package event is emitted
47295
+ * when a packages rule has "Confirm with the LLM" on.
47296
+ *
47297
+ * `package` is the verdict. `confidence` is 0..1. `reason` is the model's
47298
+ * one-line explanation, stored on the event so a drop is not silent.
47299
+ */
47300
+ var PackageLlmVerdictSchema = zod.z.object({
47301
+ package: zod.z.boolean(),
47302
+ confidence: zod.z.number().min(0).max(1),
47303
+ reason: zod.z.string().min(1)
47304
+ });
47305
+ //#endregion
47262
47306
  //#region src/pipeline/cluster-model-scope.ts
47263
47307
  /**
47264
47308
  * A model choice has a SCOPE, and a scope change has a workflow.
@@ -48377,6 +48421,21 @@ var ObjectEventSchema = zod.z.object({
48377
48421
  * pick-up written before D684 — UNKNOWN, never 0 and never `timestamp`.
48378
48422
  */
48379
48423
  observedAt: zod.z.number().optional(),
48424
+ /**
48425
+ * The delivery was emitted without a vision-model yes (D931).
48426
+ *
48427
+ * Set only when the packages rule asked for an LLM confirm and the model
48428
+ * was unreachable, timed out, or answered something that did not parse.
48429
+ * A notification reads it so it can say "possible" instead of a definite
48430
+ * delivery. Absent on every other row, and on a confirm that said yes.
48431
+ */
48432
+ packagePossible: zod.z.boolean().optional(),
48433
+ /**
48434
+ * The vision model's verdict, when a packages rule asked for one and the
48435
+ * model answered the schema (D931). A "no" never becomes a row — the
48436
+ * detector drops it and logs the reason. Present on a yes.
48437
+ */
48438
+ packageLlmVerdict: PackageLlmVerdictSchema.optional(),
48380
48439
  /** The parent track's key-event importance [0,1], propagated to every object
48381
48440
  * event of the track (so an event row can be sorted by importance without a
48382
48441
  * track join). Absent on legacy rows / before the track was scored. */
@@ -51015,7 +51074,6 @@ var PipelineAddonSchemaSchema = zod.z.object({
51015
51074
  defaultModelId: zod.z.string(),
51016
51075
  defaultModelIdByFormat: zod.z.record(zod.z.string(), zod.z.string()).optional(),
51017
51076
  enabledByDefault: zod.z.boolean().optional(),
51018
- backfillIntoExistingOverrides: zod.z.boolean().optional(),
51019
51077
  defaultConfidence: zod.z.number(),
51020
51078
  group: zod.z.string().optional(),
51021
51079
  configSchema: zod.z.array(ConfigFieldBridge).readonly().optional()
@@ -51142,7 +51200,6 @@ var AudioTestResultSchema = zod.z.object({
51142
51200
  error: zod.z.string().optional(),
51143
51201
  frame: zod.z.custom().optional()
51144
51202
  });
51145
- var PipelineConfigBridge = zod.z.custom();
51146
51203
  var ConfigUISchemaBridge = zod.z.custom();
51147
51204
  var ConfigUISchemaNullableBridge = zod.z.custom();
51148
51205
  var InferenceCapabilitiesBridge = zod.z.custom();
@@ -51167,7 +51224,6 @@ var pipelineExecutorCapability = {
51167
51224
  methods: {
51168
51225
  getAvailableEngines: require_sleep.method(zod.z.void(), zod.z.array(PipelineEngineChoiceSchema)),
51169
51226
  getSelectedEngine: require_sleep.method(zod.z.void(), PipelineEngineChoiceSchema),
51170
- getDefaultSteps: require_sleep.method(PipelineEngineChoiceSchema, zod.z.array(PipelineDefaultStepSchema)),
51171
51227
  /**
51172
51228
  * Per-node detection-engine provisioning snapshot. Returns the live
51173
51229
  * state of the lazy runtime-provisioning machine on `nodeId`
@@ -51189,54 +51245,6 @@ var pipelineExecutorCapability = {
51189
51245
  auth: "admin"
51190
51246
  }),
51191
51247
  /**
51192
- * Clear THIS node's executor-side PER-DEVICE settings stores (the
51193
- * per-camera step overrides the object-detection root reads via
51194
- * `applyDeviceOverridesToTree`). `nodeId` is the ROUTING key — the
51195
- * generated cap-router strips it (default `nodeIdMode: 'routing'`) and
51196
- * dispatches to that node, so the provider method runs ON the target
51197
- * node and receives no `nodeId`.
51198
- *
51199
- * This is the slimmed executor leg of the orchestrator's
51200
- * `resetNodePipelineDefaults` flow (which owns the real reset: node
51201
- * addonDefaults pins + per-camera orchestrator overrides). The legacy
51202
- * `resetToDefault` — which reset a persisted global step-tree seed
51203
- * nothing in the live per-camera path read — was removed together with
51204
- * that seed.
51205
- */
51206
- clearDeviceOverrides: require_sleep.method(zod.z.object({ nodeId: zod.z.string() }), zod.z.object({
51207
- success: zod.z.literal(true),
51208
- clearedDevices: zod.z.number()
51209
- }), {
51210
- kind: "mutation",
51211
- auth: "admin"
51212
- }),
51213
- /**
51214
- * Auto tune (D745): the MODEL-BOUND thresholds (`bindsToModel` keys of
51215
- * the object-detection root, and their binding keys) stored in THIS node's
51216
- * per-camera detector settings — and, unless `dryRun`, remove them so the
51217
- * running model's own defaults apply. Every other key of a camera's store
51218
- * is kept. `nodeId` is the ROUTING key. `deviceIds` absent ⇒ every camera
51219
- * with a store. A camera whose store could not be read is `refused`, never
51220
- * reported as clean.
51221
- */
51222
- clearModelBoundDeviceOverrides: require_sleep.method(zod.z.object({
51223
- nodeId: zod.z.string(),
51224
- deviceIds: zod.z.array(zod.z.number()).readonly().optional(),
51225
- dryRun: zod.z.boolean()
51226
- }), zod.z.object({
51227
- entries: zod.z.array(zod.z.object({
51228
- deviceId: zod.z.number(),
51229
- keys: zod.z.array(zod.z.string()).readonly()
51230
- })).readonly(),
51231
- refused: zod.z.array(zod.z.object({
51232
- deviceId: zod.z.number(),
51233
- reason: zod.z.string()
51234
- })).readonly()
51235
- }), {
51236
- kind: "mutation",
51237
- auth: "admin"
51238
- }),
51239
- /**
51240
51248
  * Which of THIS node's inference devices the executor currently refuses,
51241
51249
  * and why. `nodeId` is the ROUTING key (stripped by the generated router).
51242
51250
  *
@@ -51291,8 +51299,6 @@ var pipelineExecutorCapability = {
51291
51299
  auth: "admin"
51292
51300
  }),
51293
51301
  getSchema: require_sleep.method(zod.z.void(), PipelineSchemaSchema),
51294
- getGlobalSteps: require_sleep.method(zod.z.void(), zod.z.array(PipelineDefaultStepSchema).readonly().nullable()),
51295
- getGlobalPipelineConfig: require_sleep.method(zod.z.void(), PipelineConfigBridge),
51296
51302
  getOrchestratorConfigSchema: require_sleep.method(zod.z.void(), ConfigUISchemaBridge),
51297
51303
  /**
51298
51304
  * Gate B (pre-init config-correctness gate). PURE COMPUTE against this
@@ -51655,11 +51661,18 @@ var KeptReasonSchema = zod.z.enum(["custom-pin"]);
51655
51661
  * Where a choice's latency came from (D791): this node's own benchmark
51656
51662
  * result, or the hardware-class table. Measured on the node beats the table.
51657
51663
  */
51658
- var LatencySourceSchema = zod.z.discriminatedUnion("kind", [zod.z.object({ kind: zod.z.literal("table") }), zod.z.object({
51659
- kind: zod.z.literal("node-benchmark"),
51660
- resultId: zod.z.string(),
51661
- measuredAtMs: zod.z.number()
51662
- })]);
51664
+ var LatencySourceSchema = zod.z.discriminatedUnion("kind", [
51665
+ zod.z.object({ kind: zod.z.literal("table") }),
51666
+ zod.z.object({
51667
+ kind: zod.z.literal("node-benchmark"),
51668
+ resultId: zod.z.string(),
51669
+ measuredAtMs: zod.z.number()
51670
+ }),
51671
+ zod.z.object({
51672
+ kind: zod.z.literal("estimated"),
51673
+ estimatedFrom: HardwareClassIdSchema
51674
+ })
51675
+ ]);
51663
51676
  var DeviceModelChoiceSchema = zod.z.object({
51664
51677
  modelId: zod.z.string(),
51665
51678
  p50Ms: zod.z.number(),
@@ -51667,6 +51680,11 @@ var DeviceModelChoiceSchema = zod.z.object({
51667
51680
  measured: zod.z.boolean(),
51668
51681
  /** Absent on a plan from before D791 — read as the table. */
51669
51682
  source: LatencySourceSchema.optional(),
51683
+ /**
51684
+ * The measured class this latency was scaled from (D930). Absent when the
51685
+ * cell is the table or a node benchmark.
51686
+ */
51687
+ estimatedFrom: HardwareClassIdSchema.optional(),
51670
51688
  /** Parallel sessions at the camera rate with the headroom kept free: ⌊fps × (1 − headroom) / camera fps⌋. */
51671
51689
  capacitySessions: zod.z.number(),
51672
51690
  personRecall: zod.z.number()
@@ -51706,40 +51724,20 @@ var NodeDeviceChangeSchema = zod.z.object({
51706
51724
  keys: zod.z.array(zod.z.string()).readonly()
51707
51725
  })).readonly()
51708
51726
  });
51709
- var CameraOverrideClearSchema = zod.z.object({
51710
- deviceId: zod.z.number(),
51711
- source: zod.z.enum(["step-override", "wholesale"]),
51727
+ /**
51728
+ * Every model and model-bound threshold lives on the accelerator that runs it
51729
+ * (D927), so the plan writes node / device entries only — there is no camera
51730
+ * layer for it to clear.
51731
+ */
51732
+ var AutoTuneWarningSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
51733
+ kind: zod.z.literal("custom-pin-kept"),
51712
51734
  nodeId: zod.z.string(),
51713
- deviceKey: zod.z.string().nullable(),
51714
- stepId: zod.z.string(),
51715
- modelIdBefore: zod.z.string().nullable(),
51716
- keys: zod.z.array(zod.z.string()).readonly()
51717
- });
51718
- var DetectorStoreClearSchema = zod.z.object({
51719
- deviceId: zod.z.number(),
51720
- keys: zod.z.array(zod.z.string()).readonly()
51721
- });
51722
- var AutoTuneWarningSchema = zod.z.discriminatedUnion("kind", [
51723
- zod.z.object({
51724
- kind: zod.z.literal("detector-store-cluster-wide"),
51725
- skippedNodes: zod.z.array(zod.z.string()).readonly()
51726
- }),
51727
- zod.z.object({
51728
- kind: zod.z.literal("detector-store-unreadable"),
51729
- error: zod.z.string()
51730
- }),
51731
- zod.z.object({
51732
- kind: zod.z.literal("custom-pin-kept"),
51733
- nodeId: zod.z.string(),
51734
- deviceKey: zod.z.string().nullable(),
51735
- deviceId: zod.z.number().nullable(),
51736
- modelId: zod.z.string()
51737
- }),
51738
- zod.z.object({
51739
- kind: zod.z.literal("custom-registry-unreadable"),
51740
- error: zod.z.string()
51741
- })
51742
- ]);
51735
+ deviceKey: zod.z.string(),
51736
+ modelId: zod.z.string()
51737
+ }), zod.z.object({
51738
+ kind: zod.z.literal("custom-registry-unreadable"),
51739
+ error: zod.z.string()
51740
+ })]);
51743
51741
  var AutoTuneOptionsSchema = zod.z.object({
51744
51742
  /** Share of each accelerator's throughput kept free (0.3–0.5). */
51745
51743
  headroom: zod.z.number().min(.3).max(.5).default(.4),
@@ -51761,8 +51759,6 @@ var AutoTunePlanSchema = zod.z.object({
51761
51759
  generatedAt: zod.z.number(),
51762
51760
  nodes: zod.z.array(NodeRecommendationSchema).readonly(),
51763
51761
  nodeChanges: zod.z.array(NodeDeviceChangeSchema).readonly(),
51764
- cameraClears: zod.z.array(CameraOverrideClearSchema).readonly(),
51765
- detectorStoreClears: zod.z.array(DetectorStoreClearSchema).readonly(),
51766
51762
  warnings: zod.z.array(AutoTuneWarningSchema).readonly()
51767
51763
  });
51768
51764
  var AutoTuneApplyInputSchema = zod.z.object({
@@ -51773,8 +51769,6 @@ var AutoTuneApplyInputSchema = zod.z.object({
51773
51769
  var AutoTunePhaseResultSchema = zod.z.object({
51774
51770
  phase: zod.z.enum([
51775
51771
  "download",
51776
- "camera-overrides",
51777
- "detector-thresholds",
51778
51772
  "node-models",
51779
51773
  "redispatch"
51780
51774
  ]),
@@ -53966,6 +53960,12 @@ var AgentPipelineSettingsSchema = zod.z.object({
53966
53960
  /** Hub-persisted UI colour (`--color-node-*` theme tokens). */
53967
53961
  color: require_node_color.NodeColorIdSchema.optional(),
53968
53962
  /**
53963
+ * NODE-wide step settings (D927): `stepId → { key → value }`, applied on
53964
+ * every accelerator of the node under the device's own settings. Never a
53965
+ * model or a model-bound threshold. Written by `setNodeStepSettings`.
53966
+ */
53967
+ stepSettings: zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.unknown()).readonly()).optional(),
53968
+ /**
53969
53969
  * Multi-device inference opt-in (Phase 4). Per-node map deviceKey → {enabled,
53970
53970
  * weight, steps}. Absent / all-disabled ⇒ the node's single default
53971
53971
  * accelerator (safe default); two+ enabled ⇒ the dispatcher balances
@@ -53981,17 +53981,6 @@ var AgentPipelineSettingsSchema = zod.z.object({
53981
53981
  steps: zod.z.record(zod.z.string(), DeviceStepConfigSchema).optional()
53982
53982
  })).optional()
53983
53983
  });
53984
- var CameraPipelineForAgentSchema = zod.z.object({
53985
- steps: zod.z.array(PipelineStepInputSchema).readonly(),
53986
- audio: zod.z.object({
53987
- modelId: zod.z.string(),
53988
- enabled: zod.z.boolean()
53989
- }).nullable()
53990
- });
53991
- var CameraStepOverridePatchSchema = zod.z.object({
53992
- modelId: zod.z.string().optional(),
53993
- settings: zod.z.record(zod.z.string(), zod.z.unknown()).readonly().optional()
53994
- });
53995
53984
  var CameraStepSettingsSchema = zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.unknown()).readonly());
53996
53985
  /** The three root-detector choices a camera has (D507: at most one root). */
53997
53986
  var CameraDetectionSourceSchema = zod.z.enum([
@@ -54001,9 +53990,7 @@ var CameraDetectionSourceSchema = zod.z.enum([
54001
53990
  ]);
54002
53991
  var CameraPipelineSettingsSchema = zod.z.object({
54003
53992
  stepToggles: zod.z.record(zod.z.string(), zod.z.boolean()).optional(),
54004
- stepSettings: CameraStepSettingsSchema.optional(),
54005
- stepOverridesByDevice: zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), CameraStepOverridePatchSchema))).optional(),
54006
- pipelineByAgent: zod.z.record(zod.z.string(), CameraPipelineForAgentSchema).optional()
53993
+ stepSettings: CameraStepSettingsSchema.optional()
54007
53994
  });
54008
53995
  /**
54009
53996
  * Pipeline placement record persisted by `addon-pipeline-orchestrator` via
@@ -54790,6 +54777,21 @@ var pipelineOrchestratorCapability = {
54790
54777
  auth: "admin"
54791
54778
  }),
54792
54779
  /**
54780
+ * Write or clear one step's NODE-wide settings (D927) — the content that
54781
+ * holds on every accelerator of the node (`agentSettings[node].stepSettings`).
54782
+ * `settings` is the whole bag for the step; `null` or `{}` clears it.
54783
+ * REFUSES a model-scoped key (a model id, a binding, a `minConfidence*`
54784
+ * floor): those are set on the accelerator (`setAgentInferenceDevices`).
54785
+ */
54786
+ setNodeStepSettings: require_sleep.method(zod.z.object({
54787
+ agentNodeId: zod.z.string(),
54788
+ addonId: zod.z.string(),
54789
+ settings: zod.z.record(zod.z.string(), zod.z.unknown()).nullable()
54790
+ }), zod.z.object({ success: zod.z.literal(true) }), {
54791
+ kind: "mutation",
54792
+ auth: "admin"
54793
+ }),
54794
+ /**
54793
54795
  * DEVICE-AWARE merged view of ONE node's inference accelerators. Joins each
54794
54796
  * physical device the node HAS — probed live via a node-pinned
54795
54797
  * `platformProbe.getCapabilities` (the platform-probe singleton is targeted
@@ -54823,23 +54825,17 @@ var pipelineOrchestratorCapability = {
54823
54825
  * (a) strips every `modelId` pin from that node's
54824
54826
  * `agentSettings.addonDefaults` (cells revert to Auto — the node
54825
54827
  * resolves its own hardware-aware format default at runtime);
54826
- * (b) clears per-camera step overrides scoped to that node
54827
- * (`stepOverridesByAgent[node]` + the wholesale
54828
- * `pipelineByAgent[node]` escape hatch);
54829
- * (c) clears the executor-side per-device settings stores on that node
54830
- * (`pipelineExecutor.clearDeviceOverrides`);
54831
- * (d) hot-reloads every camera assigned to the node.
54828
+ * (b) hot-reloads every camera assigned to the node.
54829
+ * There is no per-camera model to clear (D927).
54832
54830
  *
54833
54831
  * Returns the EFFECTIVE post-reset object-detection model for the node
54834
- * (the executor's pure hardware-aware default) so the UI can report what
54835
- * will actually run — not a value that never reaches the live path.
54832
+ * (the format default of the accelerator hosting the camera root) so the
54833
+ * UI can report what will actually run.
54836
54834
  */
54837
54835
  resetNodePipelineDefaults: require_sleep.method(zod.z.object({ agentNodeId: zod.z.string() }), zod.z.object({
54838
54836
  success: zod.z.literal(true),
54839
54837
  /** Hardware-aware default detection model now in effect on the node (null when unresolvable). */
54840
- effectiveModelId: zod.z.string().nullable(),
54841
- /** Number of cameras whose node-scoped overrides were cleared. */
54842
- clearedCameraOverrides: zod.z.number()
54838
+ effectiveModelId: zod.z.string().nullable()
54843
54839
  }), {
54844
54840
  kind: "mutation",
54845
54841
  auth: "admin"
@@ -54851,8 +54847,8 @@ var pipelineOrchestratorCapability = {
54851
54847
  * that sustains at least `minSessionsPerDevice` parallel sessions at the
54852
54848
  * camera rate with the headroom kept free (no load input), and lists every write
54853
54849
  * the apply would make: the node model pins set, the enrichment pins and
54854
- * model-bound thresholds removed (node level and per camera), and the
54855
- * per-camera detector thresholds cleared. Writes nothing. Step
54850
+ * model-bound thresholds removed — all on the accelerators, the only
54851
+ * layer that carries a model (D927). Writes nothing. Step
54856
54852
  * enablement, device enablement, pins, recording, notifications, zones and
54857
54853
  * rules are never part of it. `planHash` names exactly this diff.
54858
54854
  */
@@ -55005,31 +55001,14 @@ var pipelineOrchestratorCapability = {
55005
55001
  kind: "mutation",
55006
55002
  auth: "admin"
55007
55003
  }),
55008
- /** Read every per-(node,device) override map for a device. Null when device has no overrides. */
55009
- getCameraStepOverrides: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), zod.z.record(zod.z.string(), CameraStepOverridePatchSchema))).nullable()),
55010
- /**
55011
- * Write or clear the per-(camera,node,device) override for one addonId.
55012
- * `patch: null` clears the entry entirely for that (node, deviceKey, addon)
55013
- * triple (empty parents are pruned). Model/settings only — enablement is
55014
- * the per-camera `stepToggles` authority.
55015
- */
55016
- setCameraStepOverride: require_sleep.method(zod.z.object({
55017
- deviceId: zod.z.number(),
55018
- agentNodeId: zod.z.string(),
55019
- deviceKey: zod.z.string(),
55020
- addonId: zod.z.string(),
55021
- patch: CameraStepOverridePatchSchema.nullable()
55022
- }), zod.z.object({ success: zod.z.literal(true) }), {
55023
- kind: "mutation",
55024
- auth: "admin"
55025
- }),
55026
55004
  /**
55027
- * Write or clear one step's CAMERA-WIDE settings (D754) — the fields this
55028
- * camera overrides on every accelerator it runs on. `settings` is the
55029
- * whole field-level bag for that step (only the overridden keys; a
55030
- * model-bound field travels with its binding key); `null` or `{}` clears
55031
- * it. Merged at dispatch between the node base and the per-(node,device)
55032
- * override, key by key.
55005
+ * Write or clear one step's CAMERA-WIDE settings (D754) — the
55006
+ * camera-semantic fields this camera overrides on every accelerator it
55007
+ * runs on. `settings` is the whole field-level bag for that step (only the
55008
+ * overridden keys); `null` or `{}` clears it. Merged at dispatch over the
55009
+ * node base, key by key. REFUSES a model-scoped key — a model id, a model
55010
+ * binding or a `minConfidence*` floor (D927): those belong to the
55011
+ * accelerator that runs the model.
55033
55012
  */
55034
55013
  setCameraStepSettings: require_sleep.method(zod.z.object({
55035
55014
  deviceId: zod.z.number(),
@@ -55039,15 +55018,6 @@ var pipelineOrchestratorCapability = {
55039
55018
  kind: "mutation",
55040
55019
  auth: "admin"
55041
55020
  }),
55042
- /** Write or clear the wholesale per-agent pipeline override. Pass `pipeline: null` to drop the override. */
55043
- setCameraPipelineForAgent: require_sleep.method(zod.z.object({
55044
- deviceId: zod.z.number(),
55045
- agentNodeId: zod.z.string(),
55046
- pipeline: CameraPipelineForAgentSchema.nullable()
55047
- }), zod.z.object({ success: zod.z.literal(true) }), {
55048
- kind: "mutation",
55049
- auth: "admin"
55050
- }),
55051
55021
  /** Resolve the effective pipeline for `(deviceId, agentNodeId?)` — walks the full precedence chain. Read-only preview used by the UI to show what will run. */
55052
55022
  resolvePipeline: require_sleep.method(zod.z.object({
55053
55023
  deviceId: zod.z.number(),
@@ -67733,352 +67703,1126 @@ includeCrops: zod.z.boolean().optional() }).optional(), zod.z.array(VehicleSchem
67733
67703
  }
67734
67704
  };
67735
67705
  //#endregion
67736
- //#region src/capabilities/platform-probe.cap.ts
67737
- var ModelFormatSchema = zod.z.enum(require_sleep.MODEL_FORMATS);
67738
- var HwAccelBackendInputSchema = zod.z.enum([
67739
- "videotoolbox",
67740
- "cuda",
67741
- "nvdec",
67742
- "vaapi",
67743
- "qsv",
67744
- "d3d11va",
67745
- "dxva2",
67746
- "amf",
67747
- "vdpau",
67748
- "drm",
67749
- "coreml",
67750
- "tensorrt",
67751
- "openvino",
67752
- "directml",
67753
- "webgpu",
67754
- "none"
67755
- ]).nullable().optional();
67756
- var HwAccelResolutionSchema = zod.z.object({
67757
- preferred: zod.z.array(zod.z.string()).readonly(),
67758
- rationale: zod.z.string()
67759
- });
67760
- var HardwareEncoderIdSchema = zod.z.enum([
67761
- "h264_videotoolbox",
67762
- "hevc_videotoolbox",
67763
- "h264_vaapi",
67764
- "hevc_vaapi",
67765
- "h264_nvenc",
67766
- "hevc_nvenc",
67767
- "h264_qsv",
67768
- "hevc_qsv",
67769
- "h264_amf",
67770
- "hevc_amf",
67771
- "libx264",
67772
- "libx265"
67773
- ]);
67774
- var HardwareEncoderProbeSchema = zod.z.object({
67775
- encoder: HardwareEncoderIdSchema,
67776
- codec: zod.z.enum(["H264", "H265"]),
67777
- family: zod.z.enum([
67778
- "videotoolbox",
67779
- "vaapi",
67780
- "nvenc",
67781
- "qsv",
67782
- "amf",
67783
- "software"
67784
- ]),
67785
- available: zod.z.boolean(),
67786
- reason: zod.z.string().optional()
67787
- });
67788
- zod.z.object({
67789
- encoders: zod.z.array(HardwareEncoderProbeSchema).readonly(),
67790
- defaultH264: HardwareEncoderIdSchema,
67791
- defaultH265: HardwareEncoderIdSchema,
67792
- probedAt: zod.z.number()
67793
- });
67794
- zod.z.object({
67795
- methods: zod.z.array(zod.z.string()).readonly(),
67796
- probedAt: zod.z.number()
67797
- });
67798
- var HardwarePlatformSchema = zod.z.enum([
67799
- "darwin",
67800
- "linux",
67801
- "win32"
67802
- ]);
67803
- var HardwareArchSchema = zod.z.enum(["arm64", "x64"]);
67804
- var GpuInfoSchema = zod.z.object({
67805
- type: zod.z.enum([
67806
- "nvidia",
67807
- "amd",
67808
- "intel",
67809
- "apple"
67810
- ]),
67811
- name: zod.z.string(),
67812
- memoryMB: zod.z.number().optional()
67813
- });
67814
- var NpuInfoSchema = zod.z.object({
67815
- type: zod.z.enum([
67816
- "apple-ane",
67817
- "intel-npu",
67818
- "amd-xdna"
67819
- ]),
67820
- name: zod.z.string().optional(),
67821
- generation: zod.z.enum(["xdna1", "xdna2"]).optional()
67822
- });
67823
- var CoralBusSchema = zod.z.enum(["usb", "pci"]);
67824
- var CoralInfoSchema = zod.z.object({
67825
- type: zod.z.literal("coral-edgetpu"),
67826
- /** Legacy single-bus report (older nodes) — read via `coralUsableUnits`. */
67827
- bus: zod.z.string().optional(),
67828
- /** Legacy USB unit count (older nodes) — read via `coralUsableUnits`. */
67829
- count: zod.z.number().int().nonnegative().optional(),
67830
- /** Usable units per bus — each one is its own `edgetpu:<bus>[:N]` device. */
67831
- units: zod.z.array(zod.z.object({
67832
- bus: CoralBusSchema,
67833
- count: zod.z.number().int().nonnegative()
67834
- })).readonly().optional(),
67835
- /** Present-but-unusable units, each with its typed reason (never "absent"). */
67836
- unavailable: zod.z.array(zod.z.object({
67837
- bus: CoralBusSchema,
67838
- count: zod.z.number().int().nonnegative(),
67839
- reason: zod.z.enum(["driver-missing", "device-not-accessible"])
67840
- })).readonly().optional()
67841
- });
67842
- var HardwareInfoSchema = zod.z.object({
67843
- platform: HardwarePlatformSchema,
67844
- arch: HardwareArchSchema,
67845
- cpuModel: zod.z.string(),
67846
- cpuCores: zod.z.number(),
67847
- totalRAM_MB: zod.z.number(),
67848
- availableRAM_MB: zod.z.number(),
67849
- gpu: GpuInfoSchema.nullable(),
67850
- npu: NpuInfoSchema.nullable(),
67851
- coral: CoralInfoSchema.nullable().optional()
67852
- });
67853
- var PlatformScoreSchema = zod.z.object({
67854
- runtime: zod.z.literal("python"),
67855
- backend: zod.z.string(),
67856
- format: zod.z.enum([
67857
- "onnx",
67858
- "coreml",
67859
- "openvino",
67860
- "tflite"
67861
- ]),
67862
- score: zod.z.number(),
67863
- reason: zod.z.string(),
67864
- available: zod.z.boolean()
67865
- });
67866
- var InferenceDeviceDescriptorSchema = zod.z.object({
67867
- key: zod.z.string(),
67868
- backend: zod.z.string(),
67869
- device: zod.z.string(),
67870
- format: ModelFormatSchema,
67871
- runtime: zod.z.literal("python"),
67872
- score: zod.z.number(),
67873
- available: zod.z.boolean()
67874
- });
67875
- var PlatformCapabilitiesSchema = zod.z.object({
67876
- hardware: HardwareInfoSchema,
67877
- scores: zod.z.array(PlatformScoreSchema).readonly(),
67878
- bestScore: PlatformScoreSchema,
67879
- pythonPath: zod.z.string().nullable(),
67880
- devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly()
67881
- });
67882
- var ModelRequirementSchema = zod.z.object({
67883
- modelId: zod.z.string(),
67884
- name: zod.z.string(),
67885
- minRAM_MB: zod.z.number(),
67886
- accuracyScore: zod.z.number(),
67887
- formats: zod.z.array(zod.z.string()).readonly()
67888
- });
67889
- var ResolvedInferenceConfigSchema = zod.z.object({
67890
- modelId: zod.z.string(),
67891
- runtime: zod.z.literal("python"),
67892
- backend: zod.z.string(),
67893
- format: ModelFormatSchema,
67894
- reason: zod.z.string()
67895
- });
67896
- /**
67897
- * The answer of an accelerator rescan (D919): the node's device list after
67898
- * the re-probe, what changed, and the evidence that decided the Coral answer
67899
- * (`usbBus` — could the USB bus be read at all; `decidedBy` — USB ids, the PCI
67900
- * bus, the installed-delegate fallback, or nothing).
67901
- */
67902
- var AcceleratorRescanSchema = zod.z.object({
67903
- devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly(),
67904
- appeared: zod.z.array(zod.z.string()).readonly(),
67905
- disappeared: zod.z.array(zod.z.string()).readonly(),
67906
- usbBus: zod.z.enum(["observed", "unobservable"]),
67907
- decidedBy: zod.z.enum([
67908
- "usb-ids",
67909
- "pci",
67910
- "delegate-fallback",
67911
- "none"
67912
- ])
67913
- });
67914
- var platformProbeCapability = {
67915
- name: "platform-probe",
67916
- scope: "system",
67917
- mode: "singleton",
67918
- methods: {
67919
- getCapabilities: require_sleep.method(zod.z.void(), PlatformCapabilitiesSchema),
67920
- getHardware: require_sleep.method(zod.z.void(), HardwareInfoSchema),
67921
- resolveInferenceConfig: require_sleep.method(zod.z.object({ requirements: zod.z.array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema),
67922
- resolveHwAccel: require_sleep.method(zod.z.object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema),
67923
- /**
67924
- * Re-probe this node's accelerators NOW (Coral USB ids + PCIe apex units;
67925
- * `/sys` and `/dev` reads, no model load) and rebuild the device list that
67926
- * `getCapabilities` serves. The same re-probe runs every 60 s on its own;
67927
- * this is the operator's "rescan inference devices". Target a node with the
67928
- * `{ nodeId }` routing input, like `getCapabilities`.
67929
- */
67930
- rescanAccelerators: require_sleep.method(zod.z.void(), AcceleratorRescanSchema, {
67931
- kind: "mutation",
67932
- auth: "admin"
67933
- })
67934
- }
67935
- };
67936
- //#endregion
67937
- //#region src/capabilities/power-meter.cap.ts
67938
- /**
67939
- * Multi-metric electrical meter. One slice can carry any combination
67940
- * of instantaneous power (W), cumulative energy (kWh), voltage (V),
67941
- * and current (A) — all fields optional so a single-metric source
67942
- * (HA `sensor` with `device_class: power`) populates only what it
67943
- * has and aggregators (energy dashboards, billing exports) compose
67944
- * across providers without needing one cap per metric.
67945
- *
67946
- * Trade-off acknowledged: a HomeKit export that wants to surface
67947
- * "power" and "energy" as separate services has to crack open the
67948
- * slice and emit two characteristics from one cap. Worth it to avoid
67949
- * exploding the cap catalog with `power-w` / `energy-kwh` /
67950
- * `voltage-v` / `current-a` quartets that always travel together
67951
- * in real-world devices.
67952
- */
67953
- var PowerMeterStatusSchema = zod.z.object({
67954
- /** Instantaneous power draw in watts. */
67955
- watts: zod.z.number().optional(),
67956
- /** Cumulative energy in kilowatt-hours since the meter was reset. */
67957
- kwhTotal: zod.z.number().optional(),
67958
- /** Voltage in volts. */
67959
- volts: zod.z.number().optional(),
67960
- /** Current in amperes. */
67961
- amps: zod.z.number().optional(),
67962
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
67963
- va: zod.z.number().optional(),
67964
- /** Power factor (0..1) if reported. */
67965
- powerFactor: zod.z.number().min(0).max(1).optional(),
67966
- /** Ms epoch when the slice was last updated. */
67967
- lastFetchedAt: zod.z.number(),
67968
- /** Live display unit of the single metric this slice carries (e.g. HA
67969
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
67970
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
67971
- * unit per slice is unambiguous. The UI prefers this over the role's
67972
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
67973
- unit: zod.z.string().optional(),
67974
- /** Suggested decimal places for numeric display.
67975
- * Populated live from the upstream source when provided (e.g. HA
67976
- * `attributes.suggested_display_precision`). Falls back to
67977
- * auto-formatting when absent. */
67978
- precision: zod.z.number().int().min(0).max(10).optional()
67979
- });
67980
- var powerMeterCapability = {
67981
- name: "power-meter",
67982
- scope: "device",
67983
- deviceNative: true,
67984
- mode: "singleton",
67985
- deviceTypes: [require_err_msg.DeviceType.Sensor],
67986
- methods: {},
67987
- status: {
67988
- schema: PowerMeterStatusSchema,
67989
- kind: "push"
67990
- },
67991
- runtimeState: PowerMeterStatusSchema,
67992
- /**
67993
- * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
67994
- *
67995
- * See `RuntimeStateDurability`. Enforced by
67996
- * `scripts/check-runtime-state-durability.ts`.
67997
- */
67998
- durability: "restored",
67999
- /**
68000
- * History (D850): recorded series. Gauges are floored at 30 s; `kwhTotal` is the one monotonic meter (counter).
68001
- */
68002
- history: { fields: {
68003
- watts: {
68004
- kind: "gauge",
68005
- unit: "W",
68006
- deadband: 1,
68007
- minIntervalMs: 3e4
67706
+ //#region src/pipeline/auto-tune/detector-performance.ts
67707
+ var DETECTOR_PERF = {
67708
+ "intel-core-ultra": {
67709
+ "intel-npu": {
67710
+ "yolov9t-320": {
67711
+ p50Ms: 2.9,
67712
+ fps: 383,
67713
+ measured: true
67714
+ },
67715
+ "yolov9t-320-int8": {
67716
+ p50Ms: 3.8,
67717
+ fps: 333,
67718
+ measured: true
67719
+ },
67720
+ "yolov9t-640": {
67721
+ p50Ms: 7.4,
67722
+ fps: 139,
67723
+ measured: true
67724
+ },
67725
+ "yolov9t-640-int8": {
67726
+ p50Ms: 7.5,
67727
+ fps: 144,
67728
+ measured: true
67729
+ },
67730
+ "yolov9s-320": {
67731
+ p50Ms: 4.4,
67732
+ fps: 241,
67733
+ measured: true
67734
+ },
67735
+ "yolov9s-320-int8": {
67736
+ p50Ms: 4.1,
67737
+ fps: 264,
67738
+ measured: true
67739
+ },
67740
+ "yolov9s-640": {
67741
+ p50Ms: 12.7,
67742
+ fps: 82,
67743
+ measured: true
67744
+ },
67745
+ "yolov9s-640-int8": {
67746
+ p50Ms: 10.7,
67747
+ fps: 99,
67748
+ measured: true
67749
+ },
67750
+ "yolov9m-320": {
67751
+ p50Ms: 8.4,
67752
+ fps: 123,
67753
+ measured: true
67754
+ },
67755
+ "yolov9m-320-int8": {
67756
+ p50Ms: 6.5,
67757
+ fps: 165,
67758
+ measured: true
67759
+ },
67760
+ "yolov9m-640": {
67761
+ p50Ms: 29.5,
67762
+ fps: 32,
67763
+ measured: true
67764
+ },
67765
+ "yolov9m-640-int8": {
67766
+ p50Ms: 19.5,
67767
+ fps: 52,
67768
+ measured: true
67769
+ }
68008
67770
  },
68009
- kwhTotal: {
68010
- kind: "counter",
68011
- unit: "kWh",
68012
- deadband: .01,
68013
- minIntervalMs: 6e4
67771
+ "intel-igpu": {
67772
+ "yolov9t-320": {
67773
+ p50Ms: 2.6,
67774
+ fps: 405,
67775
+ measured: true
67776
+ },
67777
+ "yolov9t-320-int8": {
67778
+ p50Ms: 2.8,
67779
+ fps: 324,
67780
+ measured: true
67781
+ },
67782
+ "yolov9t-640": {
67783
+ p50Ms: 6.5,
67784
+ fps: 157,
67785
+ measured: true
67786
+ },
67787
+ "yolov9t-640-int8": {
67788
+ p50Ms: 5.6,
67789
+ fps: 185,
67790
+ measured: true
67791
+ },
67792
+ "yolov9s-320": {
67793
+ p50Ms: 4.6,
67794
+ fps: 200,
67795
+ measured: true
67796
+ },
67797
+ "yolov9s-320-int8": {
67798
+ p50Ms: 4.1,
67799
+ fps: 239,
67800
+ measured: true
67801
+ },
67802
+ "yolov9s-640": {
67803
+ p50Ms: 15.2,
67804
+ fps: 69,
67805
+ measured: true
67806
+ },
67807
+ "yolov9s-640-int8": {
67808
+ p50Ms: 10.5,
67809
+ fps: 99,
67810
+ measured: true
67811
+ },
67812
+ "yolov9m-320": {
67813
+ p50Ms: 11.4,
67814
+ fps: 88,
67815
+ measured: true
67816
+ },
67817
+ "yolov9m-320-int8": {
67818
+ p50Ms: 7,
67819
+ fps: 127,
67820
+ measured: true
67821
+ },
67822
+ "yolov9m-640": {
67823
+ p50Ms: 40,
67824
+ fps: 25,
67825
+ measured: true
67826
+ },
67827
+ "yolov9m-640-int8": {
67828
+ p50Ms: 23.5,
67829
+ fps: 44,
67830
+ measured: true
67831
+ }
68014
67832
  },
68015
- volts: {
68016
- kind: "gauge",
68017
- unit: "V",
68018
- deadband: 1,
68019
- minIntervalMs: 3e4
67833
+ "x86-cpu": {
67834
+ "yolov9t-320": {
67835
+ p50Ms: 6.9,
67836
+ fps: 145,
67837
+ measured: true
67838
+ },
67839
+ "yolov9t-320-int8": {
67840
+ p50Ms: 2.7,
67841
+ fps: 405,
67842
+ measured: true
67843
+ },
67844
+ "yolov9t-640": {
67845
+ p50Ms: 23,
67846
+ fps: 44,
67847
+ measured: true
67848
+ },
67849
+ "yolov9t-640-int8": {
67850
+ p50Ms: 8.8,
67851
+ fps: 124,
67852
+ measured: true
67853
+ },
67854
+ "yolov9s-320": {
67855
+ p50Ms: 15.8,
67856
+ fps: 63,
67857
+ measured: true
67858
+ },
67859
+ "yolov9s-320-int8": {
67860
+ p50Ms: 6.5,
67861
+ fps: 180,
67862
+ measured: true
67863
+ },
67864
+ "yolov9s-640": {
67865
+ p50Ms: 58.2,
67866
+ fps: 17,
67867
+ measured: true
67868
+ },
67869
+ "yolov9s-640-int8": {
67870
+ p50Ms: 20.1,
67871
+ fps: 52,
67872
+ measured: true
67873
+ },
67874
+ "yolov9m-320": {
67875
+ p50Ms: 40,
67876
+ fps: 25,
67877
+ measured: true
67878
+ },
67879
+ "yolov9m-320-int8": {
67880
+ p50Ms: 14.2,
67881
+ fps: 74,
67882
+ measured: true
67883
+ },
67884
+ "yolov9m-640": {
67885
+ p50Ms: 155.8,
67886
+ fps: 6,
67887
+ measured: true
67888
+ },
67889
+ "yolov9m-640-int8": {
67890
+ p50Ms: 52.4,
67891
+ fps: 19,
67892
+ measured: true
67893
+ }
68020
67894
  },
68021
- amps: {
68022
- kind: "gauge",
68023
- unit: "A",
68024
- deadband: .05,
68025
- minIntervalMs: 3e4
67895
+ coral: { "yolov9s-320-edgetpu": {
67896
+ p50Ms: 32,
67897
+ fps: 31,
67898
+ measured: false
67899
+ } }
67900
+ },
67901
+ "intel-n-series": {
67902
+ "intel-igpu": {
67903
+ "yolov9t-320": {
67904
+ p50Ms: 15,
67905
+ fps: 48,
67906
+ measured: true
67907
+ },
67908
+ "yolov9t-320-int8": {
67909
+ p50Ms: 14.1,
67910
+ fps: 67,
67911
+ measured: true
67912
+ },
67913
+ "yolov9t-640": {
67914
+ p50Ms: 38.5,
67915
+ fps: 26,
67916
+ measured: true
67917
+ },
67918
+ "yolov9t-640-int8": {
67919
+ p50Ms: 50,
67920
+ fps: 20,
67921
+ measured: true
67922
+ },
67923
+ "yolov9s-320": {
67924
+ p50Ms: 41.4,
67925
+ fps: 24,
67926
+ measured: true
67927
+ },
67928
+ "yolov9s-320-int8": {
67929
+ p50Ms: 23.3,
67930
+ fps: 34,
67931
+ measured: true
67932
+ },
67933
+ "yolov9s-640": {
67934
+ p50Ms: 127.4,
67935
+ fps: 8,
67936
+ measured: true
67937
+ },
67938
+ "yolov9s-640-int8": {
67939
+ p50Ms: 81,
67940
+ fps: 12,
67941
+ measured: true
67942
+ },
67943
+ "yolov9m-320": {
67944
+ p50Ms: 63.5,
67945
+ fps: 16,
67946
+ measured: true
67947
+ },
67948
+ "yolov9m-320-int8": {
67949
+ p50Ms: 66,
67950
+ fps: 15,
67951
+ measured: true
67952
+ },
67953
+ "yolov9m-640": {
67954
+ p50Ms: 215.5,
67955
+ fps: 5,
67956
+ measured: true
67957
+ },
67958
+ "yolov9m-640-int8": {
67959
+ p50Ms: 163,
67960
+ fps: 6,
67961
+ measured: true
67962
+ }
68026
67963
  },
68027
- va: {
68028
- kind: "gauge",
68029
- unit: "VA",
68030
- deadband: 1,
68031
- minIntervalMs: 3e4
67964
+ "x86-cpu": {
67965
+ "yolov9t-320": {
67966
+ p50Ms: 86.2,
67967
+ fps: 12,
67968
+ measured: true
67969
+ },
67970
+ "yolov9t-320-int8": {
67971
+ p50Ms: 52.7,
67972
+ fps: 20,
67973
+ measured: true
67974
+ },
67975
+ "yolov9t-640": {
67976
+ p50Ms: 322.1,
67977
+ fps: 3,
67978
+ measured: true
67979
+ },
67980
+ "yolov9t-640-int8": {
67981
+ p50Ms: 205.6,
67982
+ fps: 5,
67983
+ measured: true
67984
+ },
67985
+ "yolov9s-320": {
67986
+ p50Ms: 164.4,
67987
+ fps: 6,
67988
+ measured: true
67989
+ },
67990
+ "yolov9s-320-int8": {
67991
+ p50Ms: 86.9,
67992
+ fps: 12,
67993
+ measured: true
67994
+ },
67995
+ "yolov9s-640": {
67996
+ p50Ms: 341.3,
67997
+ fps: 3,
67998
+ measured: true
67999
+ },
68000
+ "yolov9s-640-int8": {
68001
+ p50Ms: 185.3,
68002
+ fps: 5,
68003
+ measured: true
68004
+ },
68005
+ "yolov9m-320": {
68006
+ p50Ms: 166.8,
68007
+ fps: 6,
68008
+ measured: true
68009
+ },
68010
+ "yolov9m-320-int8": {
68011
+ p50Ms: 226.3,
68012
+ fps: 4,
68013
+ measured: true
68014
+ },
68015
+ "yolov9m-640": {
68016
+ p50Ms: 663.5,
68017
+ fps: 2,
68018
+ measured: true
68019
+ },
68020
+ "yolov9m-640-int8": {
68021
+ p50Ms: 324.4,
68022
+ fps: 3,
68023
+ measured: true
68024
+ }
68032
68025
  },
68033
- powerFactor: {
68034
- kind: "gauge",
68035
- unit: "ratio",
68036
- deadband: .01,
68037
- minIntervalMs: 6e4
68038
- }
68039
- } },
68040
- /** Clock fields: written, but excluded from the compare that decides
68041
- * whether persisting is worth a SQLite commit. */
68042
- volatileStateFields: ["lastFetchedAt"]
68026
+ coral: { "yolov9s-320-edgetpu": {
68027
+ p50Ms: 32,
68028
+ fps: 31,
68029
+ measured: false
68030
+ } }
68031
+ },
68032
+ "amd-phoenix": {
68033
+ "x86-cpu": {
68034
+ "yolov9t-320": {
68035
+ p50Ms: 24,
68036
+ fps: 42,
68037
+ measured: true
68038
+ },
68039
+ "yolov9t-320-int8": {
68040
+ p50Ms: 12,
68041
+ fps: 83,
68042
+ measured: true
68043
+ },
68044
+ "yolov9t-640": {
68045
+ p50Ms: 50,
68046
+ fps: 20,
68047
+ measured: true
68048
+ },
68049
+ "yolov9t-640-int8": {
68050
+ p50Ms: 20,
68051
+ fps: 50,
68052
+ measured: true
68053
+ },
68054
+ "yolov9s-320": {
68055
+ p50Ms: 30,
68056
+ fps: 33,
68057
+ measured: true
68058
+ },
68059
+ "yolov9s-320-int8": {
68060
+ p50Ms: 15,
68061
+ fps: 67,
68062
+ measured: true
68063
+ },
68064
+ "yolov9s-640": {
68065
+ p50Ms: 92,
68066
+ fps: 11,
68067
+ measured: true
68068
+ },
68069
+ "yolov9s-640-int8": {
68070
+ p50Ms: 27,
68071
+ fps: 37,
68072
+ measured: true
68073
+ },
68074
+ "yolov9m-320": {
68075
+ p50Ms: 67,
68076
+ fps: 15,
68077
+ measured: true
68078
+ },
68079
+ "yolov9m-320-int8": {
68080
+ p50Ms: 21,
68081
+ fps: 48,
68082
+ measured: true
68083
+ },
68084
+ "yolov9m-640": {
68085
+ p50Ms: 229,
68086
+ fps: 4,
68087
+ measured: true
68088
+ },
68089
+ "yolov9m-640-int8": {
68090
+ p50Ms: 48,
68091
+ fps: 21,
68092
+ measured: true
68093
+ }
68094
+ },
68095
+ "amd-igpu": {
68096
+ "yolov9t-320": {
68097
+ p50Ms: 40,
68098
+ fps: 25,
68099
+ measured: true
68100
+ },
68101
+ "yolov9t-640": {
68102
+ p50Ms: 89,
68103
+ fps: 11,
68104
+ measured: true
68105
+ },
68106
+ "yolov9s-320": {
68107
+ p50Ms: 65,
68108
+ fps: 15,
68109
+ measured: true
68110
+ },
68111
+ "yolov9s-640": {
68112
+ p50Ms: 125,
68113
+ fps: 8,
68114
+ measured: true
68115
+ },
68116
+ "yolov9m-320": {
68117
+ p50Ms: 85,
68118
+ fps: 12,
68119
+ measured: true
68120
+ },
68121
+ "yolov9m-640": {
68122
+ p50Ms: 228,
68123
+ fps: 4,
68124
+ measured: true
68125
+ }
68126
+ },
68127
+ coral: { "yolov9s-320-edgetpu": {
68128
+ p50Ms: 32,
68129
+ fps: 31,
68130
+ measured: true
68131
+ } }
68132
+ },
68133
+ "apple-m-pro": {
68134
+ "apple-ane": {
68135
+ "yolov9t-320": {
68136
+ p50Ms: 1.1,
68137
+ fps: 952,
68138
+ measured: true
68139
+ },
68140
+ "yolov9t-640": {
68141
+ p50Ms: 3,
68142
+ fps: 333,
68143
+ measured: true
68144
+ },
68145
+ "yolov9s-320": {
68146
+ p50Ms: 1.5,
68147
+ fps: 667,
68148
+ measured: true
68149
+ },
68150
+ "yolov9s-640": {
68151
+ p50Ms: 5.1,
68152
+ fps: 196,
68153
+ measured: true
68154
+ },
68155
+ "yolov9m-320": {
68156
+ p50Ms: 3.2,
68157
+ fps: 312,
68158
+ measured: true
68159
+ },
68160
+ "yolov9m-640": {
68161
+ p50Ms: 10.2,
68162
+ fps: 98,
68163
+ measured: true
68164
+ }
68165
+ },
68166
+ coral: { "yolov9s-320-edgetpu": {
68167
+ p50Ms: 32,
68168
+ fps: 31,
68169
+ measured: false
68170
+ } }
68171
+ },
68172
+ "apple-m-base": {
68173
+ "apple-ane": {
68174
+ "yolov9t-320": {
68175
+ p50Ms: 1.32,
68176
+ fps: 758,
68177
+ measured: false
68178
+ },
68179
+ "yolov9t-640": {
68180
+ p50Ms: 3.45,
68181
+ fps: 290,
68182
+ measured: true
68183
+ },
68184
+ "yolov9s-320": {
68185
+ p50Ms: 1.8,
68186
+ fps: 556,
68187
+ measured: false
68188
+ },
68189
+ "yolov9s-640": {
68190
+ p50Ms: 5.46,
68191
+ fps: 183,
68192
+ measured: true
68193
+ },
68194
+ "yolov9m-320": {
68195
+ p50Ms: 3.79,
68196
+ fps: 264,
68197
+ measured: true
68198
+ },
68199
+ "yolov9m-640": {
68200
+ p50Ms: 10.34,
68201
+ fps: 97,
68202
+ measured: true
68203
+ }
68204
+ },
68205
+ coral: { "yolov9s-320-edgetpu": {
68206
+ p50Ms: 32,
68207
+ fps: 31,
68208
+ measured: false
68209
+ } }
68210
+ }
68211
+ };
68212
+ var DETECTOR_ACCURACY = {
68213
+ "yolov9m-320": {
68214
+ personPrecision: .979,
68215
+ personRecall: .649
68216
+ },
68217
+ "yolov9m-320-int8": {
68218
+ personPrecision: .974,
68219
+ personRecall: .684
68220
+ },
68221
+ "yolov9m-640": {
68222
+ personPrecision: .976,
68223
+ personRecall: .854
68224
+ },
68225
+ "yolov9m-640-int8": {
68226
+ personPrecision: .976,
68227
+ personRecall: .847
68228
+ },
68229
+ "yolov9s-320": {
68230
+ personPrecision: .958,
68231
+ personRecall: .479
68232
+ },
68233
+ "yolov9s-320-int8": {
68234
+ personPrecision: .969,
68235
+ personRecall: .438
68236
+ },
68237
+ "yolov9s-640": {
68238
+ personPrecision: .986,
68239
+ personRecall: .753
68240
+ },
68241
+ "yolov9s-640-int8": {
68242
+ personPrecision: .963,
68243
+ personRecall: .823
68244
+ },
68245
+ "yolov9t-320": {
68246
+ personPrecision: .946,
68247
+ personRecall: .368
68248
+ },
68249
+ "yolov9t-320-int8": {
68250
+ personPrecision: .943,
68251
+ personRecall: .347
68252
+ },
68253
+ "yolov9t-640": {
68254
+ personPrecision: .979,
68255
+ personRecall: .646
68256
+ },
68257
+ "yolov9t-640-int8": {
68258
+ personPrecision: .979,
68259
+ personRecall: .646
68260
+ }
68043
68261
  };
68044
68262
  //#endregion
68045
- //#region src/capabilities/presence.cap.ts
68263
+ //#region src/pipeline/auto-tune/accelerator-compute.ts
68264
+ /**
68265
+ * Capacity of an accelerator we have not measured (D930).
68266
+ *
68267
+ * The latency table stays the measured columns. A device that reports compute
68268
+ * (OpenVINO GOPS, or execution units × clock, or compute units, or TOPS) is
68269
+ * estimated from the nearest measured column of the same accelerator kind,
68270
+ * scaled by the compute ratio and clamped. A node benchmark still replaces
68271
+ * the estimate. A device that reports neither compute nor a known class stays
68272
+ * `unknown`.
68273
+ *
68274
+ * A failed probe field is `null`, never `0` (D393): zero would be a compute
68275
+ * ratio of zero and would size the device as if it could not run.
68276
+ */
68277
+ /**
68278
+ * An unmeasured device is never estimated outside this band of its reference.
68279
+ *
68280
+ * Peak GOPS and detector latency do not move together: a 640 model is memory
68281
+ * bound, a 320 model less so, and the N100→N305 public EU×clock ratio is
68282
+ * about 2.2×. 2× is the widest that spread justifies without a measurement
68283
+ * on the device; 0.5× is the symmetric floor so a tiny iGPU is not told it
68284
+ * is many times slower than the weakest column we measured. A node benchmark
68285
+ * replaces the estimate and is not clamped.
68286
+ */
68287
+ var ESTIMATE_COMPUTE_RATIO_MIN = .5;
68288
+ var ESTIMATE_COMPUTE_RATIO_MAX = 2;
68289
+ /**
68290
+ * Within this of 1.0, a device whose class IS the reference is that measured
68291
+ * machine (a probe clock a few percent off the spec), not a different chip.
68292
+ * An unknown class is still estimated, so the plan says so.
68293
+ */
68294
+ var REFERENCE_MATCH_FRACTION = .05;
68295
+ /** N100 iGPU: 24 execution units, 750 MHz graphics max (Intel specification). */
68296
+ var N100_IGPU_EU = 24;
68297
+ var N100_IGPU_CLOCK_MHZ = 750;
68298
+ /**
68299
+ * Core Ultra 7 265K iGPU: 4 Xe-cores. OpenVINO's execution-unit count for
68300
+ * Xe-LP is 16 per Xe-core, and the graphics max dynamic frequency is 2000 MHz.
68301
+ * Used only to choose the nearest column for a chip that is not itself a
68302
+ * Core Ultra — that class is the measurement and is never scaled.
68303
+ */
68304
+ var CORE_ULTRA_265K_IGPU_EU = 64;
68305
+ var CORE_ULTRA_265K_IGPU_CLOCK_MHZ = 2e3;
68306
+ /** Phoenix / Hawk Point Radeon 780M: 12 compute units. */
68307
+ var PHOENIX_780M_CU = 12;
68308
+ var positiveOrNull = zod.z.number().nullable().transform((value) => value !== null && Number.isFinite(value) && value > 0 ? value : null);
68309
+ var textOrNull = zod.z.string().nullable().transform((value) => value !== null && value.trim() !== "" ? value : null);
68310
+ var PrecisionGopsSchema = zod.z.object({
68311
+ int8: positiveOrNull,
68312
+ fp16: positiveOrNull
68313
+ });
68314
+ /** What one accelerator device reported. Every failed query is null. */
68315
+ var AcceleratorComputeSchema = zod.z.object({
68316
+ deviceKey: zod.z.string(),
68317
+ fullDeviceName: textOrNull,
68318
+ executionUnits: positiveOrNull,
68319
+ clockMHz: positiveOrNull,
68320
+ gops: PrecisionGopsSchema.nullable(),
68321
+ architecture: textOrNull,
68322
+ npuName: textOrNull,
68323
+ tops: positiveOrNull,
68324
+ vendor: textOrNull,
68325
+ webgpuArchitecture: textOrNull,
68326
+ webgpuDevice: textOrNull,
68327
+ computeUnits: positiveOrNull
68328
+ });
68329
+ var AcceleratorFactsPayloadSchema = zod.z.object({ accelerators: zod.z.array(AcceleratorComputeSchema).nullable() });
68330
+ /**
68331
+ * Compute of the machines the latency columns were measured on.
68332
+ * GOPS is preferred when a column carries it; none of these do yet — the
68333
+ * boxes were measured before the probe reported `DEVICE_GOPS` — so the scale
68334
+ * uses execution units × clock (Intel) or compute units (AMD) until a
68335
+ * re-measure locks a GOPS figure on the column.
68336
+ */
68337
+ var REFERENCE_COMPUTE = [
68338
+ {
68339
+ hardwareClass: "intel-n-series",
68340
+ accelerator: "intel-igpu",
68341
+ score: {
68342
+ metric: "eu-mhz",
68343
+ value: 24 * 750
68344
+ }
68345
+ },
68346
+ {
68347
+ hardwareClass: "intel-core-ultra",
68348
+ accelerator: "intel-igpu",
68349
+ score: {
68350
+ metric: "eu-mhz",
68351
+ value: 64 * CORE_ULTRA_265K_IGPU_CLOCK_MHZ
68352
+ }
68353
+ },
68354
+ {
68355
+ hardwareClass: "amd-phoenix",
68356
+ accelerator: "amd-igpu",
68357
+ score: {
68358
+ metric: "cu",
68359
+ value: 12
68360
+ }
68361
+ }
68362
+ ];
68363
+ /** Parse a probe payload. `null` means the probe failed — not "no accelerators". */
68364
+ function parseAcceleratorFactsJson(raw) {
68365
+ const parsed = AcceleratorFactsPayloadSchema.safeParse(raw);
68366
+ if (!parsed.success || parsed.data.accelerators === null) return null;
68367
+ return parsed.data.accelerators;
68368
+ }
68369
+ function positive$1(value) {
68370
+ return value !== null && value > 0 ? value : null;
68371
+ }
68372
+ /** Scores in the order the estimate prefers. A non-positive field is absent. */
68373
+ function computeScores(report) {
68374
+ const scores = [];
68375
+ const int8 = positive$1(report.gops?.int8 ?? null);
68376
+ if (int8 !== null) scores.push({
68377
+ metric: "gops-int8",
68378
+ value: int8
68379
+ });
68380
+ const fp16 = positive$1(report.gops?.fp16 ?? null);
68381
+ if (fp16 !== null) scores.push({
68382
+ metric: "gops-fp16",
68383
+ value: fp16
68384
+ });
68385
+ const units = positive$1(report.executionUnits);
68386
+ const clock = positive$1(report.clockMHz);
68387
+ if (units !== null && clock !== null) scores.push({
68388
+ metric: "eu-mhz",
68389
+ value: units * clock
68390
+ });
68391
+ const computeUnits = positive$1(report.computeUnits);
68392
+ if (computeUnits !== null) scores.push({
68393
+ metric: "cu",
68394
+ value: computeUnits
68395
+ });
68396
+ const tops = positive$1(report.tops);
68397
+ if (tops !== null) scores.push({
68398
+ metric: "tops",
68399
+ value: tops
68400
+ });
68401
+ return scores;
68402
+ }
68403
+ function nearestReference(kind, score, references) {
68404
+ const same = references.filter((ref) => ref.accelerator === kind && ref.score.metric === score.metric && ref.score.value > 0);
68405
+ const first = same[0];
68406
+ if (first === void 0) return null;
68407
+ return same.reduce((best, ref) => {
68408
+ return Math.abs(Math.log(score.value / ref.score.value)) < Math.abs(Math.log(score.value / best.score.value)) ? ref : best;
68409
+ }, first);
68410
+ }
68411
+ function scoreAgainstReferences(report, kind, references) {
68412
+ for (const score of computeScores(report)) if (nearestReference(kind, score, references) !== null) return score;
68413
+ return null;
68414
+ }
68415
+ /** True when this report can scale a measured column of `kind`. */
68416
+ function reportIsScalable(report, kind, references = REFERENCE_COMPUTE) {
68417
+ return scoreAgainstReferences(report, kind, references) !== null;
68418
+ }
68419
+ function classColumn(perf, hardwareClass, accelerator) {
68420
+ if (hardwareClass === "unknown") return void 0;
68421
+ return perf[hardwareClass]?.[accelerator];
68422
+ }
68423
+ function round1(value) {
68424
+ return Math.round(value * 10) / 10;
68425
+ }
68426
+ function clampRatio(ratio) {
68427
+ return Math.min(2, Math.max(ESTIMATE_COMPUTE_RATIO_MIN, ratio));
68428
+ }
68429
+ function scaleColumn(column, ratio, estimatedFrom) {
68430
+ const out = {};
68431
+ for (const [modelId, cell] of Object.entries(column)) out[modelId] = {
68432
+ p50Ms: round1(cell.p50Ms / ratio),
68433
+ fps: Math.max(1, Math.round(cell.fps * ratio)),
68434
+ measured: false,
68435
+ estimatedFrom
68436
+ };
68437
+ return out;
68438
+ }
68046
68439
  /**
68047
- * Presence cap. Models HA `person.*` and `device_tracker.*` entities
68048
- * on `DeviceType.Presence`. Read-only — no setters: presence is
68049
- * derived from upstream signals (mobile-app location, router DHCP
68050
- * lease, Bluetooth beacon, manual zone change) and pushed into the
68051
- * slice.
68052
- *
68053
- * `state` follows HA's canonical values — `home` / `not_home` /
68054
- * `<zone-name>` for any other named zone. The zone name is freeform
68055
- * because users define arbitrary zones (`work`, `gym`, `school`).
68056
- *
68057
- * GPS coordinates are nullable — tracker types without location
68058
- * (Bluetooth scanner, MQTT presence) omit them. The presence of
68059
- * GPS data is signaled by `DeviceFeature.PresenceGps` for fast
68060
- * UI map-pin rendering.
68440
+ * The latency column for one device. A measured class other than the N-series
68441
+ * spread is returned unchanged — one class there is one measured machine.
68442
+ * `intel-n-series` is the N100 column; a different reported compute scales it.
68443
+ * An unknown class scales from the nearest reference of the same kind.
68061
68444
  */
68062
- var GpsLocationSchema = zod.z.object({
68063
- /** Latitude in decimal degrees, -90..90. */
68064
- latitude: zod.z.number().min(-90).max(90),
68065
- /** Longitude in decimal degrees, -180..180. */
68066
- longitude: zod.z.number().min(-180).max(180),
68067
- /** Reported accuracy in meters (lower = better). */
68068
- accuracyMeters: zod.z.number().nonnegative()
68069
- });
68070
- var PresenceStatusSchema = zod.z.object({
68071
- /** `home` / `not_home` / any user-defined zone name. */
68072
- state: zod.z.string(),
68073
- /** Optional textual location label (zone name, city, address). Null
68074
- * when only the binary state is known. */
68075
- location: zod.z.string().nullable(),
68076
- /** GPS coordinates when available. Null otherwise. */
68077
- gps: GpsLocationSchema.nullable(),
68078
- /** Optional battery level of the tracking device (0..100). Null
68079
- * when not reported. */
68080
- batteryPercent: zod.z.number().min(0).max(100).nullable(),
68081
- /** Ms epoch when the slice was last updated. */
68445
+ function resolveLatencyColumn(input) {
68446
+ const perf = input.perf ?? DETECTOR_PERF;
68447
+ const references = input.references ?? REFERENCE_COMPUTE;
68448
+ const measured = classColumn(perf, input.hardwareClass, input.accelerator);
68449
+ if (measured !== void 0 && input.hardwareClass !== "intel-n-series") return {
68450
+ column: measured,
68451
+ estimatedFrom: null
68452
+ };
68453
+ const score = input.report === null ? null : scoreAgainstReferences(input.report, input.accelerator, references);
68454
+ if (score === null) return measured === void 0 ? null : {
68455
+ column: measured,
68456
+ estimatedFrom: null
68457
+ };
68458
+ const nearest = nearestReference(input.accelerator, score, references);
68459
+ const base = nearest === null ? void 0 : classColumn(perf, nearest.hardwareClass, input.accelerator);
68460
+ if (nearest === null || base === void 0) return measured === void 0 ? null : {
68461
+ column: measured,
68462
+ estimatedFrom: null
68463
+ };
68464
+ const rawRatio = score.value / nearest.score.value;
68465
+ if (input.hardwareClass === nearest.hardwareClass && Math.abs(rawRatio - 1) <= .05) return {
68466
+ column: base,
68467
+ estimatedFrom: null
68468
+ };
68469
+ return {
68470
+ column: scaleColumn(base, clampRatio(rawRatio), nearest.hardwareClass),
68471
+ estimatedFrom: nearest.hardwareClass
68472
+ };
68473
+ }
68474
+ //#endregion
68475
+ //#region src/capabilities/platform-probe.cap.ts
68476
+ var ModelFormatSchema = zod.z.enum(require_sleep.MODEL_FORMATS);
68477
+ var HwAccelBackendInputSchema = zod.z.enum([
68478
+ "videotoolbox",
68479
+ "cuda",
68480
+ "nvdec",
68481
+ "vaapi",
68482
+ "qsv",
68483
+ "d3d11va",
68484
+ "dxva2",
68485
+ "amf",
68486
+ "vdpau",
68487
+ "drm",
68488
+ "coreml",
68489
+ "tensorrt",
68490
+ "openvino",
68491
+ "directml",
68492
+ "webgpu",
68493
+ "none"
68494
+ ]).nullable().optional();
68495
+ var HwAccelResolutionSchema = zod.z.object({
68496
+ preferred: zod.z.array(zod.z.string()).readonly(),
68497
+ rationale: zod.z.string()
68498
+ });
68499
+ var HardwareEncoderIdSchema = zod.z.enum([
68500
+ "h264_videotoolbox",
68501
+ "hevc_videotoolbox",
68502
+ "h264_vaapi",
68503
+ "hevc_vaapi",
68504
+ "h264_nvenc",
68505
+ "hevc_nvenc",
68506
+ "h264_qsv",
68507
+ "hevc_qsv",
68508
+ "h264_amf",
68509
+ "hevc_amf",
68510
+ "libx264",
68511
+ "libx265"
68512
+ ]);
68513
+ var HardwareEncoderProbeSchema = zod.z.object({
68514
+ encoder: HardwareEncoderIdSchema,
68515
+ codec: zod.z.enum(["H264", "H265"]),
68516
+ family: zod.z.enum([
68517
+ "videotoolbox",
68518
+ "vaapi",
68519
+ "nvenc",
68520
+ "qsv",
68521
+ "amf",
68522
+ "software"
68523
+ ]),
68524
+ available: zod.z.boolean(),
68525
+ reason: zod.z.string().optional()
68526
+ });
68527
+ zod.z.object({
68528
+ encoders: zod.z.array(HardwareEncoderProbeSchema).readonly(),
68529
+ defaultH264: HardwareEncoderIdSchema,
68530
+ defaultH265: HardwareEncoderIdSchema,
68531
+ probedAt: zod.z.number()
68532
+ });
68533
+ zod.z.object({
68534
+ methods: zod.z.array(zod.z.string()).readonly(),
68535
+ probedAt: zod.z.number()
68536
+ });
68537
+ var HardwarePlatformSchema = zod.z.enum([
68538
+ "darwin",
68539
+ "linux",
68540
+ "win32"
68541
+ ]);
68542
+ var HardwareArchSchema = zod.z.enum(["arm64", "x64"]);
68543
+ var GpuInfoSchema = zod.z.object({
68544
+ type: zod.z.enum([
68545
+ "nvidia",
68546
+ "amd",
68547
+ "intel",
68548
+ "apple"
68549
+ ]),
68550
+ name: zod.z.string(),
68551
+ memoryMB: zod.z.number().optional()
68552
+ });
68553
+ var NpuInfoSchema = zod.z.object({
68554
+ type: zod.z.enum([
68555
+ "apple-ane",
68556
+ "intel-npu",
68557
+ "amd-xdna"
68558
+ ]),
68559
+ name: zod.z.string().optional(),
68560
+ generation: zod.z.enum(["xdna1", "xdna2"]).optional()
68561
+ });
68562
+ var CoralBusSchema = zod.z.enum(["usb", "pci"]);
68563
+ var CoralInfoSchema = zod.z.object({
68564
+ type: zod.z.literal("coral-edgetpu"),
68565
+ /** Legacy single-bus report (older nodes) — read via `coralUsableUnits`. */
68566
+ bus: zod.z.string().optional(),
68567
+ /** Legacy USB unit count (older nodes) — read via `coralUsableUnits`. */
68568
+ count: zod.z.number().int().nonnegative().optional(),
68569
+ /** Usable units per bus — each one is its own `edgetpu:<bus>[:N]` device. */
68570
+ units: zod.z.array(zod.z.object({
68571
+ bus: CoralBusSchema,
68572
+ count: zod.z.number().int().nonnegative()
68573
+ })).readonly().optional(),
68574
+ /** Present-but-unusable units, each with its typed reason (never "absent"). */
68575
+ unavailable: zod.z.array(zod.z.object({
68576
+ bus: CoralBusSchema,
68577
+ count: zod.z.number().int().nonnegative(),
68578
+ reason: zod.z.enum(["driver-missing", "device-not-accessible"])
68579
+ })).readonly().optional()
68580
+ });
68581
+ var HardwareInfoSchema = zod.z.object({
68582
+ platform: HardwarePlatformSchema,
68583
+ arch: HardwareArchSchema,
68584
+ cpuModel: zod.z.string(),
68585
+ cpuCores: zod.z.number(),
68586
+ totalRAM_MB: zod.z.number(),
68587
+ availableRAM_MB: zod.z.number(),
68588
+ gpu: GpuInfoSchema.nullable(),
68589
+ npu: NpuInfoSchema.nullable(),
68590
+ coral: CoralInfoSchema.nullable().optional(),
68591
+ /**
68592
+ * Per-accelerator compute (D930). Omitted by a node that has not probed it.
68593
+ * `null` fields inside an entry are a failed query, never a zero.
68594
+ */
68595
+ accelerators: zod.z.array(AcceleratorComputeSchema).readonly().optional()
68596
+ });
68597
+ var PlatformScoreSchema = zod.z.object({
68598
+ runtime: zod.z.literal("python"),
68599
+ backend: zod.z.string(),
68600
+ format: zod.z.enum([
68601
+ "onnx",
68602
+ "coreml",
68603
+ "openvino",
68604
+ "tflite"
68605
+ ]),
68606
+ score: zod.z.number(),
68607
+ reason: zod.z.string(),
68608
+ available: zod.z.boolean()
68609
+ });
68610
+ var InferenceDeviceDescriptorSchema = zod.z.object({
68611
+ key: zod.z.string(),
68612
+ backend: zod.z.string(),
68613
+ device: zod.z.string(),
68614
+ format: ModelFormatSchema,
68615
+ runtime: zod.z.literal("python"),
68616
+ score: zod.z.number(),
68617
+ available: zod.z.boolean()
68618
+ });
68619
+ var PlatformCapabilitiesSchema = zod.z.object({
68620
+ hardware: HardwareInfoSchema,
68621
+ scores: zod.z.array(PlatformScoreSchema).readonly(),
68622
+ bestScore: PlatformScoreSchema,
68623
+ pythonPath: zod.z.string().nullable(),
68624
+ devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly()
68625
+ });
68626
+ var ModelRequirementSchema = zod.z.object({
68627
+ modelId: zod.z.string(),
68628
+ name: zod.z.string(),
68629
+ minRAM_MB: zod.z.number(),
68630
+ accuracyScore: zod.z.number(),
68631
+ formats: zod.z.array(zod.z.string()).readonly()
68632
+ });
68633
+ var ResolvedInferenceConfigSchema = zod.z.object({
68634
+ modelId: zod.z.string(),
68635
+ runtime: zod.z.literal("python"),
68636
+ backend: zod.z.string(),
68637
+ format: ModelFormatSchema,
68638
+ reason: zod.z.string()
68639
+ });
68640
+ /**
68641
+ * The answer of an accelerator rescan (D919): the node's device list after
68642
+ * the re-probe, what changed, and the evidence that decided the Coral answer
68643
+ * (`usbBus` — could the USB bus be read at all; `decidedBy` — USB ids, the PCI
68644
+ * bus, the installed-delegate fallback, or nothing).
68645
+ */
68646
+ var AcceleratorRescanSchema = zod.z.object({
68647
+ devices: zod.z.array(InferenceDeviceDescriptorSchema).readonly(),
68648
+ appeared: zod.z.array(zod.z.string()).readonly(),
68649
+ disappeared: zod.z.array(zod.z.string()).readonly(),
68650
+ usbBus: zod.z.enum(["observed", "unobservable"]),
68651
+ decidedBy: zod.z.enum([
68652
+ "usb-ids",
68653
+ "pci",
68654
+ "delegate-fallback",
68655
+ "none"
68656
+ ])
68657
+ });
68658
+ var platformProbeCapability = {
68659
+ name: "platform-probe",
68660
+ scope: "system",
68661
+ mode: "singleton",
68662
+ methods: {
68663
+ getCapabilities: require_sleep.method(zod.z.void(), PlatformCapabilitiesSchema),
68664
+ getHardware: require_sleep.method(zod.z.void(), HardwareInfoSchema),
68665
+ resolveInferenceConfig: require_sleep.method(zod.z.object({ requirements: zod.z.array(ModelRequirementSchema).readonly() }), ResolvedInferenceConfigSchema),
68666
+ resolveHwAccel: require_sleep.method(zod.z.object({ prefer: HwAccelBackendInputSchema }), HwAccelResolutionSchema),
68667
+ /**
68668
+ * Re-probe this node's accelerators NOW (Coral USB ids + PCIe apex units;
68669
+ * `/sys` and `/dev` reads, no model load) and rebuild the device list that
68670
+ * `getCapabilities` serves. The same re-probe runs every 60 s on its own;
68671
+ * this is the operator's "rescan inference devices". Target a node with the
68672
+ * `{ nodeId }` routing input, like `getCapabilities`.
68673
+ */
68674
+ rescanAccelerators: require_sleep.method(zod.z.void(), AcceleratorRescanSchema, {
68675
+ kind: "mutation",
68676
+ auth: "admin"
68677
+ })
68678
+ }
68679
+ };
68680
+ //#endregion
68681
+ //#region src/capabilities/power-meter.cap.ts
68682
+ /**
68683
+ * Multi-metric electrical meter. One slice can carry any combination
68684
+ * of instantaneous power (W), cumulative energy (kWh), voltage (V),
68685
+ * and current (A) — all fields optional so a single-metric source
68686
+ * (HA `sensor` with `device_class: power`) populates only what it
68687
+ * has and aggregators (energy dashboards, billing exports) compose
68688
+ * across providers without needing one cap per metric.
68689
+ *
68690
+ * Trade-off acknowledged: a HomeKit export that wants to surface
68691
+ * "power" and "energy" as separate services has to crack open the
68692
+ * slice and emit two characteristics from one cap. Worth it to avoid
68693
+ * exploding the cap catalog with `power-w` / `energy-kwh` /
68694
+ * `voltage-v` / `current-a` quartets that always travel together
68695
+ * in real-world devices.
68696
+ */
68697
+ var PowerMeterStatusSchema = zod.z.object({
68698
+ /** Instantaneous power draw in watts. */
68699
+ watts: zod.z.number().optional(),
68700
+ /** Cumulative energy in kilowatt-hours since the meter was reset. */
68701
+ kwhTotal: zod.z.number().optional(),
68702
+ /** Voltage in volts. */
68703
+ volts: zod.z.number().optional(),
68704
+ /** Current in amperes. */
68705
+ amps: zod.z.number().optional(),
68706
+ /** Apparent power in volt-amperes (W × power-factor reciprocal). */
68707
+ va: zod.z.number().optional(),
68708
+ /** Power factor (0..1) if reported. */
68709
+ powerFactor: zod.z.number().min(0).max(1).optional(),
68710
+ /** Ms epoch when the slice was last updated. */
68711
+ lastFetchedAt: zod.z.number(),
68712
+ /** Live display unit of the single metric this slice carries (e.g. HA
68713
+ * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
68714
+ * Each upstream `sensor.*` entity surfaces ONE device_class, so one
68715
+ * unit per slice is unambiguous. The UI prefers this over the role's
68716
+ * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
68717
+ unit: zod.z.string().optional(),
68718
+ /** Suggested decimal places for numeric display.
68719
+ * Populated live from the upstream source when provided (e.g. HA
68720
+ * `attributes.suggested_display_precision`). Falls back to
68721
+ * auto-formatting when absent. */
68722
+ precision: zod.z.number().int().min(0).max(10).optional()
68723
+ });
68724
+ var powerMeterCapability = {
68725
+ name: "power-meter",
68726
+ scope: "device",
68727
+ deviceNative: true,
68728
+ mode: "singleton",
68729
+ deviceTypes: [require_err_msg.DeviceType.Sensor],
68730
+ methods: {},
68731
+ status: {
68732
+ schema: PowerMeterStatusSchema,
68733
+ kind: "push"
68734
+ },
68735
+ runtimeState: PowerMeterStatusSchema,
68736
+ /**
68737
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
68738
+ *
68739
+ * See `RuntimeStateDurability`. Enforced by
68740
+ * `scripts/check-runtime-state-durability.ts`.
68741
+ */
68742
+ durability: "restored",
68743
+ /**
68744
+ * History (D850): recorded series. Gauges are floored at 30 s; `kwhTotal` is the one monotonic meter (counter).
68745
+ */
68746
+ history: { fields: {
68747
+ watts: {
68748
+ kind: "gauge",
68749
+ unit: "W",
68750
+ deadband: 1,
68751
+ minIntervalMs: 3e4
68752
+ },
68753
+ kwhTotal: {
68754
+ kind: "counter",
68755
+ unit: "kWh",
68756
+ deadband: .01,
68757
+ minIntervalMs: 6e4
68758
+ },
68759
+ volts: {
68760
+ kind: "gauge",
68761
+ unit: "V",
68762
+ deadband: 1,
68763
+ minIntervalMs: 3e4
68764
+ },
68765
+ amps: {
68766
+ kind: "gauge",
68767
+ unit: "A",
68768
+ deadband: .05,
68769
+ minIntervalMs: 3e4
68770
+ },
68771
+ va: {
68772
+ kind: "gauge",
68773
+ unit: "VA",
68774
+ deadband: 1,
68775
+ minIntervalMs: 3e4
68776
+ },
68777
+ powerFactor: {
68778
+ kind: "gauge",
68779
+ unit: "ratio",
68780
+ deadband: .01,
68781
+ minIntervalMs: 6e4
68782
+ }
68783
+ } },
68784
+ /** Clock fields: written, but excluded from the compare that decides
68785
+ * whether persisting is worth a SQLite commit. */
68786
+ volatileStateFields: ["lastFetchedAt"]
68787
+ };
68788
+ //#endregion
68789
+ //#region src/capabilities/presence.cap.ts
68790
+ /**
68791
+ * Presence cap. Models HA `person.*` and `device_tracker.*` entities
68792
+ * on `DeviceType.Presence`. Read-only — no setters: presence is
68793
+ * derived from upstream signals (mobile-app location, router DHCP
68794
+ * lease, Bluetooth beacon, manual zone change) and pushed into the
68795
+ * slice.
68796
+ *
68797
+ * `state` follows HA's canonical values — `home` / `not_home` /
68798
+ * `<zone-name>` for any other named zone. The zone name is freeform
68799
+ * because users define arbitrary zones (`work`, `gym`, `school`).
68800
+ *
68801
+ * GPS coordinates are nullable — tracker types without location
68802
+ * (Bluetooth scanner, MQTT presence) omit them. The presence of
68803
+ * GPS data is signaled by `DeviceFeature.PresenceGps` for fast
68804
+ * UI map-pin rendering.
68805
+ */
68806
+ var GpsLocationSchema = zod.z.object({
68807
+ /** Latitude in decimal degrees, -90..90. */
68808
+ latitude: zod.z.number().min(-90).max(90),
68809
+ /** Longitude in decimal degrees, -180..180. */
68810
+ longitude: zod.z.number().min(-180).max(180),
68811
+ /** Reported accuracy in meters (lower = better). */
68812
+ accuracyMeters: zod.z.number().nonnegative()
68813
+ });
68814
+ var PresenceStatusSchema = zod.z.object({
68815
+ /** `home` / `not_home` / any user-defined zone name. */
68816
+ state: zod.z.string(),
68817
+ /** Optional textual location label (zone name, city, address). Null
68818
+ * when only the binary state is known. */
68819
+ location: zod.z.string().nullable(),
68820
+ /** GPS coordinates when available. Null otherwise. */
68821
+ gps: GpsLocationSchema.nullable(),
68822
+ /** Optional battery level of the tracking device (0..100). Null
68823
+ * when not reported. */
68824
+ batteryPercent: zod.z.number().min(0).max(100).nullable(),
68825
+ /** Ms epoch when the slice was last updated. */
68082
68826
  lastChangedAt: zod.z.number()
68083
68827
  });
68084
68828
  var presenceCapability = {
@@ -71757,7 +72501,20 @@ var ZoneRuleSchema = zod.z.object({
71757
72501
  * Defaults to `true` so operators creating a rule via the UI
71758
72502
  * see it active immediately.
71759
72503
  */
71760
- enabled: zod.z.boolean().default(true)
72504
+ enabled: zod.z.boolean().default(true),
72505
+ /**
72506
+ * Package stage only. When true, a package candidate may open from a
72507
+ * scene difference in this rule's zone after a person leaves. The
72508
+ * package model, when it boxes something overlapping, is extra evidence
72509
+ * and is not the gate. Absent or false keeps today's model path.
72510
+ */
72511
+ detectBySceneDifference: zod.z.boolean().optional(),
72512
+ /**
72513
+ * Package stage only. When true, a promoted package is shown to the
72514
+ * `llm` cap before the delivery event is emitted. Absent or false emits
72515
+ * as today. Usable with or without `detectBySceneDifference`.
72516
+ */
72517
+ confirmWithLlm: zod.z.boolean().optional()
71761
72518
  });
71762
72519
  /**
71763
72520
  * Convenience array schema — used by addon device-settings
@@ -74721,7 +75478,7 @@ var trainingProviderCapability = {
74721
75478
  * - **Jobs.** A persisted state machine, a reconcile loop against the
74722
75479
  * provider, a cost guard, a daily orphan sweep and the retention rules.
74723
75480
  * - **The return flow.** Verified artefacts → Model Studio
74724
- * (`custom-model-registry.importModelBundle`) → canary on cameras →
75481
+ * (`custom-model-registry.importModelBundle`) → canary on accelerators →
74725
75482
  * rollback. Nothing is ever registered automatically.
74726
75483
  *
74727
75484
  * Plan/start takes a live {@link TrainingCorpusSelection} (collections +
@@ -74858,20 +75615,24 @@ var TrainingProgressPointSchema = zod.z.object({
74858
75615
  map50: zod.z.number().nullable(),
74859
75616
  map50_95: zod.z.number().nullable()
74860
75617
  });
75618
+ /** One accelerator: a `(node, inference device)` that runs the root detector. */
75619
+ var TrainingAcceleratorRefSchema = zod.z.object({
75620
+ nodeId: zod.z.string(),
75621
+ deviceKey: zod.z.string()
75622
+ });
75623
+ /**
75624
+ * A canary pins the registered model on chosen ACCELERATORS — a model is
75625
+ * chosen by the accelerator that runs it (D927); every camera balanced onto
75626
+ * one runs it. `previous` is the root-detector pin each one had (`null` = it
75627
+ * ran its default), exactly, so rollback restores it.
75628
+ */
74861
75629
  var TrainingCanarySchema = zod.z.object({
74862
- deviceIds: zod.z.array(zod.z.number().int()).readonly(),
75630
+ accelerators: zod.z.array(TrainingAcceleratorRefSchema).readonly(),
74863
75631
  since: zod.z.number(),
74864
- /** Every override the canary replaced, exactly, so rollback restores it. */
74865
- previous: zod.z.array(zod.z.object({
74866
- deviceId: zod.z.number().int(),
74867
- nodeId: zod.z.string(),
74868
- deviceKey: zod.z.string(),
74869
- patch: zod.z.object({
74870
- modelId: zod.z.string().optional(),
74871
- settings: zod.z.record(zod.z.string(), zod.z.unknown()).readonly().optional()
74872
- }).nullable()
74873
- })).readonly()
75632
+ previous: zod.z.array(TrainingAcceleratorRefSchema.extend({ modelId: zod.z.string().nullable() })).readonly()
74874
75633
  });
75634
+ /** An accelerator a canary may pin: it runs the root detector, on this model now. */
75635
+ var TrainingCanaryTargetSchema = TrainingAcceleratorRefSchema.extend({ pinnedModelId: zod.z.string().nullable() });
74875
75636
  var TrainingJobSchema = zod.z.object({
74876
75637
  id: zod.z.string(),
74877
75638
  idempotencyKey: zod.z.string(),
@@ -75011,19 +75772,21 @@ var trainingCapability = {
75011
75772
  }),
75012
75773
  /** The verified `result.json` of a job in `ready` or later; null before. */
75013
75774
  getJobResult: require_sleep.method(IdInput, TrainingResultSchema.nullable(), { auth: "admin" }),
75775
+ /** The accelerators a canary may pin: every enabled one that runs the root detector. */
75776
+ listCanaryTargets: require_sleep.method(zod.z.void(), zod.z.array(TrainingCanaryTargetSchema).readonly(), { auth: "admin" }),
75014
75777
  /** Hand the verified bundle to Model Studio; optionally canary right after. */
75015
- registerResult: require_sleep.method(IdInput.extend({ canaryDeviceIds: zod.z.array(zod.z.number().int()).readonly() }), TrainingJobSchema, {
75778
+ registerResult: require_sleep.method(IdInput.extend({ canaryAccelerators: zod.z.array(TrainingAcceleratorRefSchema).readonly() }), TrainingJobSchema, {
75016
75779
  kind: "mutation",
75017
75780
  auth: "admin",
75018
75781
  timeoutMs: 30 * 6e4
75019
75782
  }),
75020
- /** Pin the registered model on these cameras, remembering what it replaced. */
75021
- canaryResult: require_sleep.method(IdInput.extend({ deviceIds: zod.z.array(zod.z.number().int()).min(1).readonly() }), TrainingJobSchema, {
75783
+ /** Pin the registered model on these accelerators, remembering what it replaced. */
75784
+ canaryResult: require_sleep.method(IdInput.extend({ accelerators: zod.z.array(TrainingAcceleratorRefSchema).min(1).readonly() }), TrainingJobSchema, {
75022
75785
  kind: "mutation",
75023
75786
  auth: "admin",
75024
75787
  timeoutMs: 12e4
75025
75788
  }),
75026
- /** Put every canary camera back exactly as it was. */
75789
+ /** Put every canary accelerator back exactly as it was. */
75027
75790
  rollbackCanary: require_sleep.method(IdInput, TrainingJobSchema, {
75028
75791
  kind: "mutation",
75029
75792
  auth: "admin",
@@ -84623,20 +85386,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
84623
85386
  access: "create",
84624
85387
  role: "admin"
84625
85388
  },
84626
- "pipelineExecutor.clearDeviceOverrides": {
84627
- capName: "pipeline-executor",
84628
- capScope: "system",
84629
- addonId: null,
84630
- access: "delete",
84631
- role: "admin"
84632
- },
84633
- "pipelineExecutor.clearModelBoundDeviceOverrides": {
84634
- capName: "pipeline-executor",
84635
- capScope: "system",
84636
- addonId: null,
84637
- access: "delete",
84638
- role: "admin"
84639
- },
84640
85389
  "pipelineExecutor.deleteModel": {
84641
85390
  capName: "pipeline-executor",
84642
85391
  capScope: "system",
@@ -84679,13 +85428,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
84679
85428
  access: "view",
84680
85429
  role: "admin"
84681
85430
  },
84682
- "pipelineExecutor.getDefaultSteps": {
84683
- capName: "pipeline-executor",
84684
- capScope: "system",
84685
- addonId: null,
84686
- access: "view",
84687
- role: "admin"
84688
- },
84689
85431
  "pipelineExecutor.getDetectionConfigSchema": {
84690
85432
  capName: "pipeline-executor",
84691
85433
  capScope: "system",
@@ -84707,20 +85449,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
84707
85449
  access: "view",
84708
85450
  role: "admin"
84709
85451
  },
84710
- "pipelineExecutor.getGlobalPipelineConfig": {
84711
- capName: "pipeline-executor",
84712
- capScope: "system",
84713
- addonId: null,
84714
- access: "view",
84715
- role: "admin"
84716
- },
84717
- "pipelineExecutor.getGlobalSteps": {
84718
- capName: "pipeline-executor",
84719
- capScope: "system",
84720
- addonId: null,
84721
- access: "view",
84722
- role: "admin"
84723
- },
84724
85452
  "pipelineExecutor.getInferenceDeviceHealth": {
84725
85453
  capName: "pipeline-executor",
84726
85454
  capScope: "system",
@@ -84980,13 +85708,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
84980
85708
  access: "view",
84981
85709
  role: "admin"
84982
85710
  },
84983
- "pipelineOrchestrator.getCameraStepOverrides": {
84984
- capName: "pipeline-orchestrator",
84985
- capScope: "system",
84986
- addonId: null,
84987
- access: "view",
84988
- role: "admin"
84989
- },
84990
85711
  "pipelineOrchestrator.getCameraSwitches": {
84991
85712
  capName: "pipeline-orchestrator",
84992
85713
  capScope: "system",
@@ -85225,13 +85946,6 @@ var METHOD_ACCESS_MAP = Object.freeze({
85225
85946
  access: "create",
85226
85947
  role: "admin"
85227
85948
  },
85228
- "pipelineOrchestrator.setCameraPipelineForAgent": {
85229
- capName: "pipeline-orchestrator",
85230
- capScope: "system",
85231
- addonId: null,
85232
- access: "create",
85233
- role: "admin"
85234
- },
85235
85949
  "pipelineOrchestrator.setCameraSensors": {
85236
85950
  capName: "pipeline-orchestrator",
85237
85951
  capScope: "system",
@@ -85239,42 +85953,42 @@ var METHOD_ACCESS_MAP = Object.freeze({
85239
85953
  access: "create",
85240
85954
  role: "admin"
85241
85955
  },
85242
- "pipelineOrchestrator.setCameraStepOverride": {
85956
+ "pipelineOrchestrator.setCameraStepSettings": {
85243
85957
  capName: "pipeline-orchestrator",
85244
85958
  capScope: "system",
85245
85959
  addonId: null,
85246
85960
  access: "create",
85247
85961
  role: "admin"
85248
85962
  },
85249
- "pipelineOrchestrator.setCameraStepSettings": {
85963
+ "pipelineOrchestrator.setCameraStepToggle": {
85250
85964
  capName: "pipeline-orchestrator",
85251
85965
  capScope: "system",
85252
85966
  addonId: null,
85253
85967
  access: "create",
85254
85968
  role: "admin"
85255
85969
  },
85256
- "pipelineOrchestrator.setCameraStepToggle": {
85970
+ "pipelineOrchestrator.setCameraSwitch": {
85257
85971
  capName: "pipeline-orchestrator",
85258
85972
  capScope: "system",
85259
85973
  addonId: null,
85260
85974
  access: "create",
85261
85975
  role: "admin"
85262
85976
  },
85263
- "pipelineOrchestrator.setCameraSwitch": {
85977
+ "pipelineOrchestrator.setCapabilityBinding": {
85264
85978
  capName: "pipeline-orchestrator",
85265
85979
  capScope: "system",
85266
85980
  addonId: null,
85267
85981
  access: "create",
85268
85982
  role: "admin"
85269
85983
  },
85270
- "pipelineOrchestrator.setCapabilityBinding": {
85984
+ "pipelineOrchestrator.setLoadBalancingShares": {
85271
85985
  capName: "pipeline-orchestrator",
85272
85986
  capScope: "system",
85273
85987
  addonId: null,
85274
85988
  access: "create",
85275
85989
  role: "admin"
85276
85990
  },
85277
- "pipelineOrchestrator.setLoadBalancingShares": {
85991
+ "pipelineOrchestrator.setNodeStepSettings": {
85278
85992
  capName: "pipeline-orchestrator",
85279
85993
  capScope: "system",
85280
85994
  addonId: null,
@@ -87633,6 +88347,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
87633
88347
  access: "view",
87634
88348
  role: "admin"
87635
88349
  },
88350
+ "training.listCanaryTargets": {
88351
+ capName: "training",
88352
+ capScope: "system",
88353
+ addonId: null,
88354
+ access: "view",
88355
+ role: "admin"
88356
+ },
87636
88357
  "training.listJobs": {
87637
88358
  capName: "training",
87638
88359
  capScope: "system",
@@ -97172,11 +97893,6 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
97172
97893
  form: "single",
97173
97894
  optional: false
97174
97895
  }],
97175
- "pipelineExecutor.clearModelBoundDeviceOverrides": [{
97176
- name: "deviceIds",
97177
- form: "array",
97178
- optional: true
97179
- }],
97180
97896
  "pipelineExecutor.runPipeline": [{
97181
97897
  name: "deviceId",
97182
97898
  form: "single",
@@ -97232,11 +97948,6 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
97232
97948
  form: "array",
97233
97949
  optional: true
97234
97950
  }],
97235
- "pipelineOrchestrator.getCameraStepOverrides": [{
97236
- name: "deviceId",
97237
- form: "single",
97238
- optional: false
97239
- }],
97240
97951
  "pipelineOrchestrator.getCameraSwitches": [{
97241
97952
  name: "deviceId",
97242
97953
  form: "single",
@@ -97277,21 +97988,11 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
97277
97988
  form: "single",
97278
97989
  optional: false
97279
97990
  }],
97280
- "pipelineOrchestrator.setCameraPipelineForAgent": [{
97281
- name: "deviceId",
97282
- form: "single",
97283
- optional: false
97284
- }],
97285
97991
  "pipelineOrchestrator.setCameraSensors": [{
97286
97992
  name: "deviceId",
97287
97993
  form: "single",
97288
97994
  optional: false
97289
97995
  }],
97290
- "pipelineOrchestrator.setCameraStepOverride": [{
97291
- name: "deviceId",
97292
- form: "single",
97293
- optional: false
97294
- }],
97295
97996
  "pipelineOrchestrator.setCameraStepSettings": [{
97296
97997
  name: "deviceId",
97297
97998
  form: "single",
@@ -97969,11 +98670,6 @@ var METHOD_DEVICE_SELECTORS = Object.freeze({
97969
98670
  form: "single",
97970
98671
  optional: false
97971
98672
  }],
97972
- "training.canaryResult": [{
97973
- name: "deviceIds",
97974
- form: "array",
97975
- optional: false
97976
- }],
97977
98673
  "update.getStatus": [{
97978
98674
  name: "deviceId",
97979
98675
  form: "single",
@@ -98307,7 +99003,6 @@ var SYSTEM_SCOPE_DEVICE_METHODS = [
98307
99003
  "osdManager.renderDevice",
98308
99004
  "osdManager.setSlotBinding",
98309
99005
  "osdManager.setSlotCameraEnabled",
98310
- "pipelineExecutor.clearModelBoundDeviceOverrides",
98311
99006
  "pipelineExecutor.runPipeline",
98312
99007
  "pipelineExecutor.runPipelineBatch",
98313
99008
  "pipelineOrchestrator.applyDeviceSettingsPatch",
@@ -98319,7 +99014,6 @@ var SYSTEM_SCOPE_DEVICE_METHODS = [
98319
99014
  "pipelineOrchestrator.getCameraSettings",
98320
99015
  "pipelineOrchestrator.getCameraStatus",
98321
99016
  "pipelineOrchestrator.getCameraStatuses",
98322
- "pipelineOrchestrator.getCameraStepOverrides",
98323
99017
  "pipelineOrchestrator.getCameraSwitches",
98324
99018
  "pipelineOrchestrator.getDeviceLiveContribution",
98325
99019
  "pipelineOrchestrator.getDeviceSettingsContribution",
@@ -98328,9 +99022,7 @@ var SYSTEM_SCOPE_DEVICE_METHODS = [
98328
99022
  "pipelineOrchestrator.requestOccupancyLook",
98329
99023
  "pipelineOrchestrator.resolvePipeline",
98330
99024
  "pipelineOrchestrator.setCameraDetectionSource",
98331
- "pipelineOrchestrator.setCameraPipelineForAgent",
98332
99025
  "pipelineOrchestrator.setCameraSensors",
98333
- "pipelineOrchestrator.setCameraStepOverride",
98334
99026
  "pipelineOrchestrator.setCameraStepSettings",
98335
99027
  "pipelineOrchestrator.setCameraStepToggle",
98336
99028
  "pipelineOrchestrator.setCameraSwitch",
@@ -98395,8 +99087,7 @@ var SYSTEM_SCOPE_DEVICE_METHODS = [
98395
99087
  "streamBroker.restartProfile",
98396
99088
  "streamBroker.retractCameraStream",
98397
99089
  "streamBroker.setDeviceAudioMute",
98398
- "streamBroker.unassignProfile",
98399
- "training.canaryResult"
99090
+ "streamBroker.unassignProfile"
98400
99091
  ];
98401
99092
  //#endregion
98402
99093
  //#region src/generated/provider-kind-map.ts
@@ -99863,17 +100554,12 @@ function createSystemProxy(api) {
99863
100554
  pipelineExecutor: {
99864
100555
  getAvailableEngines: (input) => dispatch("pipelineExecutor", "getAvailableEngines", "query", input),
99865
100556
  getSelectedEngine: (input) => dispatch("pipelineExecutor", "getSelectedEngine", "query", input),
99866
- getDefaultSteps: (input) => dispatch("pipelineExecutor", "getDefaultSteps", "query", input),
99867
100557
  getEngineProvisioning: (input) => dispatch("pipelineExecutor", "getEngineProvisioning", "query", input),
99868
100558
  getVideoPipelineSteps: (input) => dispatch("pipelineExecutor", "getVideoPipelineSteps", "query", input),
99869
100559
  setVideoPipelineSteps: (input) => dispatch("pipelineExecutor", "setVideoPipelineSteps", "mutation", input),
99870
- clearDeviceOverrides: (input) => dispatch("pipelineExecutor", "clearDeviceOverrides", "mutation", input),
99871
- clearModelBoundDeviceOverrides: (input) => dispatch("pipelineExecutor", "clearModelBoundDeviceOverrides", "mutation", input),
99872
100560
  getInferenceDeviceHealth: (input) => dispatch("pipelineExecutor", "getInferenceDeviceHealth", "query", input),
99873
100561
  rearmInferenceDevice: (input) => dispatch("pipelineExecutor", "rearmInferenceDevice", "mutation", input),
99874
100562
  getSchema: (input) => dispatch("pipelineExecutor", "getSchema", "query", input),
99875
- getGlobalSteps: (input) => dispatch("pipelineExecutor", "getGlobalSteps", "query", input),
99876
- getGlobalPipelineConfig: (input) => dispatch("pipelineExecutor", "getGlobalPipelineConfig", "query", input),
99877
100563
  getOrchestratorConfigSchema: (input) => dispatch("pipelineExecutor", "getOrchestratorConfigSchema", "query", input),
99878
100564
  validatePipeline: (input) => dispatch("pipelineExecutor", "validatePipeline", "query", input),
99879
100565
  getCapabilities: (input) => dispatch("pipelineExecutor", "getCapabilities", "query", input),
@@ -99917,6 +100603,7 @@ function createSystemProxy(api) {
99917
100603
  setAgentDecodeLimits: (input) => dispatch("pipelineOrchestrator", "setAgentDecodeLimits", "mutation", input),
99918
100604
  setAgentReachableHost: (input) => dispatch("pipelineOrchestrator", "setAgentReachableHost", "mutation", input),
99919
100605
  setAgentInferenceDevices: (input) => dispatch("pipelineOrchestrator", "setAgentInferenceDevices", "mutation", input),
100606
+ setNodeStepSettings: (input) => dispatch("pipelineOrchestrator", "setNodeStepSettings", "mutation", input),
99920
100607
  getNodeInferenceDevices: (input) => dispatch("pipelineOrchestrator", "getNodeInferenceDevices", "query", input),
99921
100608
  forgetInferenceDevice: (input) => dispatch("pipelineOrchestrator", "forgetInferenceDevice", "mutation", input),
99922
100609
  resetNodePipelineDefaults: (input) => dispatch("pipelineOrchestrator", "resetNodePipelineDefaults", "mutation", input),
@@ -100137,6 +100824,7 @@ function createSystemProxy(api) {
100137
100824
  getJob: (input) => dispatch("training", "getJob", "query", input),
100138
100825
  cancelJob: (input) => dispatch("training", "cancelJob", "mutation", input),
100139
100826
  getJobResult: (input) => dispatch("training", "getJobResult", "query", input),
100827
+ listCanaryTargets: (input) => dispatch("training", "listCanaryTargets", "query", input),
100140
100828
  registerResult: (input) => dispatch("training", "registerResult", "mutation", input),
100141
100829
  canaryResult: (input) => dispatch("training", "canaryResult", "mutation", input),
100142
100830
  rollbackCanary: (input) => dispatch("training", "rollbackCanary", "mutation", input),
@@ -105734,911 +106422,350 @@ function providerMotionEvent(source, data) {
105734
106422
  function hasBuild(model, format) {
105735
106423
  return Boolean(model.formats[format]);
105736
106424
  }
105737
- /**
105738
- * Can a pool of `format` run a step whose catalog is `models`, given the model
105739
- * the step names (`null`/absent = unpinned, the format default)?
105740
- */
105741
- function stepModelRunsOnFormat(models, modelId, format) {
105742
- if (modelId !== void 0 && modelId !== null) {
105743
- const named = models.find((m) => m.id === modelId);
105744
- if (named !== void 0 && hasBuild(named, format)) return true;
105745
- }
105746
- return models.some((m) => m.legacy !== true && hasBuild(m, format));
105747
- }
105748
- //#endregion
105749
- //#region src/pipeline/removed-model-ids.ts
105750
- /**
105751
- * Model ids DELETED from a step's catalog (D703, D710, D719), per step — and
105752
- * the one question asked of them: is a stored pin to this id a pin to a model
105753
- * that no longer exists? (D725)
105754
- *
105755
- * This is NOT a resolver input. `resolveModelForFormat` still returns an
105756
- * unknown id unchanged (a custom model), and a bulk pass pinned to one still
105757
- * refuses rather than substitutes (D54). The ONE reader is the orchestrator's
105758
- * stored-pin reset (`removed-model-pin-reset.ts`), which clears such a pin from
105759
- * the store so the step follows its default again — unless a registered custom
105760
- * model carries the same id.
105761
- *
105762
- * `removed-model-ids.spec.ts` (addon-pipeline) pins that every id here is
105763
- * really absent from its step's catalog. A model deleted from a catalog is
105764
- * added here in the same change, or its stored pins stay behind as custom ids.
105765
- */
105766
- /** `stepId → ids deleted from that step's catalog`. */
105767
- var REMOVED_MODEL_IDS = {
105768
- "object-detection": ["efficientdet-lite0-edgetpu", "ssdlite-mobiledet-coco-edgetpu"],
105769
- "face-detection": [
105770
- "scrypted-yolov9t-face",
105771
- "scrfd-2.5g",
105772
- "ssd-mobilenet-v2-face-edgetpu"
105773
- ],
105774
- "face-embedding": ["arcface-r100", "inception-resnet-v1"],
105775
- "plate-detection": ["yolov8n-plate"],
105776
- "plate-ocr": ["vgg-english-g2"],
105777
- "clip-embedding": [
105778
- "mobileclip-s0",
105779
- "mobileclip-s1",
105780
- "mobileclip-s2"
105781
- ],
105782
- "package-detection": ["yolov8n-package"],
105783
- "bird-classifier": ["bird-classifier-aiy-edgetpu", "bird-nabirds-404"],
105784
- "animal-classifier": ["animals-10"],
105785
- "vehicle-classifier": ["vehicle-type-efficientnet"]
105786
- };
105787
- /** True when `modelId` was deleted from `stepId`'s catalog. */
105788
- function isRemovedModelId(stepId, modelId) {
105789
- return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
105790
- }
105791
- //#endregion
105792
- //#region src/pipeline/model-bound-settings.ts
105793
- /** Suffix of the sibling key that names the model a stored value was made for. */
105794
- var MODEL_BINDING_SUFFIX = "ModelId";
105795
- /** `minConfidencePerson` → `minConfidencePersonModelId`. */
105796
- function modelBindingKey(key) {
105797
- return `${key}${MODEL_BINDING_SUFFIX}`;
105798
- }
105799
- /** The model a stored value was made for, or `undefined` when unbound. */
105800
- function storedBinding(settings, key) {
105801
- const binding = settings?.[modelBindingKey(key)];
105802
- return typeof binding === "string" && binding.length > 0 ? binding : void 0;
105803
- }
105804
- /**
105805
- * The effective value of `key` for the model that runs (`runningModelId`,
105806
- * `undefined` when unknown — it then matches no binding).
105807
- */
105808
- function resolveModelBoundSetting(settings, key, runningModelId, modelDefault, globalDefault) {
105809
- const stored = settings?.[key];
105810
- const binding = storedBinding(settings, key);
105811
- if (stored !== void 0 && binding === void 0) return {
105812
- value: stored,
105813
- source: "override"
105814
- };
105815
- if (stored !== void 0 && binding !== void 0 && binding === runningModelId) return {
105816
- value: stored,
105817
- source: "override",
105818
- boundModelId: binding
105819
- };
105820
- const inapplicable = stored !== void 0 && binding !== void 0 ? { inapplicableOverride: {
105821
- value: stored,
105822
- modelId: binding
105823
- } } : {};
105824
- if (modelDefault !== void 0) return {
105825
- value: modelDefault,
105826
- source: "model-default",
105827
- ...inapplicable
105828
- };
105829
- return {
105830
- value: globalDefault,
105831
- source: "global-default",
105832
- ...inapplicable
105833
- };
105834
- }
105835
- /**
105836
- * The OPERATOR layer of a step's settings as the running model sees it: every
105837
- * model-bound value made for another model is removed (so the default
105838
- * underneath — the running model's — wins the merge), and every binding key is
105839
- * stripped. Unbound values are kept. Returns a fresh object; `undefined` stays
105840
- * `undefined`.
105841
- */
105842
- function applyModelBoundSettings(settings, boundKeys, runningModelId) {
105843
- if (settings === void 0) return void 0;
105844
- const out = { ...settings };
105845
- for (const key of boundKeys) {
105846
- const binding = storedBinding(settings, key);
105847
- if (binding !== void 0 && binding !== runningModelId) delete out[key];
105848
- delete out[modelBindingKey(key)];
105849
- }
105850
- return out;
105851
- }
105852
- /** Store `value` for `key`, made for `modelId`. Returns a fresh object. */
105853
- function writeModelBoundSetting(settings, key, value, modelId) {
105854
- return {
105855
- ...settings,
105856
- [key]: value,
105857
- [modelBindingKey(key)]: modelId
105858
- };
105859
- }
105860
- /** Remove an override and its binding — "reset to the model default". */
105861
- function clearModelBoundSetting(settings, key) {
105862
- const out = { ...settings };
105863
- delete out[key];
105864
- delete out[modelBindingKey(key)];
105865
- return out;
105866
- }
105867
- /** True when any of `boundKeys` has an override IN FORCE for `modelId`. */
105868
- function hasModelBoundOverrides(settings, boundKeys, modelId) {
105869
- return boundKeys.some((key) => resolveModelBoundSetting(settings, key, modelId, void 0, void 0).source === "override");
105870
- }
105871
- /**
105872
- * "Reset thresholds to model defaults": remove every override of `boundKeys`
105873
- * that is in force for `modelId` (bound to it, or unbound), with its binding —
105874
- * one result, so the editor writes it in one save. An override stored for
105875
- * ANOTHER model is kept: it is not in force here, and it applies again when
105876
- * that model runs.
105877
- */
105878
- function clearModelBoundOverrides(settings, boundKeys, modelId) {
105879
- return boundKeys.reduce((acc, key) => resolveModelBoundSetting(acc, key, modelId, void 0, void 0).source === "override" ? clearModelBoundSetting(acc, key) : acc, { ...settings });
105880
- }
105881
- /**
105882
- * The keys of every `bindsToModel` field of a step's config schema, groups
105883
- * flattened (D738). One definition for the runner (which applies the binding)
105884
- * and the orchestrator's Auto tune (which clears those overrides, D745).
105885
- */
105886
- function collectModelBoundKeys(fields) {
105887
- return fields.flatMap((field) => {
105888
- if (field.type === "group") return collectModelBoundKeys(field.fields);
105889
- return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
105890
- });
105891
- }
105892
- //#endregion
105893
- //#region src/pipeline/step-settings-layers.ts
105894
- /**
105895
- * The layers of a detection step's settings, and the field-level edits on them
105896
- * (D754).
105897
- *
105898
- * A step's settings reach a session through up to three stored layers, merged
105899
- * key by key — a key in a higher layer wins:
105900
- *
105901
- * node the node's folded base (`inferenceDevices[node][*].steps`)
105902
- * camera `cameraSettings[cam].stepSettings[step]` — every accelerator
105903
- * accelerator `cameraSettings[cam].stepOverridesByDevice[node][key][step].settings`
105904
- *
105905
- * Below them sits the default: the running model's (D738) or the field's own.
105906
- *
105907
- * Every layer is SPARSE and FIELD-LEVEL. A key present in a layer is an
105908
- * override; an absent key inherits. Two consequences that every editor must
105909
- * respect, and why this module exists:
105910
- *
105911
- * - A model-bound field (D738) occupies TWO keys, its value and
105912
- * `<key>ModelId`. They are written, compared and removed as one unit — a
105913
- * value without its binding over a lower layer's binding would be judged
105914
- * against the wrong model.
105915
- * - An editor writes a DIFF against the merged layers below, never a copy of
105916
- * them. A copy pins every field at the value it had that day, so later
105917
- * edits below never reach it (the full-copy "Override" this replaced).
105918
- */
105919
- /** Top-down order of the stored layers. */
105920
- var LAYER_ORDER = [
105921
- "accelerator",
105922
- "camera",
105923
- "node"
105924
- ];
105925
- /**
105926
- * The keys one field occupies in a bag: its own, plus its binding key when it
105927
- * binds to a model. `boundKeys` = `collectModelBoundKeys(schema)`.
105928
- */
105929
- function fieldUnitKeys(key, boundKeys) {
105930
- return boundKeys.includes(key) ? [key, modelBindingKey(key)] : [key];
105931
- }
105932
- /** The field a stored key belongs to (`minConfidencePersonModelId` → `minConfidencePerson`). */
105933
- function unitOf(storedKey, boundKeys) {
105934
- return boundKeys.find((k) => modelBindingKey(k) === storedKey) ?? storedKey;
105935
- }
105936
- /** Structural equality for stored values (numbers, strings, booleans, arrays, plain objects). */
105937
- function settingValuesEqual(a, b) {
105938
- if (Object.is(a, b)) return true;
105939
- if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => settingValuesEqual(v, b[i]));
105940
- if (isPlainObject(a) && isPlainObject(b)) {
105941
- const ka = Object.keys(a);
105942
- const kb = Object.keys(b);
105943
- return ka.length === kb.length && ka.every((k) => settingValuesEqual(a[k], b[k]));
105944
- }
105945
- return false;
105946
- }
105947
- function isPlainObject(v) {
105948
- return typeof v === "object" && v !== null && !Array.isArray(v);
105949
- }
105950
- /** Merge bags bottom-up (later wins, per key). Fresh object. */
105951
- function mergeSettingsBags(...bags) {
105952
- return bags.reduce((acc, bag) => ({
105953
- ...acc,
105954
- ...bag
105955
- }), {});
105956
- }
105957
- /** The merged value of the layers BELOW `layer` (what removing a key from `layer` falls back to). */
105958
- function settingsBelow(layers, layer) {
105959
- return mergeSettingsBags(...LAYER_ORDER.slice(LAYER_ORDER.indexOf(layer) + 1).toReversed().map((id) => layers[id]));
105960
- }
105961
- /** The effective bag: every stored layer merged. */
105962
- function effectiveStepSettings(layers) {
105963
- return mergeSettingsBags(layers.node, layers.camera, layers.accelerator);
105964
- }
105965
- /** The highest stored layer that holds any key of `key`'s unit, or `default`. */
105966
- function stepSettingSource(layers, key, boundKeys) {
105967
- const unit = fieldUnitKeys(key, boundKeys);
105968
- for (const id of LAYER_ORDER) {
105969
- const bag = layers[id];
105970
- if (bag !== void 0 && unit.some((k) => bag[k] !== void 0)) return id;
105971
- }
105972
- return "default";
105973
- }
105974
- /**
105975
- * `layer` with `key`'s unit set from `values` (a bag carrying the unit's new
105976
- * value — e.g. the editor's whole next settings bag). A unit key that `values`
105977
- * does not carry is removed from the layer. Fresh object.
105978
- */
105979
- function withFieldFrom(layer, key, values, boundKeys) {
105980
- const out = { ...layer };
105981
- for (const k of fieldUnitKeys(key, boundKeys)) if (values[k] === void 0) delete out[k];
105982
- else out[k] = values[k];
105983
- return out;
105984
- }
105985
- /** `layer` without `key`'s unit — "reset to the next level". Fresh object. */
105986
- function withoutField(layer, key, boundKeys) {
105987
- const out = { ...layer };
105988
- for (const k of fieldUnitKeys(key, boundKeys)) delete out[k];
105989
- return out;
105990
- }
105991
- /** Every field unit stored in `layer`, by its field key. */
105992
- function unitsIn(layer, boundKeys) {
105993
- return [...new Set(Object.keys(layer).map((k) => unitOf(k, boundKeys)))];
105994
- }
105995
- /** True when every key of `key`'s unit has the same value in `layer` as in `below`. */
105996
- function unitEqualsBelow(layer, below, key, boundKeys) {
105997
- return fieldUnitKeys(key, boundKeys).every((k) => settingValuesEqual(layer[k], below[k]));
105998
- }
105999
- /**
106000
- * `layer` reduced to the fields that DIFFER from `below` — what an editor
106001
- * stores. A unit is kept or dropped whole. `undefined` when nothing is left.
106002
- */
106003
- function diffStepSettings(layer, below, boundKeys) {
106004
- if (layer === void 0) return void 0;
106005
- const out = {};
106006
- for (const unit of unitsIn(layer, boundKeys)) {
106007
- if (unitEqualsBelow(layer, below, unit, boundKeys)) continue;
106008
- for (const k of fieldUnitKeys(unit, boundKeys)) if (layer[k] !== void 0) out[k] = layer[k];
106009
- }
106010
- return Object.keys(out).length > 0 ? out : void 0;
106011
- }
106012
- /**
106013
- * The fields `layer` stores at exactly the value of the layers below — a
106014
- * legacy full copy's leftovers. They change nothing today and stop every
106015
- * later edit below from reaching this layer. Sorted.
106016
- */
106017
- function fieldsPinnedAtValueBelow(layer, below, boundKeys) {
106018
- if (layer === void 0) return [];
106019
- return unitsIn(layer, boundKeys).filter((unit) => unitEqualsBelow(layer, below, unit, boundKeys)).toSorted();
106020
- }
106021
- //#endregion
106022
- //#region src/pipeline/auto-tune/hardware-class.ts
106023
- var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
106024
- var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
106025
- var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
106026
- /** Phoenix / Hawk Point mobile (7040/8040) and Ryzen 8000G desktop APUs. */
106027
- var AMD_PHOENIX = /Ryzen \d+ (7[0-9]40|8[0-9]40|8[0-9]45)[HSU]|Ryzen \d+ 8[0-9]00G/i;
106028
- function parseAppleChip(cpuModel) {
106029
- const match = APPLE_CHIP.exec(cpuModel.trim());
106030
- if (match === null) return null;
106031
- const generation = Number(match[1]);
106032
- const variantRaw = (match[2] ?? "base").toLowerCase();
106033
- return {
106034
- generation,
106035
- variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
106036
- };
106037
- }
106038
- /**
106039
- * Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
106040
- * Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
106041
- * 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
106042
- * matters less than the column's own error.
106043
- */
106044
- function classifyInferenceHardware(hw) {
106045
- const cpuModel = hw.cpuModel;
106046
- const apple = parseAppleChip(cpuModel);
106047
- if (apple !== null) return {
106048
- hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
106049
- cpuModel,
106050
- apple
106051
- };
106052
- if (INTEL_CORE_ULTRA.test(cpuModel)) return {
106053
- hardwareClass: "intel-core-ultra",
106054
- cpuModel
106055
- };
106056
- if (INTEL_N_SERIES.test(cpuModel)) return {
106057
- hardwareClass: "intel-n-series",
106058
- cpuModel
106059
- };
106060
- if (AMD_PHOENIX.test(cpuModel)) return {
106061
- hardwareClass: "amd-phoenix",
106062
- cpuModel
106063
- };
106064
- return {
106065
- hardwareClass: "unknown",
106066
- cpuModel
106067
- };
106068
- }
106069
- /**
106070
- * The accelerator kind of one of a node's device keys (`openvino:npu`,
106071
- * `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
106072
- * for a key this table has no column for.
106073
- */
106074
- function acceleratorKindOf(deviceKey, hw) {
106075
- const [backend = "", device = ""] = deviceKey.split(":");
106076
- if (backend === "edgetpu") return "coral";
106077
- if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
106078
- if (backend === "openvino" && device === "npu") return "intel-npu";
106079
- if (backend === "openvino" && device === "gpu") return "intel-igpu";
106080
- if (backend === "onnx" && device === "webgpu") return "amd-igpu";
106081
- if (backend === "vitisai" && device === "npu") return "amd-npu";
106082
- if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
106083
- return null;
106084
- }
106085
- //#endregion
106086
- //#region src/pipeline/auto-tune/detector-performance.ts
106087
- var DETECTOR_PERF = {
106088
- "intel-core-ultra": {
106089
- "intel-npu": {
106090
- "yolov9t-320": {
106091
- p50Ms: 2.9,
106092
- fps: 383,
106093
- measured: true
106094
- },
106095
- "yolov9t-320-int8": {
106096
- p50Ms: 3.8,
106097
- fps: 333,
106098
- measured: true
106099
- },
106100
- "yolov9t-640": {
106101
- p50Ms: 7.4,
106102
- fps: 139,
106103
- measured: true
106104
- },
106105
- "yolov9t-640-int8": {
106106
- p50Ms: 7.5,
106107
- fps: 144,
106108
- measured: true
106109
- },
106110
- "yolov9s-320": {
106111
- p50Ms: 4.4,
106112
- fps: 241,
106113
- measured: true
106114
- },
106115
- "yolov9s-320-int8": {
106116
- p50Ms: 4.1,
106117
- fps: 264,
106118
- measured: true
106119
- },
106120
- "yolov9s-640": {
106121
- p50Ms: 12.7,
106122
- fps: 82,
106123
- measured: true
106124
- },
106125
- "yolov9s-640-int8": {
106126
- p50Ms: 10.7,
106127
- fps: 99,
106128
- measured: true
106129
- },
106130
- "yolov9m-320": {
106131
- p50Ms: 8.4,
106132
- fps: 123,
106133
- measured: true
106134
- },
106135
- "yolov9m-320-int8": {
106136
- p50Ms: 6.5,
106137
- fps: 165,
106138
- measured: true
106139
- },
106140
- "yolov9m-640": {
106141
- p50Ms: 29.5,
106142
- fps: 32,
106143
- measured: true
106144
- },
106145
- "yolov9m-640-int8": {
106146
- p50Ms: 19.5,
106147
- fps: 52,
106148
- measured: true
106149
- }
106150
- },
106151
- "intel-igpu": {
106152
- "yolov9t-320": {
106153
- p50Ms: 2.6,
106154
- fps: 405,
106155
- measured: true
106156
- },
106157
- "yolov9t-320-int8": {
106158
- p50Ms: 2.8,
106159
- fps: 324,
106160
- measured: true
106161
- },
106162
- "yolov9t-640": {
106163
- p50Ms: 6.5,
106164
- fps: 157,
106165
- measured: true
106166
- },
106167
- "yolov9t-640-int8": {
106168
- p50Ms: 5.6,
106169
- fps: 185,
106170
- measured: true
106171
- },
106172
- "yolov9s-320": {
106173
- p50Ms: 4.6,
106174
- fps: 200,
106175
- measured: true
106176
- },
106177
- "yolov9s-320-int8": {
106178
- p50Ms: 4.1,
106179
- fps: 239,
106180
- measured: true
106181
- },
106182
- "yolov9s-640": {
106183
- p50Ms: 15.2,
106184
- fps: 69,
106185
- measured: true
106186
- },
106187
- "yolov9s-640-int8": {
106188
- p50Ms: 10.5,
106189
- fps: 99,
106190
- measured: true
106191
- },
106192
- "yolov9m-320": {
106193
- p50Ms: 11.4,
106194
- fps: 88,
106195
- measured: true
106196
- },
106197
- "yolov9m-320-int8": {
106198
- p50Ms: 7,
106199
- fps: 127,
106200
- measured: true
106201
- },
106202
- "yolov9m-640": {
106203
- p50Ms: 40,
106204
- fps: 25,
106205
- measured: true
106206
- },
106207
- "yolov9m-640-int8": {
106208
- p50Ms: 23.5,
106209
- fps: 44,
106210
- measured: true
106211
- }
106212
- },
106213
- "x86-cpu": {
106214
- "yolov9t-320": {
106215
- p50Ms: 6.9,
106216
- fps: 145,
106217
- measured: true
106218
- },
106219
- "yolov9t-320-int8": {
106220
- p50Ms: 2.7,
106221
- fps: 405,
106222
- measured: true
106223
- },
106224
- "yolov9t-640": {
106225
- p50Ms: 23,
106226
- fps: 44,
106227
- measured: true
106228
- },
106229
- "yolov9t-640-int8": {
106230
- p50Ms: 8.8,
106231
- fps: 124,
106232
- measured: true
106233
- },
106234
- "yolov9s-320": {
106235
- p50Ms: 15.8,
106236
- fps: 63,
106237
- measured: true
106238
- },
106239
- "yolov9s-320-int8": {
106240
- p50Ms: 6.5,
106241
- fps: 180,
106242
- measured: true
106243
- },
106244
- "yolov9s-640": {
106245
- p50Ms: 58.2,
106246
- fps: 17,
106247
- measured: true
106248
- },
106249
- "yolov9s-640-int8": {
106250
- p50Ms: 20.1,
106251
- fps: 52,
106252
- measured: true
106253
- },
106254
- "yolov9m-320": {
106255
- p50Ms: 40,
106256
- fps: 25,
106257
- measured: true
106258
- },
106259
- "yolov9m-320-int8": {
106260
- p50Ms: 14.2,
106261
- fps: 74,
106262
- measured: true
106263
- },
106264
- "yolov9m-640": {
106265
- p50Ms: 155.8,
106266
- fps: 6,
106267
- measured: true
106268
- },
106269
- "yolov9m-640-int8": {
106270
- p50Ms: 52.4,
106271
- fps: 19,
106272
- measured: true
106273
- }
106274
- },
106275
- coral: { "yolov9s-320-edgetpu": {
106276
- p50Ms: 32,
106277
- fps: 31,
106278
- measured: false
106279
- } }
106280
- },
106281
- "intel-n-series": {
106282
- "intel-igpu": {
106283
- "yolov9t-320": {
106284
- p50Ms: 15,
106285
- fps: 48,
106286
- measured: true
106287
- },
106288
- "yolov9t-320-int8": {
106289
- p50Ms: 14.1,
106290
- fps: 67,
106291
- measured: true
106292
- },
106293
- "yolov9t-640": {
106294
- p50Ms: 38.5,
106295
- fps: 26,
106296
- measured: true
106297
- },
106298
- "yolov9t-640-int8": {
106299
- p50Ms: 33.1,
106300
- fps: 33,
106301
- measured: true
106302
- },
106303
- "yolov9s-320": {
106304
- p50Ms: 41.4,
106305
- fps: 24,
106306
- measured: true
106307
- },
106308
- "yolov9s-320-int8": {
106309
- p50Ms: 23.3,
106310
- fps: 34,
106311
- measured: true
106312
- },
106313
- "yolov9s-640": {
106314
- p50Ms: 127.4,
106315
- fps: 8,
106316
- measured: true
106317
- },
106318
- "yolov9s-640-int8": {
106319
- p50Ms: 92.9,
106320
- fps: 11,
106321
- measured: true
106322
- },
106323
- "yolov9m-320": {
106324
- p50Ms: 63.5,
106325
- fps: 16,
106326
- measured: true
106327
- },
106328
- "yolov9m-320-int8": {
106329
- p50Ms: 39.4,
106330
- fps: 26,
106331
- measured: true
106332
- },
106333
- "yolov9m-640": {
106334
- p50Ms: 215.5,
106335
- fps: 5,
106336
- measured: true
106337
- },
106338
- "yolov9m-640-int8": {
106339
- p50Ms: 139.1,
106340
- fps: 7,
106341
- measured: true
106342
- }
106343
- },
106344
- "x86-cpu": {
106345
- "yolov9t-320": {
106346
- p50Ms: 86.2,
106347
- fps: 12,
106348
- measured: true
106349
- },
106350
- "yolov9t-320-int8": {
106351
- p50Ms: 52.7,
106352
- fps: 20,
106353
- measured: true
106354
- },
106355
- "yolov9t-640": {
106356
- p50Ms: 322.1,
106357
- fps: 3,
106358
- measured: true
106359
- },
106360
- "yolov9t-640-int8": {
106361
- p50Ms: 205.6,
106362
- fps: 5,
106363
- measured: true
106364
- },
106365
- "yolov9s-320": {
106366
- p50Ms: 164.4,
106367
- fps: 6,
106368
- measured: true
106369
- },
106370
- "yolov9s-320-int8": {
106371
- p50Ms: 86.9,
106372
- fps: 12,
106373
- measured: true
106374
- },
106375
- "yolov9s-640": {
106376
- p50Ms: 341.3,
106377
- fps: 3,
106378
- measured: true
106379
- },
106380
- "yolov9s-640-int8": {
106381
- p50Ms: 185.3,
106382
- fps: 5,
106383
- measured: true
106384
- },
106385
- "yolov9m-320": {
106386
- p50Ms: 166.8,
106387
- fps: 6,
106388
- measured: true
106389
- },
106390
- "yolov9m-320-int8": {
106391
- p50Ms: 226.3,
106392
- fps: 4,
106393
- measured: true
106394
- },
106395
- "yolov9m-640": {
106396
- p50Ms: 663.5,
106397
- fps: 2,
106398
- measured: true
106399
- },
106400
- "yolov9m-640-int8": {
106401
- p50Ms: 324.4,
106402
- fps: 3,
106403
- measured: true
106404
- }
106405
- },
106406
- coral: { "yolov9s-320-edgetpu": {
106407
- p50Ms: 32,
106408
- fps: 31,
106409
- measured: false
106410
- } }
106411
- },
106412
- "amd-phoenix": {
106413
- "x86-cpu": {
106414
- "yolov9t-320": {
106415
- p50Ms: 24,
106416
- fps: 42,
106417
- measured: true
106418
- },
106419
- "yolov9t-320-int8": {
106420
- p50Ms: 12,
106421
- fps: 83,
106422
- measured: true
106423
- },
106424
- "yolov9t-640": {
106425
- p50Ms: 50,
106426
- fps: 20,
106427
- measured: true
106428
- },
106429
- "yolov9t-640-int8": {
106430
- p50Ms: 20,
106431
- fps: 50,
106432
- measured: true
106433
- },
106434
- "yolov9s-320": {
106435
- p50Ms: 30,
106436
- fps: 33,
106437
- measured: true
106438
- },
106439
- "yolov9s-320-int8": {
106440
- p50Ms: 15,
106441
- fps: 67,
106442
- measured: true
106443
- },
106444
- "yolov9s-640": {
106445
- p50Ms: 92,
106446
- fps: 11,
106447
- measured: true
106448
- },
106449
- "yolov9s-640-int8": {
106450
- p50Ms: 27,
106451
- fps: 37,
106452
- measured: true
106453
- },
106454
- "yolov9m-320": {
106455
- p50Ms: 67,
106456
- fps: 15,
106457
- measured: true
106458
- },
106459
- "yolov9m-320-int8": {
106460
- p50Ms: 21,
106461
- fps: 48,
106462
- measured: true
106463
- },
106464
- "yolov9m-640": {
106465
- p50Ms: 229,
106466
- fps: 4,
106467
- measured: true
106468
- },
106469
- "yolov9m-640-int8": {
106470
- p50Ms: 48,
106471
- fps: 21,
106472
- measured: true
106473
- }
106474
- },
106475
- "amd-igpu": {
106476
- "yolov9t-320": {
106477
- p50Ms: 40,
106478
- fps: 25,
106479
- measured: true
106480
- },
106481
- "yolov9t-640": {
106482
- p50Ms: 89,
106483
- fps: 11,
106484
- measured: true
106485
- },
106486
- "yolov9s-320": {
106487
- p50Ms: 65,
106488
- fps: 15,
106489
- measured: true
106490
- },
106491
- "yolov9s-640": {
106492
- p50Ms: 125,
106493
- fps: 8,
106494
- measured: true
106495
- },
106496
- "yolov9m-320": {
106497
- p50Ms: 85,
106498
- fps: 12,
106499
- measured: true
106500
- },
106501
- "yolov9m-640": {
106502
- p50Ms: 228,
106503
- fps: 4,
106504
- measured: true
106505
- }
106506
- },
106507
- coral: { "yolov9s-320-edgetpu": {
106508
- p50Ms: 32,
106509
- fps: 31,
106510
- measured: true
106511
- } }
106512
- },
106513
- "apple-m-pro": {
106514
- "apple-ane": {
106515
- "yolov9t-320": {
106516
- p50Ms: 1.1,
106517
- fps: 952,
106518
- measured: true
106519
- },
106520
- "yolov9t-640": {
106521
- p50Ms: 3,
106522
- fps: 333,
106523
- measured: true
106524
- },
106525
- "yolov9s-320": {
106526
- p50Ms: 1.5,
106527
- fps: 667,
106528
- measured: true
106529
- },
106530
- "yolov9s-640": {
106531
- p50Ms: 5.1,
106532
- fps: 196,
106533
- measured: true
106534
- },
106535
- "yolov9m-320": {
106536
- p50Ms: 3.2,
106537
- fps: 312,
106538
- measured: true
106539
- },
106540
- "yolov9m-640": {
106541
- p50Ms: 10.2,
106542
- fps: 98,
106543
- measured: true
106544
- }
106545
- },
106546
- coral: { "yolov9s-320-edgetpu": {
106547
- p50Ms: 32,
106548
- fps: 31,
106549
- measured: false
106550
- } }
106551
- },
106552
- "apple-m-base": {
106553
- "apple-ane": {
106554
- "yolov9t-320": {
106555
- p50Ms: 1.32,
106556
- fps: 758,
106557
- measured: false
106558
- },
106559
- "yolov9t-640": {
106560
- p50Ms: 3.45,
106561
- fps: 290,
106562
- measured: true
106563
- },
106564
- "yolov9s-320": {
106565
- p50Ms: 1.8,
106566
- fps: 556,
106567
- measured: false
106568
- },
106569
- "yolov9s-640": {
106570
- p50Ms: 5.46,
106571
- fps: 183,
106572
- measured: true
106573
- },
106574
- "yolov9m-320": {
106575
- p50Ms: 3.79,
106576
- fps: 264,
106577
- measured: true
106578
- },
106579
- "yolov9m-640": {
106580
- p50Ms: 10.34,
106581
- fps: 97,
106582
- measured: true
106583
- }
106584
- },
106585
- coral: { "yolov9s-320-edgetpu": {
106586
- p50Ms: 32,
106587
- fps: 31,
106588
- measured: false
106589
- } }
106425
+ /**
106426
+ * Can a pool of `format` run a step whose catalog is `models`, given the model
106427
+ * the step names (`null`/absent = unpinned, the format default)?
106428
+ */
106429
+ function stepModelRunsOnFormat(models, modelId, format) {
106430
+ if (modelId !== void 0 && modelId !== null) {
106431
+ const named = models.find((m) => m.id === modelId);
106432
+ if (named !== void 0 && hasBuild(named, format)) return true;
106590
106433
  }
106434
+ return models.some((m) => m.legacy !== true && hasBuild(m, format));
106435
+ }
106436
+ //#endregion
106437
+ //#region src/pipeline/removed-model-ids.ts
106438
+ /**
106439
+ * Model ids DELETED from a step's catalog (D703, D710, D719), per step — and
106440
+ * the one question asked of them: is a stored pin to this id a pin to a model
106441
+ * that no longer exists? (D725)
106442
+ *
106443
+ * This is NOT a resolver input. `resolveModelForFormat` still returns an
106444
+ * unknown id unchanged (a custom model), and a bulk pass pinned to one still
106445
+ * refuses rather than substitutes (D54). The ONE reader is the orchestrator's
106446
+ * stored-pin reset (`removed-model-pin-reset.ts`), which clears such a pin from
106447
+ * the store so the step follows its default again — unless a registered custom
106448
+ * model carries the same id.
106449
+ *
106450
+ * `removed-model-ids.spec.ts` (addon-pipeline) pins that every id here is
106451
+ * really absent from its step's catalog. A model deleted from a catalog is
106452
+ * added here in the same change, or its stored pins stay behind as custom ids.
106453
+ */
106454
+ /** `stepId → ids deleted from that step's catalog`. */
106455
+ var REMOVED_MODEL_IDS = {
106456
+ "object-detection": ["efficientdet-lite0-edgetpu", "ssdlite-mobiledet-coco-edgetpu"],
106457
+ "face-detection": [
106458
+ "scrypted-yolov9t-face",
106459
+ "scrfd-2.5g",
106460
+ "ssd-mobilenet-v2-face-edgetpu"
106461
+ ],
106462
+ "face-embedding": ["arcface-r100", "inception-resnet-v1"],
106463
+ "plate-detection": ["yolov8n-plate"],
106464
+ "plate-ocr": ["vgg-english-g2"],
106465
+ "clip-embedding": [
106466
+ "mobileclip-s0",
106467
+ "mobileclip-s1",
106468
+ "mobileclip-s2"
106469
+ ],
106470
+ "package-detection": ["yolov8n-package"],
106471
+ "bird-classifier": ["bird-classifier-aiy-edgetpu", "bird-nabirds-404"],
106472
+ "animal-classifier": ["animals-10"],
106473
+ "vehicle-classifier": ["vehicle-type-efficientnet"]
106591
106474
  };
106592
- var DETECTOR_ACCURACY = {
106593
- "yolov9m-320": {
106594
- personPrecision: .979,
106595
- personRecall: .649
106596
- },
106597
- "yolov9m-320-int8": {
106598
- personPrecision: .974,
106599
- personRecall: .684
106600
- },
106601
- "yolov9m-640": {
106602
- personPrecision: .976,
106603
- personRecall: .854
106604
- },
106605
- "yolov9m-640-int8": {
106606
- personPrecision: .976,
106607
- personRecall: .847
106608
- },
106609
- "yolov9s-320": {
106610
- personPrecision: .958,
106611
- personRecall: .479
106612
- },
106613
- "yolov9s-320-int8": {
106614
- personPrecision: .969,
106615
- personRecall: .438
106616
- },
106617
- "yolov9s-640": {
106618
- personPrecision: .986,
106619
- personRecall: .753
106620
- },
106621
- "yolov9s-640-int8": {
106622
- personPrecision: .963,
106623
- personRecall: .823
106624
- },
106625
- "yolov9t-320": {
106626
- personPrecision: .946,
106627
- personRecall: .368
106628
- },
106629
- "yolov9t-320-int8": {
106630
- personPrecision: .943,
106631
- personRecall: .347
106632
- },
106633
- "yolov9t-640": {
106634
- personPrecision: .979,
106635
- personRecall: .646
106636
- },
106637
- "yolov9t-640-int8": {
106638
- personPrecision: .979,
106639
- personRecall: .646
106475
+ /** True when `modelId` was deleted from `stepId`'s catalog. */
106476
+ function isRemovedModelId(stepId, modelId) {
106477
+ return REMOVED_MODEL_IDS[stepId]?.includes(modelId) ?? false;
106478
+ }
106479
+ //#endregion
106480
+ //#region src/pipeline/model-bound-settings.ts
106481
+ /** Suffix of the sibling key that names the model a stored value was made for. */
106482
+ var MODEL_BINDING_SUFFIX = "ModelId";
106483
+ /** `minConfidencePerson` → `minConfidencePersonModelId`. */
106484
+ function modelBindingKey(key) {
106485
+ return `${key}${MODEL_BINDING_SUFFIX}`;
106486
+ }
106487
+ /** The model a stored value was made for, or `undefined` when unbound. */
106488
+ function storedBinding(settings, key) {
106489
+ const binding = settings?.[modelBindingKey(key)];
106490
+ return typeof binding === "string" && binding.length > 0 ? binding : void 0;
106491
+ }
106492
+ /**
106493
+ * The effective value of `key` for the model that runs (`runningModelId`,
106494
+ * `undefined` when unknown — it then matches no binding).
106495
+ */
106496
+ function resolveModelBoundSetting(settings, key, runningModelId, modelDefault, globalDefault) {
106497
+ const stored = settings?.[key];
106498
+ const binding = storedBinding(settings, key);
106499
+ if (stored !== void 0 && binding === void 0) return {
106500
+ value: stored,
106501
+ source: "override"
106502
+ };
106503
+ if (stored !== void 0 && binding !== void 0 && binding === runningModelId) return {
106504
+ value: stored,
106505
+ source: "override",
106506
+ boundModelId: binding
106507
+ };
106508
+ const inapplicable = stored !== void 0 && binding !== void 0 ? { inapplicableOverride: {
106509
+ value: stored,
106510
+ modelId: binding
106511
+ } } : {};
106512
+ if (modelDefault !== void 0) return {
106513
+ value: modelDefault,
106514
+ source: "model-default",
106515
+ ...inapplicable
106516
+ };
106517
+ return {
106518
+ value: globalDefault,
106519
+ source: "global-default",
106520
+ ...inapplicable
106521
+ };
106522
+ }
106523
+ /**
106524
+ * The OPERATOR layer of a step's settings as the running model sees it: every
106525
+ * model-bound value made for another model is removed (so the default
106526
+ * underneath — the running model's — wins the merge), and every binding key is
106527
+ * stripped. Unbound values are kept. Returns a fresh object; `undefined` stays
106528
+ * `undefined`.
106529
+ */
106530
+ function applyModelBoundSettings(settings, boundKeys, runningModelId) {
106531
+ if (settings === void 0) return void 0;
106532
+ const out = { ...settings };
106533
+ for (const key of boundKeys) {
106534
+ const binding = storedBinding(settings, key);
106535
+ if (binding !== void 0 && binding !== runningModelId) delete out[key];
106536
+ delete out[modelBindingKey(key)];
106640
106537
  }
106641
- };
106538
+ return out;
106539
+ }
106540
+ /** Store `value` for `key`, made for `modelId`. Returns a fresh object. */
106541
+ function writeModelBoundSetting(settings, key, value, modelId) {
106542
+ return {
106543
+ ...settings,
106544
+ [key]: value,
106545
+ [modelBindingKey(key)]: modelId
106546
+ };
106547
+ }
106548
+ /** Remove an override and its binding — "reset to the model default". */
106549
+ function clearModelBoundSetting(settings, key) {
106550
+ const out = { ...settings };
106551
+ delete out[key];
106552
+ delete out[modelBindingKey(key)];
106553
+ return out;
106554
+ }
106555
+ /** True when any of `boundKeys` has an override IN FORCE for `modelId`. */
106556
+ function hasModelBoundOverrides(settings, boundKeys, modelId) {
106557
+ return boundKeys.some((key) => resolveModelBoundSetting(settings, key, modelId, void 0, void 0).source === "override");
106558
+ }
106559
+ /**
106560
+ * "Reset thresholds to model defaults": remove every override of `boundKeys`
106561
+ * that is in force for `modelId` (bound to it, or unbound), with its binding —
106562
+ * one result, so the editor writes it in one save. An override stored for
106563
+ * ANOTHER model is kept: it is not in force here, and it applies again when
106564
+ * that model runs.
106565
+ */
106566
+ function clearModelBoundOverrides(settings, boundKeys, modelId) {
106567
+ return boundKeys.reduce((acc, key) => resolveModelBoundSetting(acc, key, modelId, void 0, void 0).source === "override" ? clearModelBoundSetting(acc, key) : acc, { ...settings });
106568
+ }
106569
+ /**
106570
+ * The keys of every `bindsToModel` field of a step's config schema, groups
106571
+ * flattened (D738). One definition for the runner (which applies the binding)
106572
+ * and the orchestrator's Auto tune (which clears those overrides, D745).
106573
+ */
106574
+ function collectModelBoundKeys(fields) {
106575
+ return fields.flatMap((field) => {
106576
+ if (field.type === "group") return collectModelBoundKeys(field.fields);
106577
+ return "key" in field && "bindsToModel" in field && field.bindsToModel === true ? [field.key] : [];
106578
+ });
106579
+ }
106580
+ //#endregion
106581
+ //#region src/pipeline/step-settings-layers.ts
106582
+ /**
106583
+ * The layers of a detection step's settings, and the field-level edits on them
106584
+ * (D754, D927).
106585
+ *
106586
+ * A step's settings reach a session through two stored layers, merged key by
106587
+ * key — a key in the higher layer wins:
106588
+ *
106589
+ * node the node's base for the accelerator that runs the step
106590
+ * camera `cameraSettings[cam].stepSettings[step]` — camera-semantic
106591
+ * keys only, on every accelerator (never a model-bound field)
106592
+ *
106593
+ * Below them sits the default: the running model's (D738) or the field's own.
106594
+ *
106595
+ * Every layer is SPARSE and FIELD-LEVEL. A key present in a layer is an
106596
+ * override; an absent key inherits. Two consequences that every editor must
106597
+ * respect, and why this module exists:
106598
+ *
106599
+ * - A model-bound field (D738) occupies TWO keys, its value and
106600
+ * `<key>ModelId`. They are written, compared and removed as one unit — a
106601
+ * value without its binding over a lower layer's binding would be judged
106602
+ * against the wrong model.
106603
+ * - An editor writes a DIFF against the merged layers below, never a copy of
106604
+ * them. A copy pins every field at the value it had that day, so later
106605
+ * edits below never reach it (the full-copy "Override" this replaced).
106606
+ */
106607
+ /** Top-down order of the stored layers. */
106608
+ var LAYER_ORDER = ["camera", "node"];
106609
+ /**
106610
+ * The keys one field occupies in a bag: its own, plus its binding key when it
106611
+ * binds to a model. `boundKeys` = `collectModelBoundKeys(schema)`.
106612
+ */
106613
+ function fieldUnitKeys(key, boundKeys) {
106614
+ return boundKeys.includes(key) ? [key, modelBindingKey(key)] : [key];
106615
+ }
106616
+ /** The field a stored key belongs to (`minConfidencePersonModelId` → `minConfidencePerson`). */
106617
+ function unitOf(storedKey, boundKeys) {
106618
+ return boundKeys.find((k) => modelBindingKey(k) === storedKey) ?? storedKey;
106619
+ }
106620
+ /** Structural equality for stored values (numbers, strings, booleans, arrays, plain objects). */
106621
+ function settingValuesEqual(a, b) {
106622
+ if (Object.is(a, b)) return true;
106623
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => settingValuesEqual(v, b[i]));
106624
+ if (isPlainObject(a) && isPlainObject(b)) {
106625
+ const ka = Object.keys(a);
106626
+ const kb = Object.keys(b);
106627
+ return ka.length === kb.length && ka.every((k) => settingValuesEqual(a[k], b[k]));
106628
+ }
106629
+ return false;
106630
+ }
106631
+ function isPlainObject(v) {
106632
+ return typeof v === "object" && v !== null && !Array.isArray(v);
106633
+ }
106634
+ /** Merge bags bottom-up (later wins, per key). Fresh object. */
106635
+ function mergeSettingsBags(...bags) {
106636
+ return bags.reduce((acc, bag) => ({
106637
+ ...acc,
106638
+ ...bag
106639
+ }), {});
106640
+ }
106641
+ /** The merged value of the layers BELOW `layer` (what removing a key from `layer` falls back to). */
106642
+ function settingsBelow(layers, layer) {
106643
+ return mergeSettingsBags(...LAYER_ORDER.slice(LAYER_ORDER.indexOf(layer) + 1).toReversed().map((id) => layers[id]));
106644
+ }
106645
+ /** The effective bag: every stored layer merged. */
106646
+ function effectiveStepSettings(layers) {
106647
+ return mergeSettingsBags(layers.node, layers.camera);
106648
+ }
106649
+ /** The highest stored layer that holds any key of `key`'s unit, or `default`. */
106650
+ function stepSettingSource(layers, key, boundKeys) {
106651
+ const unit = fieldUnitKeys(key, boundKeys);
106652
+ for (const id of LAYER_ORDER) {
106653
+ const bag = layers[id];
106654
+ if (bag !== void 0 && unit.some((k) => bag[k] !== void 0)) return id;
106655
+ }
106656
+ return "default";
106657
+ }
106658
+ /**
106659
+ * `layer` with `key`'s unit set from `values` (a bag carrying the unit's new
106660
+ * value — e.g. the editor's whole next settings bag). A unit key that `values`
106661
+ * does not carry is removed from the layer. Fresh object.
106662
+ */
106663
+ function withFieldFrom(layer, key, values, boundKeys) {
106664
+ const out = { ...layer };
106665
+ for (const k of fieldUnitKeys(key, boundKeys)) if (values[k] === void 0) delete out[k];
106666
+ else out[k] = values[k];
106667
+ return out;
106668
+ }
106669
+ /** `layer` without `key`'s unit — "reset to the next level". Fresh object. */
106670
+ function withoutField(layer, key, boundKeys) {
106671
+ const out = { ...layer };
106672
+ for (const k of fieldUnitKeys(key, boundKeys)) delete out[k];
106673
+ return out;
106674
+ }
106675
+ /** Every field unit stored in `layer`, by its field key. */
106676
+ function unitsIn(layer, boundKeys) {
106677
+ return [...new Set(Object.keys(layer).map((k) => unitOf(k, boundKeys)))];
106678
+ }
106679
+ /** True when every key of `key`'s unit has the same value in `layer` as in `below`. */
106680
+ function unitEqualsBelow(layer, below, key, boundKeys) {
106681
+ return fieldUnitKeys(key, boundKeys).every((k) => settingValuesEqual(layer[k], below[k]));
106682
+ }
106683
+ /**
106684
+ * `layer` reduced to the fields that DIFFER from `below` — what an editor
106685
+ * stores. A unit is kept or dropped whole. `undefined` when nothing is left.
106686
+ */
106687
+ function diffStepSettings(layer, below, boundKeys) {
106688
+ if (layer === void 0) return void 0;
106689
+ const out = {};
106690
+ for (const unit of unitsIn(layer, boundKeys)) {
106691
+ if (unitEqualsBelow(layer, below, unit, boundKeys)) continue;
106692
+ for (const k of fieldUnitKeys(unit, boundKeys)) if (layer[k] !== void 0) out[k] = layer[k];
106693
+ }
106694
+ return Object.keys(out).length > 0 ? out : void 0;
106695
+ }
106696
+ /**
106697
+ * The fields `layer` stores at exactly the value of the layers below — a
106698
+ * legacy full copy's leftovers. They change nothing today and stop every
106699
+ * later edit below from reaching this layer. Sorted.
106700
+ */
106701
+ function fieldsPinnedAtValueBelow(layer, below, boundKeys) {
106702
+ if (layer === void 0) return [];
106703
+ return unitsIn(layer, boundKeys).filter((unit) => unitEqualsBelow(layer, below, unit, boundKeys)).toSorted();
106704
+ }
106705
+ //#endregion
106706
+ //#region src/pipeline/auto-tune/hardware-class.ts
106707
+ var APPLE_CHIP = /^Apple M(\d+)(?:\s+(Pro|Max|Ultra))?\b/i;
106708
+ var INTEL_CORE_ULTRA = /Core\(TM\)\s+Ultra|Core Ultra/i;
106709
+ var INTEL_N_SERIES = /Intel\(R\)\s+N\d{2,3}\b|\bIntel\s+N\d{2,3}\b/i;
106710
+ /** Phoenix / Hawk Point mobile (7040/8040) and Ryzen 8000G desktop APUs. */
106711
+ var AMD_PHOENIX = /Ryzen \d+ (7[0-9]40|8[0-9]40|8[0-9]45)[HSU]|Ryzen \d+ 8[0-9]00G/i;
106712
+ function parseAppleChip(cpuModel) {
106713
+ const match = APPLE_CHIP.exec(cpuModel.trim());
106714
+ if (match === null) return null;
106715
+ const generation = Number(match[1]);
106716
+ const variantRaw = (match[2] ?? "base").toLowerCase();
106717
+ return {
106718
+ generation,
106719
+ variant: variantRaw === "pro" || variantRaw === "max" || variantRaw === "ultra" ? variantRaw : "base"
106720
+ };
106721
+ }
106722
+ /**
106723
+ * Classify a node. Apple base chips (M1–M4) map to the measured M2 column, the
106724
+ * Pro/Max/Ultra chips to the measured M3 Pro column — the M2 was measured at
106725
+ * 1.0–1.2× the M3 Pro on the Neural Engine, so the generation inside a variant
106726
+ * matters less than the column's own error.
106727
+ */
106728
+ function classifyInferenceHardware(hw) {
106729
+ const cpuModel = hw.cpuModel;
106730
+ const apple = parseAppleChip(cpuModel);
106731
+ if (apple !== null) return {
106732
+ hardwareClass: apple.variant === "base" ? "apple-m-base" : "apple-m-pro",
106733
+ cpuModel,
106734
+ apple
106735
+ };
106736
+ if (INTEL_CORE_ULTRA.test(cpuModel)) return {
106737
+ hardwareClass: "intel-core-ultra",
106738
+ cpuModel
106739
+ };
106740
+ if (INTEL_N_SERIES.test(cpuModel)) return {
106741
+ hardwareClass: "intel-n-series",
106742
+ cpuModel
106743
+ };
106744
+ if (AMD_PHOENIX.test(cpuModel)) return {
106745
+ hardwareClass: "amd-phoenix",
106746
+ cpuModel
106747
+ };
106748
+ return {
106749
+ hardwareClass: "unknown",
106750
+ cpuModel
106751
+ };
106752
+ }
106753
+ /**
106754
+ * The accelerator kind of one of a node's device keys (`openvino:npu`,
106755
+ * `openvino:gpu`, `coreml:all`, `edgetpu:usb`, `cpu`, `onnx:cpu`, …). `null`
106756
+ * for a key this table has no column for.
106757
+ */
106758
+ function acceleratorKindOf(deviceKey, hw) {
106759
+ const [backend = "", device = ""] = deviceKey.split(":");
106760
+ if (backend === "edgetpu") return "coral";
106761
+ if (backend === "coreml") return device === "cpu" ? "arm-cpu" : "apple-ane";
106762
+ if (backend === "openvino" && device === "npu") return "intel-npu";
106763
+ if (backend === "openvino" && device === "gpu") return "intel-igpu";
106764
+ if (backend === "onnx" && device === "webgpu") return "amd-igpu";
106765
+ if (backend === "vitisai" && device === "npu") return "amd-npu";
106766
+ if (backend === "cpu" || device === "cpu") return hw.arch === "arm64" ? "arm-cpu" : "x86-cpu";
106767
+ return null;
106768
+ }
106642
106769
  //#endregion
106643
106770
  //#region src/pipeline/auto-tune/recommend.ts
106644
106771
  /** The family the cluster runs. */
@@ -106654,10 +106781,35 @@ var RECALL_TIE = .02;
106654
106781
  var DEFAULT_HEADROOM = .4;
106655
106782
  /** Detection rate a session is counted at when no camera says otherwise. */
106656
106783
  var DEFAULT_CAMERA_FPS = 10;
106657
- /** Parallel sessions one device sustains at `fps` with `headroom` kept free. */
106658
- function capacitySessionsOf(fps, headroom, cameraFps) {
106659
- if (!(fps > 0) || !(cameraFps > 0)) return 0;
106660
- return Math.max(0, Math.floor(fps * (1 - headroom) / cameraFps + 1e-9));
106784
+ /**
106785
+ * Camera rate at which serial `1000/p50` matched the cameras that held
106786
+ * (N100 iGPU, 2026-10-24). Below this the second InferRequest's overlap
106787
+ * becomes extra sessions; at and above it the serial ceiling is the count.
106788
+ */
106789
+ var SERIAL_CAPACITY_CAMERA_FPS = 10;
106790
+ /**
106791
+ * Aggregate throughput ÷ serial throughput when the frame interval is wide
106792
+ * enough to hide the second request. intel-igpu is the N100 measurement
106793
+ * (~1.4× under LATENCY with 2 InferRequests). Every other kind stays 1
106794
+ * until it is measured the same way.
106795
+ */
106796
+ var AGGREGATE_THROUGHPUT_BY_KIND = { "intel-igpu": 1.4 };
106797
+ /** The overlap factor for this kind at this camera rate. 1 at {@link SERIAL_CAPACITY_CAMERA_FPS} and above. */
106798
+ function aggregateThroughputFactor(kind, cameraFps) {
106799
+ if (!(cameraFps > 0) || cameraFps >= 10) return 1;
106800
+ return AGGREGATE_THROUGHPUT_BY_KIND[kind] ?? 1;
106801
+ }
106802
+ /**
106803
+ * Parallel sessions one device sustains at `fps` with `headroom` kept free.
106804
+ * `aggregateFactor` is the kind's overlap (1 unless the caller measured
106805
+ * extra throughput at this camera rate).
106806
+ */
106807
+ function capacitySessionsOf(fps, headroom, cameraFps, aggregateFactor = 1) {
106808
+ if (!(fps > 0) || !(cameraFps > 0) || !(aggregateFactor > 0)) return 0;
106809
+ return Math.max(0, Math.floor(fps * aggregateFactor * (1 - headroom) / cameraFps + 1e-9));
106810
+ }
106811
+ function reportFor(hardware, deviceKey) {
106812
+ return hardware.accelerators?.find((report) => report.deviceKey === deviceKey) ?? null;
106661
106813
  }
106662
106814
  /** The table column with the node's own measurements laid over it. */
106663
106815
  function overlayMeasured(table, measured) {
@@ -106679,11 +106831,12 @@ function round(value, digits = 3) {
106679
106831
  const f = 10 ** digits;
106680
106832
  return Math.round(value * f) / f;
106681
106833
  }
106682
- function sessionsOn(cell, sizing) {
106683
- return cell === void 0 ? 0 : capacitySessionsOf(cell.fps, sizing.headroom, sizing.cameraFps);
106834
+ function sessionsOn(cell, sizing, kind) {
106835
+ if (cell === void 0) return 0;
106836
+ return capacitySessionsOf(cell.fps, sizing.headroom, sizing.cameraFps, aggregateThroughputFactor(kind, sizing.cameraFps));
106684
106837
  }
106685
- function fitsOn(cell, sizing) {
106686
- return sessionsOn(cell, sizing) >= sizing.minSessions;
106838
+ function fitsOn(ctx, modelId, sizing) {
106839
+ return sessionsOn(ctx.column[modelId], sizing, ctx.accelerator) >= sizing.minSessions;
106687
106840
  }
106688
106841
  function candidatesFor(contexts, formatsByModel) {
106689
106842
  return Object.keys(DETECTOR_ACCURACY).filter((id) => id.startsWith(AUTO_TUNE_FAMILY)).filter((id) => contexts.every((c) => c.column[id] !== void 0 && (formatsByModel[id] ?? []).includes(c.device.format))).map((id) => ({
@@ -106696,13 +106849,13 @@ function totalLatency(contexts, modelId) {
106696
106849
  }
106697
106850
  /** The fewest sessions any device of the group sustains with this model. */
106698
106851
  function worstSessions(contexts, modelId, sizing) {
106699
- return contexts.reduce((worst, c) => Math.min(worst, sessionsOn(c.column[modelId], sizing)), Number.POSITIVE_INFINITY);
106852
+ return contexts.reduce((worst, c) => Math.min(worst, sessionsOn(c.column[modelId], sizing, c.accelerator)), Number.POSITIVE_INFINITY);
106700
106853
  }
106701
106854
  /** One model for a group of devices that must agree (step 2–4 of the header). */
106702
106855
  function pickForGroup(contexts, formatsByModel, sizing) {
106703
106856
  const candidates = candidatesFor(contexts, formatsByModel);
106704
106857
  if (candidates.length === 0) return null;
106705
- const fitting = candidates.filter((c) => contexts.every((ctx) => fitsOn(ctx.column[c.modelId], sizing)));
106858
+ const fitting = candidates.filter((c) => contexts.every((ctx) => fitsOn(ctx, c.modelId, sizing)));
106706
106859
  if (fitting.length === 0) {
106707
106860
  const lightest = candidates.toSorted((a, b) => worstSessions(contexts, b.modelId, sizing) - worstSessions(contexts, a.modelId, sizing) || totalLatency(contexts, a.modelId) - totalLatency(contexts, b.modelId))[0];
106708
106861
  if (lightest === void 0) return null;
@@ -106721,7 +106874,7 @@ function pickForGroup(contexts, formatsByModel, sizing) {
106721
106874
  const reasons = [];
106722
106875
  const mostAccurate = candidates[0];
106723
106876
  if (mostAccurate !== void 0 && mostAccurate.modelId !== best.modelId) {
106724
- const limitedBy = contexts.find((ctx) => !fitsOn(ctx.column[mostAccurate.modelId], sizing))?.device.key ?? "";
106877
+ const limitedBy = contexts.find((ctx) => !fitsOn(ctx, mostAccurate.modelId, sizing))?.device.key ?? "";
106725
106878
  reasons.push({
106726
106879
  kind: "stepped-down",
106727
106880
  from: mostAccurate.modelId,
@@ -106742,13 +106895,19 @@ function pickForGroup(contexts, formatsByModel, sizing) {
106742
106895
  function choiceOn(ctx, modelId, sizing) {
106743
106896
  const cell = ctx.column[modelId];
106744
106897
  if (cell === void 0) return null;
106898
+ const estimatedFrom = cell.source?.kind === "node-benchmark" ? void 0 : cell.estimatedFrom;
106899
+ const source = cell.source ?? (estimatedFrom !== void 0 ? {
106900
+ kind: "estimated",
106901
+ estimatedFrom
106902
+ } : { kind: "table" });
106745
106903
  return {
106746
106904
  modelId,
106747
106905
  p50Ms: cell.p50Ms,
106748
106906
  fps: cell.fps,
106749
106907
  measured: cell.measured,
106750
- source: cell.source ?? { kind: "table" },
106751
- capacitySessions: sessionsOn(cell, sizing),
106908
+ source,
106909
+ ...estimatedFrom !== void 0 ? { estimatedFrom } : {},
106910
+ capacitySessions: sessionsOn(cell, sizing, ctx.accelerator),
106752
106911
  personRecall: DETECTOR_ACCURACY[modelId]?.personRecall ?? 0
106753
106912
  };
106754
106913
  }
@@ -106799,7 +106958,11 @@ function recommendNode(input) {
106799
106958
  const measured = input.measured ?? {};
106800
106959
  const hasMeasured = Object.values(measured).some((m) => Object.keys(m).length > 0);
106801
106960
  const knownTable = hardwareClass === "unknown" ? void 0 : perf[hardwareClass];
106802
- if (knownTable === void 0 && !hasMeasured) return skippedNode(node, {
106961
+ const canEstimate = (hardware.accelerators ?? []).some((report) => {
106962
+ const kind = acceleratorKindOf(report.deviceKey, hardware);
106963
+ return kind !== null && reportIsScalable(report, kind);
106964
+ });
106965
+ if (knownTable === void 0 && !hasMeasured && !canEstimate) return skippedNode(node, {
106803
106966
  kind: "no-measured-data",
106804
106967
  hardwareClass
106805
106968
  }, hardwareClass);
@@ -106835,7 +106998,12 @@ function recommendNode(input) {
106835
106998
  });
106836
106999
  continue;
106837
107000
  }
106838
- const column = accelerator === null ? void 0 : overlayMeasured(classTable[accelerator], measured[device.key]);
107001
+ const column = accelerator === null ? void 0 : overlayMeasured(resolveLatencyColumn({
107002
+ hardwareClass,
107003
+ accelerator,
107004
+ report: reportFor(hardware, device.key),
107005
+ perf
107006
+ })?.column, measured[device.key]);
106839
107007
  if (accelerator === null || column === void 0) {
106840
107008
  early.set(device.key, [{
106841
107009
  kind: "no-family-build",
@@ -107583,6 +107751,507 @@ function buildBenchStepTree(schema, rootModelId, enabledStepIds) {
107583
107751
  }];
107584
107752
  }
107585
107753
  //#endregion
107754
+ //#region src/pipeline/second-pass/small-subject-recovery.ts
107755
+ /** The object-detection step id — the first-level detector both recipes run. */
107756
+ var OBJECT_DETECTION_STEP_ID = "object-detection";
107757
+ /**
107758
+ * Smallest crop side, native pixels. Below this the model no longer
107759
+ * magnifies enough to matter on a 4K frame, and on a 1080p frame 640 is
107760
+ * already a third of the picture. Measured: 480–800 px all recover the dog.
107761
+ */
107762
+ var RECOVERY_CROP_MIN_SIDE_PX = 640;
107763
+ /**
107764
+ * Largest crop side. A 320 model on a 1024 px square is a 3.2× reduction and
107765
+ * scored the dog at 0.52–0.76; at 1280 px it was absent again. A region wider
107766
+ * than this is tiled, never zoomed out.
107767
+ */
107768
+ var RECOVERY_CROP_MAX_SIDE_PX = 800;
107769
+ /** Context around the region before squaring, as a fraction of its larger side. */
107770
+ var RECOVERY_CROP_PADDING_RATIO = .25;
107771
+ /**
107772
+ * The view the crop is resolved at, compressed. Never larger than the crop
107773
+ * (the floor is {@link RECOVERY_CROP_MIN_SIDE_PX}), so it never upscales, and
107774
+ * the model letterboxes it to its own input from here as it does the frame.
107775
+ */
107776
+ var RECOVERY_VIEW_PX = 640;
107777
+ /**
107778
+ * Crops per frame. Each is one more inference on the same pool (15–25 ms on
107779
+ * CoreML for a 640 view); two cover a person-and-dog blob and a second mover.
107780
+ */
107781
+ var RECOVERY_MAX_CROPS_PER_FRAME = 2;
107782
+ /**
107783
+ * The pass runs at most this often per camera — at 10 fps that is one frame
107784
+ * in five. A subject the full pass misses is missed for seconds, not for one
107785
+ * frame, so the recovery does not need every frame to find it.
107786
+ *
107787
+ * The floor under this number is the tracker: a track coasts for
107788
+ * `maxMissedMs` (4 000 ms by default) before it is dropped, so 2 recoveries a
107789
+ * second keep a subject the full-frame pass has lost ALIVE with 8× margin,
107790
+ * and reach `minHits` (3) in 1.5 s. Raising it past ~1 300 ms would start to
107791
+ * lose tracks between passes.
107792
+ */
107793
+ var RECOVERY_MIN_INTERVAL_MS = 500;
107794
+ /**
107795
+ * A region larger than this share of the frame is not a small subject: a
107796
+ * lighting change, a car filling the picture, a person at the lens. The
107797
+ * full-frame pass already sees those at scale.
107798
+ */
107799
+ var RECOVERY_MAX_REGION_FRAME_FRACTION = .25;
107800
+ /**
107801
+ * How much of a region must lie OUTSIDE every full-frame detection for the
107802
+ * pass to run on it. A person's own blob (dilated, with shadow) is covered
107803
+ * 70–90% by the person box; a person-and-dog blob is covered ~55% by it.
107804
+ */
107805
+ var RECOVERY_MIN_UNCOVERED_FRACTION = .35;
107806
+ /** Motion regions older than this describe a different frame. */
107807
+ var RECOVERY_MOTION_MAX_AGE_MS = 2e3;
107808
+ /** A recovered box overlapping a same-class full-frame box this much is the same subject. */
107809
+ var RECOVERY_FUSE_IOU = .5;
107810
+ /**
107811
+ * A recovered box CONTAINED this much in a same-class full-frame box is a part
107812
+ * of it — a torso the crop cut out of a person the full pass already has.
107813
+ */
107814
+ var RECOVERY_FUSE_CONTAINMENT = .7;
107815
+ /** A box within this many crop-view pixels of the crop edge was clipped by it. */
107816
+ var RECOVERY_EDGE_MARGIN_PX = 2;
107817
+ /** Area of a normalised rectangle, never negative. Shared with the D396 recipe. */
107818
+ function normalizedArea(r) {
107819
+ return Math.max(0, r.w) * Math.max(0, r.h);
107820
+ }
107821
+ var area = normalizedArea;
107822
+ function intersection(a, b) {
107823
+ return Math.max(0, Math.min(a.x + a.w, b.x + b.w) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
107824
+ }
107825
+ /**
107826
+ * Share of `region` not covered by any detection box. The sum of pairwise
107827
+ * intersections over-counts where detections overlap each other, which only
107828
+ * makes the pass MORE conservative; clipped at 1.
107829
+ */
107830
+ function uncoveredFraction(region, detections) {
107831
+ const regionArea = area(region);
107832
+ if (regionArea <= 0) return 0;
107833
+ let covered = 0;
107834
+ for (const detection of detections) covered += intersection(region, detection);
107835
+ return 1 - Math.min(1, covered / regionArea);
107836
+ }
107837
+ /**
107838
+ * THE square-placement rule of this pass — one owner, shared by both recipes.
107839
+ *
107840
+ * A crop is a square of `side` native pixels centred where it can be and slid
107841
+ * inside the frame where it cannot. Both region rules place their squares
107842
+ * through this function so a crop means the same rectangle whichever rule
107843
+ * chose the region.
107844
+ */
107845
+ function squareAround(centreX, centreY, side, native) {
107846
+ return {
107847
+ left: Math.round(Math.max(0, Math.min(native.width - side, centreX - side / 2))),
107848
+ top: Math.round(Math.max(0, Math.min(native.height - side, centreY - side / 2))),
107849
+ side
107850
+ };
107851
+ }
107852
+ function contains(crop, x, y) {
107853
+ return x >= crop.left && x <= crop.left + crop.side && y >= crop.top && y <= crop.top + crop.side;
107854
+ }
107855
+ /**
107856
+ * Squares for one region: one when it fits the largest side, otherwise up to
107857
+ * two tiles along its longer axis (a person-sized blob with something small
107858
+ * at either end). The padded region decides the side, never the frame.
107859
+ */
107860
+ function cropsForRegion(region, native) {
107861
+ const widthPx = region.w * native.width;
107862
+ const heightPx = region.h * native.height;
107863
+ const longest = Math.max(widthPx, heightPx);
107864
+ const padded = longest * 1.5;
107865
+ const side = Math.round(Math.min(Math.max(padded, 640), 800));
107866
+ const centreX = (region.x + region.w / 2) * native.width;
107867
+ const centreY = (region.y + region.h / 2) * native.height;
107868
+ if (padded <= 800) return [squareAround(centreX, centreY, side, native)];
107869
+ const alongX = widthPx >= heightPx;
107870
+ const quarter = longest / 4;
107871
+ return [squareAround(alongX ? centreX - quarter : centreX, alongX ? centreY : centreY - quarter, side, native), squareAround(alongX ? centreX + quarter : centreX, alongX ? centreY : centreY + quarter, side, native)];
107872
+ }
107873
+ /**
107874
+ * The ONE place the two region gates are decided. `planRecoveryCrops` filters
107875
+ * on it and {@link countRecoverySkips} counts it, so the report can never
107876
+ * describe a gate the planner does not actually apply.
107877
+ */
107878
+ function classifyRegion(region, uncovered) {
107879
+ if (area(region) > .25) return "too-large";
107880
+ if (uncovered < .35) return "covered";
107881
+ return "eligible";
107882
+ }
107883
+ /**
107884
+ * WHY A FRAME PLANNED NO CROP, AS A NUMBER (D378).
107885
+ *
107886
+ * The pass report counts the passes that RAN, so "the second pass stops running
107887
+ * when a person is beside the dog" — a plausible reading of
107888
+ * {@link RECOVERY_MAX_REGION_FRAME_FRACTION} and
107889
+ * {@link RECOVERY_MIN_UNCOVERED_FRACTION}, and a live hypothesis on camera 615
107890
+ * — could not be confirmed or killed from the logs at all. This classifies the
107891
+ * regions a frame offered, against the same predicate the planner filters on.
107892
+ */
107893
+ function countRecoverySkips(input) {
107894
+ let tooLarge = 0;
107895
+ let covered = 0;
107896
+ let eligible = 0;
107897
+ for (const region of input.regions) switch (classifyRegion(region, uncoveredFraction(region, input.detections))) {
107898
+ case "too-large":
107899
+ tooLarge += 1;
107900
+ break;
107901
+ case "covered":
107902
+ covered += 1;
107903
+ break;
107904
+ case "eligible":
107905
+ eligible += 1;
107906
+ break;
107907
+ }
107908
+ return {
107909
+ tooLarge,
107910
+ covered,
107911
+ eligible
107912
+ };
107913
+ }
107914
+ function planRecoveryCrops(input) {
107915
+ const { native } = input;
107916
+ if (Math.min(native.width, native.height) < 640) return [];
107917
+ const ranked = input.regions.map((region) => ({
107918
+ region,
107919
+ uncovered: uncoveredFraction(region, input.detections)
107920
+ })).filter(({ region, uncovered }) => classifyRegion(region, uncovered) === "eligible").toSorted((a, b) => area(b.region) * b.uncovered - area(a.region) * a.uncovered);
107921
+ const planned = [];
107922
+ for (const { region } of ranked) for (const crop of cropsForRegion(region, native)) {
107923
+ if (planned.length >= 2) return planned;
107924
+ const centreX = crop.left + crop.side / 2;
107925
+ const centreY = crop.top + crop.side / 2;
107926
+ if (planned.some((existing) => contains(existing, centreX, centreY))) continue;
107927
+ planned.push(crop);
107928
+ }
107929
+ return planned;
107930
+ }
107931
+ /**
107932
+ * Boxes the executor produced on the crop VIEW → the result's own space.
107933
+ *
107934
+ * Three spaces meet here: the view the model saw (`view`, ≤640 px), the native
107935
+ * square it was cut from (`crop`, native px) and the space the full-frame
107936
+ * result reports in (`result`, native px on the `frameRef` path, the inline
107937
+ * ≤640 view otherwise). A box touching the view edge was clipped by the crop
107938
+ * and is dropped: half a person is not a person, and the tracker would take
107939
+ * the half as the whole.
107940
+ */
107941
+ function remapCropDetections(detections, crop, view, result, native) {
107942
+ const viewToNative = crop.side / Math.max(1, view.width);
107943
+ const nativeToResultX = result.width / Math.max(1, native.width);
107944
+ const nativeToResultY = result.height / Math.max(1, native.height);
107945
+ const out = [];
107946
+ for (const detection of detections) {
107947
+ const { x, y, width, height } = detection.bbox;
107948
+ if (x <= 2 || y <= 2 || x + width >= view.width - 2 || y + height >= view.height - 2) continue;
107949
+ out.push({
107950
+ ...detection,
107951
+ nativeRecovery: {
107952
+ score: detection.score,
107953
+ cropSidePx: crop.side,
107954
+ viewPx: view.width
107955
+ },
107956
+ bbox: {
107957
+ x: (crop.left + x * viewToNative) * nativeToResultX,
107958
+ y: (crop.top + y * viewToNative) * nativeToResultY,
107959
+ width: width * viewToNative * nativeToResultX,
107960
+ height: height * viewToNative * nativeToResultY
107961
+ }
107962
+ });
107963
+ }
107964
+ return out;
107965
+ }
107966
+ function boxIntersection(a, b) {
107967
+ return Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
107968
+ }
107969
+ function sameSubject(recovered, existing) {
107970
+ const inter = boxIntersection(recovered, existing);
107971
+ if (inter <= 0) return false;
107972
+ const areaR = recovered.width * recovered.height;
107973
+ const union = areaR + existing.width * existing.height - inter;
107974
+ if (union > 0 && inter / union >= .5) return true;
107975
+ return areaR > 0 && inter / areaR >= .7;
107976
+ }
107977
+ /**
107978
+ * Class-aware NMS of the recovered boxes against the full-frame ones. Every
107979
+ * full-frame box is kept as it is — the second pass only ADDS. A recovered box
107980
+ * is added when no full-frame box of the same macro class is the same subject
107981
+ * (`RECOVERY_FUSE_IOU`) or contains it (`RECOVERY_FUSE_CONTAINMENT`). Ids are
107982
+ * re-minted so a crop's `d0` cannot collide with the frame's.
107983
+ */
107984
+ function fuseRecoveredDetections(full, recovered) {
107985
+ const accepted = [];
107986
+ for (const [index, candidate] of recovered.entries()) {
107987
+ if ([...full, ...accepted].some((existing) => existing.macroClass === candidate.macroClass && sameSubject(candidate.bbox, existing.bbox))) continue;
107988
+ accepted.push({
107989
+ ...candidate,
107990
+ id: `recovered-${index}-${candidate.id}`
107991
+ });
107992
+ }
107993
+ return [...full, ...accepted];
107994
+ }
107995
+ /**
107996
+ * The step the second pass runs: the enabled first-level object detector of
107997
+ * the camera's own tree, with its own model and no children. Same model as
107998
+ * the full pass so a recovered box is comparable to a full-frame one.
107999
+ */
108000
+ function selectRecoveryStep(steps) {
108001
+ const detector = steps.find((step) => step.addonId === "object-detection" && step.enabled !== false);
108002
+ if (!detector) return null;
108003
+ return {
108004
+ addonId: detector.addonId,
108005
+ ...detector.modelId !== void 0 ? { modelId: detector.modelId } : {},
108006
+ enabled: true
108007
+ };
108008
+ }
108009
+ //#endregion
108010
+ //#region src/pipeline/second-pass/motion-crop-plan.ts
108011
+ /**
108012
+ * Step-settings key of the opt-in, on the `object-detection` step — so it is
108013
+ * per camera through `pipelineOrchestrator.setCameraStepSettings` (a
108014
+ * camera-semantic key, D927), and it rides the same
108015
+ * `PipelineStepInputOutput.settings` the per-macro confidence floors ride.
108016
+ */
108017
+ var MOTION_CROP_DISPATCH_KEY = "motionCropDispatch";
108018
+ /**
108019
+ * Read the opt-in. Anything that is not the boolean `true` is the shipped
108020
+ * rule: a stored string or a number is a mis-write (this repo has shipped a
108021
+ * setting written into the wrong sqlite table before), and a mis-write must
108022
+ * not silently change what a camera detects on.
108023
+ */
108024
+ function readRecoveryRecipe(settings) {
108025
+ return settings?.["motionCropDispatch"] === true ? "motion-crop" : "uncovered-region";
108026
+ }
108027
+ /**
108028
+ * The recipe this camera's resolved step tree selects.
108029
+ *
108030
+ * Reads the ENABLED first-level detector's own settings, which is where
108031
+ * `pipelineOrchestrator.setCameraStepSettings` writes a per-camera value. A
108032
+ * tree with no enabled detector runs no second pass at all, so the answer
108033
+ * there is the shipped rule and it is never used.
108034
+ */
108035
+ function readRecoveryRecipeForSteps(steps) {
108036
+ return readRecoveryRecipe(steps.find((step) => step.addonId === "object-detection" && step.enabled !== false)?.settings);
108037
+ }
108038
+ /**
108039
+ * A motion box is grown ×1.5 about its centre before anything else.
108040
+ *
108041
+ * The motion step's box is the CHANGED pixels, which on a moving animal is the
108042
+ * leading edge plus its shadow, not the animal. The detector needs the subject
108043
+ * and the context it was trained on.
108044
+ */
108045
+ var MOTION_CROP_MOTION_PAD = 1.5;
108046
+ /**
108047
+ * An intersecting tracked box is grown ×1.25 — less, because it is already a
108048
+ * subject rectangle rather than a change mask.
108049
+ */
108050
+ var MOTION_CROP_TRACKED_PAD = 1.25;
108051
+ /**
108052
+ * Regions per frame, largest area first.
108053
+ *
108054
+ * Measured as NON-BINDING: over the 156 frames of the recall harness the cap
108055
+ * bound on 0 of them. It is the bound on a storm, not a tuning knob, and the
108056
+ * runner reports when it binds so a camera on which it starts to is visible.
108057
+ */
108058
+ var MOTION_CROP_MAX_REGIONS = 3;
108059
+ /**
108060
+ * The floor a `person` or `vehicle` box must clear when it came out of a CROP
108061
+ * rather than out of the full frame.
108062
+ *
108063
+ * Collected, not assumed: 3 085 quiet frames produced 27 motion-positive
108064
+ * frames and 28 crop invocations. The full-frame arm produced 0 boxes above
108065
+ * any gate; the crop arm produced 3, of which two are genuine false positives,
108066
+ * and both are night-IR corners upscaled into a 320 model — `vehicle 0.749`
108067
+ * and `person 0.701`. Zero false `animal` at any gate, which is why `animal`
108068
+ * is deliberately absent from this table: the class the pass exists to recover
108069
+ * is the class that never produced a false positive, and gating it would cost
108070
+ * the whole measured gain.
108071
+ *
108072
+ * This is a CROP-ONLY floor, applied on top of the per-macro floors the
108073
+ * executor already enforced. A full-frame `person` at 0.70 is untouched — the
108074
+ * full-frame arm is the one arm with ground truth behind its floor
108075
+ * (`minConfidencePerson` 0.65, raised on 2026-07-30 from a measured
108076
+ * distribution).
108077
+ *
108078
+ * The cost of this floor is stated rather than assumed away: it can also
108079
+ * remove a real distant person or vehicle recovered from a crop at 0.70–0.80.
108080
+ * Nothing in the quiet-footage sample measures that, so the floor is a
108081
+ * constant here rather than a slider, and the runner reports every drop it
108082
+ * makes per camera.
108083
+ */
108084
+ var CROP_CLASS_FLOORS = Object.freeze({
108085
+ person: .8,
108086
+ vehicle: .8
108087
+ });
108088
+ /**
108089
+ * Apply the crop-only class floors, REPORTING every drop.
108090
+ *
108091
+ * A class with no entry in {@link CROP_CLASS_FLOORS} is kept: a floor this
108092
+ * table cannot express is not a floor of zero, and silently dropping a class
108093
+ * nobody measured is the exact shape of silent drop this repo has paid for
108094
+ * three separate times.
108095
+ */
108096
+ function applyCropClassFloors(detections) {
108097
+ const kept = [];
108098
+ const dropped = [];
108099
+ for (const detection of detections) {
108100
+ const floor = CROP_CLASS_FLOORS[detection.macroClass];
108101
+ if (floor !== void 0 && detection.score < floor) {
108102
+ dropped.push({
108103
+ macroClass: detection.macroClass,
108104
+ score: detection.score,
108105
+ floor
108106
+ });
108107
+ continue;
108108
+ }
108109
+ kept.push(detection);
108110
+ }
108111
+ return {
108112
+ kept,
108113
+ dropped
108114
+ };
108115
+ }
108116
+ function scaleAbout(rect, factor) {
108117
+ const w = rect.w * factor;
108118
+ const h = rect.h * factor;
108119
+ return {
108120
+ x: rect.x + rect.w / 2 - w / 2,
108121
+ y: rect.y + rect.h / 2 - h / 2,
108122
+ w,
108123
+ h
108124
+ };
108125
+ }
108126
+ function overlaps(a, b) {
108127
+ return Math.min(a.x + a.w, b.x + b.w) > Math.max(a.x, b.x) && Math.min(a.y + a.h, b.y + b.h) > Math.max(a.y, b.y);
108128
+ }
108129
+ function union(a, b) {
108130
+ const x = Math.min(a.x, b.x);
108131
+ const y = Math.min(a.y, b.y);
108132
+ return {
108133
+ x,
108134
+ y,
108135
+ w: Math.max(a.x + a.w, b.x + b.w) - x,
108136
+ h: Math.max(a.y + a.h, b.y + b.h) - y
108137
+ };
108138
+ }
108139
+ function clampToFrame(rect) {
108140
+ const x = Math.max(0, rect.x);
108141
+ const y = Math.max(0, rect.y);
108142
+ return {
108143
+ x,
108144
+ y,
108145
+ w: Math.min(1, rect.x + rect.w) - x,
108146
+ h: Math.min(1, rect.y + rect.h) - y
108147
+ };
108148
+ }
108149
+ /**
108150
+ * Merge transitively every seed whose padded box overlaps another, and answer
108151
+ * the union rectangles.
108152
+ *
108153
+ * Transitive on purpose: a person, the dog beside them and the dog's shadow
108154
+ * are three components of one subject, and cutting three crops for them spends
108155
+ * two inferences on pixels the first one already showed the detector.
108156
+ */
108157
+ function mergeSeeds(seeds) {
108158
+ const merged = [];
108159
+ for (const seed of seeds) {
108160
+ let current = seed;
108161
+ let changed = true;
108162
+ while (changed) {
108163
+ changed = false;
108164
+ for (let i = merged.length - 1; i >= 0; i -= 1) {
108165
+ const other = merged[i];
108166
+ if (other === void 0 || !overlaps(current, other)) continue;
108167
+ current = union(current, other);
108168
+ merged.splice(i, 1);
108169
+ changed = true;
108170
+ }
108171
+ }
108172
+ merged.push(current);
108173
+ }
108174
+ return merged;
108175
+ }
108176
+ /**
108177
+ * The regions the measured recipe judges this frame on.
108178
+ *
108179
+ * Motion boxes padded {@link MOTION_CROP_MOTION_PAD}; every full-frame box
108180
+ * that intersects a RAW motion box padded {@link MOTION_CROP_TRACKED_PAD} and
108181
+ * added as a seed of its own; everything that overlaps merged; the result
108182
+ * clamped to the frame, ordered largest first and capped at
108183
+ * {@link MOTION_CROP_MAX_REGIONS}.
108184
+ *
108185
+ * There is deliberately no coverage gate and no size gate here. Those belong
108186
+ * to the shipped rule, and they are what makes it a rule about a subject
108187
+ * BESIDE a detected one; the measurement says the pass is worth running on the
108188
+ * detected subject's own region too.
108189
+ */
108190
+ function planMotionCropRegions(input) {
108191
+ if (input.regions.length === 0) return [];
108192
+ const seeds = input.regions.map((r) => scaleAbout(r, MOTION_CROP_MOTION_PAD));
108193
+ for (const detection of input.detections) {
108194
+ if (!input.regions.some((region) => overlaps(detection, region))) continue;
108195
+ seeds.push(scaleAbout(detection, MOTION_CROP_TRACKED_PAD));
108196
+ }
108197
+ return mergeSeeds(seeds).map(clampToFrame).filter((r) => r.w > 0 && r.h > 0).toSorted((a, b) => normalizedArea(b) - normalizedArea(a)).slice(0, 3);
108198
+ }
108199
+ /**
108200
+ * The square the detector is fed for one measured region.
108201
+ *
108202
+ * "Grow to the detector's input aspect ratio without ever shrinking" is a
108203
+ * SQUARE here, and that is a fact about the production detector rather than a
108204
+ * simplification: `yolov9m-320-int8` takes 320×320, as does every hardware
108205
+ * default in the catalog.
108206
+ *
108207
+ * The side is the region's longer side, floored at
108208
+ * {@link RECOVERY_CROP_MIN_SIDE_PX} — the growth is what magnifies the subject
108209
+ * — and ceilinged at {@link RECOVERY_CROP_MAX_SIDE_PX}. The ceiling is NOT a
108210
+ * simplification either: at 1024 px the same 320 model scored the reference
108211
+ * dog 0.52–0.76, and at 1280 px lost it entirely
108212
+ * (`docs/benchmarks/pipeline-frame-model-eval.md`, case `miss-dog-615`). A
108213
+ * region longer than the ceiling is TILED along its longer axis, never zoomed
108214
+ * out.
108215
+ */
108216
+ function motionCropsForRegion(region, native) {
108217
+ const widthPx = region.w * native.width;
108218
+ const heightPx = region.h * native.height;
108219
+ const longest = Math.max(widthPx, heightPx);
108220
+ const side = Math.round(Math.min(Math.max(longest, 640), 800));
108221
+ const centreX = (region.x + region.w / 2) * native.width;
108222
+ const centreY = (region.y + region.h / 2) * native.height;
108223
+ if (longest <= 800) return [squareAround(centreX, centreY, side, native)];
108224
+ const alongX = widthPx >= heightPx;
108225
+ const quarter = longest / 4;
108226
+ return [squareAround(alongX ? centreX - quarter : centreX, alongX ? centreY : centreY - quarter, side, native), squareAround(alongX ? centreX + quarter : centreX, alongX ? centreY : centreY + quarter, side, native)];
108227
+ }
108228
+ /** `true` when `inner` lies entirely inside `outer`. */
108229
+ function cropContains(outer, inner) {
108230
+ return inner.left >= outer.left && inner.top >= outer.top && inner.left + inner.side <= outer.left + outer.side && inner.top + inner.side <= outer.top + outer.side;
108231
+ }
108232
+ /**
108233
+ * The crops the measured recipe runs on this frame.
108234
+ *
108235
+ * Dedupe is by CONTAINMENT only. Two squares that merely overlap both run: the
108236
+ * pixels the second adds are pixels no other crop shows the detector, and the
108237
+ * fusion step downstream already refuses a duplicate BOX. Dropping a crop
108238
+ * because it overlapped would drop the subject sitting at its edge, which is
108239
+ * the whole population this pass exists for.
108240
+ *
108241
+ * A frame that cannot hold the smallest crop yields nothing: it is already
108242
+ * seen at scale.
108243
+ */
108244
+ function planMotionCrops(input) {
108245
+ const { native } = input;
108246
+ if (Math.min(native.width, native.height) < 640) return [];
108247
+ const planned = [];
108248
+ for (const region of planMotionCropRegions(input)) for (const crop of motionCropsForRegion(region, native)) {
108249
+ if (planned.some((existing) => cropContains(existing, crop))) continue;
108250
+ planned.push(crop);
108251
+ }
108252
+ return planned;
108253
+ }
108254
+ //#endregion
107586
108255
  //#region src/pipeline/root-face-crop.ts
107587
108256
  /** Side of the square, as a multiple of the face box's longer side. */
107588
108257
  var ROOT_FACE_REFINE_SCALE = 1.6;
@@ -109937,6 +110606,7 @@ exports.ADMIN_SCOPE = ADMIN_SCOPE;
109937
110606
  exports.AGENT_MDNS_QUERY_NAME = AGENT_MDNS_QUERY_NAME;
109938
110607
  exports.AGENT_MDNS_SERVICE_TYPE = AGENT_MDNS_SERVICE_TYPE;
109939
110608
  exports.AGENT_MDNS_TXT_KEYS = AGENT_MDNS_TXT_KEYS;
110609
+ exports.AGGREGATE_THROUGHPUT_BY_KIND = AGGREGATE_THROUGHPUT_BY_KIND;
109940
110610
  exports.ALEXA_EGRESS_PROFILE = ALEXA_EGRESS_PROFILE;
109941
110611
  exports.ALL_CAPABILITY_DEFINITIONS = ALL_CAPABILITY_DEFINITIONS;
109942
110612
  exports.ALL_METHOD_ACCESS = ALL_METHOD_ACCESS;
@@ -109964,6 +110634,7 @@ exports.AUDIO_SCALE_EPOCH_MS = AUDIO_SCALE_EPOCH_MS;
109964
110634
  exports.AUTOMATIC_HUB_ADDRESS_FLAGS = AUTOMATIC_HUB_ADDRESS_FLAGS;
109965
110635
  exports.AUTO_SHARE_HEADROOM = AUTO_SHARE_HEADROOM;
109966
110636
  exports.AUTO_TUNE_FAMILY = AUTO_TUNE_FAMILY;
110637
+ exports.AcceleratorComputeSchema = AcceleratorComputeSchema;
109967
110638
  exports.AcceleratorKindSchema = AcceleratorKindSchema;
109968
110639
  exports.AccessoriesStatusSchema = AccessoriesStatusSchema;
109969
110640
  exports.AccessoryKind = AccessoryKind;
@@ -110150,8 +110821,11 @@ exports.CONTAINER_CHILD_PRIORITY = CONTAINER_CHILD_PRIORITY;
110150
110821
  exports.CORAL_DEFAULT_MODEL_ID = CORAL_DEFAULT_MODEL_ID;
110151
110822
  exports.CORE_BLOCKS_ADDON_ID = CORE_BLOCKS_ADDON_ID;
110152
110823
  exports.CORE_BLOCK_ADDON_PREFIX = CORE_BLOCK_ADDON_PREFIX;
110824
+ exports.CORE_ULTRA_265K_IGPU_CLOCK_MHZ = CORE_ULTRA_265K_IGPU_CLOCK_MHZ;
110825
+ exports.CORE_ULTRA_265K_IGPU_EU = CORE_ULTRA_265K_IGPU_EU;
110153
110826
  exports.CREDENTIAL_TOKEN_AUDIENCE = CREDENTIAL_TOKEN_AUDIENCE;
110154
110827
  exports.CREDENTIAL_TOKEN_KIND = CREDENTIAL_TOKEN_KIND;
110828
+ exports.CROP_CLASS_FLOORS = CROP_CLASS_FLOORS;
110155
110829
  exports.CROP_SUPPLY_SOURCES = CROP_SUPPLY_SOURCES;
110156
110830
  exports.CROP_SUPPLY_STEP_IDS = CROP_SUPPLY_STEP_IDS;
110157
110831
  exports.CUSTOMIZATION_BLOCK_NAME_PREFIX = require_composition.CUSTOMIZATION_BLOCK_NAME_PREFIX;
@@ -110184,7 +110858,6 @@ exports.CameraMetricsSchema = CameraMetricsSchema;
110184
110858
  exports.CameraMetricsWithDeviceIdSchema = CameraMetricsWithDeviceIdSchema;
110185
110859
  exports.CameraMotionStatusSchema = CameraMotionStatusSchema;
110186
110860
  exports.CameraOccupancySnapshotForDeviceSchema = CameraOccupancySnapshotForDeviceSchema;
110187
- exports.CameraOverrideClearSchema = CameraOverrideClearSchema;
110188
110861
  exports.CameraOwnMotionSchema = CameraOwnMotionSchema;
110189
110862
  exports.CameraRawLevelSchema = CameraRawLevelSchema;
110190
110863
  exports.CameraRecordingModeSchema = CameraRecordingModeSchema;
@@ -110437,7 +111110,6 @@ exports.DetailCropConventionSchema = DetailCropConventionSchema;
110437
111110
  exports.DetectionCatalogClassMapSchema = DetectionCatalogClassMapSchema;
110438
111111
  exports.DetectionSourceSchema = DetectionSourceSchema;
110439
111112
  exports.DetectionSourcesSchema = DetectionSourcesSchema;
110440
- exports.DetectorStoreClearSchema = DetectorStoreClearSchema;
110441
111113
  exports.DeviceAdminLinkSchema = DeviceAdminLinkSchema;
110442
111114
  exports.DeviceCaStatusSchema = DeviceCaStatusSchema;
110443
111115
  exports.DeviceCertificateLoginMethodSchema = DeviceCertificateLoginMethodSchema;
@@ -110504,6 +111176,8 @@ exports.DoorbellStatusSchema = DoorbellStatusSchema;
110504
111176
  exports.EMBEDDING_MODEL_FAILED = EMBEDDING_MODEL_FAILED;
110505
111177
  exports.EMBEDDING_REQUIREMENTS_FAILED = EMBEDDING_REQUIREMENTS_FAILED;
110506
111178
  exports.EMBEDDING_REQUIREMENTS_UNAVAILABLE = EMBEDDING_REQUIREMENTS_UNAVAILABLE;
111179
+ exports.ESTIMATE_COMPUTE_RATIO_MAX = ESTIMATE_COMPUTE_RATIO_MAX;
111180
+ exports.ESTIMATE_COMPUTE_RATIO_MIN = ESTIMATE_COMPUTE_RATIO_MIN;
110507
111181
  exports.EVENTFUL_CAP_NAMES = EVENTFUL_CAP_NAMES;
110508
111182
  exports.EVENT_DENSITY_BATCH_MAX = EVENT_DENSITY_BATCH_MAX;
110509
111183
  exports.EVENT_KIND_BY_CAP = EVENT_KIND_BY_CAP;
@@ -110854,6 +111528,10 @@ exports.MODEL_FORMATS = require_sleep.MODEL_FORMATS;
110854
111528
  exports.MODEL_HOSTING = MODEL_HOSTING;
110855
111529
  exports.MODEL_PROVIDER_IDS = MODEL_PROVIDER_IDS;
110856
111530
  exports.MOTION_CLOSE_AFTER_MS = MOTION_CLOSE_AFTER_MS;
111531
+ exports.MOTION_CROP_DISPATCH_KEY = MOTION_CROP_DISPATCH_KEY;
111532
+ exports.MOTION_CROP_MAX_REGIONS = MOTION_CROP_MAX_REGIONS;
111533
+ exports.MOTION_CROP_MOTION_PAD = MOTION_CROP_MOTION_PAD;
111534
+ exports.MOTION_CROP_TRACKED_PAD = MOTION_CROP_TRACKED_PAD;
110857
111535
  exports.MOTION_SOURCE_LABELS = MOTION_SOURCE_LABELS;
110858
111536
  exports.MOTION_TRIGGER_FEATURE = MOTION_TRIGGER_FEATURE;
110859
111537
  exports.ManagedModelCatalogEntrySchema = ManagedModelCatalogEntrySchema;
@@ -110918,6 +111596,8 @@ exports.MotionZoneStatusSchema = MotionZoneStatusSchema;
110918
111596
  exports.MqttBrokerInfoSchema = BrokerInfoSchema$1;
110919
111597
  exports.MqttBrokerStatusSchema = StatusSchema;
110920
111598
  exports.MutationFilterSchema = MutationFilterSchema;
111599
+ exports.N100_IGPU_CLOCK_MHZ = N100_IGPU_CLOCK_MHZ;
111600
+ exports.N100_IGPU_EU = N100_IGPU_EU;
110921
111601
  exports.NATIVE_LEASE_ACTIVITY_FIELD = NATIVE_LEASE_ACTIVITY_FIELD;
110922
111602
  exports.NATIVE_LEASE_ACTIVITY_KEY = NATIVE_LEASE_ACTIVITY_KEY;
110923
111603
  exports.NATIVE_LEASE_ADMISSION_FIELD = NATIVE_LEASE_ADMISSION_FIELD;
@@ -111183,6 +111863,7 @@ exports.NotifierStatusSchema = NotifierStatusSchema;
111183
111863
  exports.NumericSensorStatusSchema = NumericSensorStatusSchema;
111184
111864
  exports.OAUTH_LANDING_PATH = OAUTH_LANDING_PATH;
111185
111865
  exports.OAUTH_LANDING_REASONS = OAUTH_LANDING_REASONS;
111866
+ exports.OBJECT_DETECTION_STEP_ID = OBJECT_DETECTION_STEP_ID;
111186
111867
  exports.OPERATOR_WRITTEN_STALE_MS = OPERATOR_WRITTEN_STALE_MS;
111187
111868
  exports.OPS_LOG_DEFAULT_LIMIT = OPS_LOG_DEFAULT_LIMIT;
111188
111869
  exports.OPS_LOG_RING_DEFAULT_MAX = OPS_LOG_RING_DEFAULT_MAX;
@@ -111233,6 +111914,7 @@ exports.PEER_BYTES_TICKET_TTL_MS = require_sleep.PEER_BYTES_TICKET_TTL_MS;
111233
111914
  exports.PENDING_BLOCK_ID = PENDING_BLOCK_ID;
111234
111915
  exports.PET_FEEDER_MANUAL_FEED_MAX = PET_FEEDER_MANUAL_FEED_MAX;
111235
111916
  exports.PET_FEEDER_MANUAL_FEED_MIN = PET_FEEDER_MANUAL_FEED_MIN;
111917
+ exports.PHOENIX_780M_CU = PHOENIX_780M_CU;
111236
111918
  exports.PIPELINE_ANALYTICS_CAP_NAME = PIPELINE_ANALYTICS_CAP_NAME;
111237
111919
  exports.PIPELINE_FLOW_CAPABILITY_NAMES = PIPELINE_FLOW_CAPABILITY_NAMES;
111238
111920
  exports.PIPELINE_OWNER_CAPABILITY_NAMES = PIPELINE_OWNER_CAPABILITY_NAMES;
@@ -111249,6 +111931,7 @@ exports.PUBLIC_ENDPOINT_HTTP_REASON = PUBLIC_ENDPOINT_HTTP_REASON;
111249
111931
  exports.PYTHON_SCRIPT = PYTHON_SCRIPT;
111250
111932
  exports.PackageInstallationSchema = PackageInstallationSchema;
111251
111933
  exports.PackageInstallationsSchema = PackageInstallationsSchema;
111934
+ exports.PackageLlmVerdictSchema = PackageLlmVerdictSchema;
111252
111935
  exports.PackageUpdateSchema = PackageUpdateSchema;
111253
111936
  exports.PackageVersionInfoSchema = PackageVersionInfoSchema;
111254
111937
  exports.ParkRefusalSchema = ParkRefusalSchema;
@@ -111281,6 +111964,7 @@ exports.PlaintextSecretError = require_sleep.PlaintextSecretError;
111281
111964
  exports.PolygonPointSchema = PolygonPointSchema;
111282
111965
  exports.PoolMemoryWatchdog = PoolMemoryWatchdog;
111283
111966
  exports.PowerMeterStatusSchema = PowerMeterStatusSchema;
111967
+ exports.PrecisionGopsSchema = PrecisionGopsSchema;
111284
111968
  exports.PreferenceDeviceIdSchema = PreferenceDeviceIdSchema;
111285
111969
  exports.PreferenceNamespaceSchema = PreferenceNamespaceSchema;
111286
111970
  exports.PreferenceRowSchema = PreferenceRowSchema;
@@ -111349,7 +112033,21 @@ exports.RECORD_QUERY_MAX = require_sleep.RECORD_QUERY_MAX;
111349
112033
  exports.RECORD_SECRET_COLLECTION_PREFIX = require_sleep.RECORD_SECRET_COLLECTION_PREFIX;
111350
112034
  exports.RECORD_SETTINGS_MAX_ROWS = require_sleep.RECORD_SETTINGS_MAX_ROWS;
111351
112035
  exports.RECORD_SWEEP_INTERVAL_MS = require_sleep.RECORD_SWEEP_INTERVAL_MS;
112036
+ exports.RECOVERY_CROP_MAX_SIDE_PX = RECOVERY_CROP_MAX_SIDE_PX;
112037
+ exports.RECOVERY_CROP_MIN_SIDE_PX = RECOVERY_CROP_MIN_SIDE_PX;
112038
+ exports.RECOVERY_CROP_PADDING_RATIO = RECOVERY_CROP_PADDING_RATIO;
112039
+ exports.RECOVERY_EDGE_MARGIN_PX = RECOVERY_EDGE_MARGIN_PX;
112040
+ exports.RECOVERY_FUSE_CONTAINMENT = RECOVERY_FUSE_CONTAINMENT;
112041
+ exports.RECOVERY_FUSE_IOU = RECOVERY_FUSE_IOU;
112042
+ exports.RECOVERY_MAX_CROPS_PER_FRAME = RECOVERY_MAX_CROPS_PER_FRAME;
112043
+ exports.RECOVERY_MAX_REGION_FRAME_FRACTION = RECOVERY_MAX_REGION_FRAME_FRACTION;
112044
+ exports.RECOVERY_MIN_INTERVAL_MS = RECOVERY_MIN_INTERVAL_MS;
112045
+ exports.RECOVERY_MIN_UNCOVERED_FRACTION = RECOVERY_MIN_UNCOVERED_FRACTION;
112046
+ exports.RECOVERY_MOTION_MAX_AGE_MS = RECOVERY_MOTION_MAX_AGE_MS;
112047
+ exports.RECOVERY_VIEW_PX = RECOVERY_VIEW_PX;
111352
112048
  exports.REDACTED_SECRET = require_sleep.REDACTED_SECRET;
112049
+ exports.REFERENCE_COMPUTE = REFERENCE_COMPUTE;
112050
+ exports.REFERENCE_MATCH_FRACTION = REFERENCE_MATCH_FRACTION;
111353
112051
  exports.REMOVAL_RECONCILE_INTERVAL_MS = REMOVAL_RECONCILE_INTERVAL_MS;
111354
112052
  exports.REMOVED_MODEL_IDS = REMOVED_MODEL_IDS;
111355
112053
  exports.REMOVE_LEAF = require_sleep.REMOVE_LEAF;
@@ -111536,6 +112234,7 @@ exports.SECRET_META = require_sleep.SECRET_META;
111536
112234
  exports.SECRET_REF_SCHEME = require_sleep.SECRET_REF_SCHEME;
111537
112235
  exports.SENSOR_FEATURES = SENSOR_FEATURES;
111538
112236
  exports.SENSOR_MAP = SENSOR_MAP;
112237
+ exports.SERIAL_CAPACITY_CAMERA_FPS = SERIAL_CAPACITY_CAMERA_FPS;
111539
112238
  exports.SERVER_NAME_MAX_LENGTH = SERVER_NAME_MAX_LENGTH;
111540
112239
  exports.SERVER_NAME_SETTING_KEY = SERVER_NAME_SETTING_KEY;
111541
112240
  exports.SESSION_PLACEMENT_SETTING_KEY = SESSION_PLACEMENT_SETTING_KEY;
@@ -111745,8 +112444,10 @@ exports.TrackStateSchema = TrackStateSchema;
111745
112444
  exports.TrackZoneFilterSchema = TrackZoneFilterSchema;
111746
112445
  exports.TrackedDetectionSchema = TrackedDetectionSchema;
111747
112446
  exports.TrainerSourceRefSchema = TrainerSourceRefSchema;
112447
+ exports.TrainingAcceleratorRefSchema = TrainingAcceleratorRefSchema;
111748
112448
  exports.TrainingBaseModelSchema = TrainingBaseModelSchema;
111749
112449
  exports.TrainingCanarySchema = TrainingCanarySchema;
112450
+ exports.TrainingCanaryTargetSchema = TrainingCanaryTargetSchema;
111750
112451
  exports.TrainingCatalogDraftSchema = TrainingCatalogDraftSchema;
111751
112452
  exports.TrainingCorpusSelectionSchema = TrainingCorpusSelectionSchema;
111752
112453
  exports.TrainingCredentialCheckSchema = TrainingCredentialCheckSchema;
@@ -111889,6 +112590,7 @@ exports.addonWidgetsSourceCapability = addonWidgetsSourceCapability;
111889
112590
  exports.addonsCapability = addonsCapability;
111890
112591
  exports.adminUiCapability = require_sleep.adminUiCapability;
111891
112592
  exports.advertisedHubOrigins = advertisedHubOrigins;
112593
+ exports.aggregateThroughputFactor = aggregateThroughputFactor;
111892
112594
  exports.airQualitySensorCapability = airQualitySensorCapability;
111893
112595
  exports.alarmDeviceBlocksArming = alarmDeviceBlocksArming;
111894
112596
  exports.alarmDeviceSecureOnArm = alarmDeviceSecureOnArm;
@@ -111906,6 +112608,7 @@ exports.ambientLightSensorCapability = ambientLightSensorCapability;
111906
112608
  exports.annotateClipsWithEventLabels = annotateClipsWithEventLabels;
111907
112609
  exports.appFlowLocation = appFlowLocation;
111908
112610
  exports.applyBlockingEdit = applyBlockingEdit;
112611
+ exports.applyCropClassFloors = applyCropClassFloors;
111909
112612
  exports.applyModeDelaysEdits = applyModeDelaysEdits;
111910
112613
  exports.applyModeDeviceListEdit = applyModeDeviceListEdit;
111911
112614
  exports.applyModelBoundSettings = applyModelBoundSettings;
@@ -111977,6 +112680,7 @@ exports.cameraCredentialsCapability = cameraCredentialsCapability;
111977
112680
  exports.cameraGridLayoutCapability = cameraGridLayoutCapability;
111978
112681
  exports.cameraOwnsMotionViaSensor = cameraOwnsMotionViaSensor;
111979
112682
  exports.cameraPipelineConfigCapability = cameraPipelineConfigCapability;
112683
+ exports.cameraScopedStepSettings = cameraScopedStepSettings;
111980
112684
  exports.cameraSensorRefKey = cameraSensorRefKey;
111981
112685
  exports.cameraStreamsCapability = cameraStreamsCapability;
111982
112686
  exports.canConvertUnit = canConvertUnit;
@@ -112040,6 +112744,7 @@ exports.compositionSliceKey = require_composition.compositionSliceKey;
112040
112744
  exports.compositionSourceKey = require_composition.compositionSourceKey;
112041
112745
  exports.compositionSourceRefs = compositionSourceRefs;
112042
112746
  exports.computeCapacityShares = computeCapacityShares;
112747
+ exports.computeScores = computeScores;
112043
112748
  exports.conditionDepth = conditionDepth;
112044
112749
  exports.conditionExclusionReason = conditionExclusionReason;
112045
112750
  exports.conditionVisibleForKind = conditionVisibleForKind;
@@ -112062,6 +112767,7 @@ exports.coreBlockSourceKind = coreBlockSourceKind;
112062
112767
  exports.coreBlocksCapability = coreBlocksCapability;
112063
112768
  exports.cosineSimilarity = cosineSimilarity;
112064
112769
  exports.countConditionLeaves = countConditionLeaves;
112770
+ exports.countRecoverySkips = countRecoverySkips;
112065
112771
  exports.coverCapability = coverCapability;
112066
112772
  exports.createAlarmProfile = createAlarmProfile;
112067
112773
  exports.createDeviceExtensionProvider = createDeviceExtensionProvider;
@@ -112232,6 +112938,7 @@ exports.formatSecretScope = require_sleep.formatSecretScope;
112232
112938
  exports.freshResults = freshResults;
112233
112939
  exports.frigateDefaultClassFloors = frigateDefaultClassFloors;
112234
112940
  exports.frigateLabelFields = frigateLabelFields;
112941
+ exports.fuseRecoveredDetections = fuseRecoveredDetections;
112235
112942
  exports.gasCapability = gasCapability;
112236
112943
  exports.gbToBytes = gbToBytes;
112237
112944
  exports.generateAutomationBlock = generateAutomationBlock;
@@ -112314,6 +113021,7 @@ exports.isKnownTemplateVar = isKnownTemplateVar;
112314
113021
  exports.isLabelSupplyStepId = isLabelSupplyStepId;
112315
113022
  exports.isLocationEnabled = isLocationEnabled;
112316
113023
  exports.isMediaOwnerType = isMediaOwnerType;
113024
+ exports.isModelScopedStepSettingKey = isModelScopedStepSettingKey;
112317
113025
  exports.isNode = isNode;
112318
113026
  exports.isOauthLandingReason = isOauthLandingReason;
112319
113027
  exports.isObjectInput = isObjectInput;
@@ -112413,6 +113121,7 @@ exports.modelBindingKey = modelBindingKey;
112413
113121
  exports.modelConvertCapability = modelConvertCapability;
112414
113122
  exports.modelDistributorCapability = modelDistributorCapability;
112415
113123
  exports.modelFormatForRuntime = modelFormatForRuntime;
113124
+ exports.modelScopedStepSettingKeys = modelScopedStepSettingKeys;
112416
113125
  exports.motionCapability = motionCapability;
112417
113126
  exports.motionDetectionCapability = motionDetectionCapability;
112418
113127
  exports.motionSourceLabel = motionSourceLabel;
@@ -112440,6 +113149,7 @@ exports.normalizeOrigin = normalizeOrigin;
112440
113149
  exports.normalizeSecretOwner = require_sleep.normalizeSecretOwner;
112441
113150
  exports.normalizeTokenScopes = normalizeTokenScopes;
112442
113151
  exports.normalizeUnit = normalizeUnit;
113152
+ exports.normalizedArea = normalizedArea;
112443
113153
  exports.notificationOutputCapability = notificationOutputCapability;
112444
113154
  exports.notificationRulesCapability = notificationRulesCapability;
112445
113155
  exports.notifierCapability = notifierCapability;
@@ -112457,6 +113167,7 @@ exports.osdCapability = osdCapability;
112457
113167
  exports.osdManagerCapability = osdManagerCapability;
112458
113168
  exports.overlayClusterStepSettings = overlayClusterStepSettings;
112459
113169
  exports.packagePickerItems = packagePickerItems;
113170
+ exports.parseAcceleratorFactsJson = parseAcceleratorFactsJson;
112460
113171
  exports.parseAdminBase = parseAdminBase;
112461
113172
  exports.parseAlertEpisodePayload = parseAlertEpisodePayload;
112462
113173
  exports.parseAppAuthCallback = parseAppAuthCallback;
@@ -112507,6 +113218,9 @@ exports.pipelineRunnerCapability = pipelineRunnerCapability;
112507
113218
  exports.planComposition = planComposition;
112508
113219
  exports.planCompositionEvents = planCompositionEvents;
112509
113220
  exports.planItems = planItems;
113221
+ exports.planMotionCropRegions = planMotionCropRegions;
113222
+ exports.planMotionCrops = planMotionCrops;
113223
+ exports.planRecoveryCrops = planRecoveryCrops;
112510
113224
  exports.plateGalleryCapability = plateGalleryCapability;
112511
113225
  exports.platformProbeCapability = platformProbeCapability;
112512
113226
  exports.poolMemoryThreshold = poolMemoryThreshold;
@@ -112535,6 +113249,8 @@ exports.readHubMotionStatus = readHubMotionStatus;
112535
113249
  exports.readMotionTriggeredBy = readMotionTriggeredBy;
112536
113250
  exports.readNativeLeaseOverride = readNativeLeaseOverride;
112537
113251
  exports.readNodePin = require_sleep.readNodePin;
113252
+ exports.readRecoveryRecipe = readRecoveryRecipe;
113253
+ exports.readRecoveryRecipeForSteps = readRecoveryRecipeForSteps;
112538
113254
  exports.readTimelapseGeneratedAt = readTimelapseGeneratedAt;
112539
113255
  exports.readUseRootModel = readUseRootModel;
112540
113256
  exports.readinessKey = require_sleep.readinessKey;
@@ -112556,9 +113272,11 @@ exports.recordsErrorFromMessage = require_sleep.recordsErrorFromMessage;
112556
113272
  exports.rectsToCells = rectsToCells;
112557
113273
  exports.reducePoints = reducePoints;
112558
113274
  exports.registerKnownSecret = require_sleep.registerKnownSecret;
113275
+ exports.remapCropDetections = remapCropDetections;
112559
113276
  exports.renameAlarmProfile = renameAlarmProfile;
112560
113277
  exports.renderVocabPhrase = renderVocabPhrase;
112561
113278
  exports.replacedFieldKeys = replacedFieldKeys;
113279
+ exports.reportIsScalable = reportIsScalable;
112562
113280
  exports.requestMountPrefix = requestMountPrefix;
112563
113281
  exports.requiredTopLevelKeys = requiredTopLevelKeys;
112564
113282
  exports.requiresPython = requiresPython;
@@ -112586,6 +113304,7 @@ exports.resolveHubAddressRoles = resolveHubAddressRoles;
112586
113304
  exports.resolveHydratedFieldValue = require_sleep.resolveHydratedFieldValue;
112587
113305
  exports.resolveIconKey = resolveIconKey;
112588
113306
  exports.resolveInternalAddress = resolveInternalAddress;
113307
+ exports.resolveLatencyColumn = resolveLatencyColumn;
112589
113308
  exports.resolveLocationMode = resolveLocationMode;
112590
113309
  exports.resolveMethodAuth = resolveMethodAuth;
112591
113310
  exports.resolveMethodRole = resolveMethodRole;
@@ -112652,6 +113371,7 @@ exports.secretTreeFromSchema = require_sleep.secretTreeFromSchema;
112652
113371
  exports.secureActuationFor = secureActuationFor;
112653
113372
  exports.selectAppConnectionEndpoints = selectAppConnectionEndpoints;
112654
113373
  exports.selectAssignedProfileSlots = require_sleep.selectAssignedProfileSlots;
113374
+ exports.selectRecoveryStep = selectRecoveryStep;
112655
113375
  exports.sendTimesForEditor = sendTimesForEditor;
112656
113376
  exports.sendTimesOfSchedule = sendTimesOfSchedule;
112657
113377
  exports.sensorRaisedOf = sensorRaisedOf;
@@ -112678,6 +113398,7 @@ exports.smokeCapability = smokeCapability;
112678
113398
  exports.smtpProviderCapability = smtpProviderCapability;
112679
113399
  exports.snapshotCapability = snapshotCapability;
112680
113400
  exports.splitTranslationRef = splitTranslationRef;
113401
+ exports.squareAround = squareAround;
112681
113402
  exports.ssoBridgeCapability = ssoBridgeCapability;
112682
113403
  exports.startMoverUnderClearance = startMoverUnderClearance;
112683
113404
  exports.startReachabilityPoll = startReachabilityPoll;