@camstack/addon-post-analysis 1.2.131 → 1.2.132
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-B8_nKey5.js → dist-BB-Ol201.js} +87 -5
- package/dist/{dist-C8JUWFmR.mjs → dist-XrPlCmxK.mjs} +87 -5
- 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-CaoDYCwF.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-_ZUR9O1b.mjs} +3 -3
- package/dist/pipeline-analytics/{hostInit-c_jfj7E4.mjs → hostInit-BrwrIFi_.mjs} +3 -3
- package/dist/pipeline-analytics/index.js +110 -26
- package/dist/pipeline-analytics/index.mjs +110 -26
- package/dist/pipeline-analytics/remoteEntry.js +1 -1
- package/package.json +1 -1
|
@@ -29876,17 +29876,60 @@ var SetSiteLocationInputSchema = object({
|
|
|
29876
29876
|
longitude: number().min(-180).max(180)
|
|
29877
29877
|
}).nullable();
|
|
29878
29878
|
/**
|
|
29879
|
-
*
|
|
29879
|
+
* The TRANSPORT a call arrived on.
|
|
29880
|
+
*
|
|
29881
|
+
* Every counted call carries exactly one of these, and `unknown` is a PLANE
|
|
29882
|
+
* rather than a gap: a plane that cannot attribute a call declares it here, so
|
|
29883
|
+
* the call lands in a named bucket instead of vanishing. `planes` summing to
|
|
29884
|
+
* `procedureCalls` is what makes "the sum of the planes explains the total"
|
|
29885
|
+
* checkable rather than asserted.
|
|
29886
|
+
*
|
|
29887
|
+
* - `http` — the Fastify tRPC plugin (`/trpc/*`), one context per request.
|
|
29888
|
+
* - `ws` — `applyWSSHandler`, counted per OPERATION rather than per
|
|
29889
|
+
* connection; the viewer talks to the hub over `wsLink`
|
|
29890
|
+
* exclusively, so this is the plane the HTTP census could not see.
|
|
29891
|
+
* - `mesh` — the in-process `$core-caps` bridge (`createCaller`), which
|
|
29892
|
+
* never touches a socket and therefore never touched a census.
|
|
29893
|
+
* - `unknown` — counted, plane undecidable. No hook produces it today, and
|
|
29894
|
+
* that is exactly what its `0` asserts: every plane the hub has can name
|
|
29895
|
+
* itself. It is an output bucket, never a knob — a call that arrives on a
|
|
29896
|
+
* plane nobody instrumented lands here instead of vanishing from the total.
|
|
29897
|
+
*/
|
|
29898
|
+
var TransportPlaneSchema = _enum([
|
|
29899
|
+
"http",
|
|
29900
|
+
"ws",
|
|
29901
|
+
"mesh",
|
|
29902
|
+
"unknown"
|
|
29903
|
+
]);
|
|
29904
|
+
/**
|
|
29905
|
+
* Calls per plane. Every key is always present, `0` included — an absent plane
|
|
29906
|
+
* reads as "not instrumented", which is the one thing this census must never
|
|
29907
|
+
* make an operator wonder about.
|
|
29908
|
+
*/
|
|
29909
|
+
var TransportPlaneCountsSchema = object({
|
|
29910
|
+
http: number(),
|
|
29911
|
+
ws: number(),
|
|
29912
|
+
mesh: number(),
|
|
29913
|
+
unknown: number()
|
|
29914
|
+
});
|
|
29915
|
+
/**
|
|
29916
|
+
* One `(plane, procedure, user-agent, ip, principal)` tuple of the transport
|
|
29880
29917
|
* census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
|
|
29881
29918
|
* `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
|
|
29882
29919
|
* already prints - never a token, never an `Authorization` header.
|
|
29920
|
+
*
|
|
29921
|
+
* `subscriptions` is counted APART from `calls`: a subscription is opened once
|
|
29922
|
+
* and lives for hours, so folding it into a call count makes one long-lived
|
|
29923
|
+
* stream look like a storm.
|
|
29883
29924
|
*/
|
|
29884
29925
|
var RequestCensusGroupSchema = object({
|
|
29926
|
+
plane: TransportPlaneSchema,
|
|
29885
29927
|
procedure: string(),
|
|
29886
29928
|
userAgent: string(),
|
|
29887
29929
|
ip: string(),
|
|
29888
29930
|
principal: string(),
|
|
29889
29931
|
calls: number(),
|
|
29932
|
+
subscriptions: number(),
|
|
29890
29933
|
perMin: number()
|
|
29891
29934
|
});
|
|
29892
29935
|
/**
|
|
@@ -29899,6 +29942,14 @@ var RequestCensusGroupSchema = object({
|
|
|
29899
29942
|
var RequestCensusProcedureSchema = object({
|
|
29900
29943
|
procedure: string(),
|
|
29901
29944
|
calls: number(),
|
|
29945
|
+
/**
|
|
29946
|
+
* The same total, split by transport. THIS is the row that answers the
|
|
29947
|
+
* question the census exists for: one look at `deviceManager.listAll` says
|
|
29948
|
+
* which plane carried the 4 960, without joining two log lines by eye.
|
|
29949
|
+
*/
|
|
29950
|
+
planes: TransportPlaneCountsSchema,
|
|
29951
|
+
/** Subscription STARTS on this procedure. Never folded into `calls`. */
|
|
29952
|
+
subscriptions: number(),
|
|
29902
29953
|
perMin: number()
|
|
29903
29954
|
});
|
|
29904
29955
|
/**
|
|
@@ -29926,14 +29977,45 @@ var RequestCensusStatusSchema = object({
|
|
|
29926
29977
|
*/
|
|
29927
29978
|
procedureCalls: number(),
|
|
29928
29979
|
/**
|
|
29980
|
+
* `procedureCalls` split by transport. The four keys sum to
|
|
29981
|
+
* `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
|
|
29982
|
+
* `planesExplainTotal` is that identity, checked rather than assumed.
|
|
29983
|
+
*/
|
|
29984
|
+
planes: TransportPlaneCountsSchema,
|
|
29985
|
+
/**
|
|
29986
|
+
* True iff `planes` sums to `procedureCalls`. False means a call was counted
|
|
29987
|
+
* on no plane at all - which is a RESULT (a plane is missing from the
|
|
29988
|
+
* instrument), not a failure, and it has to be visible to be read as one.
|
|
29989
|
+
*/
|
|
29990
|
+
planesExplainTotal: boolean(),
|
|
29991
|
+
/**
|
|
29929
29992
|
* tRPC WebSocket connections opened during the window. NOT calls - the WS
|
|
29930
|
-
*
|
|
29931
|
-
*
|
|
29993
|
+
* adapter resolves one context per connection - kept because a plane's call
|
|
29994
|
+
* count of zero against 37 open connections says something different from a
|
|
29995
|
+
* plane with no connections at all.
|
|
29932
29996
|
*/
|
|
29933
29997
|
wsConnections: number(),
|
|
29998
|
+
/**
|
|
29999
|
+
* Client frames the WS plane looked at. `wsMessages` far above
|
|
30000
|
+
* `planes.ws + subscriptions` means most traffic is not operations
|
|
30001
|
+
* (keepalives, connection params) - which is itself an answer.
|
|
30002
|
+
*/
|
|
30003
|
+
wsMessages: number(),
|
|
30004
|
+
/**
|
|
30005
|
+
* Subscription STARTS across every plane, excluded from `procedureCalls` on
|
|
30006
|
+
* purpose: one live-events stream opened at boot and held for six hours is
|
|
30007
|
+
* one subscription, and counting it as a call would let a quiet plane
|
|
30008
|
+
* masquerade as the storm.
|
|
30009
|
+
*/
|
|
30010
|
+
subscriptions: number(),
|
|
30011
|
+
/** `subscription.stop` frames. Starts minus stops is what is still open. */
|
|
30012
|
+
subscriptionStops: number(),
|
|
29934
30013
|
distinctGroups: number(),
|
|
29935
|
-
/**
|
|
29936
|
-
*
|
|
30014
|
+
/**
|
|
30015
|
+
* Operations counted in the totals whose CALLER attribution was shed at the
|
|
30016
|
+
* cardinality bound. Unrelated to the `unknown` PLANE: these calls know
|
|
30017
|
+
* which transport they arrived on, they just lost their group row.
|
|
30018
|
+
*/
|
|
29937
30019
|
unattributedCalls: number(),
|
|
29938
30020
|
procedures: array(RequestCensusProcedureSchema).readonly(),
|
|
29939
30021
|
groups: array(RequestCensusGroupSchema).readonly()
|
|
@@ -29845,17 +29845,60 @@ var SetSiteLocationInputSchema = object({
|
|
|
29845
29845
|
longitude: number().min(-180).max(180)
|
|
29846
29846
|
}).nullable();
|
|
29847
29847
|
/**
|
|
29848
|
-
*
|
|
29848
|
+
* The TRANSPORT a call arrived on.
|
|
29849
|
+
*
|
|
29850
|
+
* Every counted call carries exactly one of these, and `unknown` is a PLANE
|
|
29851
|
+
* rather than a gap: a plane that cannot attribute a call declares it here, so
|
|
29852
|
+
* the call lands in a named bucket instead of vanishing. `planes` summing to
|
|
29853
|
+
* `procedureCalls` is what makes "the sum of the planes explains the total"
|
|
29854
|
+
* checkable rather than asserted.
|
|
29855
|
+
*
|
|
29856
|
+
* - `http` — the Fastify tRPC plugin (`/trpc/*`), one context per request.
|
|
29857
|
+
* - `ws` — `applyWSSHandler`, counted per OPERATION rather than per
|
|
29858
|
+
* connection; the viewer talks to the hub over `wsLink`
|
|
29859
|
+
* exclusively, so this is the plane the HTTP census could not see.
|
|
29860
|
+
* - `mesh` — the in-process `$core-caps` bridge (`createCaller`), which
|
|
29861
|
+
* never touches a socket and therefore never touched a census.
|
|
29862
|
+
* - `unknown` — counted, plane undecidable. No hook produces it today, and
|
|
29863
|
+
* that is exactly what its `0` asserts: every plane the hub has can name
|
|
29864
|
+
* itself. It is an output bucket, never a knob — a call that arrives on a
|
|
29865
|
+
* plane nobody instrumented lands here instead of vanishing from the total.
|
|
29866
|
+
*/
|
|
29867
|
+
var TransportPlaneSchema = _enum([
|
|
29868
|
+
"http",
|
|
29869
|
+
"ws",
|
|
29870
|
+
"mesh",
|
|
29871
|
+
"unknown"
|
|
29872
|
+
]);
|
|
29873
|
+
/**
|
|
29874
|
+
* Calls per plane. Every key is always present, `0` included — an absent plane
|
|
29875
|
+
* reads as "not instrumented", which is the one thing this census must never
|
|
29876
|
+
* make an operator wonder about.
|
|
29877
|
+
*/
|
|
29878
|
+
var TransportPlaneCountsSchema = object({
|
|
29879
|
+
http: number(),
|
|
29880
|
+
ws: number(),
|
|
29881
|
+
mesh: number(),
|
|
29882
|
+
unknown: number()
|
|
29883
|
+
});
|
|
29884
|
+
/**
|
|
29885
|
+
* One `(plane, procedure, user-agent, ip, principal)` tuple of the transport
|
|
29849
29886
|
* census. `principal` is the DERIVED identity (`apocaliss92 (admin)`,
|
|
29850
29887
|
* `scoped:1a2b3c4d (scoped-token)`, `anonymous`) that the tRPC error log
|
|
29851
29888
|
* already prints - never a token, never an `Authorization` header.
|
|
29889
|
+
*
|
|
29890
|
+
* `subscriptions` is counted APART from `calls`: a subscription is opened once
|
|
29891
|
+
* and lives for hours, so folding it into a call count makes one long-lived
|
|
29892
|
+
* stream look like a storm.
|
|
29852
29893
|
*/
|
|
29853
29894
|
var RequestCensusGroupSchema = object({
|
|
29895
|
+
plane: TransportPlaneSchema,
|
|
29854
29896
|
procedure: string(),
|
|
29855
29897
|
userAgent: string(),
|
|
29856
29898
|
ip: string(),
|
|
29857
29899
|
principal: string(),
|
|
29858
29900
|
calls: number(),
|
|
29901
|
+
subscriptions: number(),
|
|
29859
29902
|
perMin: number()
|
|
29860
29903
|
});
|
|
29861
29904
|
/**
|
|
@@ -29868,6 +29911,14 @@ var RequestCensusGroupSchema = object({
|
|
|
29868
29911
|
var RequestCensusProcedureSchema = object({
|
|
29869
29912
|
procedure: string(),
|
|
29870
29913
|
calls: number(),
|
|
29914
|
+
/**
|
|
29915
|
+
* The same total, split by transport. THIS is the row that answers the
|
|
29916
|
+
* question the census exists for: one look at `deviceManager.listAll` says
|
|
29917
|
+
* which plane carried the 4 960, without joining two log lines by eye.
|
|
29918
|
+
*/
|
|
29919
|
+
planes: TransportPlaneCountsSchema,
|
|
29920
|
+
/** Subscription STARTS on this procedure. Never folded into `calls`. */
|
|
29921
|
+
subscriptions: number(),
|
|
29871
29922
|
perMin: number()
|
|
29872
29923
|
});
|
|
29873
29924
|
/**
|
|
@@ -29895,14 +29946,45 @@ var RequestCensusStatusSchema = object({
|
|
|
29895
29946
|
*/
|
|
29896
29947
|
procedureCalls: number(),
|
|
29897
29948
|
/**
|
|
29949
|
+
* `procedureCalls` split by transport. The four keys sum to
|
|
29950
|
+
* `procedureCalls` by construction - {@link RequestCensusSnapshotSchema}'s
|
|
29951
|
+
* `planesExplainTotal` is that identity, checked rather than assumed.
|
|
29952
|
+
*/
|
|
29953
|
+
planes: TransportPlaneCountsSchema,
|
|
29954
|
+
/**
|
|
29955
|
+
* True iff `planes` sums to `procedureCalls`. False means a call was counted
|
|
29956
|
+
* on no plane at all - which is a RESULT (a plane is missing from the
|
|
29957
|
+
* instrument), not a failure, and it has to be visible to be read as one.
|
|
29958
|
+
*/
|
|
29959
|
+
planesExplainTotal: boolean(),
|
|
29960
|
+
/**
|
|
29898
29961
|
* tRPC WebSocket connections opened during the window. NOT calls - the WS
|
|
29899
|
-
*
|
|
29900
|
-
*
|
|
29962
|
+
* adapter resolves one context per connection - kept because a plane's call
|
|
29963
|
+
* count of zero against 37 open connections says something different from a
|
|
29964
|
+
* plane with no connections at all.
|
|
29901
29965
|
*/
|
|
29902
29966
|
wsConnections: number(),
|
|
29967
|
+
/**
|
|
29968
|
+
* Client frames the WS plane looked at. `wsMessages` far above
|
|
29969
|
+
* `planes.ws + subscriptions` means most traffic is not operations
|
|
29970
|
+
* (keepalives, connection params) - which is itself an answer.
|
|
29971
|
+
*/
|
|
29972
|
+
wsMessages: number(),
|
|
29973
|
+
/**
|
|
29974
|
+
* Subscription STARTS across every plane, excluded from `procedureCalls` on
|
|
29975
|
+
* purpose: one live-events stream opened at boot and held for six hours is
|
|
29976
|
+
* one subscription, and counting it as a call would let a quiet plane
|
|
29977
|
+
* masquerade as the storm.
|
|
29978
|
+
*/
|
|
29979
|
+
subscriptions: number(),
|
|
29980
|
+
/** `subscription.stop` frames. Starts minus stops is what is still open. */
|
|
29981
|
+
subscriptionStops: number(),
|
|
29903
29982
|
distinctGroups: number(),
|
|
29904
|
-
/**
|
|
29905
|
-
*
|
|
29983
|
+
/**
|
|
29984
|
+
* Operations counted in the totals whose CALLER attribution was shed at the
|
|
29985
|
+
* cardinality bound. Unrelated to the `unknown` PLANE: these calls know
|
|
29986
|
+
* which transport they arrived on, they just lost their group row.
|
|
29987
|
+
*/
|
|
29906
29988
|
unattributedCalls: number(),
|
|
29907
29989
|
procedures: array(RequestCensusProcedureSchema).readonly(),
|
|
29908
29990
|
groups: array(RequestCensusGroupSchema).readonly()
|
|
@@ -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-BB-Ol201.js");
|
|
6
6
|
let node_fs = require("node:fs");
|
|
7
7
|
let node_fs$1 = require_dist.__toESM(node_fs, 1);
|
|
8
8
|
node_fs = require_dist.__toESM(node_fs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as PoolMemoryWatchdog, at as hfModelUrl, gt as resolvePoolMemoryPolicy, nt as embeddingEncoderCapability, ut as parseProcStatus, wt as BaseAddon } from "../dist-
|
|
1
|
+
import { M as PoolMemoryWatchdog, at as hfModelUrl, gt as resolvePoolMemoryPolicy, nt as embeddingEncoderCapability, ut as parseProcStatus, wt as BaseAddon } from "../dist-XrPlCmxK.mjs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import "node:fs";
|
|
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
|
|
|
3
3
|
var e = {
|
|
4
4
|
"@camstack/sdk": {
|
|
5
5
|
name: "@camstack/sdk",
|
|
6
|
-
version: "1.2.
|
|
6
|
+
version: "1.2.36",
|
|
7
7
|
scope: ["default"],
|
|
8
8
|
loaded: !1,
|
|
9
9
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -18,7 +18,7 @@ var e = {
|
|
|
18
18
|
},
|
|
19
19
|
"@camstack/types": {
|
|
20
20
|
name: "@camstack/types",
|
|
21
|
-
version: "1.2.
|
|
21
|
+
version: "1.2.114",
|
|
22
22
|
scope: ["default"],
|
|
23
23
|
loaded: !1,
|
|
24
24
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -33,7 +33,7 @@ var e = {
|
|
|
33
33
|
},
|
|
34
34
|
"@camstack/ui-library": {
|
|
35
35
|
name: "@camstack/ui-library",
|
|
36
|
-
version: "1.2.
|
|
36
|
+
version: "1.2.77",
|
|
37
37
|
scope: ["default"],
|
|
38
38
|
loaded: !1,
|
|
39
39
|
from: "addon_pipeline_analytics_widgets",
|
|
@@ -36,7 +36,7 @@ async function r() {
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"@camstack/types": {
|
|
39
|
-
version: "1.2.
|
|
39
|
+
version: "1.2.114",
|
|
40
40
|
scope: "default",
|
|
41
41
|
shareConfig: {
|
|
42
42
|
singleton: !0,
|
|
@@ -45,7 +45,7 @@ async function r() {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"@camstack/sdk": {
|
|
48
|
-
version: "1.2.
|
|
48
|
+
version: "1.2.36",
|
|
49
49
|
scope: "default",
|
|
50
50
|
shareConfig: {
|
|
51
51
|
singleton: !0,
|
|
@@ -81,7 +81,7 @@ async function r() {
|
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
83
|
"@camstack/ui-library": {
|
|
84
|
-
version: "1.2.
|
|
84
|
+
version: "1.2.77",
|
|
85
85
|
scope: "default",
|
|
86
86
|
shareConfig: {
|
|
87
87
|
singleton: !0,
|
|
@@ -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-BB-Ol201.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");
|
|
@@ -15365,33 +15365,95 @@ function remainingSummaryCandidates(kept, taken, rank) {
|
|
|
15365
15365
|
/**
|
|
15366
15366
|
* The media a tile is drawn from, best first.
|
|
15367
15367
|
*
|
|
15368
|
-
*
|
|
15369
|
-
*
|
|
15370
|
-
*
|
|
15371
|
-
*
|
|
15372
|
-
*
|
|
15373
|
-
*
|
|
15374
|
-
*
|
|
15375
|
-
*
|
|
15368
|
+
* A digest tile is a SUBJECT, so the crops come first: the group's first-level
|
|
15369
|
+
* crop (`groupCrop`), the group's native-frame pointer, then the TRACK's own
|
|
15370
|
+
* subject crop (`thumbnail`, then its `thumbnailSmall` downscale), and only
|
|
15371
|
+
* then the clean whole scene (`keyFrame` → `keyFrameSmall` → `fullFrame`).
|
|
15372
|
+
*
|
|
15373
|
+
* ⚠ The whole-scene rungs are a FALLBACK and stay on the ladder. Measured on
|
|
15374
|
+
* 60 real tracks (615/617, 24 h): 52 carry a crop, 43 carry a scene rung, and
|
|
15375
|
+
* 2 carry a scene rung and NO crop — dropping the fallback would delete those
|
|
15376
|
+
* two tiles for nothing. A track with neither is dropped, never drawn as an
|
|
15377
|
+
* empty cell (see `NcSummaryProducer.loadTiles`).
|
|
15378
|
+
*
|
|
15379
|
+
* ## Why `thumbnail` is back (2026-08-27)
|
|
15380
|
+
*
|
|
15381
|
+
* It was removed on 2026-08-24 (160200681) because a successful plate read
|
|
15382
|
+
* overwrites the vehicle first-level crop under the same `thumbnail` kind.
|
|
15383
|
+
* **That collision belongs to the GROUP entity**: the sealer is what writes
|
|
15384
|
+
* `ownerKind: 'group', kind: 'thumbnail'` (`group/group-sealer.ts`), and the
|
|
15385
|
+
* group process is OFF (`GROUPS_PROCESS_ENABLED`, `group/group-enabled.ts`).
|
|
15386
|
+
* On a TRACK, plates live under `ownerKind: 'plate'` / `kind: 'plateCrop'` and
|
|
15387
|
+
* never touch `thumbnail`. The same commit turned the group process off, so
|
|
15388
|
+
* `groupCrop` and `nativeFrame` became unreachable and every digest tile fell
|
|
15389
|
+
* through to `keyFrame` — a whole frame — for three days.
|
|
15390
|
+
* `scripts/check-summary-ladder-reachable.ts` is the guard that refuses that
|
|
15391
|
+
* shape now.
|
|
15392
|
+
*
|
|
15393
|
+
* Boxed frames stay absent: a drawn rectangle is a permanent lie in a digest
|
|
15394
|
+
* tile. Plate and face crops stay absent: a digest tile of a targa is the
|
|
15395
|
+
* wrong picture. Duplicate driveways from tracker ID churn are collapsed by
|
|
15396
|
+
* `frameDedupeKey`, not by hiding the whole-scene fallback.
|
|
15376
15397
|
*/
|
|
15377
15398
|
var SUMMARY_MEDIA_LADDER = [
|
|
15378
15399
|
"groupCrop",
|
|
15379
15400
|
"nativeFrame",
|
|
15401
|
+
"thumbnail",
|
|
15402
|
+
"thumbnailSmall",
|
|
15380
15403
|
"keyFrame",
|
|
15381
15404
|
"keyFrameSmall",
|
|
15382
15405
|
"fullFrame"
|
|
15383
15406
|
];
|
|
15384
15407
|
/**
|
|
15408
|
+
* The floor below which a `thumbnail` row is NOT a subject crop.
|
|
15409
|
+
*
|
|
15410
|
+
* `copyFaceCropIfTrackHasNoThumbnail` (`pipeline-analytics/copy-face-crop-preview.ts`)
|
|
15411
|
+
* copies the persisted face crop onto a track that has no preview of its own,
|
|
15412
|
+
* and it stores it under `kind: 'thumbnail'` — so an ArcFace **112×112**
|
|
15413
|
+
* alignment template lands on the ladder wearing the same kind as a real
|
|
15414
|
+
* subject crop. Measured: 3 of 52 track thumbnails (5.8%) are 3–10 KB
|
|
15415
|
+
* templates. Delivering one is a face in a digest of passages.
|
|
15416
|
+
*
|
|
15417
|
+
* 20 KiB is chosen as the smallest number that is unambiguous in BOTH
|
|
15418
|
+
* directions: it is 2× the largest template observed (10 KB) and above the
|
|
15419
|
+
* theoretical ceiling of a 112×112 JPEG at any quality, while a native-
|
|
15420
|
+
* resolution subject crop measures in the hundreds of KB. The second reason
|
|
15421
|
+
* the number is safe: a genuine crop small enough to fall under it is a
|
|
15422
|
+
* subject a few dozen pixels across, which is a smear in a mosaic cell — the
|
|
15423
|
+
* whole-scene rung below is the better picture either way.
|
|
15424
|
+
*
|
|
15425
|
+
* The check costs NOTHING: `MediaFileInfo.sizeBytes` is already on the index
|
|
15426
|
+
* row, so no blob is fetched and nothing is decoded to apply it.
|
|
15427
|
+
*/
|
|
15428
|
+
var SUMMARY_THUMBNAIL_MIN_BYTES = 20480;
|
|
15429
|
+
/** The rungs {@link SUMMARY_THUMBNAIL_MIN_BYTES} is applied to. */
|
|
15430
|
+
var SUMMARY_SIZE_GATED_RUNGS = new Set(["thumbnail"]);
|
|
15431
|
+
/**
|
|
15432
|
+
* Is this row a picture, or a template wearing a picture's kind?
|
|
15433
|
+
*
|
|
15434
|
+
* Only a size the index REPORTED can demote a row. A `null` size is unknown,
|
|
15435
|
+
* and demoting on unknown would silently delete every group-owned pointer.
|
|
15436
|
+
*/
|
|
15437
|
+
function rowClearsSizeFloor(row, rung) {
|
|
15438
|
+
if (!SUMMARY_SIZE_GATED_RUNGS.has(rung)) return true;
|
|
15439
|
+
if (typeof row.sizeBytes !== "number") return true;
|
|
15440
|
+
return row.sizeBytes >= SUMMARY_THUMBNAIL_MIN_BYTES;
|
|
15441
|
+
}
|
|
15442
|
+
/**
|
|
15385
15443
|
* Pick ONE media row for a track — the cheap question first (index rows only),
|
|
15386
15444
|
* so exactly one blob is ever read.
|
|
15387
15445
|
*
|
|
15388
15446
|
* Within a rung the row CLOSEST to the track's peak is chosen; a member may
|
|
15389
15447
|
* carry more than one full-frame still, so "the first one" would systematically
|
|
15390
15448
|
* pick the moment the subject entered rather than the moment it was best seen.
|
|
15449
|
+
*
|
|
15450
|
+
* A `thumbnail` under {@link SUMMARY_THUMBNAIL_MIN_BYTES} DESCENDS to the next
|
|
15451
|
+
* rung rather than being drawn — see that constant for the face template it
|
|
15452
|
+
* exists to catch.
|
|
15391
15453
|
*/
|
|
15392
15454
|
function pickSummaryMedia(rows, aroundMs) {
|
|
15393
15455
|
for (const rung of SUMMARY_MEDIA_LADDER) {
|
|
15394
|
-
const matches = rows.filter((row) => row.kind === rung);
|
|
15456
|
+
const matches = rows.filter((row) => row.kind === rung && rowClearsSizeFloor(row, rung));
|
|
15395
15457
|
if (matches.length === 0) continue;
|
|
15396
15458
|
return matches.reduce((best, row) => Math.abs(row.timestamp - aroundMs) < Math.abs(best.timestamp - aroundMs) ? row : best);
|
|
15397
15459
|
}
|
|
@@ -15703,12 +15765,13 @@ var NcSummaryProducer = class {
|
|
|
15703
15765
|
droppedStationary: filtered.droppedStationary
|
|
15704
15766
|
};
|
|
15705
15767
|
}
|
|
15706
|
-
const
|
|
15768
|
+
const tileLoad = await this.loadTiles({
|
|
15707
15769
|
rule,
|
|
15708
15770
|
preferred: selection.tiles,
|
|
15709
15771
|
refill: remainingSummaryCandidates(filtered.kept, selection.tiles, rule.rank),
|
|
15710
15772
|
maxTiles: rule.maxTiles
|
|
15711
15773
|
});
|
|
15774
|
+
const loaded = tileLoad.tiles;
|
|
15712
15775
|
const detections = countDetections({ tracks: selection.tiles.map((t) => ({
|
|
15713
15776
|
firstSeen: t.firstSeen,
|
|
15714
15777
|
lastSeen: t.lastSeen,
|
|
@@ -15766,7 +15829,8 @@ var NcSummaryProducer = class {
|
|
|
15766
15829
|
tiles: loaded.length,
|
|
15767
15830
|
cameras: new Set(loaded.map((t) => t.candidate.deviceId)).size,
|
|
15768
15831
|
camerasDropped: selection.camerasDropped,
|
|
15769
|
-
droppedNoMedia:
|
|
15832
|
+
droppedNoMedia: tileLoad.droppedNoMedia,
|
|
15833
|
+
droppedDuplicateFrame: tileLoad.droppedDuplicateFrame,
|
|
15770
15834
|
mosaicBytes: mosaic.bytes,
|
|
15771
15835
|
mosaicPublished: mosaic.ref !== null,
|
|
15772
15836
|
enqueued,
|
|
@@ -15832,14 +15896,20 @@ var NcSummaryProducer = class {
|
|
|
15832
15896
|
}
|
|
15833
15897
|
/** One blob per tile — the cheap question (index rows) always first.
|
|
15834
15898
|
* Duplicate camera-seconds are skipped so tracker ID churn cannot paint
|
|
15835
|
-
* the same driveway eleven times; leftover ranked candidates refill.
|
|
15899
|
+
* the same driveway eleven times; leftover ranked candidates refill.
|
|
15900
|
+
*
|
|
15901
|
+
* It COUNTS what it drops, per reason, because only this loop knows: the
|
|
15902
|
+
* caller sees tiles and has no way to tell a track that had no picture from
|
|
15903
|
+
* a rule whose quota was simply larger than the night. */
|
|
15836
15904
|
async loadTiles(input) {
|
|
15837
|
-
const
|
|
15905
|
+
const tiles = [];
|
|
15838
15906
|
const seenFrames = /* @__PURE__ */ new Set();
|
|
15839
15907
|
const seenIds = /* @__PURE__ */ new Set();
|
|
15908
|
+
let droppedNoMedia = 0;
|
|
15909
|
+
let droppedDuplicateFrame = 0;
|
|
15840
15910
|
const queue = [...input.preferred, ...input.refill];
|
|
15841
15911
|
for (const candidate of queue) {
|
|
15842
|
-
if (
|
|
15912
|
+
if (tiles.length >= input.maxTiles) break;
|
|
15843
15913
|
if (seenIds.has(candidate.trackId)) continue;
|
|
15844
15914
|
seenIds.add(candidate.trackId);
|
|
15845
15915
|
const tags = { deviceId: candidate.deviceId };
|
|
@@ -15847,19 +15917,22 @@ var NcSummaryProducer = class {
|
|
|
15847
15917
|
const rows = await this.deps.listMediaInfo(candidate.trackId);
|
|
15848
15918
|
const picked = pickSummaryMedia(rows, (candidate.firstSeen + candidate.lastSeen) / 2);
|
|
15849
15919
|
if (picked === null) {
|
|
15850
|
-
|
|
15920
|
+
droppedNoMedia += 1;
|
|
15921
|
+
this.deps.logger.info("summary tile dropped: the track carries no clean media", {
|
|
15851
15922
|
tags,
|
|
15852
15923
|
meta: {
|
|
15853
15924
|
ruleId: input.rule.id,
|
|
15854
15925
|
trackId: candidate.trackId,
|
|
15855
|
-
mediaRows: rows.length
|
|
15926
|
+
mediaRows: rows.length,
|
|
15927
|
+
mediaKinds: [...new Set(rows.map((row) => row.kind))].sort()
|
|
15856
15928
|
}
|
|
15857
15929
|
});
|
|
15858
15930
|
continue;
|
|
15859
15931
|
}
|
|
15860
15932
|
const frameKey = frameDedupeKey(candidate.deviceId, picked.timestamp);
|
|
15861
15933
|
if (seenFrames.has(frameKey)) {
|
|
15862
|
-
|
|
15934
|
+
droppedDuplicateFrame += 1;
|
|
15935
|
+
this.deps.logger.info("summary tile dropped: same frame as an earlier tile", {
|
|
15863
15936
|
tags,
|
|
15864
15937
|
meta: {
|
|
15865
15938
|
ruleId: input.rule.id,
|
|
@@ -15871,22 +15944,25 @@ var NcSummaryProducer = class {
|
|
|
15871
15944
|
}
|
|
15872
15945
|
const jpeg = await this.deps.readMedia(picked.key);
|
|
15873
15946
|
if (jpeg === null || jpeg.byteLength === 0) {
|
|
15874
|
-
|
|
15947
|
+
droppedNoMedia += 1;
|
|
15948
|
+
this.deps.logger.info("summary tile dropped: the media blob could not be read", {
|
|
15875
15949
|
tags,
|
|
15876
15950
|
meta: {
|
|
15877
15951
|
ruleId: input.rule.id,
|
|
15878
15952
|
trackId: candidate.trackId,
|
|
15879
|
-
mediaKey: picked.key
|
|
15953
|
+
mediaKey: picked.key,
|
|
15954
|
+
mediaKind: picked.kind
|
|
15880
15955
|
}
|
|
15881
15956
|
});
|
|
15882
15957
|
continue;
|
|
15883
15958
|
}
|
|
15884
15959
|
seenFrames.add(frameKey);
|
|
15885
|
-
|
|
15960
|
+
tiles.push({
|
|
15886
15961
|
candidate,
|
|
15887
15962
|
jpeg
|
|
15888
15963
|
});
|
|
15889
15964
|
} catch (err) {
|
|
15965
|
+
droppedNoMedia += 1;
|
|
15890
15966
|
this.deps.logger.warn("summary tile dropped: reading its media threw", {
|
|
15891
15967
|
tags,
|
|
15892
15968
|
meta: {
|
|
@@ -15897,7 +15973,11 @@ var NcSummaryProducer = class {
|
|
|
15897
15973
|
});
|
|
15898
15974
|
}
|
|
15899
15975
|
}
|
|
15900
|
-
return
|
|
15976
|
+
return {
|
|
15977
|
+
tiles,
|
|
15978
|
+
droppedNoMedia,
|
|
15979
|
+
droppedDuplicateFrame
|
|
15980
|
+
};
|
|
15901
15981
|
}
|
|
15902
15982
|
/**
|
|
15903
15983
|
* Render the mosaic, file it where event media lives, and mint the public
|
|
@@ -41300,12 +41380,14 @@ function digestMediaRows(input) {
|
|
|
41300
41380
|
if (input.mediaKey !== null && input.mediaKey.length > 0) owned.push({
|
|
41301
41381
|
key: input.mediaKey,
|
|
41302
41382
|
kind: "groupCrop",
|
|
41303
|
-
timestamp: input.timestamp
|
|
41383
|
+
timestamp: input.timestamp,
|
|
41384
|
+
sizeBytes: null
|
|
41304
41385
|
});
|
|
41305
41386
|
if (input.nativeKey !== null && input.nativeKey.length > 0) owned.push({
|
|
41306
41387
|
key: input.nativeKey,
|
|
41307
41388
|
kind: "nativeFrame",
|
|
41308
|
-
timestamp: input.timestamp
|
|
41389
|
+
timestamp: input.timestamp,
|
|
41390
|
+
sizeBytes: null
|
|
41309
41391
|
});
|
|
41310
41392
|
if (owned.length > 0) return owned;
|
|
41311
41393
|
return input.memberRows.filter((row) => DIGEST_MEMBER_FALLBACK_KINDS.has(row.kind));
|
|
@@ -60568,7 +60650,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
60568
60650
|
const memberRows = (await Promise.all(members.map((m) => stores.mediaStore.listInfoByOwner("track", m.trackId)))).flatMap((rows) => rows.map((r) => ({
|
|
60569
60651
|
key: r.key,
|
|
60570
60652
|
kind: r.kind,
|
|
60571
|
-
timestamp: r.timestamp
|
|
60653
|
+
timestamp: r.timestamp,
|
|
60654
|
+
sizeBytes: r.sizeBytes
|
|
60572
60655
|
})));
|
|
60573
60656
|
return digestMediaRows({
|
|
60574
60657
|
mediaKey: group.mediaKey,
|
|
@@ -60580,7 +60663,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends require_dist.B
|
|
|
60580
60663
|
return (await stores.mediaStore.listInfoByOwner("track", id)).map((r) => ({
|
|
60581
60664
|
key: r.key,
|
|
60582
60665
|
kind: r.kind,
|
|
60583
|
-
timestamp: r.timestamp
|
|
60666
|
+
timestamp: r.timestamp,
|
|
60667
|
+
sizeBytes: r.sizeBytes
|
|
60584
60668
|
}));
|
|
60585
60669
|
},
|
|
60586
60670
|
readMedia: async (key) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as customAction, A as NcTaxonomySchema, At as sleep, B as TIMELAPSE_DENSE_FLOOR_SEC, Bt as string, C as NcRuleSchema, Ct as errMsg, D as NcSnoozeSchema, Dt as hydrateSchema, E as NcSnoozeInputSchema, Et as createEvent, Ft as literal, G as VISIT_MERGE_GAP_MS, H as TimelapseRulePatchSchema, Ht as EventCategory, I as SCENE_DEFAULT_ANCHOR_THRESHOLD, It as number, J as assertTimelapseCadences, K as addonWidgetsSourceCapability, L as SCENE_DEFAULT_UNCOVERED_POLICY, Lt as object, Mt as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nt as boolean, O as NcSnoozeSuppressedSchema, Ot as isDeviceScopedCap, P as RetrainStatusSchema, Pt as discriminatedUnion, Q as cosineSimilarity$1, R as SCENE_DIVERGED, Rt as partialRecord, S as NcRulePatchSchema, St as zoneAnalyticsCapability, T as NcScheduleSchema, Tt as DeviceType, U as TimelapseRuleSchema, V as TimelapseRuleInputSchema, Vt as unknown, W as TrackSourceSchema, X as audioModeOf, Y as audioMetricsCapability, Z as buildEventKindDescriptor, _ as NC_DEFAULT_SNOOZE_MINUTES, _t as sceneMonitorCapability, a as DEFAULT_EVENT_COLOR, b as NcConditionDescriptorSchema, bt as vectorDimFromBase64, ct as kebabToCamel, d as EVENT_PAD_MS, dt as pickClusterStepModels, et as defineCustomActions, f as FULL_IMAGE_BBOX, ft as pipelineAnalyticsCapability, g as NC_CONDITION_CATALOG, h as NC_ALARM_SYSTEM_EVENT_KINDS, ht as readTimelapseGeneratedAt, i as CLUSTER_MODEL_SCOPED_STEPS, it as faceGalleryCapability, j as OpsLogEntrySchema, jt as _enum, k as NcSystemEventKindSchema, kt as nodePin, l as DeclaredDevices, lt as notificationRulesCapability, m as MACRO_LABELS, mt as readDeviceStateFrom, n as AnalyticsGroupRecordSchema, ot as isDetectionMacroClass, p as LabelAttributionSchema, pt as plateGalleryCapability, q as alarmPanelCapability, r as BaseDevice, rt as encodeVectorBase64, s as DETECTION_MACRO_CLASSES, st as isScheduleActive, t as AnalyticsGroupDetailSchema, tt as deriveRecordingMode, u as EVENT_KIND_BY_CAP, vt as subKindsOf, w as NcRuleTargetSchema, wt as BaseAddon, x as NcRuleInputSchema, xt as videoclipsCapability, y as NC_TAXONOMY, yt as systemEventFilterApplies, z as SceneMonitorSchema, zt as record } from "../dist-
|
|
1
|
+
import { $ as customAction, A as NcTaxonomySchema, At as sleep, B as TIMELAPSE_DENSE_FLOOR_SEC, Bt as string, C as NcRuleSchema, Ct as errMsg, D as NcSnoozeSchema, Dt as hydrateSchema, E as NcSnoozeInputSchema, Et as createEvent, Ft as literal, G as VISIT_MERGE_GAP_MS, H as TimelapseRulePatchSchema, Ht as EventCategory, I as SCENE_DEFAULT_ANCHOR_THRESHOLD, It as number, J as assertTimelapseCadences, K as addonWidgetsSourceCapability, L as SCENE_DEFAULT_UNCOVERED_POLICY, Lt as object, Mt as array, N as RECORDING_EXPORT_MAX_READ_BYTES, Nt as boolean, O as NcSnoozeSuppressedSchema, Ot as isDeviceScopedCap, P as RetrainStatusSchema, Pt as discriminatedUnion, Q as cosineSimilarity$1, R as SCENE_DIVERGED, Rt as partialRecord, S as NcRulePatchSchema, St as zoneAnalyticsCapability, T as NcScheduleSchema, Tt as DeviceType, U as TimelapseRuleSchema, V as TimelapseRuleInputSchema, Vt as unknown, W as TrackSourceSchema, X as audioModeOf, Y as audioMetricsCapability, Z as buildEventKindDescriptor, _ as NC_DEFAULT_SNOOZE_MINUTES, _t as sceneMonitorCapability, a as DEFAULT_EVENT_COLOR, b as NcConditionDescriptorSchema, bt as vectorDimFromBase64, ct as kebabToCamel, d as EVENT_PAD_MS, dt as pickClusterStepModels, et as defineCustomActions, f as FULL_IMAGE_BBOX, ft as pipelineAnalyticsCapability, g as NC_CONDITION_CATALOG, h as NC_ALARM_SYSTEM_EVENT_KINDS, ht as readTimelapseGeneratedAt, i as CLUSTER_MODEL_SCOPED_STEPS, it as faceGalleryCapability, j as OpsLogEntrySchema, jt as _enum, k as NcSystemEventKindSchema, kt as nodePin, l as DeclaredDevices, lt as notificationRulesCapability, m as MACRO_LABELS, mt as readDeviceStateFrom, n as AnalyticsGroupRecordSchema, ot as isDetectionMacroClass, p as LabelAttributionSchema, pt as plateGalleryCapability, q as alarmPanelCapability, r as BaseDevice, rt as encodeVectorBase64, s as DETECTION_MACRO_CLASSES, st as isScheduleActive, t as AnalyticsGroupDetailSchema, tt as deriveRecordingMode, u as EVENT_KIND_BY_CAP, vt as subKindsOf, w as NcRuleTargetSchema, wt as BaseAddon, x as NcRuleInputSchema, xt as videoclipsCapability, y as NC_TAXONOMY, yt as systemEventFilterApplies, z as SceneMonitorSchema, zt as record } from "../dist-XrPlCmxK.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";
|
|
@@ -15358,33 +15358,95 @@ function remainingSummaryCandidates(kept, taken, rank) {
|
|
|
15358
15358
|
/**
|
|
15359
15359
|
* The media a tile is drawn from, best first.
|
|
15360
15360
|
*
|
|
15361
|
-
*
|
|
15362
|
-
*
|
|
15363
|
-
*
|
|
15364
|
-
*
|
|
15365
|
-
*
|
|
15366
|
-
*
|
|
15367
|
-
*
|
|
15368
|
-
*
|
|
15361
|
+
* A digest tile is a SUBJECT, so the crops come first: the group's first-level
|
|
15362
|
+
* crop (`groupCrop`), the group's native-frame pointer, then the TRACK's own
|
|
15363
|
+
* subject crop (`thumbnail`, then its `thumbnailSmall` downscale), and only
|
|
15364
|
+
* then the clean whole scene (`keyFrame` → `keyFrameSmall` → `fullFrame`).
|
|
15365
|
+
*
|
|
15366
|
+
* ⚠ The whole-scene rungs are a FALLBACK and stay on the ladder. Measured on
|
|
15367
|
+
* 60 real tracks (615/617, 24 h): 52 carry a crop, 43 carry a scene rung, and
|
|
15368
|
+
* 2 carry a scene rung and NO crop — dropping the fallback would delete those
|
|
15369
|
+
* two tiles for nothing. A track with neither is dropped, never drawn as an
|
|
15370
|
+
* empty cell (see `NcSummaryProducer.loadTiles`).
|
|
15371
|
+
*
|
|
15372
|
+
* ## Why `thumbnail` is back (2026-08-27)
|
|
15373
|
+
*
|
|
15374
|
+
* It was removed on 2026-08-24 (160200681) because a successful plate read
|
|
15375
|
+
* overwrites the vehicle first-level crop under the same `thumbnail` kind.
|
|
15376
|
+
* **That collision belongs to the GROUP entity**: the sealer is what writes
|
|
15377
|
+
* `ownerKind: 'group', kind: 'thumbnail'` (`group/group-sealer.ts`), and the
|
|
15378
|
+
* group process is OFF (`GROUPS_PROCESS_ENABLED`, `group/group-enabled.ts`).
|
|
15379
|
+
* On a TRACK, plates live under `ownerKind: 'plate'` / `kind: 'plateCrop'` and
|
|
15380
|
+
* never touch `thumbnail`. The same commit turned the group process off, so
|
|
15381
|
+
* `groupCrop` and `nativeFrame` became unreachable and every digest tile fell
|
|
15382
|
+
* through to `keyFrame` — a whole frame — for three days.
|
|
15383
|
+
* `scripts/check-summary-ladder-reachable.ts` is the guard that refuses that
|
|
15384
|
+
* shape now.
|
|
15385
|
+
*
|
|
15386
|
+
* Boxed frames stay absent: a drawn rectangle is a permanent lie in a digest
|
|
15387
|
+
* tile. Plate and face crops stay absent: a digest tile of a targa is the
|
|
15388
|
+
* wrong picture. Duplicate driveways from tracker ID churn are collapsed by
|
|
15389
|
+
* `frameDedupeKey`, not by hiding the whole-scene fallback.
|
|
15369
15390
|
*/
|
|
15370
15391
|
var SUMMARY_MEDIA_LADDER = [
|
|
15371
15392
|
"groupCrop",
|
|
15372
15393
|
"nativeFrame",
|
|
15394
|
+
"thumbnail",
|
|
15395
|
+
"thumbnailSmall",
|
|
15373
15396
|
"keyFrame",
|
|
15374
15397
|
"keyFrameSmall",
|
|
15375
15398
|
"fullFrame"
|
|
15376
15399
|
];
|
|
15377
15400
|
/**
|
|
15401
|
+
* The floor below which a `thumbnail` row is NOT a subject crop.
|
|
15402
|
+
*
|
|
15403
|
+
* `copyFaceCropIfTrackHasNoThumbnail` (`pipeline-analytics/copy-face-crop-preview.ts`)
|
|
15404
|
+
* copies the persisted face crop onto a track that has no preview of its own,
|
|
15405
|
+
* and it stores it under `kind: 'thumbnail'` — so an ArcFace **112×112**
|
|
15406
|
+
* alignment template lands on the ladder wearing the same kind as a real
|
|
15407
|
+
* subject crop. Measured: 3 of 52 track thumbnails (5.8%) are 3–10 KB
|
|
15408
|
+
* templates. Delivering one is a face in a digest of passages.
|
|
15409
|
+
*
|
|
15410
|
+
* 20 KiB is chosen as the smallest number that is unambiguous in BOTH
|
|
15411
|
+
* directions: it is 2× the largest template observed (10 KB) and above the
|
|
15412
|
+
* theoretical ceiling of a 112×112 JPEG at any quality, while a native-
|
|
15413
|
+
* resolution subject crop measures in the hundreds of KB. The second reason
|
|
15414
|
+
* the number is safe: a genuine crop small enough to fall under it is a
|
|
15415
|
+
* subject a few dozen pixels across, which is a smear in a mosaic cell — the
|
|
15416
|
+
* whole-scene rung below is the better picture either way.
|
|
15417
|
+
*
|
|
15418
|
+
* The check costs NOTHING: `MediaFileInfo.sizeBytes` is already on the index
|
|
15419
|
+
* row, so no blob is fetched and nothing is decoded to apply it.
|
|
15420
|
+
*/
|
|
15421
|
+
var SUMMARY_THUMBNAIL_MIN_BYTES = 20480;
|
|
15422
|
+
/** The rungs {@link SUMMARY_THUMBNAIL_MIN_BYTES} is applied to. */
|
|
15423
|
+
var SUMMARY_SIZE_GATED_RUNGS = new Set(["thumbnail"]);
|
|
15424
|
+
/**
|
|
15425
|
+
* Is this row a picture, or a template wearing a picture's kind?
|
|
15426
|
+
*
|
|
15427
|
+
* Only a size the index REPORTED can demote a row. A `null` size is unknown,
|
|
15428
|
+
* and demoting on unknown would silently delete every group-owned pointer.
|
|
15429
|
+
*/
|
|
15430
|
+
function rowClearsSizeFloor(row, rung) {
|
|
15431
|
+
if (!SUMMARY_SIZE_GATED_RUNGS.has(rung)) return true;
|
|
15432
|
+
if (typeof row.sizeBytes !== "number") return true;
|
|
15433
|
+
return row.sizeBytes >= SUMMARY_THUMBNAIL_MIN_BYTES;
|
|
15434
|
+
}
|
|
15435
|
+
/**
|
|
15378
15436
|
* Pick ONE media row for a track — the cheap question first (index rows only),
|
|
15379
15437
|
* so exactly one blob is ever read.
|
|
15380
15438
|
*
|
|
15381
15439
|
* Within a rung the row CLOSEST to the track's peak is chosen; a member may
|
|
15382
15440
|
* carry more than one full-frame still, so "the first one" would systematically
|
|
15383
15441
|
* pick the moment the subject entered rather than the moment it was best seen.
|
|
15442
|
+
*
|
|
15443
|
+
* A `thumbnail` under {@link SUMMARY_THUMBNAIL_MIN_BYTES} DESCENDS to the next
|
|
15444
|
+
* rung rather than being drawn — see that constant for the face template it
|
|
15445
|
+
* exists to catch.
|
|
15384
15446
|
*/
|
|
15385
15447
|
function pickSummaryMedia(rows, aroundMs) {
|
|
15386
15448
|
for (const rung of SUMMARY_MEDIA_LADDER) {
|
|
15387
|
-
const matches = rows.filter((row) => row.kind === rung);
|
|
15449
|
+
const matches = rows.filter((row) => row.kind === rung && rowClearsSizeFloor(row, rung));
|
|
15388
15450
|
if (matches.length === 0) continue;
|
|
15389
15451
|
return matches.reduce((best, row) => Math.abs(row.timestamp - aroundMs) < Math.abs(best.timestamp - aroundMs) ? row : best);
|
|
15390
15452
|
}
|
|
@@ -15696,12 +15758,13 @@ var NcSummaryProducer = class {
|
|
|
15696
15758
|
droppedStationary: filtered.droppedStationary
|
|
15697
15759
|
};
|
|
15698
15760
|
}
|
|
15699
|
-
const
|
|
15761
|
+
const tileLoad = await this.loadTiles({
|
|
15700
15762
|
rule,
|
|
15701
15763
|
preferred: selection.tiles,
|
|
15702
15764
|
refill: remainingSummaryCandidates(filtered.kept, selection.tiles, rule.rank),
|
|
15703
15765
|
maxTiles: rule.maxTiles
|
|
15704
15766
|
});
|
|
15767
|
+
const loaded = tileLoad.tiles;
|
|
15705
15768
|
const detections = countDetections({ tracks: selection.tiles.map((t) => ({
|
|
15706
15769
|
firstSeen: t.firstSeen,
|
|
15707
15770
|
lastSeen: t.lastSeen,
|
|
@@ -15759,7 +15822,8 @@ var NcSummaryProducer = class {
|
|
|
15759
15822
|
tiles: loaded.length,
|
|
15760
15823
|
cameras: new Set(loaded.map((t) => t.candidate.deviceId)).size,
|
|
15761
15824
|
camerasDropped: selection.camerasDropped,
|
|
15762
|
-
droppedNoMedia:
|
|
15825
|
+
droppedNoMedia: tileLoad.droppedNoMedia,
|
|
15826
|
+
droppedDuplicateFrame: tileLoad.droppedDuplicateFrame,
|
|
15763
15827
|
mosaicBytes: mosaic.bytes,
|
|
15764
15828
|
mosaicPublished: mosaic.ref !== null,
|
|
15765
15829
|
enqueued,
|
|
@@ -15825,14 +15889,20 @@ var NcSummaryProducer = class {
|
|
|
15825
15889
|
}
|
|
15826
15890
|
/** One blob per tile — the cheap question (index rows) always first.
|
|
15827
15891
|
* Duplicate camera-seconds are skipped so tracker ID churn cannot paint
|
|
15828
|
-
* the same driveway eleven times; leftover ranked candidates refill.
|
|
15892
|
+
* the same driveway eleven times; leftover ranked candidates refill.
|
|
15893
|
+
*
|
|
15894
|
+
* It COUNTS what it drops, per reason, because only this loop knows: the
|
|
15895
|
+
* caller sees tiles and has no way to tell a track that had no picture from
|
|
15896
|
+
* a rule whose quota was simply larger than the night. */
|
|
15829
15897
|
async loadTiles(input) {
|
|
15830
|
-
const
|
|
15898
|
+
const tiles = [];
|
|
15831
15899
|
const seenFrames = /* @__PURE__ */ new Set();
|
|
15832
15900
|
const seenIds = /* @__PURE__ */ new Set();
|
|
15901
|
+
let droppedNoMedia = 0;
|
|
15902
|
+
let droppedDuplicateFrame = 0;
|
|
15833
15903
|
const queue = [...input.preferred, ...input.refill];
|
|
15834
15904
|
for (const candidate of queue) {
|
|
15835
|
-
if (
|
|
15905
|
+
if (tiles.length >= input.maxTiles) break;
|
|
15836
15906
|
if (seenIds.has(candidate.trackId)) continue;
|
|
15837
15907
|
seenIds.add(candidate.trackId);
|
|
15838
15908
|
const tags = { deviceId: candidate.deviceId };
|
|
@@ -15840,19 +15910,22 @@ var NcSummaryProducer = class {
|
|
|
15840
15910
|
const rows = await this.deps.listMediaInfo(candidate.trackId);
|
|
15841
15911
|
const picked = pickSummaryMedia(rows, (candidate.firstSeen + candidate.lastSeen) / 2);
|
|
15842
15912
|
if (picked === null) {
|
|
15843
|
-
|
|
15913
|
+
droppedNoMedia += 1;
|
|
15914
|
+
this.deps.logger.info("summary tile dropped: the track carries no clean media", {
|
|
15844
15915
|
tags,
|
|
15845
15916
|
meta: {
|
|
15846
15917
|
ruleId: input.rule.id,
|
|
15847
15918
|
trackId: candidate.trackId,
|
|
15848
|
-
mediaRows: rows.length
|
|
15919
|
+
mediaRows: rows.length,
|
|
15920
|
+
mediaKinds: [...new Set(rows.map((row) => row.kind))].sort()
|
|
15849
15921
|
}
|
|
15850
15922
|
});
|
|
15851
15923
|
continue;
|
|
15852
15924
|
}
|
|
15853
15925
|
const frameKey = frameDedupeKey(candidate.deviceId, picked.timestamp);
|
|
15854
15926
|
if (seenFrames.has(frameKey)) {
|
|
15855
|
-
|
|
15927
|
+
droppedDuplicateFrame += 1;
|
|
15928
|
+
this.deps.logger.info("summary tile dropped: same frame as an earlier tile", {
|
|
15856
15929
|
tags,
|
|
15857
15930
|
meta: {
|
|
15858
15931
|
ruleId: input.rule.id,
|
|
@@ -15864,22 +15937,25 @@ var NcSummaryProducer = class {
|
|
|
15864
15937
|
}
|
|
15865
15938
|
const jpeg = await this.deps.readMedia(picked.key);
|
|
15866
15939
|
if (jpeg === null || jpeg.byteLength === 0) {
|
|
15867
|
-
|
|
15940
|
+
droppedNoMedia += 1;
|
|
15941
|
+
this.deps.logger.info("summary tile dropped: the media blob could not be read", {
|
|
15868
15942
|
tags,
|
|
15869
15943
|
meta: {
|
|
15870
15944
|
ruleId: input.rule.id,
|
|
15871
15945
|
trackId: candidate.trackId,
|
|
15872
|
-
mediaKey: picked.key
|
|
15946
|
+
mediaKey: picked.key,
|
|
15947
|
+
mediaKind: picked.kind
|
|
15873
15948
|
}
|
|
15874
15949
|
});
|
|
15875
15950
|
continue;
|
|
15876
15951
|
}
|
|
15877
15952
|
seenFrames.add(frameKey);
|
|
15878
|
-
|
|
15953
|
+
tiles.push({
|
|
15879
15954
|
candidate,
|
|
15880
15955
|
jpeg
|
|
15881
15956
|
});
|
|
15882
15957
|
} catch (err) {
|
|
15958
|
+
droppedNoMedia += 1;
|
|
15883
15959
|
this.deps.logger.warn("summary tile dropped: reading its media threw", {
|
|
15884
15960
|
tags,
|
|
15885
15961
|
meta: {
|
|
@@ -15890,7 +15966,11 @@ var NcSummaryProducer = class {
|
|
|
15890
15966
|
});
|
|
15891
15967
|
}
|
|
15892
15968
|
}
|
|
15893
|
-
return
|
|
15969
|
+
return {
|
|
15970
|
+
tiles,
|
|
15971
|
+
droppedNoMedia,
|
|
15972
|
+
droppedDuplicateFrame
|
|
15973
|
+
};
|
|
15894
15974
|
}
|
|
15895
15975
|
/**
|
|
15896
15976
|
* Render the mosaic, file it where event media lives, and mint the public
|
|
@@ -41237,12 +41317,14 @@ function digestMediaRows(input) {
|
|
|
41237
41317
|
if (input.mediaKey !== null && input.mediaKey.length > 0) owned.push({
|
|
41238
41318
|
key: input.mediaKey,
|
|
41239
41319
|
kind: "groupCrop",
|
|
41240
|
-
timestamp: input.timestamp
|
|
41320
|
+
timestamp: input.timestamp,
|
|
41321
|
+
sizeBytes: null
|
|
41241
41322
|
});
|
|
41242
41323
|
if (input.nativeKey !== null && input.nativeKey.length > 0) owned.push({
|
|
41243
41324
|
key: input.nativeKey,
|
|
41244
41325
|
kind: "nativeFrame",
|
|
41245
|
-
timestamp: input.timestamp
|
|
41326
|
+
timestamp: input.timestamp,
|
|
41327
|
+
sizeBytes: null
|
|
41246
41328
|
});
|
|
41247
41329
|
if (owned.length > 0) return owned;
|
|
41248
41330
|
return input.memberRows.filter((row) => DIGEST_MEMBER_FALLBACK_KINDS.has(row.kind));
|
|
@@ -60505,7 +60587,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60505
60587
|
const memberRows = (await Promise.all(members.map((m) => stores.mediaStore.listInfoByOwner("track", m.trackId)))).flatMap((rows) => rows.map((r) => ({
|
|
60506
60588
|
key: r.key,
|
|
60507
60589
|
kind: r.kind,
|
|
60508
|
-
timestamp: r.timestamp
|
|
60590
|
+
timestamp: r.timestamp,
|
|
60591
|
+
sizeBytes: r.sizeBytes
|
|
60509
60592
|
})));
|
|
60510
60593
|
return digestMediaRows({
|
|
60511
60594
|
mediaKey: group.mediaKey,
|
|
@@ -60517,7 +60600,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
|
|
|
60517
60600
|
return (await stores.mediaStore.listInfoByOwner("track", id)).map((r) => ({
|
|
60518
60601
|
key: r.key,
|
|
60519
60602
|
kind: r.kind,
|
|
60520
|
-
timestamp: r.timestamp
|
|
60603
|
+
timestamp: r.timestamp,
|
|
60604
|
+
sizeBytes: r.sizeBytes
|
|
60521
60605
|
}));
|
|
60522
60606
|
},
|
|
60523
60607
|
readMedia: async (key) => {
|
|
@@ -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-_ZUR9O1b.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.132",
|
|
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",
|