Services/Behavior.js

/**Copyright (c) 2009-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.**/
"use strict";const deepcopy=require("deepcopy"),filterTemplate=require("../queryTemplates/filter.json"),Database=require("../Utils/Database"),Elasticsearch=require("../Utils/Elasticsearch"),Validator=require("../Utils/Validator"),InternalServerError=require("../Errors/InternalServerError"),InvalidInputError=require("../Errors/InvalidInputError"),BadRequestError=require("../Errors/BadRequestError"),Frames=require("./Frames");
/** 
 * Class which defines Behavior
 * @memberof mdxWebApiCore.Services
 * */
class Behavior{static#e=25;static#r=1e3;static#t=100;
/**
     * returns the max behaviors that can be present in location query.
     * @public
     * @static
     * @returns {number} Max behaviors that can be present in location query is returned. The returned value must be an integer.
     * @example
     * let result = mdx.Services.Behavior.getMaxBehaviorsInLocationQuery();
     */
static getMaxBehaviorsInLocationQuery(){return this.#t}async#a(e,{sensorId:r,objectId:t,timestampWithinBehavior:a}){const i=e.getConfigs().get("indexPrefix");let s=deepcopy(filterTemplate);s.query.bool.must.push({term:{"sensor.id.keyword":r}}),s.query.bool.must.push({term:{"object.id.keyword":t}}),s.query.bool.must.push({range:{timestamp:{lte:a}}}),s.query.bool.must.push({range:{end:{gte:a}}});let o={index:`${i}${Elasticsearch.getIndex("behavior")}`,body:s,sort:"end:desc",_sourceIncludes:["timestamp"],size:1},n=null,l=await Elasticsearch.getSearchResults(e.getClient(),o,!1);return l.indexAbsent||(l=Elasticsearch.searchResultFormatter(l.body),l.length>0&&(n=l[0].timestamp)),n}
/** 
     * returns the start timestamp of a behavior.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.sensorId
     * @param {string} input.objectId
     * @param {string} input.timestampWithinBehavior
     * @returns {Promise<?string>} Start timestamp of a behavior is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {sensorId: "abc", objectId: "200", timestampWithinBehavior: "2023-01-12T14:20:10.000Z"};
     * let behaviorObject = new mdx.Services.Behavior();
     * let timestamp = await behaviorObject.getTimestampOfBehavior(elastic,input);
     */async getTimestampOfBehavior(e,r){let t=Validator.validateJsonSchema(r,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:"string",minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},objectId:{type:"string",minLength:1,errorMessage:{minLength:"objectId should have atleast 1 character."}},timestampWithinBehavior:{type:"string"}},required:["sensorId","objectId","timestampWithinBehavior"],errorMessage:{required:"Input should have required properties 'sensorId', 'objectId' and 'timestampWithinBehavior'."}});if(!t.valid)throw new BadRequestError(t.reason);if(!Validator.isValidISOTimestamp(r.timestampWithinBehavior))throw new InvalidInputError("Invalid timestampWithinBehavior.");let a=null;if("Elasticsearch"===e.getName())return a=await this.#a(e,r),a;throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#i(e,{sensorId:r,place:t,objectId:a,fromTimestamp:i,toTimestamp:s,queryString:o,maxResultSize:n}){const l=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("behavior")}`;let m=deepcopy(filterTemplate);null!=i&&null!=s&&(m.query.bool.must.push({range:{timestamp:{lte:s}}}),m.query.bool.must.push({range:{end:{gte:i}}})),null!=t?m.query.bool.must.push({prefix:{"place.name.keyword":t}}):(m.query.bool.must.push({term:{"sensor.id.keyword":r}}),null!=a&&m.query.bool.must.push({term:{"object.id.keyword":a}})),null!=o&&m.query.bool.must.push({query_string:{query:o}});let d={index:l,body:m,sort:"end:desc",size:null!=a?1:n};return await Elasticsearch.getSearchResults(e.getClient(),d,!1)}
