myio-js-library 0.1.4 → 0.1.6

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/README.md CHANGED
@@ -289,6 +289,45 @@ strings.normalizeRecipients('a; b, c'); // "a,b,c"
289
289
 
290
290
  ### Number Utilities
291
291
 
292
+ #### `formatNumberReadable(value: unknown, locale?: string, minimumFractionDigits?: number, maximumFractionDigits?: number): string`
293
+
294
+ Formats numbers for Brazilian locale with robust input handling. Safely handles strings, numbers, and other types with sensible defaults.
295
+
296
+ **Parameters:**
297
+ - `value: unknown` - Value to format (number, string, or any other type)
298
+ - `locale?: string` - Locale string (default: 'pt-BR')
299
+ - `minimumFractionDigits?: number` - Minimum decimal places (default: 2)
300
+ - `maximumFractionDigits?: number` - Maximum decimal places (default: 2)
301
+
302
+ **Features:**
303
+ - Handles string inputs with comma decimal separators (e.g., "12,34" → 12.34)
304
+ - Normalizes -0 to 0
305
+ - Returns "-" for null, undefined, NaN, or invalid inputs
306
+ - Configurable locale and decimal places
307
+
308
+ ```javascript
309
+ import { formatNumberReadable } from 'myio-js-library';
310
+
311
+ // Basic usage
312
+ formatNumberReadable(1234.56); // "1.234,56"
313
+ formatNumberReadable(1000); // "1.000,00"
314
+ formatNumberReadable(12.3); // "12,30"
315
+
316
+ // String inputs (handles comma separators)
317
+ formatNumberReadable("1234,56"); // "1.234,56"
318
+ formatNumberReadable("12.34"); // "12,34"
319
+
320
+ // Invalid inputs
321
+ formatNumberReadable(null); // "-"
322
+ formatNumberReadable(NaN); // "-"
323
+ formatNumberReadable("invalid"); // "-"
324
+
325
+ // Custom locale and precision
326
+ formatNumberReadable(1234.56, 'en-US'); // "1,234.56"
327
+ formatNumberReadable(1234.56, 'pt-BR', 0, 0); // "1.235"
328
+ formatNumberReadable(1234.56, 'pt-BR', 1, 3); // "1.234,560"
329
+ ```
330
+
292
331
  #### `numbers.fmtPerc(x: number, digits?: number): string`
293
332
 
294
333
  Formats a ratio (0-1) as a percentage string.
@@ -315,16 +354,34 @@ numbers.toFixedSafe(Infinity); // "—"
315
354
 
316
355
  ### Energy Formatting Utilities
317
356
 
318
- #### `formatEnergy(value: number, unit: string): string`
357
+ #### `formatEnergy(value: number, unit?: string): string`
319
358
 
320
- Formats energy values with Brazilian locale formatting and appropriate units.
359
+ Formats energy values with Brazilian locale formatting and appropriate units. If no unit is provided, automatically selects the most appropriate unit based on the value magnitude.
360
+
361
+ **Parameters:**
362
+ - `value: number` - The energy value to format
363
+ - `unit?: string` - Optional unit ('kWh', 'MWh', 'GWh'). If not provided, automatically determined based on value
364
+
365
+ **Auto-unit selection:**
366
+ - Values ≥ 1,000,000: Converts to GWh
367
+ - Values ≥ 1,000: Converts to MWh
368
+ - Values < 1,000: Uses kWh
321
369
 
322
370
  ```javascript
323
371
  import { formatEnergy } from 'myio-js-library';
324
372
 
373
+ // With explicit unit
325
374
  formatEnergy(1234.56, 'kWh'); // "1.234,56 kWh"
326
375
  formatEnergy(1000, 'MWh'); // "1.000,00 MWh"
327
- formatEnergy(null, 'kWh'); // "-"
376
+
377
+ // Auto-unit selection
378
+ formatEnergy(500); // "500,00 kWh"
379
+ formatEnergy(1500); // "1,50 MWh"
380
+ formatEnergy(2500000); // "2,50 GWh"
381
+
382
+ // Invalid values
383
+ formatEnergy(null); // "-"
384
+ formatEnergy(NaN); // "-"
328
385
  ```
329
386
 
330
387
  #### `formatAllInSameUnit(values: Array<{value: number, unit: string}>, targetUnit: string): string[]`
@@ -770,6 +827,83 @@ const legacyData = [
770
827
  findValue(legacyData, 'sensor1', 'temperature'); // 25
771
828
  ```
772
829
 
830
+ ### ThingsBoard Utilities
831
+
832
+ #### `getEntityInfoAndAttributesTB(deviceId: string, opts: TBFetchOptions): Promise<TBEntityInfo>`
833
+
834
+ Fetches a ThingsBoard device and its `SERVER_SCOPE` attributes with one call. Provides robust parsing and sensible defaults for direct UI use.
835
+
836
+ **Parameters:**
837
+ - `deviceId: string` - The ThingsBoard device ID
838
+ - `opts: TBFetchOptions` - Configuration options:
839
+ - `jwt: string` - Bearer token for authentication (required)
840
+ - `baseUrl?: string` - ThingsBoard base URL (default: '', supports relative or absolute URLs)
841
+ - `scope?: string` - Attribute scope (default: 'SERVER_SCOPE')
842
+ - `attributeKeys?: string[]` - Specific attribute keys to fetch (optional)
843
+ - `fetcher?: typeof fetch` - Custom fetch implementation (default: globalThis.fetch)
844
+
845
+ **Returns:** Promise resolving to `TBEntityInfo` object with:
846
+ - `label: string` - Device label (fallback to name or 'Sem etiqueta')
847
+ - `andar: string` - Floor information from 'floor' attribute
848
+ - `numeroLoja: string` - Store number from 'NumLoja' attribute
849
+ - `identificadorMedidor: string` - Meter ID from 'IDMedidor' attribute
850
+ - `identificadorDispositivo: string` - Device ID from 'deviceId' attribute
851
+ - `guid: string` - GUID from 'guid' attribute
852
+ - `consumoDiario: number` - Daily consumption from 'maxDailyConsumption' attribute
853
+ - `consumoMadrugada: number` - Night consumption from 'maxNightConsumption' attribute
854
+
855
+ **UMD Usage (ThingsBoard widgets):**
856
+ ```html
857
+ <script src="https://unpkg.com/myio-js-library@0.1.4/dist/myio-js-library.umd.min.js"></script>
858
+ <script>
859
+ (async () => {
860
+ const { getEntityInfoAndAttributesTB } = MyIOLibrary;
861
+ const jwt = localStorage.getItem('jwt_token'); // or your JWT source
862
+ const deviceId = 'YOUR_DEVICE_ID';
863
+
864
+ try {
865
+ const info = await getEntityInfoAndAttributesTB(deviceId, { jwt });
866
+ console.log('TB entity info:', info);
867
+ // {
868
+ // label: 'My Device',
869
+ // andar: '1',
870
+ // numeroLoja: 'A-12',
871
+ // identificadorMedidor: 'ID-123',
872
+ // identificadorDispositivo: 'DEV-456',
873
+ // guid: '...',
874
+ // consumoDiario: 10.5,
875
+ // consumoMadrugada: 1.2
876
+ // }
877
+ } catch (error) {
878
+ console.error('Failed to fetch device info:', error);
879
+ }
880
+ })();
881
+ </script>
882
+ ```
883
+
884
+ **ESM Usage:**
885
+ ```javascript
886
+ import { getEntityInfoAndAttributesTB } from 'myio-js-library';
887
+
888
+ const info = await getEntityInfoAndAttributesTB('DEVICE_ID', {
889
+ jwt: process.env.TB_JWT!,
890
+ baseUrl: 'https://thingsboard.example.com'
891
+ });
892
+ ```
893
+
894
+ **Error Handling:**
895
+ The function throws meaningful errors for:
896
+ - Missing `deviceId` parameter
897
+ - Missing `jwt` token
898
+ - HTTP failures (device not found, authentication issues, etc.)
899
+ - Missing fetch implementation
900
+
901
+ **Robust Parsing:**
902
+ - Numbers are safely coerced from strings (handles comma decimal separators)
903
+ - Invalid numbers default to 0
904
+ - Missing string attributes default to empty string
905
+ - Handles null/undefined values gracefully
906
+
773
907
  ## 🧪 Development
774
908
 
775
909
  ```bash
package/dist/index.cjs CHANGED
@@ -45,10 +45,12 @@ __export(index_exports, {
45
45
  formatDateWithTimezoneOffset: () => formatDateWithTimezoneOffset,
46
46
  formatEnergy: () => formatEnergy,
47
47
  formatEnergyByGroup: () => formatEnergyByGroup,
48
+ formatNumberReadable: () => formatNumberReadable,
48
49
  formatTankHeadFromCm: () => formatTankHeadFromCm,
49
50
  formatWaterVolumeM3: () => formatWaterVolumeM3,
50
51
  getAvailableContexts: () => getAvailableContexts,
51
52
  getDateRangeArray: () => getDateRangeArray,
53
+ getEntityInfoAndAttributesTB: () => getEntityInfoAndAttributesTB,
52
54
  getSaoPauloISOString: () => getSaoPauloISOString,
53
55
  getSaoPauloISOStringFixed: () => getSaoPauloISOStringFixed,
54
56
  getValueByDatakey: () => getValueByDatakey,
@@ -72,11 +74,24 @@ function formatEnergy(value, unit) {
72
74
  if (value === null || value === void 0 || isNaN(value)) {
73
75
  return "-";
74
76
  }
75
- const formattedValue = value.toLocaleString("pt-BR", {
77
+ let adjustedValue = value;
78
+ let adjustedUnit = unit;
79
+ if (!adjustedUnit) {
80
+ if (value >= 1e6) {
81
+ adjustedValue = value / 1e6;
82
+ adjustedUnit = "GWh";
83
+ } else if (value >= 1e3) {
84
+ adjustedValue = value / 1e3;
85
+ adjustedUnit = "MWh";
86
+ } else {
87
+ adjustedUnit = "kWh";
88
+ }
89
+ }
90
+ const formattedValue = adjustedValue.toLocaleString("pt-BR", {
76
91
  minimumFractionDigits: 2,
77
92
  maximumFractionDigits: 2
78
93
  });
79
- return `${formattedValue} ${unit}`;
94
+ return `${formattedValue} ${adjustedUnit}`;
80
95
  }
81
96
  function formatAllInSameUnit(values, targetUnit) {
82
97
  const unitMultipliers = {
@@ -107,6 +122,19 @@ function fmtPerc(value) {
107
122
  }) + "%";
108
123
  }
109
124
 
125
+ // src/format/numbers.ts
126
+ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2, maximumFractionDigits = 2) {
127
+ const n = typeof value === "string" ? Number(value.replace(",", ".")) : Number(value);
128
+ if (!Number.isFinite(n)) {
129
+ return "-";
130
+ }
131
+ const safe = Object.is(n, -0) ? 0 : n;
132
+ return safe.toLocaleString(locale, {
133
+ minimumFractionDigits,
134
+ maximumFractionDigits
135
+ });
136
+ }
137
+
110
138
  // src/format/water.ts
111
139
  function formatWaterVolumeM3(value, locale = "pt-BR") {
112
140
  if (value === null || value === void 0 || isNaN(value)) {
@@ -548,15 +576,6 @@ function toCSV(rows, delimiter = ";") {
548
576
  }).join(delimiter)
549
577
  ).join("\n");
550
578
  }
551
- function formatNumberReadable(value) {
552
- if (value == null || isNaN(value)) {
553
- return "-";
554
- }
555
- return value.toLocaleString("pt-BR", {
556
- minimumFractionDigits: 2,
557
- maximumFractionDigits: 2
558
- });
559
- }
560
579
 
561
580
  // src/classify/energyEntity.ts
562
581
  function classify(entity, criteria) {
@@ -706,6 +725,69 @@ function findValue(data, keyOrPath, legacyDataKey) {
706
725
  return getValueByDatakey(data, keyOrPath);
707
726
  }
708
727
 
728
+ // src/thingsboard/entity.ts
729
+ async function getEntityInfoAndAttributesTB(deviceId, opts) {
730
+ if (!deviceId) throw new Error("getEntityInfoAndAttributesTB: deviceId is required");
731
+ const {
732
+ jwt,
733
+ baseUrl = "",
734
+ scope = "SERVER_SCOPE",
735
+ attributeKeys = [
736
+ "floor",
737
+ "NumLoja",
738
+ "IDMedidor",
739
+ "deviceId",
740
+ "guid",
741
+ "maxDailyConsumption",
742
+ "maxNightConsumption"
743
+ ],
744
+ fetcher = globalThis.fetch?.bind(globalThis)
745
+ } = opts || {};
746
+ if (!jwt) throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");
747
+ if (!fetcher) throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");
748
+ const headers = {
749
+ "Content-Type": "application/json",
750
+ "X-Authorization": `Bearer ${jwt}`
751
+ };
752
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
753
+ const deviceRes = await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`, { headers });
754
+ if (!deviceRes.ok) {
755
+ throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`);
756
+ }
757
+ const device = await deviceRes.json();
758
+ const label = device?.label || device?.name || "Sem etiqueta";
759
+ const attrUrl = `${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;
760
+ const attrRes = await fetcher(attrUrl, { headers });
761
+ if (!attrRes.ok) {
762
+ throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`);
763
+ }
764
+ const attributes = await attrRes.json();
765
+ const map = /* @__PURE__ */ new Map();
766
+ for (const a of attributes) map.set(a.key, a.value);
767
+ const getStr = (k) => {
768
+ const v = map.get(k);
769
+ if (v == null) return "";
770
+ return typeof v === "string" ? v : String(v);
771
+ };
772
+ const getNum = (k) => {
773
+ const v = map.get(k);
774
+ if (v == null) return 0;
775
+ const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
776
+ return Number.isFinite(n) ? n : 0;
777
+ };
778
+ void attributeKeys;
779
+ return {
780
+ label,
781
+ andar: getStr("floor") || "",
782
+ numeroLoja: getStr("NumLoja") || "",
783
+ identificadorMedidor: getStr("IDMedidor") || "",
784
+ identificadorDispositivo: getStr("deviceId") || "",
785
+ guid: getStr("guid") || "",
786
+ consumoDiario: getNum("maxDailyConsumption"),
787
+ consumoMadrugada: getNum("maxNightConsumption")
788
+ };
789
+ }
790
+
709
791
  // src/utils/deviceType.js
