@camstack/addon-post-analysis 1.2.344 → 1.2.346

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.
Files changed (16) hide show
  1. package/assets/pipeline-analytics/icon.svg +7 -0
  2. package/dist/{clip-model-registry-Ca_IbZyD.js → clip-model-registry--UsI8Ud4.js} +175 -2
  3. package/dist/{clip-model-registry-C44gfwjm.mjs → clip-model-registry-BftmbgcV.mjs} +175 -2
  4. package/dist/embedding-encoder/index.js +1 -1
  5. package/dist/embedding-encoder/index.mjs +1 -1
  6. package/dist/pipeline-analytics/_stub.js +2 -2
  7. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-D73dyYqt.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CeJTtS8B.mjs} +3 -3
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-4Fu7jNn9.mjs +26 -0
  9. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-Cutp7k0_.mjs +26 -0
  10. package/dist/pipeline-analytics/{hostInit-h_8fvfFv.mjs → hostInit-DkInRTZ0.mjs} +3 -3
  11. package/dist/pipeline-analytics/index.js +851 -289
  12. package/dist/pipeline-analytics/index.mjs +851 -289
  13. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  14. package/package.json +2 -2
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C-2BbuN4.mjs +0 -26
  16. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-ZZJLA-YE.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { $ as NC_ALARM_DEFAULT_PROFILE_ID, $r as subKindsOf, $t as RECORDING_EXPORT_MAX_READ_BYTES, A as DetectionSourceSchema, Ai as boolean, An as applyBlockingEdit, At as NcConditionsSchema, B as FailureCounters, Bi as templateLiteral, Bn as consumableLowPercentOf, Br as resolveModeNotifyPolicy, Bt as NcSharedSequenceSchema, C as CoverStateSchema, Ci as isDeviceScopedCap, Cn as TrackRelationSchema, Cr as isVectorIndexDimConflict, Ct as NC_TEMPLATE_VARS, D as DETECTION_MACRO_CLASSES, Di as ZodObject, Dn as addonWidgetsSourceCapability, Dr as memoizePinnedFfmpeg, Dt as NcAlarmSensorsSchema, E as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ei as sleep$1, En as activateAlarmProfile, Er as mayWriteToLocation, Et as NcAlarmSensorsInputSchema, F as EVENT_KIND_BY_CAP, Fi as object, Fn as audioMetricsCapability, Fr as readTimelapseGeneratedAt, Ft as NcRulePatchSchema, G as MAX_ARCHIVED_DEBUG_NOTES, Gn as deriveDetailCropRect, Gr as ruleDeviceCompatCapQueries, Gt as NcSystemEventKindSchema, H as ListTrackRelationsInputSchema, Hi as union, Hn as cosineSimilarity$1, Hr as rootSupplyServes, Ht as NcSnoozeSchema, I as EVENT_OWNER_TYPES, Ii as partialRecord, In as audioModeOf, Ir as renameAlarmProfile, It as NcRuleSchema, J as MAX_TRACK_PURITIES, Jn as describeSystemEventDeliveryProblem, Jr as sceneMonitorCapability, Jt as NcTemplatePreviewSchema, K as MAX_BIRTH_DECISION_RECORDS, Kn as derivePostTriggeredAction, Kr as ruleGroupingApplies, Kt as NcTaxonomySchema, L as EXPORT_DENSE_MAX_RANGES, Li as record, Ln as buildEventKindDescriptor, Lr as resolveClipSearchMinScore, Lt as NcRuleTargetSchema, M as EMBEDDING_REQUIREMENTS_FAILED, Mi as literal, Mn as applyNonBlockingEdit, Mr as plateGalleryCapability, Mt as NcRuleActionSchema, N as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Ni as looseObject, Nn as applySecureOnArmEdit, Nr as readDeviceStateFrom, Nt as NcRuleActionsSchema, O as DETECTION_PIPELINE_CAP_NAME, Oi as _enum, On as alarmDeviceSecureOnArm, Or as notificationRulesCapability, Ot as NcAlarmSettingsPatchSchema, P as EVENT_DENSITY_BATCH_MAX, Pi as number, Pn as assertTimelapseCadences, Pr as readMotionTriggeredBy, Pt as NcRuleInputSchema, Q as MotionTriggeredBySchema, Qn as embeddingRefusalOf, Qr as storageOccupancyCapability, R as FIRST_LEVEL_MACRO_CLASSES, Ri as strictObject, Rn as buildViewerLink, Rr as resolveLocationMode, Rt as NcRuleUpdateInputSchema, S as ClearTrackRelationResultSchema, Si as hydrateSchema, Sn as TrackRelationKindSchema, Sr as isSystemEventSummaryRule, St as NC_TAXONOMY, T as DEFAULT_EVENT_COLOR, Tn as TrackSourceSchema, Tr as linkedStepOf, Tt as NcAlarmPostTriggeredActionSchema, U as ListTrackRelationsResultSchema, Ui as unknown, Un as createAlarmProfile, Ur as ruleAutoTriggersAlarm, Ut as NcSnoozeSuppressedSchema, V as LabelAttributionSchema, Vi as tuple, Vn as coreBlockIdFromAddonId, Vt as NcSnoozeInputSchema, W as LockStateSchema, Wi as EventCategory, Wn as deleteAlarmProfile, Wr as ruleBelongsToAlarm$1, Wt as NcStateSummarySectionIdSchema, X as MOTION_CLOSE_AFTER_MS, Xr as sensorRaisedOf, Xt as OPS_LOG_DEFAULT_LIMIT, Y as MAX_TRACK_RELATIONS, Yn as describeSystemEventFamilyProblem, Yr as secureActuationFor, Yt as NcTemplateVarDescriptorSchema, Z as MediaFileKindEnum, Zr as servedTemplateVars, Zt as OpsLogEntrySchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as BaseAddon, _n as TRACK_RELATIONS_DEFAULT_LIMIT, _r as isMediaOwnerType, _t as NC_GROUPING_MIN_SEC, a as CLIP_EMBEDDING_STEP_ID, ai as systemEventSummarySplitOf, an as SCENE_DEFAULT_ANCHOR_THRESHOLD, ar as faceGalleryCapability, at as NC_ALARM_TRIGGERED_MAX_SEC, b as COCO_TO_MACRO, bi as customAction, bn as TimelapseRuleSchema, br as isScheduleActive, bt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, c as resolveClusterModelPin, ci as vectorDimFromBase64, cn as SCENE_DEFAULT_QUIET_SECONDS, cr as gifFromMp4Args, ct as NC_CLIP_POST_ROLL_DEFAULT_SEC, d as APPEARANCE_BLOCKS, di as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, dn as SCENE_RESET_RECAPTURES, dr as isAudioClassName, dt as NC_CONDITION_CATALOG, ei as syncActiveAlarmProfileSettings, en as RECORDING_SOURCE_CAMSTACK_ADDON, et as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as canonicalHash, fn as SceneMonitorSchema, fr as isDefaultAlarmProfileId, ft as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, g as ArchivedDebugNoteSchema, gi as errMsg, gn as TIMELAPSE_DENSE_FLOOR_SEC, gr as isKnownTemplateVar, gt as NC_GROUPING_MAX_SEC, h as AlarmArmModeSchema, hi as DeviceType, hn as SetTrackRelationInputSchema, hr as isEventOwnerType, ht as NC_GROUPING_DEFAULT_SEC, ii as systemEventSummaryScheduleOf, in as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ir as evictionPolicyOfLocation, it as NC_ALARM_SYSTEM_EVENT_KINDS, j as EMBEDDING_MODEL_FAILED, ji as discriminatedUnion, jn as applyModeDelaysEdits, jr as pipelineAnalyticsCapability, jt as NcLegacyRuleActionSequenceSchema, k as DeclaredDevices, ki as array, kn as alarmPanelCapability, kr as occupancyScope, kt as NcConditionDescriptorSchema, l as ALL_CAPABILITY_DEFINITIONS, li as viewerLinkBaseOf, ln as SCENE_DEFAULT_UNCOVERED_POLICY, lr as isAlarmControllableCap, lt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, m as AUDIO_MACRO_LABELS, mi as isSourceCap, mn as SetTrackPurityResultSchema, mr as isEventKindLabelKey, mt as NC_DEFAULT_SNOOZE_MINUTES, ni as systemEventFamilyProblems, nn as RetrainStatusSchema, nr as ensureAlarmProfiles, nt as NC_ALARM_DELAY_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, oi as templateCarriesSummary, on as SCENE_DEFAULT_CHECK_INTERVAL_SEC, or as failureContributionCapability, ot as NC_AUDIO_CONFIRM_HITS_DEFAULT, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as evaluateSensorEdge, pn as SetTrackPurityInputSchema, pr as isDetectionMacroClass, pt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, q as MAX_BIRTH_LATENCY_PROXY_MS, qn as deriveRecordingMode, qr as ruleKindOf, qt as NcTemplatePreviewInputSchema, ri as systemEventFilterApplies, rn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, rr as eventKindLabel, rt as NC_ALARM_NOTIFY_FIELDS, s as readClusterModelPinStrict, si as templateVarsFor, sn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, sr as gifDeriveSpeed, st as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as systemEventDeliveryProblems, tn as RebuildObjectEmbeddingsRefusalSchema, tr as encodeVectorBase64, tt as NC_ALARM_DELAY_FIELDS, u as APPEARANCE_BIN_COUNT, ui as zoneAnalyticsCapability, un as SCENE_DIVERGED, ur as isAppLinkBase, ut as NC_CLIP_RETENTION_MAX_SEC, v as BaseDevice, vi as CamProfileSchema, vn as TimelapseRuleInputSchema, vr as isRetiredClearKey, vt as NC_SENSOR_TRANSITION_KINDS, w as DEFAULT_DETAIL_CROP_CONVENTION, wi as nodePin, wn as TrackSideSchema, wr as kebabToCamel, wt as NcAlarmConfigSchema, x as ClearTrackRelationInputSchema, xi as defineCustomActions, xn as TrackPuritySchema, xr as isSystemDelivery, xt as NC_TANK_SYSTEM_EVENT_KINDS, y as BirthDecisionRecordSchema, yi as createEvent, yn as TimelapseRulePatchSchema, yr as isRootSuppliableStepId, yt as NC_SNOOZE_MAX_MINUTES, z as FULL_IMAGE_BBOX, zi as string, zn as cloneAlarmSettings, zr as resolveModeDelays, zt as NcScheduleSchema } from "../clip-model-registry-C44gfwjm.mjs";