/** 
     * returns an object containing an array of behaviors.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} [input.sensorId] - Either sensorId or place should be present.
     * @param {string} [input.place] - Either sensorId or place should be present.
     * @param {?string} [input.objectId=null] - objectId can be present only if (sensorId, fromTimestamp and toTimestamp) are present. objectId can't be used together with either queryString or maxResultSize.
     * @param {string} [input.fromTimestamp] - Either queryString or (fromTimestamp and toTimestamp) or (queryString, fromTimestamp and toTimestamp) should be present.
     * @param {string} [input.toTimestamp] - Either queryString or (fromTimestamp and toTimestamp) or (queryString, fromTimestamp and toTimestamp) should be present.
     * @param {string} [input.queryString] - queryString follows lucene syntax. Either queryString or (fromTimestamp and toTimestamp) or (queryString, fromTimestamp and toTimestamp) should be present. queryString and objectId can't occur together.
     * @param {number} [input.maxResultSize=25] - maxResultSize must be an integer. objectId and maxResultSize can't occur together. If objectId is not null, then maxResultSize is set to 1.
     * @returns {Promise<Object>} An object containing an array of behaviors is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {sensorId: "abc", fromTimestamp: "2023-01-12T11:20:10.000Z", toTimestamp: "2023-01-12T14:20:10.000Z"};
     * let behaviorMetadata = new mdx.Services.Behavior();
     * let behaviorResult = await behaviorMetadata.getBehaviors(elastic,input);
     */async getBehaviors(e,r){const t={type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},place:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"place should have atleast 1 character."}},objectId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectId should have atleast 1 character."}},fromTimestamp:{type:["string","null"],default:null},toTimestamp:{type:["string","null"],default:null},queryString:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"queryString should have atleast 1 character."}},maxResultSize:{type:"integer",minimum:1,maximum:Behavior.#r,default:Behavior.#e,errorMessage:{type:"maxResultSize is not an integer.",minimum:"maxResultSize can have a minimum value of 1.",maximum:`maxResultSize can have a maximum value of ${Behavior.#r}.`}}},allOf:[{oneOf:[{required:["sensorId"]},{required:["place"]}],errorMessage:{oneOf:"Only one of 'sensorId' or 'place' should exist in the query."}},{oneOf:[{required:["fromTimestamp","toTimestamp"],not:{required:["queryString"]}},{required:["queryString"],not:{anyOf:[{required:["fromTimestamp","queryString"]},{required:["toTimestamp","queryString"]}]}},{required:["queryString","fromTimestamp","toTimestamp"]}],errorMessage:{oneOf:"One of the following combinations should be present in input: ('fromTimestamp', 'toTimestamp') or ('queryString') or ('fromTimestamp', 'toTimestamp', 'queryString')."}}],dependentRequired:{objectId:["sensorId","fromTimestamp","toTimestamp"]},not:{anyOf:[{required:["queryString","objectId"]},{required:["objectId","maxResultSize"]}]},errorMessage:{allOf:"Only one of 'sensorId' or 'place' should exist in the query. One of the following combinations should be present in input: ('fromTimestamp', 'toTimestamp') or ('queryString') or ('fromTimestamp', 'toTimestamp', 'queryString').",dependentRequired:"'objectId' requires 'sensorId', 'fromTimestamp' and 'toTimestamp'.",not:"'objectId' can't be used together with either 'queryString' or 'maxResultSize'."}};let a=Validator.validateJsonSchema(r,t);if(!a.valid)throw new BadRequestError(a.reason);if(null!=r.fromTimestamp&&null!=r.toTimestamp){let e=Validator.isValidTimeRange(r.fromTimestamp,r.toTimestamp);if(!e.valid)throw new InvalidInputError(e.reason)}if("maxResultSize"in r&&!Number.isFinite(r.maxResultSize))throw new InvalidInputError("maxResultSize is not a finite integer.");let i=new Array;if("Elasticsearch"===e.getName()){let t=await this.#i(e,r);return t.indexAbsent||(i=Elasticsearch.searchResultFormatter(t.body)),{behaviors:i}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns the start and end pts of a behavior.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.sensorId
     * @param {number} input.endFrameId - endFrameId must be an integer.
     * @param {number} input.behaviorTimeInterval
     * @returns {Promise<Object>} Start and end pts of a behavior is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {sensorId: "abc", endFrameId: 200, behaviorTimeInterval: 20};
     * let behaviorMetadata = new mdx.Services.Behavior();
     * let behaviorPts = await behaviorMetadata.getPts(elastic,input);
     */async getPts(e,r){let t=Validator.validateJsonSchema(r,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:"string",minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},endFrameId:{type:"integer"},behaviorTimeInterval:{type:"number"}},required:["sensorId","endFrameId","behaviorTimeInterval"],errorMessage:{required:"Input should have required properties 'sensorId', 'endFrameId' and 'behaviorTimeInterval'."}});if(!t.valid)throw new BadRequestError(t.reason);if(!Number.isFinite(r.endFrameId))throw new InvalidInputError("endFrameId is not a finite integer.");if(!Number.isFinite(r.behaviorTimeInterval))throw new InvalidInputError("behaviorTimeInterval is not a finite integer.");let a=new Frames,i=await a.getPts(e,{sensorId:r.sensorId,frameId:r.endFrameId});i=i.pts;let s=Math.floor(1e3*r.behaviorTimeInterval);if(s>i)throw new InternalServerError("Invalid behaviorTimeInterval. behaviorTimeInterval should be smaller than endPts.");return{startPts:i-s,endPts:i}}async#s(e,{fromTimestamp:r,toTimestamp:t,behaviorIds:a}){const i=e.getConfigs().get("indexPrefix");let s=deepcopy(filterTemplate);s.query.bool.must.push({range:{timestamp:{lte:t}}}),s.query.bool.must.push({range:{end:{gte:r}}});let o=new Array;for(let e of a)o.push({term:{"id.keyword":e}});s.query.bool.must.push({bool:{should:o,minimum_should_match:1}});let n={index:`${i}${Elasticsearch.getIndex("behavior")}`,body:s,sort:"end:desc",size:1e4,_sourceIncludes:["Id","id","locations","smoothLocations","speedOverTime","speed","timeInterval","length","distance","bearing","timestamp","end"]};return await Elasticsearch.getSearchResults(e.getClient(),n,!1)}