710
792
  var contexts = {
711
793
  building: (name) => {
@@ -953,10 +1035,12 @@ function isRetryableNetworkError(err) {
953
1035
  formatDateWithTimezoneOffset,
954
1036
  formatEnergy,
955
1037
  formatEnergyByGroup,
1038
+ formatNumberReadable,
956
1039
  formatTankHeadFromCm,
957
1040
  formatWaterVolumeM3,
958
1041
  getAvailableContexts,
959
1042
  getDateRangeArray,
1043
+ getEntityInfoAndAttributesTB,
960
1044
  getSaoPauloISOString,
961
1045
  getSaoPauloISOStringFixed,
962
1046
  getValueByDatakey,
package/dist/index.d.cts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Formats energy values with appropriate units (kWh, MWh, GWh) using Brazilian locale formatting
3
3
  * @param value - The energy value to format
4
- * @param unit - The unit of the energy value ('kWh', 'MWh', 'GWh')
4
+ * @param unit - Optional unit of the energy value ('kWh', 'MWh', 'GWh')
5
5
  * @returns Formatted energy string with Brazilian locale number formatting
6
6
  */
7
- declare function formatEnergy(value: number, unit: string): string;
7
+ declare function formatEnergy(value: number, unit?: string): string;
8
8
  /**
9
9
  * Formats all energy values to the same unit for consistent display
10
10
  * @param values - Array of energy values with their units
@@ -23,6 +23,16 @@ declare function formatAllInSameUnit(values: Array<{
23
23
  */
24
24
  declare function fmtPerc$1(value: number): string;
25
25
 
26
+ /**
27
+ * Formats numbers for Brazilian locale with robust input handling
28
+ * @param value - Value to format (number, string, or any other type)
29
+ * @param locale - Locale string (default: 'pt-BR')
30
+ * @param minimumFractionDigits - Minimum decimal places (default: 2)
31
+ * @param maximumFractionDigits - Maximum decimal places (default: 2)
32
+ * @returns Formatted number string with locale formatting, or '-' for invalid inputs
33
+ */
34
+ declare function formatNumberReadable(value: unknown, locale?: string, minimumFractionDigits?: number, maximumFractionDigits?: number): string;
35
+
26
36
  /**
27
37
  * Formats water volume in cubic meters (M³) using Brazilian locale formatting
28
38
  * @param value - The water volume value in cubic meters
@@ -298,6 +308,37 @@ declare function getValueByDatakeyLegacy(dataList: any[], dataSourceNameTarget:
298
308
  */
299
309
  declare function findValue(data: any, keyOrPath: string, legacyDataKey?: string): any;
300
310
 
311
+ /**
312
+ * ThingsBoard entity and attributes fetching utilities
313
+ */
314
+ interface TBFetchOptions {
315
+ jwt: string;
316
+ baseUrl?: string;
317
+ scope?: 'SERVER_SCOPE' | 'CLIENT_SCOPE' | 'SHARED_SCOPE';
318
+ attributeKeys?: string[];
319
+ fetcher?: typeof fetch;
320
+ }
321
+ interface TBEntityInfo {
322
+ label: string;
323
+ andar: string;
324
+ numeroLoja: string;
325
+ identificadorMedidor: string;
326
+ identificadorDispositivo: string;
327
+ guid: string;
328
+ consumoDiario: number;
329
+ consumoMadrugada: number;
330
+ }
331
+ /**
332
+ * Fetches ThingsBoard Device info + attributes (default: SERVER_SCOPE).
333
+ * Safe defaults and robust coercion, suitable for direct UI use.
334
+ *
335
+ * @param deviceId - The ThingsBoard device ID
336
+ * @param opts - Configuration options including JWT token and API settings
337
+ * @returns Promise resolving to device info and attributes
338
+ * @throws Error if deviceId is missing, JWT is missing, or HTTP requests fail
339
+ */
340
+ declare function getEntityInfoAndAttributesTB(deviceId: string, opts: TBFetchOptions): Promise<TBEntityInfo>;
341
+
301
342
  /**
302
343
  * Detects the device type based on the given name and context.
303
344
  * Uses the specified detection context to identify device types.
@@ -392,4 +433,4 @@ declare function fetchWithRetry(url: any, options?: {}): Promise<any>;
392
433
  */
393
434
  declare function http(url: any, options?: {}): Promise<any>;
394
435
 
395
- export { type StoreRow, type TimedValue, type WaterRow, addDetectionContext, addNamespace, averageByDay, buildWaterReportCSV, buildWaterStoresCSV, calcDeltaPercent, classify, classifyWaterLabel, classifyWaterLabels, decodePayload, decodePayloadBase64Xor, detectDeviceType, determineInterval, exportToCSV, exportToCSVAll, fetchWithRetry, findValue, fmtPerc$1 as fmtPerc, fmtPerc as fmtPercLegacy, formatAllInSameUnit, formatAllInSameWaterUnit, formatDateForInput, formatDateToYMD, formatDateWithTimezoneOffset, formatEnergy, formatEnergyByGroup, formatTankHeadFromCm, formatWaterVolumeM3, getAvailableContexts, getDateRangeArray, getSaoPauloISOString, getSaoPauloISOStringFixed, getValueByDatakey, getValueByDatakeyLegacy, getWaterCategories, groupByDay, http, isWaterCategory, normalizeRecipients, numbers, parseInputDateToDate, strings, timeWindowFromInputYMD, toCSV, toFixedSafe };
436
+ export { type StoreRow, type TBEntityInfo, type TBFetchOptions, type TimedValue, type WaterRow, addDetectionContext, addNamespace, averageByDay, buildWaterReportCSV, buildWaterStoresCSV, calcDeltaPercent, classify, classifyWaterLabel, classifyWaterLabels, decodePayload, decodePayloadBase64Xor, detectDeviceType, determineInterval, exportToCSV, exportToCSVAll, fetchWithRetry, findValue, fmtPerc$1 as fmtPerc, fmtPerc as fmtPercLegacy, formatAllInSameUnit, formatAllInSameWaterUnit, formatDateForInput, formatDateToYMD, formatDateWithTimezoneOffset, formatEnergy, formatEnergyByGroup, formatNumberReadable, formatTankHeadFromCm, formatWaterVolumeM3, getAvailableContexts, getDateRangeArray, getEntityInfoAndAttributesTB, getSaoPauloISOString, getSaoPauloISOStringFixed, getValueByDatakey, getValueByDatakeyLegacy, getWaterCategories, groupByDay, http, isWaterCategory, normalizeRecipients, numbers, parseInputDateToDate, strings, timeWindowFromInputYMD, toCSV, toFixedSafe };
package/dist/index.js CHANGED
@@ -9,11 +9,24 @@ function formatEnergy(value, unit) {
9
9
  if (value === null || value === void 0 || isNaN(value)) {
10
10
  return "-";
11
11
  }
12
- const formattedValue = value.toLocaleString("pt-BR", {
12
+ let adjustedValue = value;
13
+ let adjustedUnit = unit;
14
+ if (!adjustedUnit) {
15
+ if (value >= 1e6) {
16
+ adjustedValue = value / 1e6;
17
+ adjustedUnit = "GWh";
18
+ } else if (value >= 1e3) {
19
+ adjustedValue = value / 1e3;
20
+ adjustedUnit = "MWh";
21
+ } else {
22
+ adjustedUnit = "kWh";
23
+ }
24
+ }
25
+ const formattedValue = adjustedValue.toLocaleString("pt-BR", {
13
26
  minimumFractionDigits: 2,
14
27
  maximumFractionDigits: 2
15
28
  });
16
- return `${formattedValue} ${unit}`;
29
+ return `${formattedValue} ${adjustedUnit}`;
17
30
  }
18
31
  function formatAllInSameUnit(values, targetUnit) {
19
32
  const unitMultipliers = {
@@ -44,6 +57,19 @@ function fmtPerc(value) {
44
57
  }) + "%";
45
58
  }
46
59
 
60
+ // src/format/numbers.ts
61
+ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2, maximumFractionDigits = 2) {
62
+ const n = typeof value === "string" ? Number(value.replace(",", ".")) : Number(value);
63
+ if (!Number.isFinite(n)) {
64
+ return "-";
65
+ }
66
+ const safe = Object.is(n, -0) ? 0 : n;
67
+ return safe.toLocaleString(locale, {
68
+ minimumFractionDigits,
69
+ maximumFractionDigits
70
+ });
71
+ }
72
+
47
73
  // src/format/water.ts
48
74
  function formatWaterVolumeM3(value, locale = "pt-BR") {
49
75
  if (value === null || value === void 0 || isNaN(value)) {
@@ -485,15 +511,6 @@ function toCSV(rows, delimiter = ";") {
485
511
  }).join(delimiter)
486
512
  ).join("\n");
487
513
  }
488
- function formatNumberReadable(value) {
489
- if (value == null || isNaN(value)) {
490
- return "-";
491
- }
492
- return value.toLocaleString("pt-BR", {
493
- minimumFractionDigits: 2,
494
- maximumFractionDigits: 2
495
- });
496
- }
497
514
 
498
515
  // src/classify/energyEntity.ts
499
516
  function classify(entity, criteria) {
@@ -643,6 +660,69 @@ function findValue(data, keyOrPath, legacyDataKey) {
643
660
  return getValueByDatakey(data, keyOrPath);
644
661
  }
645
662
 
663
+ // src/thingsboard/entity.ts
664
+ async function getEntityInfoAndAttributesTB(deviceId, opts) {
665
+ if (!deviceId) throw new Error("getEntityInfoAndAttributesTB: deviceId is required");
666
+ const {
667
+ jwt,
668
+ baseUrl = "",
669
+ scope = "SERVER_SCOPE",
670
+ attributeKeys = [
671
+ "floor",
672
+ "NumLoja",
673
+ "IDMedidor",
674
+ "deviceId",
675
+ "guid",
676
+ "maxDailyConsumption",
677
+ "maxNightConsumption"
678
+ ],
679
+ fetcher = globalThis.fetch?.bind(globalThis)
680
+ } = opts || {};
681
+ if (!jwt) throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");
682
+ if (!fetcher) throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");
683
+ const headers = {
684
+ "Content-Type": "application/json",
685
+ "X-Authorization": `Bearer ${jwt}`
686
+ };
687
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
688
+ const deviceRes = await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`, { headers });
689
+ if (!deviceRes.ok) {
690
+ throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`);
691
+ }
692
+ const device = await deviceRes.json();
693
+ const label = device?.label || device?.name || "Sem etiqueta";
694
+ const attrUrl = `${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;
695
+ const attrRes = await fetcher(attrUrl, { headers });
696
+ if (!attrRes.ok) {
697
+ throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`);
698
+ }
699
+ const attributes = await attrRes.json();
700
+ const map = /* @__PURE__ */ new Map();
701
+ for (const a of attributes) map.set(a.key, a.value);
702
+ const getStr = (k) => {
703
+ const v = map.get(k);
704
+ if (v == null) return "";
705
+ return typeof v === "string" ? v : String(v);
706
+ };
707
+ const getNum = (k) => {
708
+ const v = map.get(k);
709
+ if (v == null) return 0;
710
+ const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
711
+ return Number.isFinite(n) ? n : 0;
712
+ };
713
+ void attributeKeys;
714
+ return {
715
+ label,
716
+ andar: getStr("floor") || "",
717
+ numeroLoja: getStr("NumLoja") || "",
718
+ identificadorMedidor: getStr("IDMedidor") || "",
719
+ identificadorDispositivo: getStr("deviceId") || "",
720
+ guid: getStr("guid") || "",
721
+ consumoDiario: getNum("maxDailyConsumption"),
722
+ consumoMadrugada: getNum("maxNightConsumption")
723
+ };
724
+ }
725
+
646
726
  // src/utils/deviceType.js
