@camstack/addon-export-hap 1.2.15 → 1.2.16
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/export-hap.addon.js +398 -8
- package/dist/export-hap.addon.mjs +398 -9
- package/package.json +1 -1
package/dist/export-hap.addon.js
CHANGED
|
@@ -15546,6 +15546,60 @@ var TrackFlagsSchema = object({
|
|
|
15546
15546
|
* `trained` without a re-fetch. */
|
|
15547
15547
|
retrainStatus: RetrainStatusSchema
|
|
15548
15548
|
});
|
|
15549
|
+
union([literal(1), literal(2)]);
|
|
15550
|
+
/**
|
|
15551
|
+
* WHO decided a label, and when. Carried per tier so a value can be traced to
|
|
15552
|
+
* the step and model that produced it — which is what makes the write rule
|
|
15553
|
+
* arguable after the fact ("why is 592's label `dog` and not `Canis lupus`?")
|
|
15554
|
+
* and what lets a migrated, UNATTRIBUTED value be told apart from a real one.
|
|
15555
|
+
*
|
|
15556
|
+
* `stepId` is the pipeline step id (`animal-classifier`, `bird-classifier`,
|
|
15557
|
+
* `plate-ocr`, `face-embedding`, `object-detection`), or the sentinel
|
|
15558
|
+
* `migration:4g` for a value the 4g migration moved from the single-slot era —
|
|
15559
|
+
* that value has no provenance, and the write rule lets ANY properly-attributed
|
|
15560
|
+
* write of the same tier replace it regardless of score.
|
|
15561
|
+
*/
|
|
15562
|
+
var LabelAttributionSchema = object({
|
|
15563
|
+
stepId: string(),
|
|
15564
|
+
modelId: string().optional(),
|
|
15565
|
+
decidedAt: number()
|
|
15566
|
+
});
|
|
15567
|
+
/**
|
|
15568
|
+
* The TIERED label model (roadmap 4g), spread into `TrackSchema` and
|
|
15569
|
+
* `ObjectEventSchema` from ONE place so the two surfaces cannot drift — a
|
|
15570
|
+
* track and its events always answer the same question the same way.
|
|
15571
|
+
*
|
|
15572
|
+
* Two scalar columns, not an array: every consumer wants "the coarse one" or
|
|
15573
|
+
* "the fine one", and an array made both a scan. `label` is tier 1, `subLabel`
|
|
15574
|
+
* is tier 2, and each carries its own score + attribution.
|
|
15575
|
+
*
|
|
15576
|
+
* **Reading it.** What a human should be shown is `subLabel ?? label` — the
|
|
15577
|
+
* finest thing known. Before 4g the single `label` column held the finest
|
|
15578
|
+
* value, so a consumer that has not been updated reads the tier-1 slot and
|
|
15579
|
+
* shows nothing on a species-only row; that is why the migration puts every
|
|
15580
|
+
* pre-4g value in tier 2 (it cannot regress a display that reads the fallback)
|
|
15581
|
+
* and why the read surfaces were changed in the same train.
|
|
15582
|
+
*
|
|
15583
|
+
* **Writing it.** The slots are independent, which is the whole point: a
|
|
15584
|
+
* tier-1 write (`bird`) can never overwrite a tier-2 value (`Turdus
|
|
15585
|
+
* migratorius`), so fineness cannot regress by construction. Within a tier the
|
|
15586
|
+
* higher score wins. One rule, one implementation — see
|
|
15587
|
+
* `pipeline/label-tier.ts` in addon-post-analysis.
|
|
15588
|
+
*/
|
|
15589
|
+
var TieredLabelFields = {
|
|
15590
|
+
/** Tier 1 — the sub-class. See {@link LabelTierSchema}. */
|
|
15591
|
+
label: string().optional(),
|
|
15592
|
+
/** Confidence of the tier-1 value, as reported by the deciding step. */
|
|
15593
|
+
labelScore: number().optional(),
|
|
15594
|
+
/** Provenance of the tier-1 value. See {@link LabelAttributionSchema}. */
|
|
15595
|
+
labelMeta: LabelAttributionSchema.optional(),
|
|
15596
|
+
/** Tier 2 — the instance. See {@link LabelTierSchema}. */
|
|
15597
|
+
subLabel: string().optional(),
|
|
15598
|
+
/** Confidence of the tier-2 value, as reported by the deciding step. */
|
|
15599
|
+
subLabelScore: number().optional(),
|
|
15600
|
+
/** Provenance of the tier-2 value. See {@link LabelAttributionSchema}. */
|
|
15601
|
+
subLabelMeta: LabelAttributionSchema.optional()
|
|
15602
|
+
};
|
|
15549
15603
|
/** Per-camera slice of a training-export estimate. */
|
|
15550
15604
|
var TrainingExportDeviceTotalsSchema = object({
|
|
15551
15605
|
deviceId: number(),
|
|
@@ -15570,7 +15624,7 @@ var TrackSchema = object({
|
|
|
15570
15624
|
trackId: string(),
|
|
15571
15625
|
deviceId: number(),
|
|
15572
15626
|
className: string(),
|
|
15573
|
-
|
|
15627
|
+
...TieredLabelFields,
|
|
15574
15628
|
producingDeviceName: string().optional(),
|
|
15575
15629
|
/** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
|
|
15576
15630
|
source: TrackSourceSchema.optional(),
|
|
@@ -15683,7 +15737,7 @@ var ObjectEventSchema = object({
|
|
|
15683
15737
|
/** Omitted in slim projection. */
|
|
15684
15738
|
trackId: string().optional(),
|
|
15685
15739
|
className: string(),
|
|
15686
|
-
|
|
15740
|
+
...TieredLabelFields,
|
|
15687
15741
|
/** Omitted in slim projection. */
|
|
15688
15742
|
confidence: number().optional(),
|
|
15689
15743
|
/** Heavy JSON — omitted in slim projection. */
|
|
@@ -15764,6 +15818,173 @@ var MediaFileSchema = object({
|
|
|
15764
15818
|
* stored blob and a `?variant=thumb` rendering without fetching either.
|
|
15765
15819
|
*/
|
|
15766
15820
|
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
15821
|
+
/**
|
|
15822
|
+
* The MACRO tier of an annotation — a CLOSED set.
|
|
15823
|
+
*
|
|
15824
|
+
* This is what the exported detector predicts, so a typo here is a new class
|
|
15825
|
+
* with one example in it. `label` and `subLabel` are open strings by contrast:
|
|
15826
|
+
* the whole point of the page is teaching the model things it does not know
|
|
15827
|
+
* yet, and constraining that vocabulary would make it useless.
|
|
15828
|
+
*
|
|
15829
|
+
* A macro class is NEVER a label. The provider refuses a write whose `label` or
|
|
15830
|
+
* `subLabel` is one of these values, in any casing, because once `person`
|
|
15831
|
+
* exists in both tiers "every person box" stops being answerable without
|
|
15832
|
+
* knowing every string anyone ever typed — and the damage is retroactive.
|
|
15833
|
+
*/
|
|
15834
|
+
var RetrainMacroClassSchema = _enum([
|
|
15835
|
+
"person",
|
|
15836
|
+
"vehicle",
|
|
15837
|
+
"animal",
|
|
15838
|
+
"package",
|
|
15839
|
+
"face",
|
|
15840
|
+
"plate"
|
|
15841
|
+
]);
|
|
15842
|
+
/** A subject to learn, or a phantom to unlearn (taught by OMISSION). */
|
|
15843
|
+
var RetrainAnnotationKindSchema = _enum(["subject", "model_error"]);
|
|
15844
|
+
/** Did a human draw this box, or did the assist propose it? */
|
|
15845
|
+
var RetrainAnnotationSourceSchema = _enum(["operator", "assist"]);
|
|
15846
|
+
/** Normalised `[0,1]` rectangle against the FULL frame — the canonical form. */
|
|
15847
|
+
var RetrainBboxSchema = object({
|
|
15848
|
+
x: number(),
|
|
15849
|
+
y: number(),
|
|
15850
|
+
w: number(),
|
|
15851
|
+
h: number()
|
|
15852
|
+
});
|
|
15853
|
+
/**
|
|
15854
|
+
* One annotated subject.
|
|
15855
|
+
*
|
|
15856
|
+
* `bbox` is normalised against the full frame, ALWAYS. The per-model shapes
|
|
15857
|
+
* (letterboxed root / zone-cropped package / subject-cropped classifier) are
|
|
15858
|
+
* derived from it at export and never stored — storing them is how one feature
|
|
15859
|
+
* space ends up holding two crops of the same subject (D52).
|
|
15860
|
+
*/
|
|
15861
|
+
var RetrainAnnotationSchema = object({
|
|
15862
|
+
id: string(),
|
|
15863
|
+
trackId: string(),
|
|
15864
|
+
deviceId: number(),
|
|
15865
|
+
/** The COPY in retrain storage — never the source track's media key. */
|
|
15866
|
+
mediaKey: string(),
|
|
15867
|
+
bbox: RetrainBboxSchema,
|
|
15868
|
+
macroClass: RetrainMacroClassSchema,
|
|
15869
|
+
label: string().optional(),
|
|
15870
|
+
subLabel: string().optional(),
|
|
15871
|
+
kind: RetrainAnnotationKindSchema,
|
|
15872
|
+
source: RetrainAnnotationSourceSchema,
|
|
15873
|
+
/** Which model proposed this box — or, on a `model_error`, drew the phantom. */
|
|
15874
|
+
assistModelId: string().optional(),
|
|
15875
|
+
assistScore: number().optional(),
|
|
15876
|
+
exportedInBatch: string().optional(),
|
|
15877
|
+
createdAt: number()
|
|
15878
|
+
});
|
|
15879
|
+
/** The write form — the server owns `id`, `createdAt` and the frame binding. */
|
|
15880
|
+
var RetrainAnnotationDraftSchema = RetrainAnnotationSchema.omit({
|
|
15881
|
+
id: true,
|
|
15882
|
+
trackId: true,
|
|
15883
|
+
deviceId: true,
|
|
15884
|
+
mediaKey: true,
|
|
15885
|
+
createdAt: true,
|
|
15886
|
+
exportedInBatch: true
|
|
15887
|
+
});
|
|
15888
|
+
/** A track sitting in `staging`, with everything the worklist needs to rank it. */
|
|
15889
|
+
var RetrainTrackSchema = object({
|
|
15890
|
+
trackId: string(),
|
|
15891
|
+
deviceId: number(),
|
|
15892
|
+
className: string(),
|
|
15893
|
+
label: string().optional(),
|
|
15894
|
+
firstSeen: number(),
|
|
15895
|
+
lastSeen: number(),
|
|
15896
|
+
/** How many frames the dataset already holds from this track. */
|
|
15897
|
+
frameCount: number().int(),
|
|
15898
|
+
/** How many subjects have been annotated on those frames. `0` with
|
|
15899
|
+
* `frameCount: 0` is exactly "staging, still to work". */
|
|
15900
|
+
annotationCount: number().int()
|
|
15901
|
+
});
|
|
15902
|
+
/** A frame the picker may offer — an index row, no blob was read to produce it. */
|
|
15903
|
+
var RetrainFrameCandidateSchema = object({
|
|
15904
|
+
mediaKey: string(),
|
|
15905
|
+
kind: MediaFileKindEnum,
|
|
15906
|
+
timestamp: number(),
|
|
15907
|
+
sizeBytes: number().int(),
|
|
15908
|
+
/** A copy of this original already exists — selecting it is free and cannot
|
|
15909
|
+
* fail, whatever became of the original. */
|
|
15910
|
+
copied: boolean()
|
|
15911
|
+
});
|
|
15912
|
+
/** A frame the dataset OWNS: bytes copied at selection time. */
|
|
15913
|
+
var RetrainFrameSchema = object({
|
|
15914
|
+
frameId: string(),
|
|
15915
|
+
deviceId: number(),
|
|
15916
|
+
trackId: string(),
|
|
15917
|
+
/** Provenance only. It may already point at nothing — that is expected. */
|
|
15918
|
+
sourceMediaKey: string(),
|
|
15919
|
+
sourceKind: MediaFileKindEnum,
|
|
15920
|
+
sizeBytes: number().int(),
|
|
15921
|
+
width: number().int(),
|
|
15922
|
+
height: number().int(),
|
|
15923
|
+
copiedAt: number()
|
|
15924
|
+
});
|
|
15925
|
+
/** Why a copy-on-select could not be honoured — named, never a silent skip. */
|
|
15926
|
+
var RetrainCopyRefusalSchema = _enum([
|
|
15927
|
+
"source-missing",
|
|
15928
|
+
"unreadable-image",
|
|
15929
|
+
"write-failed"
|
|
15930
|
+
]);
|
|
15931
|
+
var RetrainFrameSelectionSchema = object({
|
|
15932
|
+
copied: array(RetrainFrameSchema).readonly(),
|
|
15933
|
+
refused: array(object({
|
|
15934
|
+
sourceMediaKey: string(),
|
|
15935
|
+
reason: RetrainCopyRefusalSchema
|
|
15936
|
+
})).readonly()
|
|
15937
|
+
});
|
|
15938
|
+
var RetrainFrameListSchema = object({
|
|
15939
|
+
candidates: array(RetrainFrameCandidateSchema).readonly(),
|
|
15940
|
+
copies: array(RetrainFrameSchema).readonly(),
|
|
15941
|
+
/** What the page pre-selects — the native key frame when one survives. */
|
|
15942
|
+
autoPickMediaKey: string().optional()
|
|
15943
|
+
});
|
|
15944
|
+
/** What the operator asked the assist to look for. */
|
|
15945
|
+
var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
|
|
15946
|
+
kind: literal("package"),
|
|
15947
|
+
zone: RetrainBboxSchema.optional()
|
|
15948
|
+
}), object({
|
|
15949
|
+
kind: literal("objects"),
|
|
15950
|
+
modelId: string(),
|
|
15951
|
+
minScore: number().optional()
|
|
15952
|
+
})]);
|
|
15953
|
+
/**
|
|
15954
|
+
* The assist's answer — a discriminated union, because "the model saw nothing"
|
|
15955
|
+
* and "this node cannot run that model" lead to different next moves and a
|
|
15956
|
+
* nullable result cannot tell them apart.
|
|
15957
|
+
*/
|
|
15958
|
+
var RetrainAssistResultSchema = discriminatedUnion("kind", [object({
|
|
15959
|
+
kind: literal("proposed"),
|
|
15960
|
+
modelId: string(),
|
|
15961
|
+
stepId: string(),
|
|
15962
|
+
minScore: number(),
|
|
15963
|
+
/** Drafts, ready to edit. `source: 'assist'` until the operator touches one. */
|
|
15964
|
+
proposals: array(RetrainAnnotationDraftSchema).readonly(),
|
|
15965
|
+
/** Returned by the runner but removed by the threshold. */
|
|
15966
|
+
belowThreshold: number().int()
|
|
15967
|
+
}), object({
|
|
15968
|
+
kind: literal("refused"),
|
|
15969
|
+
/** `no-zone` is ours; the rest are the runner's own refusal vocabulary. */
|
|
15970
|
+
reason: string(),
|
|
15971
|
+
detail: string().optional()
|
|
15972
|
+
})]);
|
|
15973
|
+
/** The outcome of a lifecycle move owned by the retrain page. */
|
|
15974
|
+
var RetrainTransitionResultSchema = object({
|
|
15975
|
+
trackId: string(),
|
|
15976
|
+
/** Where the track ended up, whatever happened. */
|
|
15977
|
+
retrainStatus: RetrainStatusSchema,
|
|
15978
|
+
/** `false` ⇒ the move was refused or was a no-op; `reason` says which. */
|
|
15979
|
+
changed: boolean(),
|
|
15980
|
+
reason: _enum([
|
|
15981
|
+
"unknown-track",
|
|
15982
|
+
"no-frames-copied",
|
|
15983
|
+
"not-staging",
|
|
15984
|
+
"not-trained",
|
|
15985
|
+
"unchanged"
|
|
15986
|
+
]).optional()
|
|
15987
|
+
});
|
|
15767
15988
|
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
15768
15989
|
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
15769
15990
|
var DeviceEventQueryInput = object({
|
|
@@ -15818,7 +16039,7 @@ var KeyEventSchema = object({
|
|
|
15818
16039
|
/** Track start time (firstSeen). */
|
|
15819
16040
|
timestamp: number(),
|
|
15820
16041
|
className: string(),
|
|
15821
|
-
|
|
16042
|
+
...TieredLabelFields,
|
|
15822
16043
|
importance: number(),
|
|
15823
16044
|
/** Highest-confidence ObjectEvent id for the track (empty when none). */
|
|
15824
16045
|
bestEventId: string(),
|
|
@@ -16092,6 +16313,79 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
16092
16313
|
}), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
|
|
16093
16314
|
kind: "query",
|
|
16094
16315
|
auth: "admin"
|
|
16316
|
+
}), method(object({
|
|
16317
|
+
/** Empty ⇒ every camera that has staging tracks. A LIST, not a single
|
|
16318
|
+
* `deviceId`, deliberately: `deviceId` would make this device-bound and
|
|
16319
|
+
* route it at one camera's owner, and "every camera" would stop being
|
|
16320
|
+
* expressible at all. */
|
|
16321
|
+
deviceIds: array(number()).optional(),
|
|
16322
|
+
limit: number().int().min(1).max(500).optional()
|
|
16323
|
+
}), array(RetrainTrackSchema).readonly(), {
|
|
16324
|
+
kind: "query",
|
|
16325
|
+
auth: "admin"
|
|
16326
|
+
}), method(object({ trackId: string() }), RetrainFrameListSchema, {
|
|
16327
|
+
kind: "query",
|
|
16328
|
+
auth: "admin"
|
|
16329
|
+
}), method(object({
|
|
16330
|
+
deviceId: number(),
|
|
16331
|
+
trackId: string(),
|
|
16332
|
+
mediaKeys: array(string()).min(1)
|
|
16333
|
+
}), RetrainFrameSelectionSchema, {
|
|
16334
|
+
kind: "mutation",
|
|
16335
|
+
auth: "admin"
|
|
16336
|
+
}), method(object({
|
|
16337
|
+
deviceId: number(),
|
|
16338
|
+
trackId: string(),
|
|
16339
|
+
frameId: string()
|
|
16340
|
+
}), object({
|
|
16341
|
+
removed: boolean(),
|
|
16342
|
+
removedAnnotations: number().int()
|
|
16343
|
+
}), {
|
|
16344
|
+
kind: "mutation",
|
|
16345
|
+
auth: "admin"
|
|
16346
|
+
}), method(object({ frameId: string() }), object({
|
|
16347
|
+
base64: string(),
|
|
16348
|
+
width: number().int(),
|
|
16349
|
+
height: number().int()
|
|
16350
|
+
}), {
|
|
16351
|
+
kind: "query",
|
|
16352
|
+
auth: "admin"
|
|
16353
|
+
}), method(object({
|
|
16354
|
+
deviceId: number(),
|
|
16355
|
+
trackId: string(),
|
|
16356
|
+
frameId: string(),
|
|
16357
|
+
subject: RetrainAssistSubjectSchema,
|
|
16358
|
+
/** Which node runs it. Absent ⇒ wherever an unowned call lands. */
|
|
16359
|
+
nodeId: string().optional()
|
|
16360
|
+
}), RetrainAssistResultSchema, {
|
|
16361
|
+
kind: "mutation",
|
|
16362
|
+
auth: "admin"
|
|
16363
|
+
}), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
|
|
16364
|
+
kind: "query",
|
|
16365
|
+
auth: "admin"
|
|
16366
|
+
}), method(object({
|
|
16367
|
+
deviceId: number(),
|
|
16368
|
+
trackId: string(),
|
|
16369
|
+
frameId: string(),
|
|
16370
|
+
annotations: array(RetrainAnnotationDraftSchema)
|
|
16371
|
+
}), array(RetrainAnnotationSchema).readonly(), {
|
|
16372
|
+
kind: "mutation",
|
|
16373
|
+
auth: "admin"
|
|
16374
|
+
}), method(object({
|
|
16375
|
+
deviceId: number(),
|
|
16376
|
+
trackId: string()
|
|
16377
|
+
}), RetrainTransitionResultSchema, {
|
|
16378
|
+
kind: "mutation",
|
|
16379
|
+
auth: "admin"
|
|
16380
|
+
}), method(object({
|
|
16381
|
+
deviceId: number(),
|
|
16382
|
+
trackId: string()
|
|
16383
|
+
}), RetrainTransitionResultSchema, {
|
|
16384
|
+
kind: "mutation",
|
|
16385
|
+
auth: "admin"
|
|
16386
|
+
}), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
|
|
16387
|
+
kind: "query",
|
|
16388
|
+
auth: "admin"
|
|
16095
16389
|
}), method(object({
|
|
16096
16390
|
eventId: string(),
|
|
16097
16391
|
kind: MediaFileKindEnum.optional()
|
|
@@ -16671,6 +16965,22 @@ var DetailResultSchema = object({
|
|
|
16671
16965
|
bbox: NativeCropBboxSchema.optional(),
|
|
16672
16966
|
embedding: string().optional(),
|
|
16673
16967
|
label: string().optional(),
|
|
16968
|
+
/**
|
|
16969
|
+
* The tier `label` occupies, copied VERBATIM from the producing step's
|
|
16970
|
+
* `StepDefinition.labelTier` (roadmap 4g). Present only when `label` is.
|
|
16971
|
+
*
|
|
16972
|
+
* It rides the wire rather than being resolved by the consumer because the
|
|
16973
|
+
* declaration lives with the step definition, which only the executing node
|
|
16974
|
+
* has: post-analysis holds no step registry, and re-deriving the tier from
|
|
16975
|
+
* `className` there would be exactly the inference this model exists to
|
|
16976
|
+
* forbid. A `label` that arrives WITHOUT this field is refused by the write
|
|
16977
|
+
* rule and logged (`label tier undeclared`) — an older runner therefore
|
|
16978
|
+
* stops enriching rather than guessing, which is why addon-pipeline is
|
|
16979
|
+
* deployed BEFORE addon-post-analysis.
|
|
16980
|
+
*/
|
|
16981
|
+
labelTier: union([literal(1), literal(2)]).optional(),
|
|
16982
|
+
/** Model that produced `label` — carried into the tier's attribution. */
|
|
16983
|
+
labelModelId: string().optional(),
|
|
16674
16984
|
alignedCropJpeg: string().optional(),
|
|
16675
16985
|
/** Face short side (px) measured on the NATIVE crop surface. The `bbox`
|
|
16676
16986
|
* above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
|
|
@@ -26988,6 +27298,12 @@ Object.freeze({
|
|
|
26988
27298
|
addonId: null,
|
|
26989
27299
|
access: "delete"
|
|
26990
27300
|
},
|
|
27301
|
+
"pipelineAnalytics.completeRetrainTrack": {
|
|
27302
|
+
capName: "pipeline-analytics",
|
|
27303
|
+
capScope: "device",
|
|
27304
|
+
addonId: null,
|
|
27305
|
+
access: "create"
|
|
27306
|
+
},
|
|
26991
27307
|
"pipelineAnalytics.deleteDeviceEvents": {
|
|
26992
27308
|
capName: "pipeline-analytics",
|
|
26993
27309
|
capScope: "device",
|
|
@@ -27000,6 +27316,12 @@ Object.freeze({
|
|
|
27000
27316
|
addonId: null,
|
|
27001
27317
|
access: "delete"
|
|
27002
27318
|
},
|
|
27319
|
+
"pipelineAnalytics.deselectRetrainFrame": {
|
|
27320
|
+
capName: "pipeline-analytics",
|
|
27321
|
+
capScope: "device",
|
|
27322
|
+
addonId: null,
|
|
27323
|
+
access: "create"
|
|
27324
|
+
},
|
|
27003
27325
|
"pipelineAnalytics.getActiveTracks": {
|
|
27004
27326
|
capName: "pipeline-analytics",
|
|
27005
27327
|
capScope: "device",
|
|
@@ -27060,6 +27382,18 @@ Object.freeze({
|
|
|
27060
27382
|
addonId: null,
|
|
27061
27383
|
access: "view"
|
|
27062
27384
|
},
|
|
27385
|
+
"pipelineAnalytics.getRetrainExportUrl": {
|
|
27386
|
+
capName: "pipeline-analytics",
|
|
27387
|
+
capScope: "device",
|
|
27388
|
+
addonId: null,
|
|
27389
|
+
access: "view"
|
|
27390
|
+
},
|
|
27391
|
+
"pipelineAnalytics.getRetrainFrameImage": {
|
|
27392
|
+
capName: "pipeline-analytics",
|
|
27393
|
+
capScope: "device",
|
|
27394
|
+
addonId: null,
|
|
27395
|
+
access: "view"
|
|
27396
|
+
},
|
|
27063
27397
|
"pipelineAnalytics.getSensorEvents": {
|
|
27064
27398
|
capName: "pipeline-analytics",
|
|
27065
27399
|
capScope: "device",
|
|
@@ -27114,6 +27448,24 @@ Object.freeze({
|
|
|
27114
27448
|
addonId: null,
|
|
27115
27449
|
access: "view"
|
|
27116
27450
|
},
|
|
27451
|
+
"pipelineAnalytics.listRetrainAnnotations": {
|
|
27452
|
+
capName: "pipeline-analytics",
|
|
27453
|
+
capScope: "device",
|
|
27454
|
+
addonId: null,
|
|
27455
|
+
access: "view"
|
|
27456
|
+
},
|
|
27457
|
+
"pipelineAnalytics.listRetrainFrames": {
|
|
27458
|
+
capName: "pipeline-analytics",
|
|
27459
|
+
capScope: "device",
|
|
27460
|
+
addonId: null,
|
|
27461
|
+
access: "view"
|
|
27462
|
+
},
|
|
27463
|
+
"pipelineAnalytics.listRetrainStaging": {
|
|
27464
|
+
capName: "pipeline-analytics",
|
|
27465
|
+
capScope: "device",
|
|
27466
|
+
addonId: null,
|
|
27467
|
+
access: "view"
|
|
27468
|
+
},
|
|
27117
27469
|
"pipelineAnalytics.listTrackMedia": {
|
|
27118
27470
|
capName: "pipeline-analytics",
|
|
27119
27471
|
capScope: "device",
|
|
@@ -27126,6 +27478,12 @@ Object.freeze({
|
|
|
27126
27478
|
addonId: null,
|
|
27127
27479
|
access: "view"
|
|
27128
27480
|
},
|
|
27481
|
+
"pipelineAnalytics.proposeRetrainAnnotations": {
|
|
27482
|
+
capName: "pipeline-analytics",
|
|
27483
|
+
capScope: "device",
|
|
27484
|
+
addonId: null,
|
|
27485
|
+
access: "create"
|
|
27486
|
+
},
|
|
27129
27487
|
"pipelineAnalytics.pruneEvents": {
|
|
27130
27488
|
capName: "pipeline-analytics",
|
|
27131
27489
|
capScope: "device",
|
|
@@ -27156,12 +27514,30 @@ Object.freeze({
|
|
|
27156
27514
|
addonId: null,
|
|
27157
27515
|
access: "create"
|
|
27158
27516
|
},
|
|
27517
|
+
"pipelineAnalytics.restageRetrainTrack": {
|
|
27518
|
+
capName: "pipeline-analytics",
|
|
27519
|
+
capScope: "device",
|
|
27520
|
+
addonId: null,
|
|
27521
|
+
access: "create"
|
|
27522
|
+
},
|
|
27523
|
+
"pipelineAnalytics.saveRetrainAnnotations": {
|
|
27524
|
+
capName: "pipeline-analytics",
|
|
27525
|
+
capScope: "device",
|
|
27526
|
+
addonId: null,
|
|
27527
|
+
access: "create"
|
|
27528
|
+
},
|
|
27159
27529
|
"pipelineAnalytics.searchObjectEvents": {
|
|
27160
27530
|
capName: "pipeline-analytics",
|
|
27161
27531
|
capScope: "device",
|
|
27162
27532
|
addonId: null,
|
|
27163
27533
|
access: "view"
|
|
27164
27534
|
},
|
|
27535
|
+
"pipelineAnalytics.selectRetrainFrames": {
|
|
27536
|
+
capName: "pipeline-analytics",
|
|
27537
|
+
capScope: "device",
|
|
27538
|
+
addonId: null,
|
|
27539
|
+
access: "create"
|
|
27540
|
+
},
|
|
27165
27541
|
"pipelineAnalytics.setTrackFlags": {
|
|
27166
27542
|
capName: "pipeline-analytics",
|
|
27167
27543
|
capScope: "device",
|
|
@@ -45421,8 +45797,21 @@ function syncStateToJson(map) {
|
|
|
45421
45797
|
*/
|
|
45422
45798
|
var DEFAULT_DEVICE_SETTINGS = {
|
|
45423
45799
|
streamPreference: "auto",
|
|
45424
|
-
hksvRecording:
|
|
45800
|
+
hksvRecording: true
|
|
45425
45801
|
};
|
|
45802
|
+
/**
|
|
45803
|
+
* ON unless explicitly switched off — operator decision 2026-08-08 (flipped
|
|
45804
|
+
* from the launch default of off). ABSENT must resolve to ON or the flip is a
|
|
45805
|
+
* lie for every entry persisted before the field existed, so every read goes
|
|
45806
|
+
* through this one resolver (`!== false`), never a scattered `=== true`. The
|
|
45807
|
+
* cost that made off-by-default look prudent is measured and small on the only
|
|
45808
|
+
* branch the recorder accepts (copy: 0.7 % of a core / ~30 MB RSS, D84), and a
|
|
45809
|
+
* camera the recorder cannot copy refuses recording with a logged reason
|
|
45810
|
+
* rather than paying for a transcode.
|
|
45811
|
+
*/
|
|
45812
|
+
function resolveHksvRecording(settings) {
|
|
45813
|
+
return settings?.hksvRecording !== false;
|
|
45814
|
+
}
|
|
45426
45815
|
var HAP_STREAM_PREFERENCE_OPTIONS = [
|
|
45427
45816
|
{
|
|
45428
45817
|
value: "auto",
|
|
@@ -45699,7 +46088,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
45699
46088
|
decodeMemos: this.decodeMemos,
|
|
45700
46089
|
hapDeviceSettings: {
|
|
45701
46090
|
streamPreference: entrySettings.streamPreference ?? "auto",
|
|
45702
|
-
hksvRecording: entrySettings
|
|
46091
|
+
hksvRecording: resolveHksvRecording(entrySettings)
|
|
45703
46092
|
}
|
|
45704
46093
|
}
|
|
45705
46094
|
});
|
|
@@ -46050,7 +46439,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
46050
46439
|
label: "HomeKit recording (Secure Video)",
|
|
46051
46440
|
description: "Offer “Stream and Allow Recording” in iOS Home. Requires iCloud+ and a home hub. Keeps a continuous 8s prebuffer for this camera (~0.7% of one CPU core, H.264 sources only).",
|
|
46052
46441
|
style: "switch",
|
|
46053
|
-
value: settings
|
|
46442
|
+
value: resolveHksvRecording(settings),
|
|
46054
46443
|
showWhen: {
|
|
46055
46444
|
field: enabledKey,
|
|
46056
46445
|
equals: true
|
|
@@ -46084,7 +46473,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
46084
46473
|
const enabledValue = enabledKey in patch ? Boolean(patch[enabledKey]) : wasEnabled;
|
|
46085
46474
|
const streamPreferenceRaw = streamPreferenceKey in patch ? patch[streamPreferenceKey] : current?.settings?.streamPreference;
|
|
46086
46475
|
const streamPreference = typeof streamPreferenceRaw === "string" && streamPreferenceRaw.trim().length > 0 ? streamPreferenceRaw : "auto";
|
|
46087
|
-
const hksvRecording = hksvKey in patch ? Boolean(patch[hksvKey]) : current?.settings
|
|
46476
|
+
const hksvRecording = hksvKey in patch ? Boolean(patch[hksvKey]) : resolveHksvRecording(current?.settings);
|
|
46088
46477
|
const nextSettings = {
|
|
46089
46478
|
...current?.settings ?? DEFAULT_DEVICE_SETTINGS,
|
|
46090
46479
|
streamPreference,
|
|
@@ -46100,7 +46489,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
46100
46489
|
return { success: true };
|
|
46101
46490
|
}
|
|
46102
46491
|
const currentPref = current?.settings?.streamPreference ?? "auto";
|
|
46103
|
-
const currentHksv = current?.settings
|
|
46492
|
+
const currentHksv = resolveHksvRecording(current?.settings);
|
|
46104
46493
|
await this.updateEntrySettings(deviceIdStr, nextSettings);
|
|
46105
46494
|
if (currentPref !== streamPreference || currentHksv !== hksvRecording) {
|
|
46106
46495
|
log.info("export-hap: per-camera export settings changed — refreshing accessory", { meta: {
|
|
@@ -46160,4 +46549,5 @@ exports.default = ExportHapAddon;
|
|
|
46160
46549
|
exports.deriveUsername = deriveUsername;
|
|
46161
46550
|
exports.initHapStorage = initHapStorage;
|
|
46162
46551
|
exports.publishStandalone = publishStandalone;
|
|
46552
|
+
exports.resolveHksvRecording = resolveHksvRecording;
|
|
46163
46553
|
exports.unpublishAccessory = unpublishAccessory;
|
|
@@ -15520,6 +15520,60 @@ var TrackFlagsSchema = object({
|
|
|
15520
15520
|
* `trained` without a re-fetch. */
|
|
15521
15521
|
retrainStatus: RetrainStatusSchema
|
|
15522
15522
|
});
|
|
15523
|
+
union([literal(1), literal(2)]);
|
|
15524
|
+
/**
|
|
15525
|
+
* WHO decided a label, and when. Carried per tier so a value can be traced to
|
|
15526
|
+
* the step and model that produced it — which is what makes the write rule
|
|
15527
|
+
* arguable after the fact ("why is 592's label `dog` and not `Canis lupus`?")
|
|
15528
|
+
* and what lets a migrated, UNATTRIBUTED value be told apart from a real one.
|
|
15529
|
+
*
|
|
15530
|
+
* `stepId` is the pipeline step id (`animal-classifier`, `bird-classifier`,
|
|
15531
|
+
* `plate-ocr`, `face-embedding`, `object-detection`), or the sentinel
|
|
15532
|
+
* `migration:4g` for a value the 4g migration moved from the single-slot era —
|
|
15533
|
+
* that value has no provenance, and the write rule lets ANY properly-attributed
|
|
15534
|
+
* write of the same tier replace it regardless of score.
|
|
15535
|
+
*/
|
|
15536
|
+
var LabelAttributionSchema = object({
|
|
15537
|
+
stepId: string(),
|
|
15538
|
+
modelId: string().optional(),
|
|
15539
|
+
decidedAt: number()
|
|
15540
|
+
});
|
|
15541
|
+
/**
|
|
15542
|
+
* The TIERED label model (roadmap 4g), spread into `TrackSchema` and
|
|
15543
|
+
* `ObjectEventSchema` from ONE place so the two surfaces cannot drift — a
|
|
15544
|
+
* track and its events always answer the same question the same way.
|
|
15545
|
+
*
|
|
15546
|
+
* Two scalar columns, not an array: every consumer wants "the coarse one" or
|
|
15547
|
+
* "the fine one", and an array made both a scan. `label` is tier 1, `subLabel`
|
|
15548
|
+
* is tier 2, and each carries its own score + attribution.
|
|
15549
|
+
*
|
|
15550
|
+
* **Reading it.** What a human should be shown is `subLabel ?? label` — the
|
|
15551
|
+
* finest thing known. Before 4g the single `label` column held the finest
|
|
15552
|
+
* value, so a consumer that has not been updated reads the tier-1 slot and
|
|
15553
|
+
* shows nothing on a species-only row; that is why the migration puts every
|
|
15554
|
+
* pre-4g value in tier 2 (it cannot regress a display that reads the fallback)
|
|
15555
|
+
* and why the read surfaces were changed in the same train.
|
|
15556
|
+
*
|
|
15557
|
+
* **Writing it.** The slots are independent, which is the whole point: a
|
|
15558
|
+
* tier-1 write (`bird`) can never overwrite a tier-2 value (`Turdus
|
|
15559
|
+
* migratorius`), so fineness cannot regress by construction. Within a tier the
|
|
15560
|
+
* higher score wins. One rule, one implementation — see
|
|
15561
|
+
* `pipeline/label-tier.ts` in addon-post-analysis.
|
|
15562
|
+
*/
|
|
15563
|
+
var TieredLabelFields = {
|
|
15564
|
+
/** Tier 1 — the sub-class. See {@link LabelTierSchema}. */
|
|
15565
|
+
label: string().optional(),
|
|
15566
|
+
/** Confidence of the tier-1 value, as reported by the deciding step. */
|
|
15567
|
+
labelScore: number().optional(),
|
|
15568
|
+
/** Provenance of the tier-1 value. See {@link LabelAttributionSchema}. */
|
|
15569
|
+
labelMeta: LabelAttributionSchema.optional(),
|
|
15570
|
+
/** Tier 2 — the instance. See {@link LabelTierSchema}. */
|
|
15571
|
+
subLabel: string().optional(),
|
|
15572
|
+
/** Confidence of the tier-2 value, as reported by the deciding step. */
|
|
15573
|
+
subLabelScore: number().optional(),
|
|
15574
|
+
/** Provenance of the tier-2 value. See {@link LabelAttributionSchema}. */
|
|
15575
|
+
subLabelMeta: LabelAttributionSchema.optional()
|
|
15576
|
+
};
|
|
15523
15577
|
/** Per-camera slice of a training-export estimate. */
|
|
15524
15578
|
var TrainingExportDeviceTotalsSchema = object({
|
|
15525
15579
|
deviceId: number(),
|
|
@@ -15544,7 +15598,7 @@ var TrackSchema = object({
|
|
|
15544
15598
|
trackId: string(),
|
|
15545
15599
|
deviceId: number(),
|
|
15546
15600
|
className: string(),
|
|
15547
|
-
|
|
15601
|
+
...TieredLabelFields,
|
|
15548
15602
|
producingDeviceName: string().optional(),
|
|
15549
15603
|
/** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
|
|
15550
15604
|
source: TrackSourceSchema.optional(),
|
|
@@ -15657,7 +15711,7 @@ var ObjectEventSchema = object({
|
|
|
15657
15711
|
/** Omitted in slim projection. */
|
|
15658
15712
|
trackId: string().optional(),
|
|
15659
15713
|
className: string(),
|
|
15660
|
-
|
|
15714
|
+
...TieredLabelFields,
|
|
15661
15715
|
/** Omitted in slim projection. */
|
|
15662
15716
|
confidence: number().optional(),
|
|
15663
15717
|
/** Heavy JSON — omitted in slim projection. */
|
|
@@ -15738,6 +15792,173 @@ var MediaFileSchema = object({
|
|
|
15738
15792
|
* stored blob and a `?variant=thumb` rendering without fetching either.
|
|
15739
15793
|
*/
|
|
15740
15794
|
var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
|
|
15795
|
+
/**
|
|
15796
|
+
* The MACRO tier of an annotation — a CLOSED set.
|
|
15797
|
+
*
|
|
15798
|
+
* This is what the exported detector predicts, so a typo here is a new class
|
|
15799
|
+
* with one example in it. `label` and `subLabel` are open strings by contrast:
|
|
15800
|
+
* the whole point of the page is teaching the model things it does not know
|
|
15801
|
+
* yet, and constraining that vocabulary would make it useless.
|
|
15802
|
+
*
|
|
15803
|
+
* A macro class is NEVER a label. The provider refuses a write whose `label` or
|
|
15804
|
+
* `subLabel` is one of these values, in any casing, because once `person`
|
|
15805
|
+
* exists in both tiers "every person box" stops being answerable without
|
|
15806
|
+
* knowing every string anyone ever typed — and the damage is retroactive.
|
|
15807
|
+
*/
|
|
15808
|
+
var RetrainMacroClassSchema = _enum([
|
|
15809
|
+
"person",
|
|
15810
|
+
"vehicle",
|
|
15811
|
+
"animal",
|
|
15812
|
+
"package",
|
|
15813
|
+
"face",
|
|
15814
|
+
"plate"
|
|
15815
|
+
]);
|
|
15816
|
+
/** A subject to learn, or a phantom to unlearn (taught by OMISSION). */
|
|
15817
|
+
var RetrainAnnotationKindSchema = _enum(["subject", "model_error"]);
|
|
15818
|
+
/** Did a human draw this box, or did the assist propose it? */
|
|
15819
|
+
var RetrainAnnotationSourceSchema = _enum(["operator", "assist"]);
|
|
15820
|
+
/** Normalised `[0,1]` rectangle against the FULL frame — the canonical form. */
|
|
15821
|
+
var RetrainBboxSchema = object({
|
|
15822
|
+
x: number(),
|
|
15823
|
+
y: number(),
|
|
15824
|
+
w: number(),
|
|
15825
|
+
h: number()
|
|
15826
|
+
});
|
|
15827
|
+
/**
|
|
15828
|
+
* One annotated subject.
|
|
15829
|
+
*
|
|
15830
|
+
* `bbox` is normalised against the full frame, ALWAYS. The per-model shapes
|
|
15831
|
+
* (letterboxed root / zone-cropped package / subject-cropped classifier) are
|
|
15832
|
+
* derived from it at export and never stored — storing them is how one feature
|
|
15833
|
+
* space ends up holding two crops of the same subject (D52).
|
|
15834
|
+
*/
|
|
15835
|
+
var RetrainAnnotationSchema = object({
|
|
15836
|
+
id: string(),
|
|
15837
|
+
trackId: string(),
|
|
15838
|
+
deviceId: number(),
|
|
15839
|
+
/** The COPY in retrain storage — never the source track's media key. */
|
|
15840
|
+
mediaKey: string(),
|
|
15841
|
+
bbox: RetrainBboxSchema,
|
|
15842
|
+
macroClass: RetrainMacroClassSchema,
|
|
15843
|
+
label: string().optional(),
|
|
15844
|
+
subLabel: string().optional(),
|
|
15845
|
+
kind: RetrainAnnotationKindSchema,
|
|
15846
|
+
source: RetrainAnnotationSourceSchema,
|
|
15847
|
+
/** Which model proposed this box — or, on a `model_error`, drew the phantom. */
|
|
15848
|
+
assistModelId: string().optional(),
|
|
15849
|
+
assistScore: number().optional(),
|
|
15850
|
+
exportedInBatch: string().optional(),
|
|
15851
|
+
createdAt: number()
|
|
15852
|
+
});
|
|
15853
|
+
/** The write form — the server owns `id`, `createdAt` and the frame binding. */
|
|
15854
|
+
var RetrainAnnotationDraftSchema = RetrainAnnotationSchema.omit({
|
|
15855
|
+
id: true,
|
|
15856
|
+
trackId: true,
|
|
15857
|
+
deviceId: true,
|
|
15858
|
+
mediaKey: true,
|
|
15859
|
+
createdAt: true,
|
|
15860
|
+
exportedInBatch: true
|
|
15861
|
+
});
|
|
15862
|
+
/** A track sitting in `staging`, with everything the worklist needs to rank it. */
|
|
15863
|
+
var RetrainTrackSchema = object({
|
|
15864
|
+
trackId: string(),
|
|
15865
|
+
deviceId: number(),
|
|
15866
|
+
className: string(),
|
|
15867
|
+
label: string().optional(),
|
|
15868
|
+
firstSeen: number(),
|
|
15869
|
+
lastSeen: number(),
|
|
15870
|
+
/** How many frames the dataset already holds from this track. */
|
|
15871
|
+
frameCount: number().int(),
|
|
15872
|
+
/** How many subjects have been annotated on those frames. `0` with
|
|
15873
|
+
* `frameCount: 0` is exactly "staging, still to work". */
|
|
15874
|
+
annotationCount: number().int()
|
|
15875
|
+
});
|
|
15876
|
+
/** A frame the picker may offer — an index row, no blob was read to produce it. */
|
|
15877
|
+
var RetrainFrameCandidateSchema = object({
|
|
15878
|
+
mediaKey: string(),
|
|
15879
|
+
kind: MediaFileKindEnum,
|
|
15880
|
+
timestamp: number(),
|
|
15881
|
+
sizeBytes: number().int(),
|
|
15882
|
+
/** A copy of this original already exists — selecting it is free and cannot
|
|
15883
|
+
* fail, whatever became of the original. */
|
|
15884
|
+
copied: boolean()
|
|
15885
|
+
});
|
|
15886
|
+
/** A frame the dataset OWNS: bytes copied at selection time. */
|
|
15887
|
+
var RetrainFrameSchema = object({
|
|
15888
|
+
frameId: string(),
|
|
15889
|
+
deviceId: number(),
|
|
15890
|
+
trackId: string(),
|
|
15891
|
+
/** Provenance only. It may already point at nothing — that is expected. */
|
|
15892
|
+
sourceMediaKey: string(),
|
|
15893
|
+
sourceKind: MediaFileKindEnum,
|
|
15894
|
+
sizeBytes: number().int(),
|
|
15895
|
+
width: number().int(),
|
|
15896
|
+
height: number().int(),
|
|
15897
|
+
copiedAt: number()
|
|
15898
|
+
});
|
|
15899
|
+
/** Why a copy-on-select could not be honoured — named, never a silent skip. */
|
|
15900
|
+
var RetrainCopyRefusalSchema = _enum([
|
|
15901
|
+
"source-missing",
|
|
15902
|
+
"unreadable-image",
|
|
15903
|
+
"write-failed"
|
|
15904
|
+
]);
|
|
15905
|
+
var RetrainFrameSelectionSchema = object({
|
|
15906
|
+
copied: array(RetrainFrameSchema).readonly(),
|
|
15907
|
+
refused: array(object({
|
|
15908
|
+
sourceMediaKey: string(),
|
|
15909
|
+
reason: RetrainCopyRefusalSchema
|
|
15910
|
+
})).readonly()
|
|
15911
|
+
});
|
|
15912
|
+
var RetrainFrameListSchema = object({
|
|
15913
|
+
candidates: array(RetrainFrameCandidateSchema).readonly(),
|
|
15914
|
+
copies: array(RetrainFrameSchema).readonly(),
|
|
15915
|
+
/** What the page pre-selects — the native key frame when one survives. */
|
|
15916
|
+
autoPickMediaKey: string().optional()
|
|
15917
|
+
});
|
|
15918
|
+
/** What the operator asked the assist to look for. */
|
|
15919
|
+
var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
|
|
15920
|
+
kind: literal("package"),
|
|
15921
|
+
zone: RetrainBboxSchema.optional()
|
|
15922
|
+
}), object({
|
|
15923
|
+
kind: literal("objects"),
|
|
15924
|
+
modelId: string(),
|
|
15925
|
+
minScore: number().optional()
|
|
15926
|
+
})]);
|
|
15927
|
+
/**
|
|
15928
|
+
* The assist's answer — a discriminated union, because "the model saw nothing"
|
|
15929
|
+
* and "this node cannot run that model" lead to different next moves and a
|
|
15930
|
+
* nullable result cannot tell them apart.
|
|
15931
|
+
*/
|
|
15932
|
+
var RetrainAssistResultSchema = discriminatedUnion("kind", [object({
|
|
15933
|
+
kind: literal("proposed"),
|
|
15934
|
+
modelId: string(),
|
|
15935
|
+
stepId: string(),
|
|
15936
|
+
minScore: number(),
|
|
15937
|
+
/** Drafts, ready to edit. `source: 'assist'` until the operator touches one. */
|
|
15938
|
+
proposals: array(RetrainAnnotationDraftSchema).readonly(),
|
|
15939
|
+
/** Returned by the runner but removed by the threshold. */
|
|
15940
|
+
belowThreshold: number().int()
|
|
15941
|
+
}), object({
|
|
15942
|
+
kind: literal("refused"),
|
|
15943
|
+
/** `no-zone` is ours; the rest are the runner's own refusal vocabulary. */
|
|
15944
|
+
reason: string(),
|
|
15945
|
+
detail: string().optional()
|
|
15946
|
+
})]);
|
|
15947
|
+
/** The outcome of a lifecycle move owned by the retrain page. */
|
|
15948
|
+
var RetrainTransitionResultSchema = object({
|
|
15949
|
+
trackId: string(),
|
|
15950
|
+
/** Where the track ended up, whatever happened. */
|
|
15951
|
+
retrainStatus: RetrainStatusSchema,
|
|
15952
|
+
/** `false` ⇒ the move was refused or was a no-op; `reason` says which. */
|
|
15953
|
+
changed: boolean(),
|
|
15954
|
+
reason: _enum([
|
|
15955
|
+
"unknown-track",
|
|
15956
|
+
"no-frames-copied",
|
|
15957
|
+
"not-staging",
|
|
15958
|
+
"not-trained",
|
|
15959
|
+
"unchanged"
|
|
15960
|
+
]).optional()
|
|
15961
|
+
});
|
|
15741
15962
|
var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
|
|
15742
15963
|
var MAX_EVENT_QUERY_LIMIT = 5e3;
|
|
15743
15964
|
var DeviceEventQueryInput = object({
|
|
@@ -15792,7 +16013,7 @@ var KeyEventSchema = object({
|
|
|
15792
16013
|
/** Track start time (firstSeen). */
|
|
15793
16014
|
timestamp: number(),
|
|
15794
16015
|
className: string(),
|
|
15795
|
-
|
|
16016
|
+
...TieredLabelFields,
|
|
15796
16017
|
importance: number(),
|
|
15797
16018
|
/** Highest-confidence ObjectEvent id for the track (empty when none). */
|
|
15798
16019
|
bestEventId: string(),
|
|
@@ -16066,6 +16287,79 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
|
|
|
16066
16287
|
}), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
|
|
16067
16288
|
kind: "query",
|
|
16068
16289
|
auth: "admin"
|
|
16290
|
+
}), method(object({
|
|
16291
|
+
/** Empty ⇒ every camera that has staging tracks. A LIST, not a single
|
|
16292
|
+
* `deviceId`, deliberately: `deviceId` would make this device-bound and
|
|
16293
|
+
* route it at one camera's owner, and "every camera" would stop being
|
|
16294
|
+
* expressible at all. */
|
|
16295
|
+
deviceIds: array(number()).optional(),
|
|
16296
|
+
limit: number().int().min(1).max(500).optional()
|
|
16297
|
+
}), array(RetrainTrackSchema).readonly(), {
|
|
16298
|
+
kind: "query",
|
|
16299
|
+
auth: "admin"
|
|
16300
|
+
}), method(object({ trackId: string() }), RetrainFrameListSchema, {
|
|
16301
|
+
kind: "query",
|
|
16302
|
+
auth: "admin"
|
|
16303
|
+
}), method(object({
|
|
16304
|
+
deviceId: number(),
|
|
16305
|
+
trackId: string(),
|
|
16306
|
+
mediaKeys: array(string()).min(1)
|
|
16307
|
+
}), RetrainFrameSelectionSchema, {
|
|
16308
|
+
kind: "mutation",
|
|
16309
|
+
auth: "admin"
|
|
16310
|
+
}), method(object({
|
|
16311
|
+
deviceId: number(),
|
|
16312
|
+
trackId: string(),
|
|
16313
|
+
frameId: string()
|
|
16314
|
+
}), object({
|
|
16315
|
+
removed: boolean(),
|
|
16316
|
+
removedAnnotations: number().int()
|
|
16317
|
+
}), {
|
|
16318
|
+
kind: "mutation",
|
|
16319
|
+
auth: "admin"
|
|
16320
|
+
}), method(object({ frameId: string() }), object({
|
|
16321
|
+
base64: string(),
|
|
16322
|
+
width: number().int(),
|
|
16323
|
+
height: number().int()
|
|
16324
|
+
}), {
|
|
16325
|
+
kind: "query",
|
|
16326
|
+
auth: "admin"
|
|
16327
|
+
}), method(object({
|
|
16328
|
+
deviceId: number(),
|
|
16329
|
+
trackId: string(),
|
|
16330
|
+
frameId: string(),
|
|
16331
|
+
subject: RetrainAssistSubjectSchema,
|
|
16332
|
+
/** Which node runs it. Absent ⇒ wherever an unowned call lands. */
|
|
16333
|
+
nodeId: string().optional()
|
|
16334
|
+
}), RetrainAssistResultSchema, {
|
|
16335
|
+
kind: "mutation",
|
|
16336
|
+
auth: "admin"
|
|
16337
|
+
}), method(object({ trackId: string() }), array(RetrainAnnotationSchema).readonly(), {
|
|
16338
|
+
kind: "query",
|
|
16339
|
+
auth: "admin"
|
|
16340
|
+
}), method(object({
|
|
16341
|
+
deviceId: number(),
|
|
16342
|
+
trackId: string(),
|
|
16343
|
+
frameId: string(),
|
|
16344
|
+
annotations: array(RetrainAnnotationDraftSchema)
|
|
16345
|
+
}), array(RetrainAnnotationSchema).readonly(), {
|
|
16346
|
+
kind: "mutation",
|
|
16347
|
+
auth: "admin"
|
|
16348
|
+
}), method(object({
|
|
16349
|
+
deviceId: number(),
|
|
16350
|
+
trackId: string()
|
|
16351
|
+
}), RetrainTransitionResultSchema, {
|
|
16352
|
+
kind: "mutation",
|
|
16353
|
+
auth: "admin"
|
|
16354
|
+
}), method(object({
|
|
16355
|
+
deviceId: number(),
|
|
16356
|
+
trackId: string()
|
|
16357
|
+
}), RetrainTransitionResultSchema, {
|
|
16358
|
+
kind: "mutation",
|
|
16359
|
+
auth: "admin"
|
|
16360
|
+
}), method(object({ deviceIds: array(number()).optional() }), object({ url: string() }), {
|
|
16361
|
+
kind: "query",
|
|
16362
|
+
auth: "admin"
|
|
16069
16363
|
}), method(object({
|
|
16070
16364
|
eventId: string(),
|
|
16071
16365
|
kind: MediaFileKindEnum.optional()
|
|
@@ -16645,6 +16939,22 @@ var DetailResultSchema = object({
|
|
|
16645
16939
|
bbox: NativeCropBboxSchema.optional(),
|
|
16646
16940
|
embedding: string().optional(),
|
|
16647
16941
|
label: string().optional(),
|
|
16942
|
+
/**
|
|
16943
|
+
* The tier `label` occupies, copied VERBATIM from the producing step's
|
|
16944
|
+
* `StepDefinition.labelTier` (roadmap 4g). Present only when `label` is.
|
|
16945
|
+
*
|
|
16946
|
+
* It rides the wire rather than being resolved by the consumer because the
|
|
16947
|
+
* declaration lives with the step definition, which only the executing node
|
|
16948
|
+
* has: post-analysis holds no step registry, and re-deriving the tier from
|
|
16949
|
+
* `className` there would be exactly the inference this model exists to
|
|
16950
|
+
* forbid. A `label` that arrives WITHOUT this field is refused by the write
|
|
16951
|
+
* rule and logged (`label tier undeclared`) — an older runner therefore
|
|
16952
|
+
* stops enriching rather than guessing, which is why addon-pipeline is
|
|
16953
|
+
* deployed BEFORE addon-post-analysis.
|
|
16954
|
+
*/
|
|
16955
|
+
labelTier: union([literal(1), literal(2)]).optional(),
|
|
16956
|
+
/** Model that produced `label` — carried into the tier's attribution. */
|
|
16957
|
+
labelModelId: string().optional(),
|
|
16648
16958
|
alignedCropJpeg: string().optional(),
|
|
16649
16959
|
/** Face short side (px) measured on the NATIVE crop surface. The `bbox`
|
|
16650
16960
|
* above is detection-frame px (≈6× smaller on a 4K camera) — min-face-size
|
|
@@ -26962,6 +27272,12 @@ Object.freeze({
|
|
|
26962
27272
|
addonId: null,
|
|
26963
27273
|
access: "delete"
|
|
26964
27274
|
},
|
|
27275
|
+
"pipelineAnalytics.completeRetrainTrack": {
|
|
27276
|
+
capName: "pipeline-analytics",
|
|
27277
|
+
capScope: "device",
|
|
27278
|
+
addonId: null,
|
|
27279
|
+
access: "create"
|
|
27280
|
+
},
|
|
26965
27281
|
"pipelineAnalytics.deleteDeviceEvents": {
|
|
26966
27282
|
capName: "pipeline-analytics",
|
|
26967
27283
|
capScope: "device",
|
|
@@ -26974,6 +27290,12 @@ Object.freeze({
|
|
|
26974
27290
|
addonId: null,
|
|
26975
27291
|
access: "delete"
|
|
26976
27292
|
},
|
|
27293
|
+
"pipelineAnalytics.deselectRetrainFrame": {
|
|
27294
|
+
capName: "pipeline-analytics",
|
|
27295
|
+
capScope: "device",
|
|
27296
|
+
addonId: null,
|
|
27297
|
+
access: "create"
|
|
27298
|
+
},
|
|
26977
27299
|
"pipelineAnalytics.getActiveTracks": {
|
|
26978
27300
|
capName: "pipeline-analytics",
|
|
26979
27301
|
capScope: "device",
|
|
@@ -27034,6 +27356,18 @@ Object.freeze({
|
|
|
27034
27356
|
addonId: null,
|
|
27035
27357
|
access: "view"
|
|
27036
27358
|
},
|
|
27359
|
+
"pipelineAnalytics.getRetrainExportUrl": {
|
|
27360
|
+
capName: "pipeline-analytics",
|
|
27361
|
+
capScope: "device",
|
|
27362
|
+
addonId: null,
|
|
27363
|
+
access: "view"
|
|
27364
|
+
},
|
|
27365
|
+
"pipelineAnalytics.getRetrainFrameImage": {
|
|
27366
|
+
capName: "pipeline-analytics",
|
|
27367
|
+
capScope: "device",
|
|
27368
|
+
addonId: null,
|
|
27369
|
+
access: "view"
|
|
27370
|
+
},
|
|
27037
27371
|
"pipelineAnalytics.getSensorEvents": {
|
|
27038
27372
|
capName: "pipeline-analytics",
|
|
27039
27373
|
capScope: "device",
|
|
@@ -27088,6 +27422,24 @@ Object.freeze({
|
|
|
27088
27422
|
addonId: null,
|
|
27089
27423
|
access: "view"
|
|
27090
27424
|
},
|
|
27425
|
+
"pipelineAnalytics.listRetrainAnnotations": {
|
|
27426
|
+
capName: "pipeline-analytics",
|
|
27427
|
+
capScope: "device",
|
|
27428
|
+
addonId: null,
|
|
27429
|
+
access: "view"
|
|
27430
|
+
},
|
|
27431
|
+
"pipelineAnalytics.listRetrainFrames": {
|
|
27432
|
+
capName: "pipeline-analytics",
|
|
27433
|
+
capScope: "device",
|
|
27434
|
+
addonId: null,
|
|
27435
|
+
access: "view"
|
|
27436
|
+
},
|
|
27437
|
+
"pipelineAnalytics.listRetrainStaging": {
|
|
27438
|
+
capName: "pipeline-analytics",
|
|
27439
|
+
capScope: "device",
|
|
27440
|
+
addonId: null,
|
|
27441
|
+
access: "view"
|
|
27442
|
+
},
|
|
27091
27443
|
"pipelineAnalytics.listTrackMedia": {
|
|
27092
27444
|
capName: "pipeline-analytics",
|
|
27093
27445
|
capScope: "device",
|
|
@@ -27100,6 +27452,12 @@ Object.freeze({
|
|
|
27100
27452
|
addonId: null,
|
|
27101
27453
|
access: "view"
|
|
27102
27454
|
},
|
|
27455
|
+
"pipelineAnalytics.proposeRetrainAnnotations": {
|
|
27456
|
+
capName: "pipeline-analytics",
|
|
27457
|
+
capScope: "device",
|
|
27458
|
+
addonId: null,
|
|
27459
|
+
access: "create"
|
|
27460
|
+
},
|
|
27103
27461
|
"pipelineAnalytics.pruneEvents": {
|
|
27104
27462
|
capName: "pipeline-analytics",
|
|
27105
27463
|
capScope: "device",
|
|
@@ -27130,12 +27488,30 @@ Object.freeze({
|
|
|
27130
27488
|
addonId: null,
|
|
27131
27489
|
access: "create"
|
|
27132
27490
|
},
|
|
27491
|
+
"pipelineAnalytics.restageRetrainTrack": {
|
|
27492
|
+
capName: "pipeline-analytics",
|
|
27493
|
+
capScope: "device",
|
|
27494
|
+
addonId: null,
|
|
27495
|
+
access: "create"
|
|
27496
|
+
},
|
|
27497
|
+
"pipelineAnalytics.saveRetrainAnnotations": {
|
|
27498
|
+
capName: "pipeline-analytics",
|
|
27499
|
+
capScope: "device",
|
|
27500
|
+
addonId: null,
|
|
27501
|
+
access: "create"
|
|
27502
|
+
},
|
|
27133
27503
|
"pipelineAnalytics.searchObjectEvents": {
|
|
27134
27504
|
capName: "pipeline-analytics",
|
|
27135
27505
|
capScope: "device",
|
|
27136
27506
|
addonId: null,
|
|
27137
27507
|
access: "view"
|
|
27138
27508
|
},
|
|
27509
|
+
"pipelineAnalytics.selectRetrainFrames": {
|
|
27510
|
+
capName: "pipeline-analytics",
|
|
27511
|
+
capScope: "device",
|
|
27512
|
+
addonId: null,
|
|
27513
|
+
access: "create"
|
|
27514
|
+
},
|
|
27139
27515
|
"pipelineAnalytics.setTrackFlags": {
|
|
27140
27516
|
capName: "pipeline-analytics",
|
|
27141
27517
|
capScope: "device",
|
|
@@ -45395,8 +45771,21 @@ function syncStateToJson(map) {
|
|
|
45395
45771
|
*/
|
|
45396
45772
|
var DEFAULT_DEVICE_SETTINGS = {
|
|
45397
45773
|
streamPreference: "auto",
|
|
45398
|
-
hksvRecording:
|
|
45774
|
+
hksvRecording: true
|
|
45399
45775
|
};
|
|
45776
|
+
/**
|
|
45777
|
+
* ON unless explicitly switched off — operator decision 2026-08-08 (flipped
|
|
45778
|
+
* from the launch default of off). ABSENT must resolve to ON or the flip is a
|
|
45779
|
+
* lie for every entry persisted before the field existed, so every read goes
|
|
45780
|
+
* through this one resolver (`!== false`), never a scattered `=== true`. The
|
|
45781
|
+
* cost that made off-by-default look prudent is measured and small on the only
|
|
45782
|
+
* branch the recorder accepts (copy: 0.7 % of a core / ~30 MB RSS, D84), and a
|
|
45783
|
+
* camera the recorder cannot copy refuses recording with a logged reason
|
|
45784
|
+
* rather than paying for a transcode.
|
|
45785
|
+
*/
|
|
45786
|
+
function resolveHksvRecording(settings) {
|
|
45787
|
+
return settings?.hksvRecording !== false;
|
|
45788
|
+
}
|
|
45400
45789
|
var HAP_STREAM_PREFERENCE_OPTIONS = [
|
|
45401
45790
|
{
|
|
45402
45791
|
value: "auto",
|
|
@@ -45673,7 +46062,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
45673
46062
|
decodeMemos: this.decodeMemos,
|
|
45674
46063
|
hapDeviceSettings: {
|
|
45675
46064
|
streamPreference: entrySettings.streamPreference ?? "auto",
|
|
45676
|
-
hksvRecording: entrySettings
|
|
46065
|
+
hksvRecording: resolveHksvRecording(entrySettings)
|
|
45677
46066
|
}
|
|
45678
46067
|
}
|
|
45679
46068
|
});
|
|
@@ -46024,7 +46413,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
46024
46413
|
label: "HomeKit recording (Secure Video)",
|
|
46025
46414
|
description: "Offer “Stream and Allow Recording” in iOS Home. Requires iCloud+ and a home hub. Keeps a continuous 8s prebuffer for this camera (~0.7% of one CPU core, H.264 sources only).",
|
|
46026
46415
|
style: "switch",
|
|
46027
|
-
value: settings
|
|
46416
|
+
value: resolveHksvRecording(settings),
|
|
46028
46417
|
showWhen: {
|
|
46029
46418
|
field: enabledKey,
|
|
46030
46419
|
equals: true
|
|
@@ -46058,7 +46447,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
46058
46447
|
const enabledValue = enabledKey in patch ? Boolean(patch[enabledKey]) : wasEnabled;
|
|
46059
46448
|
const streamPreferenceRaw = streamPreferenceKey in patch ? patch[streamPreferenceKey] : current?.settings?.streamPreference;
|
|
46060
46449
|
const streamPreference = typeof streamPreferenceRaw === "string" && streamPreferenceRaw.trim().length > 0 ? streamPreferenceRaw : "auto";
|
|
46061
|
-
const hksvRecording = hksvKey in patch ? Boolean(patch[hksvKey]) : current?.settings
|
|
46450
|
+
const hksvRecording = hksvKey in patch ? Boolean(patch[hksvKey]) : resolveHksvRecording(current?.settings);
|
|
46062
46451
|
const nextSettings = {
|
|
46063
46452
|
...current?.settings ?? DEFAULT_DEVICE_SETTINGS,
|
|
46064
46453
|
streamPreference,
|
|
@@ -46074,7 +46463,7 @@ var ExportHapAddon = class extends BaseAddon {
|
|
|
46074
46463
|
return { success: true };
|
|
46075
46464
|
}
|
|
46076
46465
|
const currentPref = current?.settings?.streamPreference ?? "auto";
|
|
46077
|
-
const currentHksv = current?.settings
|
|
46466
|
+
const currentHksv = resolveHksvRecording(current?.settings);
|
|
46078
46467
|
await this.updateEntrySettings(deviceIdStr, nextSettings);
|
|
46079
46468
|
if (currentPref !== streamPreference || currentHksv !== hksvRecording) {
|
|
46080
46469
|
log.info("export-hap: per-camera export settings changed — refreshing accessory", { meta: {
|
|
@@ -46129,4 +46518,4 @@ function errMsg(err) {
|
|
|
46129
46518
|
return err instanceof Error ? err.message : String(err);
|
|
46130
46519
|
}
|
|
46131
46520
|
//#endregion
|
|
46132
|
-
export { ExportHapAddon, ExportHapAddon as default, unpublishAccessory as i, initHapStorage as n, publishStandalone as r, deriveUsername as t };
|
|
46521
|
+
export { ExportHapAddon, ExportHapAddon as default, unpublishAccessory as i, initHapStorage as n, publishStandalone as r, resolveHksvRecording, deriveUsername as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/addon-export-hap",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.16",
|
|
4
4
|
"description": "HomeKit (HAP) bridge exporter for CamStack devices. Publishes a bridged accessory per exposed device — MotionSensor in this MVP; Camera/Doorbell/Switch/Light follow.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|