@camstack/addon-post-analysis 1.2.208 → 1.2.209
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/{dist-WxrHfFRz.mjs → dist-BV15Z8DR.mjs} +69 -69
- package/dist/{dist-CoLA3NHh.js → dist-BttKNVmP.js} +69 -69
- package/dist/embedding-encoder/index.js +1 -1
- package/dist/embedding-encoder/index.mjs +1 -1
- package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CHmvWGlL.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DCfZE21-.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-B5t-MBOX.mjs → hostInit-WwnFkrnz.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +98 -13
- package/dist/pipeline-analytics/index.mjs +98 -13
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
-
const require_dist = require("../dist-
|
|
5
|
+
const require_dist = require("../dist-BttKNVmP.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
node_fs = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
let node_path = require("node:path");
|
|
@@ -7520,7 +7520,8 @@ function cooldownKey(rule, subject) {
|
|
|
7520
7520
|
const classKey = keysPerClass(rule, subject) && first !== void 0 ? `:c:${first}` : "";
|
|
7521
7521
|
const scopeRef = subject.systemEvent !== void 0 ? systemEventCooldownScope(subject.systemEvent) : `d:${subject.deviceId}`;
|
|
7522
7522
|
const kindKey = subject.systemEvent !== void 0 ? `:k:${subject.systemEvent.kind}` : "";
|
|
7523
|
-
|
|
7523
|
+
const phaseKey = subject.packagePhase !== void 0 ? `:p:${subject.packagePhase}` : "";
|
|
7524
|
+
return rule.throttle.scope === "rule" ? `r:${rule.id}${kindKey}${phaseKey}${classKey}` : `r:${rule.id}:${scopeRef}${phaseKey}${classKey}`;
|
|
7524
7525
|
}
|
|
7525
7526
|
/** True when the rule fired within its cooldown window before `now`. */
|
|
7526
7527
|
function isCoolingDown(rule, lastFiredAt, now) {
|
|
@@ -9957,6 +9958,10 @@ var en_default = {
|
|
|
9957
9958
|
"one": "{{count}} other {{classNoun}}",
|
|
9958
9959
|
"other": "{{count}} other {{classNoun}}"
|
|
9959
9960
|
},
|
|
9961
|
+
"package.delivered.title": "Parcel delivered",
|
|
9962
|
+
"package.delivered.body": "Delivered at {{camera}}{{inZones}}",
|
|
9963
|
+
"package.picked-up.title": "Parcel collected",
|
|
9964
|
+
"package.picked-up.body": "Collected from {{camera}}{{inZones}}",
|
|
9960
9965
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
9961
9966
|
"occupancy.op.occupied": "occupied",
|
|
9962
9967
|
"occupancy.op.free": "free",
|
|
@@ -10208,6 +10213,10 @@ var it_default = {
|
|
|
10208
10213
|
"one": "{{count}} altro {{classNoun}}",
|
|
10209
10214
|
"other": "{{count}} altri {{classNoun}}"
|
|
10210
10215
|
},
|
|
10216
|
+
"package.delivered.title": "Pacco consegnato",
|
|
10217
|
+
"package.delivered.body": "Consegnato su {{camera}}{{inZones}}",
|
|
10218
|
+
"package.picked-up.title": "Pacco ritirato",
|
|
10219
|
+
"package.picked-up.body": "Ritirato da {{camera}}{{inZones}}",
|
|
10211
10220
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
10212
10221
|
"occupancy.op.occupied": "occupata",
|
|
10213
10222
|
"occupancy.op.free": "libera",
|
|
@@ -12050,6 +12059,7 @@ function bodyKeyFor(entry) {
|
|
|
12050
12059
|
const subject = entry.payload.subject;
|
|
12051
12060
|
if (subject.systemEvent !== void 0) return subject.systemEvent.bodyKey ?? "";
|
|
12052
12061
|
if (subject.occupancy !== void 0) return "occupancy.body";
|
|
12062
|
+
if (subject.packagePhase !== void 0) return `package.${subject.packagePhase}.body`;
|
|
12053
12063
|
if (entry.payload.enhancement === true) return "detection.body.enhanced";
|
|
12054
12064
|
return entry.recordKind === "track-end" ? "detection.body.trackEnd" : "detection.body";
|
|
12055
12065
|
}
|
|
@@ -12057,6 +12067,8 @@ function bodyKeyFor(entry) {
|
|
|
12057
12067
|
function titleKeyFor(entry) {
|
|
12058
12068
|
const systemEvent = entry.payload.subject.systemEvent;
|
|
12059
12069
|
if (systemEvent !== void 0) return systemEvent.titleKey ?? "";
|
|
12070
|
+
const packagePhase = entry.payload.subject.packagePhase;
|
|
12071
|
+
if (packagePhase !== void 0) return `package.${packagePhase}.title`;
|
|
12060
12072
|
return "detection.title";
|
|
12061
12073
|
}
|
|
12062
12074
|
/**
|
|
@@ -24856,6 +24868,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
24856
24868
|
zones: renderedZones,
|
|
24857
24869
|
timestamp: subject.timestamp,
|
|
24858
24870
|
...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {},
|
|
24871
|
+
...subject.packagePhase !== void 0 ? { packagePhase: subject.packagePhase } : {},
|
|
24859
24872
|
...subject.systemEvent !== void 0 ? { systemEvent: subject.systemEvent } : {}
|
|
24860
24873
|
}
|
|
24861
24874
|
};
|
|
@@ -32769,9 +32782,14 @@ var FaceStore = class {
|
|
|
32769
32782
|
* with that person named. High means "this looks like someone we can name",
|
|
32770
32783
|
* which is what a mis-enrolment actually looks like.
|
|
32771
32784
|
*
|
|
32772
|
-
* `suspicion`
|
|
32773
|
-
* one
|
|
32774
|
-
*
|
|
32785
|
+
* `suspicion` is the EXCESS — `bestForeign − selfMean` — because that is the
|
|
32786
|
+
* only one of the two that discriminates on real data. Measured on Roberta's
|
|
32787
|
+
* 106 samples, 2026-09-08: `selfMean` has a median of 0.188 and a p90 of 0.235,
|
|
32788
|
+
* so ANY absolute reading of "does not look like itself" fires on the whole
|
|
32789
|
+
* gallery. The first version of this module scored `(1 − selfMean) / 2` and
|
|
32790
|
+
* flagged 56 of 106 — a finding that fires on healthy samples means nothing.
|
|
32791
|
+
* `bestForeign` on the same gallery: median 0.283, p95 0.376, and one single
|
|
32792
|
+
* outlier at 0.612, which is the mis-enrolment.
|
|
32775
32793
|
*
|
|
32776
32794
|
* ## What it refuses to do
|
|
32777
32795
|
*
|
|
@@ -32786,8 +32804,20 @@ var FaceStore = class {
|
|
|
32786
32804
|
*
|
|
32787
32805
|
* Pure — the caller loads the vectors and performs the removal.
|
|
32788
32806
|
*/
|
|
32789
|
-
/**
|
|
32790
|
-
|
|
32807
|
+
/**
|
|
32808
|
+
* A row is FLAGGED when its best foreign match would auto-assign the face to
|
|
32809
|
+
* that other person — i.e. it reaches the recogniser's own
|
|
32810
|
+
* `similarityThreshold`.
|
|
32811
|
+
*
|
|
32812
|
+
* Tied to the live threshold rather than to a constant of its own, because
|
|
32813
|
+
* that is what the flag MEANS: this sample can steal a match. A second number
|
|
32814
|
+
* invented here would drift from the one the recogniser actually uses, and the
|
|
32815
|
+
* panel would accuse samples that cannot hurt anyone while staying quiet about
|
|
32816
|
+
* ones that can. On the live gallery (threshold 0.40) it flags 2 of 106.
|
|
32817
|
+
*/
|
|
32818
|
+
function flaggedCount(rows, autoAssignThreshold) {
|
|
32819
|
+
return rows.filter((row) => (row.bestForeign?.score ?? 0) >= autoAssignThreshold).length;
|
|
32820
|
+
}
|
|
32791
32821
|
/**
|
|
32792
32822
|
* How far a foreign match has to stand OUT to count as looking like someone
|
|
32793
32823
|
* else: `bestForeign - selfMean`. A sample that scores 0.6 against a stranger
|
|
@@ -32842,8 +32872,7 @@ function auditIdentitySamples(input) {
|
|
|
32842
32872
|
};
|
|
32843
32873
|
const selfMean = meanCosine(sample, comparable);
|
|
32844
32874
|
const foreign = bestForeignMatch(sample, input.others, input.modelId);
|
|
32845
|
-
const
|
|
32846
|
-
const suspicion = selfMean === null ? 0 : (1 - selfMean) / 2 + excess;
|
|
32875
|
+
const suspicion = foreignExcess(selfMean, foreign);
|
|
32847
32876
|
return {
|
|
32848
32877
|
sampleId: sample.sampleId,
|
|
32849
32878
|
enrolledAt: sample.enrolledAt,
|
|
@@ -32854,12 +32883,15 @@ function auditIdentitySamples(input) {
|
|
|
32854
32883
|
suspicion
|
|
32855
32884
|
};
|
|
32856
32885
|
});
|
|
32857
|
-
rows.sort((a, b) =>
|
|
32886
|
+
rows.sort((a, b) => {
|
|
32887
|
+
if (b.suspicion !== a.suspicion) return b.suspicion - a.suspicion;
|
|
32888
|
+
return (a.selfMean ?? Number.POSITIVE_INFINITY) - (b.selfMean ?? Number.POSITIVE_INFINITY);
|
|
32889
|
+
});
|
|
32858
32890
|
return {
|
|
32859
32891
|
identityId: input.identityId,
|
|
32860
32892
|
totalSamples: input.samples.length,
|
|
32861
32893
|
comparableSamples: comparable.length,
|
|
32862
|
-
flagged: rows
|
|
32894
|
+
flagged: flaggedCount(rows, input.autoAssignThreshold),
|
|
32863
32895
|
rows
|
|
32864
32896
|
};
|
|
32865
32897
|
}
|
|
@@ -32916,6 +32948,7 @@ var FaceGalleryProvider = class {
|
|
|
32916
32948
|
logger;
|
|
32917
32949
|
refreshGallery;
|
|
32918
32950
|
resolveClusterModelId;
|
|
32951
|
+
resolveAutoAssignThreshold;
|
|
32919
32952
|
eventMediaBaseUrl;
|
|
32920
32953
|
eventMediaPathPrefix;
|
|
32921
32954
|
emitFaceGalleryChanged;
|
|
@@ -32928,6 +32961,7 @@ var FaceGalleryProvider = class {
|
|
|
32928
32961
|
this.logger = deps.logger;
|
|
32929
32962
|
this.refreshGallery = deps.refreshGallery;
|
|
32930
32963
|
this.resolveClusterModelId = deps.resolveClusterModelId;
|
|
32964
|
+
this.resolveAutoAssignThreshold = deps.resolveAutoAssignThreshold;
|
|
32931
32965
|
this.eventMediaBaseUrl = deps.eventMediaBaseUrl;
|
|
32932
32966
|
this.eventMediaPathPrefix = deps.eventMediaPathPrefix;
|
|
32933
32967
|
this.emitFaceGalleryChanged = deps.emitFaceGalleryChanged;
|
|
@@ -33019,6 +33053,7 @@ var FaceGalleryProvider = class {
|
|
|
33019
33053
|
async auditIdentitySamples(input) {
|
|
33020
33054
|
const modelId = await this.resolveClusterModelId();
|
|
33021
33055
|
if (modelId === null) throw new Error("auditIdentitySamples: the cluster face-embedding model could not be resolved");
|
|
33056
|
+
const autoAssignThreshold = await this.resolveAutoAssignThreshold();
|
|
33022
33057
|
const identities = await this.identityStore.listIdentities();
|
|
33023
33058
|
const target = identities.find((i) => i.id === input.identityId);
|
|
33024
33059
|
if (target === void 0) throw new Error(`auditIdentitySamples: no identity ${input.identityId}`);
|
|
@@ -33044,7 +33079,8 @@ var FaceGalleryProvider = class {
|
|
|
33044
33079
|
name: identity.name,
|
|
33045
33080
|
samples: inputsFor(identity.id)
|
|
33046
33081
|
})),
|
|
33047
|
-
modelId
|
|
33082
|
+
modelId,
|
|
33083
|
+
autoAssignThreshold
|
|
33048
33084
|
});
|
|
33049
33085
|
const meta = new Map((samplesByIdentity.get(input.identityId) ?? []).map((sample) => [sample.id, sample]));
|
|
33050
33086
|
return {
|
|
@@ -33054,7 +33090,7 @@ var FaceGalleryProvider = class {
|
|
|
33054
33090
|
totalSamples: report.totalSamples,
|
|
33055
33091
|
comparableSamples: report.comparableSamples,
|
|
33056
33092
|
flagged: report.flagged,
|
|
33057
|
-
flagThreshold:
|
|
33093
|
+
flagThreshold: autoAssignThreshold,
|
|
33058
33094
|
rows: report.rows.map((row) => {
|
|
33059
33095
|
const sample = meta.get(row.sampleId);
|
|
33060
33096
|
return {
|
|
@@ -57932,6 +57968,9 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
57932
57968
|
let keyless = 0;
|
|
57933
57969
|
let skippedGrace = 0;
|
|
57934
57970
|
let stoppedByCeiling = false;
|
|
57971
|
+
let missed = 0;
|
|
57972
|
+
/** Verdicts held until there are enough of them to tell rot from a move. */
|
|
57973
|
+
const held = [];
|
|
57935
57974
|
const identities = await deps.listIdentities();
|
|
57936
57975
|
for (const identity of identities) {
|
|
57937
57976
|
const samples = await deps.listSamples(identity.id);
|
|
@@ -57976,6 +58015,43 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
57976
58015
|
}
|
|
57977
58016
|
checked += 1;
|
|
57978
58017
|
if (state === "exists") continue;
|
|
58018
|
+
missed += 1;
|
|
58019
|
+
const minProbes = options.massMissMinProbes ?? 8;
|
|
58020
|
+
const bar = options.massMissFraction ?? .5;
|
|
58021
|
+
if (checked < minProbes) {
|
|
58022
|
+
held.push({
|
|
58023
|
+
identityId: identity.id,
|
|
58024
|
+
sampleId: sample.id,
|
|
58025
|
+
mediaKey: sample.mediaKey
|
|
58026
|
+
});
|
|
58027
|
+
continue;
|
|
58028
|
+
}
|
|
58029
|
+
if (missed / checked >= bar) {
|
|
58030
|
+
const reason = `mass miss: ${String(missed)} of ${String(checked)} probes found no blob`;
|
|
58031
|
+
deps.logger.warn("gallery reconcile: nearly every blob is missing — that is the volume, not the samples. Aborting and keeping every row; check where the gallery location points.", { meta: {
|
|
58032
|
+
checked,
|
|
58033
|
+
missed,
|
|
58034
|
+
held: held.length,
|
|
58035
|
+
identity: identity.name
|
|
58036
|
+
} });
|
|
58037
|
+
return {
|
|
58038
|
+
checked,
|
|
58039
|
+
reclaimed,
|
|
58040
|
+
keyless,
|
|
58041
|
+
skippedGrace,
|
|
58042
|
+
stoppedByCeiling,
|
|
58043
|
+
aborted: reason
|
|
58044
|
+
};
|
|
58045
|
+
}
|
|
58046
|
+
for (const pending of held.splice(0, held.length)) {
|
|
58047
|
+
await deps.removeSample(pending.identityId, pending.sampleId);
|
|
58048
|
+
reclaimed += 1;
|
|
58049
|
+
deps.logger.warn("gallery reconcile: sample blob is gone from the filesystem — reference removed", { meta: {
|
|
58050
|
+
sampleId: pending.sampleId,
|
|
58051
|
+
mediaKey: pending.mediaKey,
|
|
58052
|
+
state: "missing-blob"
|
|
58053
|
+
} });
|
|
58054
|
+
}
|
|
57979
58055
|
await deps.removeSample(identity.id, sample.id);
|
|
57980
58056
|
reclaimed += 1;
|
|
57981
58057
|
deps.logger.warn("gallery reconcile: sample blob is gone from the filesystem — reference removed", { meta: {
|
|
@@ -57986,6 +58062,14 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
57986
58062
|
} });
|
|
57987
58063
|
}
|
|
57988
58064
|
}
|
|
58065
|
+
for (const pending of held.splice(0, held.length)) {
|
|
58066
|
+
if (reclaimed >= options.maxReclaimPerRun) {
|
|
58067
|
+
stoppedByCeiling = true;
|
|
58068
|
+
break;
|
|
58069
|
+
}
|
|
58070
|
+
await deps.removeSample(pending.identityId, pending.sampleId);
|
|
58071
|
+
reclaimed += 1;
|
|
58072
|
+
}
|
|
57989
58073
|
if (reclaimed > 0) deps.logger.info("gallery reconcile: dead references removed", { meta: {
|
|
57990
58074
|
checked,
|
|
57991
58075
|
reclaimed,
|
|
@@ -71964,6 +72048,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
71964
72048
|
eventStore: stores.eventStore,
|
|
71965
72049
|
logger: this.ctx.logger,
|
|
71966
72050
|
refreshGallery: () => this.faceRecognizer?.refreshGallery(),
|
|
72051
|
+
resolveAutoAssignThreshold: async () => (await this.readDeviceSettings(0, resolveFaceSettings)).similarityThreshold,
|
|
71967
72052
|
resolveClusterModelId: async () => {
|
|
71968
72053
|
const api = this.ctx.api;
|
|
71969
72054
|
if (!api) return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-
|
|
1
|
+
import { $ as audioModeOf, $t as EventCategory, A as NcSnoozeSchema, At as vectorDimFromBase64, B as SCENE_DEFAULT_UNCOVERED_POLICY, Bt as nodePin, C as NcConditionDescriptorSchema, Ct as readTimelapseGeneratedAt, D as NcRuleTargetSchema, Dt as storageOccupancyCapability, E as NcRuleSchema, Et as sceneMonitorCapability, Ft as CamProfileSchema, G as TimelapseRulePatchSchema, Gt as discriminatedUnion, H as SceneMonitorSchema, Ht as _enum, I as RECORDING_EXPORT_MAX_READ_BYTES, It as DeviceType, J as VISIT_MERGE_GAP_MS, Jt as object, K as TimelapseRuleSchema, Kt as literal, L as RetrainStatusSchema, Lt as createEvent, M as NcSystemEventKindSchema, Mt as zoneAnalyticsCapability, N as NcTaxonomySchema, Nt as errMsg$1, O as NcScheduleSchema, Ot as subKindsOf, P as OpsLogEntrySchema, Pt as BaseAddon, Q as audioMetricsCapability, Qt as unknown, Rt as hydrateSchema, S as NC_TAXONOMY, St as readDeviceStateFrom, T as NcRulePatchSchema, U as TIMELAPSE_DENSE_FLOOR_SEC, Ut as array, V as SCENE_DIVERGED, Vt as sleep$1, W as TimelapseRuleInputSchema, Wt as boolean, X as alarmPanelCapability, Xt as record, Y as addonWidgetsSourceCapability, Yt as partialRecord, Z as assertTimelapseCadences, Zt as string, _ as MediaFileKindEnum, _t as notificationRulesCapability, a as DEFAULT_EVENT_COLOR, b as NC_DEFAULT_SNOOZE_MINUTES, bt as pipelineAnalyticsCapability, c as DETECTION_MACRO_CLASSES, ct as evictionPolicyOfLocation, d as EVENT_KIND_BY_CAP, et as buildEventKindDescriptor, f as EVENT_PAD_MS, ft as isDetectionMacroClass, g as MACRO_LABELS, gt as mayWriteToLocation, h as LabelAttributionSchema, ht as kebabToCamel, i as COCO_TO_MACRO, it as deriveRecordingMode, j as NcSnoozeSuppressedSchema, jt as videoclipsCapability, k as NcSnoozeInputSchema, kt as systemEventFilterApplies, lt as faceGalleryCapability, m as FailureCounters, mt as isSourceCap, n as BaseDevice, nt as customAction, ot as encodeVectorBase64, p as FULL_IMAGE_BBOX, pt as isScheduleActive, q as TrackSourceSchema, qt as number, r as CLUSTER_MODEL_SCOPED_STEPS, rt as defineCustomActions, st as evaluateSensorEdge, t as AUDIO_MACRO_LABELS, tt as cosineSimilarity$1, u as DeclaredDevices, ut as failureContributionCapability, v as NC_ALARM_SYSTEM_EVENT_KINDS, w as NcRuleInputSchema, wt as resolveLocationMode, xt as plateGalleryCapability, y as NC_CONDITION_CATALOG, yt as pickClusterStepModels, z as SCENE_DEFAULT_ANCHOR_THRESHOLD, zt as isDeviceScopedCap } from "../dist-BV15Z8DR.mjs";
|
|
2
2
|
import { t as __exportAll } from "../embedding-encoder/index.mjs";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -7513,7 +7513,8 @@ function cooldownKey(rule, subject) {
|
|
|
7513
7513
|
const classKey = keysPerClass(rule, subject) && first !== void 0 ? `:c:${first}` : "";
|
|
7514
7514
|
const scopeRef = subject.systemEvent !== void 0 ? systemEventCooldownScope(subject.systemEvent) : `d:${subject.deviceId}`;
|
|
7515
7515
|
const kindKey = subject.systemEvent !== void 0 ? `:k:${subject.systemEvent.kind}` : "";
|
|
7516
|
-
|
|
7516
|
+
const phaseKey = subject.packagePhase !== void 0 ? `:p:${subject.packagePhase}` : "";
|
|
7517
|
+
return rule.throttle.scope === "rule" ? `r:${rule.id}${kindKey}${phaseKey}${classKey}` : `r:${rule.id}:${scopeRef}${phaseKey}${classKey}`;
|
|
7517
7518
|
}
|
|
7518
7519
|
/** True when the rule fired within its cooldown window before `now`. */
|
|
7519
7520
|
function isCoolingDown(rule, lastFiredAt, now) {
|
|
@@ -9950,6 +9951,10 @@ var en_default = {
|
|
|
9950
9951
|
"one": "{{count}} other {{classNoun}}",
|
|
9951
9952
|
"other": "{{count}} other {{classNoun}}"
|
|
9952
9953
|
},
|
|
9954
|
+
"package.delivered.title": "Parcel delivered",
|
|
9955
|
+
"package.delivered.body": "Delivered at {{camera}}{{inZones}}",
|
|
9956
|
+
"package.picked-up.title": "Parcel collected",
|
|
9957
|
+
"package.picked-up.body": "Collected from {{camera}}{{inZones}}",
|
|
9953
9958
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
9954
9959
|
"occupancy.op.occupied": "occupied",
|
|
9955
9960
|
"occupancy.op.free": "free",
|
|
@@ -10201,6 +10206,10 @@ var it_default = {
|
|
|
10201
10206
|
"one": "{{count}} altro {{classNoun}}",
|
|
10202
10207
|
"other": "{{count}} altri {{classNoun}}"
|
|
10203
10208
|
},
|
|
10209
|
+
"package.delivered.title": "Pacco consegnato",
|
|
10210
|
+
"package.delivered.body": "Consegnato su {{camera}}{{inZones}}",
|
|
10211
|
+
"package.picked-up.title": "Pacco ritirato",
|
|
10212
|
+
"package.picked-up.body": "Ritirato da {{camera}}{{inZones}}",
|
|
10204
10213
|
"occupancy.body": "{{scope}} {{op}} ({{count}}/{{capacity}})",
|
|
10205
10214
|
"occupancy.op.occupied": "occupata",
|
|
10206
10215
|
"occupancy.op.free": "libera",
|
|
@@ -12043,6 +12052,7 @@ function bodyKeyFor(entry) {
|
|
|
12043
12052
|
const subject = entry.payload.subject;
|
|
12044
12053
|
if (subject.systemEvent !== void 0) return subject.systemEvent.bodyKey ?? "";
|
|
12045
12054
|
if (subject.occupancy !== void 0) return "occupancy.body";
|
|
12055
|
+
if (subject.packagePhase !== void 0) return `package.${subject.packagePhase}.body`;
|
|
12046
12056
|
if (entry.payload.enhancement === true) return "detection.body.enhanced";
|
|
12047
12057
|
return entry.recordKind === "track-end" ? "detection.body.trackEnd" : "detection.body";
|
|
12048
12058
|
}
|
|
@@ -12050,6 +12060,8 @@ function bodyKeyFor(entry) {
|
|
|
12050
12060
|
function titleKeyFor(entry) {
|
|
12051
12061
|
const systemEvent = entry.payload.subject.systemEvent;
|
|
12052
12062
|
if (systemEvent !== void 0) return systemEvent.titleKey ?? "";
|
|
12063
|
+
const packagePhase = entry.payload.subject.packagePhase;
|
|
12064
|
+
if (packagePhase !== void 0) return `package.${packagePhase}.title`;
|
|
12053
12065
|
return "detection.title";
|
|
12054
12066
|
}
|
|
12055
12067
|
/**
|
|
@@ -24834,6 +24846,7 @@ var NotificationCenter = class NotificationCenter {
|
|
|
24834
24846
|
zones: renderedZones,
|
|
24835
24847
|
timestamp: subject.timestamp,
|
|
24836
24848
|
...subject.occupancy !== void 0 ? { occupancy: frozenOccupancy(rule, subject.occupancy) } : {},
|
|
24849
|
+
...subject.packagePhase !== void 0 ? { packagePhase: subject.packagePhase } : {},
|
|
24837
24850
|
...subject.systemEvent !== void 0 ? { systemEvent: subject.systemEvent } : {}
|
|
24838
24851
|
}
|
|
24839
24852
|
};
|
|
@@ -32746,9 +32759,14 @@ var FaceStore = class {
|
|
|
32746
32759
|
* with that person named. High means "this looks like someone we can name",
|
|
32747
32760
|
* which is what a mis-enrolment actually looks like.
|
|
32748
32761
|
*
|
|
32749
|
-
* `suspicion`
|
|
32750
|
-
* one
|
|
32751
|
-
*
|
|
32762
|
+
* `suspicion` is the EXCESS — `bestForeign − selfMean` — because that is the
|
|
32763
|
+
* only one of the two that discriminates on real data. Measured on Roberta's
|
|
32764
|
+
* 106 samples, 2026-09-08: `selfMean` has a median of 0.188 and a p90 of 0.235,
|
|
32765
|
+
* so ANY absolute reading of "does not look like itself" fires on the whole
|
|
32766
|
+
* gallery. The first version of this module scored `(1 − selfMean) / 2` and
|
|
32767
|
+
* flagged 56 of 106 — a finding that fires on healthy samples means nothing.
|
|
32768
|
+
* `bestForeign` on the same gallery: median 0.283, p95 0.376, and one single
|
|
32769
|
+
* outlier at 0.612, which is the mis-enrolment.
|
|
32752
32770
|
*
|
|
32753
32771
|
* ## What it refuses to do
|
|
32754
32772
|
*
|
|
@@ -32763,8 +32781,20 @@ var FaceStore = class {
|
|
|
32763
32781
|
*
|
|
32764
32782
|
* Pure — the caller loads the vectors and performs the removal.
|
|
32765
32783
|
*/
|
|
32766
|
-
/**
|
|
32767
|
-
|
|
32784
|
+
/**
|
|
32785
|
+
* A row is FLAGGED when its best foreign match would auto-assign the face to
|
|
32786
|
+
* that other person — i.e. it reaches the recogniser's own
|
|
32787
|
+
* `similarityThreshold`.
|
|
32788
|
+
*
|
|
32789
|
+
* Tied to the live threshold rather than to a constant of its own, because
|
|
32790
|
+
* that is what the flag MEANS: this sample can steal a match. A second number
|
|
32791
|
+
* invented here would drift from the one the recogniser actually uses, and the
|
|
32792
|
+
* panel would accuse samples that cannot hurt anyone while staying quiet about
|
|
32793
|
+
* ones that can. On the live gallery (threshold 0.40) it flags 2 of 106.
|
|
32794
|
+
*/
|
|
32795
|
+
function flaggedCount(rows, autoAssignThreshold) {
|
|
32796
|
+
return rows.filter((row) => (row.bestForeign?.score ?? 0) >= autoAssignThreshold).length;
|
|
32797
|
+
}
|
|
32768
32798
|
/**
|
|
32769
32799
|
* How far a foreign match has to stand OUT to count as looking like someone
|
|
32770
32800
|
* else: `bestForeign - selfMean`. A sample that scores 0.6 against a stranger
|
|
@@ -32819,8 +32849,7 @@ function auditIdentitySamples(input) {
|
|
|
32819
32849
|
};
|
|
32820
32850
|
const selfMean = meanCosine(sample, comparable);
|
|
32821
32851
|
const foreign = bestForeignMatch(sample, input.others, input.modelId);
|
|
32822
|
-
const
|
|
32823
|
-
const suspicion = selfMean === null ? 0 : (1 - selfMean) / 2 + excess;
|
|
32852
|
+
const suspicion = foreignExcess(selfMean, foreign);
|
|
32824
32853
|
return {
|
|
32825
32854
|
sampleId: sample.sampleId,
|
|
32826
32855
|
enrolledAt: sample.enrolledAt,
|
|
@@ -32831,12 +32860,15 @@ function auditIdentitySamples(input) {
|
|
|
32831
32860
|
suspicion
|
|
32832
32861
|
};
|
|
32833
32862
|
});
|
|
32834
|
-
rows.sort((a, b) =>
|
|
32863
|
+
rows.sort((a, b) => {
|
|
32864
|
+
if (b.suspicion !== a.suspicion) return b.suspicion - a.suspicion;
|
|
32865
|
+
return (a.selfMean ?? Number.POSITIVE_INFINITY) - (b.selfMean ?? Number.POSITIVE_INFINITY);
|
|
32866
|
+
});
|
|
32835
32867
|
return {
|
|
32836
32868
|
identityId: input.identityId,
|
|
32837
32869
|
totalSamples: input.samples.length,
|
|
32838
32870
|
comparableSamples: comparable.length,
|
|
32839
|
-
flagged: rows
|
|
32871
|
+
flagged: flaggedCount(rows, input.autoAssignThreshold),
|
|
32840
32872
|
rows
|
|
32841
32873
|
};
|
|
32842
32874
|
}
|
|
@@ -32893,6 +32925,7 @@ var FaceGalleryProvider = class {
|
|
|
32893
32925
|
logger;
|
|
32894
32926
|
refreshGallery;
|
|
32895
32927
|
resolveClusterModelId;
|
|
32928
|
+
resolveAutoAssignThreshold;
|
|
32896
32929
|
eventMediaBaseUrl;
|
|
32897
32930
|
eventMediaPathPrefix;
|
|
32898
32931
|
emitFaceGalleryChanged;
|
|
@@ -32905,6 +32938,7 @@ var FaceGalleryProvider = class {
|
|
|
32905
32938
|
this.logger = deps.logger;
|
|
32906
32939
|
this.refreshGallery = deps.refreshGallery;
|
|
32907
32940
|
this.resolveClusterModelId = deps.resolveClusterModelId;
|
|
32941
|
+
this.resolveAutoAssignThreshold = deps.resolveAutoAssignThreshold;
|
|
32908
32942
|
this.eventMediaBaseUrl = deps.eventMediaBaseUrl;
|
|
32909
32943
|
this.eventMediaPathPrefix = deps.eventMediaPathPrefix;
|
|
32910
32944
|
this.emitFaceGalleryChanged = deps.emitFaceGalleryChanged;
|
|
@@ -32996,6 +33030,7 @@ var FaceGalleryProvider = class {
|
|
|
32996
33030
|
async auditIdentitySamples(input) {
|
|
32997
33031
|
const modelId = await this.resolveClusterModelId();
|
|
32998
33032
|
if (modelId === null) throw new Error("auditIdentitySamples: the cluster face-embedding model could not be resolved");
|
|
33033
|
+
const autoAssignThreshold = await this.resolveAutoAssignThreshold();
|
|
32999
33034
|
const identities = await this.identityStore.listIdentities();
|
|
33000
33035
|
const target = identities.find((i) => i.id === input.identityId);
|
|
33001
33036
|
if (target === void 0) throw new Error(`auditIdentitySamples: no identity ${input.identityId}`);
|
|
@@ -33021,7 +33056,8 @@ var FaceGalleryProvider = class {
|
|
|
33021
33056
|
name: identity.name,
|
|
33022
33057
|
samples: inputsFor(identity.id)
|
|
33023
33058
|
})),
|
|
33024
|
-
modelId
|
|
33059
|
+
modelId,
|
|
33060
|
+
autoAssignThreshold
|
|
33025
33061
|
});
|
|
33026
33062
|
const meta = new Map((samplesByIdentity.get(input.identityId) ?? []).map((sample) => [sample.id, sample]));
|
|
33027
33063
|
return {
|
|
@@ -33031,7 +33067,7 @@ var FaceGalleryProvider = class {
|
|
|
33031
33067
|
totalSamples: report.totalSamples,
|
|
33032
33068
|
comparableSamples: report.comparableSamples,
|
|
33033
33069
|
flagged: report.flagged,
|
|
33034
|
-
flagThreshold:
|
|
33070
|
+
flagThreshold: autoAssignThreshold,
|
|
33035
33071
|
rows: report.rows.map((row) => {
|
|
33036
33072
|
const sample = meta.get(row.sampleId);
|
|
33037
33073
|
return {
|
|
@@ -57858,6 +57894,9 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
57858
57894
|
let keyless = 0;
|
|
57859
57895
|
let skippedGrace = 0;
|
|
57860
57896
|
let stoppedByCeiling = false;
|
|
57897
|
+
let missed = 0;
|
|
57898
|
+
/** Verdicts held until there are enough of them to tell rot from a move. */
|
|
57899
|
+
const held = [];
|
|
57861
57900
|
const identities = await deps.listIdentities();
|
|
57862
57901
|
for (const identity of identities) {
|
|
57863
57902
|
const samples = await deps.listSamples(identity.id);
|
|
@@ -57902,6 +57941,43 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
57902
57941
|
}
|
|
57903
57942
|
checked += 1;
|
|
57904
57943
|
if (state === "exists") continue;
|
|
57944
|
+
missed += 1;
|
|
57945
|
+
const minProbes = options.massMissMinProbes ?? 8;
|
|
57946
|
+
const bar = options.massMissFraction ?? .5;
|
|
57947
|
+
if (checked < minProbes) {
|
|
57948
|
+
held.push({
|
|
57949
|
+
identityId: identity.id,
|
|
57950
|
+
sampleId: sample.id,
|
|
57951
|
+
mediaKey: sample.mediaKey
|
|
57952
|
+
});
|
|
57953
|
+
continue;
|
|
57954
|
+
}
|
|
57955
|
+
if (missed / checked >= bar) {
|
|
57956
|
+
const reason = `mass miss: ${String(missed)} of ${String(checked)} probes found no blob`;
|
|
57957
|
+
deps.logger.warn("gallery reconcile: nearly every blob is missing — that is the volume, not the samples. Aborting and keeping every row; check where the gallery location points.", { meta: {
|
|
57958
|
+
checked,
|
|
57959
|
+
missed,
|
|
57960
|
+
held: held.length,
|
|
57961
|
+
identity: identity.name
|
|
57962
|
+
} });
|
|
57963
|
+
return {
|
|
57964
|
+
checked,
|
|
57965
|
+
reclaimed,
|
|
57966
|
+
keyless,
|
|
57967
|
+
skippedGrace,
|
|
57968
|
+
stoppedByCeiling,
|
|
57969
|
+
aborted: reason
|
|
57970
|
+
};
|
|
57971
|
+
}
|
|
57972
|
+
for (const pending of held.splice(0, held.length)) {
|
|
57973
|
+
await deps.removeSample(pending.identityId, pending.sampleId);
|
|
57974
|
+
reclaimed += 1;
|
|
57975
|
+
deps.logger.warn("gallery reconcile: sample blob is gone from the filesystem — reference removed", { meta: {
|
|
57976
|
+
sampleId: pending.sampleId,
|
|
57977
|
+
mediaKey: pending.mediaKey,
|
|
57978
|
+
state: "missing-blob"
|
|
57979
|
+
} });
|
|
57980
|
+
}
|
|
57905
57981
|
await deps.removeSample(identity.id, sample.id);
|
|
57906
57982
|
reclaimed += 1;
|
|
57907
57983
|
deps.logger.warn("gallery reconcile: sample blob is gone from the filesystem — reference removed", { meta: {
|
|
@@ -57912,6 +57988,14 @@ async function reconcileGallerySamples(deps, options) {
|
|
|
57912
57988
|
} });
|
|
57913
57989
|
}
|
|
57914
57990
|
}
|
|
57991
|
+
for (const pending of held.splice(0, held.length)) {
|
|
57992
|
+
if (reclaimed >= options.maxReclaimPerRun) {
|
|
57993
|
+
stoppedByCeiling = true;
|
|
57994
|
+
break;
|
|
57995
|
+
}
|
|
57996
|
+
await deps.removeSample(pending.identityId, pending.sampleId);
|
|
57997
|
+
reclaimed += 1;
|
|
57998
|
+
}
|
|
57915
57999
|
if (reclaimed > 0) deps.logger.info("gallery reconcile: dead references removed", { meta: {
|
|
57916
58000
|
checked,
|
|
57917
58001
|
reclaimed,
|
|
@@ -71890,6 +71974,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
71890
71974
|
eventStore: stores.eventStore,
|
|
71891
71975
|
logger: this.ctx.logger,
|
|
71892
71976
|
refreshGallery: () => this.faceRecognizer?.refreshGallery(),
|
|
71977
|
+
resolveAutoAssignThreshold: async () => (await this.readDeviceSettings(0, resolveFaceSettings)).similarityThreshold,
|
|
71893
71978
|
resolveClusterModelId: async () => {
|
|
71894
71979
|
const api = this.ctx.api;
|
|
71895
71980
|
if (!api) return null;
|
|
@@ -30,7 +30,7 @@ async function d(e) {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
async function f() {
|
|
33
|
-
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-
|
|
33
|
+
return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-DCfZE21-.mjs")).catch((e) => {
|
|
34
34
|
throw l = void 0, e;
|
|
35
35
|
}), l;
|
|
36
36
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-post-analysis",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.209",
|
|
4
4
|
"description": "Post-Analysis bundle — enrichment, embedding-encoder, pipeline-analytics. Multi-entry npm package shipping addons that consume pipeline output.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|