647
727
  var contexts = {
648
728
  building: (name) => {
@@ -889,10 +969,12 @@ export {
889
969
  formatDateWithTimezoneOffset,
890
970
  formatEnergy,
891
971
  formatEnergyByGroup,
972
+ formatNumberReadable,
892
973
  formatTankHeadFromCm,
893
974
  formatWaterVolumeM3,
894
975
  getAvailableContexts,
895
976
  getDateRangeArray,
977
+ getEntityInfoAndAttributesTB,
896
978
  getSaoPauloISOString,
897
979
  getSaoPauloISOStringFixed,
898
980
  getValueByDatakey,
@@ -15,11 +15,24 @@
15
15
  if (value === null || value === void 0 || isNaN(value)) {
16
16
  return "-";
17
17
  }
18
- const formattedValue = value.toLocaleString("pt-BR", {
18
+ let adjustedValue = value;
19
+ let adjustedUnit = unit;
20
+ if (!adjustedUnit) {
21
+ if (value >= 1e6) {
22
+ adjustedValue = value / 1e6;
23
+ adjustedUnit = "GWh";
24
+ } else if (value >= 1e3) {
25
+ adjustedValue = value / 1e3;
26
+ adjustedUnit = "MWh";
27
+ } else {
28
+ adjustedUnit = "kWh";
29
+ }
30
+ }
31
+ const formattedValue = adjustedValue.toLocaleString("pt-BR", {
19
32
  minimumFractionDigits: 2,
20
33
  maximumFractionDigits: 2
21
34
  });
22
- return `${formattedValue} ${unit}`;
35
+ return `${formattedValue} ${adjustedUnit}`;
23
36
  }
24
37
  function formatAllInSameUnit(values, targetUnit) {
25
38
  const unitMultipliers = {
@@ -50,6 +63,19 @@
50
63
  }) + "%";
51
64
  }
52
65
 
66
+ // src/format/numbers.ts
67
+ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2, maximumFractionDigits = 2) {
68
+ const n = typeof value === "string" ? Number(value.replace(",", ".")) : Number(value);
69
+ if (!Number.isFinite(n)) {
70
+ return "-";
71
+ }
72
+ const safe = Object.is(n, -0) ? 0 : n;
73
+ return safe.toLocaleString(locale, {
74
+ minimumFractionDigits,
75
+ maximumFractionDigits
76
+ });
77
+ }
78
+
53
79
  // src/format/water.ts
54
80
  function formatWaterVolumeM3(value, locale = "pt-BR") {
55
81
  if (value === null || value === void 0 || isNaN(value)) {
@@ -491,15 +517,6 @@
491
517
  }).join(delimiter)
492
518
  ).join("\n");
493
519
  }
