@camstack/addon-post-analysis 1.2.607 → 1.2.609
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{clip-model-registry-VgOXFRFr.js → clip-model-registry-B11yS32V.js} +472 -47
- package/dist/{clip-model-registry-CTfe5v5_.mjs → clip-model-registry-BItfTZEx.mjs} +431 -48
- 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-DUNQoBDI.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-LGLlz5Sk.mjs} +3 -3
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-iXmJsQ9Y.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-Cm7pZeMH.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-B8mC_3oV.mjs → hostInit-Cko0y33G.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +583 -68
- package/dist/pipeline-analytics/index.mjs +583 -68
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-1AlitJg2.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-DalpVM1q.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as MediaFileKindEnum, $i as
|
|
1
|
+
import { $ as MediaFileKindEnum, $i as boolean, $n as coreBlockIdFromAddonId, $r as readTimelapseGeneratedAt, $t as NcTemplateVarDescriptorSchema, A as DeclaredDevices, Ai as viewerLinkBaseOf, An as TrackSideSchema, Ar as isEventOwnerType, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as RECORD_MAX_DATA_BYTES, Bn as applyModeDelaysEdits, Br as kebabToCamel, Bt as NcRuleSchema, C as ClearTrackRelationResultSchema, Ci as systemEventFamilyProblems, Cn as TRACK_RELATIONS_DEFAULT_LIMIT, Cr as isAlertEpisodeOpen, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as templateCarriesSummary, Dn as TrackPuritySchema, Dr as isDefaultAlarmProfileId, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ei as systemEventSummarySplitOf, En as TimelapseRuleSchema, Er as isAudioClassName, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as isSourceCap, Fn as alarmPanelCapability, Fr as isRootSuppliableStepId, Ft as NcLegacyRuleActionSequenceSchema, G as ListTrackRelationsResultSchema, Gi as hydrateSchema, Gn as audioMetricsCapability, Gr as occupancyScope, Gt as NcSnoozeInputSchema, H as LABEL_SUPPLY_MACRO, Hi as customAction, Hn as applySecureOnArmEdit, Hr as mayWriteToLocation, Ht as NcRuleUpdateInputSchema, I as EVENT_KIND_BY_CAP, Ii as DeviceType, In as alertEpisodeId, Ir as isScheduleActive, It as NcRuleActionSchema, J as MAX_BIRTH_DECISION_RECORDS, Jn as buildViewerLink, Jr as pickFaceRescoreIntervalMinutes, Jt as NcStateSummarySectionIdSchema, K as LockStateSchema, Ki as isDeviceScopedCap, Kn as audioModeOf, Kr as parseAlertEpisodePayload, Kt as NcSnoozeSchema, L as EVENT_OWNER_TYPES, Li as errMsg, Ln as alertEpisodeKind, Lr as isSystemDelivery, Lt as NcRuleActionsSchema, M as EMBEDDING_MODEL_FAILED, Mi as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Mn as activateAlarmProfile, Mr as isLabelSupplyStepId, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as canonicalHash, Nn as addonWidgetsSourceCapability, Nr as isMediaOwnerType, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as templateVarsFor, On as TrackRelationKindSchema, Or as isDetectionMacroClass, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as evaluateSensorEdge, Pn as alarmDeviceSecureOnArm, Pr as isRetiredClearKey, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as array, Qn as consumableLowPercentOf, Qr as readMotionTriggeredBy, Qt as NcTemplatePreviewSchema, R as EXPORT_DENSE_MAX_RANGES, Ri as BaseAddon, Rn as alertEpisodePayload, Rr as isSystemEventSummaryRule, Rt as NcRuleInputSchema, S as ClearTrackRelationInputSchema, Si as systemEventDeliveryProblems, Sn as TIMELAPSE_DENSE_FLOOR_SEC, Sr as isAlarmControllableCap, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ti as systemEventSummaryScheduleOf, Tn as TimelapseRulePatchSchema, Tr as isAppLinkBase, Tt as NC_TANK_SYSTEM_EVENT_KINDS, U as LabelAttributionSchema, Ui as defineCustomActions, Un as armRemovalReconcile, Ur as memoizePinnedFfmpeg, Ut as NcScheduleSchema, V as FailureCounters, Vi as createEvent, Vn as applyNonBlockingEdit, Vr as linkedStepOf, Vt as NcRuleTargetSchema, W as ListTrackRelationsInputSchema, Wi as defineRecordKinds, Wn as assertTimelapseCadences, Wr as notificationRulesCapability, Wt as NcSharedSequenceSchema, X as MAX_TRACK_PURITIES, Xi as ZodObject, Xn as classifyRetrainReview, Xr as plateGalleryCapability, Xt as NcTaxonomySchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as sleep$1, Yn as calendarEventLocalSpan, Yr as pipelineAnalyticsCapability, Yt as NcSystemEventKindSchema, Z as MAX_TRACK_RELATIONS, Zi as _enum, Zn as cloneAlarmSettings, Zr as readDeviceStateFrom, Zt as NcTemplatePreviewInputSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as startMoverUnderClearance, _n as SCENE_RESET_RECAPTURES, _r as evictionPolicyOfLocation, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as partialRecord, an as RebuildObjectEmbeddingsRefusalSchema, ar as deriveRecordingMode, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, bi as summarizeReview, bn as SetTrackPurityResultSchema, br as gifDeriveSpeed, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as string, ci as ruleAutoTriggersAlarm, cn as RetrainStatusSchema, cr as deviceRemovalHookCapability, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, da as union, di as ruleGroupingApplies, dn as SCENE_DEFAULT_ANCHOR_THRESHOLD, dr as embeddingRefusalOf, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as discriminatedUnion, ei as renameAlarmProfile, en as OPS_LOG_DEFAULT_LIMIT, er as cosineSimilarity$1, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as unknown, fi as ruleKindOf, fn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, gi as servedTemplateVars, gn as SCENE_DIVERGED, gr as eventKindLabel, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, hi as sensorRaisedOf, hn as SCENE_DEFAULT_UNCOVERED_POLICY, hr as ensureAlarmProfiles, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as object, ii as resolveModeNotifyPolicy, in as RECORDING_SOURCE_CAMSTACK_ADDON, ir as derivePostTriggeredAction, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as zoneAnalyticsCapability, jn as TrackSourceSchema, jr as isKnownTemplateVar, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as vectorDimFromBase64, kn as TrackRelationSchema, kr as isEventKindLabelKey, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as templateLiteral, li as ruleBelongsToAlarm$1, ln as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, mi as secureActuationFor, mn as SCENE_DEFAULT_QUIET_SECONDS, mr as encodeVectorBase64, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as looseObject, ni as resolveLocationMode, nr as deleteAlarmProfile, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as record, oi as retrainRevisionConflictMessage, on as RetrainDismissedSchema, or as describeSystemEventDeliveryProblem, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as EventCategory, pi as sceneMonitorCapability, pn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as nodePin, qn as buildEventKindDescriptor, qt as NcSnoozeSuppressedSchema, ra as number, ri as resolveModeDelays, rn as RECORDING_EXPORT_MAX_READ_BYTES, rr as deriveDetailCropRect, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as strictObject, si as rootSupplyServes, sn as RetrainOriginSchema, sr as describeSystemEventFamilyProblem, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as literal, ti as resolveClipSearchMinScore, tn as OpsLogEntrySchema, tr as createAlarmProfile, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as tuple, ui as ruleDeviceCompatCapQueries, un as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, vi as storageOccupancyCapability, vn as SceneMonitorSchema, vr as faceGalleryCapability, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wi as systemEventFilterApplies, wn as TimelapseRuleInputSchema, wr as isAlertEpisodeSeverity, wt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, x as COCO_TO_MACRO, xi as syncActiveAlarmProfileSettings, xn as SetTrackRelationInputSchema, xr as gifFromMp4Args, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, yi as subKindsOf, yn as SetTrackPurityInputSchema, yr as failureContributionCapability, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as CamProfileSchema, zn as applyBlockingEdit, zr as isVectorIndexDimConflict, zt as NcRulePatchSchema } from "../clip-model-registry-BItfTZEx.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs$1 from "node:fs";
|
|
4
4
|
import { createWriteStream, promises } from "node:fs";
|
|
@@ -10273,6 +10273,7 @@ var NC_SYSTEM_STATE_LEGACY = "notification-center:system-state";
|
|
|
10273
10273
|
var NC_OCCUPANCY_LEGACY = "notification-center:occupancy";
|
|
10274
10274
|
var NC_DEVICE_PRUNES_LEGACY = "notification-center:device-prunes";
|
|
10275
10275
|
var NC_STATE_SUMMARY_STAMPS_LEGACY = "notification-center:state-summary-stamps";
|
|
10276
|
+
var NC_ALERT_EPISODES_LEGACY = "notification-center:alert-episodes";
|
|
10276
10277
|
var NC_META_LEGACY = "notification-center:meta";
|
|
10277
10278
|
var NC_SNOOZES_LEGACY = "notification-center:snoozes";
|
|
10278
10279
|
var NC_SNOOZE_SUPPRESSED_LEGACY = "notification-center:snooze-suppressed";
|
|
@@ -10293,6 +10294,7 @@ var ncDurableRecordKinds = defineRecordKinds({
|
|
|
10293
10294
|
liveness: ledger(NC_LIVENESS_LEGACY, "a liveness state TRANSITION the centre accepted (seed or flip); a no-flip writes nothing"),
|
|
10294
10295
|
"device-alerts": ledger(NC_DEVICE_ALERTS_LEGACY, "a sustained device alert episode the centre confirmed or cleared"),
|
|
10295
10296
|
"system-state": ledger(NC_SYSTEM_STATE_LEGACY, "a system-state subject flip the state summary accepted"),
|
|
10297
|
+
"alert-episodes": ledger(NC_ALERT_EPISODES_LEGACY, "an alert episode the centre notified (critical or error); its resolution deletes the row"),
|
|
10296
10298
|
occupancy: ledger(NC_OCCUPANCY_LEGACY, "a confirmed occupancy level write-through after the watcher decided an edge"),
|
|
10297
10299
|
"device-prunes": ledger(NC_DEVICE_PRUNES_LEGACY, "one row recording what a deleted-device prune removed"),
|
|
10298
10300
|
"state-summary-stamps": ledger(NC_STATE_SUMMARY_STAMPS_LEGACY, "digest delivery stamps per rule kind"),
|
|
@@ -10312,6 +10314,7 @@ var NC_PHASE3_RECORD_BACKED_LEGACY_COLLECTIONS = [
|
|
|
10312
10314
|
NC_LIVENESS_LEGACY,
|
|
10313
10315
|
NC_DEVICE_ALERTS_LEGACY,
|
|
10314
10316
|
NC_SYSTEM_STATE_LEGACY,
|
|
10317
|
+
NC_ALERT_EPISODES_LEGACY,
|
|
10315
10318
|
NC_OCCUPANCY_LEGACY,
|
|
10316
10319
|
NC_DEVICE_PRUNES_LEGACY,
|
|
10317
10320
|
NC_STATE_SUMMARY_STAMPS_LEGACY,
|
|
@@ -10733,6 +10736,120 @@ var NcAlertStore = class {
|
|
|
10733
10736
|
}
|
|
10734
10737
|
};
|
|
10735
10738
|
//#endregion
|
|
10739
|
+
//#region src/notification-center/alert-episode-ledger.ts
|
|
10740
|
+
/**
|
|
10741
|
+
* @durable class=ledger owner=notification-center
|
|
10742
|
+
* write="an alert episode the centre notified (alert-critical / alert-error); its resolution deletes the row"
|
|
10743
|
+
* retention="a row lives while its episode is open. A row whose alert was pruned by the alert centre's own retention is dropped after NC_ALERT_EPISODE_MAX_AGE_MS by the reconcile, never on absence alone (D49)."
|
|
10744
|
+
*/
|
|
10745
|
+
var NC_ALERT_EPISODES_COLLECTION = "notification-center:alert-episodes";
|
|
10746
|
+
var NC_ALERT_EPISODES_COLUMNS = [
|
|
10747
|
+
{
|
|
10748
|
+
name: "episodeId",
|
|
10749
|
+
type: "TEXT",
|
|
10750
|
+
primaryKey: true,
|
|
10751
|
+
notNull: true
|
|
10752
|
+
},
|
|
10753
|
+
(
|
|
10754
|
+
/** The kind that was notified: `alert-critical` or `alert-error`. */
|
|
10755
|
+
{
|
|
10756
|
+
name: "kind",
|
|
10757
|
+
type: "TEXT",
|
|
10758
|
+
notNull: true
|
|
10759
|
+
}),
|
|
10760
|
+
{
|
|
10761
|
+
name: "alertId",
|
|
10762
|
+
type: "TEXT",
|
|
10763
|
+
notNull: true
|
|
10764
|
+
},
|
|
10765
|
+
{
|
|
10766
|
+
name: "since",
|
|
10767
|
+
type: "INTEGER",
|
|
10768
|
+
notNull: true
|
|
10769
|
+
}
|
|
10770
|
+
];
|
|
10771
|
+
/** Longer than the alert centre's maximum retention (365 days). */
|
|
10772
|
+
var NC_ALERT_EPISODE_MAX_AGE_MS = 400 * 24 * 60 * 60 * 1e3;
|
|
10773
|
+
function isEpisodeKind(value) {
|
|
10774
|
+
return value === "alert-critical" || value === "alert-error";
|
|
10775
|
+
}
|
|
10776
|
+
var NC_ALERT_EPISODES_SPEC = {
|
|
10777
|
+
collection: NC_ALERT_EPISODES_COLLECTION,
|
|
10778
|
+
columns: NC_ALERT_EPISODES_COLUMNS,
|
|
10779
|
+
writeMode: "write-behind",
|
|
10780
|
+
keyOf: (row) => row.episodeId,
|
|
10781
|
+
toValue: (row) => ({
|
|
10782
|
+
kind: row.kind,
|
|
10783
|
+
alertId: row.alertId,
|
|
10784
|
+
since: row.since
|
|
10785
|
+
}),
|
|
10786
|
+
fromRecord: (key, data) => {
|
|
10787
|
+
const kind = data["kind"];
|
|
10788
|
+
const alertId = data["alertId"];
|
|
10789
|
+
const since = data["since"];
|
|
10790
|
+
if (!isEpisodeKind(kind) || typeof alertId !== "string" || typeof since !== "number") return null;
|
|
10791
|
+
return {
|
|
10792
|
+
episodeId: key,
|
|
10793
|
+
kind,
|
|
10794
|
+
alertId,
|
|
10795
|
+
since
|
|
10796
|
+
};
|
|
10797
|
+
}
|
|
10798
|
+
};
|
|
10799
|
+
NC_ALERT_EPISODES_SPEC.fromRecord, NC_ALERT_EPISODES_SPEC.toValue;
|
|
10800
|
+
var NcAlertEpisodeLedger = class {
|
|
10801
|
+
ledger;
|
|
10802
|
+
constructor(deps) {
|
|
10803
|
+
this.ledger = new DurableLedger({
|
|
10804
|
+
spec: NC_ALERT_EPISODES_SPEC,
|
|
10805
|
+
kind: resolveDurableKind(NC_ALERT_EPISODES_SPEC, ncDurableRecordKinds["alert-episodes"], deps),
|
|
10806
|
+
logger: deps.logger
|
|
10807
|
+
});
|
|
10808
|
+
}
|
|
10809
|
+
static declare(store) {
|
|
10810
|
+
return declareDurableSpecLegacy(store, NC_ALERT_EPISODES_SPEC);
|
|
10811
|
+
}
|
|
10812
|
+
async load() {
|
|
10813
|
+
return (await this.ledger.load()).length;
|
|
10814
|
+
}
|
|
10815
|
+
/** Has a load READ the store once? Until then the mirror is not the truth. */
|
|
10816
|
+
hasAnswered() {
|
|
10817
|
+
return this.ledger.hasAnswered();
|
|
10818
|
+
}
|
|
10819
|
+
has(episodeId) {
|
|
10820
|
+
return this.ledger.has(episodeId);
|
|
10821
|
+
}
|
|
10822
|
+
snapshot() {
|
|
10823
|
+
return this.ledger.snapshot();
|
|
10824
|
+
}
|
|
10825
|
+
/**
|
|
10826
|
+
* Judge one announced edge and advance the ledger. Synchronous: the verdict
|
|
10827
|
+
* is a function of the mirror (D49); the durable write trails it.
|
|
10828
|
+
*/
|
|
10829
|
+
admit(edge, now) {
|
|
10830
|
+
const held = this.ledger.get(edge.episodeId);
|
|
10831
|
+
if (edge.phase === "raised") {
|
|
10832
|
+
if (edge.kind !== "alert-critical" && edge.kind !== "alert-error") return "duplicate";
|
|
10833
|
+
if (held !== void 0 && (held.kind === "alert-critical" || edge.kind === "alert-error")) return "duplicate";
|
|
10834
|
+
this.ledger.put({
|
|
10835
|
+
episodeId: edge.episodeId,
|
|
10836
|
+
kind: edge.kind,
|
|
10837
|
+
alertId: edge.alertId,
|
|
10838
|
+
since: held?.since ?? now
|
|
10839
|
+
});
|
|
10840
|
+
return "notify";
|
|
10841
|
+
}
|
|
10842
|
+
if (held === void 0) return "unknown-episode";
|
|
10843
|
+
this.ledger.forget(edge.episodeId);
|
|
10844
|
+
return held.kind === "alert-critical" && edge.kind === "alert-critical-resolved" ? "notify" : "closed-silently";
|
|
10845
|
+
}
|
|
10846
|
+
/** Drop rows older than the alert centre could still hold the alert. */
|
|
10847
|
+
async pruneOlderThan(cutoffMs) {
|
|
10848
|
+
const keep = new Set(this.ledger.snapshot().filter((row) => row.since >= cutoffMs).map((row) => row.episodeId));
|
|
10849
|
+
return this.ledger.pruneExcept(keep);
|
|
10850
|
+
}
|
|
10851
|
+
};
|
|
10852
|
+
//#endregion
|
|
10736
10853
|
//#region src/notification-center/device-prune-ledger.ts
|
|
10737
10854
|
/**
|
|
10738
10855
|
* @durable class=ledger owner=notification-center
|
|
@@ -16018,6 +16135,34 @@ function bestMatchingKindPreference(signal, ownerKind) {
|
|
|
16018
16135
|
}
|
|
16019
16136
|
//#endregion
|
|
16020
16137
|
//#region src/notification-center/event-intake.ts
|
|
16138
|
+
/**
|
|
16139
|
+
* The notification path's INTAKE SEAM — one event in, no knowledge of who
|
|
16140
|
+
* produced it.
|
|
16141
|
+
*
|
|
16142
|
+
* Why this file exists at all: until now every producer reached the centre
|
|
16143
|
+
* through its own record-shaped door (`onObjectEventPersisted(ObjectEvent)`,
|
|
16144
|
+
* `onSensorEventPersisted(SensorEvent)`, …). The doors did nothing but build a
|
|
16145
|
+
* subject and schedule an evaluation, yet their SIGNATURES made the notification
|
|
16146
|
+
* path look like a function of the pipeline's row types — so "notify about this"
|
|
16147
|
+
* was not expressible without first having a persisted row to hand.
|
|
16148
|
+
*
|
|
16149
|
+
* The split this file draws is between PRODUCING an event and CONSUMING one:
|
|
16150
|
+
*
|
|
16151
|
+
* - The producer owns persistence. The tracker persists its tracks, the sensor
|
|
16152
|
+
* ingest persists its rows, and a synthetic producer (the rule tester)
|
|
16153
|
+
* persists NOTHING. Whether a row exists is the producer's business and
|
|
16154
|
+
* never the consumer's.
|
|
16155
|
+
* - The consumer owns match → media → delivery. It receives an
|
|
16156
|
+
* {@link NcIncomingEvent} and cannot tell which producer built it, because
|
|
16157
|
+
* the only thing the matcher can read is `subject`, and `subject` carries no
|
|
16158
|
+
* provenance beyond the detection `source` a real record would carry anyway.
|
|
16159
|
+
*
|
|
16160
|
+
* {@link NcIncomingEvent.origin} exists for LOGS, and for the outbox row's
|
|
16161
|
+
* marker. It is deliberately NOT part of `NcRuleSubject`: a condition cannot
|
|
16162
|
+
* reach it, so no rule can be authored that behaves differently for a test. That
|
|
16163
|
+
* is the property the tester is worth nothing without — a test that takes a
|
|
16164
|
+
* different branch is a test of the branch, not of the rule.
|
|
16165
|
+
*/
|
|
16021
16166
|
/** Object-event insert (movement-state transitions + zone crossings). */
|
|
16022
16167
|
function incomingFromObjectEvent(event) {
|
|
16023
16168
|
return {
|
|
@@ -16861,9 +17006,45 @@ function incomingFromSystemEvent(event, lookup) {
|
|
|
16861
17006
|
case "system.restarting": return incomingFromNodeRestarting(facts, data);
|
|
16862
17007
|
case "recording.export.completed": return incomingFromExportCompleted(facts, data);
|
|
16863
17008
|
case "backup.run-completed": return incomingFromBackupCompleted(facts, data);
|
|
17009
|
+
case "alert.episode": return incomingFromAlertEpisode(facts);
|
|
16864
17010
|
default: return null;
|
|
16865
17011
|
}
|
|
16866
17012
|
}
|
|
17013
|
+
/**
|
|
17014
|
+
* An alert episode edge (`alert-critical`, `alert-error`, `alert-critical-resolved`).
|
|
17015
|
+
*
|
|
17016
|
+
* The WORDS are the alert's own: the alert centre composed the title and the
|
|
17017
|
+
* message when it opened the alert, and a rule's template may reword them with
|
|
17018
|
+
* `{{title}}`, `{{message}}`, `{{severity}}`, `{{category}}`, `{{source}}`,
|
|
17019
|
+
* `{{node}}` and `{{actionUrl}}`. The subject is the EPISODE, so the dedup slot
|
|
17020
|
+
* of a new episode is never the one a previous, resolved, episode held.
|
|
17021
|
+
*
|
|
17022
|
+
* Whether an edge is notified at all (once per episode, reconciled from the
|
|
17023
|
+
* alerts store) is decided before this point, by `NcAlertEpisodeLedger`.
|
|
17024
|
+
*/
|
|
17025
|
+
function incomingFromAlertEpisode(facts) {
|
|
17026
|
+
const payload = parseAlertEpisodePayload(facts.data);
|
|
17027
|
+
if (payload === null) return null;
|
|
17028
|
+
const kind = alertEpisodeKind(payload.severity, payload.phase);
|
|
17029
|
+
if (kind === null) return null;
|
|
17030
|
+
return systemSubject({
|
|
17031
|
+
facts,
|
|
17032
|
+
kind,
|
|
17033
|
+
subject: `alert:${payload.episodeId}`,
|
|
17034
|
+
titleKey: `system.${kind}.title`,
|
|
17035
|
+
bodyKey: `system.${kind}.body`,
|
|
17036
|
+
textParams: {
|
|
17037
|
+
title: payload.title,
|
|
17038
|
+
message: payload.message,
|
|
17039
|
+
severity: payload.severity,
|
|
17040
|
+
category: payload.category,
|
|
17041
|
+
source: payload.sourceId ?? "",
|
|
17042
|
+
nodeId: payload.nodeId ?? "",
|
|
17043
|
+
actionUrl: payload.actionUrl ?? ""
|
|
17044
|
+
},
|
|
17045
|
+
...payload.nodeId !== void 0 ? { nodeId: payload.nodeId } : {}
|
|
17046
|
+
});
|
|
17047
|
+
}
|
|
16867
17048
|
/** The shells whose update has its own sentence. Anything else gets the plain
|
|
16868
17049
|
* one: a body that names a shell nobody observed is worse than a vague body. */
|
|
16869
17050
|
var WRAPPER_BODY_KINDS = new Set([
|
|
@@ -19346,6 +19527,7 @@ async function declareNcPhase3SettingsStoreLegacyCollections(store) {
|
|
|
19346
19527
|
async function declareNcPhase3RecordBackedLegacyCollections(store) {
|
|
19347
19528
|
await OccupancyStore.declare(store);
|
|
19348
19529
|
await NcLivenessLedger.declare(store);
|
|
19530
|
+
await NcAlertEpisodeLedger.declare(store);
|
|
19349
19531
|
await NcAlertStore.declare(store);
|
|
19350
19532
|
await NcStateSummaryService.declare(store);
|
|
19351
19533
|
await NcSnoozeStore.declare(store);
|
|
@@ -22157,6 +22339,41 @@ function audioDbfsByBucket(events, since, bucketMs) {
|
|
|
22157
22339
|
}
|
|
22158
22340
|
return out;
|
|
22159
22341
|
}
|
|
22342
|
+
/**
|
|
22343
|
+
* PEAK dBFS per bucket index from device-history points (D431 follow-up).
|
|
22344
|
+
*
|
|
22345
|
+
* The bar answers "was it loud here", so a bucket reports the loudest window
|
|
22346
|
+
* in it. A raw point has `max === value`; a rollup row's `max` is the loudest
|
|
22347
|
+
* point it covers, which a mean would flatten. A bucket with no point is
|
|
22348
|
+
* absent — never zero. A rollup row also credits a held value across a quiet
|
|
22349
|
+
* gap, so its max can echo an earlier measured peak.
|
|
22350
|
+
*/
|
|
22351
|
+
function audioPeakByBucket(points, since, bucketMs) {
|
|
22352
|
+
const out = /* @__PURE__ */ new Map();
|
|
22353
|
+
if (bucketMs <= 0) return out;
|
|
22354
|
+
for (const p of points) {
|
|
22355
|
+
if (!Number.isFinite(p.max)) continue;
|
|
22356
|
+
const idx = Math.floor((p.t - since) / bucketMs);
|
|
22357
|
+
if (idx < 0) continue;
|
|
22358
|
+
const prev = out.get(idx);
|
|
22359
|
+
if (prev === void 0 || p.max > prev) out.set(idx, p.max);
|
|
22360
|
+
}
|
|
22361
|
+
return out;
|
|
22362
|
+
}
|
|
22363
|
+
//#endregion
|
|
22364
|
+
//#region src/pipeline-analytics/store/analytics-history-keys.ts
|
|
22365
|
+
/** Declared addon series keys (D850). */
|
|
22366
|
+
var ANALYTICS_HISTORY_BUCKET_SERIES = {
|
|
22367
|
+
motion: "addon:pipeline-analytics/motion",
|
|
22368
|
+
"audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
|
|
22369
|
+
occupancy: "addon:pipeline-analytics/occupancy"
|
|
22370
|
+
};
|
|
22371
|
+
var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
|
|
22372
|
+
/** Map the public cap enum to the device-history key (read path). */
|
|
22373
|
+
function historyKeyForLtsSeries(series) {
|
|
22374
|
+
if (series === "battery") return "cap:battery.percentage";
|
|
22375
|
+
return ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
22376
|
+
}
|
|
22160
22377
|
//#endregion
|
|
22161
22378
|
//#region src/pipeline-analytics/store/event-store.ts
|
|
22162
22379
|
/**
|
|
@@ -22529,6 +22746,24 @@ function eventRowId(logger, operation, deviceId, key) {
|
|
|
22529
22746
|
});
|
|
22530
22747
|
return null;
|
|
22531
22748
|
}
|
|
22749
|
+
var RAW_RETENTION_MS = 7 * 864e5;
|
|
22750
|
+
var FIVE_MIN_RETENTION_MS = 14 * 864e5;
|
|
22751
|
+
var FIVE_MIN_MS = 3e5;
|
|
22752
|
+
var HOUR_MS$2 = 36e5;
|
|
22753
|
+
/** The producer writes one raw point per window of this length. */
|
|
22754
|
+
var AUDIO_LEVEL_POINT_MS = 1e4;
|
|
22755
|
+
var HISTORY_MAX_POINTS = 5e3;
|
|
22756
|
+
/**
|
|
22757
|
+
* Pick the device-history tier for a density window: raw while the buckets are
|
|
22758
|
+
* finer than the 5m rollup, the window is inside raw retention and its points
|
|
22759
|
+
* fit one query; the 5m rollup while inside its retention; else 1h.
|
|
22760
|
+
*/
|
|
22761
|
+
function audioLevelResolution(q, now) {
|
|
22762
|
+
const rawFits = Math.ceil((q.until - q.since) / AUDIO_LEVEL_POINT_MS) <= HISTORY_MAX_POINTS;
|
|
22763
|
+
if (q.bucketMs < FIVE_MIN_MS && rawFits && q.since >= now - RAW_RETENTION_MS) return "raw";
|
|
22764
|
+
if (q.bucketMs < HOUR_MS$2 && q.since >= now - FIVE_MIN_RETENTION_MS) return "5m";
|
|
22765
|
+
return "1h";
|
|
22766
|
+
}
|
|
22532
22767
|
var EventStore = class EventStore {
|
|
22533
22768
|
store;
|
|
22534
22769
|
logger;
|
|
@@ -22536,6 +22771,10 @@ var EventStore = class EventStore {
|
|
|
22536
22771
|
audioLedger;
|
|
22537
22772
|
media;
|
|
22538
22773
|
onMotionSample;
|
|
22774
|
+
audioLevelHistory;
|
|
22775
|
+
nowFn;
|
|
22776
|
+
/** Cameras whose history read already failed once (log once per camera, D315). */
|
|
22777
|
+
levelFallbackLogged = /* @__PURE__ */ new Set();
|
|
22539
22778
|
constructor(deps) {
|
|
22540
22779
|
this.store = deps.store;
|
|
22541
22780
|
this.logger = deps.logger;
|
|
@@ -22544,6 +22783,8 @@ var EventStore = class EventStore {
|
|
|
22544
22783
|
this.audioLedger = deps.audioLedger ?? wireAudioEventLedger(wiring);
|
|
22545
22784
|
this.media = deps.media;
|
|
22546
22785
|
this.onMotionSample = deps.onMotionSample;
|
|
22786
|
+
this.audioLevelHistory = deps.audioLevelHistory;
|
|
22787
|
+
this.nowFn = deps.now ?? (() => Date.now());
|
|
22547
22788
|
}
|
|
22548
22789
|
static async declareTracklessEventCollections(store) {
|
|
22549
22790
|
await store.declareCollection.mutate({
|
|
@@ -23272,8 +23513,8 @@ var EventStore = class EventStore {
|
|
|
23272
23513
|
const m = motionMap.get(i) ?? 0;
|
|
23273
23514
|
const o = objectMap.get(i) ?? 0;
|
|
23274
23515
|
const a = audioMap.get(i) ?? 0;
|
|
23275
|
-
if (m + o + a === 0) continue;
|
|
23276
23516
|
const dbfs = audioDbfsMap.get(i);
|
|
23517
|
+
if (m + o + a === 0 && dbfs === void 0) continue;
|
|
23277
23518
|
out.push({
|
|
23278
23519
|
bucketStart: q.since + i * q.bucketMs,
|
|
23279
23520
|
motion: m,
|
|
@@ -23294,6 +23535,28 @@ var EventStore = class EventStore {
|
|
|
23294
23535
|
* missing bar is the honest rendering of "not measured" (D431).
|
|
23295
23536
|
*/
|
|
23296
23537
|
async audioDbfsPerBucket(q) {
|
|
23538
|
+
const history = this.audioLevelHistory;
|
|
23539
|
+
if (history !== void 0) try {
|
|
23540
|
+
const resolution = audioLevelResolution(q, this.nowFn());
|
|
23541
|
+
const points = (await history({
|
|
23542
|
+
deviceId: q.deviceId,
|
|
23543
|
+
key: ANALYTICS_AUDIO_LEVEL_KEY,
|
|
23544
|
+
scope: "",
|
|
23545
|
+
from: q.since,
|
|
23546
|
+
to: q.until,
|
|
23547
|
+
resolution,
|
|
23548
|
+
maxPoints: HISTORY_MAX_POINTS
|
|
23549
|
+
})).series.flatMap((s) => s.points);
|
|
23550
|
+
if (points.length > 0) return audioPeakByBucket(points, q.since, q.bucketMs);
|
|
23551
|
+
} catch (err) {
|
|
23552
|
+
if (!this.levelFallbackLogged.has(q.deviceId)) {
|
|
23553
|
+
this.levelFallbackLogged.add(q.deviceId);
|
|
23554
|
+
this.logger.warn("density: audio level history unreadable — falling back to audio events", {
|
|
23555
|
+
tags: { deviceId: q.deviceId },
|
|
23556
|
+
meta: { error: String(err) }
|
|
23557
|
+
});
|
|
23558
|
+
}
|
|
23559
|
+
}
|
|
23297
23560
|
try {
|
|
23298
23561
|
return audioDbfsByBucket((await this.queryAudio({
|
|
23299
23562
|
deviceId: q.deviceId,
|
|
@@ -32855,6 +33118,12 @@ var en_default = {
|
|
|
32855
33118
|
"system.backup-completed.title": "Backup completed",
|
|
32856
33119
|
"system.backup-completed.body": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations ({{sizeMB}} MB)",
|
|
32857
33120
|
"system.backup-completed.body.partial": "Backup saved to {{destinationsOk}} of {{destinationsTotal}} destinations — {{destinationsFailed}} failed",
|
|
33121
|
+
"system.alert-critical.title": "{{title}}",
|
|
33122
|
+
"system.alert-critical.body": "{{message}}",
|
|
33123
|
+
"system.alert-error.title": "{{title}}",
|
|
33124
|
+
"system.alert-error.body": "{{message}}",
|
|
33125
|
+
"system.alert-critical-resolved.title": "Resolved: {{title}}",
|
|
33126
|
+
"system.alert-critical-resolved.body": "{{message}}",
|
|
32858
33127
|
"system.device-consumable-low.title": "Consumable low — {{deviceName}}",
|
|
32859
33128
|
"system.device-consumable-low.body": "{{item}} on {{deviceName}} is at {{percentage}}%",
|
|
32860
33129
|
"system.device-consumable-low.body.unmeasured": "{{item}} on {{deviceName}} needs replacing",
|
|
@@ -33235,6 +33504,12 @@ var it_default = {
|
|
|
33235
33504
|
"system.backup-completed.title": "Backup completato",
|
|
33236
33505
|
"system.backup-completed.body": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} ({{sizeMB}} MB)",
|
|
33237
33506
|
"system.backup-completed.body.partial": "Backup salvato su {{destinationsOk}} destinazioni su {{destinationsTotal}} — {{destinationsFailed}} fallite",
|
|
33507
|
+
"system.alert-critical.title": "{{title}}",
|
|
33508
|
+
"system.alert-critical.body": "{{message}}",
|
|
33509
|
+
"system.alert-error.title": "{{title}}",
|
|
33510
|
+
"system.alert-error.body": "{{message}}",
|
|
33511
|
+
"system.alert-critical-resolved.title": "Risolto: {{title}}",
|
|
33512
|
+
"system.alert-critical-resolved.body": "{{message}}",
|
|
33238
33513
|
"system.device-consumable-low.title": "Consumabile basso — {{deviceName}}",
|
|
33239
33514
|
"system.device-consumable-low.body": "{{item}} su {{deviceName}} è al {{percentage}}%",
|
|
33240
33515
|
"system.device-consumable-low.body.unmeasured": "{{item}} su {{deviceName}} va sostituito",
|
|
@@ -35210,6 +35485,7 @@ function systemEventSubject(kind, scope) {
|
|
|
35210
35485
|
if (kind === "node-updating") return `node-updating:${scope.nodeId ?? "hub"}:1.0.1`;
|
|
35211
35486
|
if (kind === "export-completed") return `export:${scope.deviceId ?? 0}`;
|
|
35212
35487
|
if (kind === "backup-completed") return "backup:test";
|
|
35488
|
+
if (kind.startsWith("alert-")) return "alert:test";
|
|
35213
35489
|
return `alarm:${scope.deviceId ?? 0}`;
|
|
35214
35490
|
}
|
|
35215
35491
|
/**
|
|
@@ -35350,7 +35626,12 @@ function planRuleTest(input) {
|
|
|
35350
35626
|
nodes: "hub",
|
|
35351
35627
|
fromVersion: "1.0.0",
|
|
35352
35628
|
toVersion: "1.0.1",
|
|
35353
|
-
title: "Export",
|
|
35629
|
+
title: systemEventKind.startsWith("alert-") ? "Recording location full" : "Export",
|
|
35630
|
+
message: "No free space left on the recording location",
|
|
35631
|
+
severity: systemEventKind === "alert-error" ? "error" : "critical",
|
|
35632
|
+
category: "recorder.placement",
|
|
35633
|
+
source: "recorder",
|
|
35634
|
+
actionUrl: "/system/addons",
|
|
35354
35635
|
count: "1"
|
|
35355
35636
|
},
|
|
35356
35637
|
...systemScope.deviceId !== void 0 ? { deviceId: systemScope.deviceId } : {},
|
|
@@ -51921,6 +52202,10 @@ function groupedAlarmPhaseKey(ruleId, trackId) {
|
|
|
51921
52202
|
var NO_MATCH_REPORT_INTERVAL_MS = 6e4;
|
|
51922
52203
|
var DEFAULT_DRAIN_INTERVAL_MS = 2e3;
|
|
51923
52204
|
var DEFAULT_RULE_RELOAD_INTERVAL_MS = 3e4;
|
|
52205
|
+
/** How often open critical/error alerts are reconciled against the notified episodes. */
|
|
52206
|
+
var NC_ALERT_RECONCILE_INTERVAL_MS = 5 * 6e4;
|
|
52207
|
+
/** The most alerts one reconcile reads (the alert centre's own ceiling). */
|
|
52208
|
+
var NC_ALERT_RECONCILE_LIMIT = 5e3;
|
|
51924
52209
|
var DEFAULT_RECONCILE_WINDOW_MS = 15 * 6e4;
|
|
51925
52210
|
/** Overlap subtracted from the watermark on boot (idempotent, cheap). */
|
|
51926
52211
|
var RECONCILE_OVERLAP_MS = 6e4;
|
|
@@ -52493,6 +52778,15 @@ var NotificationCenter = class {
|
|
|
52493
52778
|
* seeds, only a real flip notifies.
|
|
52494
52779
|
*/
|
|
52495
52780
|
liveness;
|
|
52781
|
+
/**
|
|
52782
|
+
* Which alert episodes were already notified (`alert-episode-ledger.ts`): the
|
|
52783
|
+
* alert centre sends nothing of its own, so this is what makes its bus
|
|
52784
|
+
* announcements and the reconcile from the alerts store safe to run together.
|
|
52785
|
+
*/
|
|
52786
|
+
alertEpisodes;
|
|
52787
|
+
/** The boot load of {@link alertEpisodes} has been ATTEMPTED (bus edges before it are the reconcile's). */
|
|
52788
|
+
alertEpisodesLoaded = false;
|
|
52789
|
+
alertReconcileTimer = null;
|
|
52496
52790
|
/** Batches `addon.updated` (version-bearing) into one notification per 30 s. */
|
|
52497
52791
|
addonUpdates;
|
|
52498
52792
|
/** Trailing 5 min hold-off for camera liveness flaps (plugin restart). */
|
|
@@ -53194,6 +53488,10 @@ var NotificationCenter = class {
|
|
|
53194
53488
|
...durableStore,
|
|
53195
53489
|
logger: this.logger.child("liveness")
|
|
53196
53490
|
});
|
|
53491
|
+
this.alertEpisodes = new NcAlertEpisodeLedger({
|
|
53492
|
+
...durableStore,
|
|
53493
|
+
logger: this.logger.child("alert-episodes")
|
|
53494
|
+
});
|
|
53197
53495
|
this.addonUpdates = new AddonUpdateCoalescer((updates) => {
|
|
53198
53496
|
const incoming = incomingFromAddonUpdates({
|
|
53199
53497
|
eventId: `addon-updated:${updates.map((u) => u.packageName).join(",")}`,
|
|
@@ -53590,6 +53888,10 @@ var NotificationCenter = class {
|
|
|
53590
53888
|
this.reloadTimer = setInterval(() => {
|
|
53591
53889
|
this.reloadRules();
|
|
53592
53890
|
}, this.deps.ruleReloadIntervalMs ?? DEFAULT_RULE_RELOAD_INTERVAL_MS);
|
|
53891
|
+
this.alertReconcileTimer = setInterval(() => {
|
|
53892
|
+
this.reconcileAlertEpisodes();
|
|
53893
|
+
}, this.deps.alertReconcileIntervalMs ?? NC_ALERT_RECONCILE_INTERVAL_MS);
|
|
53894
|
+
this.alertReconcileTimer.unref?.();
|
|
53593
53895
|
const timelapseScheduler = this.timelapseScheduler;
|
|
53594
53896
|
if (timelapseScheduler !== null) await timed("timelapseProducer", () => timelapseScheduler.start()).catch((err) => {
|
|
53595
53897
|
this.logger.warn("timelapse producer did not start", { meta: { error: String(err) } });
|
|
@@ -53705,6 +54007,7 @@ var NotificationCenter = class {
|
|
|
53705
54007
|
await this.deviceDirectory.refresh();
|
|
53706
54008
|
})) return;
|
|
53707
54009
|
if (!await phase("liveness", () => this.hydrateLiveness())) return;
|
|
54010
|
+
if (!await phase("alertEpisodes", () => this.hydrateAlertEpisodes())) return;
|
|
53708
54011
|
if (!await phase("deviceAlerts", async () => {
|
|
53709
54012
|
const rows = await this.alertStore.load();
|
|
53710
54013
|
this.logger.info("device alert ledger reseeded", { meta: { rows } });
|
|
@@ -53747,6 +54050,10 @@ var NotificationCenter = class {
|
|
|
53747
54050
|
clearInterval(this.reloadTimer);
|
|
53748
54051
|
this.reloadTimer = null;
|
|
53749
54052
|
}
|
|
54053
|
+
if (this.alertReconcileTimer !== null) {
|
|
54054
|
+
clearInterval(this.alertReconcileTimer);
|
|
54055
|
+
this.alertReconcileTimer = null;
|
|
54056
|
+
}
|
|
53750
54057
|
this.timelapseScheduler?.stop();
|
|
53751
54058
|
this.summaryProducer?.stop();
|
|
53752
54059
|
await this.closeGroupedWindowsOnStop();
|
|
@@ -54758,6 +55065,10 @@ var NotificationCenter = class {
|
|
|
54758
55065
|
return;
|
|
54759
55066
|
}
|
|
54760
55067
|
this.stateSummary.observeRawEvent(event.category, event.data, this.now(), event.source.nodeId);
|
|
55068
|
+
if (event.category === "alert.episode") {
|
|
55069
|
+
if (this.alertEpisodesLoaded) this.admitAlertEpisode(event, "bus");
|
|
55070
|
+
return;
|
|
55071
|
+
}
|
|
54761
55072
|
if (event.category === "addon.started" || event.category === "addon.restarted") return;
|
|
54762
55073
|
if (event.category === "pipeline.camera-assigned") return;
|
|
54763
55074
|
const battery = batterySliceOf(event);
|
|
@@ -54791,10 +55102,6 @@ var NotificationCenter = class {
|
|
|
54791
55102
|
const system = incoming.subject.systemEvent;
|
|
54792
55103
|
if (system !== void 0) this.stateSummary.observeSystem(system, this.now());
|
|
54793
55104
|
if (system !== void 0) this.updateLandings.observe(system, this.now());
|
|
54794
|
-
if (system !== void 0 && system.kind === "addon-crash-loop") {
|
|
54795
|
-
this.logger.debug("addon-crash-loop is pushed by the hub alert centre, not by a rule", { meta: { subject: system.subject } });
|
|
54796
|
-
return;
|
|
54797
|
-
}
|
|
54798
55105
|
const observation = system !== void 0 ? ledgeredLiveness(system) : null;
|
|
54799
55106
|
if (observation !== null) {
|
|
54800
55107
|
const previous = this.liveness.stateOf(observation.subject);
|
|
@@ -57446,6 +57753,89 @@ var NotificationCenter = class {
|
|
|
57446
57753
|
await this.occupancyStore.pruneExcept(this.activeOccupancyKeys());
|
|
57447
57754
|
}
|
|
57448
57755
|
/**
|
|
57756
|
+
* One edge of an alert episode, from the bus or from the reconcile. The
|
|
57757
|
+
* ledger decides whether it is news; only news reaches the rules.
|
|
57758
|
+
*/
|
|
57759
|
+
admitAlertEpisode(event, via) {
|
|
57760
|
+
const payload = parseAlertEpisodePayload(event.data);
|
|
57761
|
+
if (payload === null) {
|
|
57762
|
+
this.logger.warn("alert episode event with an unreadable payload ignored", { meta: {
|
|
57763
|
+
via,
|
|
57764
|
+
eventId: event.id
|
|
57765
|
+
} });
|
|
57766
|
+
return false;
|
|
57767
|
+
}
|
|
57768
|
+
const verdict = this.alertEpisodes.admit({
|
|
57769
|
+
kind: alertEpisodeKind(payload.severity, payload.phase),
|
|
57770
|
+
phase: payload.phase,
|
|
57771
|
+
episodeId: payload.episodeId,
|
|
57772
|
+
alertId: payload.alertId
|
|
57773
|
+
}, this.now());
|
|
57774
|
+
const meta = {
|
|
57775
|
+
via,
|
|
57776
|
+
alertId: payload.alertId,
|
|
57777
|
+
episodeId: payload.episodeId,
|
|
57778
|
+
phase: payload.phase,
|
|
57779
|
+
severity: payload.severity,
|
|
57780
|
+
verdict
|
|
57781
|
+
};
|
|
57782
|
+
if (verdict !== "notify") {
|
|
57783
|
+
this.logger.debug("alert episode edge not notified", { meta });
|
|
57784
|
+
return false;
|
|
57785
|
+
}
|
|
57786
|
+
const incoming = incomingFromSystemEvent(event, this.deviceDirectory.lookup());
|
|
57787
|
+
if (incoming === null) return false;
|
|
57788
|
+
this.logger.info("alert episode notified to the rules", { meta });
|
|
57789
|
+
this.consumeEvent(incoming);
|
|
57790
|
+
return true;
|
|
57791
|
+
}
|
|
57792
|
+
async hydrateAlertEpisodes() {
|
|
57793
|
+
try {
|
|
57794
|
+
const rows = await this.alertEpisodes.load();
|
|
57795
|
+
this.logger.info("alert episode ledger reseeded", { meta: { rows } });
|
|
57796
|
+
} finally {
|
|
57797
|
+
this.alertEpisodesLoaded = true;
|
|
57798
|
+
}
|
|
57799
|
+
await this.reconcileAlertEpisodes();
|
|
57800
|
+
}
|
|
57801
|
+
/**
|
|
57802
|
+
* The alerts store against the notified episodes (D11, D49): every OPEN
|
|
57803
|
+
* critical/error alert not yet notified is notified, and every notified
|
|
57804
|
+
* episode whose alert is CLOSED gets its resolution. Never runs on a ledger
|
|
57805
|
+
* the store has not answered — an empty, unread mirror would re-notify every
|
|
57806
|
+
* open alert — and never reads absence as closure: an alert that is not in
|
|
57807
|
+
* the answer is left alone (rows age out by {@link NC_ALERT_EPISODE_MAX_AGE_MS}).
|
|
57808
|
+
*/
|
|
57809
|
+
async reconcileAlertEpisodes() {
|
|
57810
|
+
const read = this.deps.readAlerts;
|
|
57811
|
+
if (read === void 0 || !this.evaluationActive || !this.alertEpisodes.hasAnswered()) return;
|
|
57812
|
+
let alerts;
|
|
57813
|
+
try {
|
|
57814
|
+
alerts = await read();
|
|
57815
|
+
} catch (err) {
|
|
57816
|
+
this.logger.warn("alert episode reconcile could not read the alerts", { meta: { error: errMsg(err) } });
|
|
57817
|
+
return;
|
|
57818
|
+
}
|
|
57819
|
+
let notified = 0;
|
|
57820
|
+
for (const alert of alerts) {
|
|
57821
|
+
const episodeId = alertEpisodeId(alert);
|
|
57822
|
+
const open = isAlertEpisodeOpen(alert.status);
|
|
57823
|
+
if (open && !isAlertEpisodeSeverity(alert.severity)) continue;
|
|
57824
|
+
if (!open && !this.alertEpisodes.has(episodeId)) continue;
|
|
57825
|
+
const phase = open ? "raised" : "resolved";
|
|
57826
|
+
if (this.admitAlertEpisode(createEvent(EventCategory.AlertEpisode, {
|
|
57827
|
+
type: "addon",
|
|
57828
|
+
id: "notification-center"
|
|
57829
|
+
}, alertEpisodePayload(alert, phase, this.now())), "reconcile")) notified += 1;
|
|
57830
|
+
}
|
|
57831
|
+
const pruned = alerts.length < NC_ALERT_RECONCILE_LIMIT ? await this.alertEpisodes.pruneOlderThan(this.now() - NC_ALERT_EPISODE_MAX_AGE_MS) : 0;
|
|
57832
|
+
if (notified > 0 || pruned > 0) this.logger.info("alert episodes reconciled", { meta: {
|
|
57833
|
+
alerts: alerts.length,
|
|
57834
|
+
notified,
|
|
57835
|
+
pruned
|
|
57836
|
+
} });
|
|
57837
|
+
}
|
|
57838
|
+
/**
|
|
57449
57839
|
* Boot reseed of the durable liveness ledger.
|
|
57450
57840
|
*
|
|
57451
57841
|
* The count is said OUT LOUD at info, because it is the one line that
|
|
@@ -94546,6 +94936,62 @@ function pickWritableMediaLocation(locations, stickyId) {
|
|
|
94546
94936
|
return writable.toSorted((a, b) => b.headroomBytes - a.headroomBytes || a.id.localeCompare(b.id))[0]?.id ?? null;
|
|
94547
94937
|
}
|
|
94548
94938
|
//#endregion
|
|
94939
|
+
//#region src/pipeline-analytics/event-media-write-location.ts
|
|
94940
|
+
var EventMediaWriteLocator = class {
|
|
94941
|
+
deps;
|
|
94942
|
+
cached = null;
|
|
94943
|
+
/**
|
|
94944
|
+
* The last location actually picked. Survives {@link invalidate}, so a ring
|
|
94945
|
+
* keeps writing where it was writing while that stays writable — the
|
|
94946
|
+
* stickiness the class default used to provide (D383).
|
|
94947
|
+
*/
|
|
94948
|
+
lastPick = null;
|
|
94949
|
+
constructor(deps) {
|
|
94950
|
+
this.deps = deps;
|
|
94951
|
+
}
|
|
94952
|
+
/** Drop the cached pick: the next write re-reads the location set. */
|
|
94953
|
+
invalidate() {
|
|
94954
|
+
this.cached = null;
|
|
94955
|
+
}
|
|
94956
|
+
async resolve(logger, deviceId) {
|
|
94957
|
+
if (this.cached !== null) return this.cached;
|
|
94958
|
+
let locations;
|
|
94959
|
+
try {
|
|
94960
|
+
locations = await this.deps.list();
|
|
94961
|
+
} catch (err) {
|
|
94962
|
+
logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
|
|
94963
|
+
this.cached = DEFAULT_MEDIA_LOCATION;
|
|
94964
|
+
return DEFAULT_MEDIA_LOCATION;
|
|
94965
|
+
}
|
|
94966
|
+
const picked = pickWritableMediaLocation(locations.map((l) => ({
|
|
94967
|
+
id: l.id,
|
|
94968
|
+
mode: resolveLocationMode(l),
|
|
94969
|
+
headroomBytes: l.capacity?.availableBytes ?? 0
|
|
94970
|
+
})), this.lastPick ?? void 0);
|
|
94971
|
+
if (picked === null) {
|
|
94972
|
+
const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
|
|
94973
|
+
logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
|
|
94974
|
+
tags: { deviceId },
|
|
94975
|
+
meta: { locationsSeen: seen }
|
|
94976
|
+
});
|
|
94977
|
+
throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
|
|
94978
|
+
}
|
|
94979
|
+
this.cached = picked;
|
|
94980
|
+
this.lastPick = picked;
|
|
94981
|
+
return picked;
|
|
94982
|
+
}
|
|
94983
|
+
};
|
|
94984
|
+
/**
|
|
94985
|
+
* Ring the locator from the location-set doorbell. `onRing` lets the caller drop
|
|
94986
|
+
* its other location-derived caches in the same breath.
|
|
94987
|
+
*/
|
|
94988
|
+
function subscribeEventMediaWriteLocator(eventBus, locator, onRing) {
|
|
94989
|
+
return eventBus.subscribe({ category: EventCategory.StorageLocationsChanged }, () => {
|
|
94990
|
+
locator.invalidate();
|
|
94991
|
+
onRing();
|
|
94992
|
+
});
|
|
94993
|
+
}
|
|
94994
|
+
//#endregion
|
|
94549
94995
|
//#region src/pipeline-analytics/media-relocate-engine.ts
|
|
94550
94996
|
/**
|
|
94551
94997
|
* Event-media relocation engine (entity-routing spec, Phase 4) — the
|
|
@@ -104539,7 +104985,8 @@ var NC_SYSTEM_EVENT_CATEGORIES = [
|
|
|
104539
104985
|
EventCategory.SystemRestartCompleted,
|
|
104540
104986
|
EventCategory.SystemRestarting,
|
|
104541
104987
|
EventCategory.RecordingExportCompleted,
|
|
104542
|
-
EventCategory.BackupRunCompleted
|
|
104988
|
+
EventCategory.BackupRunCompleted,
|
|
104989
|
+
EventCategory.AlertEpisode
|
|
104543
104990
|
];
|
|
104544
104991
|
/**
|
|
104545
104992
|
* Install the addon's bus subscriptions. Identical set + relative order to the
|
|
@@ -107427,6 +107874,15 @@ Object.freeze({
|
|
|
107427
107874
|
"valueType": "boolean"
|
|
107428
107875
|
}],
|
|
107429
107876
|
"dishwasher": [
|
|
107877
|
+
{
|
|
107878
|
+
"path": "availability",
|
|
107879
|
+
"valueType": "enum",
|
|
107880
|
+
"enumValues": [
|
|
107881
|
+
"available",
|
|
107882
|
+
"offline",
|
|
107883
|
+
"description-missing"
|
|
107884
|
+
]
|
|
107885
|
+
},
|
|
107430
107886
|
{
|
|
107431
107887
|
"path": "state",
|
|
107432
107888
|
"valueType": "enum",
|
|
@@ -107439,12 +107895,35 @@ Object.freeze({
|
|
|
107439
107895
|
"unavailable"
|
|
107440
107896
|
]
|
|
107441
107897
|
},
|
|
107898
|
+
{
|
|
107899
|
+
"path": "operationState",
|
|
107900
|
+
"valueType": "enum",
|
|
107901
|
+
"enumValues": [
|
|
107902
|
+
"Inactive",
|
|
107903
|
+
"Ready",
|
|
107904
|
+
"DelayedStart",
|
|
107905
|
+
"Run",
|
|
107906
|
+
"Pause",
|
|
107907
|
+
"ActionRequired",
|
|
107908
|
+
"Finished",
|
|
107909
|
+
"Error",
|
|
107910
|
+
"Aborting"
|
|
107911
|
+
]
|
|
107912
|
+
},
|
|
107442
107913
|
{
|
|
107443
107914
|
"path": "program",
|
|
107444
107915
|
"valueType": "string"
|
|
107445
107916
|
},
|
|
107446
107917
|
{
|
|
107447
|
-
"path": "
|
|
107918
|
+
"path": "selectedProgram",
|
|
107919
|
+
"valueType": "string"
|
|
107920
|
+
},
|
|
107921
|
+
{
|
|
107922
|
+
"path": "remainingSeconds",
|
|
107923
|
+
"valueType": "number"
|
|
107924
|
+
},
|
|
107925
|
+
{
|
|
107926
|
+
"path": "delayedStartSeconds",
|
|
107448
107927
|
"valueType": "number"
|
|
107449
107928
|
},
|
|
107450
107929
|
{
|
|
@@ -107455,14 +107934,30 @@ Object.freeze({
|
|
|
107455
107934
|
"path": "doorOpen",
|
|
107456
107935
|
"valueType": "boolean"
|
|
107457
107936
|
},
|
|
107937
|
+
{
|
|
107938
|
+
"path": "doorLocked",
|
|
107939
|
+
"valueType": "boolean"
|
|
107940
|
+
},
|
|
107458
107941
|
{
|
|
107459
107942
|
"path": "remoteStartAllowed",
|
|
107460
107943
|
"valueType": "boolean"
|
|
107461
107944
|
},
|
|
107945
|
+
{
|
|
107946
|
+
"path": "remoteControlActive",
|
|
107947
|
+
"valueType": "boolean"
|
|
107948
|
+
},
|
|
107949
|
+
{
|
|
107950
|
+
"path": "localControlActive",
|
|
107951
|
+
"valueType": "boolean"
|
|
107952
|
+
},
|
|
107462
107953
|
{
|
|
107463
107954
|
"path": "powerOn",
|
|
107464
107955
|
"valueType": "boolean"
|
|
107465
107956
|
},
|
|
107957
|
+
{
|
|
107958
|
+
"path": "powerState",
|
|
107959
|
+
"valueType": "string"
|
|
107960
|
+
},
|
|
107466
107961
|
{
|
|
107467
107962
|
"path": "saltLevel",
|
|
107468
107963
|
"valueType": "enum",
|
|
@@ -111046,20 +111541,6 @@ var MigrationMarkerStore = class MigrationMarkerStore {
|
|
|
111046
111541
|
};
|
|
111047
111542
|
};
|
|
111048
111543
|
//#endregion
|
|
111049
|
-
//#region src/pipeline-analytics/store/analytics-history-keys.ts
|
|
111050
|
-
/** Declared addon series keys (D850). */
|
|
111051
|
-
var ANALYTICS_HISTORY_BUCKET_SERIES = {
|
|
111052
|
-
motion: "addon:pipeline-analytics/motion",
|
|
111053
|
-
"audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
|
|
111054
|
-
occupancy: "addon:pipeline-analytics/occupancy"
|
|
111055
|
-
};
|
|
111056
|
-
var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
|
|
111057
|
-
/** Map the public cap enum to the device-history key (read path). */
|
|
111058
|
-
function historyKeyForLtsSeries(series) {
|
|
111059
|
-
if (series === "battery") return "cap:battery.percentage";
|
|
111060
|
-
return ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
111061
|
-
}
|
|
111062
|
-
//#endregion
|
|
111063
111544
|
//#region src/pipeline-analytics/store/analytics-history-producer.ts
|
|
111064
111545
|
var BUCKET_SERIES_META = [
|
|
111065
111546
|
{
|
|
@@ -111081,6 +111562,10 @@ var AUDIO_LEVEL_SERIES = {
|
|
|
111081
111562
|
displayName: "Audio level (dBFS)",
|
|
111082
111563
|
unit: "dBFS"
|
|
111083
111564
|
};
|
|
111565
|
+
/** One audio level point per window; equals device-history's 1 point / 10 s record bound. */
|
|
111566
|
+
var AUDIO_LEVEL_WINDOW_MS = 1e4;
|
|
111567
|
+
/** Stored for a measured-silent window (−∞ dBFS cannot be a history value). */
|
|
111568
|
+
var AUDIO_LEVEL_SILENT_DBFS = -120;
|
|
111084
111569
|
function refusalCounts(result) {
|
|
111085
111570
|
const counts = {};
|
|
111086
111571
|
for (const r of result.refused) counts[r.reason] = (counts[r.reason] ?? 0) + r.count;
|
|
@@ -111092,6 +111577,7 @@ var AnalyticsHistoryProducer = class {
|
|
|
111092
111577
|
logger;
|
|
111093
111578
|
nowFn;
|
|
111094
111579
|
declared = false;
|
|
111580
|
+
levelWindows = /* @__PURE__ */ new Map();
|
|
111095
111581
|
constructor(deps) {
|
|
111096
111582
|
this.deviceHistory = deps.deviceHistory;
|
|
111097
111583
|
this.logger = deps.logger;
|
|
@@ -111124,6 +111610,43 @@ var AnalyticsHistoryProducer = class {
|
|
|
111124
111610
|
deviceId
|
|
111125
111611
|
}, scope ?? "", value, at ?? this.nowFn());
|
|
111126
111612
|
}
|
|
111613
|
+
/**
|
|
111614
|
+
* Feed EVERY analysed audio sample. One point per window of
|
|
111615
|
+
* {@link AUDIO_LEVEL_WINDOW_MS} is written, stamped at the window's first
|
|
111616
|
+
* sample, carrying the window's PEAK dBFS: a 2 s spike inside a stable window
|
|
111617
|
+
* is not averaged away, and stable audio still yields a point each window
|
|
111618
|
+
* (continuity over dedup — the level bar draws a continuous line).
|
|
111619
|
+
*
|
|
111620
|
+
* Measured silence (−∞) is stored at {@link AUDIO_LEVEL_SILENT_DBFS}: it is a
|
|
111621
|
+
* measurement, not an absence (D431); NaN carries no claim and is dropped.
|
|
111622
|
+
* A window closes on the first sample past its end, so no timer is needed and
|
|
111623
|
+
* points are always at least a window apart (the history's 1 point / 10 s
|
|
111624
|
+
* bound).
|
|
111625
|
+
*/
|
|
111626
|
+
observeAudioLevel(deviceId, dbfs, at) {
|
|
111627
|
+
if (Number.isNaN(dbfs)) return;
|
|
111628
|
+
const value = Number.isFinite(dbfs) ? dbfs : AUDIO_LEVEL_SILENT_DBFS;
|
|
111629
|
+
const win = this.levelWindows.get(deviceId);
|
|
111630
|
+
if (win === void 0) {
|
|
111631
|
+
this.levelWindows.set(deviceId, {
|
|
111632
|
+
start: at,
|
|
111633
|
+
peak: value
|
|
111634
|
+
});
|
|
111635
|
+
return;
|
|
111636
|
+
}
|
|
111637
|
+
if (at - win.start < AUDIO_LEVEL_WINDOW_MS) {
|
|
111638
|
+
if (value > win.peak) this.levelWindows.set(deviceId, {
|
|
111639
|
+
start: win.start,
|
|
111640
|
+
peak: value
|
|
111641
|
+
});
|
|
111642
|
+
return;
|
|
111643
|
+
}
|
|
111644
|
+
this.levelWindows.set(deviceId, {
|
|
111645
|
+
start: at,
|
|
111646
|
+
peak: value
|
|
111647
|
+
});
|
|
111648
|
+
this.recordAudioLevel(deviceId, win.peak, win.start);
|
|
111649
|
+
}
|
|
111127
111650
|
recordAudioLevel(deviceId, dbfs, at) {
|
|
111128
111651
|
const record = this.deviceHistory.record;
|
|
111129
111652
|
if (record === void 0) return;
|
|
@@ -113784,20 +114307,13 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
113784
114307
|
mediaRelocate = null;
|
|
113785
114308
|
mediaLocationStorage = null;
|
|
113786
114309
|
/**
|
|
113787
|
-
*
|
|
113788
|
-
*
|
|
113789
|
-
* `
|
|
113790
|
-
*
|
|
113791
|
-
* `enabled` flags only change via operator reconfig.
|
|
113792
|
-
*/
|
|
113793
|
-
eventMediaWriteLocationCache = null;
|
|
113794
|
-
/**
|
|
113795
|
-
* The last location this process actually picked for an `eventMedia` write.
|
|
113796
|
-
* Survives the cache invalidation a migration performs, so a refresh keeps
|
|
113797
|
-
* writing where it was writing while that stays writable — the stickiness the
|
|
113798
|
-
* class default used to provide before it was deleted (D383).
|
|
114310
|
+
* Where NEW `eventMedia` writes land. Its pick is cached per location-set
|
|
114311
|
+
* generation and dropped by the `StorageLocationsChanged` doorbell (D387) and
|
|
114312
|
+
* by `refreshStorageLocationsForMigration`, so a mode change or a repoint takes
|
|
114313
|
+
* effect without a restart.
|
|
113799
114314
|
*/
|
|
113800
|
-
|
|
114315
|
+
eventMediaWriteLocator = null;
|
|
114316
|
+
eventMediaLocationDoorbellUnsub = null;
|
|
113801
114317
|
mediaWriteGate = new MediaWriteGate();
|
|
113802
114318
|
storageMigrationLeaseId = null;
|
|
113803
114319
|
/** Per-camera history of LINKED-device sensor state changes (Part B). */
|
|
@@ -115837,33 +116353,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
115837
116353
|
}
|
|
115838
116354
|
/** One line per process: this repeats on every occupancy refresh otherwise. */
|
|
115839
116355
|
mediaOccupancyUnattributedLogged = false;
|
|
115840
|
-
|
|
115841
|
-
|
|
115842
|
-
|
|
115843
|
-
let locations;
|
|
115844
|
-
try {
|
|
115845
|
-
locations = await api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION });
|
|
115846
|
-
} catch (err) {
|
|
115847
|
-
logger.debug("pipeline-analytics: eventMedia location list unavailable — writing through the default ref", { meta: { error: errMsg(err) } });
|
|
115848
|
-
this.eventMediaWriteLocationCache = DEFAULT_MEDIA_LOCATION;
|
|
115849
|
-
return DEFAULT_MEDIA_LOCATION;
|
|
115850
|
-
}
|
|
115851
|
-
const picked = pickWritableMediaLocation(locations.map((l) => ({
|
|
115852
|
-
id: l.id,
|
|
115853
|
-
mode: resolveLocationMode(l),
|
|
115854
|
-
headroomBytes: l.capacity?.availableBytes ?? 0
|
|
115855
|
-
})), this.lastEventMediaWritePick ?? void 0);
|
|
115856
|
-
if (picked === null) {
|
|
115857
|
-
const seen = locations.length > 0 ? locations.map((l) => l.id).join(", ") : "(none registered)";
|
|
115858
|
-
logger.error("pipeline-analytics: no writable eventMedia storage location — refusing the write instead of failing far downstream as a filesystem error", {
|
|
115859
|
-
tags: { deviceId },
|
|
115860
|
-
meta: { locationsSeen: seen }
|
|
115861
|
-
});
|
|
115862
|
-
throw new Error(`no writable eventMedia storage location (seen: ${seen})`);
|
|
115863
|
-
}
|
|
115864
|
-
this.eventMediaWriteLocationCache = picked;
|
|
115865
|
-
this.lastEventMediaWritePick = picked;
|
|
115866
|
-
return picked;
|
|
116356
|
+
resolveEventMediaWriteLocation(api, logger, deviceId) {
|
|
116357
|
+
this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
|
|
116358
|
+
return this.eventMediaWriteLocator.resolve(logger, deviceId);
|
|
115867
116359
|
}
|
|
115868
116360
|
/** Constructs every SQLite-backed store plus the stationary/package-drop/
|
|
115869
116361
|
* sensor plumbing, in the exact pre-S7 order. Returns the non-null bundle
|
|
@@ -115964,6 +116456,9 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
115964
116456
|
resolveEventMediaLocation: (deviceId) => this.resolveEventMediaWriteLocation(api, logger, deviceId)
|
|
115965
116457
|
});
|
|
115966
116458
|
this.mediaStore = mediaStore;
|
|
116459
|
+
this.eventMediaWriteLocator ??= new EventMediaWriteLocator({ list: () => api.storage.listLocations.query({ type: DEFAULT_MEDIA_LOCATION }) });
|
|
116460
|
+
this.eventMediaLocationDoorbellUnsub?.();
|
|
116461
|
+
this.eventMediaLocationDoorbellUnsub = subscribeEventMediaWriteLocator(this.ctx.eventBus, this.eventMediaWriteLocator, () => this.mediaLocationStorage?.invalidateRoots());
|
|
115967
116462
|
const eventLedgerWiring = this.eventLedgerWiring(api.settingsStore);
|
|
115968
116463
|
const retrainFrames = new RetrainFrameStore({
|
|
115969
116464
|
store: api.settingsStore,
|
|
@@ -116114,6 +116609,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
116114
116609
|
const eventStore = new EventStore({
|
|
116115
116610
|
store: api.settingsStore,
|
|
116116
116611
|
logger: logger.child("EventStore"),
|
|
116612
|
+
audioLevelHistory: (input) => api.deviceHistory.query.query(input),
|
|
116117
116613
|
motionLedger: wireMotionEventLedger(eventLedgerWiring),
|
|
116118
116614
|
audioLedger: wireAudioEventLedger(eventLedgerWiring),
|
|
116119
116615
|
onMotionSample: (deviceId, regionCount, at) => lts.note("motion", deviceId, regionCount, at),
|
|
@@ -116546,6 +117042,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
116546
117042
|
coreBlocks: async () => (await api.coreBlocks.list.query({})).blocks,
|
|
116547
117043
|
nodeAddons: (nodeId) => api.nodes.getNodeAddons.query({ nodeId })
|
|
116548
117044
|
},
|
|
117045
|
+
readAlerts: () => api.alerts.list.query({ limit: 5e3 }),
|
|
116549
117046
|
getSnapshot: (deviceId) => api.snapshot.getSnapshot.query({ deviceId }),
|
|
116550
117047
|
recognitionOutlook: ({ deviceId, trackId, className, labelKind, now }) => recognitionOutlook({
|
|
116551
117048
|
className,
|
|
@@ -118559,6 +119056,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
118559
119056
|
this.alarmPanelConvergence?.stop();
|
|
118560
119057
|
this.alarmPanelConvergence = null;
|
|
118561
119058
|
for (const unsub of this.subscriptionUnsubs) unsub();
|
|
119059
|
+
this.eventMediaLocationDoorbellUnsub?.();
|
|
119060
|
+
this.eventMediaLocationDoorbellUnsub = null;
|
|
118562
119061
|
this.subscriptionUnsubs = [];
|
|
118563
119062
|
await Promise.all([this.drainSensorHandlers(), this.motionEpisodes?.closeAll("addon-shutdown")]);
|
|
118564
119063
|
await this.settleTrackFusions();
|
|
@@ -121586,6 +122085,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
121586
122085
|
score: topClassification.score
|
|
121587
122086
|
} : {}
|
|
121588
122087
|
}, this.audioMarkerConfig(settings));
|
|
122088
|
+
if (level !== void 0) this.lts?.observeAudioLevel(deviceId, level.dbfs, timestamp);
|
|
121589
122089
|
this.notificationCenter?.observeAudio(deviceId, ncAudioSampleFromFrame(frame, timestamp, settings));
|
|
121590
122090
|
const route = classifyAudioFrame(topClassification ? {
|
|
121591
122091
|
className: topClassification.macroClass,
|
|
@@ -121661,8 +122161,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
121661
122161
|
});
|
|
121662
122162
|
this.levelStateByDevice.set(deviceId, res.nextState);
|
|
121663
122163
|
if (!res.emit) return;
|
|
121664
|
-
const dbfs = level?.dbfs ?? Number.NEGATIVE_INFINITY;
|
|
121665
|
-
this.lts?.recordAudioLevel(deviceId, dbfs, timestamp);
|
|
121666
122164
|
}
|
|
121667
122165
|
/**
|
|
121668
122166
|
* Persist motion-analysis bus events into the EPISODE tracker (D475): a
|
|
@@ -125813,7 +126311,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
125813
126311
|
async refreshStorageLocationsForMigration(input) {
|
|
125814
126312
|
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
125815
126313
|
this.mediaLocationStorage?.invalidateRoots();
|
|
125816
|
-
this.
|
|
126314
|
+
this.eventMediaWriteLocator?.invalidate();
|
|
125817
126315
|
return { refreshed: true };
|
|
125818
126316
|
}
|
|
125819
126317
|
/**
|
|
@@ -126176,7 +126674,24 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
126176
126674
|
async startStorageMigrationMove(input) {
|
|
126177
126675
|
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
126178
126676
|
if (this.storageMigrationLeaseId !== input.leaseId) throw new Error("event-media storage migration lease does not match");
|
|
126179
|
-
return { jobId: this.
|
|
126677
|
+
return { jobId: await this.startMediaMover(input) };
|
|
126678
|
+
}
|
|
126679
|
+
/**
|
|
126680
|
+
* Start a media move only while no single-location repoint holds an overlapping
|
|
126681
|
+
* physical root (the storage orchestrator owns that claim). A shared recordings
|
|
126682
|
+
* root is the case: a repoint of another class there must not run beside us.
|
|
126683
|
+
*/
|
|
126684
|
+
startMediaMover(input) {
|
|
126685
|
+
const engine = this.mediaRelocate;
|
|
126686
|
+
if (!engine) throw new Error("media relocation unavailable");
|
|
126687
|
+
return startMoverUnderClearance({
|
|
126688
|
+
clearance: (locationIds) => this.ctx.api.storageMigration.moverClearance.query({ locationIds }),
|
|
126689
|
+
start: () => engine.start(input),
|
|
126690
|
+
cancel: (jobId) => {
|
|
126691
|
+
engine.cancel(jobId);
|
|
126692
|
+
},
|
|
126693
|
+
locationIds: [input.fromLocationId ?? "*", input.toLocationId]
|
|
126694
|
+
});
|
|
126180
126695
|
}
|
|
126181
126696
|
async getStorageMigrationMoveStatus(input) {
|
|
126182
126697
|
return this.mediaRelocate?.list().find((job) => job.jobId === input.jobId) ?? null;
|
|
@@ -126194,7 +126709,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
126194
126709
|
*/
|
|
126195
126710
|
async relocateMedia(input) {
|
|
126196
126711
|
if (!this.mediaRelocate) throw new Error("media relocation unavailable");
|
|
126197
|
-
const jobId = this.
|
|
126712
|
+
const jobId = await this.startMediaMover(input);
|
|
126198
126713
|
this.ctx.logger.info("pipeline-analytics: media relocate job enqueued", { meta: {
|
|
126199
126714
|
jobId,
|
|
126200
126715
|
mode: input.mode ?? "move",
|