import DatumSamplesTypes from "./datumSamplesType.js";
import DatumStreamMetadata from "./datumStreamMetadata.js";
import DatumStreamMetadataRegistry from "../util/datumStreamMetadataRegistry.js";
function pushProperties(result, values) {
if (!values) {
return;
}
for (let e of values) {
result.push(e);
}
}
function populateProperties(obj, names, values, type, withoutStatistics) {
if (!Array.isArray(names) || !Array.isArray(values)) {
return;
}
var val, name, valLen;
for (let i = 0, iMax = Math.min(names.length, values.length); i < iMax; i += 1) {
val = values[i];
if (DatumSamplesTypes.Instantaneous === type) {
if (Array.isArray(val)) {
name = names[i];
valLen = val.length;
if (
valLen > 0 &&
val[0] !== null &&
!Object.prototype.hasOwnProperty.call(obj, name)
) {
obj[name] = val[0];
if (!withoutStatistics) {
if (valLen > 1 && val[1] !== null) {
obj[name + "_count"] = val[1];
}
if (valLen > 2 && val[2] !== null) {
obj[name + "_min"] = val[2];
}
if (valLen > 3 && val[3] !== null) {
obj[name + "_max"] = val[3];
}
}
}
}
} else if (DatumSamplesTypes.Accumulating === type) {
if (Array.isArray(val)) {
name = names[i];
valLen = val.length;
if (
valLen > 0 &&
val[0] !== null &&
!Object.prototype.hasOwnProperty.call(obj, name)
) {
obj[name] = val[0];
if (!withoutStatistics) {
if (valLen > 1 && val[1] !== null) {
obj[name + "_start"] = val[1];
}
if (valLen > 2 && val[2] !== null) {
obj[name + "_end"] = val[2];
}
}
}
}
} else {
if (val !== undefined && val !== null) {
name = names[i];
if (!Object.prototype.hasOwnProperty.call(obj, name)) {
obj[name] = val;
}
}
}
}
}
/**
* A stream aggregate datum entity.
*
* A stream aggregate datum is a datum representing some aggregate calculation, without any metadata describing the datum property names.
* The instantantaneous and accumulating property values are stored as 2D array fields `iProps` and `aProps` that hold the property values
* as well as associated aggregate statistics. The datum status properties are stroed in the 1D array field `sProps`. A
* {@link module:domain~DatumStreamMetadata DatumStreamMetadata} object is required to associate names with these arrays.
*
* The instantaneous properties are 4-element arrays containing:
*
* 1. property average value
* 2. property count
* 3. minimum value
* 4. maximum value
*
* The accumulatingn statistics are 3-element arrays containing:
*
* 1. difference between ending and starting property values
* 2. starting property value
* 3. ending property value
*
* @alias module:domain~StreamAggregateDatum
*/
class StreamAggregateDatum {
/**
* Constructor.
* @param {string} streamId the datum stream ID
* @param {Array<Date|number|string>} ts an array with 2 elements for the datum start and end timestamps, either as a `Date` instance
* or a form suitable for constructing as `new Date(ts)`
* @param {Array<Array<number>>} [iProps] the instantaneous property values and associated statistics
* @param {Array<Array<number>>} [aProps] the accumulating property values and associated statistics
* @param {Array<String>} [sProps] the status property values
* @param {Set<String>|Array<String>} [tags] the tag values
*/
constructor(streamId, ts, iProps, aProps, sProps, tags) {
this.streamId = streamId;
this.ts = Array.isArray(ts) ? ts.map((e) => (e instanceof Date ? e : new Date(e))) : [];
this.iProps = iProps;
this.aProps = aProps;
this.sProps = sProps;
this.tags = tags ? (tags instanceof Set ? tags : new Set(tags)) : undefined;
if (this.constructor === StreamAggregateDatum) {
Object.freeze(this);
}
}
/**
* Get this instance as a simple object.
*
* The following basic properties will be set on the returned object:
*
* * `streamId` - the stream ID
* * `date` - the timestamp
* * `date_end` - the ending timestamp, if available
* * `sourceId` - the metadata source ID
* * `nodeId` or `locationId` - either the node ID or location ID from the metadata
* * `tags` - any tags (as an Array)
*
* Beyond that, all instantaneous, accumulating, and status properties will be included.
* If duplicate property names exist between the different classifications, the first-available
* value will be used. Any available statistics for each property are included as well, using
* property names with the following suffixes:
*
* * `_count` - count of datum
* * `_min` - minimum value
* * `_max` - maximum value
* * `_start` - starting value
* * `_end` - ending value
*
* @param {module:domain~DatumStreamMetadata} meta a metadata instance to encode the property names with
* @param {boolean} [withoutStatistics] `true` to omit statistic properties
* @returns {Object} an object populated with all available properties
*/
toObject(meta, withoutStatistics) {
var obj = {
streamId: this.streamId,
sourceId: meta.sourceId,
};
if (this.ts.length > 0) {
obj.date = this.ts[0];
if (this.ts.length > 1) {
obj.date_end = this.ts[1];
}
}
if (meta.nodeId !== undefined) {
obj.nodeId = meta.nodeId;
} else if (meta.locationId !== undefined) {
obj.locationId = meta.locationId;
}
if (this.tags) {
obj.tags = Array.from(this.tags);
}
populateProperties(
obj,
meta.instantaneousNames,
this.iProps,
DatumSamplesTypes.Instantaneous,
withoutStatistics,
);
populateProperties(
obj,
meta.accumulatingNames,
this.aProps,
DatumSamplesTypes.Accumulating,
withoutStatistics,
);
populateProperties(obj, meta.statusNames, this.sProps, DatumSamplesTypes.Status);
return obj;
}
/**
* Get this object as a standard JSON encoded string value.
*
* This method returns the JSON form of the result of {@link module:domain~StreamAggregateDatum#toJsonObject StreamAggregateDatum#toJsonObject()}.
*
* @param {module:util~DatumStreamMetadataRegistry} [registry] a stream metadata registry to encode as a registry-indexed stream datum
* @return {string} the JSON encoded string
*/
toJsonEncoding(registry) {
return JSON.stringify(this.toJsonObject(registry));
}
/**
* Get this object as an array suitable for encoding into a standard stream datum JSON string.
*
* This method can encode the datum into an array using one of two ways, depending on whether the `registry` argument is provided.
* When provided, the first array element will be the stream metadata index based on calling
* {@link module:util~DatumStreamMetadataRegistry#indexOfMetadataStreamId DatumStreamMetadataRegistry#indexOfMetadataStreamId()}.
* Otherwise the first array element will be the stream ID itself.
*
* For example if a registry is used, the resulting array might look like this:
*
* ```
* [0,[1650945600000,1651032000000],[3.6,2,0,7.2],[19.1,2,18.1, 20.1],[1.422802,1138.446687,1139.869489]]
* ```
*
* while without a registry the array might look like this:
*
* ```
* ["7714f762-2361-4ec2-98ab-7e96807b32a6", [1650945600000,1651032000000],[3.6,2,0,7.2],[19.1,2,18.1, 20.1],[1.422802,1138.446687,1139.869489]]
* ```
*
* @param {module:util~DatumStreamMetadataRegistry} [registry] a stream metadata registry to encode as a registry-indexed stream datum
* @return {Array} the datum stream array object
*/
toJsonObject(registry) {
const result = [
registry instanceof DatumStreamMetadataRegistry
? registry.indexOfMetadataStreamId(this.streamId)
: this.streamId,
this.ts.map((e) => (e ? e.getTime() : null)),
];
pushProperties(result, this.iProps);
pushProperties(result, this.aProps);
pushProperties(result, this.sProps);
pushProperties(result, this.tags);
return result;
}
/**
* Parse a JSON string into a {@link module:domain~StreamAggregateDatum StreamAggregateDatum} instance.
*
* The JSON must be encoded the same way {@link module:domain~StreamAggregateDatum#toJsonEncoding StreamAggregateDatum#toJsonEncoding()} does.
*
* @param {string} json the JSON to parse
* @param {module:domain~DatumStreamMetadata|module:util~DatumStreamMetadataRegistry} meta a metadata instance or metadata registry to decode with
* @returns {module:domain~StreamAggregateDatum} the stream datum instance
*/
static fromJsonEncoding(json, meta) {
return this.fromJsonObject(JSON.parse(json), meta);
}
/**
* Create a new {@link module:domain~StreamAggregateDatum StreamAggregateDatum} instance from an array parsed from a stream datum JSON string.
*
* The array must have been parsed from JSON that was encoded the same way {@link module:domain~StreamAggregateDatum#toJsonEncoding StreamAggregateDatum#toJsonEncoding()} does.
*
* @param {Array} data the array parsed from JSON
* @param {module:domain~DatumStreamMetadata|module:util~DatumStreamMetadataRegistry} meta a metadata instance or metadata registry to decode with
* @returns {module:domain~StreamAggregateDatum} the stream datum instance
*/
static fromJsonObject(data, meta) {
let i, len, m, iProps, aProps, sProps, tags;
if (Array.isArray(data) && data.length > 1) {
if (typeof data[0] === "string") {
// treat as an embedded stream ID stream datum
m = meta instanceof DatumStreamMetadata ? meta : meta.metadataForStreamId(data[0]);
} else {
// treat as a registry-indexed stream datum
m = meta instanceof DatumStreamMetadata ? meta : meta.metadataAt(data[0]);
}
i = 2;
len = m.instantaneousLength;
if (len > 0) {
iProps = data.slice(i, i + len);
i += len;
}
len = m.accumulatingLength;
if (len > 0) {
aProps = data.slice(i, i + len);
i += len;
}
len = m.statusLength;
if (len > 0) {
sProps = data.slice(i, i + len);
i += len;
}
if (i < data.length) {
tags = new Set(data.slice(i));
}
// to support StreamDatumMetadataMixin we pass meta as additional argument
return new this(m.streamId, data[1], iProps, aProps, sProps, tags, meta);
}
return null;
}
}
export default StreamAggregateDatum;