494
- function formatNumberReadable(value) {
495
- if (value == null || isNaN(value)) {
496
- return "-";
497
- }
498
- return value.toLocaleString("pt-BR", {
499
- minimumFractionDigits: 2,
500
- maximumFractionDigits: 2
501
- });
502
- }
503
520
 
504
521
  // src/classify/energyEntity.ts
505
522
  function classify(entity, criteria) {
@@ -649,6 +666,68 @@
649
666
  return getValueByDatakey(data, keyOrPath);
650
667
  }
651
668
 
669
+ // src/thingsboard/entity.ts
670
+ async function getEntityInfoAndAttributesTB(deviceId, opts) {
671
+ if (!deviceId) throw new Error("getEntityInfoAndAttributesTB: deviceId is required");
672
+ const {
673
+ jwt,
674
+ baseUrl = "",
675
+ scope = "SERVER_SCOPE",
676
+ attributeKeys = [
677
+ "floor",
678
+ "NumLoja",
679
+ "IDMedidor",
680
+ "deviceId",
681
+ "guid",
682
+ "maxDailyConsumption",
683
+ "maxNightConsumption"
684
+ ],
685
+ fetcher = globalThis.fetch?.bind(globalThis)
686
+ } = opts || {};
687
+ if (!jwt) throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");
688
+ if (!fetcher) throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");
689
+ const headers = {
690
+ "Content-Type": "application/json",
691
+ "X-Authorization": `Bearer ${jwt}`
692
+ };
693
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
694
+ const deviceRes = await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`, { headers });
695
+ if (!deviceRes.ok) {
696
+ throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`);
697
+ }
698
+ const device = await deviceRes.json();
699
+ const label = device?.label || device?.name || "Sem etiqueta";
700
+ const attrUrl = `${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;
701
+ const attrRes = await fetcher(attrUrl, { headers });
702
+ if (!attrRes.ok) {
703
+ throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`);
704
+ }
705
+ const attributes = await attrRes.json();
706
+ const map = /* @__PURE__ */ new Map();
707
+ for (const a of attributes) map.set(a.key, a.value);
708
+ const getStr = (k) => {
709
+ const v = map.get(k);
710
+ if (v == null) return "";
711
+ return typeof v === "string" ? v : String(v);
712
+ };
713
+ const getNum = (k) => {
714
+ const v = map.get(k);
715
+ if (v == null) return 0;
716
+ const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
717
+ return Number.isFinite(n) ? n : 0;
718
+ };
719
+ return {
720
+ label,
721
+ andar: getStr("floor") || "",
722
+ numeroLoja: getStr("NumLoja") || "",
723
+ identificadorMedidor: getStr("IDMedidor") || "",
724
+ identificadorDispositivo: getStr("deviceId") || "",
725
+ guid: getStr("guid") || "",
726
+ consumoDiario: getNum("maxDailyConsumption"),
727
+ consumoMadrugada: getNum("maxNightConsumption")
728
+ };
729
+ }
730
+
652
731
  // src/utils/deviceType.js