/** 
     * returns the locations of a given set of input behaviors.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.fromTimestamp
     * @param {string} input.toTimestamp
     * @param {Array<string>} input.behaviorIds
     * @returns {Promise<Object>} An object containing locations of the input behaviors is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {fromTimestamp: "2023-01-12T11:20:10.000Z", toTimestamp: "2023-01-12T14:20:10.000Z", behaviorIds: ["abc #-# 272","def #-# 2152"]};
     * let behaviorMetadata = new mdx.Services.Behavior();
     * let behaviorPts = await behaviorMetadata.getLocationsOfBehaviors(elastic,input);
     */async getLocationsOfBehaviors(e,r){const t={type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{fromTimestamp:{type:"string"},toTimestamp:{type:"string"},behaviorIds:{type:"array",items:{type:"string",minLength:1,errorMessage:{minLength:"Element of behaviorIds array should have atleast 1 character."}},minItems:1,maxItems:Behavior.#t,errorMessage:{minItems:"behaviorIds should have atleast 1 item",maxItems:`behaviorIds can have atmost ${Behavior.#t} items.`}}},required:["fromTimestamp","toTimestamp","behaviorIds"],errorMessage:{required:"Input should have required properties 'behaviorIds', 'fromTimestamp' and 'toTimestamp'."}};let a=Validator.validateJsonSchema(r,t);if(!a.valid)throw new BadRequestError(a.reason);let i=Validator.isValidTimeRange(r.fromTimestamp,r.toTimestamp);if(!i.valid)throw new InvalidInputError(i.reason);let s=new Array;if("Elasticsearch"===e.getName()){let t=await this.#s(e,r);return t.indexAbsent||(s=Elasticsearch.searchResultFormatter(t.body)),{behaviors:s}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#o(e,{behaviorInfo:r}){if(0==r.length)return new Array;const t=e.getConfigs().get("indexPrefix");let a=deepcopy(filterTemplate),i=new Array;for(let e of r){let r={bool:{must:new Array}};r.bool.must.push({term:{"sensor.id.keyword":e.sensorId}}),r.bool.must.push({term:{"object.id.keyword":e.objectId}}),r.bool.must.push({term:{timestamp:e.timestamp}}),i.push(r)}a.query.bool.must.push({bool:{should:i,minimum_should_match:1}});let s={index:`${t}${Elasticsearch.getIndex("behavior")}`,body:a,sort:"end:desc",size:r.length},o=await Elasticsearch.getSearchResults(e.getClient(),s,!1);return o.indexAbsent||(o=Elasticsearch.searchResultFormatter(o.body)),o}
/** 
     * returns an object containing an array of behaviors.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {Array<{sensorId:string,objectId:string,timestamp:string}>} input.behaviorInfo
     * @returns {Promise<Object>} An object containing an array of behaviors is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {behaviorInfo:[{sensorId: "abc", objectId: "200", timestamp: "2023-01-12T14:20:10.000Z"},{sensorId: "def", objectId: "210", timestamp: "2023-01-12T14:24:10.000Z"}]};
     * let behaviorMetadata = new mdx.Services.Behavior();
     * let behaviorResult = await behaviorMetadata.getBehaviorsUsingIdsAndTimestamp(elastic,input);
     */async getBehaviorsUsingIdsAndTimestamp(e,r){let t=Validator.validateJsonSchema(r,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{behaviorInfo:{type:"array",items:{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:"string",minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},objectId:{type:"string",minLength:1,errorMessage:{minLength:"objectId should have atleast 1 character."}},timestamp:{type:"string"}},required:["sensorId","objectId","timestamp"],errorMessage:{required:"Object in behaviorInfo array should have required properties 'sensorId', 'objectId' and 'timestamp'."}}}},required:["behaviorInfo"],errorMessage:{required:"Input should have required property 'behaviorInfo'."}},!1);if(!t.valid)throw new BadRequestError(t.reason);let a=new Array;for(let e=0;e<r.behaviorInfo.length;e++)Validator.isValidISOTimestamp(r.behaviorInfo[e].timestamp)||a.push(e);if(a.length>0)throw new InvalidInputError(`behaviorInfo has invalid timestamp in the following indices: ${a.join(", ")}.`);let i=new Array;if("Elasticsearch"===e.getName())return i=await this.#o(e,r),{behaviors:i};throw new InternalServerError(`Invalid database: ${e.getName()}.`)}}module.exports=Behavior;