1
+ import { $ as NC_ALARM_DEFAULT_PROFILE_ID, $r as subKindsOf, $t as RECORDING_EXPORT_MAX_READ_BYTES, A as DetectionSourceSchema, Ai as boolean, An as applyBlockingEdit, At as NcConditionsSchema, B as FailureCounters, Bi as templateLiteral, Bn as consumableLowPercentOf, Br as resolveModeNotifyPolicy, Bt as NcSharedSequenceSchema, C as CoverStateSchema, Ci as isDeviceScopedCap, Cn as TrackRelationSchema, Cr as isVectorIndexDimConflict, Ct as NC_TEMPLATE_VARS, D as DETECTION_MACRO_CLASSES, Di as ZodObject, Dn as addonWidgetsSourceCapability, Dr as memoizePinnedFfmpeg, Dt as NcAlarmSensorsSchema, E as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Ei as sleep$1, En as activateAlarmProfile, Er as mayWriteToLocation, Et as NcAlarmSensorsInputSchema, F as EVENT_KIND_BY_CAP, Fi as object, Fn as audioMetricsCapability, Fr as readTimelapseGeneratedAt, Ft as NcRulePatchSchema, G as MAX_ARCHIVED_DEBUG_NOTES, Gn as deriveDetailCropRect, Gr as ruleDeviceCompatCapQueries, Gt as NcSystemEventKindSchema, H as ListTrackRelationsInputSchema, Hi as union, Hn as cosineSimilarity$1, Hr as rootSupplyServes, Ht as NcSnoozeSchema, I as EVENT_OWNER_TYPES, Ii as partialRecord, In as audioModeOf, Ir as renameAlarmProfile, It as NcRuleSchema, J as MAX_TRACK_PURITIES, Jn as describeSystemEventDeliveryProblem, Jr as sceneMonitorCapability, Jt as NcTemplatePreviewSchema, K as MAX_BIRTH_DECISION_RECORDS, Kn as derivePostTriggeredAction, Kr as ruleGroupingApplies, Kt as NcTaxonomySchema, L as EXPORT_DENSE_MAX_RANGES, Li as record, Ln as buildEventKindDescriptor, Lr as resolveClipSearchMinScore, Lt as NcRuleTargetSchema, M as EMBEDDING_REQUIREMENTS_FAILED, Mi as literal, Mn as applyNonBlockingEdit, Mr as plateGalleryCapability, Mt as NcRuleActionSchema, N as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Ni as looseObject, Nn as applySecureOnArmEdit, Nr as readDeviceStateFrom, Nt as NcRuleActionsSchema, O as DETECTION_PIPELINE_CAP_NAME, Oi as _enum, On as alarmDeviceSecureOnArm, Or as notificationRulesCapability, Ot as NcAlarmSettingsPatchSchema, P as EVENT_DENSITY_BATCH_MAX, Pi as number, Pn as assertTimelapseCadences, Pr as readMotionTriggeredBy, Pt as NcRuleInputSchema, Q as MotionTriggeredBySchema, Qn as embeddingRefusalOf, Qr as storageOccupancyCapability, R as FIRST_LEVEL_MACRO_CLASSES, Ri as strictObject, Rn as buildViewerLink, Rr as resolveLocationMode, Rt as NcRuleUpdateInputSchema, S as ClearTrackRelationResultSchema, Si as hydrateSchema, Sn as TrackRelationKindSchema, Sr as isSystemEventSummaryRule, St as NC_TAXONOMY, T as DEFAULT_EVENT_COLOR, Tn as TrackSourceSchema, Tr as linkedStepOf, Tt as NcAlarmPostTriggeredActionSchema, U as ListTrackRelationsResultSchema, Ui as unknown, Un as createAlarmProfile, Ur as ruleAutoTriggersAlarm, Ut as NcSnoozeSuppressedSchema, V as LabelAttributionSchema, Vi as tuple, Vn as coreBlockIdFromAddonId, Vt as NcSnoozeInputSchema, W as LockStateSchema, Wi as EventCategory, Wn as deleteAlarmProfile, Wr as ruleBelongsToAlarm$1, Wt as NcStateSummarySectionIdSchema, X as MOTION_CLOSE_AFTER_MS, Xr as sensorRaisedOf, Xt as OPS_LOG_DEFAULT_LIMIT, Y as MAX_TRACK_RELATIONS, Yn as describeSystemEventFamilyProblem, Yr as secureActuationFor, Yt as NcTemplateVarDescriptorSchema, Z as MediaFileKindEnum, Zr as servedTemplateVars, Zt as OpsLogEntrySchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _i as BaseAddon, _n as TRACK_RELATIONS_DEFAULT_LIMIT, _r as isMediaOwnerType, _t as NC_GROUPING_MIN_SEC, a as CLIP_EMBEDDING_STEP_ID, ai as systemEventSummarySplitOf, an as SCENE_DEFAULT_ANCHOR_THRESHOLD, ar as faceGalleryCapability, at as NC_ALARM_TRIGGERED_MAX_SEC, b as COCO_TO_MACRO, bi as customAction, bn as TimelapseRuleSchema, br as isScheduleActive, bt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, c as resolveClusterModelPin, ci as vectorDimFromBase64, cn as SCENE_DEFAULT_QUIET_SECONDS, cr as gifFromMp4Args, ct as NC_CLIP_POST_ROLL_DEFAULT_SEC, d as APPEARANCE_BLOCKS, di as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, dn as SCENE_RESET_RECAPTURES, dr as isAudioClassName, dt as NC_CONDITION_CATALOG, ei as syncActiveAlarmProfileSettings, en as RECORDING_SOURCE_CAMSTACK_ADDON, et as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fi as canonicalHash, fn as SceneMonitorSchema, fr as isDefaultAlarmProfileId, ft as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, g as ArchivedDebugNoteSchema, gi as errMsg, gn as TIMELAPSE_DENSE_FLOOR_SEC, gr as isKnownTemplateVar, gt as NC_GROUPING_MAX_SEC, h as AlarmArmModeSchema, hi as DeviceType, hn as SetTrackRelationInputSchema, hr as isEventOwnerType, ht as NC_GROUPING_DEFAULT_SEC, ii as systemEventSummaryScheduleOf, in as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, ir as evictionPolicyOfLocation, it as NC_ALARM_SYSTEM_EVENT_KINDS, j as EMBEDDING_MODEL_FAILED, ji as discriminatedUnion, jn as applyModeDelaysEdits, jr as pipelineAnalyticsCapability, jt as NcLegacyRuleActionSequenceSchema, k as DeclaredDevices, ki as array, kn as alarmPanelCapability, kr as occupancyScope, kt as NcConditionDescriptorSchema, l as ALL_CAPABILITY_DEFINITIONS, li as viewerLinkBaseOf, ln as SCENE_DEFAULT_UNCOVERED_POLICY, lr as isAlarmControllableCap, lt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, m as AUDIO_MACRO_LABELS, mi as isSourceCap, mn as SetTrackPurityResultSchema, mr as isEventKindLabelKey, mt as NC_DEFAULT_SNOOZE_MINUTES, ni as systemEventFamilyProblems, nn as RetrainStatusSchema, nr as ensureAlarmProfiles, nt as NC_ALARM_DELAY_MAX_SEC, o as FACE_EMBEDDING_STEP_ID, oi as templateCarriesSummary, on as SCENE_DEFAULT_CHECK_INTERVAL_SEC, or as failureContributionCapability, ot as NC_AUDIO_CONFIRM_HITS_DEFAULT, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pi as evaluateSensorEdge, pn as SetTrackPurityInputSchema, pr as isDetectionMacroClass, pt as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, q as MAX_BIRTH_LATENCY_PROXY_MS, qn as deriveRecordingMode, qr as ruleKindOf, qt as NcTemplatePreviewInputSchema, ri as systemEventFilterApplies, rn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, rr as eventKindLabel, rt as NC_ALARM_NOTIFY_FIELDS, s as readClusterModelPinStrict, si as templateVarsFor, sn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, sr as gifDeriveSpeed, st as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, t as BUILTIN_CLIP_MODELS, ti as systemEventDeliveryProblems, tn as RebuildObjectEmbeddingsRefusalSchema, tr as encodeVectorBase64, tt as NC_ALARM_DELAY_FIELDS, u as APPEARANCE_BIN_COUNT, ui as zoneAnalyticsCapability, un as SCENE_DIVERGED, ur as isAppLinkBase, ut as NC_CLIP_RETENTION_MAX_SEC, v as BaseDevice, vi as CamProfileSchema, vn as TimelapseRuleInputSchema, vr as isRetiredClearKey, vt as NC_SENSOR_TRANSITION_KINDS, w as DEFAULT_DETAIL_CROP_CONVENTION, wi as nodePin, wn as TrackSideSchema, wr as kebabToCamel, wt as NcAlarmConfigSchema, x as ClearTrackRelationInputSchema, xi as defineCustomActions, xn as TrackPuritySchema, xr as isSystemDelivery, xt as NC_TANK_SYSTEM_EVENT_KINDS, y as BirthDecisionRecordSchema, yi as createEvent, yn as TimelapseRulePatchSchema, yr as isRootSuppliableStepId, yt as NC_SNOOZE_MAX_MINUTES, z as FULL_IMAGE_BBOX, zi as string, zn as cloneAlarmSettings, zr as resolveModeDelays, zt as NcScheduleSchema } from "../clip-model-registry-BftmbgcV.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs$1 from "node:fs";
4
4
  import { createWriteStream, promises } from "node:fs";
@@ -12914,7 +12914,7 @@ function stringField(data, key) {
12914
12914
  const value = data[key];
12915
12915
  return typeof value === "string" && value.length > 0 ? value : void 0;
12916
12916
  }