653
732
  var contexts = {
654
733
  building: (name) => {
@@ -895,10 +974,12 @@
895
974
  exports.formatDateWithTimezoneOffset = formatDateWithTimezoneOffset;
896
975
  exports.formatEnergy = formatEnergy;
897
976
  exports.formatEnergyByGroup = formatEnergyByGroup;
977
+ exports.formatNumberReadable = formatNumberReadable;
898
978
  exports.formatTankHeadFromCm = formatTankHeadFromCm;
899
979
  exports.formatWaterVolumeM3 = formatWaterVolumeM3;
900
980
  exports.getAvailableContexts = getAvailableContexts;
901
981
  exports.getDateRangeArray = getDateRangeArray;
982
+ exports.getEntityInfoAndAttributesTB = getEntityInfoAndAttributesTB;
902
983
  exports.getSaoPauloISOString = getSaoPauloISOString;
903
984
  exports.getSaoPauloISOStringFixed = getSaoPauloISOStringFixed;
904
985
  exports.getValueByDatakey = getValueByDatakey;
@@ -1 +1 @@
1
- (function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.MyIOLibrary={}))})(this,function(exports){"use strict";var __defProp=Object.defineProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};function formatEnergy(value,unit){if(value===null||value===void 0||isNaN(value)){return"-"}const formattedValue=value.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} ${unit}`}function formatAllInSameUnit(values,targetUnit){const unitMultipliers={kWh:1,MWh:1e3,GWh:1e6};const targetMultiplier=unitMultipliers[targetUnit]||1;return values.map(item=>{if(item.value===null||item.value===void 0||isNaN(item.value)){return"-"}const sourceMultiplier=unitMultipliers[item.unit]||1;const convertedValue=item.value*sourceMultiplier/targetMultiplier;return formatEnergy(convertedValue,targetUnit)})}function fmtPerc(value){if(value===null||value===void 0||isNaN(value)||!isFinite(value)){return"-"}const percentage=value*100;return percentage.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2})+"%"}function formatWaterVolumeM3(value,locale="pt-BR"){if(value===null||value===void 0||isNaN(value)){return"-"}const formattedValue=value.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} M³`}function formatTankHeadFromCm(valueCm,locale="pt-BR"){if(valueCm===null||valueCm===void 0||isNaN(valueCm)){return"-"}const valueMeters=valueCm/100;const formattedValue=valueMeters.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} m.c.a.`}function calcDeltaPercent(prev,current){if(prev===null||prev===void 0||isNaN(prev)||current===null||current===void 0||isNaN(current)){return{value:0,type:"neutral"}}if(prev===0&&current===0){return{value:0,type:"neutral"}}if(prev===0&&current>0){return{value:100,type:"increase"}}if(prev===0&&current<0){return{value:100,type:"decrease"}}const percentChange=(current-prev)/prev*100;if(percentChange>0){return{value:percentChange,type:"increase"}}else if(percentChange<0){return{value:Math.abs(percentChange),type:"decrease"}}else{return{value:0,type:"neutral"}}}function formatEnergyByGroup(value,group){if(value===null||value===void 0||isNaN(value)){return"-"}if(group==="Caixas D'Água"){return formatTankHeadFromCm(value)}if(value>=1e6){return formatWaterVolumeM3(value/1e6)+" (GWh scale)"}if(value>=1e3){return formatWaterVolumeM3(value/1e3)+" (MWh scale)"}return formatWaterVolumeM3(value)}function formatAllInSameWaterUnit(values){const max=Math.max(...values.filter(v=>!isNaN(v)&&v!==null&&v!==void 0));let divisor=1;let unit="M³";if(max>=1e6){divisor=1e6;unit="M³"}else if(max>=1e3){divisor=1e3;unit="M³"}return{format:val=>{if(val===null||val===void 0||isNaN(val)){return"-"}return(val/divisor).toFixed(2)+" "+unit},unit:unit}}function formatDateToYMD(date){if(!date){return""}const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const year=dateObj.getFullYear();const month=String(dateObj.getMonth()+1).padStart(2,"0");const day=String(dateObj.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function determineInterval(startDate,endDate){const start=new Date(startDate);const end=new Date(endDate);if(isNaN(start.getTime())||isNaN(end.getTime())){return"day"}const diffMs=end.getTime()-start.getTime();const diffDays=diffMs/(1e3*60*60*24);if(diffDays<=1){return"hour"}else if(diffDays<=7){return"day"}else if(diffDays<=31){return"week"}else if(diffDays<=365){return"month"}else{return"year"}}function getSaoPauloISOString(date,edge="start"){const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const saoPauloOffset=-3;const saoPauloDate=new Date(dateObj.getTime()+saoPauloOffset*60*60*1e3);if(edge==="start"){saoPauloDate.setHours(0,0,0,0)}else{saoPauloDate.setHours(23,59,59,999)}const utcDate=new Date(saoPauloDate.getTime()-saoPauloOffset*60*60*1e3);return utcDate.toISOString()}function getDateRangeArray(startDate,endDate,interval="day"){const start=new Date(startDate);const end=new Date(endDate);const dates=[];if(isNaN(start.getTime())||isNaN(end.getTime())||start>end){return dates}const current=new Date(start);while(current<=end){dates.push(new Date(current));switch(interval){case"day":current.setDate(current.getDate()+1);break;case"week":current.setDate(current.getDate()+7);break;case"month":current.setMonth(current.getMonth()+1);break;case"year":current.setFullYear(current.getFullYear()+1);break;default:current.setDate(current.getDate()+1)}}return dates}function formatDateForInput(date){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function parseInputDateToDate(inputDateStr){if(!inputDateStr){return null}const parts=inputDateStr.split("-");if(parts.length!==3){return null}const year=parseInt(parts[0],10);const month=parseInt(parts[1],10)-1;const day=parseInt(parts[2],10);if(isNaN(year)||isNaN(month)||isNaN(day)){return null}return new Date(year,month,day,0,0,0,0)}function timeWindowFromInputYMD(startYmd,endYmd,tzOffset="-03:00"){if(!startYmd||!endYmd){return{startTs:0,endTs:0}}const startParts=startYmd.split("-");const endParts=endYmd.split("-");if(startParts.length!==3||endParts.length!==3){return{startTs:0,endTs:0}}const startDate=new Date(parseInt(startParts[0],10),parseInt(startParts[1],10)-1,parseInt(startParts[2],10),0,0,0,0);const endDate=new Date(parseInt(endParts[0],10),parseInt(endParts[1],10)-1,parseInt(endParts[2],10),23,59,59,999);return{startTs:startDate.getTime(),endTs:endDate.getTime()}}function formatDateWithTimezoneOffset(date,endOfDay=false,tzOffset="-03:00"){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");let hours,minutes,seconds,milliseconds;if(endOfDay){hours="23";minutes="59";seconds="59";milliseconds="999"}else{hours=String(date.getHours()).padStart(2,"0");minutes=String(date.getMinutes()).padStart(2,"0");seconds=String(date.getSeconds()).padStart(2,"0");milliseconds=String(date.getMilliseconds()).padStart(3,"0")}return`${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}${tzOffset}`}function getSaoPauloISOStringFixed(dateStr,endOfDay=false){if(!dateStr)return"";if(endOfDay){return`${dateStr}T23:59:59.999-03:00`}else{return`${dateStr}T00:00:00.000-03:00`}}function averageByDay(data){if(!data||data.length===0){return[]}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});const result=Object.entries(grouped).map(([day,values])=>{const sum=values.reduce((acc,val)=>acc+val,0);const average=sum/values.length;return{day:day,average:average}});result.sort((a,b)=>a.day.localeCompare(b.day));return result}function groupByDay(data){if(!data||data.length===0){return{}}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});return grouped}function exportToCSV(data,headers,filename){if(!data||data.length===0){return""}const csvHeaders=headers.join(",");const csvRows=data.map(row=>headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue}).join(","));const csvContent=[csvHeaders,...csvRows].join("\n");return csvContent}function exportToCSVAll(storesData,headers,filename){if(!storesData||Object.keys(storesData).length===0){return""}const csvRows=[];const csvHeaders=["Store",...headers].join(",");csvRows.push(csvHeaders);Object.entries(storesData).forEach(([storeName,storeData])=>{if(!storeData||storeData.length===0){return}storeData.forEach(row=>{let formattedStoreName=storeName;if(storeName.includes(",")||storeName.includes('"')||storeName.includes("\n")){formattedStoreName=`"${storeName.replace(/"/g,'""')}"`}const csvRow=[formattedStoreName,...headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue})].join(",");csvRows.push(csvRow)})});return csvRows.join("\n")}function buildWaterReportCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumptionStr=String(row.totalConsumption).replace(",",".");const consumption=Number(consumptionStr)||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);if(meta.name&&meta.identifier){csvRows.push(["Loja:",meta.name,meta.identifier])}csvRows.push(["Data","Dia da Semana","Consumo Médio (m³)","Consumo Mínimo (m³)","Consumo Máximo (m³)","Consumo (m³)"]);rows.forEach(row=>{csvRows.push([row.formattedDate,row.day,String(row.avgConsumption),String(row.minDemand),String(row.maxDemand),String(row.totalConsumption)])});return csvRows.map(row=>row.join(";")).join("\n")}function buildWaterStoresCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);csvRows.push(["Loja","Identificador","Consumo"]);rows.forEach(row=>{const label=row.entityLabel||row.deviceName||"-";const deviceId=row.deviceId||"-";const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;const formattedConsumption=consumption!==null&&consumption!==void 0?formatNumberReadable(consumption):"0,00";csvRows.push([label,deviceId,formattedConsumption])});return csvRows.map(row=>row.join(";")).join("\n")}function toCSV(rows,delimiter=";"){if(!rows||rows.length===0){return""}return rows.map(row=>row.map(cell=>{const value=String(cell);if(value.includes(delimiter)||value.includes('"')||value.includes("\n")){return`"${value.replace(/"/g,'""')}"`}return value}).join(delimiter)).join("\n")}function formatNumberReadable(value){if(value==null||isNaN(value)){return"-"}return value.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2})}function classify(entity,criteria){if(!entity||!criteria){return{category:"unknown",confidence:0}}let category="unknown";let subcategory;let confidence=0;if(entity.type){switch(entity.type.toLowerCase()){case"consumption":category="energy_consumption";confidence=.9;break;case"generation":category="energy_generation";confidence=.9;break;case"storage":category="energy_storage";confidence=.9;break;case"distribution":category="energy_distribution";confidence=.8;break;default:category="energy_other";confidence=.5}}if(entity.powerRating){const power=parseFloat(entity.powerRating);if(!isNaN(power)){if(power<1e3){subcategory="small_scale"}else if(power<1e4){subcategory="medium_scale"}else{subcategory="large_scale"}confidence=Math.min(confidence+.1,1)}}return{category:category,subcategory:subcategory,confidence:confidence}}function classifyWaterLabel(label){if(!label){console.warn('classifyWaterLabel: empty label, defaulting to "Lojas"');return"Lojas"}const normalizedLabel=label.toLowerCase().trim();if(/rel[óo]gio|caixa|superior|inferior|nível_terraço/.test(normalizedLabel)){return"Caixas D'Água"}if(/administra|bomba|chiller|adm/.test(normalizedLabel)){return"Área Comum"}return"Lojas"}function classifyWaterLabels(labels){const counts={"Caixas D'Água":0,Lojas:0,"Área Comum":0,total:0};labels.forEach(label=>{const category=classifyWaterLabel(label);counts[category]++;counts.total++});return counts}function getWaterCategories(){return["Caixas D'Água","Lojas","Área Comum"]}function isWaterCategory(label,category){return classifyWaterLabel(label)===category}function getValueByDatakey(data,datakey){if(!data||!datakey){return void 0}if(Array.isArray(data)){for(const item of data){const value=getValueByDatakey(item,datakey);if(value!==void 0){return value}}return void 0}if(typeof data==="object"&&data!==null){const keys=datakey.split(".");let current=data;for(const key of keys){if(current===null||current===void 0){return void 0}if(key.includes("[")&&key.includes("]")){const arrayKey=key.substring(0,key.indexOf("["));const indexMatch=key.match(/\[(\d+)\]/);if(indexMatch){const index=parseInt(indexMatch[1],10);current=current[arrayKey];if(Array.isArray(current)&&index>=0&&index<current.length){current=current[index]}else{return void 0}}else{return void 0}}else{current=current[key]}}return current}return void 0}function getValueByDatakeyLegacy(dataList,dataSourceNameTarget,dataKeyTarget){if(!Array.isArray(dataList)||!dataSourceNameTarget||!dataKeyTarget){return void 0}for(const item of dataList){if(item&&item.dataSourceName===dataSourceNameTarget&&item.dataKey===dataKeyTarget){return item.value}}return void 0}function findValue(data,keyOrPath,legacyDataKey){if(legacyDataKey!==void 0){return getValueByDatakeyLegacy(data,keyOrPath,legacyDataKey)}return getValueByDatakey(data,keyOrPath)}var contexts={building:name=>{const upper=name.toUpperCase();if(upper.includes("COMPRESSOR"))return"COMPRESSOR";if(upper.includes("VENT"))return"VENTILADOR";if(upper.includes("AUTOMATICO")||upper.includes("AUTOMÁTICO"))return"SELETOR_AUTO_MANUAL";if(upper.includes("TERMOSTATO"))return"TERMOSTATO";if(upper.includes("3F"))return"3F_MEDIDOR";if(upper.includes("TERMO")||upper.includes("TEMP"))return"TERMOSTATO";if(upper.includes("HIDR"))return"HIDROMETRO";if(upper.includes("ABRE"))return"SOLENOIDE";if(upper.includes("RECALQUE"))return"MOTOR";if(upper.includes("AUTOMACAO")||upper.includes("AUTOMAÇÃO"))return"GLOBAL_AUTOMACAO";if(upper.includes("AC"))return"CONTROLE REMOTO";if(upper.includes("SCD"))return"CAIXA_D_AGUA";return"default"},mall:name=>{const upper=name.toUpperCase();if(upper.includes("CHILLER"))return"CHILLER";if(upper.includes("ESCADA"))return"ESCADA_ROLANTE";if(upper.includes("LOJA"))return"LOJA_SENSOR";if(upper.includes("ILUMINACAO")||upper.includes("ILUMINAÇÃO"))return"ILUMINACAO";return"default"}};function detectDeviceType(name,context="building"){if(typeof name!=="string"){throw new Error("Device name must be a string.")}const detectFunction=contexts[context];if(!detectFunction){console.warn(`[myio-js-library] Context "${context}" not found. Using default fallback.`);return contexts.building(name)}return detectFunction(name)}function getAvailableContexts(){return Object.keys(contexts)}function addDetectionContext(contextName,detectFunction){if(typeof contextName!=="string"){throw new Error("Context name must be a string.")}if(typeof detectFunction!=="function"){throw new Error("Detection function must be a function.")}contexts[contextName]=detectFunction}function addNamespace(payload,namespace=""){if(!payload||typeof payload!=="object"||Array.isArray(payload)){throw new Error("Payload must be an object.")}const keys=Object.keys(payload);const suffix=namespace.trim()?` (${namespace.trim()})`:"";return keys.reduce((acc,key)=>{acc[`${key}${suffix}`]=payload[key];return acc},{})}var numbers_exports={};__export(numbers_exports,{fmtPerc:()=>fmtPerc2,toFixedSafe:()=>toFixedSafe});function fmtPerc2(x,digits=2){if(!Number.isFinite(x))return"—";return(x*100).toFixed(digits)+"%"}function toFixedSafe(x,digits=2){if(!Number.isFinite(x))return"—";return x.toFixed(digits)}var strings_exports={};__export(strings_exports,{normalizeRecipients:()=>normalizeRecipients});function normalizeRecipients(val){if(val===null||val===void 0||val==="")return"";if(Object.prototype.toString.call(val)==="[object Array]"){return val.filter(Boolean).join(",")}let s=String(val).trim();if(/^\s*\[/.test(s)){try{const arr=JSON.parse(s);if(Object.prototype.toString.call(arr)==="[object Array]"){return arr.filter(Boolean).join(",")}}catch{}}s=s.replace(/[;\s]+/g,",");s=s.replace(/,+/g,",").replace(/^,|,$/g,"");return s}function decodePayload(encoded,key){const bytes=base64ToBytesStrict(encoded);if(bytes.length===0)return"";if(key===""||key===void 0||key===null){return(new TextDecoder).decode(bytes)}if(typeof key==="number"&&Number.isFinite(key)){const k=key&255;for(let i=0;i<bytes.length;i++)bytes[i]^=k;return(new TextDecoder).decode(bytes)}const keyStr=String(key);const keyBytes=(new TextEncoder).encode(keyStr);if(keyBytes.length===0){return(new TextDecoder).decode(bytes)}for(let i=0;i<bytes.length;i++){bytes[i]^=keyBytes[i%keyBytes.length]}return(new TextDecoder).decode(bytes)}function decodePayloadBase64Xor(encoded,xorKey=73){const bytes=base64ToBytesStrict(encoded);for(let i=0;i<bytes.length;i++)bytes[i]^=xorKey&255;return(new TextDecoder).decode(bytes)}function base64ToBytesStrict(b64){if(b64===""||b64===void 0||b64===null)return new Uint8Array;const s=String(b64).replace(/\s+/g,"");const re=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;if(!re.test(s))throw new Error("Invalid base64");if(typeof Buffer!=="undefined"&&Buffer.from){return Uint8Array.from(Buffer.from(s,"base64"))}const bin=atob(s);const out=new Uint8Array(bin.length);for(let i=0;i<bin.length;i++)out[i]=bin.charCodeAt(i);return out}async function fetchWithRetry(url,options={}){const{retries:retries=0,retryDelay:retryDelay=100,timeout:timeout=1e4,retryCondition:retryCondition,...passThrough}=options;const baseInit={...passThrough,timeout:timeout};let attempt=0;while(true){try{const res=await withTimeout(fetch(url,baseInit),timeout);if(!res.ok){const doRetry=typeof retryCondition==="function"&&retryCondition(null,res)||res.status>=500;if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}const msg=`HTTP ${res.status}: ${res.statusText||""}`.trim();throw new Error(msg)}return res}catch(err){if(err&&err.message==="Request timeout"){if(attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}const doRetry=typeof retryCondition==="function"&&retryCondition(err,void 0)||isRetryableNetworkError(err);if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}}}var http=fetchWithRetry;function withTimeout(promise,ms){return new Promise((resolve,reject)=>{const t=setTimeout(()=>reject(new Error("Request timeout")),ms);promise.then(v=>{clearTimeout(t);resolve(v)},e=>{clearTimeout(t);reject(e)})})}function delay(ms){return new Promise(r=>setTimeout(r,ms))}function expBackoff(base,attempt){return base*Math.pow(2,attempt)}function isRetryableNetworkError(err){if(!err)return false;const msg=String(err.message||"").toLowerCase();return msg.includes("network")||err.name==="AbortError"}exports.addDetectionContext=addDetectionContext;exports.addNamespace=addNamespace;exports.averageByDay=averageByDay;exports.buildWaterReportCSV=buildWaterReportCSV;exports.buildWaterStoresCSV=buildWaterStoresCSV;exports.calcDeltaPercent=calcDeltaPercent;exports.classify=classify;exports.classifyWaterLabel=classifyWaterLabel;exports.classifyWaterLabels=classifyWaterLabels;exports.decodePayload=decodePayload;exports.decodePayloadBase64Xor=decodePayloadBase64Xor;exports.detectDeviceType=detectDeviceType;exports.determineInterval=determineInterval;exports.exportToCSV=exportToCSV;exports.exportToCSVAll=exportToCSVAll;exports.fetchWithRetry=fetchWithRetry;exports.findValue=findValue;exports.fmtPerc=fmtPerc;exports.fmtPercLegacy=fmtPerc2;exports.formatAllInSameUnit=formatAllInSameUnit;exports.formatAllInSameWaterUnit=formatAllInSameWaterUnit;exports.formatDateForInput=formatDateForInput;exports.formatDateToYMD=formatDateToYMD;exports.formatDateWithTimezoneOffset=formatDateWithTimezoneOffset;exports.formatEnergy=formatEnergy;exports.formatEnergyByGroup=formatEnergyByGroup;exports.formatTankHeadFromCm=formatTankHeadFromCm;exports.formatWaterVolumeM3=formatWaterVolumeM3;exports.getAvailableContexts=getAvailableContexts;exports.getDateRangeArray=getDateRangeArray;exports.getSaoPauloISOString=getSaoPauloISOString;exports.getSaoPauloISOStringFixed=getSaoPauloISOStringFixed;exports.getValueByDatakey=getValueByDatakey;exports.getValueByDatakeyLegacy=getValueByDatakeyLegacy;exports.getWaterCategories=getWaterCategories;exports.groupByDay=groupByDay;exports.http=http;exports.isWaterCategory=isWaterCategory;exports.normalizeRecipients=normalizeRecipients;exports.numbers=numbers_exports;exports.parseInputDateToDate=parseInputDateToDate;exports.strings=strings_exports;exports.timeWindowFromInputYMD=timeWindowFromInputYMD;exports.toCSV=toCSV;exports.toFixedSafe=toFixedSafe});
1
+ (function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.MyIOLibrary={}))})(this,function(exports){"use strict";var __defProp=Object.defineProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};function formatEnergy(value,unit){if(value===null||value===void 0||isNaN(value)){return"-"}let adjustedValue=value;let adjustedUnit=unit;if(!adjustedUnit){if(value>=1e6){adjustedValue=value/1e6;adjustedUnit="GWh"}else if(value>=1e3){adjustedValue=value/1e3;adjustedUnit="MWh"}else{adjustedUnit="kWh"}}const formattedValue=adjustedValue.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} ${adjustedUnit}`}function formatAllInSameUnit(values,targetUnit){const unitMultipliers={kWh:1,MWh:1e3,GWh:1e6};const targetMultiplier=unitMultipliers[targetUnit]||1;return values.map(item=>{if(item.value===null||item.value===void 0||isNaN(item.value)){return"-"}const sourceMultiplier=unitMultipliers[item.unit]||1;const convertedValue=item.value*sourceMultiplier/targetMultiplier;return formatEnergy(convertedValue,targetUnit)})}function fmtPerc(value){if(value===null||value===void 0||isNaN(value)||!isFinite(value)){return"-"}const percentage=value*100;return percentage.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2})+"%"}function formatNumberReadable(value,locale="pt-BR",minimumFractionDigits=2,maximumFractionDigits=2){const n=typeof value==="string"?Number(value.replace(",",".")):Number(value);if(!Number.isFinite(n)){return"-"}const safe=Object.is(n,-0)?0:n;return safe.toLocaleString(locale,{minimumFractionDigits:minimumFractionDigits,maximumFractionDigits:maximumFractionDigits})}function formatWaterVolumeM3(value,locale="pt-BR"){if(value===null||value===void 0||isNaN(value)){return"-"}const formattedValue=value.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} M³`}function formatTankHeadFromCm(valueCm,locale="pt-BR"){if(valueCm===null||valueCm===void 0||isNaN(valueCm)){return"-"}const valueMeters=valueCm/100;const formattedValue=valueMeters.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} m.c.a.`}function calcDeltaPercent(prev,current){if(prev===null||prev===void 0||isNaN(prev)||current===null||current===void 0||isNaN(current)){return{value:0,type:"neutral"}}if(prev===0&&current===0){return{value:0,type:"neutral"}}if(prev===0&&current>0){return{value:100,type:"increase"}}if(prev===0&&current<0){return{value:100,type:"decrease"}}const percentChange=(current-prev)/prev*100;if(percentChange>0){return{value:percentChange,type:"increase"}}else if(percentChange<0){return{value:Math.abs(percentChange),type:"decrease"}}else{return{value:0,type:"neutral"}}}function formatEnergyByGroup(value,group){if(value===null||value===void 0||isNaN(value)){return"-"}if(group==="Caixas D'Água"){return formatTankHeadFromCm(value)}if(value>=1e6){return formatWaterVolumeM3(value/1e6)+" (GWh scale)"}if(value>=1e3){return formatWaterVolumeM3(value/1e3)+" (MWh scale)"}return formatWaterVolumeM3(value)}function formatAllInSameWaterUnit(values){const max=Math.max(...values.filter(v=>!isNaN(v)&&v!==null&&v!==void 0));let divisor=1;let unit="M³";if(max>=1e6){divisor=1e6;unit="M³"}else if(max>=1e3){divisor=1e3;unit="M³"}return{format:val=>{if(val===null||val===void 0||isNaN(val)){return"-"}return(val/divisor).toFixed(2)+" "+unit},unit:unit}}function formatDateToYMD(date){if(!date){return""}const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const year=dateObj.getFullYear();const month=String(dateObj.getMonth()+1).padStart(2,"0");const day=String(dateObj.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function determineInterval(startDate,endDate){const start=new Date(startDate);const end=new Date(endDate);if(isNaN(start.getTime())||isNaN(end.getTime())){return"day"}const diffMs=end.getTime()-start.getTime();const diffDays=diffMs/(1e3*60*60*24);if(diffDays<=1){return"hour"}else if(diffDays<=7){return"day"}else if(diffDays<=31){return"week"}else if(diffDays<=365){return"month"}else{return"year"}}function getSaoPauloISOString(date,edge="start"){const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const saoPauloOffset=-3;const saoPauloDate=new Date(dateObj.getTime()+saoPauloOffset*60*60*1e3);if(edge==="start"){saoPauloDate.setHours(0,0,0,0)}else{saoPauloDate.setHours(23,59,59,999)}const utcDate=new Date(saoPauloDate.getTime()-saoPauloOffset*60*60*1e3);return utcDate.toISOString()}function getDateRangeArray(startDate,endDate,interval="day"){const start=new Date(startDate);const end=new Date(endDate);const dates=[];if(isNaN(start.getTime())||isNaN(end.getTime())||start>end){return dates}const current=new Date(start);while(current<=end){dates.push(new Date(current));switch(interval){case"day":current.setDate(current.getDate()+1);break;case"week":current.setDate(current.getDate()+7);break;case"month":current.setMonth(current.getMonth()+1);break;case"year":current.setFullYear(current.getFullYear()+1);break;default:current.setDate(current.getDate()+1)}}return dates}function formatDateForInput(date){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function parseInputDateToDate(inputDateStr){if(!inputDateStr){return null}const parts=inputDateStr.split("-");if(parts.length!==3){return null}const year=parseInt(parts[0],10);const month=parseInt(parts[1],10)-1;const day=parseInt(parts[2],10);if(isNaN(year)||isNaN(month)||isNaN(day)){return null}return new Date(year,month,day,0,0,0,0)}function timeWindowFromInputYMD(startYmd,endYmd,tzOffset="-03:00"){if(!startYmd||!endYmd){return{startTs:0,endTs:0}}const startParts=startYmd.split("-");const endParts=endYmd.split("-");if(startParts.length!==3||endParts.length!==3){return{startTs:0,endTs:0}}const startDate=new Date(parseInt(startParts[0],10),parseInt(startParts[1],10)-1,parseInt(startParts[2],10),0,0,0,0);const endDate=new Date(parseInt(endParts[0],10),parseInt(endParts[1],10)-1,parseInt(endParts[2],10),23,59,59,999);return{startTs:startDate.getTime(),endTs:endDate.getTime()}}function formatDateWithTimezoneOffset(date,endOfDay=false,tzOffset="-03:00"){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");let hours,minutes,seconds,milliseconds;if(endOfDay){hours="23";minutes="59";seconds="59";milliseconds="999"}else{hours=String(date.getHours()).padStart(2,"0");minutes=String(date.getMinutes()).padStart(2,"0");seconds=String(date.getSeconds()).padStart(2,"0");milliseconds=String(date.getMilliseconds()).padStart(3,"0")}return`${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}${tzOffset}`}function getSaoPauloISOStringFixed(dateStr,endOfDay=false){if(!dateStr)return"";if(endOfDay){return`${dateStr}T23:59:59.999-03:00`}else{return`${dateStr}T00:00:00.000-03:00`}}function averageByDay(data){if(!data||data.length===0){return[]}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});const result=Object.entries(grouped).map(([day,values])=>{const sum=values.reduce((acc,val)=>acc+val,0);const average=sum/values.length;return{day:day,average:average}});result.sort((a,b)=>a.day.localeCompare(b.day));return result}function groupByDay(data){if(!data||data.length===0){return{}}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});return grouped}function exportToCSV(data,headers,filename){if(!data||data.length===0){return""}const csvHeaders=headers.join(",");const csvRows=data.map(row=>headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue}).join(","));const csvContent=[csvHeaders,...csvRows].join("\n");return csvContent}function exportToCSVAll(storesData,headers,filename){if(!storesData||Object.keys(storesData).length===0){return""}const csvRows=[];const csvHeaders=["Store",...headers].join(",");csvRows.push(csvHeaders);Object.entries(storesData).forEach(([storeName,storeData])=>{if(!storeData||storeData.length===0){return}storeData.forEach(row=>{let formattedStoreName=storeName;if(storeName.includes(",")||storeName.includes('"')||storeName.includes("\n")){formattedStoreName=`"${storeName.replace(/"/g,'""')}"`}const csvRow=[formattedStoreName,...headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue})].join(",");csvRows.push(csvRow)})});return csvRows.join("\n")}function buildWaterReportCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumptionStr=String(row.totalConsumption).replace(",",".");const consumption=Number(consumptionStr)||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);if(meta.name&&meta.identifier){csvRows.push(["Loja:",meta.name,meta.identifier])}csvRows.push(["Data","Dia da Semana","Consumo Médio (m³)","Consumo Mínimo (m³)","Consumo Máximo (m³)","Consumo (m³)"]);rows.forEach(row=>{csvRows.push([row.formattedDate,row.day,String(row.avgConsumption),String(row.minDemand),String(row.maxDemand),String(row.totalConsumption)])});return csvRows.map(row=>row.join(";")).join("\n")}function buildWaterStoresCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);csvRows.push(["Loja","Identificador","Consumo"]);rows.forEach(row=>{const label=row.entityLabel||row.deviceName||"-";const deviceId=row.deviceId||"-";const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;const formattedConsumption=consumption!==null&&consumption!==void 0?formatNumberReadable(consumption):"0,00";csvRows.push([label,deviceId,formattedConsumption])});return csvRows.map(row=>row.join(";")).join("\n")}function toCSV(rows,delimiter=";"){if(!rows||rows.length===0){return""}return rows.map(row=>row.map(cell=>{const value=String(cell);if(value.includes(delimiter)||value.includes('"')||value.includes("\n")){return`"${value.replace(/"/g,'""')}"`}return value}).join(delimiter)).join("\n")}function classify(entity,criteria){if(!entity||!criteria){return{category:"unknown",confidence:0}}let category="unknown";let subcategory;let confidence=0;if(entity.type){switch(entity.type.toLowerCase()){case"consumption":category="energy_consumption";confidence=.9;break;case"generation":category="energy_generation";confidence=.9;break;case"storage":category="energy_storage";confidence=.9;break;case"distribution":category="energy_distribution";confidence=.8;break;default:category="energy_other";confidence=.5}}if(entity.powerRating){const power=parseFloat(entity.powerRating);if(!isNaN(power)){if(power<1e3){subcategory="small_scale"}else if(power<1e4){subcategory="medium_scale"}else{subcategory="large_scale"}confidence=Math.min(confidence+.1,1)}}return{category:category,subcategory:subcategory,confidence:confidence}}function classifyWaterLabel(label){if(!label){console.warn('classifyWaterLabel: empty label, defaulting to "Lojas"');return"Lojas"}const normalizedLabel=label.toLowerCase().trim();if(/rel[óo]gio|caixa|superior|inferior|nível_terraço/.test(normalizedLabel)){return"Caixas D'Água"}if(/administra|bomba|chiller|adm/.test(normalizedLabel)){return"Área Comum"}return"Lojas"}function classifyWaterLabels(labels){const counts={"Caixas D'Água":0,Lojas:0,"Área Comum":0,total:0};labels.forEach(label=>{const category=classifyWaterLabel(label);counts[category]++;counts.total++});return counts}function getWaterCategories(){return["Caixas D'Água","Lojas","Área Comum"]}function isWaterCategory(label,category){return classifyWaterLabel(label)===category}function getValueByDatakey(data,datakey){if(!data||!datakey){return void 0}if(Array.isArray(data)){for(const item of data){const value=getValueByDatakey(item,datakey);if(value!==void 0){return value}}return void 0}if(typeof data==="object"&&data!==null){const keys=datakey.split(".");let current=data;for(const key of keys){if(current===null||current===void 0){return void 0}if(key.includes("[")&&key.includes("]")){const arrayKey=key.substring(0,key.indexOf("["));const indexMatch=key.match(/\[(\d+)\]/);if(indexMatch){const index=parseInt(indexMatch[1],10);current=current[arrayKey];if(Array.isArray(current)&&index>=0&&index<current.length){current=current[index]}else{return void 0}}else{return void 0}}else{current=current[key]}}return current}return void 0}function getValueByDatakeyLegacy(dataList,dataSourceNameTarget,dataKeyTarget){if(!Array.isArray(dataList)||!dataSourceNameTarget||!dataKeyTarget){return void 0}for(const item of dataList){if(item&&item.dataSourceName===dataSourceNameTarget&&item.dataKey===dataKeyTarget){return item.value}}return void 0}function findValue(data,keyOrPath,legacyDataKey){if(legacyDataKey!==void 0){return getValueByDatakeyLegacy(data,keyOrPath,legacyDataKey)}return getValueByDatakey(data,keyOrPath)}async function getEntityInfoAndAttributesTB(deviceId,opts){if(!deviceId)throw new Error("getEntityInfoAndAttributesTB: deviceId is required");const{jwt:jwt,baseUrl:baseUrl="",scope:scope="SERVER_SCOPE",attributeKeys:attributeKeys=["floor","NumLoja","IDMedidor","deviceId","guid","maxDailyConsumption","maxNightConsumption"],fetcher:fetcher=globalThis.fetch?.bind(globalThis)}=opts||{};if(!jwt)throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");if(!fetcher)throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");const headers={"Content-Type":"application/json","X-Authorization":`Bearer ${jwt}`};const base=baseUrl.endsWith("/")?baseUrl.slice(0,-1):baseUrl;const deviceRes=await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`,{headers:headers});if(!deviceRes.ok){throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`)}const device=await deviceRes.json();const label=device?.label||device?.name||"Sem etiqueta";const attrUrl=`${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;const attrRes=await fetcher(attrUrl,{headers:headers});if(!attrRes.ok){throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`)}const attributes=await attrRes.json();const map=new Map;for(const a of attributes)map.set(a.key,a.value);const getStr=k=>{const v=map.get(k);if(v==null)return"";return typeof v==="string"?v:String(v)};const getNum=k=>{const v=map.get(k);if(v==null)return 0;const n=typeof v==="string"?Number(v.replace(",",".")):Number(v);return Number.isFinite(n)?n:0};return{label:label,andar:getStr("floor")||"",numeroLoja:getStr("NumLoja")||"",identificadorMedidor:getStr("IDMedidor")||"",identificadorDispositivo:getStr("deviceId")||"",guid:getStr("guid")||"",consumoDiario:getNum("maxDailyConsumption"),consumoMadrugada:getNum("maxNightConsumption")}}var contexts={building:name=>{const upper=name.toUpperCase();if(upper.includes("COMPRESSOR"))return"COMPRESSOR";if(upper.includes("VENT"))return"VENTILADOR";if(upper.includes("AUTOMATICO")||upper.includes("AUTOMÁTICO"))return"SELETOR_AUTO_MANUAL";if(upper.includes("TERMOSTATO"))return"TERMOSTATO";if(upper.includes("3F"))return"3F_MEDIDOR";if(upper.includes("TERMO")||upper.includes("TEMP"))return"TERMOSTATO";if(upper.includes("HIDR"))return"HIDROMETRO";if(upper.includes("ABRE"))return"SOLENOIDE";if(upper.includes("RECALQUE"))return"MOTOR";if(upper.includes("AUTOMACAO")||upper.includes("AUTOMAÇÃO"))return"GLOBAL_AUTOMACAO";if(upper.includes("AC"))return"CONTROLE REMOTO";if(upper.includes("SCD"))return"CAIXA_D_AGUA";return"default"},mall:name=>{const upper=name.toUpperCase();if(upper.includes("CHILLER"))return"CHILLER";if(upper.includes("ESCADA"))return"ESCADA_ROLANTE";if(upper.includes("LOJA"))return"LOJA_SENSOR";if(upper.includes("ILUMINACAO")||upper.includes("ILUMINAÇÃO"))return"ILUMINACAO";return"default"}};function detectDeviceType(name,context="building"){if(typeof name!=="string"){throw new Error("Device name must be a string.")}const detectFunction=contexts[context];if(!detectFunction){console.warn(`[myio-js-library] Context "${context}" not found. Using default fallback.`);return contexts.building(name)}return detectFunction(name)}function getAvailableContexts(){return Object.keys(contexts)}function addDetectionContext(contextName,detectFunction){if(typeof contextName!=="string"){throw new Error("Context name must be a string.")}if(typeof detectFunction!=="function"){throw new Error("Detection function must be a function.")}contexts[contextName]=detectFunction}function addNamespace(payload,namespace=""){if(!payload||typeof payload!=="object"||Array.isArray(payload)){throw new Error("Payload must be an object.")}const keys=Object.keys(payload);const suffix=namespace.trim()?` (${namespace.trim()})`:"";return keys.reduce((acc,key)=>{acc[`${key}${suffix}`]=payload[key];return acc},{})}var numbers_exports={};__export(numbers_exports,{fmtPerc:()=>fmtPerc2,toFixedSafe:()=>toFixedSafe});function fmtPerc2(x,digits=2){if(!Number.isFinite(x))return"—";return(x*100).toFixed(digits)+"%"}function toFixedSafe(x,digits=2){if(!Number.isFinite(x))return"—";return x.toFixed(digits)}var strings_exports={};__export(strings_exports,{normalizeRecipients:()=>normalizeRecipients});function normalizeRecipients(val){if(val===null||val===void 0||val==="")return"";if(Object.prototype.toString.call(val)==="[object Array]"){return val.filter(Boolean).join(",")}let s=String(val).trim();if(/^\s*\[/.test(s)){try{const arr=JSON.parse(s);if(Object.prototype.toString.call(arr)==="[object Array]"){return arr.filter(Boolean).join(",")}}catch{}}s=s.replace(/[;\s]+/g,",");s=s.replace(/,+/g,",").replace(/^,|,$/g,"");return s}function decodePayload(encoded,key){const bytes=base64ToBytesStrict(encoded);if(bytes.length===0)return"";if(key===""||key===void 0||key===null){return(new TextDecoder).decode(bytes)}if(typeof key==="number"&&Number.isFinite(key)){const k=key&255;for(let i=0;i<bytes.length;i++)bytes[i]^=k;return(new TextDecoder).decode(bytes)}const keyStr=String(key);const keyBytes=(new TextEncoder).encode(keyStr);if(keyBytes.length===0){return(new TextDecoder).decode(bytes)}for(let i=0;i<bytes.length;i++){bytes[i]^=keyBytes[i%keyBytes.length]}return(new TextDecoder).decode(bytes)}function decodePayloadBase64Xor(encoded,xorKey=73){const bytes=base64ToBytesStrict(encoded);for(let i=0;i<bytes.length;i++)bytes[i]^=xorKey&255;return(new TextDecoder).decode(bytes)}function base64ToBytesStrict(b64){if(b64===""||b64===void 0||b64===null)return new Uint8Array;const s=String(b64).replace(/\s+/g,"");const re=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;if(!re.test(s))throw new Error("Invalid base64");if(typeof Buffer!=="undefined"&&Buffer.from){return Uint8Array.from(Buffer.from(s,"base64"))}const bin=atob(s);const out=new Uint8Array(bin.length);for(let i=0;i<bin.length;i++)out[i]=bin.charCodeAt(i);return out}async function fetchWithRetry(url,options={}){const{retries:retries=0,retryDelay:retryDelay=100,timeout:timeout=1e4,retryCondition:retryCondition,...passThrough}=options;const baseInit={...passThrough,timeout:timeout};let attempt=0;while(true){try{const res=await withTimeout(fetch(url,baseInit),timeout);if(!res.ok){const doRetry=typeof retryCondition==="function"&&retryCondition(null,res)||res.status>=500;if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}const msg=`HTTP ${res.status}: ${res.statusText||""}`.trim();throw new Error(msg)}return res}catch(err){if(err&&err.message==="Request timeout"){if(attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}const doRetry=typeof retryCondition==="function"&&retryCondition(err,void 0)||isRetryableNetworkError(err);if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}}}var http=fetchWithRetry;function withTimeout(promise,ms){return new Promise((resolve,reject)=>{const t=setTimeout(()=>reject(new Error("Request timeout")),ms);promise.then(v=>{clearTimeout(t);resolve(v)},e=>{clearTimeout(t);reject(e)})})}function delay(ms){return new Promise(r=>setTimeout(r,ms))}function expBackoff(base,attempt){return base*Math.pow(2,attempt)}function isRetryableNetworkError(err){if(!err)return false;const msg=String(err.message||"").toLowerCase();return msg.includes("network")||err.name==="AbortError"}exports.addDetectionContext=addDetectionContext;exports.addNamespace=addNamespace;exports.averageByDay=averageByDay;exports.buildWaterReportCSV=buildWaterReportCSV;exports.buildWaterStoresCSV=buildWaterStoresCSV;exports.calcDeltaPercent=calcDeltaPercent;exports.classify=classify;exports.classifyWaterLabel=classifyWaterLabel;exports.classifyWaterLabels=classifyWaterLabels;exports.decodePayload=decodePayload;exports.decodePayloadBase64Xor=decodePayloadBase64Xor;exports.detectDeviceType=detectDeviceType;exports.determineInterval=determineInterval;exports.exportToCSV=exportToCSV;exports.exportToCSVAll=exportToCSVAll;exports.fetchWithRetry=fetchWithRetry;exports.findValue=findValue;exports.fmtPerc=fmtPerc;exports.fmtPercLegacy=fmtPerc2;exports.formatAllInSameUnit=formatAllInSameUnit;exports.formatAllInSameWaterUnit=formatAllInSameWaterUnit;exports.formatDateForInput=formatDateForInput;exports.formatDateToYMD=formatDateToYMD;exports.formatDateWithTimezoneOffset=formatDateWithTimezoneOffset;exports.formatEnergy=formatEnergy;exports.formatEnergyByGroup=formatEnergyByGroup;exports.formatNumberReadable=formatNumberReadable;exports.formatTankHeadFromCm=formatTankHeadFromCm;exports.formatWaterVolumeM3=formatWaterVolumeM3;exports.getAvailableContexts=getAvailableContexts;exports.getDateRangeArray=getDateRangeArray;exports.getEntityInfoAndAttributesTB=getEntityInfoAndAttributesTB;exports.getSaoPauloISOString=getSaoPauloISOString;exports.getSaoPauloISOStringFixed=getSaoPauloISOStringFixed;exports.getValueByDatakey=getValueByDatakey;exports.getValueByDatakeyLegacy=getValueByDatakeyLegacy;exports.getWaterCategories=getWaterCategories;exports.groupByDay=groupByDay;exports.http=http;exports.isWaterCategory=isWaterCategory;exports.normalizeRecipients=normalizeRecipients;exports.numbers=numbers_exports;exports.parseInputDateToDate=parseInputDateToDate;exports.strings=strings_exports;exports.timeWindowFromInputYMD=timeWindowFromInputYMD;exports.toCSV=toCSV;exports.toFixedSafe=toFixedSafe});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myio-js-library",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A clean, standalone JS SDK for MYIO projects",
5
5
  "license": "MIT",
6
6
  "repository": "github:gh-myio/myio-js-library",