@7365admin1/core 3.82.0 → 3.84.0

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/index.js CHANGED
@@ -77,6 +77,7 @@ __export(src_exports, {
77
77
  ConsoleAuditAction: () => ConsoleAuditAction,
78
78
  ConsoleAuditTarget: () => ConsoleAuditTarget,
79
79
  DEFAULT_RING_SECONDS: () => DEFAULT_RING_SECONDS,
80
+ DEFAULT_SCALE_M_PER_PX: () => DEFAULT_SCALE_M_PER_PX,
80
81
  DEFAULT_SITE_TIMEZONE: () => DEFAULT_SITE_TIMEZONE,
81
82
  DEVICE_STATUS: () => DEVICE_STATUS,
82
83
  DOBStatus: () => DOBStatus,
@@ -215,6 +216,8 @@ __export(src_exports, {
215
216
  ORG_OWNER_REPAIR_CLAIM_TAKEN_REASON: () => ORG_OWNER_REPAIR_CLAIM_TAKEN_REASON,
216
217
  ORG_OWNER_REPAIR_DEFAULT: () => ORG_OWNER_REPAIR_DEFAULT,
217
218
  ORG_OWNER_REPAIR_OFF_REASON: () => ORG_OWNER_REPAIR_OFF_REASON,
219
+ OUNITECH_BRAND: () => OUNITECH_BRAND,
220
+ OUNITECH_CAPABILITIES: () => OUNITECH_CAPABILITIES,
218
221
  OrgNature: () => OrgNature,
219
222
  OvernightParkingRequestSort: () => OvernightParkingRequestSort,
220
223
  OvernightParkingRequestStatus: () => OvernightParkingRequestStatus,
@@ -242,6 +245,7 @@ __export(src_exports, {
242
245
  REALTIME_MAX_FANOUT: () => REALTIME_MAX_FANOUT,
243
246
  REGISTERED_VEHICLE_CATEGORIES: () => REGISTERED_VEHICLE_CATEGORIES,
244
247
  ROBOT_APP_SCOPES: () => ROBOT_APP_SCOPES,
248
+ ROBOT_CAPABILITIES: () => ROBOT_CAPABILITIES,
245
249
  ROBOT_CLOUD_DOC_CONFIRMATIONS: () => ROBOT_CLOUD_DOC_CONFIRMATIONS,
246
250
  ROBOT_CLOUD_PATHS: () => ROBOT_CLOUD_PATHS,
247
251
  ROBOT_MODULE: () => ROBOT_MODULE,
@@ -293,6 +297,8 @@ __export(src_exports, {
293
297
  VerificationType: () => VerificationType,
294
298
  VisitorSort: () => VisitorSort,
295
299
  VisitorStatus: () => VisitorStatus,
300
+ WALL_HEIGHT_M: () => WALL_HEIGHT_M,
301
+ WALL_THICKNESS_M: () => WALL_THICKNESS_M,
296
302
  addressSchema: () => addressSchema,
297
303
  allowedFieldsSite: () => allowedFieldsSite,
298
304
  allowedNatures: () => allowedNatures,
@@ -304,6 +310,8 @@ __export(src_exports, {
304
310
  attendanceSchema: () => attendanceSchema,
305
311
  attendanceSettingsSchema: () => attendanceSettingsSchema,
306
312
  buildSelfServiceEmailContext: () => buildSelfServiceEmailContext,
313
+ buildingMapFromRobotMaps: () => buildingMapFromRobotMaps,
314
+ buildingSolids: () => buildingSolids,
307
315
  building_level_namespace_collection: () => building_level_namespace_collection,
308
316
  building_units_namespace_collection: () => building_units_namespace_collection,
309
317
  buildings_namespace_collection: () => buildings_namespace_collection,
@@ -330,13 +338,16 @@ __export(src_exports, {
330
338
  chatSchema: () => chatSchema,
331
339
  claimReadsAsStale: () => claimReadsAsStale,
332
340
  clampPtzSpeed: () => clampPtzSpeed,
341
+ clearRobotAdapters: () => clearRobotAdapters,
333
342
  clockDriftSeconds: () => clockDriftSeconds,
343
+ commandSupported: () => commandSupported,
334
344
  consoleRoleAllows: () => consoleRoleAllows,
335
345
  consoleRoleAllowsAll: () => consoleRoleAllowsAll,
336
346
  consoleSpellings: () => consoleSpellings,
337
347
  console_audit_namespace_collection: () => console_audit_namespace_collection,
338
348
  createLiftSimulator: () => createLiftSimulator,
339
349
  createManpowerRemarksDaily: () => createManpowerRemarksDaily,
350
+ createOunitechAdapter: () => createOunitechAdapter,
340
351
  createSseParser: () => createSseParser,
341
352
  customerSchema: () => customerSchema,
342
353
  customerSitePropertyFields: () => customerSitePropertyFields,
@@ -407,6 +418,9 @@ __export(src_exports, {
407
418
  isTermsCurrent: () => isTermsCurrent,
408
419
  isTrackingStale: () => isTrackingStale,
409
420
  isValidTimezone: () => isValidTimezone,
421
+ levelPixelToMetres: () => levelPixelToMetres,
422
+ levelScale: () => levelScale,
423
+ levelSolids: () => levelSolids,
410
424
  listStaffUserIds: () => listStaffUserIds,
411
425
  liveStatusFor: () => liveStatusFor,
412
426
  manpowerDesignationsSchema: () => manpowerDesignationsSchema,
@@ -418,6 +432,7 @@ __export(src_exports, {
418
432
  maskNric: () => maskNric,
419
433
  memberModulesHandlers: () => memberModulesHandlers,
420
434
  metresToPixel: () => metresToPixel,
435
+ missingForTier: () => missingForTier,
421
436
  moduleDeniedForCaller: () => moduleDeniedForCaller,
422
437
  moduleGateEnforceKeys: () => moduleGateEnforceKeys,
423
438
  moduleGateMode: () => moduleGateMode,
@@ -472,6 +487,9 @@ __export(src_exports, {
472
487
  recordConsoleAction: () => recordConsoleAction,
473
488
  refuseServiceProviderInviteAction: () => refuseServiceProviderInviteAction,
474
489
  registerCameraTransport: () => registerCameraTransport,
490
+ registerOunitechBrand: () => registerOunitechBrand,
491
+ registerRobotAdapter: () => registerRobotAdapter,
492
+ registeredRobotBrands: () => registeredRobotBrands,
475
493
  relayForRecorder: () => relayForRecorder,
476
494
  remarksSchema: () => remarksSchema,
477
495
  renderPagePdf: () => renderPagePdf,
@@ -489,6 +507,7 @@ __export(src_exports, {
489
507
  resolveSiteTimezone: () => resolveSiteTimezone,
490
508
  resolveStaffRole: () => resolveStaffRole,
491
509
  robotActionsFor: () => robotActionsFor,
510
+ robotAdapterFor: () => robotAdapterFor,
492
511
  robotCloudBody: () => robotCloudBody,
493
512
  robotCloudSign: () => robotCloudSign,
494
513
  robotCloudSigningString: () => robotCloudSigningString,
@@ -497,8 +516,10 @@ __export(src_exports, {
497
516
  robotModeFromList: () => robotModeFromList,
498
517
  robotSchema: () => robotSchema,
499
518
  robotScopeForApp: () => robotScopeForApp,
519
+ robotTier: () => robotTier,
500
520
  robotTypesVisibleTo: () => robotTypesVisibleTo,
501
521
  rtspUrl: () => rtspUrl,
522
+ scaleFromMeasurement: () => scaleFromMeasurement,
502
523
  schema: () => schema,
503
524
  schemaAppSlugNotification: () => schemaAppSlugNotification,
504
525
  schemaApprovedBy: () => schemaApprovedBy,
@@ -670,7 +691,9 @@ __export(src_exports, {
670
691
  stripFacialImageMetadata: () => stripFacialImageMetadata,
671
692
  subscriptionPlanSchema: () => subscriptionPlanSchema,
672
693
  summariseBulkCameraPlan: () => summariseBulkCameraPlan,
694
+ supports: () => supports,
673
695
  trackingBackoffMs: () => trackingBackoffMs,
696
+ uncontrollableFleetWarning: () => uncontrollableFleetWarning,
674
697
  updateRemarksStatusEod: () => updateRemarksStatusEod,
675
698
  updateRemarksisAcknowledged: () => updateRemarksisAcknowledged,
676
699
  updateSiteSchema: () => updateSiteSchema,
@@ -914,8 +937,10 @@ __export(src_exports, {
914
937
  vehicleSchema: () => vehicleSchema,
915
938
  vehicleViewOf: () => vehicleViewOf,
916
939
  vehicles_namespace_collection: () => vehicles_namespace_collection,
940
+ vertexDistancePx: () => vertexDistancePx,
917
941
  visitors_namespace_collection: () => visitors_namespace_collection,
918
942
  wallConfig: () => wallConfig,
943
+ wallsFromOccupancyGrid: () => wallsFromOccupancyGrid,
919
944
  withLiveLink: () => withLiveLink,
920
945
  workOrderSchema: () => workOrderSchema,
921
946
  work_orders2_namespace_collection: () => work_orders2_namespace_collection,
@@ -59003,6 +59028,7 @@ var robotSchema = import_joi74.default.object({
59003
59028
  // Optional, so every caller written before this field existed still validates.
59004
59029
  type: import_joi74.default.string().valid(...ROBOT_TYPES).optional().allow("", null),
59005
59030
  robotSn: import_joi74.default.string().optional().allow("", null),
59031
+ brand: import_joi74.default.string().optional().allow("", null),
59006
59032
  appKey: import_joi74.default.string().required(),
59007
59033
  appToken: import_joi74.default.string().required(),
59008
59034
  createdBy: import_joi74.default.string().hex().required(),
@@ -59064,6 +59090,7 @@ function MRobot(value) {
59064
59090
  */
59065
59091
  type: value.type || "cleaning",
59066
59092
  robotSn: value.robotSn ?? "",
59093
+ brand: value.brand || "ounitech",
59067
59094
  appKey: value.appKey ?? "",
59068
59095
  appToken: value.appToken ?? "",
59069
59096
  createdBy: value.createdBy ?? "",
@@ -59218,6 +59245,9 @@ function useRobotRepo() {
59218
59245
  // Not a secret - it is the vendor's device serial, and the apps
59219
59246
  // need it to address a robot. The CREDENTIALS stay out.
59220
59247
  robotSn: 1,
59248
+ // Not a secret - it names which protocol the robot speaks, and a
59249
+ // screen needs it to know which controls to draw.
59250
+ brand: 1,
59221
59251
  createdByName: 1,
59222
59252
  createdAt: 1,
59223
59253
  status: 1
@@ -59611,30 +59641,30 @@ function parseRobotCommand(body) {
59611
59641
  if (kind !== "full" && kind !== "area" && kind !== "spot" && kind !== "return") {
59612
59642
  return { ok: false, message: 'kind must be one of "full", "area", "spot", "return".' };
59613
59643
  }
59614
- const has = (k) => Object.prototype.hasOwnProperty.call(b, k);
59644
+ const has2 = (k) => Object.prototype.hasOwnProperty.call(b, k);
59615
59645
  const optionKeys = ["cleanTimes", "modeFlag", "subModeFlag", "levelFlag"];
59616
59646
  if (kind === "return") {
59617
59647
  for (const k of [...optionKeys, "areas", "points"]) {
59618
- if (has(k))
59648
+ if (has2(k))
59619
59649
  return { ok: false, message: `${k} is not valid for a return command.` };
59620
59650
  }
59621
59651
  return { ok: true, command: { kind: "return" } };
59622
59652
  }
59623
- if (kind !== "area" && has("areas")) {
59653
+ if (kind !== "area" && has2("areas")) {
59624
59654
  return { ok: false, message: "areas is only valid for an area command." };
59625
59655
  }
59626
- if (kind !== "spot" && has("points")) {
59656
+ if (kind !== "spot" && has2("points")) {
59627
59657
  return { ok: false, message: "points is only valid for a spot command." };
59628
59658
  }
59629
59659
  const int = (k, allowed, fallback) => {
59630
- if (!has(k))
59660
+ if (!has2(k))
59631
59661
  return fallback;
59632
59662
  const v = b[k];
59633
59663
  if (typeof v !== "number" || !Number.isInteger(v) || !allowed.includes(v))
59634
59664
  return null;
59635
59665
  return v;
59636
59666
  };
59637
- const cleanTimes = has("cleanTimes") ? typeof b.cleanTimes === "number" && Number.isInteger(b.cleanTimes) && b.cleanTimes >= 0 && b.cleanTimes <= 99 ? b.cleanTimes : null : DEFAULTS.cleanTimes;
59667
+ const cleanTimes = has2("cleanTimes") ? typeof b.cleanTimes === "number" && Number.isInteger(b.cleanTimes) && b.cleanTimes >= 0 && b.cleanTimes <= 99 ? b.cleanTimes : null : DEFAULTS.cleanTimes;
59638
59668
  if (cleanTimes === null)
59639
59669
  return { ok: false, message: "cleanTimes must be an integer 0-99." };
59640
59670
  const modeFlag = int("modeFlag", [1, 2, 3], DEFAULTS.modeFlag);
@@ -59850,6 +59880,125 @@ function robotActionsFor(actor) {
59850
59880
  );
59851
59881
  }
59852
59882
 
59883
+ // src/utils/robot-adapter.util.ts
59884
+ var ROBOT_CAPABILITIES = [
59885
+ /** Read the current location of the robot [x, y, yaw]. */
59886
+ "readLocation",
59887
+ /** Request the robot move to an [x, y, yaw] coordinate. */
59888
+ "navigateToPoint",
59889
+ /** Pause a robot while it is navigating, and resume it. */
59890
+ "pauseResume",
59891
+ /** Read the route/path taken, or the ETA to the destination. */
59892
+ "pathAndEta",
59893
+ /** Read the battery level. */
59894
+ "battery",
59895
+ /** Send the robot to its docking/charging station. */
59896
+ "sendToDock",
59897
+ /** Switch the on-board map and re-localise the robot. */
59898
+ "relocalize",
59899
+ /** Start a process — "clean zone A". */
59900
+ "startProcess",
59901
+ /** Pause, resume or stop a running process. */
59902
+ "pauseProcess",
59903
+ /** Know, rather than infer, when a process has finished. */
59904
+ "processCompletion",
59905
+ /** Report which floor the robot is on. */
59906
+ "floorReporting",
59907
+ /** Ride a lift between floors under our coordination. */
59908
+ "multiFloor"
59909
+ ];
59910
+ var TIER_REQUIREMENTS = {
59911
+ "full-control": [
59912
+ "readLocation",
59913
+ "navigateToPoint",
59914
+ "pauseResume",
59915
+ "pathAndEta",
59916
+ "battery",
59917
+ "sendToDock",
59918
+ "relocalize",
59919
+ "startProcess",
59920
+ "pauseProcess",
59921
+ "processCompletion"
59922
+ ],
59923
+ "traffic-light": [
59924
+ "readLocation",
59925
+ "pauseResume",
59926
+ "battery",
59927
+ "sendToDock",
59928
+ "startProcess",
59929
+ "pauseProcess",
59930
+ "processCompletion"
59931
+ ],
59932
+ "read-only": ["readLocation", "pathAndEta", "battery", "processCompletion"]
59933
+ };
59934
+ var has = (caps, c) => Object.prototype.hasOwnProperty.call(caps, c) && caps[c] === true;
59935
+ function robotTier(capabilities) {
59936
+ const caps = capabilities ?? {};
59937
+ for (const tier of ["full-control", "traffic-light", "read-only"]) {
59938
+ if (TIER_REQUIREMENTS[tier].every((c) => has(caps, c)))
59939
+ return tier;
59940
+ }
59941
+ return "no-interface";
59942
+ }
59943
+ function missingForTier(capabilities, tier) {
59944
+ const caps = capabilities ?? {};
59945
+ return TIER_REQUIREMENTS[tier].filter((c) => !has(caps, c));
59946
+ }
59947
+ function supports(capabilities, capability) {
59948
+ return has(capabilities ?? {}, capability);
59949
+ }
59950
+ function uncontrollableFleetWarning(tiers) {
59951
+ const count = tiers.filter(
59952
+ (t) => t === "read-only" || t === "no-interface"
59953
+ ).length;
59954
+ if (count <= 1) {
59955
+ return { ok: true, count, message: "" };
59956
+ }
59957
+ return {
59958
+ ok: false,
59959
+ count,
59960
+ message: `This site has ${count} robot fleets that cannot be controlled (read-only or no interface). A shared space supports at most one: beyond that, robots cannot reliably be kept out of each other's way.`
59961
+ };
59962
+ }
59963
+ var COMMAND_NEEDS = {
59964
+ full: "startProcess",
59965
+ area: "startProcess",
59966
+ spot: "startProcess",
59967
+ return: "sendToDock",
59968
+ pause: "pauseProcess",
59969
+ resume: "pauseProcess",
59970
+ cancel: "pauseProcess",
59971
+ navigate: "navigateToPoint",
59972
+ relocalize: "relocalize"
59973
+ };
59974
+ function commandSupported(capabilities, kind) {
59975
+ const needed = Object.prototype.hasOwnProperty.call(COMMAND_NEEDS, kind) ? COMMAND_NEEDS[kind] : void 0;
59976
+ if (!needed)
59977
+ return false;
59978
+ return supports(capabilities, needed);
59979
+ }
59980
+ var adapters = /* @__PURE__ */ new Map();
59981
+ function registerRobotAdapter(adapter) {
59982
+ if (!adapter?.brand)
59983
+ throw new Error("A robot adapter must name its brand.");
59984
+ adapters.set(adapter.brand, adapter);
59985
+ }
59986
+ function robotAdapterFor(brand) {
59987
+ if (typeof brand !== "string" || !brand)
59988
+ return null;
59989
+ return adapters.get(brand) ?? null;
59990
+ }
59991
+ function registeredRobotBrands() {
59992
+ return [...adapters.values()].map((a) => ({
59993
+ brand: a.brand,
59994
+ displayName: a.displayName,
59995
+ tier: robotTier(a.capabilities)
59996
+ }));
59997
+ }
59998
+ function clearRobotAdapters() {
59999
+ adapters.clear();
60000
+ }
60001
+
59853
60002
  // src/utils/lift-contract.util.ts
59854
60003
  var LIFT_DOOR = {
59855
60004
  CLOSED: 0,
@@ -59989,6 +60138,179 @@ function createLiftSimulator(options) {
59989
60138
  };
59990
60139
  }
59991
60140
 
60141
+ // src/utils/building-map.util.ts
60142
+ var WALL_HEIGHT_M = 2.5;
60143
+ var WALL_THICKNESS_M = 0.1;
60144
+ var DEFAULT_SCALE_M_PER_PX = 0.05;
60145
+ function scaleFromMeasurement(pixelDistance, realDistanceM) {
60146
+ if (!Number.isFinite(pixelDistance) || pixelDistance <= 0) {
60147
+ throw new Error("A measurement needs a positive pixel distance.");
60148
+ }
60149
+ if (!Number.isFinite(realDistanceM) || realDistanceM <= 0) {
60150
+ throw new Error("A measurement needs a positive real distance in metres.");
60151
+ }
60152
+ return realDistanceM / pixelDistance;
60153
+ }
60154
+ function vertexDistancePx(a, b) {
60155
+ return Math.hypot(b.x - a.x, b.y - a.y);
60156
+ }
60157
+ function levelScale(level) {
60158
+ const measurement = level.measurements?.find((m) => {
60159
+ const a = level.vertices[m.start];
60160
+ const b = level.vertices[m.end];
60161
+ return a && b && m.distanceM > 0 && vertexDistancePx(a, b) > 0;
60162
+ });
60163
+ if (measurement) {
60164
+ return scaleFromMeasurement(
60165
+ vertexDistancePx(level.vertices[measurement.start], level.vertices[measurement.end]),
60166
+ measurement.distanceM
60167
+ );
60168
+ }
60169
+ return level.scaleMPerPx > 0 ? level.scaleMPerPx : DEFAULT_SCALE_M_PER_PX;
60170
+ }
60171
+ function levelPixelToMetres(level, px, py, imageHeightPx) {
60172
+ const scale = levelScale(level);
60173
+ return {
60174
+ x: level.originXM + px * scale,
60175
+ y: level.originYM + (imageHeightPx - py) * scale
60176
+ };
60177
+ }
60178
+ function levelSolids(level, imageHeightPx) {
60179
+ const toM = (v) => levelPixelToMetres(level, v.x, v.y, imageHeightPx);
60180
+ const walls = [];
60181
+ for (const wall of level.walls ?? []) {
60182
+ const a = level.vertices[wall.start];
60183
+ const b = level.vertices[wall.end];
60184
+ if (!a || !b)
60185
+ continue;
60186
+ const pa = toM(a);
60187
+ const pb = toM(b);
60188
+ const lengthM = Math.hypot(pb.x - pa.x, pb.y - pa.y);
60189
+ if (lengthM <= 0)
60190
+ continue;
60191
+ const heightM = wall.heightM ?? WALL_HEIGHT_M;
60192
+ walls.push({
60193
+ centre: {
60194
+ x: (pa.x + pb.x) / 2,
60195
+ y: (pa.y + pb.y) / 2,
60196
+ // Boxes are centred, so a wall standing ON the floor sits at half its
60197
+ // own height above the level's elevation.
60198
+ z: level.elevationM + heightM / 2
60199
+ },
60200
+ lengthM,
60201
+ thicknessM: wall.thicknessM ?? WALL_THICKNESS_M,
60202
+ heightM,
60203
+ yaw: Math.atan2(pb.y - pa.y, pb.x - pa.x)
60204
+ });
60205
+ }
60206
+ const floors = [];
60207
+ for (const floor of level.floors ?? []) {
60208
+ const points = (floor.vertices ?? []).map((i) => level.vertices[i]).filter(Boolean).map(toM);
60209
+ if (points.length < 3)
60210
+ continue;
60211
+ floors.push({ points, elevationM: level.elevationM, hole: floor.hole === true });
60212
+ }
60213
+ return { level: level.name, elevationM: level.elevationM, walls, floors };
60214
+ }
60215
+ function buildingSolids(map, imageHeightFor) {
60216
+ return (map.levels ?? []).map((level) => levelSolids(level, imageHeightFor(level)));
60217
+ }
60218
+ function wallsFromOccupancyGrid(grid, options) {
60219
+ const threshold = options?.threshold ?? 100;
60220
+ const minRun = Math.max(2, options?.minRunPx ?? 6);
60221
+ const { data, width, height } = grid;
60222
+ if (!width || !height || !data || data.length < width * height) {
60223
+ return { vertices: [], walls: [] };
60224
+ }
60225
+ const occupied = (x, y) => Number(data[y * width + x]) <= threshold;
60226
+ const consumed = new Uint8Array(width * height);
60227
+ const vertices = [];
60228
+ const walls = [];
60229
+ const index = /* @__PURE__ */ new Map();
60230
+ const vertexAt = (x, y) => {
60231
+ const key = `${x}:${y}`;
60232
+ const found = index.get(key);
60233
+ if (found !== void 0)
60234
+ return found;
60235
+ vertices.push({ x, y, z: 0 });
60236
+ index.set(key, vertices.length - 1);
60237
+ return vertices.length - 1;
60238
+ };
60239
+ const addRun = (x1, y1, x2, y2) => {
60240
+ walls.push({ start: vertexAt(x1, y1), end: vertexAt(x2, y2) });
60241
+ };
60242
+ for (let y = 0; y < height; y++) {
60243
+ let run = 0;
60244
+ for (let x = 0; x <= width; x++) {
60245
+ const on = x < width && occupied(x, y);
60246
+ if (on) {
60247
+ run++;
60248
+ continue;
60249
+ }
60250
+ if (run >= minRun) {
60251
+ const startX = x - run;
60252
+ addRun(startX, y, x - 1, y);
60253
+ for (let i = startX; i < x; i++)
60254
+ consumed[y * width + i] = 1;
60255
+ }
60256
+ run = 0;
60257
+ }
60258
+ }
60259
+ for (let x = 0; x < width; x++) {
60260
+ let run = 0;
60261
+ for (let y = 0; y <= height; y++) {
60262
+ const on = y < height && occupied(x, y) && !consumed[y * width + x];
60263
+ if (on) {
60264
+ run++;
60265
+ continue;
60266
+ }
60267
+ if (run >= minRun) {
60268
+ addRun(x, y - run, x, y - 1);
60269
+ }
60270
+ run = 0;
60271
+ }
60272
+ }
60273
+ return { vertices, walls };
60274
+ }
60275
+ function buildingMapFromRobotMaps(name, maps, floorHeightM = 3) {
60276
+ const levels = [...maps ?? []].sort((a, b) => a.floorNo - b.floorNo).map((map) => {
60277
+ const vertices = [];
60278
+ if (map.baseX || map.baseY) {
60279
+ vertices.push({
60280
+ x: map.baseX ?? 0,
60281
+ y: map.baseY ?? 0,
60282
+ z: 0,
60283
+ name: `L${map.floorNo}_charger`,
60284
+ isCharger: true,
60285
+ theta: map.baseTheta ?? 0
60286
+ });
60287
+ }
60288
+ for (const area of map.areaList ?? []) {
60289
+ vertices.push({
60290
+ x: area.x,
60291
+ y: area.y,
60292
+ z: 0,
60293
+ name: area.areaName,
60294
+ isCleaningZone: true
60295
+ });
60296
+ }
60297
+ return {
60298
+ name: map.mapName?.trim() || `L${map.floorNo}`,
60299
+ elevationM: (map.floorNo - 1) * floorHeightM,
60300
+ drawing: { fileKey: map.backgroundFileKey },
60301
+ scaleMPerPx: map.resolution && map.resolution > 0 ? map.resolution : DEFAULT_SCALE_M_PER_PX,
60302
+ originXM: map.originX ?? 0,
60303
+ originYM: map.originY ?? 0,
60304
+ vertices,
60305
+ walls: [],
60306
+ floors: [],
60307
+ doors: [],
60308
+ measurements: []
60309
+ };
60310
+ });
60311
+ return { name, levels, lifts: [] };
60312
+ }
60313
+
59992
60314
  // src/services/robot-fleet.service.ts
59993
60315
  var import_node_server_utils157 = require("@7365admin1/node-server-utils");
59994
60316
 
@@ -60246,6 +60568,129 @@ function useRobotFleetService(deps) {
60246
60568
  };
60247
60569
  }
60248
60570
 
60571
+ // src/services/ounitech.adapter.ts
60572
+ var OUNITECH_BRAND = "ounitech";
60573
+ var OUNITECH_CAPABILITIES = {
60574
+ // The tracking stream carries x, y, theta.
60575
+ readLocation: true,
60576
+ // `/portal/robots/return-to-base`.
60577
+ sendToDock: true,
60578
+ // `start-full-cleaning`, `start-area-cleaning`, `start-spot-cleaning` — RMF's
60579
+ // "start a process (such as clean Zone_A)", and their areas are RMF's zones.
60580
+ startProcess: true,
60581
+ // Everything below is FALSE on purpose, and each one is a question already put
60582
+ // to the vendor. Their app can do most of it; the API cannot.
60583
+ navigateToPoint: false,
60584
+ // app: "Move to Target Point in standby mode"
60585
+ relocalize: false,
60586
+ // app: "Set Origin"
60587
+ pauseResume: false,
60588
+ // handlebar + app: Pause/Resume, long-press cancel
60589
+ pauseProcess: false,
60590
+ // app: "End work"
60591
+ battery: false,
60592
+ // nothing in the API carries it
60593
+ pathAndEta: false,
60594
+ // nothing in the API carries it
60595
+ processCompletion: false,
60596
+ // no task id is returned, so completion is inferred
60597
+ floorReporting: false,
60598
+ // the tracking stream has no floorNo
60599
+ multiFloor: false
60600
+ // their app does cross-floor; the API does not expose it
60601
+ };
60602
+ function createOunitechAdapter(credentials, options) {
60603
+ const cloud = useRobotCloudService(credentials, options);
60604
+ function toAdapterRobot(raw) {
60605
+ return {
60606
+ robotSn: raw.robotSn,
60607
+ name: raw.robotName,
60608
+ online: raw.onlineFlag !== false,
60609
+ mode: robotModeFromList(raw),
60610
+ statusName: raw.statusName,
60611
+ // Null, not 0 or a guess. A screen must be able to tell "we do not know"
60612
+ // from "empty", and `floorKnown: false` is what stops a robot being drawn
60613
+ // on a storey nobody confirmed.
60614
+ batteryPercent: null,
60615
+ floorNo: null,
60616
+ floorKnown: false,
60617
+ position: null
60618
+ };
60619
+ }
60620
+ return {
60621
+ brand: OUNITECH_BRAND,
60622
+ displayName: "Ounitech (Q3-G / Q3-W)",
60623
+ capabilities: OUNITECH_CAPABILITIES,
60624
+ async listRobots() {
60625
+ const fleet = await cloud.getRobots();
60626
+ return fleet.map(toAdapterRobot);
60627
+ },
60628
+ async getRobot(robotSn) {
60629
+ const fleet = await cloud.getRobots();
60630
+ const match = fleet.find((r) => r.robotSn === robotSn);
60631
+ if (!match) {
60632
+ return {
60633
+ robotSn,
60634
+ online: false,
60635
+ mode: "UNAVAILABLE",
60636
+ statusName: "Not found in the robot cloud",
60637
+ batteryPercent: null,
60638
+ floorNo: null,
60639
+ floorKnown: false,
60640
+ position: null
60641
+ };
60642
+ }
60643
+ return toAdapterRobot(match);
60644
+ },
60645
+ async sendCommand(robotSn, command) {
60646
+ const options2 = {
60647
+ cleanTimes: 1,
60648
+ modeFlag: 1,
60649
+ subModeFlag: 0,
60650
+ levelFlag: 1
60651
+ };
60652
+ switch (command.kind) {
60653
+ case "full":
60654
+ await cloud.startFullCleaning(robotSn, options2);
60655
+ return;
60656
+ case "area":
60657
+ await cloud.startAreaCleaning(robotSn, command.areas, options2);
60658
+ return;
60659
+ case "spot":
60660
+ await cloud.startSpotCleaning(robotSn, command.points, options2);
60661
+ return;
60662
+ case "return":
60663
+ await cloud.returnToBase(robotSn);
60664
+ return;
60665
+ default:
60666
+ throw new Error(
60667
+ `Ounitech robots do not support "${command.kind}" through the API yet.`
60668
+ );
60669
+ }
60670
+ },
60671
+ async getMaps(robotSn) {
60672
+ return cloud.getMaps(robotSn);
60673
+ },
60674
+ async getPlans(robotSn) {
60675
+ return cloud.getPlans(robotSn);
60676
+ }
60677
+ };
60678
+ }
60679
+ function registerOunitechBrand() {
60680
+ const unconfigured = () => {
60681
+ throw new Error(
60682
+ "No Ounitech credentials for this robot. Add the app key and token on the robot's record."
60683
+ );
60684
+ };
60685
+ registerRobotAdapter({
60686
+ brand: OUNITECH_BRAND,
60687
+ displayName: "Ounitech (Q3-G / Q3-W)",
60688
+ capabilities: OUNITECH_CAPABILITIES,
60689
+ listRobots: async () => unconfigured(),
60690
+ getRobot: async () => unconfigured()
60691
+ });
60692
+ }
60693
+
60249
60694
  // src/controllers/robot-fleet.controller.ts
60250
60695
  var import_joi76 = __toESM(require("joi"));
60251
60696
  var import_node_server_utils158 = require("@7365admin1/node-server-utils");
@@ -93683,6 +94128,7 @@ function usePersonalEmergencyChainController() {
93683
94128
  ConsoleAuditAction,
93684
94129
  ConsoleAuditTarget,
93685
94130
  DEFAULT_RING_SECONDS,
94131
+ DEFAULT_SCALE_M_PER_PX,
93686
94132
  DEFAULT_SITE_TIMEZONE,
93687
94133
  DEVICE_STATUS,
93688
94134
  DOBStatus,
@@ -93821,6 +94267,8 @@ function usePersonalEmergencyChainController() {
93821
94267
  ORG_OWNER_REPAIR_CLAIM_TAKEN_REASON,
93822
94268
  ORG_OWNER_REPAIR_DEFAULT,
93823
94269
  ORG_OWNER_REPAIR_OFF_REASON,
94270
+ OUNITECH_BRAND,
94271
+ OUNITECH_CAPABILITIES,
93824
94272
  OrgNature,
93825
94273
  OvernightParkingRequestSort,
93826
94274
  OvernightParkingRequestStatus,
@@ -93848,6 +94296,7 @@ function usePersonalEmergencyChainController() {
93848
94296
  REALTIME_MAX_FANOUT,
93849
94297
  REGISTERED_VEHICLE_CATEGORIES,
93850
94298
  ROBOT_APP_SCOPES,
94299
+ ROBOT_CAPABILITIES,
93851
94300
  ROBOT_CLOUD_DOC_CONFIRMATIONS,
93852
94301
  ROBOT_CLOUD_PATHS,
93853
94302
  ROBOT_MODULE,
@@ -93899,6 +94348,8 @@ function usePersonalEmergencyChainController() {
93899
94348
  VerificationType,
93900
94349
  VisitorSort,
93901
94350
  VisitorStatus,
94351
+ WALL_HEIGHT_M,
94352
+ WALL_THICKNESS_M,
93902
94353
  addressSchema,
93903
94354
  allowedFieldsSite,
93904
94355
  allowedNatures,
@@ -93910,6 +94361,8 @@ function usePersonalEmergencyChainController() {
93910
94361
  attendanceSchema,
93911
94362
  attendanceSettingsSchema,
93912
94363
  buildSelfServiceEmailContext,
94364
+ buildingMapFromRobotMaps,
94365
+ buildingSolids,
93913
94366
  building_level_namespace_collection,
93914
94367
  building_units_namespace_collection,
93915
94368
  buildings_namespace_collection,
@@ -93936,13 +94389,16 @@ function usePersonalEmergencyChainController() {
93936
94389
  chatSchema,
93937
94390
  claimReadsAsStale,
93938
94391
  clampPtzSpeed,
94392
+ clearRobotAdapters,
93939
94393
  clockDriftSeconds,
94394
+ commandSupported,
93940
94395
  consoleRoleAllows,
93941
94396
  consoleRoleAllowsAll,
93942
94397
  consoleSpellings,
93943
94398
  console_audit_namespace_collection,
93944
94399
  createLiftSimulator,
93945
94400
  createManpowerRemarksDaily,
94401
+ createOunitechAdapter,
93946
94402
  createSseParser,
93947
94403
  customerSchema,
93948
94404
  customerSitePropertyFields,
@@ -94013,6 +94469,9 @@ function usePersonalEmergencyChainController() {
94013
94469
  isTermsCurrent,
94014
94470
  isTrackingStale,
94015
94471
  isValidTimezone,
94472
+ levelPixelToMetres,
94473
+ levelScale,
94474
+ levelSolids,
94016
94475
  listStaffUserIds,
94017
94476
  liveStatusFor,
94018
94477
  manpowerDesignationsSchema,
@@ -94024,6 +94483,7 @@ function usePersonalEmergencyChainController() {
94024
94483
  maskNric,
94025
94484
  memberModulesHandlers,
94026
94485
  metresToPixel,
94486
+ missingForTier,
94027
94487
  moduleDeniedForCaller,
94028
94488
  moduleGateEnforceKeys,
94029
94489
  moduleGateMode,
@@ -94078,6 +94538,9 @@ function usePersonalEmergencyChainController() {
94078
94538
  recordConsoleAction,
94079
94539
  refuseServiceProviderInviteAction,
94080
94540
  registerCameraTransport,
94541
+ registerOunitechBrand,
94542
+ registerRobotAdapter,
94543
+ registeredRobotBrands,
94081
94544
  relayForRecorder,
94082
94545
  remarksSchema,
94083
94546
  renderPagePdf,
@@ -94095,6 +94558,7 @@ function usePersonalEmergencyChainController() {
94095
94558
  resolveSiteTimezone,
94096
94559
  resolveStaffRole,
94097
94560
  robotActionsFor,
94561
+ robotAdapterFor,
94098
94562
  robotCloudBody,
94099
94563
  robotCloudSign,
94100
94564
  robotCloudSigningString,
@@ -94103,8 +94567,10 @@ function usePersonalEmergencyChainController() {
94103
94567
  robotModeFromList,
94104
94568
  robotSchema,
94105
94569
  robotScopeForApp,
94570
+ robotTier,
94106
94571
  robotTypesVisibleTo,
94107
94572
  rtspUrl,
94573
+ scaleFromMeasurement,
94108
94574
  schema,
94109
94575
  schemaAppSlugNotification,
94110
94576
  schemaApprovedBy,
@@ -94276,7 +94742,9 @@ function usePersonalEmergencyChainController() {
94276
94742
  stripFacialImageMetadata,
94277
94743
  subscriptionPlanSchema,
94278
94744
  summariseBulkCameraPlan,
94745
+ supports,
94279
94746
  trackingBackoffMs,
94747
+ uncontrollableFleetWarning,
94280
94748
  updateRemarksStatusEod,
94281
94749
  updateRemarksisAcknowledged,
94282
94750
  updateSiteSchema,
@@ -94520,8 +94988,10 @@ function usePersonalEmergencyChainController() {
94520
94988
  vehicleSchema,
94521
94989
  vehicleViewOf,
94522
94990
  vehicles_namespace_collection,
94991
+ vertexDistancePx,
94523
94992
  visitors_namespace_collection,
94524
94993
  wallConfig,
94994
+ wallsFromOccupancyGrid,
94525
94995
  withLiveLink,
94526
94996
  workOrderSchema,
94527
94997
  work_orders2_namespace_collection,