12917
- function stringList(data, key) {
12917
+ function stringList$1(data, key) {
12918
12918
  const raw = data[key];
12919
12919
  if (!Array.isArray(raw)) return [];
12920
12920
  const ids = [];
@@ -13726,7 +13726,7 @@ function incomingFromExportCompleted(facts, data) {
13726
13726
  const deviceId = numberField(data, "deviceId");
13727
13727
  const title = stringField(data, "title");
13728
13728
  const bytes = numberField(data, "fileBytes");
13729
- const notifyTargetIds = stringList(data, "notifyTargetIds");
13729
+ const notifyTargetIds = stringList$1(data, "notifyTargetIds");
13730
13730
  const incoming = systemSubject({
13731
13731
  facts,
13732
13732
  kind: "export-completed",
@@ -74924,7 +74924,7 @@ function describeTapeStatus(input) {
74924
74924
  * `viewer-settings-legacy-adoption.ts`.
74925
74925
  */
74926
74926
  var VIEWER_SETTINGS_SNAPSHOTS_COLLECTION = "pipeline-analytics:viewer-settings-snapshots";
74927
- var COLUMNS = [
74927
+ var COLUMNS$1 = [
74928
74928
  {
74929
74929
  name: "id",
74930
74930
  type: "TEXT",
@@ -74994,7 +74994,7 @@ function requireCaller(caller) {
74994
74994
  if (!caller) throw new Error("viewerSettings: authenticated caller required");
74995
74995
  return caller;
74996
74996
  }
74997
- function asRecord(data) {
74997
+ function asRecord$1(data) {
74998
74998
  return typeof data === "object" && data !== null ? data : {};
74999
74999
  }
75000
75000
  function toSummary(id, data, callerUserId) {
@@ -75020,7 +75020,7 @@ var ViewerSettingsSnapshotStore = class {
75020
75020
  static async declare(store) {
75021
75021
  await store.declareCollection.mutate({
75022
75022
  collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
75023
- columns: [...COLUMNS],
75023
+ columns: [...COLUMNS$1],
75024
75024
  indexes: [{
75025
75025
  name: "idx_pa_viewer_settings_owner",
75026
75026
  columns: ["ownerUserId"]
@@ -75040,7 +75040,7 @@ var ViewerSettingsSnapshotStore = class {
75040
75040
  });
75041
75041
  const out = [];
75042
75042
  for (const row of rows) {
75043
- const summary = toSummary(row.id, asRecord(row.data), caller.userId);
75043
+ const summary = toSummary(row.id, asRecord$1(row.data), caller.userId);
75044
75044
  if (summary) out.push(summary);
75045
75045
  }
75046
75046
  return out;
@@ -75054,7 +75054,7 @@ var ViewerSettingsSnapshotStore = class {
75054
75054
  }
75055
75055
  }))[0];
75056
75056
  if (!row) return null;
75057
- const data = asRecord(row.data);
75057
+ const data = asRecord$1(row.data);
75058
75058
  const summary = toSummary(row.id, data, caller.userId);
75059
75059
  if (!summary) return null;
75060
75060
  const blob = BlobSchema.safeParse(data.blob);
@@ -75077,7 +75077,7 @@ var ViewerSettingsSnapshotStore = class {
75077
75077
  }
75078
75078
  }))[0];
75079
75079
  const id = prior?.id ?? randomUUID();
75080
- const createdAt = typeof asRecord(prior?.data).createdAt === "number" ? asRecord(prior?.data).createdAt : now;
75080
+ const createdAt = typeof asRecord$1(prior?.data).createdAt === "number" ? asRecord$1(prior?.data).createdAt : now;
75081
75081
  await this.store.set.mutate({
75082
75082
  collection: VIEWER_SETTINGS_SNAPSHOTS_COLLECTION,
75083
75083
  key: id,
@@ -82925,7 +82925,10 @@ var RetrainCollectionStore = class {
82925
82925
  deleted: false,
82926
82926
  memberFrameIds: []
82927
82927
  };
82928
- const members = await this.membershipRows({ collectionId: id });
82928
+ const members = await this.store.query.query({
82929
+ collection: RETRAIN_COLLECTION_FRAMES_COLLECTION,
82930
+ filter: { where: { collectionId: id } }
82931
+ });
82929
82932
  for (const m of members) await this.store.delete.mutate({
82930
82933
  collection: RETRAIN_COLLECTION_FRAMES_COLLECTION,
82931
82934
  key: m.id
@@ -83280,6 +83283,787 @@ var RetrainCollectionService = class {
83280
83283
  }
83281
83284
  };
83282
83285
  //#endregion
83286
+ //#region src/pipeline-analytics/retrain/retrain-dataset.ts
83287
+ /**
83288
+ * Dataset versions — the counts a version is judged by, and the hash that
83289
+ * freezes what it teaches. Pure.
83290
+ *
83291
+ * Both read the export MANIFEST (`retrain-export.ts`), not the stores: a
83292
+ * version is exactly what an export of its frames would carry, so its counts
83293
+ * and its hash cannot disagree with the archive a trainer receives.
83294
+ */
83295
+ function lightingOf(frame) {
83296
+ if (frame.lighting === null) return "unknown";
83297
+ return frame.lighting.night ? "night" : "day";
83298
+ }
83299
+ /** The counts of a dataset, from the manifest an export of it would carry. */
83300
+ function datasetStats(manifest) {
83301
+ const cells = /* @__PURE__ */ new Map();
83302
+ const cameras = /* @__PURE__ */ new Map();
83303
+ let completeFrames = 0;
83304
+ let subjects = 0;
83305
+ let modelErrors = 0;
83306
+ let hardNegatives = 0;
83307
+ for (const frame of manifest.frames) {
83308
+ const camera = cameras.get(frame.deviceId) ?? {
83309
+ frames: 0,
83310
+ completeFrames: 0
83311
+ };
83312
+ cameras.set(frame.deviceId, {
83313
+ frames: camera.frames + 1,
83314
+ completeFrames: camera.completeFrames + (frame.complete ? 1 : 0)
83315
+ });
83316
+ if (frame.complete) completeFrames += 1;
83317
+ const lighting = lightingOf(frame);
83318
+ let frameSubjects = 0;
83319
+ for (const a of frame.annotations) {
83320
+ if (a.kind === "model_error") {
83321
+ modelErrors += 1;
83322
+ continue;
83323
+ }
83324
+ frameSubjects += 1;
83325
+ const key = `${a.macroClass}|${String(frame.deviceId)}|${lighting}`;
83326
+ const entry = cells.get(key) ?? {
83327
+ cell: {
83328
+ macroClass: a.macroClass,
83329
+ deviceId: frame.deviceId,
83330
+ lighting,
83331
+ subjects: 0,
83332
+ frames: 0
83333
+ },
83334
+ frameIds: /* @__PURE__ */ new Set()
83335
+ };
83336
+ entry.frameIds.add(frame.frameId);
83337
+ cells.set(key, {
83338
+ cell: {
83339
+ ...entry.cell,
83340
+ subjects: entry.cell.subjects + 1,
83341
+ frames: entry.frameIds.size
83342
+ },
83343
+ frameIds: entry.frameIds
83344
+ });
83345
+ }
83346
+ subjects += frameSubjects;
83347
+ if (frame.complete && frameSubjects === 0) hardNegatives += 1;
83348
+ }
83349
+ const frames = manifest.frames.length;
83350
+ return {
83351
+ frames,
83352
+ completeFrames,
83353
+ completeness: frames > 0 ? completeFrames / frames : 0,
83354
+ subjects,
83355
+ modelErrors,
83356
+ hardNegatives,
83357
+ cells: [...cells.values()].map((e) => e.cell).toSorted((a, b) => a.macroClass.localeCompare(b.macroClass) || a.deviceId - b.deviceId || a.lighting.localeCompare(b.lighting)),
83358
+ cameras: [...cameras.entries()].map(([deviceId, c]) => ({
83359
+ deviceId,
83360
+ ...c
83361
+ })).toSorted((a, b) => a.deviceId - b.deviceId)
83362
+ };
83363
+ }
83364
+ /** A coordinate at the precision a box is meaningful to (sub-pixel noise off). */
83365
+ var q = (n) => Math.round(n * 1e4) / 1e4;
83366
+ /**
83367
+ * The sha256 of what the frames TEACH: per frame its completeness and, per
83368
+ * box, the class tiers, the kind and the box. Row ids, timestamps, export
83369
+ * stamps and provenance are left out — they change on a re-save that teaches
83370
+ * the same thing. Order-independent.
83371
+ */
83372
+ function datasetAnnotationHash(frames) {
83373
+ const canonical = frames.map((f) => ({
83374
+ f: f.frameId,
83375
+ c: f.complete,
83376
+ a: f.annotations.map((a) => [
83377
+ a.macroClass,
83378
+ a.label ?? "",
83379
+ a.subLabel ?? "",
83380
+ a.kind,
83381
+ q(a.bbox.x),
83382
+ q(a.bbox.y),
83383
+ q(a.bbox.w),
83384
+ q(a.bbox.h)
83385
+ ].join("|")).toSorted()
83386
+ })).toSorted((a, b) => a.f < b.f ? -1 : a.f > b.f ? 1 : 0);
83387
+ return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
83388
+ }
83389
+ /**
83390
+ * The manifest of a version's export, saying which version and whether its
83391
+ * frames still teach what was frozen. Frames of the version missing from the
83392
+ * archive count as empty and incomplete, so their absence changes the hash.
83393
+ */
83394
+ function withDatasetVersion(manifest, version) {
83395
+ const present = new Map(manifest.frames.map((f) => [f.frameId, f]));
83396
+ const annotationHashNow = datasetAnnotationHash(version.frameIds.map((frameId) => present.get(frameId) ?? {
83397
+ frameId,
83398
+ complete: false,
83399
+ annotations: []
83400
+ }));
83401
+ return {
83402
+ ...manifest,
83403
+ datasetVersion: {
83404
+ id: version.id,
83405
+ name: version.name,
83406
+ createdAt: version.createdAt,
83407
+ annotationHash: version.annotationHash,
83408
+ annotationHashNow,
83409
+ matches: annotationHashNow === version.annotationHash,
83410
+ missingFrames: version.frameIds.filter((id) => !present.has(id)).length
83411
+ }
83412
+ };
83413
+ }
83414
+ //#endregion
83415
+ //#region src/pipeline-analytics/retrain/retrain-geometry.ts
83416
+ function clamp01(v) {
83417
+ return v < 0 ? 0 : v > 1 ? 1 : v;
83418
+ }
83419
+ /** Is this a usable rectangle at all? A zero-area box is not a subject. */
83420
+ function isUsableRect(r) {
83421
+ return Number.isFinite(r.x) && Number.isFinite(r.y) && Number.isFinite(r.w) && Number.isFinite(r.h) && r.w > 0 && r.h > 0;
83422
+ }
83423
+ /**
83424
+ * The subject inside a square LETTERBOX of the full frame.
83425
+ *
83426
+ * The frame is scaled to fit the square and centred; the padding bars take the
83427
+ * rest. A wide 16:9 frame therefore occupies the middle 9/16 of the square's
83428
+ * height, and every y coordinate shifts by half the leftover.
83429
+ *
83430
+ * Square-side agnostic on purpose: the answer is normalised against the square,
83431
+ * so it is the same number for a 640 and a 1280 input and no caller has to know
83432
+ * which the model uses.
83433
+ */
83434
+ function deriveLetterboxRect(bbox, frameWidth, frameHeight) {
83435
+ if (!isUsableRect(bbox)) return null;
83436
+ if (!(frameWidth > 0) || !(frameHeight > 0)) return null;
83437
+ const longSide = Math.max(frameWidth, frameHeight);
83438
+ const scaleX = frameWidth / longSide;
83439
+ const scaleY = frameHeight / longSide;
83440
+ const offsetX = (1 - scaleX) / 2;
83441
+ const offsetY = (1 - scaleY) / 2;
83442
+ return {
83443
+ bbox: {
83444
+ x: offsetX + bbox.x * scaleX,
83445
+ y: offsetY + bbox.y * scaleY,
83446
+ w: bbox.w * scaleX,
83447
+ h: bbox.h * scaleY
83448
+ },
83449
+ visibleFraction: 1
83450
+ };
83451
+ }
83452
+ /**
83453
+ * The subject inside a ZONE CROP — the shape a package model is trained on.
83454
+ *
83455
+ * Returns `null` when the subject does not intersect the zone at all. That is
83456
+ * the whole reason the package proposal runs on the crop rather than the frame:
83457
+ * a package detector at 0.35 on a full frame proposes furniture, and a subject
83458
+ * outside the zone is not part of this model's world.
83459
+ */
83460
+ function deriveZoneCropRect(bbox, zone) {
83461
+ if (!isUsableRect(bbox) || !isUsableRect(zone)) return null;
83462
+ const left = Math.max(bbox.x, zone.x);
83463
+ const top = Math.max(bbox.y, zone.y);
83464
+ const right = Math.min(bbox.x + bbox.w, zone.x + zone.w);
83465
+ const bottom = Math.min(bbox.y + bbox.h, zone.y + zone.h);
83466
+ if (!(right > left) || !(bottom > top)) return null;
83467
+ const visibleFraction = (right - left) * (bottom - top) / (bbox.w * bbox.h);
83468
+ return {
83469
+ bbox: {
83470
+ x: (left - zone.x) / zone.w,
83471
+ y: (top - zone.y) / zone.h,
83472
+ w: (right - left) / zone.w,
83473
+ h: (bottom - top) / zone.h
83474
+ },
83475
+ visibleFraction
83476
+ };
83477
+ }
83478
+ /**
83479
+ * The subject inside its OWN padded crop — the shape a classifier is trained on.
83480
+ *
83481
+ * The crop is the box grown by `padding` of its own size on each side and then
83482
+ * clipped to the frame, so the subject sits in the middle and the classifier
83483
+ * still sees a little context. The returned rectangle is where the subject
83484
+ * lands inside that crop, which is NOT `[0,0,1,1]` whenever the padding was
83485
+ * clipped by a frame edge — a subject at the left border is flush left in its
83486
+ * crop, and a caller that assumed the centre would draw the box in the wrong
83487
+ * place.
83488
+ */
83489
+ function deriveSubjectCropRect(bbox, padding) {
83490
+ if (!isUsableRect(bbox)) return null;
83491
+ const pad = Number.isFinite(padding) && padding > 0 ? padding : 0;
83492
+ const cropX = clamp01(bbox.x - bbox.w * pad);
83493
+ const cropY = clamp01(bbox.y - bbox.h * pad);
83494
+ const cropRight = clamp01(bbox.x + bbox.w * (1 + pad));
83495
+ const cropBottom = clamp01(bbox.y + bbox.h * (1 + pad));
83496
+ const cropW = cropRight - cropX;
83497
+ const cropH = cropBottom - cropY;
83498
+ if (!(cropW > 0) || !(cropH > 0)) return null;
83499
+ return {
83500
+ bbox: {
83501
+ x: (bbox.x - cropX) / cropW,
83502
+ y: (bbox.y - cropY) / cropH,
83503
+ w: bbox.w / cropW,
83504
+ h: bbox.h / cropH
83505
+ },
83506
+ visibleFraction: 1
83507
+ };
83508
+ }
83509
+ //#endregion
83510
+ //#region src/pipeline-analytics/retrain/retrain-lighting.ts
83511
+ function isNight(condition) {
83512
+ return condition === "night" || condition === "ir";
83513
+ }
83514
+ function retrainFrameLighting(input) {
83515
+ if (input.capturedAt === void 0) {
83516
+ const measured = resolveConditionDetailed({
83517
+ ...input.chromaSpread !== void 0 ? { chromaSpread: input.chromaSpread } : {},
83518
+ now: /* @__PURE__ */ new Date(0)
83519
+ });
83520
+ return measured.source === "ir-chroma" ? {
83521
+ condition: measured.condition,
83522
+ night: true,
83523
+ source: measured.source
83524
+ } : null;
83525
+ }
83526
+ const resolved = resolveConditionDetailed({
83527
+ ...input.chromaSpread !== void 0 ? { chromaSpread: input.chromaSpread } : {},
83528
+ ...input.coordinates !== null ? { coordinates: input.coordinates } : {},
83529
+ now: new Date(input.capturedAt)
83530
+ });
83531
+ return {
83532
+ ...resolved,
83533
+ night: isNight(resolved.condition)
83534
+ };
83535
+ }
83536
+ //#endregion
83537
+ //#region src/pipeline-analytics/retrain/retrain-export.ts
83538
+ /**
83539
+ * The ANNOTATED dataset export — the retrain page's own archive.
83540
+ *
83541
+ * Sibling of `training-export/`, deliberately not the same thing. That one
83542
+ * streams a marked track's stored media verbatim and knows nothing about what
83543
+ * is in it; this one streams the frames the operator SELECTED (already copied
83544
+ * into retrain storage) plus the ground truth they drew.
83545
+ *
83546
+ * ## The one interesting decision
83547
+ *
83548
+ * `annotations.json` carries, for every subject, the canonical full-frame box
83549
+ * AND the geometry derived for each model shape. **Derived here, at export,
83550
+ * never stored** — one box in, three shapes out. Storing the derived shapes is
83551
+ * how two crops of the same subject end up in one feature space, which is the
83552
+ * failure already recorded for CLIP (D52). A consumer that wants a different
83553
+ * input size or a different padding re-derives from the canonical box; nothing
83554
+ * in the database has to change and nothing can disagree.
83555
+ *
83556
+ * Everything else follows the export next door: index reads to plan, one JPEG
83557
+ * in memory at a time, a yield between files, and NO inference — a training
83558
+ * set's pixels are the pixels the pipeline saw.
83559
+ */
83560
+ var RETRAIN_EXPORT_PREFIX = "retrain-export";
83561
+ /**
83562
+ * Padding a subject-crop classifier is trained with: the RUNTIME detail-crop
83563
+ * padding, read from the one convention `deriveDetailCropRect` cuts with
83564
+ * (`types/src/pipeline/detail-crop.ts`), never a second literal. It was `0.2`
83565
+ * here while the runtime fed `0.15`, so a classifier trained from this export
83566
+ * learned on more context than it is ever given live.
83567
+ */
83568
+ var CLASSIFIER_CROP_PADDING = DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
83569
+ /** The convention a `crops` layout cuts with — the runtime default, recorded
83570
+ * in the manifest so a consumer never has to assume it. */
83571
+ var RETRAIN_CROP_CONVENTION = DEFAULT_DETAIL_CROP_CONVENTION;
83572
+ /** Longest side of a `crops`-layout JPEG. Classifier inputs are 224–260 px;
83573
+ * the trainer resizes per model, so this only bounds the upload. */
83574
+ var RETRAIN_CROP_MAX_SIDE = 256;
83575
+ /**
83576
+ * Manifest schema id. v2 = capturedAt / complete / lighting / layout.
83577
+ * v3 = `origin` (`track` | `recording`), `epochMs` on a recording still,
83578
+ * `collectionIds`, and `trackId` ABSENT on a recording still, which has none.
83579
+ */
83580
+ var RETRAIN_MANIFEST_SCHEMA = "camstack.retrain-export/v3";
83581
+ /** Archive path of a frame: `frames/<deviceId>/<trackId>/<frameId leaf>.jpg`,
83582
+ * or `frames/<deviceId>/recording/<leaf>.jpg` for a recording still. */
83583
+ function retrainFramePath(frame) {
83584
+ const leaf = frame.frameId.slice(frame.frameId.lastIndexOf(":") + 1);
83585
+ const dir = frame.origin === "recording" ? RECORDING_STILL_DIR : frame.trackId;
83586
+ return `frames/${frame.deviceId}/${dir}/${leaf}.jpg`;
83587
+ }
83588
+ /** Archive path of a subject crop: `crops/<macroClass>/<annotationId>.jpg`. */
83589
+ function retrainCropPath(macroClass, annotationId) {
83590
+ return `crops/${macroClass}/${annotationId}.jpg`;
83591
+ }
83592
+ /**
83593
+ * Narrow the planned frames to a filter. Pure. Every field narrows; an empty
83594
+ * filter keeps everything. Order is the input's.
83595
+ */
83596
+ function filterRetrainExport(frames, annotationsByFrame, filter, collectionsByFrame = /* @__PURE__ */ new Map()) {
83597
+ const wanted = filter.frameIds !== void 0 ? new Set(filter.frameIds) : null;
83598
+ const collections = filter.collectionIds !== void 0 && filter.collectionIds.length > 0 ? new Set(filter.collectionIds) : null;
83599
+ const devices = filter.deviceIds !== void 0 && filter.deviceIds.length > 0 ? new Set(filter.deviceIds) : null;
83600
+ return frames.filter((frame) => {
83601
+ if (wanted !== null && !wanted.has(frame.frameId)) return false;
83602
+ if (devices !== null && !devices.has(frame.deviceId)) return false;
83603
+ if (filter.completeOnly === true && !frame.complete) return false;
83604
+ if (collections !== null && !(collectionsByFrame.get(frame.frameId) ?? []).some((id) => collections.has(id))) return false;
83605
+ if (filter.batchId !== void 0) {
83606
+ if (!(annotationsByFrame.get(frame.frameId) ?? []).some((a) => a.exportedInBatch === filter.batchId)) return false;
83607
+ }
83608
+ return true;
83609
+ });
83610
+ }
83611
+ /**
83612
+ * Derive every per-model shape for one annotation.
83613
+ *
83614
+ * `zone` is optional and per-frame: only a package annotation has one, and a
83615
+ * `zoneCrop` for a subject the zone does not contain is ABSENT rather than
83616
+ * clamped — a clamped box teaches the model that the subject ends where the
83617
+ * crop ends.
83618
+ */
83619
+ function deriveAll(annotation, frame, zone) {
83620
+ const letterbox = deriveLetterboxRect(annotation.bbox, frame.width, frame.height);
83621
+ const subjectCrop = deriveSubjectCropRect(annotation.bbox, CLASSIFIER_CROP_PADDING);
83622
+ const zoneCrop = zone === null ? null : deriveZoneCropRect(annotation.bbox, zone);
83623
+ return {
83624
+ ...letterbox !== null ? { letterbox } : {},
83625
+ ...zoneCrop !== null ? { zoneCrop } : {},
83626
+ ...subjectCrop !== null ? { subjectCrop } : {}
83627
+ };
83628
+ }
83629
+ /** Build the manifest. Pure — index rows in, JSON out, no blob touched. */
83630
+ function buildRetrainManifest(input) {
83631
+ const layout = input.layout ?? "frames";
83632
+ const coordinates = input.coordinates ?? null;
83633
+ let annotationCount = 0;
83634
+ let modelErrorCount = 0;
83635
+ const frames = input.frames.map((frame) => {
83636
+ const zone = input.zoneForDevice(frame.deviceId);
83637
+ const annotations = (input.annotationsByFrame.get(frame.frameId) ?? []).map((a) => {
83638
+ annotationCount += 1;
83639
+ if (a.kind === "model_error") modelErrorCount += 1;
83640
+ const derived = deriveAll(a, frame, zone);
83641
+ return {
83642
+ id: a.id,
83643
+ macroClass: a.macroClass,
83644
+ ...a.label !== void 0 ? { label: a.label } : {},
83645
+ ...a.subLabel !== void 0 ? { subLabel: a.subLabel } : {},
83646
+ kind: a.kind,
83647
+ source: a.source,
83648
+ ...a.assistModelId !== void 0 ? { assistModelId: a.assistModelId } : {},
83649
+ ...a.assistScore !== void 0 ? { assistScore: a.assistScore } : {},
83650
+ bbox: a.bbox,
83651
+ ...layout === "crops" && derived.subjectCrop !== void 0 ? { cropPath: retrainCropPath(a.macroClass, a.id) } : {},
83652
+ derived
83653
+ };
83654
+ });
83655
+ return {
83656
+ frameId: frame.frameId,
83657
+ path: retrainFramePath(frame),
83658
+ deviceId: frame.deviceId,
83659
+ origin: frame.origin,
83660
+ ...frame.origin === "track" ? { trackId: frame.trackId } : {},
83661
+ ...frame.epochMs !== void 0 ? { epochMs: frame.epochMs } : {},
83662
+ collectionIds: input.collectionsByFrame?.get(frame.frameId) ?? [],
83663
+ width: frame.width,
83664
+ height: frame.height,
83665
+ sizeBytes: frame.sizeBytes,
83666
+ sourceKind: frame.sourceKind,
83667
+ copiedAt: frame.copiedAt,
83668
+ ...frame.capturedAt !== void 0 ? { capturedAt: frame.capturedAt } : {},
83669
+ complete: frame.complete,
83670
+ lighting: retrainFrameLighting({
83671
+ ...frame.capturedAt !== void 0 ? { capturedAt: frame.capturedAt } : {},
83672
+ ...frame.chromaSpread !== void 0 ? { chromaSpread: frame.chromaSpread } : {},
83673
+ coordinates
83674
+ }),
83675
+ ...zone !== null ? { zoneUsed: zone } : {},
83676
+ annotations
83677
+ };
83678
+ });
83679
+ return {
83680
+ schema: RETRAIN_MANIFEST_SCHEMA,
83681
+ layout,
83682
+ detailCropConvention: RETRAIN_CROP_CONVENTION,
83683
+ ...layout === "crops" ? { cropMaxSide: RETRAIN_CROP_MAX_SIDE } : {},
83684
+ generatedAt: input.generatedAt,
83685
+ batchId: input.batchId,
83686
+ frameCount: frames.length,
83687
+ annotationCount,
83688
+ modelErrorCount,
83689
+ classifierCropPadding: CLASSIFIER_CROP_PADDING,
83690
+ frames
83691
+ };
83692
+ }
83693
+ //#endregion
83694
+ //#region src/pipeline-analytics/retrain/retrain-dataset-service.ts
83695
+ function filterOf(selection, completeOnly) {
83696
+ return {
83697
+ ...selection.deviceIds !== void 0 && selection.deviceIds.length > 0 ? { deviceIds: [...selection.deviceIds] } : {},
83698
+ ...selection.collectionIds !== void 0 && selection.collectionIds.length > 0 ? { collectionIds: [...selection.collectionIds] } : {},
83699
+ ...selection.frameIds !== void 0 && selection.frameIds.length > 0 ? { frameIds: [...selection.frameIds] } : {},
83700
+ ...completeOnly ? { completeOnly: true } : {}
83701
+ };
83702
+ }
83703
+ function summaryOf(version) {
83704
+ const { frameIds, ...rest } = version;
83705
+ return {
83706
+ ...rest,
83707
+ frameCount: frameIds.length
83708
+ };
83709
+ }
83710
+ var RetrainDatasetService = class {
83711
+ deps;
83712
+ constructor(deps) {
83713
+ this.deps = deps;
83714
+ }
83715
+ /** The counts a selection would freeze, without freezing anything. */
83716
+ async preview(input) {
83717
+ return datasetStats((await this.deps.buildPlan({
83718
+ filter: filterOf(input.selection, input.completeOnly),
83719
+ layout: "frames"
83720
+ })).manifest);
83721
+ }
83722
+ async create(input) {
83723
+ const name = input.name.trim();
83724
+ if (name === "") return {
83725
+ kind: "refused",
83726
+ reason: "empty-name"
83727
+ };
83728
+ if (await this.deps.versions.findIdByName(name) !== null) return {
83729
+ kind: "refused",
83730
+ reason: "duplicate-name"
83731
+ };
83732
+ const plan = await this.deps.buildPlan({
83733
+ filter: filterOf(input.selection, input.completeOnly),
83734
+ layout: input.layout
83735
+ });
83736
+ if (plan.manifest.frames.length === 0) return {
83737
+ kind: "refused",
83738
+ reason: "no-frames"
83739
+ };
83740
+ const version = {
83741
+ id: this.deps.newId(),
83742
+ name,
83743
+ createdAt: this.deps.now(),
83744
+ selection: input.selection,
83745
+ layout: input.layout,
83746
+ completeOnly: input.completeOnly,
83747
+ frameIds: plan.manifest.frames.map((f) => f.frameId).toSorted(),
83748
+ annotationHash: datasetAnnotationHash(plan.manifest.frames),
83749
+ manifestSchema: RETRAIN_MANIFEST_SCHEMA,
83750
+ stats: datasetStats(plan.manifest)
83751
+ };
83752
+ await this.deps.versions.insert(version);
83753
+ this.deps.logger.info("retrain dataset version created", { meta: {
83754
+ versionId: version.id,
83755
+ frames: version.frameIds.length,
83756
+ subjects: version.stats.subjects,
83757
+ completeness: version.stats.completeness,
83758
+ layout: version.layout
83759
+ } });
83760
+ return {
83761
+ kind: "created",
83762
+ version: summaryOf(version)
83763
+ };
83764
+ }
83765
+ async list() {
83766
+ return (await this.deps.versions.list()).map(summaryOf);
83767
+ }
83768
+ async delete(input) {
83769
+ const deleted = await this.deps.versions.remove(input.id);
83770
+ if (deleted) this.deps.logger.info("retrain dataset version deleted", { meta: { versionId: input.id } });
83771
+ return { deleted };
83772
+ }
83773
+ /** Does the corpus still teach what the version froze? */
83774
+ async verify(input) {
83775
+ const version = await this.deps.versions.get(input.id);
83776
+ if (version === null) return {
83777
+ found: false,
83778
+ annotationHash: "",
83779
+ matches: false,
83780
+ missingFrames: 0
83781
+ };
83782
+ const plan = await this.deps.buildPlan({
83783
+ filter: { frameIds: [...version.frameIds] },
83784
+ layout: "frames"
83785
+ });
83786
+ const present = new Map(plan.manifest.frames.map((f) => [f.frameId, f]));
83787
+ const annotationHash = datasetAnnotationHash(version.frameIds.map((frameId) => present.get(frameId) ?? {
83788
+ frameId,
83789
+ complete: false,
83790
+ annotations: []
83791
+ }));
83792
+ return {
83793
+ found: true,
83794
+ annotationHash,
83795
+ matches: annotationHash === version.annotationHash,
83796
+ missingFrames: version.frameIds.filter((id) => !present.has(id)).length
83797
+ };
83798
+ }
83799
+ /**
83800
+ * An export filter with `datasetVersionId` resolved to that version's
83801
+ * frames (and its completeOnly). An unknown version exports NOTHING — never
83802
+ * the whole dataset, which is what dropping the field would do.
83803
+ */
83804
+ async exportFilterFor(filter) {
83805
+ if (filter.datasetVersionId === void 0) return filter;
83806
+ const { datasetVersionId, ...rest } = filter;
83807
+ const version = await this.deps.versions.get(datasetVersionId);
83808
+ if (version === null) {
83809
+ this.deps.logger.warn("retrain export asked for an unknown dataset version", { meta: { versionId: datasetVersionId } });
83810
+ return {
83811
+ ...rest,
83812
+ frameIds: []
83813
+ };
83814
+ }
83815
+ const wanted = rest.frameIds !== void 0 ? version.frameIds.filter((id) => rest.frameIds?.includes(id) === true) : version.frameIds;
83816
+ return {
83817
+ ...rest,
83818
+ frameIds: [...wanted],
83819
+ ...version.completeOnly ? { completeOnly: true } : {}
83820
+ };
83821
+ }
83822
+ };
83823
+ //#endregion
83824
+ //#region src/pipeline-analytics/retrain/retrain-dataset-version-store.ts
83825
+ /**
83826
+ * @durable class=ledger owner=pipeline-analytics
83827
+ * write="one row per dataset version the operator creates: a frozen frameIds list,
83828
+ * the sha256 of the annotations those frames carried, the export parameters and
83829
+ * the counts at creation. Never updated after the insert — a different selection
83830
+ * is a new version."
83831
+ * retention="none — rows leave only when the operator deletes the version. The
83832
+ * frames themselves are owned by the retrain corpus; deleting a version deletes no
83833
+ * frame, and deleting a frame does not edit a version (verify reports it missing)."
83834
+ */
83835
+ var RETRAIN_DATASET_VERSIONS_COLLECTION = "pipeline-analytics:retrain-dataset-versions";
83836
+ var COLUMNS = [
83837
+ {
83838
+ name: "id",
83839
+ type: "TEXT",
83840
+ primaryKey: true,
83841
+ notNull: true
83842
+ },
83843
+ {
83844
+ name: "name",
83845
+ type: "TEXT",
83846
+ notNull: true
83847
+ },
83848
+ {
83849
+ name: "nameKey",
83850
+ type: "TEXT",
83851
+ notNull: true
83852
+ },
83853
+ {
83854
+ name: "createdAt",
83855
+ type: "INTEGER",
83856
+ notNull: true
83857
+ },
83858
+ {
83859
+ name: "selection",
83860
+ type: "JSON",
83861
+ notNull: true
83862
+ },
83863
+ {
83864
+ name: "layout",
83865
+ type: "TEXT",
83866
+ notNull: true
83867
+ },
83868
+ {
83869
+ name: "completeOnly",
83870
+ type: "BOOLEAN",
83871
+ notNull: true
83872
+ },
83873
+ {
83874
+ name: "frameIds",
83875
+ type: "JSON",
83876
+ notNull: true
83877
+ },
83878
+ {
83879
+ name: "annotationHash",
83880
+ type: "TEXT",
83881
+ notNull: true
83882
+ },
83883
+ {
83884
+ name: "manifestSchema",
83885
+ type: "TEXT",
83886
+ notNull: true
83887
+ },
83888
+ {
83889
+ name: "stats",
83890
+ type: "JSON",
83891
+ notNull: true
83892
+ }
83893
+ ];
83894
+ var INDEXES = [{
83895
+ name: "idx_retrain_dataset_versions_name_key",
83896
+ columns: ["nameKey"],
83897
+ unique: true
83898
+ }, {
83899
+ name: "idx_retrain_dataset_versions_created",
83900
+ columns: ["createdAt"]
83901
+ }];
83902
+ var nameKeyOf = (name) => name.trim().toLowerCase();
83903
+ function numberList(value) {
83904
+ return Array.isArray(value) ? value.filter((v) => typeof v === "number") : void 0;
83905
+ }
83906
+ function stringList(value) {
83907
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : void 0;
83908
+ }
83909
+ function parseJson(value) {
83910
+ if (typeof value !== "string") return value;
83911
+ try {
83912
+ return JSON.parse(value);
83913
+ } catch {
83914
+ return;
83915
+ }
83916
+ }
83917
+ function asRecord(value) {
83918
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
83919
+ }
83920
+ var EMPTY_STATS = {
83921
+ frames: 0,
83922
+ completeFrames: 0,
83923
+ completeness: 0,
83924
+ subjects: 0,
83925
+ modelErrors: 0,
83926
+ hardNegatives: 0,
83927
+ cells: [],
83928
+ cameras: []
83929
+ };
83930
+ /** A stored stats object, trusted only as far as its shape. */
83931
+ function readStats(value) {
83932
+ const raw = asRecord(parseJson(value));
83933
+ const num = (k) => typeof raw[k] === "number" ? raw[k] : 0;
83934
+ const cells = Array.isArray(raw["cells"]) ? raw["cells"] : [];
83935
+ const cameras = Array.isArray(raw["cameras"]) ? raw["cameras"] : [];
83936
+ return {
83937
+ ...EMPTY_STATS,
83938
+ frames: num("frames"),
83939
+ completeFrames: num("completeFrames"),
83940
+ completeness: num("completeness"),
83941
+ subjects: num("subjects"),
83942
+ modelErrors: num("modelErrors"),
83943
+ hardNegatives: num("hardNegatives"),
83944
+ cells: cells.map((c) => {
83945
+ const r = asRecord(c);
83946
+ const lighting = r["lighting"] === "day" || r["lighting"] === "night" ? r["lighting"] : "unknown";
83947
+ return {
83948
+ macroClass: String(r["macroClass"] ?? ""),
83949
+ deviceId: Number(r["deviceId"] ?? 0),
83950
+ lighting,
83951
+ subjects: Number(r["subjects"] ?? 0),
83952
+ frames: Number(r["frames"] ?? 0)
83953
+ };
83954
+ }),
83955
+ cameras: cameras.map((c) => {
83956
+ const r = asRecord(c);
83957
+ return {
83958
+ deviceId: Number(r["deviceId"] ?? 0),
83959
+ frames: Number(r["frames"] ?? 0),
83960
+ completeFrames: Number(r["completeFrames"] ?? 0)
83961
+ };
83962
+ })
83963
+ };
83964
+ }
83965
+ var RetrainDatasetVersionStore = class {
83966
+ store;
83967
+ logger;
83968
+ constructor(deps) {
83969
+ this.store = deps.store;
83970
+ this.logger = deps.logger;
83971
+ }
83972
+ static async declare(store) {
83973
+ await store.declareCollection.mutate({
83974
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83975
+ columns: [...COLUMNS],
83976
+ indexes: [...INDEXES]
83977
+ });
83978
+ }
83979
+ insert = async (version) => {
83980
+ await this.store.set.mutate({
83981
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
83982
+ key: version.id,
83983
+ value: {
83984
+ name: version.name,
83985
+ nameKey: nameKeyOf(version.name),
83986
+ createdAt: version.createdAt,
83987
+ selection: version.selection,
83988
+ layout: version.layout,
83989
+ completeOnly: version.completeOnly,
83990
+ frameIds: version.frameIds,
83991
+ annotationHash: version.annotationHash,
83992
+ manifestSchema: version.manifestSchema,
83993
+ stats: version.stats
83994
+ }
83995
+ });
83996
+ };
83997
+ get = async (id) => {
83998
+ try {
83999
+ const row = await this.store.get.query({
84000
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84001
+ key: id
84002
+ });
84003
+ return row === null || row === void 0 ? null : this.toVersion(id, asRecord(row));
84004
+ } catch (err) {
84005
+ this.logger.warn("retrain dataset version read failed", { meta: {
84006
+ versionId: id,
84007
+ error: String(err)
84008
+ } });
84009
+ return null;
84010
+ }
84011
+ };
84012
+ list = async () => {
84013
+ try {
84014
+ return (await this.store.query.query({
84015
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84016
+ filter: { orderBy: {
84017
+ field: "createdAt",
84018
+ direction: "desc"
84019
+ } }
84020
+ })).map((r) => this.toVersion(r.id, asRecord(r.data)));
84021
+ } catch (err) {
84022
+ this.logger.warn("retrain dataset versions list failed", { meta: { error: String(err) } });
84023
+ return [];
84024
+ }
84025
+ };
84026
+ findIdByName = async (name) => {
84027
+ return (await this.store.query.query({
84028
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84029
+ filter: {
84030
+ where: { nameKey: nameKeyOf(name) },
84031
+ limit: 1
84032
+ }
84033
+ }))[0]?.id ?? null;
84034
+ };
84035
+ remove = async (id) => {
84036
+ if (await this.get(id) === null) return false;
84037
+ await this.store.delete.mutate({
84038
+ collection: RETRAIN_DATASET_VERSIONS_COLLECTION,
84039
+ key: id
84040
+ });
84041
+ return true;
84042
+ };
84043
+ toVersion(id, data) {
84044
+ const selection = asRecord(parseJson(data["selection"]));
84045
+ const deviceIds = numberList(selection["deviceIds"]);
84046
+ const collectionIds = stringList(selection["collectionIds"]);
84047
+ const frameIdsSel = stringList(selection["frameIds"]);
84048
+ return {
84049
+ id,
84050
+ name: String(data["name"] ?? ""),
84051
+ createdAt: Number(data["createdAt"] ?? 0),
84052
+ selection: {
84053
+ ...deviceIds !== void 0 ? { deviceIds } : {},
84054
+ ...collectionIds !== void 0 ? { collectionIds } : {},
84055
+ ...frameIdsSel !== void 0 ? { frameIds: frameIdsSel } : {}
84056
+ },
84057
+ layout: data["layout"] === "crops" ? "crops" : "frames",
84058
+ completeOnly: data["completeOnly"] === true || data["completeOnly"] === 1,
84059
+ frameIds: stringList(parseJson(data["frameIds"])) ?? [],
84060
+ annotationHash: String(data["annotationHash"] ?? ""),
84061
+ manifestSchema: String(data["manifestSchema"] ?? ""),
84062
+ stats: readStats(data["stats"])
84063
+ };
84064
+ }
84065
+ };
84066
+ //#endregion
83283
84067
  //#region src/pipeline-analytics/retrain/retrain-llm-assist.ts
83284
84068
  var ALWAYS_LOCAL_KINDS = new Set(["managed-local", "apple-foundation"]);
83285
84069
  var ALWAYS_CLOUD_KINDS = new Set([
@@ -83993,285 +84777,6 @@ async function cutRetrainCrop(input) {
83993
84777
  return pipeline.jpeg({ quality: 92 }).toBuffer();
83994
84778
  }
83995
84779
  //#endregion
83996
- //#region src/pipeline-analytics/retrain/retrain-geometry.ts
83997
- function clamp01(v) {
83998
- return v < 0 ? 0 : v > 1 ? 1 : v;
83999
- }
84000
- /** Is this a usable rectangle at all? A zero-area box is not a subject. */
84001
- function isUsableRect(r) {
84002
- return Number.isFinite(r.x) && Number.isFinite(r.y) && Number.isFinite(r.w) && Number.isFinite(r.h) && r.w > 0 && r.h > 0;
84003
- }
84004
- /**
84005
- * The subject inside a square LETTERBOX of the full frame.
84006
- *
84007
- * The frame is scaled to fit the square and centred; the padding bars take the
84008
- * rest. A wide 16:9 frame therefore occupies the middle 9/16 of the square's
84009
- * height, and every y coordinate shifts by half the leftover.
84010
- *
84011
- * Square-side agnostic on purpose: the answer is normalised against the square,
84012
- * so it is the same number for a 640 and a 1280 input and no caller has to know
84013
- * which the model uses.
84014
- */
84015
- function deriveLetterboxRect(bbox, frameWidth, frameHeight) {
84016
- if (!isUsableRect(bbox)) return null;
84017
- if (!(frameWidth > 0) || !(frameHeight > 0)) return null;
84018
- const longSide = Math.max(frameWidth, frameHeight);
84019
- const scaleX = frameWidth / longSide;
84020
- const scaleY = frameHeight / longSide;
84021
- const offsetX = (1 - scaleX) / 2;
84022
- const offsetY = (1 - scaleY) / 2;
84023
- return {
84024
- bbox: {
84025
- x: offsetX + bbox.x * scaleX,
84026
- y: offsetY + bbox.y * scaleY,
84027
- w: bbox.w * scaleX,
84028
- h: bbox.h * scaleY
84029
- },
84030
- visibleFraction: 1
84031
- };
84032
- }
84033
- /**
84034
- * The subject inside a ZONE CROP — the shape a package model is trained on.
84035
- *
84036
- * Returns `null` when the subject does not intersect the zone at all. That is
84037
- * the whole reason the package proposal runs on the crop rather than the frame:
84038
- * a package detector at 0.35 on a full frame proposes furniture, and a subject
84039
- * outside the zone is not part of this model's world.
84040
- */
84041
- function deriveZoneCropRect(bbox, zone) {
84042
- if (!isUsableRect(bbox) || !isUsableRect(zone)) return null;
84043
- const left = Math.max(bbox.x, zone.x);
84044
- const top = Math.max(bbox.y, zone.y);
84045
- const right = Math.min(bbox.x + bbox.w, zone.x + zone.w);
84046
- const bottom = Math.min(bbox.y + bbox.h, zone.y + zone.h);
84047
- if (!(right > left) || !(bottom > top)) return null;
84048
- const visibleFraction = (right - left) * (bottom - top) / (bbox.w * bbox.h);
84049
- return {
84050
- bbox: {
84051
- x: (left - zone.x) / zone.w,
84052
- y: (top - zone.y) / zone.h,
84053
- w: (right - left) / zone.w,
84054
- h: (bottom - top) / zone.h
84055
- },
84056
- visibleFraction
84057
- };
84058
- }
84059
- /**
84060
- * The subject inside its OWN padded crop — the shape a classifier is trained on.
84061
- *
84062
- * The crop is the box grown by `padding` of its own size on each side and then
84063
- * clipped to the frame, so the subject sits in the middle and the classifier
84064
- * still sees a little context. The returned rectangle is where the subject
84065
- * lands inside that crop, which is NOT `[0,0,1,1]` whenever the padding was
84066
- * clipped by a frame edge — a subject at the left border is flush left in its
84067
- * crop, and a caller that assumed the centre would draw the box in the wrong
84068
- * place.
84069
- */
84070
- function deriveSubjectCropRect(bbox, padding) {
84071
- if (!isUsableRect(bbox)) return null;
84072
- const pad = Number.isFinite(padding) && padding > 0 ? padding : 0;
84073
- const cropX = clamp01(bbox.x - bbox.w * pad);
84074
- const cropY = clamp01(bbox.y - bbox.h * pad);
84075
- const cropRight = clamp01(bbox.x + bbox.w * (1 + pad));
84076
- const cropBottom = clamp01(bbox.y + bbox.h * (1 + pad));
84077
- const cropW = cropRight - cropX;
84078
- const cropH = cropBottom - cropY;
84079
- if (!(cropW > 0) || !(cropH > 0)) return null;
84080
- return {
84081
- bbox: {
84082
- x: (bbox.x - cropX) / cropW,
84083
- y: (bbox.y - cropY) / cropH,
84084
- w: bbox.w / cropW,
84085
- h: bbox.h / cropH
84086
- },
84087
- visibleFraction: 1
84088
- };
84089
- }
84090
- //#endregion
84091
- //#region src/pipeline-analytics/retrain/retrain-lighting.ts
84092
- function isNight(condition) {
84093
- return condition === "night" || condition === "ir";
84094
- }
84095
- function retrainFrameLighting(input) {
84096
- if (input.capturedAt === void 0) {
84097
- const measured = resolveConditionDetailed({
84098
- ...input.chromaSpread !== void 0 ? { chromaSpread: input.chromaSpread } : {},
84099
- now: /* @__PURE__ */ new Date(0)
84100
- });
84101
- return measured.source === "ir-chroma" ? {
84102
- condition: measured.condition,
84103
- night: true,
84104
- source: measured.source
84105
- } : null;
84106
- }
84107
- const resolved = resolveConditionDetailed({
84108
- ...input.chromaSpread !== void 0 ? { chromaSpread: input.chromaSpread } : {},
84109
- ...input.coordinates !== null ? { coordinates: input.coordinates } : {},
84110
- now: new Date(input.capturedAt)
84111
- });
84112
- return {
84113
- ...resolved,
84114
- night: isNight(resolved.condition)
84115
- };
84116
- }
84117
- //#endregion
84118
- //#region src/pipeline-analytics/retrain/retrain-export.ts
84119
- /**
84120
- * The ANNOTATED dataset export — the retrain page's own archive.
84121
- *
84122
- * Sibling of `training-export/`, deliberately not the same thing. That one
84123
- * streams a marked track's stored media verbatim and knows nothing about what
84124
- * is in it; this one streams the frames the operator SELECTED (already copied
84125
- * into retrain storage) plus the ground truth they drew.
84126
- *
84127
- * ## The one interesting decision
84128
- *
84129
- * `annotations.json` carries, for every subject, the canonical full-frame box
84130
- * AND the geometry derived for each model shape. **Derived here, at export,
84131
- * never stored** — one box in, three shapes out. Storing the derived shapes is
84132
- * how two crops of the same subject end up in one feature space, which is the
84133
- * failure already recorded for CLIP (D52). A consumer that wants a different
84134
- * input size or a different padding re-derives from the canonical box; nothing
84135
- * in the database has to change and nothing can disagree.
84136
- *
84137
- * Everything else follows the export next door: index reads to plan, one JPEG
84138
- * in memory at a time, a yield between files, and NO inference — a training
84139
- * set's pixels are the pixels the pipeline saw.
84140
- */
84141
- var RETRAIN_EXPORT_PREFIX = "retrain-export";
84142
- /**
84143
- * Padding a subject-crop classifier is trained with: the RUNTIME detail-crop
84144
- * padding, read from the one convention `deriveDetailCropRect` cuts with
84145
- * (`types/src/pipeline/detail-crop.ts`), never a second literal. It was `0.2`
84146
- * here while the runtime fed `0.15`, so a classifier trained from this export
84147
- * learned on more context than it is ever given live.
84148
- */
84149
- var CLASSIFIER_CROP_PADDING = DEFAULT_DETAIL_CROP_CONVENTION.paddingRatio;
84150
- /** The convention a `crops` layout cuts with — the runtime default, recorded
84151
- * in the manifest so a consumer never has to assume it. */
84152
- var RETRAIN_CROP_CONVENTION = DEFAULT_DETAIL_CROP_CONVENTION;
84153
- /** Longest side of a `crops`-layout JPEG. Classifier inputs are 224–260 px;
84154
- * the trainer resizes per model, so this only bounds the upload. */
84155
- var RETRAIN_CROP_MAX_SIDE = 256;
84156
- /**
84157
- * Manifest schema id. v2 = capturedAt / complete / lighting / layout.
84158
- * v3 = `origin` (`track` | `recording`), `epochMs` on a recording still,
84159
- * `collectionIds`, and `trackId` ABSENT on a recording still, which has none.
84160
- */
84161
- var RETRAIN_MANIFEST_SCHEMA = "camstack.retrain-export/v3";
84162
- /** Archive path of a frame: `frames/<deviceId>/<trackId>/<frameId leaf>.jpg`,
84163
- * or `frames/<deviceId>/recording/<leaf>.jpg` for a recording still. */
84164
- function retrainFramePath(frame) {
84165
- const leaf = frame.frameId.slice(frame.frameId.lastIndexOf(":") + 1);
84166
- const dir = frame.origin === "recording" ? RECORDING_STILL_DIR : frame.trackId;
84167
- return `frames/${frame.deviceId}/${dir}/${leaf}.jpg`;
84168
- }
84169
- /** Archive path of a subject crop: `crops/<macroClass>/<annotationId>.jpg`. */
84170
- function retrainCropPath(macroClass, annotationId) {
84171
- return `crops/${macroClass}/${annotationId}.jpg`;
84172
- }
84173
- /**
84174
- * Narrow the planned frames to a filter. Pure. Every field narrows; an empty
84175
- * filter keeps everything. Order is the input's.
84176
- */
84177
- function filterRetrainExport(frames, annotationsByFrame, filter, collectionsByFrame = /* @__PURE__ */ new Map()) {
84178
- const wanted = filter.frameIds !== void 0 ? new Set(filter.frameIds) : null;
84179
- const collections = filter.collectionIds !== void 0 && filter.collectionIds.length > 0 ? new Set(filter.collectionIds) : null;
84180
- const devices = filter.deviceIds !== void 0 && filter.deviceIds.length > 0 ? new Set(filter.deviceIds) : null;
84181
- return frames.filter((frame) => {
84182
- if (wanted !== null && !wanted.has(frame.frameId)) return false;
84183
- if (devices !== null && !devices.has(frame.deviceId)) return false;
84184
- if (filter.completeOnly === true && !frame.complete) return false;
84185
- if (collections !== null && !(collectionsByFrame.get(frame.frameId) ?? []).some((id) => collections.has(id))) return false;
84186
- if (filter.batchId !== void 0) {
84187
- if (!(annotationsByFrame.get(frame.frameId) ?? []).some((a) => a.exportedInBatch === filter.batchId)) return false;
84188
- }
84189
- return true;
84190
- });
84191
- }
84192
- /**
84193
- * Derive every per-model shape for one annotation.
84194
- *
84195
- * `zone` is optional and per-frame: only a package annotation has one, and a
84196
- * `zoneCrop` for a subject the zone does not contain is ABSENT rather than
84197
- * clamped — a clamped box teaches the model that the subject ends where the
84198
- * crop ends.
84199
- */
84200
- function deriveAll(annotation, frame, zone) {
84201
- const letterbox = deriveLetterboxRect(annotation.bbox, frame.width, frame.height);
84202
- const subjectCrop = deriveSubjectCropRect(annotation.bbox, CLASSIFIER_CROP_PADDING);
84203
- const zoneCrop = zone === null ? null : deriveZoneCropRect(annotation.bbox, zone);
84204
- return {
84205
- ...letterbox !== null ? { letterbox } : {},
84206
- ...zoneCrop !== null ? { zoneCrop } : {},
84207
- ...subjectCrop !== null ? { subjectCrop } : {}
84208
- };
84209
- }
84210
- /** Build the manifest. Pure — index rows in, JSON out, no blob touched. */
84211
- function buildRetrainManifest(input) {
84212
- const layout = input.layout ?? "frames";
84213
- const coordinates = input.coordinates ?? null;
84214
- let annotationCount = 0;
84215
- let modelErrorCount = 0;
84216
- const frames = input.frames.map((frame) => {
84217
- const zone = input.zoneForDevice(frame.deviceId);
84218
- const annotations = (input.annotationsByFrame.get(frame.frameId) ?? []).map((a) => {
84219
- annotationCount += 1;
84220
- if (a.kind === "model_error") modelErrorCount += 1;
84221
- const derived = deriveAll(a, frame, zone);
84222
- return {
84223
- id: a.id,
84224
- macroClass: a.macroClass,
84225
- ...a.label !== void 0 ? { label: a.label } : {},
84226
- ...a.subLabel !== void 0 ? { subLabel: a.subLabel } : {},
84227
- kind: a.kind,
84228
- source: a.source,
84229
- ...a.assistModelId !== void 0 ? { assistModelId: a.assistModelId } : {},
84230
- ...a.assistScore !== void 0 ? { assistScore: a.assistScore } : {},
84231
- bbox: a.bbox,
84232
- ...layout === "crops" && derived.subjectCrop !== void 0 ? { cropPath: retrainCropPath(a.macroClass, a.id) } : {},
84233
- derived
84234
- };
84235
- });
84236
- return {
84237
- frameId: frame.frameId,
84238
- path: retrainFramePath(frame),
84239
- deviceId: frame.deviceId,
84240
- origin: frame.origin,
84241
- ...frame.origin === "track" ? { trackId: frame.trackId } : {},
84242
- ...frame.epochMs !== void 0 ? { epochMs: frame.epochMs } : {},
84243
- collectionIds: input.collectionsByFrame?.get(frame.frameId) ?? [],
84244
- width: frame.width,
84245
- height: frame.height,
84246
- sizeBytes: frame.sizeBytes,
84247
- sourceKind: frame.sourceKind,
84248
- copiedAt: frame.copiedAt,
84249
- ...frame.capturedAt !== void 0 ? { capturedAt: frame.capturedAt } : {},
84250
- complete: frame.complete,
84251
- lighting: retrainFrameLighting({
84252
- ...frame.capturedAt !== void 0 ? { capturedAt: frame.capturedAt } : {},
84253
- ...frame.chromaSpread !== void 0 ? { chromaSpread: frame.chromaSpread } : {},
84254
- coordinates
84255
- }),
84256
- ...zone !== null ? { zoneUsed: zone } : {},
84257
- annotations
84258
- };
84259
- });
84260
- return {
84261
- schema: RETRAIN_MANIFEST_SCHEMA,
84262
- layout,
84263
- detailCropConvention: RETRAIN_CROP_CONVENTION,
84264
- ...layout === "crops" ? { cropMaxSide: RETRAIN_CROP_MAX_SIDE } : {},
84265
- generatedAt: input.generatedAt,
84266
- batchId: input.batchId,
84267
- frameCount: frames.length,
84268
- annotationCount,
84269
- modelErrorCount,
84270
- classifierCropPadding: CLASSIFIER_CROP_PADDING,
84271
- frames
84272
- };
84273
- }
84274
- //#endregion
84275
84780
  //#region src/pipeline-analytics/retrain/retrain-export-handler.ts
84276
84781
  /** The export request a URL carries (`getRetrainExportUrl` composes it). */
84277
84782
  function parseRetrainExportRequest(url) {
@@ -84280,12 +84785,14 @@ function parseRetrainExportRequest(url) {
84280
84785
  const deviceIds = parseRetrainDeviceIds(url);
84281
84786
  const frameIds = (params.get("frameIds") ?? "").split(",").map((id) => id.trim()).filter((id) => id.length > 0);
84282
84787
  const collectionIds = (params.get("collectionIds") ?? "").split(",").map((id) => id.trim()).filter((id) => id.length > 0);
84788
+ const datasetVersionId = params.get("datasetVersionId")?.trim() ?? "";
84283
84789
  const batchId = params.get("batchId")?.trim() ?? "";
84284
84790
  return {
84285
84791
  filter: {
84286
84792
  ...deviceIds.length > 0 ? { deviceIds: [...deviceIds] } : {},
84287
84793
  ...frameIds.length > 0 ? { frameIds } : {},
84288
84794
  ...collectionIds.length > 0 ? { collectionIds } : {},
84795
+ ...datasetVersionId.length > 0 ? { datasetVersionId } : {},
84289
84796
  ...batchId.length > 0 ? { batchId } : {},
84290
84797
  ...params.get("completeOnly") === "1" ? { completeOnly: true } : {}
84291
84798
  },
@@ -84299,6 +84806,7 @@ function retrainExportQuery(request) {
84299
84806
  if (filter.deviceIds !== void 0 && filter.deviceIds.length > 0) params.set("deviceIds", filter.deviceIds.join(","));
84300
84807
  if (filter.frameIds !== void 0 && filter.frameIds.length > 0) params.set("frameIds", filter.frameIds.join(","));
84301
84808
  if (filter.collectionIds !== void 0 && filter.collectionIds.length > 0) params.set("collectionIds", filter.collectionIds.join(","));
84809
+ if (filter.datasetVersionId !== void 0) params.set("datasetVersionId", filter.datasetVersionId);
84302
84810
  if (filter.batchId !== void 0) params.set("batchId", filter.batchId);
84303
84811
  if (filter.completeOnly === true) params.set("completeOnly", "1");
84304
84812
  if (request.layout === "crops") params.set("layout", "crops");
@@ -108407,6 +108915,8 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
108407
108915
  retrain = null;
108408
108916
  retrainCollections = null;
108409
108917
  retrainLlmAssist = null;
108918
+ retrainDatasetVersions = null;
108919
+ retrainDatasets = null;
108410
108920
  retrainCollectionStore = null;
108411
108921
  retrainFrames = null;
108412
108922
  retrainAnnotations = null;
@@ -110203,6 +110713,7 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
110203
110713
  RetrainFrameStore.declare(api.settingsStore),
110204
110714
  RetrainAnnotationStore.declare(api.settingsStore),
110205
110715
  RetrainCollectionStore.declare(api.settingsStore),
110716
+ RetrainDatasetVersionStore.declare(api.settingsStore),
110206
110717
  OpsLogStore.declare(api.settingsStore),
110207
110718
  DebugNoteArchiveStore.declare(api.settingsStore),
110208
110719
  TrackingTapeIndexStore.declare(api.settingsStore),
@@ -110510,6 +111021,18 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
110510
111021
  captureStill: (deviceId, epochMs) => retrainStills.recover(deviceId, epochMs, RETRAIN_STILL_MAX_WIDTH),
110511
111022
  logger: logger.child("RetrainCollections")
110512
111023
  });
111024
+ const retrainDatasetVersions = new RetrainDatasetVersionStore({
111025
+ store: api.settingsStore,
111026
+ logger: logger.child("RetrainDatasets")
111027
+ });
111028
+ this.retrainDatasetVersions = retrainDatasetVersions;
111029
+ this.retrainDatasets = new RetrainDatasetService({
111030
+ versions: retrainDatasetVersions,
111031
+ buildPlan: (request) => this.buildRetrainExportPlan(request),
111032
+ now: () => Date.now(),
111033
+ newId: () => randomUUID(),
111034
+ logger: logger.child("RetrainDatasets")
111035
+ });
110513
111036
  this.retrainLlmAssist = new RetrainLlmAssistService({
110514
111037
  frames: retrainFrames,
110515
111038
  llm: {
@@ -111762,6 +112285,29 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
111762
112285
  async setRetrainFrameComplete(input) {
111763
112286
  return this.requireRetrain().setFrameComplete(input);
111764
112287
  }
112288
+ requireRetrainDatasets() {
112289
+ const service = this.retrainDatasets;
112290
+ if (service === null) throw new Error("retrain datasets: analytics stores unavailable");
112291
+ return service;
112292
+ }
112293
+ async previewRetrainDataset(input) {
112294
+ return this.requireRetrainDatasets().preview({
112295
+ selection: input.selection,
112296
+ completeOnly: input.completeOnly ?? false
112297
+ });
112298
+ }
112299
+ async createRetrainDatasetVersion(input) {
112300
+ return this.requireRetrainDatasets().create(input);
112301
+ }
112302
+ async listRetrainDatasetVersions(_input) {
112303
+ return this.requireRetrainDatasets().list();
112304
+ }
112305
+ async deleteRetrainDatasetVersion(input) {
112306
+ return this.requireRetrainDatasets().delete(input);
112307
+ }
112308
+ async verifyRetrainDatasetVersion(input) {
112309
+ return this.requireRetrainDatasets().verify(input);
112310
+ }
111765
112311
  requireRetrainLlmAssist() {
111766
112312
  const service = this.retrainLlmAssist;
111767
112313
  if (service === null) throw new Error("retrain AI assist: analytics stores unavailable");
@@ -112152,6 +112698,22 @@ var PipelineAnalyticsAddon = class PipelineAnalyticsAddon extends BaseAddon {
112152
112698
  * zones simply has no `zoneCrop` geometry — absent, never a clamped guess.
112153
112699
  */
112154
112700
  async buildRetrainExportPlan(request) {
112701
+ const versionId = request.filter.datasetVersionId;
112702
+ if (versionId !== void 0) {
112703
+ const version = await this.retrainDatasetVersions?.get(versionId) ?? null;
112704
+ const { datasetVersionId: _resolved, ...rest } = await this.retrainDatasets?.exportFilterFor(request.filter) ?? {
112705
+ ...request.filter,
112706
+ frameIds: []
112707
+ };
112708
+ const plan = await this.buildRetrainExportPlan({
112709
+ ...request,
112710
+ filter: rest
112711
+ });
112712
+ return version === null ? plan : {
112713
+ ...plan,
112714
+ manifest: withDatasetVersion(plan.manifest, version)
112715
+ };
112716
+ }
112155
112717
  const frameStore = this.retrainFrames;
112156
112718
  const annotationStore = this.retrainAnnotations;
112157
112719
  const trackStore = this.trackStore;