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 +137 -3
- package/dist/index.cjs +95 -11
- package/dist/index.d.cts +44 -3
- package/dist/index.js +93 -11
- package/dist/myio-js-library.umd.js +92 -11
- package/dist/myio-js-library.umd.min.js +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
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} ${
|
|
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 -
|
|
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
|
|
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
|
-
|
|
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} ${
|
|
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
|
-
|
|
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} ${
|
|
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&¤t===0){return{value:0,type:"neutral"}}if(prev===0&¤t>0){return{value:100,type:"increase"}}if(prev===0&¤t<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&¤t===0){return{value:0,type:"neutral"}}if(prev===0&¤t>0){return{value:100,type:"increase"}}if(prev===0&¤t<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});
|