@camstack/addon-post-analysis 1.2.601 → 1.2.602
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-DAZ9ie-f.js → clip-model-registry-D0-VNIW5.js} +381 -8
- package/dist/{clip-model-registry-BuM8eXlN.mjs → clip-model-registry-lQ7QWjLf.mjs} +376 -9
- 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-DAs_96VP.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BPA0T5-F.mjs} +2 -2
- package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-1AlitJg2.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-Cfj8Mv5X.mjs +26 -0
- package/dist/pipeline-analytics/{hostInit-C3wjY7W8.mjs → hostInit-DIjMiIYW.mjs} +2 -2
- package/dist/pipeline-analytics/index.js +281 -503
- package/dist/pipeline-analytics/index.mjs +281 -503
- 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-CElkWYPg.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-SRvLcL_J.mjs +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as MediaFileKindEnum, $i as record, $n as deleteAlarmProfile, $t as NcTemplateVarDescriptorSchema, A as DeclaredDevices, Ai as errMsg, An as TrackSideSchema, Ar as isRootSuppliableStepId, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as nodePin, Bn as armRemovalReconcile, Br as occupancyScope, Bt as NcRuleSchema, C as ClearTrackRelationResultSchema, Ci as viewerLinkBaseOf, Cn as TRACK_RELATIONS_DEFAULT_LIMIT, Cr as isDetectionMacroClass, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as evaluateSensorEdge, Dn as TrackPuritySchema, Dr as isLabelSupplyStepId, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ei as canonicalHash, En as TimelapseRuleSchema, Er as isKnownTemplateVar, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as customAction, Fn as alarmPanelCapability, Fr as kebabToCamel, Ft as NcLegacyRuleActionSequenceSchema, G as ListTrackRelationsResultSchema, Gi as array, Gn as buildViewerLink, Gr as readDeviceStateFrom, Gt as NcSnoozeInputSchema, H as LABEL_SUPPLY_MACRO, Hi as sleep$1, Hn as audioMetricsCapability, Hr as pickFaceRescoreIntervalMinutes, Ht as NcRuleUpdateInputSchema, I as EVENT_KIND_BY_CAP, Ii as defineCustomActions, In as applyBlockingEdit, Ir as linkedStepOf, It as NcRuleActionSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as literal, Jn as cloneAlarmSettings, Jr as renameAlarmProfile, Jt as NcStateSummarySectionIdSchema, K as LockStateSchema, Ki as boolean, Kn as calendarEventLocalSpan, Kr as readMotionTriggeredBy, Kt as NcSnoozeSchema, L as EVENT_OWNER_TYPES, Li as defineRecordKinds, Ln as applyModeDelaysEdits, Lr as mayWriteToLocation, Lt as NcRuleActionsSchema, M as EMBEDDING_MODEL_FAILED, Mi as CamProfileSchema, Mn as activateAlarmProfile, Mr as isSystemDelivery, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as RECORD_MAX_DATA_BYTES, Nn as addonWidgetsSourceCapability, Nr as isSystemEventSummaryRule, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as isSourceCap, On as TrackRelationKindSchema, Or as isMediaOwnerType, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as createEvent, Pn as alarmDeviceSecureOnArm, Pr as isVectorIndexDimConflict, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as partialRecord, Qn as createAlarmProfile, Qr as resolveModeNotifyPolicy, Qt as NcTemplatePreviewSchema, R as EXPORT_DENSE_MAX_RANGES, Ri as hydrateSchema, Rn as applyNonBlockingEdit, Rr as memoizePinnedFfmpeg, Rt as NcRuleInputSchema, S as ClearTrackRelationInputSchema, Si as vectorDimFromBase64, Sn as TIMELAPSE_DENSE_FLOOR_SEC, Sr as isDefaultAlarmProfileId, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ti as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Tn as TimelapseRulePatchSchema, Tr as isEventOwnerType, Tt as NC_TANK_SYSTEM_EVENT_KINDS, U as LabelAttributionSchema, Ui as ZodObject, Un as audioModeOf, Ur as pipelineAnalyticsCapability, Ut as NcScheduleSchema, V as FailureCounters, Vn as assertTimelapseCadences, Vt as NcRuleTargetSchema, W as ListTrackRelationsInputSchema, Wi as _enum, Wn as buildEventKindDescriptor, Wr as plateGalleryCapability, Wt as NcSharedSequenceSchema, X as MAX_TRACK_PURITIES, Xi as number, Xn as coreBlockIdFromAddonId, Xr as resolveLocationMode, Xt as NcTaxonomySchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as looseObject, Yn as consumableLowPercentOf, Yr as resolveClipSearchMinScore, Yt as NcSystemEventKindSchema, Z as MAX_TRACK_RELATIONS, Zi as object, Zn as cosineSimilarity$1, Zr as resolveModeDelays, Zt as NcTemplatePreviewInputSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as systemEventFilterApplies, _n as SCENE_RESET_RECAPTURES, _r as gifDeriveSpeed, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as unknown, ai as ruleGroupingApplies, an as RebuildObjectEmbeddingsRefusalSchema, ar as deviceRemovalHookCapability, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, bi as templateCarriesSummary, bn as SetTrackPurityResultSchema, br as isAppLinkBase, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ci as secureActuationFor, cn as RetrainStatusSchema, cr as embeddingRefusalOf, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, di as storageOccupancyCapability, dn as SCENE_DEFAULT_ANCHOR_THRESHOLD, dr as encodeVectorBase64, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as strictObject, ei as retrainRevisionConflictMessage, en as OPS_LOG_DEFAULT_LIMIT, er as deriveDetailCropRect, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as subKindsOf, fn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, fr as ensureAlarmProfiles, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, gi as systemEventFamilyProblems, gn as SCENE_DIVERGED, gr as failureContributionCapability, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, hi as systemEventDeliveryProblems, hn as SCENE_DEFAULT_UNCOVERED_POLICY, hr as faceGalleryCapability, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as union, ii as ruleDeviceCompatCapQueries, in as RECORDING_SOURCE_CAMSTACK_ADDON, ir as describeSystemEventFamilyProblem, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as BaseAddon, jn as TrackSourceSchema, jr as isScheduleActive, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as DeviceType, kn as TrackRelationSchema, kr as isRetiredClearKey, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, li as sensorRaisedOf, ln as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, mi as syncActiveAlarmProfileSettings, mn as SCENE_DEFAULT_QUIET_SECONDS, mr as evictionPolicyOfLocation, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as templateLiteral, ni as ruleAutoTriggersAlarm, nr as deriveRecordingMode, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as EventCategory, oi as ruleKindOf, on as RetrainDismissedSchema, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as summarizeReview, pn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, pr as eventKindLabel, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as discriminatedUnion, qn as classifyRetrainReview, qr as readTimelapseGeneratedAt, qt as NcSnoozeSuppressedSchema, ra as tuple, ri as ruleBelongsToAlarm$1, rn as RECORDING_EXPORT_MAX_READ_BYTES, rr as describeSystemEventDeliveryProblem, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, si as sceneMonitorCapability, sn as RetrainOriginSchema, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as string, ti as rootSupplyServes, tn as OpsLogEntrySchema, tr as derivePostTriggeredAction, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ui as servedTemplateVars, un as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, vi as systemEventSummaryScheduleOf, vn as SceneMonitorSchema, vr as gifFromMp4Args, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wi as zoneAnalyticsCapability, wn as TimelapseRuleInputSchema, wr as isEventKindLabelKey, wt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, x as COCO_TO_MACRO, xi as templateVarsFor, xn as SetTrackRelationInputSchema, xr as isAudioClassName, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, yi as systemEventSummarySplitOf, yn as SetTrackPurityInputSchema, yr as isAlarmControllableCap, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as isDeviceScopedCap, zn as applySecureOnArmEdit, zr as notificationRulesCapability, zt as NcRulePatchSchema } from "../clip-model-registry-lQ7QWjLf.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";
|
|
@@ -6930,17 +6930,6 @@ function ncAudioSampleFromFrame(frame, timestampMs, floor) {
|
|
|
6930
6930
|
...frame.classifyOutcome !== void 0 ? { classifyOutcome: frame.classifyOutcome } : {}
|
|
6931
6931
|
};
|
|
6932
6932
|
}
|
|
6933
|
-
/** The level-path audio row for one frame — the one row kind with no label to explain it. */
|
|
6934
|
-
function levelAudioEventDraft(deviceId, timestamp, frame) {
|
|
6935
|
-
return {
|
|
6936
|
-
deviceId,
|
|
6937
|
-
timestamp,
|
|
6938
|
-
kind: "audio",
|
|
6939
|
-
rms: frame.level?.rms ?? 0,
|
|
6940
|
-
dbfs: frame.level?.dbfs ?? 0,
|
|
6941
|
-
...frame.classifyOutcome !== void 0 ? { classifyOutcome: frame.classifyOutcome } : {}
|
|
6942
|
-
};
|
|
6943
|
-
}
|
|
6944
6933
|
//#endregion
|
|
6945
6934
|
//#region src/pipeline-analytics/scene/condition-resolver.ts
|
|
6946
6935
|
var DAY = "day";
|
|
@@ -10922,7 +10911,7 @@ var NC_LIVENESS_SPEC = {
|
|
|
10922
10911
|
writeMode: "write-behind",
|
|
10923
10912
|
keyOf: (row) => row.subject,
|
|
10924
10913
|
toValue: rowToValue$1,
|
|
10925
|
-
fromRecord: recordToRow$
|
|
10914
|
+
fromRecord: recordToRow$1,
|
|
10926
10915
|
equalFact: (held, incoming) => held.state === incoming.state,
|
|
10927
10916
|
deviceIdOf: (row) => row.deviceId
|
|
10928
10917
|
};
|
|
@@ -11109,7 +11098,7 @@ function rowToValue$1(row) {
|
|
|
11109
11098
|
* (silent) rather than hydrating a state nothing can ever equal, which would
|
|
11110
11099
|
* make every future event a phantom flip.
|
|
11111
11100
|
*/
|
|
11112
|
-
function recordToRow$
|
|
11101
|
+
function recordToRow$1(subject, data) {
|
|
11113
11102
|
if (subject.length === 0) return null;
|
|
11114
11103
|
const state = data["state"];
|
|
11115
11104
|
if (state !== "online" && state !== "offline") return null;
|
|
@@ -12590,7 +12579,7 @@ var NC_OCCUPANCY_SPEC = {
|
|
|
12590
12579
|
writeMode: "write-through",
|
|
12591
12580
|
keyOf: (row) => row.key,
|
|
12592
12581
|
toValue: rowToValue,
|
|
12593
|
-
fromRecord: recordToRow
|
|
12582
|
+
fromRecord: recordToRow,
|
|
12594
12583
|
deviceIdOf: (row) => row.deviceId
|
|
12595
12584
|
};
|
|
12596
12585
|
var OccupancyStore = class {
|
|
@@ -12667,7 +12656,7 @@ function rowToValue(row) {
|
|
|
12667
12656
|
* {@link parseOccupancyKey} rejects — so the caller skips it (cold re-observe)
|
|
12668
12657
|
* rather than hydrating a mis-parsed scope.
|
|
12669
12658
|
*/
|
|
12670
|
-
function recordToRow
|
|
12659
|
+
function recordToRow(key, data) {
|
|
12671
12660
|
const scope = parseOccupancyKey(key);
|
|
12672
12661
|
if (scope === null) return null;
|
|
12673
12662
|
const deviceId = Number(data["deviceId"]);
|
|
@@ -50520,7 +50509,7 @@ function previewBlurSigma(frameWidth) {
|
|
|
50520
50509
|
}
|
|
50521
50510
|
/** Break a word too long for one line into line-width pieces. Without this a
|
|
50522
50511
|
* single 200-character token wraps to nothing and overflows both edges. */
|
|
50523
|
-
function chunk$
|
|
50512
|
+
function chunk$1(word, size) {
|
|
50524
50513
|
const pieces = [];
|
|
50525
50514
|
for (let i = 0; i < word.length; i += size) pieces.push(word.slice(i, i + size));
|
|
50526
50515
|
return pieces;
|
|
@@ -50528,7 +50517,7 @@ function chunk$2(word, size) {
|
|
|
50528
50517
|
/** Greedy word wrap at a character budget — the only measurement available,
|
|
50529
50518
|
* since librsvg lays the text out long after this string is written. */
|
|
50530
50519
|
function wrapLines(text, maxChars) {
|
|
50531
|
-
return text.split(" ").filter((w) => w.length > 0).reduce((lines, word) => (word.length > maxChars ? chunk$
|
|
50520
|
+
return text.split(" ").filter((w) => w.length > 0).reduce((lines, word) => (word.length > maxChars ? chunk$1(word, maxChars) : [word]).reduce((acc, piece) => {
|
|
50532
50521
|
const last = acc.at(-1);
|
|
50533
50522
|
if (last === void 0) return [piece];
|
|
50534
50523
|
const merged = `${last} ${piece}`;
|
|
@@ -60438,7 +60427,7 @@ async function readRescoreFingerprint(ports) {
|
|
|
60438
60427
|
clusterModelId
|
|
60439
60428
|
};
|
|
60440
60429
|
}
|
|
60441
|
-
function chunk
|
|
60430
|
+
function chunk(items, size) {
|
|
60442
60431
|
const out = [];
|
|
60443
60432
|
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
60444
60433
|
return out;
|
|
@@ -60560,7 +60549,7 @@ var FaceRescoreJob = class {
|
|
|
60560
60549
|
fingerprint: fingerprint.combined
|
|
60561
60550
|
});
|
|
60562
60551
|
const paramsByDevice = /* @__PURE__ */ new Map();
|
|
60563
|
-
for (const batchIds of chunk
|
|
60552
|
+
for (const batchIds of chunk(ids, RESCORE_BATCH_SIZE)) {
|
|
60564
60553
|
if (this.stopped) throw new Error("stopped before completion");
|
|
60565
60554
|
const storeStart = this.ports.now();
|
|
60566
60555
|
const batch = await this.ports.loadRescoreBatch(batchIds);
|
|
@@ -98265,431 +98254,6 @@ var SweepCursorStore = class SweepCursorStore {
|
|
|
98265
98254
|
}
|
|
98266
98255
|
};
|
|
98267
98256
|
//#endregion
|
|
98268
|
-
//#region src/durable/lts-aggregator.ts
|
|
98269
|
-
/** Wall-clock bucket width. Five minutes, matching HA's statistics tier. */
|
|
98270
|
-
var LTS_BUCKET_MS = 5 * 6e4;
|
|
98271
|
-
/**
|
|
98272
|
-
* Row cap per (subject, series, scope) — one year of five-minute buckets, the
|
|
98273
|
-
* same number HA's own docs use. At ~60 bytes a row that is ~6 MB per series
|
|
98274
|
-
* per camera-year, against a `media` table already measured in hundreds of MB.
|
|
98275
|
-
*/
|
|
98276
|
-
var LTS_MAX_ROWS_PER_SERIES = 105120;
|
|
98277
|
-
/** Buckets written per transaction — bounds one statement, not the flush. */
|
|
98278
|
-
var FLUSH_BATCH = 200;
|
|
98279
|
-
/** How often the row cap is enforced. Rarely: it is a bound, not a deadline. */
|
|
98280
|
-
var CAP_SWEEP_INTERVAL_MS = 6 * 36e5;
|
|
98281
|
-
/**
|
|
98282
|
-
* The row's identity IS its four key columns (D871): a composite
|
|
98283
|
-
* `primaryKey` + `WITHOUT ROWID`, so the table is its own index and there is no
|
|
98284
|
-
* TEXT `id` repeating what the row already says. The old layout kept
|
|
98285
|
-
* `<subject>|<series>|<scope>|<bucketStart>` as a TEXT primary key beside the
|
|
98286
|
-
* four columns — 85 MB of rows plus a 44 MB autoindex for 1.07 M rows. A
|
|
98287
|
-
* composite-key collection is written with `upsertMany` (a bucket flushed twice
|
|
98288
|
-
* replaces itself) and read with `query` / `count` / `deleteWhere`; the calls
|
|
98289
|
-
* that address one row by id are refused by the engine.
|
|
98290
|
-
*/
|
|
98291
|
-
var LTS_KEY_COLUMNS = [
|
|
98292
|
-
"subject",
|
|
98293
|
-
"series",
|
|
98294
|
-
"scope",
|
|
98295
|
-
"bucketStart"
|
|
98296
|
-
];
|
|
98297
|
-
var LTS_COLUMNS = [
|
|
98298
|
-
(
|
|
98299
|
-
/** The camera id, or the node id. One column, because every query is
|
|
98300
|
-
* "this thing over time" and the thing is one or the other. */
|
|
98301
|
-
{
|
|
98302
|
-
name: "subject",
|
|
98303
|
-
type: "TEXT"
|
|
98304
|
-
}),
|
|
98305
|
-
(
|
|
98306
|
-
/** Numeric mirror of `subject` for camera rows, so a per-camera question is
|
|
98307
|
-
* answered by an integer index — every log line and every query about a
|
|
98308
|
-
* device in this repo is keyed by the numeric id. `NULL` for node rows. */
|
|
98309
|
-
{
|
|
98310
|
-
name: "deviceId",
|
|
98311
|
-
type: "INTEGER"
|
|
98312
|
-
}),
|
|
98313
|
-
{
|
|
98314
|
-
name: "series",
|
|
98315
|
-
type: "TEXT"
|
|
98316
|
-
},
|
|
98317
|
-
(
|
|
98318
|
-
/** Sub-scope within a series: a zoneId for occupancy, `''` otherwise. */
|
|
98319
|
-
{
|
|
98320
|
-
name: "scope",
|
|
98321
|
-
type: "TEXT"
|
|
98322
|
-
}),
|
|
98323
|
-
(
|
|
98324
|
-
/** Bucket start, wall-clock aligned to {@link LTS_BUCKET_MS}. */
|
|
98325
|
-
{
|
|
98326
|
-
name: "bucketStart",
|
|
98327
|
-
type: "INTEGER"
|
|
98328
|
-
}),
|
|
98329
|
-
{
|
|
98330
|
-
name: "samples",
|
|
98331
|
-
type: "INTEGER",
|
|
98332
|
-
notNull: true
|
|
98333
|
-
},
|
|
98334
|
-
{
|
|
98335
|
-
name: "sum",
|
|
98336
|
-
type: "REAL",
|
|
98337
|
-
notNull: true
|
|
98338
|
-
},
|
|
98339
|
-
{
|
|
98340
|
-
name: "min",
|
|
98341
|
-
type: "REAL",
|
|
98342
|
-
notNull: true
|
|
98343
|
-
},
|
|
98344
|
-
{
|
|
98345
|
-
name: "max",
|
|
98346
|
-
type: "REAL",
|
|
98347
|
-
notNull: true
|
|
98348
|
-
}
|
|
98349
|
-
];
|
|
98350
|
-
/**
|
|
98351
|
-
* The primary key serves every per-subject read (`subject, series, scope` then a
|
|
98352
|
-
* `bucketStart` range, already in bucket order); this index serves the read that
|
|
98353
|
-
* names a series without a subject. The old `deviceId` index went with the TEXT
|
|
98354
|
-
* id: nothing queries a series by the numeric mirror.
|
|
98355
|
-
*/
|
|
98356
|
-
var LTS_INDEXES = [{
|
|
98357
|
-
name: "idx_lts_series_bucket",
|
|
98358
|
-
columns: ["series", "bucketStart"]
|
|
98359
|
-
}];
|
|
98360
|
-
function ltsBucketStart(at, bucketMs = LTS_BUCKET_MS) {
|
|
98361
|
-
return Math.floor(at / bucketMs) * bucketMs;
|
|
98362
|
-
}
|
|
98363
|
-
var LtsAggregator = class {
|
|
98364
|
-
open = /* @__PURE__ */ new Map();
|
|
98365
|
-
/** Every (subject, series, scope) that has produced a row in this process —
|
|
98366
|
-
* the groups the cap sweep has any reason to look at. */
|
|
98367
|
-
groups = /* @__PURE__ */ new Set();
|
|
98368
|
-
lastCapSweepAt = 0;
|
|
98369
|
-
collection;
|
|
98370
|
-
store;
|
|
98371
|
-
logger;
|
|
98372
|
-
nowFn;
|
|
98373
|
-
bucketMs;
|
|
98374
|
-
maxRows;
|
|
98375
|
-
constructor(deps) {
|
|
98376
|
-
this.collection = deps.collection;
|
|
98377
|
-
this.store = deps.store;
|
|
98378
|
-
this.logger = deps.logger;
|
|
98379
|
-
this.nowFn = deps.now ?? (() => Date.now());
|
|
98380
|
-
this.bucketMs = deps.bucketMs ?? LTS_BUCKET_MS;
|
|
98381
|
-
this.maxRows = deps.maxRowsPerSeries ?? LTS_MAX_ROWS_PER_SERIES;
|
|
98382
|
-
}
|
|
98383
|
-
static declare(store, collection) {
|
|
98384
|
-
return store.declareCollection.mutate({
|
|
98385
|
-
collection,
|
|
98386
|
-
columns: [...LTS_COLUMNS],
|
|
98387
|
-
primaryKey: [...LTS_KEY_COLUMNS],
|
|
98388
|
-
withoutRowid: true,
|
|
98389
|
-
indexes: [...LTS_INDEXES]
|
|
98390
|
-
});
|
|
98391
|
-
}
|
|
98392
|
-
/**
|
|
98393
|
-
* Record one observation. Synchronous, allocation-free after the first
|
|
98394
|
-
* sample of a bucket, and it cannot throw — it sits on paths that are
|
|
98395
|
-
* already producing the value for another reason and must not learn a new
|
|
98396
|
-
* failure mode.
|
|
98397
|
-
*
|
|
98398
|
-
* A non-finite value is DROPPED rather than folded in: one `NaN` would make
|
|
98399
|
-
* `sum`, `min` and `max` all `NaN` for the whole bucket, turning a skewed
|
|
98400
|
-
* row into a meaningless one.
|
|
98401
|
-
*/
|
|
98402
|
-
note(input) {
|
|
98403
|
-
if (!Number.isFinite(input.value)) return;
|
|
98404
|
-
const at = input.at ?? this.nowFn();
|
|
98405
|
-
const scope = input.scope ?? "";
|
|
98406
|
-
const bucketStart = ltsBucketStart(at, this.bucketMs);
|
|
98407
|
-
const key = rowId(input.subject, input.series, scope, bucketStart);
|
|
98408
|
-
const held = this.open.get(key);
|
|
98409
|
-
if (held === void 0) {
|
|
98410
|
-
this.open.set(key, {
|
|
98411
|
-
subject: input.subject,
|
|
98412
|
-
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
98413
|
-
series: input.series,
|
|
98414
|
-
scope,
|
|
98415
|
-
bucketStart,
|
|
98416
|
-
samples: 1,
|
|
98417
|
-
sum: input.value,
|
|
98418
|
-
min: input.value,
|
|
98419
|
-
max: input.value
|
|
98420
|
-
});
|
|
98421
|
-
return;
|
|
98422
|
-
}
|
|
98423
|
-
held.samples += 1;
|
|
98424
|
-
held.sum += input.value;
|
|
98425
|
-
if (input.value < held.min) held.min = input.value;
|
|
98426
|
-
if (input.value > held.max) held.max = input.value;
|
|
98427
|
-
}
|
|
98428
|
-
/** Buckets currently accumulating — diagnostics, and what a flush would write. */
|
|
98429
|
-
openBuckets() {
|
|
98430
|
-
return [...this.open.values()].map(toRow);
|
|
98431
|
-
}
|
|
98432
|
-
/**
|
|
98433
|
-
* Write every bucket that has CLOSED (its window ended at or before `now`)
|
|
98434
|
-
* and drop it from RAM. Returns the number of rows written.
|
|
98435
|
-
*
|
|
98436
|
-
* The current bucket is deliberately left alone: writing it early would mean
|
|
98437
|
-
* rewriting it on the next tick, which turns one row into up to sixty and
|
|
98438
|
-
* offers every one of them to the checkpoint lottery (D96).
|
|
98439
|
-
*/
|
|
98440
|
-
async flushDue(now = this.nowFn()) {
|
|
98441
|
-
const currentBucket = ltsBucketStart(now, this.bucketMs);
|
|
98442
|
-
const due = [...this.open.values()].filter((b) => b.bucketStart < currentBucket);
|
|
98443
|
-
if (due.length === 0) {
|
|
98444
|
-
await this.maybeSweepCap(now);
|
|
98445
|
-
return 0;
|
|
98446
|
-
}
|
|
98447
|
-
let written = 0;
|
|
98448
|
-
for (const batch of chunk(due, FLUSH_BATCH)) {
|
|
98449
|
-
written += await this.writeBatch(batch);
|
|
98450
|
-
for (const bucket of batch) this.open.delete(rowId(bucket.subject, bucket.series, bucket.scope, bucket.bucketStart));
|
|
98451
|
-
}
|
|
98452
|
-
await this.maybeSweepCap(now);
|
|
98453
|
-
return written;
|
|
98454
|
-
}
|
|
98455
|
-
/**
|
|
98456
|
-
* Upsert one batch in a single transaction. If the batch is refused as a
|
|
98457
|
-
* whole (all-or-nothing), each bucket is retried alone so one bad row costs
|
|
98458
|
-
* one interval, not forty. Returns the number of rows written.
|
|
98459
|
-
*/
|
|
98460
|
-
async writeBatch(batch) {
|
|
98461
|
-
try {
|
|
98462
|
-
await this.upsert(batch);
|
|
98463
|
-
for (const bucket of batch) this.groups.add(groupKey(bucket.subject, bucket.series, bucket.scope));
|
|
98464
|
-
return batch.length;
|
|
98465
|
-
} catch (err) {
|
|
98466
|
-
if (batch.length > 1) {
|
|
98467
|
-
let written = 0;
|
|
98468
|
-
for (const bucket of batch) written += await this.writeBatch([bucket]);
|
|
98469
|
-
return written;
|
|
98470
|
-
}
|
|
98471
|
-
const bucket = batch[0];
|
|
98472
|
-
this.logger.warn("lts bucket write failed — this interval will be missing", {
|
|
98473
|
-
...bucket?.deviceId !== void 0 ? { tags: { deviceId: bucket.deviceId } } : {},
|
|
98474
|
-
meta: {
|
|
98475
|
-
collection: this.collection,
|
|
98476
|
-
series: bucket?.series,
|
|
98477
|
-
subject: bucket?.subject,
|
|
98478
|
-
bucketStart: bucket?.bucketStart,
|
|
98479
|
-
error: String(err)
|
|
98480
|
-
}
|
|
98481
|
-
});
|
|
98482
|
-
return 0;
|
|
98483
|
-
}
|
|
98484
|
-
}
|
|
98485
|
-
async upsert(batch) {
|
|
98486
|
-
await this.store.upsertMany.mutate({
|
|
98487
|
-
collection: this.collection,
|
|
98488
|
-
conflict: [...LTS_KEY_COLUMNS],
|
|
98489
|
-
records: batch.map((bucket) => ({ data: {
|
|
98490
|
-
subject: bucket.subject,
|
|
98491
|
-
deviceId: bucket.deviceId ?? null,
|
|
98492
|
-
series: bucket.series,
|
|
98493
|
-
scope: bucket.scope,
|
|
98494
|
-
bucketStart: bucket.bucketStart,
|
|
98495
|
-
samples: bucket.samples,
|
|
98496
|
-
sum: bucket.sum,
|
|
98497
|
-
min: bucket.min,
|
|
98498
|
-
max: bucket.max
|
|
98499
|
-
} }))
|
|
98500
|
-
});
|
|
98501
|
-
}
|
|
98502
|
-
/** Read closed buckets back. The read surface every chart will use. */
|
|
98503
|
-
async read(query = {}) {
|
|
98504
|
-
const where = {};
|
|
98505
|
-
if (query.series !== void 0) where["series"] = query.series;
|
|
98506
|
-
if (query.subject !== void 0) where["subject"] = query.subject;
|
|
98507
|
-
if (query.scope !== void 0) where["scope"] = query.scope;
|
|
98508
|
-
const records = await this.store.query.query({
|
|
98509
|
-
collection: this.collection,
|
|
98510
|
-
filter: {
|
|
98511
|
-
...Object.keys(where).length > 0 ? { where } : {},
|
|
98512
|
-
...query.from !== void 0 || query.to !== void 0 ? { whereBetween: { bucketStart: [query.from ?? 0, query.to ?? Number.MAX_SAFE_INTEGER] } } : {},
|
|
98513
|
-
orderBy: {
|
|
98514
|
-
field: "bucketStart",
|
|
98515
|
-
direction: "asc"
|
|
98516
|
-
},
|
|
98517
|
-
limit: query.limit ?? 5e3
|
|
98518
|
-
}
|
|
98519
|
-
});
|
|
98520
|
-
const rows = [];
|
|
98521
|
-
for (const record of records) {
|
|
98522
|
-
const row = recordToRow(record.data);
|
|
98523
|
-
if (row !== null) rows.push(row);
|
|
98524
|
-
}
|
|
98525
|
-
return rows;
|
|
98526
|
-
}
|
|
98527
|
-
/**
|
|
98528
|
-
* Enforce the per-(subject, series, scope) row cap, at most once every six
|
|
98529
|
-
* hours. Only groups this process has written to are examined: a group with
|
|
98530
|
-
* no new rows cannot have crossed a cap it was under.
|
|
98531
|
-
*/
|
|
98532
|
-
async maybeSweepCap(now) {
|
|
98533
|
-
if (now - this.lastCapSweepAt < CAP_SWEEP_INTERVAL_MS) return;
|
|
98534
|
-
this.lastCapSweepAt = now;
|
|
98535
|
-
for (const group of this.groups) {
|
|
98536
|
-
const [subject, series, scope] = group.split("\0");
|
|
98537
|
-
if (subject === void 0 || series === void 0 || scope === void 0) continue;
|
|
98538
|
-
const where = {
|
|
98539
|
-
subject,
|
|
98540
|
-
series,
|
|
98541
|
-
scope
|
|
98542
|
-
};
|
|
98543
|
-
try {
|
|
98544
|
-
const excess = await this.store.count.query({
|
|
98545
|
-
collection: this.collection,
|
|
98546
|
-
filter: { where }
|
|
98547
|
-
}) - this.maxRows;
|
|
98548
|
-
if (excess <= 0) continue;
|
|
98549
|
-
const cutoff = (await this.store.query.query({
|
|
98550
|
-
collection: this.collection,
|
|
98551
|
-
filter: {
|
|
98552
|
-
where,
|
|
98553
|
-
orderBy: {
|
|
98554
|
-
field: "bucketStart",
|
|
98555
|
-
direction: "asc"
|
|
98556
|
-
},
|
|
98557
|
-
limit: excess
|
|
98558
|
-
}
|
|
98559
|
-
})).at(-1)?.data["bucketStart"];
|
|
98560
|
-
if (typeof cutoff !== "number") continue;
|
|
98561
|
-
const { deleted } = await this.store.deleteWhere.mutate({
|
|
98562
|
-
collection: this.collection,
|
|
98563
|
-
filter: {
|
|
98564
|
-
where,
|
|
98565
|
-
whereBetween: { bucketStart: [0, cutoff] }
|
|
98566
|
-
}
|
|
98567
|
-
});
|
|
98568
|
-
this.logger.info("lts row cap enforced", { meta: {
|
|
98569
|
-
collection: this.collection,
|
|
98570
|
-
series,
|
|
98571
|
-
subject,
|
|
98572
|
-
deleted,
|
|
98573
|
-
cap: this.maxRows
|
|
98574
|
-
} });
|
|
98575
|
-
} catch (err) {
|
|
98576
|
-
this.logger.warn("lts row cap sweep failed — the series keeps growing this cycle", { meta: {
|
|
98577
|
-
collection: this.collection,
|
|
98578
|
-
series,
|
|
98579
|
-
subject,
|
|
98580
|
-
error: String(err)
|
|
98581
|
-
} });
|
|
98582
|
-
}
|
|
98583
|
-
}
|
|
98584
|
-
}
|
|
98585
|
-
};
|
|
98586
|
-
function chunk(items, size) {
|
|
98587
|
-
const out = [];
|
|
98588
|
-
for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
|
|
98589
|
-
return out;
|
|
98590
|
-
}
|
|
98591
|
-
function groupKey(subject, series, scope) {
|
|
98592
|
-
return `${subject}${series}${scope}`;
|
|
98593
|
-
}
|
|
98594
|
-
function rowId(subject, series, scope, bucketStart) {
|
|
98595
|
-
return `${subject}|${series}|${scope}|${bucketStart}`;
|
|
98596
|
-
}
|
|
98597
|
-
function toRow(bucket) {
|
|
98598
|
-
return {
|
|
98599
|
-
subject: bucket.subject,
|
|
98600
|
-
...bucket.deviceId !== void 0 ? { deviceId: bucket.deviceId } : {},
|
|
98601
|
-
series: bucket.series,
|
|
98602
|
-
scope: bucket.scope,
|
|
98603
|
-
bucketStart: bucket.bucketStart,
|
|
98604
|
-
samples: bucket.samples,
|
|
98605
|
-
sum: bucket.sum,
|
|
98606
|
-
min: bucket.min,
|
|
98607
|
-
max: bucket.max
|
|
98608
|
-
};
|
|
98609
|
-
}
|
|
98610
|
-
/** Structural validation on read. A malformed row is skipped, never charted. */
|
|
98611
|
-
function recordToRow(data) {
|
|
98612
|
-
const subject = data["subject"];
|
|
98613
|
-
const series = data["series"];
|
|
98614
|
-
const scope = data["scope"];
|
|
98615
|
-
if (typeof subject !== "string" || typeof series !== "string") return null;
|
|
98616
|
-
const bucketStart = Number(data["bucketStart"]);
|
|
98617
|
-
const samples = Number(data["samples"]);
|
|
98618
|
-
const sum = Number(data["sum"]);
|
|
98619
|
-
const min = Number(data["min"]);
|
|
98620
|
-
const max = Number(data["max"]);
|
|
98621
|
-
if (![
|
|
98622
|
-
bucketStart,
|
|
98623
|
-
samples,
|
|
98624
|
-
sum,
|
|
98625
|
-
min,
|
|
98626
|
-
max
|
|
98627
|
-
].every((n) => Number.isFinite(n))) return null;
|
|
98628
|
-
const rawDeviceId = data["deviceId"];
|
|
98629
|
-
const deviceId = typeof rawDeviceId === "number" && Number.isFinite(rawDeviceId) ? rawDeviceId : void 0;
|
|
98630
|
-
return {
|
|
98631
|
-
subject,
|
|
98632
|
-
...deviceId !== void 0 ? { deviceId } : {},
|
|
98633
|
-
series,
|
|
98634
|
-
scope: typeof scope === "string" ? scope : "",
|
|
98635
|
-
bucketStart,
|
|
98636
|
-
samples,
|
|
98637
|
-
sum,
|
|
98638
|
-
min,
|
|
98639
|
-
max
|
|
98640
|
-
};
|
|
98641
|
-
}
|
|
98642
|
-
//#endregion
|
|
98643
|
-
//#region src/pipeline-analytics/store/lts-store.ts
|
|
98644
|
-
/**
|
|
98645
|
-
* @durable class=ledger owner=pipeline-analytics
|
|
98646
|
-
* write="one row per (camera, series, zone, 5-min bucket), written ONCE when the bucket closes; a camera with no sample writes nothing"
|
|
98647
|
-
* retention="row CAP per (subject, series, scope) — 105,120 rows ≈ one year of 5-minute buckets. Deliberately not an age sweep: an LTS row has no owner to be orphaned from and no clock but its own (D61)."
|
|
98648
|
-
*/
|
|
98649
|
-
var LTS_COLLECTION = "pipeline-analytics:stats-5m-v2";
|
|
98650
|
-
var AnalyticsLts = class {
|
|
98651
|
-
lts;
|
|
98652
|
-
constructor(deps) {
|
|
98653
|
-
this.lts = new LtsAggregator({
|
|
98654
|
-
collection: LTS_COLLECTION,
|
|
98655
|
-
store: deps.store,
|
|
98656
|
-
logger: deps.logger,
|
|
98657
|
-
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
98658
|
-
});
|
|
98659
|
-
}
|
|
98660
|
-
static declare(store) {
|
|
98661
|
-
return LtsAggregator.declare(store, LTS_COLLECTION);
|
|
98662
|
-
}
|
|
98663
|
-
/**
|
|
98664
|
-
* One camera sample. Synchronous and infallible by contract — every call
|
|
98665
|
-
* site is a path that already produced the value for another reason, and
|
|
98666
|
-
* none of them may learn a new failure mode because a chart exists.
|
|
98667
|
-
*/
|
|
98668
|
-
note(series, deviceId, value, at, scope) {
|
|
98669
|
-
this.lts.note({
|
|
98670
|
-
subject: String(deviceId),
|
|
98671
|
-
deviceId,
|
|
98672
|
-
series,
|
|
98673
|
-
...scope !== void 0 ? { scope } : {},
|
|
98674
|
-
value,
|
|
98675
|
-
...at !== void 0 ? { at } : {}
|
|
98676
|
-
});
|
|
98677
|
-
}
|
|
98678
|
-
/** Write every bucket that has closed. Driven by the addon's flush timer. */
|
|
98679
|
-
flushDue(now) {
|
|
98680
|
-
return this.lts.flushDue(now);
|
|
98681
|
-
}
|
|
98682
|
-
/** The read surface charts will use. */
|
|
98683
|
-
read(query) {
|
|
98684
|
-
return this.lts.read(query);
|
|
98685
|
-
}
|
|
98686
|
-
/** Buckets still accumulating — makes "is it running" answerable at once,
|
|
98687
|
-
* instead of after five minutes of silence. */
|
|
98688
|
-
openBuckets() {
|
|
98689
|
-
return this.lts.openBuckets();
|
|
98690
|
-
}
|
|
98691
|
-
};
|
|
98692
|
-
//#endregion
|
|
98693
98257
|
//#region src/pipeline-analytics/store/identity-store.ts
|
|
98694
98258
|
/**
|
|
98695
98259
|
* IdentityStore — per-person identity registry for face recognition.
|
|
@@ -101733,13 +101297,6 @@ var ANALYTICS_COLLECTIONS = [
|
|
|
101733
101297
|
why: "a phantom cell is a property of the SCENE — \"this patch of pixels keeps producing 4-second motionless tracks\" — not of any track. The tracks that produced it are dead by construction (that is the entire premise: the stationary registry cannot see them because they never live long enough to be promoted), so there is no owner it could be orphaned from. Its bound is its own 24h close window plus a 512-cell-per-device cap, both enforced by the registry on write — deliberately not an age sweep, which would be a second clock (D61)."
|
|
101734
101298
|
}
|
|
101735
101299
|
},
|
|
101736
|
-
{
|
|
101737
|
-
collection: LTS_COLLECTION,
|
|
101738
|
-
classification: {
|
|
101739
|
-
kind: "never-orphaned",
|
|
101740
|
-
why: "a five-minute statistics row summarises an INTERVAL, not an entity — there is no owner it can be orphaned from. Its bound is a ROW CAP per (subject, series, scope) enforced by the aggregator itself (105,120 rows ≈ one year), deliberately not an age sweep: D61 collapsed ten disagreeing age clocks into one, and an LTS series has no clock but its own."
|
|
101741
|
-
}
|
|
101742
|
-
},
|
|
101743
101300
|
{
|
|
101744
101301
|
collection: RETRAIN_ANNOTATIONS_COLLECTION,
|
|
101745
101302
|
classification: {
|
|
@@ -107043,6 +106600,78 @@ Object.freeze({
|
|
|
107043
106600
|
"path": "online",
|
|
107044
106601
|
"valueType": "boolean"
|
|
107045
106602
|
}],
|
|
106603
|
+
"dishwasher": [
|
|
106604
|
+
{
|
|
106605
|
+
"path": "state",
|
|
106606
|
+
"valueType": "enum",
|
|
106607
|
+
"enumValues": [
|
|
106608
|
+
"idle",
|
|
106609
|
+
"running",
|
|
106610
|
+
"paused",
|
|
106611
|
+
"finished",
|
|
106612
|
+
"error",
|
|
106613
|
+
"unavailable"
|
|
106614
|
+
]
|
|
106615
|
+
},
|
|
106616
|
+
{
|
|
106617
|
+
"path": "program",
|
|
106618
|
+
"valueType": "string"
|
|
106619
|
+
},
|
|
106620
|
+
{
|
|
106621
|
+
"path": "remainingMinutes",
|
|
106622
|
+
"valueType": "number"
|
|
106623
|
+
},
|
|
106624
|
+
{
|
|
106625
|
+
"path": "progressPercent",
|
|
106626
|
+
"valueType": "number"
|
|
106627
|
+
},
|
|
106628
|
+
{
|
|
106629
|
+
"path": "doorOpen",
|
|
106630
|
+
"valueType": "boolean"
|
|
106631
|
+
},
|
|
106632
|
+
{
|
|
106633
|
+
"path": "remoteStartAllowed",
|
|
106634
|
+
"valueType": "boolean"
|
|
106635
|
+
},
|
|
106636
|
+
{
|
|
106637
|
+
"path": "powerOn",
|
|
106638
|
+
"valueType": "boolean"
|
|
106639
|
+
},
|
|
106640
|
+
{
|
|
106641
|
+
"path": "saltLevel",
|
|
106642
|
+
"valueType": "enum",
|
|
106643
|
+
"enumValues": [
|
|
106644
|
+
"ok",
|
|
106645
|
+
"low",
|
|
106646
|
+
"empty"
|
|
106647
|
+
]
|
|
106648
|
+
},
|
|
106649
|
+
{
|
|
106650
|
+
"path": "rinseAidLevel",
|
|
106651
|
+
"valueType": "enum",
|
|
106652
|
+
"enumValues": [
|
|
106653
|
+
"ok",
|
|
106654
|
+
"low",
|
|
106655
|
+
"empty"
|
|
106656
|
+
]
|
|
106657
|
+
},
|
|
106658
|
+
{
|
|
106659
|
+
"path": "halfLoad",
|
|
106660
|
+
"valueType": "boolean"
|
|
106661
|
+
},
|
|
106662
|
+
{
|
|
106663
|
+
"path": "hygienePlus",
|
|
106664
|
+
"valueType": "boolean"
|
|
106665
|
+
},
|
|
106666
|
+
{
|
|
106667
|
+
"path": "errorCode",
|
|
106668
|
+
"valueType": "number"
|
|
106669
|
+
},
|
|
106670
|
+
{
|
|
106671
|
+
"path": "errorLabel",
|
|
106672
|
+
"valueType": "string"
|
|
106673
|
+
}
|
|
106674
|
+
],
|
|
107046
106675
|
"doorbell": [{
|
|
107047
106676
|
"path": "lastPressedAt",
|
|
107048
106677
|
"valueType": "number"
|
|
@@ -107133,6 +106762,10 @@ Object.freeze({
|
|
|
107133
106762
|
"path": "detected",
|
|
107134
106763
|
"valueType": "boolean"
|
|
107135
106764
|
}],
|
|
106765
|
+
"heating-valve": [{
|
|
106766
|
+
"path": "position",
|
|
106767
|
+
"valueType": "number"
|
|
106768
|
+
}],
|
|
107136
106769
|
"humidifier": [
|
|
107137
106770
|
{
|
|
107138
106771
|
"path": "on",
|
|
@@ -110561,6 +110194,189 @@ var MigrationMarkerStore = class MigrationMarkerStore {
|
|
|
110561
110194
|
};
|
|
110562
110195
|
};
|
|
110563
110196
|
//#endregion
|
|
110197
|
+
//#region src/pipeline-analytics/store/analytics-history-keys.ts
|
|
110198
|
+
/** Declared addon series keys (D850). */
|
|
110199
|
+
var ANALYTICS_HISTORY_BUCKET_SERIES = {
|
|
110200
|
+
motion: "addon:pipeline-analytics/motion",
|
|
110201
|
+
"audio-dbfs": "addon:pipeline-analytics/audio-dbfs",
|
|
110202
|
+
occupancy: "addon:pipeline-analytics/occupancy"
|
|
110203
|
+
};
|
|
110204
|
+
var ANALYTICS_AUDIO_LEVEL_KEY = "addon:pipeline-analytics/audio-level";
|
|
110205
|
+
/** Map the public cap enum to the device-history key (read path). */
|
|
110206
|
+
function historyKeyForLtsSeries(series) {
|
|
110207
|
+
if (series === "battery") return "cap:battery.percentage";
|
|
110208
|
+
return ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
110209
|
+
}
|
|
110210
|
+
//#endregion
|
|
110211
|
+
//#region src/pipeline-analytics/store/analytics-history-producer.ts
|
|
110212
|
+
var BUCKET_SERIES_META = [
|
|
110213
|
+
{
|
|
110214
|
+
name: "motion",
|
|
110215
|
+
displayName: "Motion events (region count)"
|
|
110216
|
+
},
|
|
110217
|
+
{
|
|
110218
|
+
name: "audio-dbfs",
|
|
110219
|
+
displayName: "Audio noise baseline (dBFS)",
|
|
110220
|
+
unit: "dBFS"
|
|
110221
|
+
},
|
|
110222
|
+
{
|
|
110223
|
+
name: "occupancy",
|
|
110224
|
+
displayName: "Occupancy count"
|
|
110225
|
+
}
|
|
110226
|
+
];
|
|
110227
|
+
var AUDIO_LEVEL_SERIES = {
|
|
110228
|
+
name: "audio-level",
|
|
110229
|
+
displayName: "Audio level (dBFS)",
|
|
110230
|
+
unit: "dBFS"
|
|
110231
|
+
};
|
|
110232
|
+
var AnalyticsHistoryProducer = class {
|
|
110233
|
+
accum = new BucketAccumulator();
|
|
110234
|
+
deviceHistory;
|
|
110235
|
+
logger;
|
|
110236
|
+
nowFn;
|
|
110237
|
+
declared = false;
|
|
110238
|
+
constructor(deps) {
|
|
110239
|
+
this.deviceHistory = deps.deviceHistory;
|
|
110240
|
+
this.logger = deps.logger;
|
|
110241
|
+
this.nowFn = deps.now ?? (() => Date.now());
|
|
110242
|
+
}
|
|
110243
|
+
async declare() {
|
|
110244
|
+
if (this.declared) return;
|
|
110245
|
+
for (const meta of BUCKET_SERIES_META) await this.deviceHistory.declareSeries({
|
|
110246
|
+
name: meta.name,
|
|
110247
|
+
subjectKind: "device",
|
|
110248
|
+
kind: "gauge",
|
|
110249
|
+
displayName: meta.displayName,
|
|
110250
|
+
ingest: "buckets",
|
|
110251
|
+
...meta.unit !== void 0 ? { unit: meta.unit } : {}
|
|
110252
|
+
});
|
|
110253
|
+
await this.deviceHistory.declareSeries({
|
|
110254
|
+
name: AUDIO_LEVEL_SERIES.name,
|
|
110255
|
+
subjectKind: "device",
|
|
110256
|
+
kind: "gauge",
|
|
110257
|
+
displayName: AUDIO_LEVEL_SERIES.displayName,
|
|
110258
|
+
unit: AUDIO_LEVEL_SERIES.unit,
|
|
110259
|
+
ingest: "points"
|
|
110260
|
+
});
|
|
110261
|
+
this.declared = true;
|
|
110262
|
+
}
|
|
110263
|
+
note(series, deviceId, value, at, scope) {
|
|
110264
|
+
const key = ANALYTICS_HISTORY_BUCKET_SERIES[series];
|
|
110265
|
+
this.accum.note(key, {
|
|
110266
|
+
kind: "device",
|
|
110267
|
+
deviceId
|
|
110268
|
+
}, scope ?? "", value, at ?? this.nowFn());
|
|
110269
|
+
}
|
|
110270
|
+
recordAudioLevel(deviceId, dbfs, at) {
|
|
110271
|
+
const record = this.deviceHistory.record;
|
|
110272
|
+
if (record === void 0) return;
|
|
110273
|
+
record.call(this.deviceHistory, { points: [{
|
|
110274
|
+
key: ANALYTICS_AUDIO_LEVEL_KEY,
|
|
110275
|
+
subject: {
|
|
110276
|
+
kind: "device",
|
|
110277
|
+
deviceId
|
|
110278
|
+
},
|
|
110279
|
+
scope: "",
|
|
110280
|
+
ts: at,
|
|
110281
|
+
value: dbfs
|
|
110282
|
+
}] }).catch((err) => {
|
|
110283
|
+
this.logger.warn("audio level history point not stored", {
|
|
110284
|
+
tags: { deviceId },
|
|
110285
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
110286
|
+
});
|
|
110287
|
+
});
|
|
110288
|
+
}
|
|
110289
|
+
async flushDue(now) {
|
|
110290
|
+
const at = now ?? this.nowFn();
|
|
110291
|
+
const closed = this.accum.takeClosed(at);
|
|
110292
|
+
if (closed.length === 0) return 0;
|
|
110293
|
+
try {
|
|
110294
|
+
return (await this.deviceHistory.recordBuckets({ buckets: closed })).accepted;
|
|
110295
|
+
} catch (err) {
|
|
110296
|
+
const dropped = this.accum.requeue(closed);
|
|
110297
|
+
this.logger.warn("analytics history bucket flush failed — requeued", { meta: {
|
|
110298
|
+
buckets: closed.length,
|
|
110299
|
+
dropped: dropped.length,
|
|
110300
|
+
error: err instanceof Error ? err.message : String(err)
|
|
110301
|
+
} });
|
|
110302
|
+
return 0;
|
|
110303
|
+
}
|
|
110304
|
+
}
|
|
110305
|
+
pendingBucketCount() {
|
|
110306
|
+
return this.accum.pendingBucketCount();
|
|
110307
|
+
}
|
|
110308
|
+
async readLongTermBuckets(input) {
|
|
110309
|
+
const key = historyKeyForLtsSeries(input.series);
|
|
110310
|
+
const result = await this.deviceHistory.query({
|
|
110311
|
+
deviceId: input.deviceId,
|
|
110312
|
+
key,
|
|
110313
|
+
...input.scope !== void 0 ? { scope: input.scope } : {},
|
|
110314
|
+
from: input.from,
|
|
110315
|
+
to: input.to,
|
|
110316
|
+
resolution: "5m",
|
|
110317
|
+
...input.limit !== void 0 ? { maxPoints: input.limit } : {}
|
|
110318
|
+
});
|
|
110319
|
+
const out = [];
|
|
110320
|
+
for (const s of result.series) for (const p of s.points) {
|
|
110321
|
+
if (p.mean === null) continue;
|
|
110322
|
+
out.push({
|
|
110323
|
+
scope: s.scope,
|
|
110324
|
+
bucketStart: p.t,
|
|
110325
|
+
samples: p.samples,
|
|
110326
|
+
sum: p.mean * p.samples,
|
|
110327
|
+
min: p.min,
|
|
110328
|
+
max: p.max
|
|
110329
|
+
});
|
|
110330
|
+
}
|
|
110331
|
+
return out.toSorted((a, b) => a.bucketStart - b.bucketStart);
|
|
110332
|
+
}
|
|
110333
|
+
/** Admin debug read — maps device-history 5m buckets to legacy LTS row shape. */
|
|
110334
|
+
async readDebug(input) {
|
|
110335
|
+
const deviceId = input.subject !== void 0 ? Number(input.subject) : NaN;
|
|
110336
|
+
if (!Number.isFinite(deviceId) || input.series === void 0) return [];
|
|
110337
|
+
const key = historyKeyForLtsSeries(input.series);
|
|
110338
|
+
const result = await this.deviceHistory.query({
|
|
110339
|
+
deviceId,
|
|
110340
|
+
key,
|
|
110341
|
+
...input.scope !== void 0 ? { scope: input.scope } : {},
|
|
110342
|
+
from: input.from ?? 0,
|
|
110343
|
+
to: input.to ?? this.nowFn(),
|
|
110344
|
+
resolution: "5m",
|
|
110345
|
+
...input.limit !== void 0 ? { maxPoints: input.limit } : {}
|
|
110346
|
+
});
|
|
110347
|
+
const rows = [];
|
|
110348
|
+
for (const s of result.series) for (const p of s.points) {
|
|
110349
|
+
if (p.mean === null) continue;
|
|
110350
|
+
rows.push({
|
|
110351
|
+
subject: String(deviceId),
|
|
110352
|
+
deviceId,
|
|
110353
|
+
series: input.series,
|
|
110354
|
+
scope: s.scope,
|
|
110355
|
+
bucketStart: p.t,
|
|
110356
|
+
samples: p.samples,
|
|
110357
|
+
sum: p.mean * p.samples,
|
|
110358
|
+
min: p.min,
|
|
110359
|
+
max: p.max
|
|
110360
|
+
});
|
|
110361
|
+
}
|
|
110362
|
+
return rows;
|
|
110363
|
+
}
|
|
110364
|
+
openBucketsForDebug() {
|
|
110365
|
+
return [];
|
|
110366
|
+
}
|
|
110367
|
+
};
|
|
110368
|
+
//#endregion
|
|
110369
|
+
//#region src/pipeline-analytics/store/device-history-write-client.ts
|
|
110370
|
+
/** Adapt tRPC-shaped `ctx.api.deviceHistory` to the producer's plain async surface. */
|
|
110371
|
+
function deviceHistoryWriteClient(api) {
|
|
110372
|
+
return {
|
|
110373
|
+
declareSeries: (input) => api.declareSeries.mutate(input),
|
|
110374
|
+
record: (input) => api.record.mutate({ points: [...input.points] }),
|
|
110375
|
+
recordBuckets: (input) => api.recordBuckets.mutate({ buckets: [...input.buckets] }),
|
|
110376
|
+
query: (input) => api.query.query(input)
|
|
110377
|
+
};
|
|
110378
|
+
}
|
|
110379
|
+
//#endregion
|
|
110564
110380
|
//#region src/pipeline-analytics/store/media-subject-shot.ts
|
|
110565
110381
|
/** Preference among rows that tie on time. Lower is better. */
|
|
110566
110382
|
var SUBJECT_SHOT_KIND_RANK = {
|
|
@@ -112895,7 +112711,7 @@ var NcArtifactSecretSchema = string();
|
|
|
112895
112711
|
var EmbeddingEnabledSchema = boolean();
|
|
112896
112712
|
var SILENCE_FLOOR_DBFS = -55;
|
|
112897
112713
|
var RETENTION_SWEEP_INTERVAL_MS = 5 * 6e4;
|
|
112898
|
-
/** How often closed
|
|
112714
|
+
/** How often closed history buckets are flushed. See `analytics-history-producer.ts`. */
|
|
112899
112715
|
var LTS_FLUSH_INTERVAL_MS = 6e4;
|
|
112900
112716
|
/**
|
|
112901
112717
|
* How long the events ops-log is kept when the cluster has said nothing. 30
|
|
@@ -114551,7 +114367,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
114551
114367
|
/**
|
|
114552
114368
|
* Five-minute long-term statistics — the tier this system did not have.
|
|
114553
114369
|
* Global for every camera (no opt-in: a baseline nobody enables is the
|
|
114554
|
-
* failure mode `observe` mode was already in). See `
|
|
114370
|
+
* failure mode `observe` mode was already in). See `analytics-history-producer.ts`.
|
|
114555
114371
|
*/
|
|
114556
114372
|
lts = null;
|
|
114557
114373
|
/** The runner node that last produced a frame for a camera — see
|
|
@@ -114718,7 +114534,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
114718
114534
|
onAudioResult: (data) => this.handleAudioResult(data),
|
|
114719
114535
|
onMotionAnalysis: (deviceId, data, timestampMs) => this.handleMotionAnalysis(deviceId, data, timestampMs),
|
|
114720
114536
|
onOnboardMotion: (deviceId, data, timestampMs) => this.handleOnboardMotion(deviceId, data, timestampMs),
|
|
114721
|
-
onBatterySample: (deviceId, percentage, at) => this.lts?.note("battery", deviceId, percentage, at),
|
|
114722
114537
|
onSensorStateChanged: (data, timestampMs) => this.dispatchSensorStateChanged(data, timestampMs),
|
|
114723
114538
|
onBindingsChanged: (data) => this.handleBindingsChangedEvent(data),
|
|
114724
114539
|
onDeviceUnregistered: (deviceId) => this.handleDeviceUnregisteredEvent(deviceId),
|
|
@@ -115047,7 +114862,6 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
115047
114862
|
TrackingTapeIndexStore.declare(api.settingsStore),
|
|
115048
114863
|
BirthDecisionLedgerStore.declare(api.settingsStore),
|
|
115049
114864
|
TrackRelationsStore.declare(api.settingsStore),
|
|
115050
|
-
AnalyticsLts.declare(api.settingsStore),
|
|
115051
114865
|
declarePaAnalyticsBootRecordBackedLegacy(api.settingsStore, isHub),
|
|
115052
114866
|
declareNotificationCenterBootCollections(api.settingsStore, isHub)
|
|
115053
114867
|
]);
|
|
@@ -115403,9 +115217,12 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
115403
115217
|
}
|
|
115404
115218
|
});
|
|
115405
115219
|
this.refreshDeviceAlertSettings();
|
|
115406
|
-
this.lts = new
|
|
115407
|
-
|
|
115408
|
-
logger: logger.child("
|
|
115220
|
+
this.lts = new AnalyticsHistoryProducer({
|
|
115221
|
+
deviceHistory: deviceHistoryWriteClient(api.deviceHistory),
|
|
115222
|
+
logger: logger.child("device-history")
|
|
115223
|
+
});
|
|
115224
|
+
this.lts.declare().catch((err) => {
|
|
115225
|
+
logger.warn("pipeline-analytics history series declare failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
115409
115226
|
});
|
|
115410
115227
|
const lts = this.lts;
|
|
115411
115228
|
const eventStore = new EventStore({
|
|
@@ -116872,8 +116689,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
116872
116689
|
open: []
|
|
116873
116690
|
};
|
|
116874
116691
|
return {
|
|
116875
|
-
rows: (await lts.
|
|
116876
|
-
open: lts.
|
|
116692
|
+
rows: (await lts.readDebug(input)).map(withMean),
|
|
116693
|
+
open: lts.openBucketsForDebug().map(withMean)
|
|
116877
116694
|
};
|
|
116878
116695
|
},
|
|
116879
116696
|
/**
|
|
@@ -120949,33 +120766,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
120949
120766
|
});
|
|
120950
120767
|
this.levelStateByDevice.set(deviceId, res.nextState);
|
|
120951
120768
|
if (!res.emit) return;
|
|
120952
|
-
const
|
|
120953
|
-
this.
|
|
120954
|
-
className: void 0,
|
|
120955
|
-
atMs: timestamp
|
|
120956
|
-
});
|
|
120957
|
-
const insertedId = await this.eventStore.insertAudio(draft);
|
|
120958
|
-
if (insertedId === null) return;
|
|
120959
|
-
const ev = {
|
|
120960
|
-
...draft,
|
|
120961
|
-
id: insertedId
|
|
120962
|
-
};
|
|
120963
|
-
this.ctx.eventBus.emit({
|
|
120964
|
-
id: `pa-${ev.id}`,
|
|
120965
|
-
timestamp: new Date(ev.timestamp),
|
|
120966
|
-
source: {
|
|
120967
|
-
type: "addon",
|
|
120968
|
-
id: "pipeline-analytics",
|
|
120969
|
-
addonId: "pipeline-analytics"
|
|
120970
|
-
},
|
|
120971
|
-
category: EventCategory.PipelineAnalyticsDetectionEvent,
|
|
120972
|
-
data: {
|
|
120973
|
-
deviceId,
|
|
120974
|
-
kind: "audio",
|
|
120975
|
-
eventId: ev.id,
|
|
120976
|
-
timestamp: ev.timestamp
|
|
120977
|
-
}
|
|
120978
|
-
});
|
|
120769
|
+
const dbfs = level?.dbfs ?? Number.NEGATIVE_INFINITY;
|
|
120770
|
+
this.lts?.recordAudioLevel(deviceId, dbfs, timestamp);
|
|
120979
120771
|
}
|
|
120980
120772
|
/**
|
|
120981
120773
|
* Persist motion-analysis bus events into the EPISODE tracker (D475): a
|
|
@@ -125090,21 +124882,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
125090
124882
|
async readLongTermSeries(input) {
|
|
125091
124883
|
const lts = this.lts;
|
|
125092
124884
|
if (!lts) throw new Error("pipeline-analytics: the long-term store is not ready yet");
|
|
125093
|
-
return
|
|
125094
|
-
subject: String(input.deviceId),
|
|
125095
|
-
series: input.series,
|
|
125096
|
-
...input.scope !== void 0 ? { scope: input.scope } : {},
|
|
125097
|
-
from: input.from,
|
|
125098
|
-
to: input.to,
|
|
125099
|
-
...input.limit !== void 0 ? { limit: input.limit } : {}
|
|
125100
|
-
})).map((r) => ({
|
|
125101
|
-
scope: r.scope,
|
|
125102
|
-
bucketStart: r.bucketStart,
|
|
125103
|
-
samples: r.samples,
|
|
125104
|
-
sum: r.sum,
|
|
125105
|
-
min: r.min,
|
|
125106
|
-
max: r.max
|
|
125107
|
-
}));
|
|
124885
|
+
return lts.readLongTermBuckets(input);
|
|
125108
124886
|
}
|
|
125109
124887
|
async getEventStoreFootprint() {
|
|
125110
124888
|
return this.queryFacade.getEventStoreFootprint();
|