@camstack/addon-post-analysis 1.2.660 → 1.2.662
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/{clip-model-registry-DAKPBZfa.js → clip-model-registry-C3N1NpST.js} +274 -36
- package/dist/{clip-model-registry-D9BbIQ-h.mjs → clip-model-registry-ELaSS9jq.mjs} +269 -37
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/_stub.js +2 -2
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CvrKdp_8.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BvOlyiL9.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D4zkNcgf.mjs +26 -0
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-vaatUWY6.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit--CbMNXQy.mjs → hostInit-BjZchMMh.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +506 -172
- package/dist/pipeline-analytics/index.mjs +506 -172
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BDKliSzv.mjs +0 -26
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BwWOFFOc.mjs +0 -26
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_clip_model_registry = require("../clip-model-registry-
|
|
5
|
+
const require_clip_model_registry = require("../clip-model-registry-C3N1NpST.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_clip_model_registry.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -8888,11 +8888,25 @@ function cleanSourcePreference(ownerKind) {
|
|
|
8888
8888
|
* The ONE placeholder grammar. `renderTemplate` substitutes what it matches and
|
|
8889
8889
|
* {@link templatePlaceholders} lists it; a second regex here would be a legend
|
|
8890
8890
|
* that disagrees with the renderer about what a variable is, which is the
|
|
8891
|
-
* failure mode where an editor advertises `{{
|
|
8891
|
+
* failure mode where an editor advertises `{{ device }}` as unsupported and the
|
|
8892
8892
|
* notification prints the camera anyway.
|
|
8893
8893
|
*/
|
|
8894
8894
|
var PLACEHOLDER_PATTERN = /\{\{\s*(\w+)\s*\}\}/g;
|
|
8895
8895
|
/**
|
|
8896
|
+
* Names that were renamed and still resolve at render time, mapped to the name
|
|
8897
|
+
* that replaced them. `{{camera}}` became `{{device}}` (a rule on an appliance
|
|
8898
|
+
* names that device, not a camera); a template written by an older client still
|
|
8899
|
+
* prints the right name. An alias is NOT declared in the catalog and no picker
|
|
8900
|
+
* offers it: the stored rules are rewritten by the `pipeline-analytics` template
|
|
8901
|
+
* migration, and this table only covers a client that has not updated yet.
|
|
8902
|
+
*/
|
|
8903
|
+
var NC_DEPRECATED_TEMPLATE_VARS = { camera: "device" };
|
|
8904
|
+
/** The deprecated names a template still uses, in order of first appearance. */
|
|
8905
|
+
function deprecatedPlaceholders(template) {
|
|
8906
|
+
if (template === void 0) return [];
|
|
8907
|
+
return templatePlaceholders(template).filter((name) => name in NC_DEPRECATED_TEMPLATE_VARS);
|
|
8908
|
+
}
|
|
8909
|
+
/**
|
|
8896
8910
|
* The variable names a template names, de-duplicated, in order of first
|
|
8897
8911
|
* appearance.
|
|
8898
8912
|
*
|
|
@@ -8918,19 +8932,35 @@ function templatePlaceholders(template) {
|
|
|
8918
8932
|
*/
|
|
8919
8933
|
function renderTemplate(template, vars) {
|
|
8920
8934
|
if (template === void 0 || template.trim().length === 0) return null;
|
|
8921
|
-
return template.replace(PLACEHOLDER_PATTERN, (_m, name) =>
|
|
8935
|
+
return template.replace(PLACEHOLDER_PATTERN, (_m, name) => {
|
|
8936
|
+
const current = vars[name];
|
|
8937
|
+
if (current !== void 0) return current;
|
|
8938
|
+
const renamed = NC_DEPRECATED_TEMPLATE_VARS[name];
|
|
8939
|
+
return (renamed !== void 0 ? vars[renamed] : void 0) ?? "";
|
|
8940
|
+
});
|
|
8922
8941
|
}
|
|
8923
8942
|
//#endregion
|
|
8924
8943
|
//#region src/notification-center/texts/en.json
|
|
8925
8944
|
var en_default = {
|
|
8926
8945
|
"detection.title": "{{rule}}",
|
|
8927
|
-
"detection.body": "{{subject}} on {{
|
|
8928
|
-
"detection.body.trackEnd": "{{subject}} on {{
|
|
8946
|
+
"detection.body": "{{subject}} on {{device}}{{inZones}}",
|
|
8947
|
+
"detection.body.trackEnd": "{{subject}} on {{device}}{{inZones}} — visit ended",
|
|
8929
8948
|
"detection.subject": "{{classNoun}}",
|
|
8930
8949
|
"detection.subject.labelled": "{{classNoun}} ({{label}})",
|
|
8931
8950
|
"detection.subject.identity": "{{label}}",
|
|
8932
8951
|
"detection.subject.plate": "{{classNoun}} {{label}}",
|
|
8933
8952
|
"detection.inZones": " in {{zones}}",
|
|
8953
|
+
"appliance.finished.title": "{{device}} finished",
|
|
8954
|
+
"appliance.finished.body": "{{device}} finished{{afterDuration}}{{usedEnergy}}",
|
|
8955
|
+
"appliance.finished.duration": " after {{duration}}",
|
|
8956
|
+
"appliance.finished.energy": " and used {{energy}}",
|
|
8957
|
+
"appliance.duration.minutes": {
|
|
8958
|
+
"one": "{{minutes}} minute",
|
|
8959
|
+
"other": "{{minutes}} minutes"
|
|
8960
|
+
},
|
|
8961
|
+
"appliance.duration.hours": "{{hours}} h",
|
|
8962
|
+
"appliance.duration.hoursMinutes": "{{hours}} h {{minutes}} min",
|
|
8963
|
+
"appliance.energy.value": "{{value}} kWh",
|
|
8934
8964
|
"class.person": {
|
|
8935
8965
|
"one": "person",
|
|
8936
8966
|
"other": "people"
|
|
@@ -8997,11 +9027,11 @@ var en_default = {
|
|
|
8997
9027
|
"other": "{{count}} other {{classNoun}}"
|
|
8998
9028
|
},
|
|
8999
9029
|
"package.delivered.title": "Parcel delivered",
|
|
9000
|
-
"package.delivered.body": "Delivered at {{
|
|
9030
|
+
"package.delivered.body": "Delivered at {{device}}{{inZones}}",
|
|
9001
9031
|
"package.delivered.possible.title": "Possible parcel",
|
|
9002
|
-
"package.delivered.possible.body": "Possible parcel at {{
|
|
9032
|
+
"package.delivered.possible.body": "Possible parcel at {{device}}{{inZones}}",
|
|
9003
9033
|
"package.picked-up.title": "Parcel collected",
|
|
9004
|
-
"package.picked-up.body": "Collected from {{
|
|
9034
|
+
"package.picked-up.body": "Collected from {{device}}{{inZones}}",
|
|
9005
9035
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
9006
9036
|
"occupancy.op.occupied": "occupied",
|
|
9007
9037
|
"occupancy.op.free": "free",
|
|
@@ -9137,7 +9167,7 @@ var en_default = {
|
|
|
9137
9167
|
"system.camera-enabled.title": "Device switched on",
|
|
9138
9168
|
"system.camera-enabled.body": "Device {{deviceName}} was switched back on",
|
|
9139
9169
|
"test.prefix": "[TEST] {{body}}",
|
|
9140
|
-
"timelapse.title": "Timelapse — {{
|
|
9170
|
+
"timelapse.title": "Timelapse — {{device}}",
|
|
9141
9171
|
"timelapse.body.window": "{{rule}} — {{from}}→{{to}}",
|
|
9142
9172
|
"timelapse.body.tooManyEvents": "too many events for dense sampling: video at a uniform cadence",
|
|
9143
9173
|
"timelapse.body.denseRanges": {
|
|
@@ -9151,7 +9181,7 @@ var en_default = {
|
|
|
9151
9181
|
},
|
|
9152
9182
|
"snooze.digest.header": "{{scope}} was snoozed, {{from}}–{{to}}.",
|
|
9153
9183
|
"snooze.digest.header.global": "{{scope}} was snoozed for everyone, {{from}}–{{to}}.",
|
|
9154
|
-
"snooze.digest.line": "• {{rule}} on {{
|
|
9184
|
+
"snooze.digest.line": "• {{rule}} on {{device}} — {{count}}",
|
|
9155
9185
|
"snooze.scope.all": "Everything",
|
|
9156
9186
|
"snooze.scope.anyDevice": "A camera",
|
|
9157
9187
|
"snooze.scope.anyRule": "A rule",
|
|
@@ -9330,13 +9360,24 @@ var en_default = {
|
|
|
9330
9360
|
//#region src/notification-center/texts/it.json
|
|
9331
9361
|
var it_default = {
|
|
9332
9362
|
"detection.title": "{{rule}}",
|
|
9333
|
-
"detection.body": "{{subject}} su {{
|
|
9334
|
-
"detection.body.trackEnd": "{{subject}} su {{
|
|
9363
|
+
"detection.body": "{{subject}} su {{device}}{{inZones}}",
|
|
9364
|
+
"detection.body.trackEnd": "{{subject}} su {{device}}{{inZones}} — visita terminata",
|
|
9335
9365
|
"detection.subject": "{{classNoun}}",
|
|
9336
9366
|
"detection.subject.labelled": "{{classNoun}} ({{label}})",
|
|
9337
9367
|
"detection.subject.identity": "{{label}}",
|
|
9338
9368
|
"detection.subject.plate": "{{classNoun}} {{label}}",
|
|
9339
9369
|
"detection.inZones": " in {{zones}}",
|
|
9370
|
+
"appliance.finished.title": "{{device}} ha terminato",
|
|
9371
|
+
"appliance.finished.body": "{{device}} ha terminato{{afterDuration}}{{usedEnergy}}",
|
|
9372
|
+
"appliance.finished.duration": " dopo {{duration}}",
|
|
9373
|
+
"appliance.finished.energy": " e ha consumato {{energy}}",
|
|
9374
|
+
"appliance.duration.minutes": {
|
|
9375
|
+
"one": "{{minutes}} minuto",
|
|
9376
|
+
"other": "{{minutes}} minuti"
|
|
9377
|
+
},
|
|
9378
|
+
"appliance.duration.hours": "{{hours}} h",
|
|
9379
|
+
"appliance.duration.hoursMinutes": "{{hours}} h {{minutes}} min",
|
|
9380
|
+
"appliance.energy.value": "{{value}} kWh",
|
|
9340
9381
|
"class.person": {
|
|
9341
9382
|
"one": "persona",
|
|
9342
9383
|
"other": "persone"
|
|
@@ -9403,11 +9444,11 @@ var it_default = {
|
|
|
9403
9444
|
"other": "{{count}} altri {{classNoun}}"
|
|
9404
9445
|
},
|
|
9405
9446
|
"package.delivered.title": "Pacco consegnato",
|
|
9406
|
-
"package.delivered.body": "Consegnato su {{
|
|
9447
|
+
"package.delivered.body": "Consegnato su {{device}}{{inZones}}",
|
|
9407
9448
|
"package.delivered.possible.title": "Possibile pacco",
|
|
9408
|
-
"package.delivered.possible.body": "Possibile pacco su {{
|
|
9449
|
+
"package.delivered.possible.body": "Possibile pacco su {{device}}{{inZones}}",
|
|
9409
9450
|
"package.picked-up.title": "Pacco ritirato",
|
|
9410
|
-
"package.picked-up.body": "Ritirato da {{
|
|
9451
|
+
"package.picked-up.body": "Ritirato da {{device}}{{inZones}}",
|
|
9411
9452
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
9412
9453
|
"occupancy.op.occupied": "occupata",
|
|
9413
9454
|
"occupancy.op.free": "libera",
|
|
@@ -9543,7 +9584,7 @@ var it_default = {
|
|
|
9543
9584
|
"system.camera-enabled.title": "Dispositivo riacceso",
|
|
9544
9585
|
"system.camera-enabled.body": "Il dispositivo {{deviceName}} è stato riacceso",
|
|
9545
9586
|
"test.prefix": "[PROVA] {{body}}",
|
|
9546
|
-
"timelapse.title": "Sequenza — {{
|
|
9587
|
+
"timelapse.title": "Sequenza — {{device}}",
|
|
9547
9588
|
"timelapse.body.window": "{{rule}} — {{from}}→{{to}}",
|
|
9548
9589
|
"timelapse.body.tooManyEvents": "troppi eventi per il campionamento denso: video a cadenza uniforme",
|
|
9549
9590
|
"timelapse.body.denseRanges": {
|
|
@@ -9557,7 +9598,7 @@ var it_default = {
|
|
|
9557
9598
|
},
|
|
9558
9599
|
"snooze.digest.header": "{{scope}} in pausa, {{from}}–{{to}}.",
|
|
9559
9600
|
"snooze.digest.header.global": "{{scope}} in pausa per tutti, {{from}}–{{to}}.",
|
|
9560
|
-
"snooze.digest.line": "• {{rule}} su {{
|
|
9601
|
+
"snooze.digest.line": "• {{rule}} su {{device}} — {{count}}",
|
|
9561
9602
|
"snooze.scope.all": "Tutto",
|
|
9562
9603
|
"snooze.scope.anyDevice": "Una camera",
|
|
9563
9604
|
"snooze.scope.anyRule": "Una regola",
|
|
@@ -9781,7 +9822,7 @@ var it_default = {
|
|
|
9781
9822
|
* as plain strings. {@link renderTemplate} never re-scans a replacement, so
|
|
9782
9823
|
* there is exactly one level of composition and it cannot grow: a fragment that
|
|
9783
9824
|
* names another composite renders it empty, and a value that happens to look
|
|
9784
|
-
* like `{{
|
|
9825
|
+
* like `{{device}}` is printed, not substituted.
|
|
9785
9826
|
*/
|
|
9786
9827
|
/** The languages the hub ships texts for. */
|
|
9787
9828
|
var NC_LANGUAGES = ["en", "it"];
|
|
@@ -56230,6 +56271,258 @@ var ZoneCatalogueCache = class {
|
|
|
56230
56271
|
}
|
|
56231
56272
|
};
|
|
56232
56273
|
//#endregion
|
|
56274
|
+
//#region src/notification-center/record-kinds.ts
|
|
56275
|
+
/**
|
|
56276
|
+
* Record kinds owned by the pipeline-analytics addon (records plan phase 2,
|
|
56277
|
+
* steps 18-24): the notification-center config tables.
|
|
56278
|
+
*
|
|
56279
|
+
* The notification-center module is hosted inside the pipeline-analytics addon.
|
|
56280
|
+
* That addon declares no `execution.group`, so the UDS owner stamp is the addon
|
|
56281
|
+
* id: `pipeline-analytics`. Declared hub-only (agents have no `ctx.records`).
|
|
56282
|
+
* One deploy covers all 7 kinds.
|
|
56283
|
+
*/
|
|
56284
|
+
/**
|
|
56285
|
+
* The hub language and operator-authored per-key overrides.
|
|
56286
|
+
* One row, id `texts`. Losing it degrades to the shipped English catalog.
|
|
56287
|
+
*/
|
|
56288
|
+
var TextsBodySchema = require_clip_model_registry.object({
|
|
56289
|
+
language: NcLanguageSchema,
|
|
56290
|
+
overrides: require_clip_model_registry.partialRecord(NcLanguageSchema, require_clip_model_registry.record(require_clip_model_registry.string(), require_clip_model_registry.string()))
|
|
56291
|
+
});
|
|
56292
|
+
/**
|
|
56293
|
+
* A shared action sequence (without id, which is the record key).
|
|
56294
|
+
* Extends the wire shape with `createdAt` / `updatedAt` (stored by the legacy table).
|
|
56295
|
+
*/
|
|
56296
|
+
var SequenceBodySchema = require_clip_model_registry.NcSharedSequenceSchema.omit({ id: true }).extend({
|
|
56297
|
+
createdAt: require_clip_model_registry.number(),
|
|
56298
|
+
updatedAt: require_clip_model_registry.number()
|
|
56299
|
+
});
|
|
56300
|
+
/**
|
|
56301
|
+
* The AI section of one ordinary rule. Record id = the rule id.
|
|
56302
|
+
*/
|
|
56303
|
+
var RuleAiBodySchema = require_clip_model_registry.object({
|
|
56304
|
+
ai: NcRuleAiSchema,
|
|
56305
|
+
updatedAt: require_clip_model_registry.number()
|
|
56306
|
+
});
|
|
56307
|
+
/** A calendar-events rule (without `id`). */
|
|
56308
|
+
var CalendarEventsRuleBodySchema = NcCalendarEventsRuleSchema.omit({ id: true });
|
|
56309
|
+
/** A multi-camera digest rule (without `id`). */
|
|
56310
|
+
var SummaryRuleBodySchema = NcSummaryRuleSchema.omit({ id: true });
|
|
56311
|
+
/** A timelapse rule (without `id`). */
|
|
56312
|
+
var TimelapseRuleBodySchema = require_clip_model_registry.TimelapseRuleSchema.omit({ id: true });
|
|
56313
|
+
/** A notification rule (without `id`). */
|
|
56314
|
+
var RuleBodySchema = require_clip_model_registry.NcRuleSchema.omit({ id: true });
|
|
56315
|
+
var ncRecordKinds = require_clip_model_registry.defineRecordKinds({
|
|
56316
|
+
/**
|
|
56317
|
+
* The hub language + per-key text overrides. One row (id `texts`).
|
|
56318
|
+
*/
|
|
56319
|
+
texts: {
|
|
56320
|
+
schema: TextsBodySchema,
|
|
56321
|
+
class: "config",
|
|
56322
|
+
writeTrigger: "an operator changes the hub language (setLanguage) or replaces the per-key override set (setOverrides)",
|
|
56323
|
+
device: "none",
|
|
56324
|
+
at: "none",
|
|
56325
|
+
retention: { none: "one row (id `texts`); losing it costs the chosen language and household wording, never a notification — every read degrades to the shipped English catalog" }
|
|
56326
|
+
},
|
|
56327
|
+
/**
|
|
56328
|
+
* Shared action sequences — named, ordered runs of steps with their own throttle.
|
|
56329
|
+
* One row per sequence, id = sequence UUID.
|
|
56330
|
+
*/
|
|
56331
|
+
sequences: {
|
|
56332
|
+
schema: SequenceBodySchema,
|
|
56333
|
+
class: "config",
|
|
56334
|
+
writeTrigger: "an admin creates/edits/deletes a shared sequence via notificationRules.createSequence / updateSequence / deleteSequence, or the one-shot boot migration that materialises legacy inline sequences",
|
|
56335
|
+
device: "none",
|
|
56336
|
+
at: "none",
|
|
56337
|
+
retention: { none: "a row goes only when an admin deletes it; bounded by the sequence set a human is willing to author" }
|
|
56338
|
+
},
|
|
56339
|
+
/**
|
|
56340
|
+
* Per-rule AI section: whether and how a rule narrates itself.
|
|
56341
|
+
* Record id = the rule id it belongs to.
|
|
56342
|
+
*/
|
|
56343
|
+
"rule-ai": {
|
|
56344
|
+
schema: RuleAiBodySchema,
|
|
56345
|
+
class: "config",
|
|
56346
|
+
writeTrigger: "an operator switches the AI section of a notification rule on or off, or edits its prompt / word cap / language",
|
|
56347
|
+
device: "none",
|
|
56348
|
+
at: "none",
|
|
56349
|
+
retention: { none: "a row goes when the operator clears the section or deletes the rule; bounded by the rule set a human is willing to author" }
|
|
56350
|
+
},
|
|
56351
|
+
/**
|
|
56352
|
+
* Calendar-events rules. One row per rule, id = rule UUID.
|
|
56353
|
+
*/
|
|
56354
|
+
"calendar-events-rules": {
|
|
56355
|
+
schema: CalendarEventsRuleBodySchema,
|
|
56356
|
+
class: "config",
|
|
56357
|
+
writeTrigger: "an operator creates or edits a calendar-events rule, or the producer stamps a delivered window via markGenerated",
|
|
56358
|
+
device: "none",
|
|
56359
|
+
at: "none",
|
|
56360
|
+
indexes: [{
|
|
56361
|
+
name: "enabled",
|
|
56362
|
+
fields: ["enabled"]
|
|
56363
|
+
}],
|
|
56364
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
56365
|
+
},
|
|
56366
|
+
/**
|
|
56367
|
+
* Multi-camera digest rules. One row per rule, id = rule UUID.
|
|
56368
|
+
*/
|
|
56369
|
+
"summary-rules": {
|
|
56370
|
+
schema: SummaryRuleBodySchema,
|
|
56371
|
+
class: "config",
|
|
56372
|
+
writeTrigger: "an operator creates or edits a digest rule, or the producer stamps a delivered window via markGenerated",
|
|
56373
|
+
device: "none",
|
|
56374
|
+
at: "none",
|
|
56375
|
+
indexes: [{
|
|
56376
|
+
name: "enabled",
|
|
56377
|
+
fields: ["enabled"]
|
|
56378
|
+
}],
|
|
56379
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
56380
|
+
},
|
|
56381
|
+
/**
|
|
56382
|
+
* Timelapse rules. One row per rule, id = rule UUID.
|
|
56383
|
+
* Three rows on prod (2026-10-18).
|
|
56384
|
+
*/
|
|
56385
|
+
"timelapse-rules": {
|
|
56386
|
+
schema: TimelapseRuleBodySchema,
|
|
56387
|
+
class: "config",
|
|
56388
|
+
writeTrigger: "an operator creates or edits a timelapse rule, or the scheduler stamps a successful per-camera generation via markGenerated",
|
|
56389
|
+
device: "none",
|
|
56390
|
+
at: "none",
|
|
56391
|
+
indexes: [{
|
|
56392
|
+
name: "enabled",
|
|
56393
|
+
fields: ["enabled"]
|
|
56394
|
+
}],
|
|
56395
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
56396
|
+
},
|
|
56397
|
+
/**
|
|
56398
|
+
* Notification rules — the main rule set. One row per rule, id = rule UUID.
|
|
56399
|
+
* 18 rows on prod (2026-10-18); most coupled kind in this owner.
|
|
56400
|
+
*/
|
|
56401
|
+
rules: {
|
|
56402
|
+
schema: RuleBodySchema,
|
|
56403
|
+
class: "config",
|
|
56404
|
+
writeTrigger: "an operator creates, edits, enables/disables a rule or opts a target out of it; nothing writes on the evaluation path",
|
|
56405
|
+
device: "none",
|
|
56406
|
+
at: "none",
|
|
56407
|
+
indexes: [{
|
|
56408
|
+
name: "delivery",
|
|
56409
|
+
fields: ["delivery"]
|
|
56410
|
+
}],
|
|
56411
|
+
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
56412
|
+
}
|
|
56413
|
+
});
|
|
56414
|
+
//#endregion
|
|
56415
|
+
//#region src/migrations/device-template-var.ts
|
|
56416
|
+
/** `{{camera}}` and `{{ camera }}`: the whitespace the renderer allows is kept as written. */
|
|
56417
|
+
var OLD_VARIABLE = /\{\{(\s*)camera(\s*)\}\}/g;
|
|
56418
|
+
/** The template text with the variable renamed; the same string when it names none. */
|
|
56419
|
+
function renameDeviceVariable(text) {
|
|
56420
|
+
return text.replace(OLD_VARIABLE, "{{$1device$2}}");
|
|
56421
|
+
}
|
|
56422
|
+
/** A `{ title, body }` template with both fields renamed; `null` when neither changed. */
|
|
56423
|
+
function renamedTemplate(template) {
|
|
56424
|
+
if (template === void 0) return null;
|
|
56425
|
+
const title = template.title === void 0 ? void 0 : renameDeviceVariable(template.title);
|
|
56426
|
+
const body = template.body === void 0 ? void 0 : renameDeviceVariable(template.body);
|
|
56427
|
+
if (title === template.title && body === template.body) return null;
|
|
56428
|
+
return {
|
|
56429
|
+
...template,
|
|
56430
|
+
...title !== void 0 ? { title } : {},
|
|
56431
|
+
...body !== void 0 ? { body } : {}
|
|
56432
|
+
};
|
|
56433
|
+
}
|
|
56434
|
+
function renamedRule(data) {
|
|
56435
|
+
const template = renamedTemplate(data.template);
|
|
56436
|
+
return template === null ? null : {
|
|
56437
|
+
...data,
|
|
56438
|
+
template
|
|
56439
|
+
};
|
|
56440
|
+
}
|
|
56441
|
+
function renamedTimelapseRule(data) {
|
|
56442
|
+
const template = renamedTemplate(data.template);
|
|
56443
|
+
const previewText = data.previewText === void 0 ? void 0 : renameDeviceVariable(data.previewText);
|
|
56444
|
+
const previewChanged = previewText !== data.previewText;
|
|
56445
|
+
if (template === null && !previewChanged) return null;
|
|
56446
|
+
return {
|
|
56447
|
+
...data,
|
|
56448
|
+
...template !== null ? { template } : {},
|
|
56449
|
+
...previewChanged && previewText !== void 0 ? { previewText } : {}
|
|
56450
|
+
};
|
|
56451
|
+
}
|
|
56452
|
+
function renamedTexts(data) {
|
|
56453
|
+
let changed = false;
|
|
56454
|
+
const overrides = Object.fromEntries(Object.entries(data.overrides).map(([language, table]) => [language, Object.fromEntries(Object.entries(table).map(([key, text]) => {
|
|
56455
|
+
const next = renameDeviceVariable(text);
|
|
56456
|
+
if (next !== text) changed = true;
|
|
56457
|
+
return [key, next];
|
|
56458
|
+
}))]));
|
|
56459
|
+
return changed ? {
|
|
56460
|
+
...data,
|
|
56461
|
+
overrides
|
|
56462
|
+
} : null;
|
|
56463
|
+
}
|
|
56464
|
+
var PAGE$1 = 500;
|
|
56465
|
+
/** Every row of a kind, page by page. */
|
|
56466
|
+
async function allRows(kind) {
|
|
56467
|
+
const rows = [];
|
|
56468
|
+
let cursor;
|
|
56469
|
+
do {
|
|
56470
|
+
const page = await kind.query({
|
|
56471
|
+
limit: PAGE$1,
|
|
56472
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
56473
|
+
});
|
|
56474
|
+
rows.push(...page.records);
|
|
56475
|
+
cursor = page.nextCursor ?? void 0;
|
|
56476
|
+
} while (cursor !== void 0);
|
|
56477
|
+
return rows;
|
|
56478
|
+
}
|
|
56479
|
+
/** Rewrite the rows of one kind that carry the old name; returns how many were written. */
|
|
56480
|
+
async function rewriteKind(kind, rewrite) {
|
|
56481
|
+
let written = 0;
|
|
56482
|
+
for (const row of await allRows(kind)) {
|
|
56483
|
+
const next = rewrite(row.data);
|
|
56484
|
+
if (next === null) continue;
|
|
56485
|
+
await kind.put({
|
|
56486
|
+
id: row.id,
|
|
56487
|
+
data: next
|
|
56488
|
+
}, { ifRev: row.rev });
|
|
56489
|
+
written += 1;
|
|
56490
|
+
}
|
|
56491
|
+
return written;
|
|
56492
|
+
}
|
|
56493
|
+
async function migrateDeviceTemplateVariable(ports) {
|
|
56494
|
+
const rules = await rewriteKind(ports.rules, renamedRule);
|
|
56495
|
+
const timelapseRules = await rewriteKind(ports.timelapseRules, renamedTimelapseRule);
|
|
56496
|
+
const texts = await rewriteKind(ports.texts, renamedTexts);
|
|
56497
|
+
return {
|
|
56498
|
+
outcome: rules + timelapseRules + texts === 0 ? "already-done" : "done",
|
|
56499
|
+
summary: {
|
|
56500
|
+
rules,
|
|
56501
|
+
timelapseRules,
|
|
56502
|
+
texts
|
|
56503
|
+
}
|
|
56504
|
+
};
|
|
56505
|
+
}
|
|
56506
|
+
//#endregion
|
|
56507
|
+
//#region src/migrations/index.ts
|
|
56508
|
+
/** The `pipeline-analytics` catalogue (D987): offered to the engine at the hub's boot. */
|
|
56509
|
+
var ADDON_MIGRATIONS = [{
|
|
56510
|
+
meta: {
|
|
56511
|
+
id: "pipeline-analytics/2026-11-01-template-camera-to-device",
|
|
56512
|
+
owner: "pipeline-analytics",
|
|
56513
|
+
level: "addon-state",
|
|
56514
|
+
seq: 1,
|
|
56515
|
+
dependsOn: [],
|
|
56516
|
+
store: "settings",
|
|
56517
|
+
scope: "cluster",
|
|
56518
|
+
onFailure: "tolerate",
|
|
56519
|
+
bodyKind: "procedure",
|
|
56520
|
+
description: "Rename the {{camera}} template variable to {{device}} in every stored notification rule, timelapse rule and text override",
|
|
56521
|
+
fixtureMark: "the rule name and the trigger conditions the migration does not know"
|
|
56522
|
+
},
|
|
56523
|
+
procedure: migrateDeviceTemplateVariable
|
|
56524
|
+
}];
|
|
56525
|
+
//#endregion
|
|
56233
56526
|
//#region src/pipeline-analytics/clip-space-plan.ts
|
|
56234
56527
|
/**
|
|
56235
56528
|
* Which CLIP feature space a search reads and a rebuild fills — decided in one
|
|
@@ -67128,7 +67421,7 @@ var CaptureScheduler = class {
|
|
|
67128
67421
|
for (let i = lane.queue.length - 1; i >= 0; i -= 1) {
|
|
67129
67422
|
const entry = lane.queue[i];
|
|
67130
67423
|
if (entry === void 0) continue;
|
|
67131
|
-
if (entry.staleAfterMs === void 0 || nowMs - entry.enqueuedAt
|
|
67424
|
+
if (entry.staleAfterMs === void 0 || nowMs - entry.enqueuedAt < entry.staleAfterMs) continue;
|
|
67132
67425
|
lane.queue.splice(i, 1);
|
|
67133
67426
|
this.bump(deviceId, "droppedStale");
|
|
67134
67427
|
this.noteDropped(deviceId, entry, "stale");
|
|
@@ -67204,7 +67497,7 @@ var CaptureScheduler = class {
|
|
|
67204
67497
|
while (lane.inFlight < this.budget && lane.queue.length > 0) {
|
|
67205
67498
|
const entry = lane.queue.shift();
|
|
67206
67499
|
if (entry === void 0) return;
|
|
67207
|
-
if (entry.staleAfterMs !== void 0 && this.now() - entry.enqueuedAt
|
|
67500
|
+
if (entry.staleAfterMs !== void 0 && this.now() - entry.enqueuedAt >= entry.staleAfterMs) {
|
|
67208
67501
|
this.bump(deviceId, "droppedStale");
|
|
67209
67502
|
this.noteDropped(deviceId, entry, "stale");
|
|
67210
67503
|
entry.drop();
|
|
@@ -68432,6 +68725,109 @@ function createAnalyticsWidgetsProvider() {
|
|
|
68432
68725
|
] };
|
|
68433
68726
|
}
|
|
68434
68727
|
//#endregion
|
|
68728
|
+
//#region src/notification-center/appliance-cycle.ts
|
|
68729
|
+
var NC_UNKNOWN_CYCLE_FACTS = {
|
|
68730
|
+
durationMs: null,
|
|
68731
|
+
energyKwh: null
|
|
68732
|
+
};
|
|
68733
|
+
var NC_EMPTY_CYCLE_VARS = {
|
|
68734
|
+
duration: "",
|
|
68735
|
+
energy: "",
|
|
68736
|
+
afterDuration: "",
|
|
68737
|
+
usedEnergy: ""
|
|
68738
|
+
};
|
|
68739
|
+
/** `52 minutes` / `1 h 12 min` / `2 h`; `''` below one minute (never "0 minutes"). */
|
|
68740
|
+
function durationText(texts, durationMs) {
|
|
68741
|
+
if (durationMs === null || !Number.isFinite(durationMs)) return "";
|
|
68742
|
+
const totalMinutes = Math.round(durationMs / 6e4);
|
|
68743
|
+
if (totalMinutes < 1) return "";
|
|
68744
|
+
if (totalMinutes < 60) return texts.text({
|
|
68745
|
+
key: "appliance.duration.minutes",
|
|
68746
|
+
count: totalMinutes,
|
|
68747
|
+
vars: { minutes: `${totalMinutes}` }
|
|
68748
|
+
});
|
|
68749
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
68750
|
+
const minutes = totalMinutes % 60;
|
|
68751
|
+
return minutes === 0 ? texts.text({
|
|
68752
|
+
key: "appliance.duration.hours",
|
|
68753
|
+
vars: { hours: `${hours}` }
|
|
68754
|
+
}) : texts.text({
|
|
68755
|
+
key: "appliance.duration.hoursMinutes",
|
|
68756
|
+
vars: {
|
|
68757
|
+
hours: `${hours}`,
|
|
68758
|
+
minutes: `${minutes}`
|
|
68759
|
+
}
|
|
68760
|
+
});
|
|
68761
|
+
}
|
|
68762
|
+
/** `0,84 kWh` (it) / `0.84 kWh` (en); `''` when unmeasured. */
|
|
68763
|
+
function energyText(texts, energyKwh) {
|
|
68764
|
+
if (energyKwh === null || !Number.isFinite(energyKwh) || energyKwh < 0) return "";
|
|
68765
|
+
const value = energyKwh.toLocaleString(texts.language, {
|
|
68766
|
+
minimumFractionDigits: 2,
|
|
68767
|
+
maximumFractionDigits: 2
|
|
68768
|
+
});
|
|
68769
|
+
return texts.text({
|
|
68770
|
+
key: "appliance.energy.value",
|
|
68771
|
+
vars: { value }
|
|
68772
|
+
});
|
|
68773
|
+
}
|
|
68774
|
+
/** The four variables of a finished cycle, fragments rendered from scalars only. */
|
|
68775
|
+
function ncApplianceCycleVars(texts, facts) {
|
|
68776
|
+
const duration = durationText(texts, facts.durationMs);
|
|
68777
|
+
const energy = energyText(texts, facts.energyKwh);
|
|
68778
|
+
return {
|
|
68779
|
+
duration,
|
|
68780
|
+
energy,
|
|
68781
|
+
afterDuration: duration === "" ? "" : texts.text({
|
|
68782
|
+
key: "appliance.finished.duration",
|
|
68783
|
+
vars: { duration }
|
|
68784
|
+
}),
|
|
68785
|
+
usedEnergy: energy === "" ? "" : texts.text({
|
|
68786
|
+
key: "appliance.finished.energy",
|
|
68787
|
+
vars: { energy }
|
|
68788
|
+
})
|
|
68789
|
+
};
|
|
68790
|
+
}
|
|
68791
|
+
/**
|
|
68792
|
+
* The frozen reference a FINISHED appliance cycle row carries (`{}` for every
|
|
68793
|
+
* other row): kind `appliance-cycle`, edge `cleared`. The appliance is the
|
|
68794
|
+
* sensor itself — a linked camera is only where the row was attributed.
|
|
68795
|
+
*/
|
|
68796
|
+
function applianceCycleOf(subject) {
|
|
68797
|
+
if (subject.sensorKind !== "appliance-cycle" || subject.sensorRaised !== false) return {};
|
|
68798
|
+
return { applianceCycle: { deviceId: subject.linkedSensorId ?? subject.sourceDeviceId ?? subject.deviceId } };
|
|
68799
|
+
}
|
|
68800
|
+
//#endregion
|
|
68801
|
+
//#region src/notification-center/appliance-cycle-runs.ts
|
|
68802
|
+
var CYCLE_FACTS_WAIT_MS = 3e3;
|
|
68803
|
+
var CYCLE_FACTS_POLL_MS = 250;
|
|
68804
|
+
/** A closed run belongs to this finish when it ended within this much of it. */
|
|
68805
|
+
var CYCLE_RUN_MATCH_MS = 10 * 6e4;
|
|
68806
|
+
function factsOfClosed(run, finishedAtMs) {
|
|
68807
|
+
if (run.endedAt === null || Math.abs(run.endedAt - finishedAtMs) > CYCLE_RUN_MATCH_MS) return NC_UNKNOWN_CYCLE_FACTS;
|
|
68808
|
+
return {
|
|
68809
|
+
durationMs: run.durationMs,
|
|
68810
|
+
energyKwh: run.detail.energyKwh
|
|
68811
|
+
};
|
|
68812
|
+
}
|
|
68813
|
+
function applianceCycleFactsFromRuns(ports) {
|
|
68814
|
+
return async ({ deviceId, finishedAtMs }) => {
|
|
68815
|
+
let waited = 0;
|
|
68816
|
+
for (;;) {
|
|
68817
|
+
const run = await ports.newestRun(deviceId);
|
|
68818
|
+
if (run === null) return NC_UNKNOWN_CYCLE_FACTS;
|
|
68819
|
+
if (run.state === "closed") return factsOfClosed(run, finishedAtMs);
|
|
68820
|
+
if (run.startedAt > finishedAtMs) return NC_UNKNOWN_CYCLE_FACTS;
|
|
68821
|
+
if (waited >= CYCLE_FACTS_WAIT_MS) return {
|
|
68822
|
+
durationMs: finishedAtMs - run.startedAt,
|
|
68823
|
+
energyKwh: null
|
|
68824
|
+
};
|
|
68825
|
+
await ports.sleep(CYCLE_FACTS_POLL_MS);
|
|
68826
|
+
waited += CYCLE_FACTS_POLL_MS;
|
|
68827
|
+
}
|
|
68828
|
+
};
|
|
68829
|
+
}
|
|
68830
|
+
//#endregion
|
|
68435
68831
|
//#region src/pipeline-analytics/runtime/wire-subscriptions.ts
|
|
68436
68832
|
/**
|
|
68437
68833
|
* Every category the Notification Center's system-event intake can turn into a
|
|
@@ -70393,6 +70789,7 @@ Object.freeze({
|
|
|
70393
70789
|
"page": 1,
|
|
70394
70790
|
"pageSize": 50
|
|
70395
70791
|
} },
|
|
70792
|
+
"device-runs": { "listRuns": { "limit": 50 } },
|
|
70396
70793
|
"llm": { "upsertProfile": { "profile": { "__nested__": {
|
|
70397
70794
|
"timeoutMs": 6e4,
|
|
70398
70795
|
"connectTimeoutMs": 1e4,
|
|
@@ -82396,147 +82793,6 @@ var NcDeviceMuteStore = class {
|
|
|
82396
82793
|
}
|
|
82397
82794
|
};
|
|
82398
82795
|
//#endregion
|
|
82399
|
-
//#region src/notification-center/record-kinds.ts
|
|
82400
|
-
/**
|
|
82401
|
-
* Record kinds owned by the pipeline-analytics addon (records plan phase 2,
|
|
82402
|
-
* steps 18-24): the notification-center config tables.
|
|
82403
|
-
*
|
|
82404
|
-
* The notification-center module is hosted inside the pipeline-analytics addon.
|
|
82405
|
-
* That addon declares no `execution.group`, so the UDS owner stamp is the addon
|
|
82406
|
-
* id: `pipeline-analytics`. Declared hub-only (agents have no `ctx.records`).
|
|
82407
|
-
* One deploy covers all 7 kinds.
|
|
82408
|
-
*/
|
|
82409
|
-
/**
|
|
82410
|
-
* The hub language and operator-authored per-key overrides.
|
|
82411
|
-
* One row, id `texts`. Losing it degrades to the shipped English catalog.
|
|
82412
|
-
*/
|
|
82413
|
-
var TextsBodySchema = require_clip_model_registry.object({
|
|
82414
|
-
language: NcLanguageSchema,
|
|
82415
|
-
overrides: require_clip_model_registry.partialRecord(NcLanguageSchema, require_clip_model_registry.record(require_clip_model_registry.string(), require_clip_model_registry.string()))
|
|
82416
|
-
});
|
|
82417
|
-
/**
|
|
82418
|
-
* A shared action sequence (without id, which is the record key).
|
|
82419
|
-
* Extends the wire shape with `createdAt` / `updatedAt` (stored by the legacy table).
|
|
82420
|
-
*/
|
|
82421
|
-
var SequenceBodySchema = require_clip_model_registry.NcSharedSequenceSchema.omit({ id: true }).extend({
|
|
82422
|
-
createdAt: require_clip_model_registry.number(),
|
|
82423
|
-
updatedAt: require_clip_model_registry.number()
|
|
82424
|
-
});
|
|
82425
|
-
/**
|
|
82426
|
-
* The AI section of one ordinary rule. Record id = the rule id.
|
|
82427
|
-
*/
|
|
82428
|
-
var RuleAiBodySchema = require_clip_model_registry.object({
|
|
82429
|
-
ai: NcRuleAiSchema,
|
|
82430
|
-
updatedAt: require_clip_model_registry.number()
|
|
82431
|
-
});
|
|
82432
|
-
/** A calendar-events rule (without `id`). */
|
|
82433
|
-
var CalendarEventsRuleBodySchema = NcCalendarEventsRuleSchema.omit({ id: true });
|
|
82434
|
-
/** A multi-camera digest rule (without `id`). */
|
|
82435
|
-
var SummaryRuleBodySchema = NcSummaryRuleSchema.omit({ id: true });
|
|
82436
|
-
/** A timelapse rule (without `id`). */
|
|
82437
|
-
var TimelapseRuleBodySchema = require_clip_model_registry.TimelapseRuleSchema.omit({ id: true });
|
|
82438
|
-
/** A notification rule (without `id`). */
|
|
82439
|
-
var RuleBodySchema = require_clip_model_registry.NcRuleSchema.omit({ id: true });
|
|
82440
|
-
var ncRecordKinds = require_clip_model_registry.defineRecordKinds({
|
|
82441
|
-
/**
|
|
82442
|
-
* The hub language + per-key text overrides. One row (id `texts`).
|
|
82443
|
-
*/
|
|
82444
|
-
texts: {
|
|
82445
|
-
schema: TextsBodySchema,
|
|
82446
|
-
class: "config",
|
|
82447
|
-
writeTrigger: "an operator changes the hub language (setLanguage) or replaces the per-key override set (setOverrides)",
|
|
82448
|
-
device: "none",
|
|
82449
|
-
at: "none",
|
|
82450
|
-
retention: { none: "one row (id `texts`); losing it costs the chosen language and household wording, never a notification — every read degrades to the shipped English catalog" }
|
|
82451
|
-
},
|
|
82452
|
-
/**
|
|
82453
|
-
* Shared action sequences — named, ordered runs of steps with their own throttle.
|
|
82454
|
-
* One row per sequence, id = sequence UUID.
|
|
82455
|
-
*/
|
|
82456
|
-
sequences: {
|
|
82457
|
-
schema: SequenceBodySchema,
|
|
82458
|
-
class: "config",
|
|
82459
|
-
writeTrigger: "an admin creates/edits/deletes a shared sequence via notificationRules.createSequence / updateSequence / deleteSequence, or the one-shot boot migration that materialises legacy inline sequences",
|
|
82460
|
-
device: "none",
|
|
82461
|
-
at: "none",
|
|
82462
|
-
retention: { none: "a row goes only when an admin deletes it; bounded by the sequence set a human is willing to author" }
|
|
82463
|
-
},
|
|
82464
|
-
/**
|
|
82465
|
-
* Per-rule AI section: whether and how a rule narrates itself.
|
|
82466
|
-
* Record id = the rule id it belongs to.
|
|
82467
|
-
*/
|
|
82468
|
-
"rule-ai": {
|
|
82469
|
-
schema: RuleAiBodySchema,
|
|
82470
|
-
class: "config",
|
|
82471
|
-
writeTrigger: "an operator switches the AI section of a notification rule on or off, or edits its prompt / word cap / language",
|
|
82472
|
-
device: "none",
|
|
82473
|
-
at: "none",
|
|
82474
|
-
retention: { none: "a row goes when the operator clears the section or deletes the rule; bounded by the rule set a human is willing to author" }
|
|
82475
|
-
},
|
|
82476
|
-
/**
|
|
82477
|
-
* Calendar-events rules. One row per rule, id = rule UUID.
|
|
82478
|
-
*/
|
|
82479
|
-
"calendar-events-rules": {
|
|
82480
|
-
schema: CalendarEventsRuleBodySchema,
|
|
82481
|
-
class: "config",
|
|
82482
|
-
writeTrigger: "an operator creates or edits a calendar-events rule, or the producer stamps a delivered window via markGenerated",
|
|
82483
|
-
device: "none",
|
|
82484
|
-
at: "none",
|
|
82485
|
-
indexes: [{
|
|
82486
|
-
name: "enabled",
|
|
82487
|
-
fields: ["enabled"]
|
|
82488
|
-
}],
|
|
82489
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
82490
|
-
},
|
|
82491
|
-
/**
|
|
82492
|
-
* Multi-camera digest rules. One row per rule, id = rule UUID.
|
|
82493
|
-
*/
|
|
82494
|
-
"summary-rules": {
|
|
82495
|
-
schema: SummaryRuleBodySchema,
|
|
82496
|
-
class: "config",
|
|
82497
|
-
writeTrigger: "an operator creates or edits a digest rule, or the producer stamps a delivered window via markGenerated",
|
|
82498
|
-
device: "none",
|
|
82499
|
-
at: "none",
|
|
82500
|
-
indexes: [{
|
|
82501
|
-
name: "enabled",
|
|
82502
|
-
fields: ["enabled"]
|
|
82503
|
-
}],
|
|
82504
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
82505
|
-
},
|
|
82506
|
-
/**
|
|
82507
|
-
* Timelapse rules. One row per rule, id = rule UUID.
|
|
82508
|
-
* Three rows on prod (2026-10-18).
|
|
82509
|
-
*/
|
|
82510
|
-
"timelapse-rules": {
|
|
82511
|
-
schema: TimelapseRuleBodySchema,
|
|
82512
|
-
class: "config",
|
|
82513
|
-
writeTrigger: "an operator creates or edits a timelapse rule, or the scheduler stamps a successful per-camera generation via markGenerated",
|
|
82514
|
-
device: "none",
|
|
82515
|
-
at: "none",
|
|
82516
|
-
indexes: [{
|
|
82517
|
-
name: "enabled",
|
|
82518
|
-
fields: ["enabled"]
|
|
82519
|
-
}],
|
|
82520
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
82521
|
-
},
|
|
82522
|
-
/**
|
|
82523
|
-
* Notification rules — the main rule set. One row per rule, id = rule UUID.
|
|
82524
|
-
* 18 rows on prod (2026-10-18); most coupled kind in this owner.
|
|
82525
|
-
*/
|
|
82526
|
-
rules: {
|
|
82527
|
-
schema: RuleBodySchema,
|
|
82528
|
-
class: "config",
|
|
82529
|
-
writeTrigger: "an operator creates, edits, enables/disables a rule or opts a target out of it; nothing writes on the evaluation path",
|
|
82530
|
-
device: "none",
|
|
82531
|
-
at: "none",
|
|
82532
|
-
indexes: [{
|
|
82533
|
-
name: "delivery",
|
|
82534
|
-
fields: ["delivery"]
|
|
82535
|
-
}],
|
|
82536
|
-
retention: { none: "a row goes only when the operator deletes the rule; bounded by the rule set a human is willing to author" }
|
|
82537
|
-
}
|
|
82538
|
-
});
|
|
82539
|
-
//#endregion
|
|
82540
82796
|
//#region src/notification-center/sequence-store.ts
|
|
82541
82797
|
/**
|
|
82542
82798
|
* Shared action sequences for the notification centre.
|
|
@@ -87621,6 +87877,56 @@ var NcDispatcher = class {
|
|
|
87621
87877
|
renderText(input) {
|
|
87622
87878
|
return renderRowText(this.texts, input);
|
|
87623
87879
|
}
|
|
87880
|
+
/** Rules already warned about a deprecated variable in this process — once each. */
|
|
87881
|
+
deprecatedVarWarned = /* @__PURE__ */ new Set();
|
|
87882
|
+
/**
|
|
87883
|
+
* A rule template written by an older client may still say `{{camera}}`. It
|
|
87884
|
+
* renders (the alias), and the rule is named once so the operator can edit it.
|
|
87885
|
+
*/
|
|
87886
|
+
warnDeprecatedVars(entry) {
|
|
87887
|
+
if (this.deprecatedVarWarned.has(entry.ruleId)) return;
|
|
87888
|
+
const template = entry.payload.template;
|
|
87889
|
+
const used = [...new Set([...deprecatedPlaceholders(template?.title), ...deprecatedPlaceholders(template?.body)])];
|
|
87890
|
+
if (used.length === 0) return;
|
|
87891
|
+
this.deprecatedVarWarned.add(entry.ruleId);
|
|
87892
|
+
this.deps.logger.warn("rule template uses a renamed variable — it still renders, edit it to the new name", {
|
|
87893
|
+
tags: { ruleId: entry.ruleId },
|
|
87894
|
+
meta: {
|
|
87895
|
+
variables: used,
|
|
87896
|
+
replacement: "device"
|
|
87897
|
+
}
|
|
87898
|
+
});
|
|
87899
|
+
}
|
|
87900
|
+
/**
|
|
87901
|
+
* The facts of a finished appliance cycle; unknown on every other row and on
|
|
87902
|
+
* any failure (a part that cannot be read is omitted, never a blocked send).
|
|
87903
|
+
*/
|
|
87904
|
+
async applianceCycleFacts(entry) {
|
|
87905
|
+
const subject = entry.payload.subject;
|
|
87906
|
+
const resolve = this.deps.resolveApplianceCycleFacts;
|
|
87907
|
+
const deviceId = subject.applianceCycle?.deviceId;
|
|
87908
|
+
if (resolve === void 0 || deviceId === void 0) return NC_UNKNOWN_CYCLE_FACTS;
|
|
87909
|
+
try {
|
|
87910
|
+
const facts = await resolve({
|
|
87911
|
+
deviceId,
|
|
87912
|
+
finishedAtMs: subject.timestamp
|
|
87913
|
+
});
|
|
87914
|
+
if (facts.durationMs === null || facts.energyKwh === null) this.deps.logger.debug("appliance cycle part omitted — unknown", {
|
|
87915
|
+
tags: { deviceId },
|
|
87916
|
+
meta: {
|
|
87917
|
+
duration: facts.durationMs === null ? "unknown" : "known",
|
|
87918
|
+
energy: facts.energyKwh === null ? "unknown" : "known"
|
|
87919
|
+
}
|
|
87920
|
+
});
|
|
87921
|
+
return facts;
|
|
87922
|
+
} catch (err) {
|
|
87923
|
+
this.deps.logger.debug("appliance cycle facts failed — parts omitted", {
|
|
87924
|
+
tags: { deviceId },
|
|
87925
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
87926
|
+
});
|
|
87927
|
+
return NC_UNKNOWN_CYCLE_FACTS;
|
|
87928
|
+
}
|
|
87929
|
+
}
|
|
87624
87930
|
/**
|
|
87625
87931
|
* Builds the notification AND returns the artefact ids its media resolve
|
|
87626
87932
|
* indexed — see {@link withArtifactUrls}. A tuple rather than a field on
|
|
@@ -87633,7 +87939,9 @@ var NcDispatcher = class {
|
|
|
87633
87939
|
const deviceName = await notificationDeviceName(entry, this.deps.getDeviceName);
|
|
87634
87940
|
const alarmSource = systemEvent?.alarmSource;
|
|
87635
87941
|
const zoneLabels = systemEvent === void 0 ? await resolveZoneLabels(this.deps.getZoneNames, subject.deviceId, subject.zones) : alarmSource?.zones !== void 0 && alarmSource.zones.length > 0 ? await resolveZoneLabels(this.deps.getZoneNames, alarmSource.deviceId, alarmSource.zones) : [];
|
|
87636
|
-
const
|
|
87942
|
+
const cycleFacts = await this.applianceCycleFacts(entry);
|
|
87943
|
+
const vars = buildTemplateVars(this.texts, entry, deviceName, zoneLabels, void 0, cycleFacts);
|
|
87944
|
+
this.warnDeprecatedVars(entry);
|
|
87637
87945
|
const systemCount = systemEvent?.textCount !== void 0 ? { count: systemEvent.textCount } : {};
|
|
87638
87946
|
const title = renderRowTitle(this.texts, entry, vars);
|
|
87639
87947
|
const authoredBody = entry.payload.template?.body;
|
|
@@ -88748,15 +89056,15 @@ async function resolveZoneLabels(getZoneNames, deviceId, zoneIds) {
|
|
|
88748
89056
|
function percentOf(confidence) {
|
|
88749
89057
|
return confidence !== void 0 ? `${Math.round(confidence * 100)}%` : "";
|
|
88750
89058
|
}
|
|
88751
|
-
/** What `{{
|
|
89059
|
+
/** What `{{device}}` renders on a system row that names no device. */
|
|
88752
89060
|
var NC_DEVICELESS_CAMERA_NAME = "system";
|
|
88753
89061
|
/**
|
|
88754
|
-
* The name `{{
|
|
89062
|
+
* The name `{{device}}` renders for a row — the ONE derivation, shared by the
|
|
88755
89063
|
* dispatcher and the template-variable parity spec.
|
|
88756
89064
|
*
|
|
88757
89065
|
* A system row that names no device (a node, a backup, an update) renders the
|
|
88758
89066
|
* filler {@link NC_DEVICELESS_CAMERA_NAME}: there is no camera to name, which
|
|
88759
|
-
* is why the catalog offers `{{
|
|
89067
|
+
* is why the catalog offers `{{device}}` on a system rule only for the kinds
|
|
88760
89068
|
* that carry a device (template-vars.ts). Everything else asks the directory,
|
|
88761
89069
|
* and falls back to `camera <id>` when it cannot answer.
|
|
88762
89070
|
*/
|
|
@@ -88799,7 +89107,7 @@ function systemEventParamsForTemplate(params) {
|
|
|
88799
89107
|
const { form: _form, attention: _attention, ...rest } = params;
|
|
88800
89108
|
return rest;
|
|
88801
89109
|
}
|
|
88802
|
-
function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
89110
|
+
function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone, cycle) {
|
|
88803
89111
|
const subject = entry.payload.subject;
|
|
88804
89112
|
const occupancy = subject.occupancy;
|
|
88805
89113
|
const systemEvent = subject.systemEvent;
|
|
@@ -88809,7 +89117,7 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
88809
89117
|
const cameraName = deviceName.toLocaleLowerCase(locale);
|
|
88810
89118
|
const zonesLower = zoneLabels.map((z) => z.toLocaleLowerCase(locale));
|
|
88811
89119
|
return {
|
|
88812
|
-
|
|
89120
|
+
device: cameraName,
|
|
88813
89121
|
class: detection?.className ?? (systemEvent !== void 0 ? "" : subject.className),
|
|
88814
89122
|
classLabel: (() => {
|
|
88815
89123
|
const wire = detection?.className ?? (systemEvent !== void 0 ? "" : subject.className);
|
|
@@ -88835,6 +89143,7 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
|
|
|
88835
89143
|
hitPercent: subject.audio?.hitPercent !== void 0 ? `${Math.round(subject.audio.hitPercent)}` : "",
|
|
88836
89144
|
db: subject.audio?.peakDbfs !== void 0 ? `${Math.round(subject.audio.peakDbfs)}` : "",
|
|
88837
89145
|
eventType: subject.eventType ?? "",
|
|
89146
|
+
...cycle !== void 0 ? ncApplianceCycleVars(texts, cycle) : NC_EMPTY_CYCLE_VARS,
|
|
88838
89147
|
rule: entry.payload.ruleName,
|
|
88839
89148
|
count: occupancy !== void 0 ? `${occupancy.count}` : group !== void 0 && group.members.length > 1 ? `${group.members.length}` : "",
|
|
88840
89149
|
capacity: occupancy !== void 0 ? `${occupancy.capacity}` : "",
|
|
@@ -88861,6 +89170,7 @@ function bodyKeyFor(entry) {
|
|
|
88861
89170
|
const subject = entry.payload.subject;
|
|
88862
89171
|
if (subject.systemEvent !== void 0) return subject.systemEvent.bodyKey ?? "";
|
|
88863
89172
|
if (subject.occupancy !== void 0) return "occupancy.body";
|
|
89173
|
+
if (subject.applianceCycle !== void 0) return "appliance.finished.body";
|
|
88864
89174
|
if (subject.packagePhase === "delivered" && subject.packagePossible === true) return "package.delivered.possible.body";
|
|
88865
89175
|
if (subject.packagePhase !== void 0) return `package.${subject.packagePhase}.body`;
|
|
88866
89176
|
return entry.recordKind === "track-end" ? "detection.body.trackEnd" : "detection.body";
|
|
@@ -88898,7 +89208,7 @@ function renderRowTitle(texts, entry, vars) {
|
|
|
88898
89208
|
const systemEvent = entry.payload.subject.systemEvent;
|
|
88899
89209
|
const intrusion = entry.payload.alarmIntrusion;
|
|
88900
89210
|
const count = systemEvent?.textCount;
|
|
88901
|
-
|
|
89211
|
+
const rendered = renderRowText(texts, {
|
|
88902
89212
|
template: intrusion !== void 0 ? void 0 : entry.payload.template?.title,
|
|
88903
89213
|
key: titleKeyFor(entry),
|
|
88904
89214
|
vars: intrusion !== void 0 ? {
|
|
@@ -88908,11 +89218,13 @@ function renderRowTitle(texts, entry, vars) {
|
|
|
88908
89218
|
...count !== void 0 ? { count } : {},
|
|
88909
89219
|
frozen: systemEvent?.title ?? entry.payload.ruleName
|
|
88910
89220
|
});
|
|
89221
|
+
return titleKeyFor(entry) === "appliance.finished.title" && entry.payload.template?.title === void 0 ? ncSentenceCaseBody(rendered) : rendered;
|
|
88911
89222
|
}
|
|
88912
89223
|
/** The default TITLE key — same shape, same pre-catalog reasoning. */
|
|
88913
89224
|
function titleKeyFor(entry) {
|
|
88914
89225
|
const systemEvent = entry.payload.subject.systemEvent;
|
|
88915
89226
|
if (systemEvent !== void 0) return systemEvent.titleKey ?? "";
|
|
89227
|
+
if (entry.payload.subject.applianceCycle !== void 0) return "appliance.finished.title";
|
|
88916
89228
|
if (entry.payload.alarmIntrusion !== void 0) return "alarm.intrusion.title";
|
|
88917
89229
|
const packagePhase = entry.payload.subject.packagePhase;
|
|
88918
89230
|
if (packagePhase === "delivered" && entry.payload.subject.packagePossible === true) return "package.delivered.possible.title";
|
|
@@ -96673,7 +96985,7 @@ function clockOf(atMs) {
|
|
|
96673
96985
|
* catalog, the same way `buildTemplateVars` is asserted for rule rows. */
|
|
96674
96986
|
function timelapseTemplateVars(input) {
|
|
96675
96987
|
return {
|
|
96676
|
-
|
|
96988
|
+
device: input.deviceName ?? `camera ${input.deviceId}`,
|
|
96677
96989
|
rule: input.ruleName,
|
|
96678
96990
|
from: clockOf(input.window.startMs),
|
|
96679
96991
|
to: clockOf(input.window.endMs),
|
|
@@ -96739,7 +97051,7 @@ function paramsFor(target, clickUrl) {
|
|
|
96739
97051
|
*/
|
|
96740
97052
|
function timelapsePreviewText(input) {
|
|
96741
97053
|
const vars = {
|
|
96742
|
-
|
|
97054
|
+
device: input.deviceName ?? `camera ${input.deviceId}`,
|
|
96743
97055
|
...input.ruleName !== void 0 ? { rule: input.ruleName } : {},
|
|
96744
97056
|
...input.window !== void 0 ? {
|
|
96745
97057
|
from: clockOf(input.window.startMs),
|
|
@@ -101984,6 +102296,7 @@ function buildEntries(host, rule, subject, kind, matchedOn, userTargets = [], or
|
|
|
101984
102296
|
...subject.audioWindow.peakDbfs !== void 0 ? { peakDbfs: subject.audioWindow.peakDbfs } : {}
|
|
101985
102297
|
} } : {},
|
|
101986
102298
|
...subject.eventType !== void 0 ? { eventType: subject.eventType } : {},
|
|
102299
|
+
...applianceCycleOf(subject),
|
|
101987
102300
|
...subject.systemEvent !== void 0 ? { systemEvent: subject.systemEvent } : {}
|
|
101988
102301
|
}
|
|
101989
102302
|
};
|
|
@@ -104271,7 +104584,7 @@ function buildSnoozeDigest(texts, snooze, rows, deviceNames) {
|
|
|
104271
104584
|
key: "snooze.digest.line",
|
|
104272
104585
|
vars: {
|
|
104273
104586
|
rule: r.ruleName,
|
|
104274
|
-
|
|
104587
|
+
device: deviceNames.get(r.deviceId) ?? `camera ${r.deviceId}`,
|
|
104275
104588
|
count: `${r.count}`
|
|
104276
104589
|
}
|
|
104277
104590
|
}));
|
|
@@ -109721,6 +110034,7 @@ var BuildNotificationCenter = class extends BuildNotificationCenterBase {
|
|
|
109721
110034
|
}));
|
|
109722
110035
|
},
|
|
109723
110036
|
getDeviceName,
|
|
110037
|
+
resolveApplianceCycleFacts: this.deps.resolveApplianceCycleFacts,
|
|
109724
110038
|
getDeviceLocation,
|
|
109725
110039
|
buildActions: (input) => this.notificationCenter?.mintButtons(input) ?? Promise.resolve([])
|
|
109726
110040
|
},
|
|
@@ -122906,6 +123220,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
122906
123220
|
return out;
|
|
122907
123221
|
};
|
|
122908
123222
|
await step("declareCollections", () => this.declareCollections(api));
|
|
123223
|
+
if (this.localOwnNodeId() === "hub") await step("migrations", () => this.runTemplateMigrations());
|
|
122909
123224
|
this.armAlarmPanelDeclaration(api);
|
|
122910
123225
|
const logger = this.ctx.logger;
|
|
122911
123226
|
const storage = await step("resolveMediaStorage", () => this.resolveMediaStorage(logger));
|
|
@@ -123256,6 +123571,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
123256
123571
|
});
|
|
123257
123572
|
return this.collectionsBootInstance;
|
|
123258
123573
|
}
|
|
123574
|
+
/** The addon's migration catalogue (D987), bound to the notification kinds. */
|
|
123575
|
+
async runTemplateMigrations() {
|
|
123576
|
+
const records = this.requireRecords();
|
|
123577
|
+
const ports = {
|
|
123578
|
+
rules: records.kind(ncRecordKinds["rules"]),
|
|
123579
|
+
timelapseRules: records.kind(ncRecordKinds["timelapse-rules"]),
|
|
123580
|
+
texts: records.kind(ncRecordKinds.texts)
|
|
123581
|
+
};
|
|
123582
|
+
await this.runMigrations(require_clip_model_registry.defineAddonMigrations(ADDON_MIGRATIONS, ports));
|
|
123583
|
+
}
|
|
123259
123584
|
/** `declare-collections.ts` — declareCollections. */
|
|
123260
123585
|
async declareCollections(api) {
|
|
123261
123586
|
return this.collectionsBoot.declareCollections(api);
|
|
@@ -123581,7 +123906,16 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_clip_m
|
|
|
123581
123906
|
buildTimelapsePorts: (api, stores) => this.buildTimelapsePorts(api, stores),
|
|
123582
123907
|
buildSummaryPorts: (api, stores) => this.buildSummaryPorts(api, stores),
|
|
123583
123908
|
wireAlarmPanel: (center) => this.wireAlarmPanel(center),
|
|
123584
|
-
serveNcActionPlane: (center) => this.serveNcActionPlane(center)
|
|
123909
|
+
serveNcActionPlane: (center) => this.serveNcActionPlane(center),
|
|
123910
|
+
resolveApplianceCycleFacts: applianceCycleFactsFromRuns({
|
|
123911
|
+
newestRun: async (deviceId) => {
|
|
123912
|
+
return (await this.ctx.api.deviceRuns.listRuns.query({
|
|
123913
|
+
deviceId,
|
|
123914
|
+
limit: 1
|
|
123915
|
+
})).runs[0] ?? null;
|
|
123916
|
+
},
|
|
123917
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
123918
|
+
})
|
|
123585
123919
|
});
|
|
123586
123920
|
return this.notificationCenterBootInstance;
|
|
123587
123921
|
}
|