Services/MTMC.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"),Frames=require("./Frames"),Behavior=require("./Behavior"),Elasticsearch=require("../Utils/Elasticsearch"),Validator=require("../Utils/Validator"),InternalServerError=require("../Errors/InternalServerError"),InvalidInputError=require("../Errors/InvalidInputError"),BadRequestError=require("../Errors/BadRequestError"),Milvus=require("../Utils/Milvus"),Kafka=require("../Utils/Kafka"),Utils=require("../Utils/Utils"),winston=require("winston"),NodeCache=require("node-cache");let objectCountWithLocationsCache=new NodeCache,amrCountWithLocationsCache=new NodeCache,amrRoutes=new NodeCache;const logger=winston.createLogger({transports:[new winston.transports.Console({timestamp:!0,level:"info"})],exitOnError:!1});
/** 
 * Class used for obtaining MTMC related information.
 * @memberof mdxWebApiCore.Services
 * */class MTMC{static#e=100;static#t=25;static#a=100;static#r=20;static#s=48;static#i=72;static#o=50;static#n=.65;static#m=3e3;static#l=200;static#u=1e3;static#d=200;static#h=2e3;async#p(e,{timestamp:t,timeWindowInMs:a,place:r,sensorIds:s,objectId:i}){const o=e.getConfigs().get("indexPrefix");let n=deepcopy(filterTemplate),m=new Date(new Date(t)-a).toISOString();if(n.query.bool.must.push({range:{timestamp:{lte:t}}}),n.query.bool.must.push({range:{end:{gte:m}}}),null!=r)n.query.bool.must.push({nested:{path:"matched",query:{bool:{must:[{prefix:{"matched.place.keyword":r}}]}}}});else if(null!=i)n.query.bool.must.push({nested:{path:"matched",query:{bool:{must:[{term:{"matched.sensorId.keyword":s[0]}},{term:{"matched.objectId.keyword":i}}]}}}});else if(null!=s){let e=new Array;for(let t of s)e.push({term:{"matched.sensorId.keyword":t}});n.query.bool.must.push({nested:{path:"matched",query:{bool:{should:e,minimum_should_match:1}}}})}let l={index:`${o}${Elasticsearch.getIndex("mtmc")}`,body:n};return await Elasticsearch.getDocCount(e.getClient(),l,!1)}async#c(e,{fromTimestamp:t,toTimestamp:a,place:r,sensorIds:s,objectId:i,globalId:o,maxResultSize:n}){const m=e.getConfigs().get("indexPrefix");let l=deepcopy(filterTemplate);if(l.query.bool.must.push({range:{timestamp:{lte:a}}}),l.query.bool.must.push({range:{end:{gte:t}}}),null!=r)l.query.bool.must.push({nested:{path:"matched",query:{bool:{must:[{prefix:{"matched.place.keyword":r}}]}}}});else if(null!=o)l.query.bool.must.push({term:{"globalId.keyword":o}});else if(null!=i)l.query.bool.must.push({nested:{path:"matched",query:{bool:{must:[{term:{"matched.sensorId.keyword":s[0]}},{term:{"matched.objectId.keyword":i}}]}}}});else if(null!=s){let e=new Array;for(let t of s)e.push({term:{"matched.sensorId.keyword":t}});l.query.bool.must.push({nested:{path:"matched",query:{bool:{should:e,minimum_should_match:1}}}})}let u={index:`${m}${Elasticsearch.getIndex("mtmc")}`,body:l,sort:"end:desc",size:null!=o?1:n};return await Elasticsearch.getSearchResults(e.getClient(),u,!1)}async#g(e,{normalizedEmbedding:t,sensorIdOfExample:a,objectIdOfExample:r,timestampOfExampleBehavior:s,sensorId:i,fromTimestamp:o,toTimestamp:n,topKMatches:m}){const l={anns_field:"embedding",topk:`${m}`,metric_type:"IP",params:JSON.stringify({nprobe:e.getConfigs().get("nprobeQbe")})};let u=await Milvus.getPartitionsOfCollection(e);if(0==u.size)return logger.info("No partitions are available in milvus database."),new Array;let d=Milvus.getPartitionsFromTimestamps(e,o,n),h=Array.from(Utils.setIntersection(u,d));if(0==h.length)return logger.info("No partitions are available for the given timestamp range."),new Array;let p=`timestamp <= ${new Date(n).valueOf()} && end >= ${new Date(o).valueOf()}`;null!=i&&null!=a&&null!=r&&null!=s?(
// search 'sensorId' for similar behaviors
p+=`&& sensorId == "${i}"`,i===a&&(
// since sensorId === sensorIdOfExample, ensure that the similar behavior is not example behavior
p+=`&& (objectId != ${BigInt(r)} || timestamp != ${new Date(s).valueOf()})`)):null!=a&&null!=r&&null!=s?
// condition to ensure that the similar behavior is not example behavior
p+=`&& (sensorId != "${a}" || objectId != ${BigInt(r)} || timestamp != ${new Date(s).valueOf()})`:null!=i&&(
// if an example behavior is not given but an embedding and sensorId is part of the input, then find similar behaviors in the sensor.
p+=`&& sensorId == "${i}"`);let c={collection_name:e.getConfigs().get("collectionName"),vectors:[t],partition_names:h,expr:p,search_params:l,vector_type:101,output_fields:["objectId","sensorId","timestamp"]};return await Milvus.getSearchResults(e,c)}#f(e,t){let a=new Array;for(let r of e){let e=new Date(parseInt(r.timestamp,10)).toISOString();r.score>=t&&a.push({score:r.score,objectId:r.objectId,sensorId:r.sensorId,timestamp:e})}return a}#I(e){let t=new Map;for(let a of e){let e=`${a.sensorId} #-# ${a.objectId} #-# ${a.timestamp}`;if(t.has(e)){let r=t.get(e);a.score>r.highestScore&&(r.highestScore=a.score),r.count+=1,t.set(e,r)}else t.set(e,{highestScore:a.score,count:1})}return t}#M(e,t){let a=new Array;for(let r of e){let e=`${r.id} #-# ${r.timestamp}`;t.has(e)&&a.push({behavior:r,match:t.get(e)})}return a}
/** 
     * returns unique object count.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.timestamp
     * @param {string} input.timeWindowInMs
     * @param {Array<string>} [input.sensorIds] - Either sensorIds or place can be present. They are mutually exclusive. Exactly one sensorId should be in the array when objectId is present.
     * @param {?string} [input.objectId=null]
     * @param {string} [input.place] - Either sensorIds or place can be present. They are mutually exclusive.
     * @returns {Promise<Object>} An object containing unique object count is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {sensorIds: ["abc"], timestamp: "2023-01-12T11:20:10.000Z", timeWindowInMs: 100};
     * let mtmc = new mdx.Services.MTMC();
     * let uniqueObjectCount = await mtmc.getUniqueObjectCount(elastic, input);
     */async getUniqueObjectCount(e,t){const a={type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{timestamp:{type:"string"},timeWindowInMs:{type:"integer",minimum:1,default:MTMC.#e,errorMessage:{type:"timeWindowInMs is not an integer.",minimum:"timeWindowInMs can have a minimum value of 1."}},sensorIds:{type:["array","null"],items:{type:"string",minLength:1,errorMessage:{minLength:"Element of sensorIds array should have atleast 1 character."}},minItems:1,maxItems:MTMC.#r,default:null,errorMessage:{minItems:"sensorIds should have atleast 1 item.",maxItems:`sensorIds can have atmost ${MTMC.#r} items.`}},objectId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectId should have atleast 1 character."}},place:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"place should have atleast 1 character."}}},required:["timestamp"],not:{required:["sensorIds","place"]},if:{not:{properties:{objectId:{const:null}}}},then:{properties:{sensorIds:{type:"array",maxItems:1,errorMessage:{maxItems:"Input should have exactly 1 'sensorId' when 'objectId' is present."}}},required:["sensorIds"],errorMessage:{required:"'sensorIds' is required in input when 'objectId' is present."}},errorMessage:{required:"Input should have required properties 'fromTimestamp' and 'toTimestamp'.",not:"Input cannot have both 'sensorIds' and 'place'.",if:"Input should have exactly 1 'sensorId' when 'objectId' is present."}};let r=Validator.validateJsonSchema(t,a);if(!r.valid)throw new BadRequestError(r.reason);if(!Validator.isValidISOTimestamp(t.timestamp))throw new InvalidInputError("Invalid timestamp.");let s=0;if("Elasticsearch"===e.getName()){let a=await this.#p(e,t);return a.indexAbsent||(s=a.count),{uniqueObjectCount:s}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns unique objects.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.fromTimestamp
     * @param {string} input.toTimestamp
     * @param {Array<string>} [input.sensorIds] - Either sensorIds, globalId or place can be present. They are mutually exclusive. Exactly one sensorId should be in the array when objectId is present.
     * @param {?string} [input.objectId=null]
     * @param {string} [input.place] - Either sensorIds, globalId or place can be present. They are mutually exclusive.
     * @param {string} [input.globalId] - Either sensorIds, globalId or place can be present. They are mutually exclusive.
     * @param {number} [input.maxResultSize=25] - maxResultSize must be an integer. globalId and maxResultSize can't occur together.
     * @returns {Promise<Object>} Unique objects are returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {sensorIds: ["abc"], fromTimestamp: "2023-01-12T11:20:10.000Z", toTimestamp: "2023-01-12T14:20:10.000Z"};
     * let mtmc = new mdx.Services.MTMC();
     * let uniqueObjects = await mtmc.getUniqueObjects(elastic, input);
     */async getUniqueObjects(e,t){const a={type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{fromTimestamp:{type:"string"},toTimestamp:{type:"string"},sensorIds:{type:["array","null"],items:{type:"string",minLength:1,errorMessage:{minLength:"Element of sensorIds array should have atleast 1 character."}},minItems:1,maxItems:MTMC.#r,default:null,errorMessage:{minItems:"sensorIds should have atleast 1 item",maxItems:`sensorIds can have atmost ${MTMC.#r} items.`}},objectId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectId should have atleast 1 character."}},place:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"place should have atleast 1 character."}},globalId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"globalId should have atleast 1 character."}},maxResultSize:{type:"integer",minimum:1,maximum:MTMC.#a,default:MTMC.#t,errorMessage:{type:"maxResultSize is not an integer.",minimum:"maxResultSize can have a minimum value of 1.",maximum:`maxResultSize can have a maximum value of ${MTMC.#a}.`}}},required:["fromTimestamp","toTimestamp"],oneOf:[{required:["sensorIds"]},{required:["place"]},{required:["globalId"]},{not:{anyOf:[{required:["sensorIds"]},{required:["place"]},{required:["globalId"]}]}}],not:{required:["globalId","maxResultSize"]},if:{not:{properties:{objectId:{const:null}}}},then:{properties:{sensorIds:{type:"array",maxItems:1,errorMessage:{maxItems:"Input should have exactly 1 'sensorId' when 'objectId' is present."}}},required:["sensorIds"],errorMessage:{required:"'sensorIds' is required in input when 'objectId' is present."}},errorMessage:{required:"Input should have required properties 'fromTimestamp' and 'toTimestamp'.",not:"Input cannot have both 'globalId' and 'maxResultSize'.",oneOf:"Input cannot have a combination of 'sensorIds', 'place' and 'globalId' as they are mutually exclusive.",if:"Input should have exactly 1 'sensorId' when 'objectId' is present."}};let r=Validator.validateJsonSchema(t,a);if(!r.valid)throw new BadRequestError(r.reason);let s=Validator.isValidTimeRange(t.fromTimestamp,t.toTimestamp);if(!s.valid)throw new InvalidInputError(s.reason);if(!Number.isFinite(t.maxResultSize))throw new InvalidInputError("maxResultSize is not a finite integer.");let i=new Array;if("Elasticsearch"===e.getName()){let a=await this.#c(e,t);return a.indexAbsent||(i=Elasticsearch.searchResultFormatter(a.body)),{uniqueObjects:i}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns locations of matched objects.
     * @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] - Either behaviorIds or globalId should be present.
     * @param {string} [input.globalId] - Either behaviorIds or globalId should be present.
     * @returns {Promise<Object>} Locations of matched objects are returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {globalId: "12", fromTimestamp: "2023-01-12T11:20:10.000Z", toTimestamp: "2023-01-12T14:20:10.000Z"};
     * let mtmc = new mdx.Services.MTMC();
     * let behaviors = await mtmc.getLocationsOfMatchedBehaviors(elastic, input);
     */async getLocationsOfMatchedBehaviors(e,t){const a={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.getMaxBehaviorsInLocationQuery(),errorMessage:{minItems:"behaviorIds should have atleast 1 item",maxItems:`behaviorIds can have atmost ${Behavior.getMaxBehaviorsInLocationQuery()} items.`}},globalId:{type:"string",minLength:1,errorMessage:{minLength:"globalId should have atleast 1 character."}}},required:["fromTimestamp","toTimestamp"],oneOf:[{required:["behaviorIds"]},{required:["globalId"]}],errorMessage:{required:"Input should have required properties 'fromTimestamp' and 'toTimestamp'.",oneOf:"Exactly one of the following has to be present in the input: 'behaviorIds' or 'globalId'."}};let r=Validator.validateJsonSchema(t,a);if(!r.valid)throw new BadRequestError(r.reason);let s=Validator.isValidTimeRange(t.fromTimestamp,t.toTimestamp);if(!s.valid)throw new InvalidInputError(s.reason);if("globalId"in t){let{uniqueObjects:a}=await this.getUniqueObjects(e,{fromTimestamp:t.fromTimestamp,toTimestamp:t.toTimestamp,globalId:t.globalId});if(0==a.length)return{behaviors:new Array};t.behaviorIds=a[0].matched.map((e=>e.id))}let i=new Behavior;if(t.behaviorIds.length>Behavior.getMaxBehaviorsInLocationQuery()){let a=Math.ceil(t.behaviorIds.length/Behavior.getMaxBehaviorsInLocationQuery()),r=new Array;for(let e=0;e<a;e++)e===a-1?r.push(t.behaviorIds.slice(e*Behavior.getMaxBehaviorsInLocationQuery(),t.behaviorIds.length)):r.push(t.behaviorIds.slice(e*Behavior.getMaxBehaviorsInLocationQuery(),(e+1)*Behavior.getMaxBehaviorsInLocationQuery()));let s=r.map((a=>i.getLocationsOfBehaviors(e,{fromTimestamp:t.fromTimestamp,toTimestamp:t.toTimestamp,behaviorIds:a})));return{behaviors:(await Promise.all(s)).map((e=>e.behaviors)).flat()}}return await i.getLocationsOfBehaviors(e,{fromTimestamp:t.fromTimestamp,toTimestamp:t.toTimestamp,behaviorIds:t.behaviorIds})}
/** 
     * returns normalized embedding.
     * @public
     * @param {Array<number>} embedding
     * @returns {Array<number>} Normalized embedding is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * let mtmc = new mdx.Services.MTMC();
     * let normalizedEmbedding = mtmc.getNormalizedEmbedding(embedding);
     */getNormalizedEmbedding(e){let t=0;for(let a of e)t+=Math.pow(a,2);let a=Math.sqrt(t),r=new Array;for(let t of e)r.push(t/a);return r}
/** 
     * returns behavior info with similar embeddings from vectorDb.
     * @public
     * @async
     * @param {Database} vectorDb - Database Object
     * @param {Object} input - Input object.
     * @param {?string} [input.sensorIdOfExample=null] - Atleast one of (sensorIdOfExample, objectIdOfExample and timestampOfExampleBehavior) or sensorId should be present
     * @param {?string} [input.objectIdOfExample=null] - Atleast one of (sensorIdOfExample, objectIdOfExample and timestampOfExampleBehavior) or sensorId should be present
     * @param {?string} [input.timestampOfExampleBehavior=null] - Atleast one of (sensorIdOfExample, objectIdOfExample and timestampOfExampleBehavior) or sensorId should be present
     * @param {?string} [input.sensorId=null] - Atleast one of (sensorIdOfExample, objectIdOfExample and timestampOfExampleBehavior) or sensorId should be present
     * @param {Array<number>} input.embedding
     * @param {string} input.fromTimestamp
     * @param {string} input.toTimestamp
     * @param {number} [input.topKMatches=50] - topKMatches must be an integer.
     * @param {number} [input.matchScoreThreshold=0.65]
     * @returns {Promise<Array<Object>>} Info of behaviors with similar embeddings are returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const milvus = new mdx.Utils.Milvus({url: "milvus-url"},databaseConfigMap);
     * let input = {fromTimestamp: "2023-01-12T11:20:10.000Z", toTimestamp: "2023-01-12T14:20:10.000Z", embedding: embedding, sensorIdOfExample: "abc", objectIdOfExample: "120", timestampOfExampleBehavior: "2023-01-12T12:00:00.000Z" };
     * let mtmc = new mdx.Services.MTMC();
     * let similarBehaviorInfo = await mtmc.getBehaviorsSimilarToEmbeddings(milvus, input);
     */async getBehaviorsSimilarToEmbeddings(e,t){if(null==e)throw new InvalidInputError("A vector database like 'milvus' is required to find behaviors with similar embeddings.");const a={type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorIdOfExample:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorIdOfExample should have atleast 1 character."}},objectIdOfExample:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectIdOfExample should have atleast 1 character."}},timestampOfExampleBehavior:{type:["string","null"],default:null},sensorId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},embedding:{type:["array","null"],items:{type:"number",errorMessage:{type:"Element of embedding should be a number."}}},fromTimestamp:{type:"string"},toTimestamp:{type:"string"},topKMatches:{type:"integer",minimum:1,default:MTMC.#o,errorMessage:{type:"topKMatches is not an integer.",minimum:"topKMatches can have a minimum value of 1."}},matchScoreThreshold:{type:"number",minimum:0,maximum:1,default:MTMC.#n,errorMessage:{type:"matchScoreThreshold is not a number.",minimum:"matchScoreThreshold can have a minimum value of 0.",maximum:"matchScoreThreshold can have a maximum value of 1."}}},anyOf:[{required:["sensorIdOfExample","objectIdOfExample","timestampOfExampleBehavior"]},{required:["sensorId"]}],required:["embedding","fromTimestamp","toTimestamp"],errorMessage:{required:"Input should have required properties 'embedding', 'fromTimestamp' and 'toTimestamp'.",anyOf:"Atleast one of the following has to be present in the input: ('sensorIdOfExample' & 'objectIdOfExample') or 'sensorId'."}};let r=Validator.validateJsonSchema(t,a);if(!r.valid)throw new BadRequestError(r.reason);if("topKMatches"in t&&!Number.isFinite(t.topKMatches))throw new InvalidInputError("topKMatches is not a finite integer.");if("matchScoreThreshold"in t&&!Number.isFinite(t.matchScoreThreshold))throw new InvalidInputError("matchScoreThreshold is not a finite integer.");let s=Validator.isValidTimeRange(t.fromTimestamp,t.toTimestamp);if(!s.valid)throw new InvalidInputError(s.reason);if(null!=t.timestampOfExampleBehavior&&!Validator.isValidISOTimestamp(t.timestampOfExampleBehavior))throw new InvalidInputError("Invalid timestampOfExampleBehavior.");if(null==t.embedding)return logger.info("Embedding has null value."),new Array;if(0==t.embedding.length)return logger.info("Embedding is an empty array."),new Array;t.normalizedEmbedding=this.getNormalizedEmbedding(t.embedding);let i=new Array;if("Milvus"===e.getName())return i=await this.#g(e,t),i=this.#f(i,t.matchScoreThreshold),i;throw new InternalServerError(`Invalid vector database: ${e.getName()}.`)}
/** 
     * returns an object containing scored behaviors which are similar to the input example.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Database} vectorDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.sensorIdOfExample
     * @param {string} input.objectIdOfExample
     * @param {string} [input.timestampOfExample] - Either timestampOfExample or frameIdOfExample should be present
     * @param {string} [input.frameIdOfExample] - Either timestampOfExample or frameIdOfExample should be present
     * @param {?string} [input.sensorId=null]
     * @param {number} [input.hoursAgo=48] - hoursAgo must be an integer. Either (fromTimestamp and toTimestamp) or hoursAgo can be present. timeRange (fromTimestamp, toTimestamp) and hoursAgo are mutually exclusive.
     * @param {string} [input.fromTimestamp] - Either (fromTimestamp and toTimestamp) or hoursAgo can be present. timeRange (fromTimestamp, toTimestamp) and hoursAgo are mutually exclusive.
     * @param {string} [input.toTimestamp] - Either (fromTimestamp and toTimestamp) or hoursAgo can be present. timeRange (fromTimestamp, toTimestamp) and hoursAgo are mutually exclusive.
     * @param {number} [input.topKMatches=50] - topKMatches must be an integer.
     * @param {number} [input.matchScoreThreshold=0.65]
     * @returns {Promise<Object>} Scored behaviors which are similar to the input example are returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * const milvus = new mdx.Utils.Milvus({url: "milvus-url"},databaseConfigMap);
     * let input = {sensorIdOfExample: "abc", objectIdOfExample: "120", timestampOfExample: "2023-01-12T12:02:00.000Z"};
     * let mtmc = new mdx.Services.MTMC();
     * let behaviors = await mtmc.getSimilarBehaviorsBasedOnExample(elastic, milvus, input);
     */async getSimilarBehaviorsBasedOnExample(e,t,a){if(null==t)throw new InvalidInputError("A vector database like 'milvus' is required to find behaviors similar to a given example.");const r={type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorIdOfExample:{type:"string",minLength:1,errorMessage:{minLength:"sensorIdOfExample should have atleast 1 character."}},objectIdOfExample:{type:"string",minLength:1,errorMessage:{minLength:"objectIdOfExample should have atleast 1 character."}},timestampOfExample:{type:"string"},frameIdOfExample:{type:"string",minLength:1,errorMessage:{minLength:"frameIdOfExample should have atleast 1 character."}},sensorId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},hoursAgo:{type:"integer",minimum:1,maximum:MTMC.#i,default:MTMC.#s,errorMessage:{type:"hoursAgo is not an integer.",minimum:"hoursAgo can have a minimum value of 1.",maximum:`hoursAgo can have a maximum value of ${MTMC.#i}.`}},topKMatches:{type:"integer",minimum:1,default:MTMC.#o,errorMessage:{type:"topKMatches is not an integer.",minimum:"topKMatches can have a minimum value of 1."}},matchScoreThreshold:{type:"number",minimum:0,maximum:1,default:MTMC.#n,errorMessage:{type:"matchScoreThreshold is not a number.",minimum:"matchScoreThreshold can have a minimum value of 0.",maximum:"matchScoreThreshold can have a maximum value of 1."}},fromTimestamp:{type:"string"},toTimestamp:{type:"string"}},required:["sensorIdOfExample","objectIdOfExample"],oneOf:[{required:["timestampOfExample"]},{required:["frameIdOfExample"]}],allOf:[{not:{required:["fromTimestamp","hoursAgo"]},errorMessage:"'fromTimestamp' and 'hoursAgo' can't exist together in the input."},{not:{required:["toTimestamp","hoursAgo"]},errorMessage:"'toTimestamp' and 'hoursAgo' can't exist together in the input."}],dependentRequired:{fromTimestamp:["toTimestamp"],toTimestamp:["fromTimestamp"]},errorMessage:{required:"Input should have required properties 'sensorIdOfExample' and 'objectIdOfExample'.",oneOf:"Exactly one of the following has to be present in the input: 'timestampOfExample' or 'frameIdOfExample'.",dependentRequired:"The input should either have both 'fromTimestamp' and 'toTimestamp' or neither of them."}};let s=Validator.validateJsonSchema(a,r);if(!s.valid)throw new BadRequestError(s.reason);if("topKMatches"in a&&!Number.isFinite(a.topKMatches))throw new InvalidInputError("topKMatches is not a finite integer.");if("matchScoreThreshold"in a&&!Number.isFinite(a.matchScoreThreshold))throw new InvalidInputError("matchScoreThreshold is not a finite integer.");if("timestampOfExample"in a){if(!Validator.isValidISOTimestamp(a.timestampOfExample))throw new InvalidInputError("Invalid timestampOfExample.")}else a.timestampOfExample=null;if("fromTimestamp"in a&&"toTimestamp"in a){let e=Validator.isValidTimeRange(a.fromTimestamp,a.toTimestamp);if(!e.valid)throw new InvalidInputError(e.reason);let t=new Date;if(Utils.tsCompare(a.fromTimestamp,">=",t.toISOString()))throw new InvalidInputError("Invalid fromTimestamp. Input refers to a future timestamp.");if(Utils.tsCompare(a.toTimestamp,">=",t.toISOString()))throw new InvalidInputError("Invalid toTimestamp. Input refers to a future timestamp.");if((t-new Date(a.fromTimestamp))/36e5>MTMC.#i)throw new InvalidInputError(`Invalid time range. fromTimestamp can take values upto ${MTMC.#i} hours before current timestamp.`)}else{if(!Number.isFinite(a.hoursAgo))throw new InvalidInputError("hoursAgo is not a finite integer.");let e=new Date;a.fromTimestamp=new Date(e-60*a.hoursAgo*60*1e3).toISOString(),a.toTimestamp=e.toISOString()}let i=new Array,o=new Frames;if(null==a.timestampOfExample&&(a.timestampOfExample=await o.getLatestTimestampOfFrameWithObject(e,{sensorId:a.sensorIdOfExample,objectId:a.objectIdOfExample,frameId:a.frameIdOfExample}),null==a.timestampOfExample))return logger.info(`Invalid input. No Record was found containing: sensorId - ${a.sensorIdOfExample}, objectId - ${a.objectIdOfExample} and frameId - ${a.frameIdOfExample}.`),{behaviors:i};let n=new Behavior,[m,l]=await Promise.all([o.getEmbedding(e,{sensorId:a.sensorIdOfExample,objectId:a.objectIdOfExample,timestamp:a.timestampOfExample}),n.getTimestampOfBehavior(e,{sensorId:a.sensorIdOfExample,objectId:a.objectIdOfExample,timestampWithinBehavior:a.timestampOfExample})]);if(null==l)return logger.info("Invalid input. Example behavior doesn't exist."),{behaviors:i};if("Elasticsearch"===e.getName()){let r=await this.getBehaviorsSimilarToEmbeddings(t,{embedding:m,sensorIdOfExample:a.sensorIdOfExample,objectIdOfExample:a.objectIdOfExample,timestampOfExampleBehavior:l,sensorId:a.sensorId,fromTimestamp:a.fromTimestamp,toTimestamp:a.toTimestamp,topKMatches:a.topKMatches,matchScoreThreshold:a.matchScoreThreshold}),s=this.#I(r),o=new Behavior,n=new Array;for(let e of r)n.push({sensorId:e.sensorId,objectId:e.objectId,timestamp:e.timestamp});let{behaviors:u}=await o.getBehaviorsUsingIdsAndTimestamp(e,{behaviorInfo:n});return i=this.#M(u,s),{behaviors:i}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * consumes incoming rtls messages.
     * @public
     * @async
     * @param {MessageBroker} messageBroker - MessageBroker Object
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const kafka = new mdx.Utils.Kafka({brokers: ["kafka-broker-url"]}, kafkaConfigMap);
     * let mtmcObject = new mdx.Services.MTMC();
     * await mtmcObject.consumeRTLSMessages(kafka);
     */async consumeRTLSMessages(e){if(null==e)throw new InvalidInputError("A message broker like 'kafka' is required to consume RTLS messages.");if("Kafka"!==e.getName())throw new InternalServerError(`Invalid message broker: ${e.getName()}.`);await this.#v(e)}async#v(e){const t=e.getClient(),a=Kafka.getTopic("rtls"),r=`${a}-web-api`,s=await Kafka.getConsumer(t,a,r);await s.run({eachMessage:async({topic:e,partition:t,message:a})=>{let r=JSON.parse(a.value.toString()),s=objectCountWithLocationsCache.get(r.place);(null==s||Utils.tsCompare(r.timestamp,">=",s.timestamp))&&objectCountWithLocationsCache.set(r.place,r)}})}
/** 
     * consumes incoming AMR messages.
     * @public
     * @async
     * @param {MessageBroker} messageBroker - MessageBroker Object
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const kafka = new mdx.Utils.Kafka({brokers: ["kafka-broker-url"]}, kafkaConfigMap);
     * let mtmcObject = new mdx.Services.MTMC();
     * await mtmcObject.consumeAMRMessages(kafka);
     */async consumeAMRMessages(e,t){if(null==e)throw new InvalidInputError("A message broker like 'kafka' is required to consume AMR messages.");if("Kafka"!==e.getName())throw new InternalServerError(`Invalid message broker: ${e.getName()}.`);await this.#b(e,t)}#T(e,t){let a=JSON.parse(e.value.toString()),r=amrRoutes.get(a.place)||new Map;for(let e of a.events)r.set(e.objectId,{timestamp:a.timestamp,event:e});let s=new Date,i=new Date(s-1e3*t.amrRetentionInSec).toISOString(),o=new Set;for(let[e,t]of r)Utils.tsCompare(t.timestamp,"<",i)&&o.add(e);for(let e of o)r.delete(e);amrRoutes.set(a.place,r)}#w(e,t){let a=JSON.parse(e.value.toString()),r=amrCountWithLocationsCache.get(a.place)||new Array;r.push(a);let s=new Date(currentTimestamp-1e3*t.amrRetentionInSec).toISOString(),i=0;for(let e of r){if(!Utils.tsCompare(e.timestamp,"<",s))break;i++}i>0&&(r=i==r.length?new Array:r.slice(i)),amrCountWithLocationsCache.set(a.place,r)}async#b(e,t){const a=e.getClient(),r=Kafka.getTopic("amr"),s=`${r}-web-api`,i=await Kafka.getConsumer(a,r,s);await i.run({eachMessage:async({topic:e,partition:a,message:r})=>{let s=r.headers.type;void 0===s?logger.warning("[AMR DATA ISSUE] Header 'type' has to be present for AMR data."):"amrLocations"===s.toString()?this.#w(r,t):"routeChange"===s.toString()?this.#T(r,t):logger.warning("[AMR DATA ISSUE] Invalid value for header 'type'.")}})}async#y(e,{place:t,timestamp:a,timeWindowInMs:r}){const s=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("rtls")}`;let i=deepcopy(filterTemplate);i.query.bool.must.push({range:{timestamp:{lte:a,gte:new Date(new Date(a)-r).toISOString()}}}),i.query.bool.must.push({term:{"place.keyword":t}});let o={index:s,body:i,sort:"timestamp:desc",size:1,_sourceExcludes:["Id","type"]};return await Elasticsearch.getSearchResults(e.getClient(),o,!1)}#x({place:e,timestamp:t,timeWindowInMs:a}){let r=objectCountWithLocationsCache.get(e);return null==r||Utils.tsCompare(r.timestamp,"<",new Date(new Date(t)-a).toISOString())?null:r}#E({place:e,amrTimestampWindowInMs:t,timestampOfRTLSRecord:a}){let r=amrCountWithLocationsCache.get(e)||new Array,s=new Date(new Date(a)-t).toISOString(),i=null;for(let e of r)if(!Utils.tsCompare(e.timestamp,"<",s)){if(!Utils.tsCompare(e.timestamp,"<=",a)){let r=new Date(new Date(a).getTime()+t).toISOString();if(Utils.tsCompare(e.timestamp,"<=",r))if(null==i)i=e;else{let t=new Date(a)-new Date(i.timestamp);new Date(e.timestamp)-new Date(a)<=t&&(i=e)}break}(null==i||Utils.tsCompare(e.timestamp,">=",i.timestamp))&&(i=e)}return i}#O({place:e,amrWithoutRtlsMinTimestampThresholdInMs:t,timestamp:a,amrRouteMinTimestampThresholdInMs:r,amrRouteMaxTimestampThresholdInMs:s,timestampOfRTLSRecord:i=null}){let o=amrRoutes.get(e)||new Map,n=new Array,m=null!=i?i:a,l=null!=i?new Date(new Date(m)-r).toISOString():new Date(new Date(m)-t).toISOString(),u=null!=i?new Date(new Date(i).getTime()+s).toISOString():a;for(let[e,t]of o)Utils.tsCompare(t.timestamp,">=",l)&&Utils.tsCompare(t.timestamp,"<=",u)&&n.push(t.event);return n}#S({place:e,timestamp:t,amrWithoutRtlsMinTimestampThresholdInMs:a}){let r=amrCountWithLocationsCache.get(e)||new Array,s=new Date(new Date(t)-a).toISOString(),i=null;for(let e of r)if(!Utils.tsCompare(e.timestamp,"<",s)){if(!Utils.tsCompare(e.timestamp,"<=",timestampOfRTLSRecord))break;(null==i||Utils.tsCompare(e.timestamp,">=",i.timestamp))&&(i=e)}return i}
/** 
     * returns an object containing unique object count of a place with object locations.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {MessageBroker} messageBroker - MessageBroker Object
     * @param {Object} input - Input object.
     * @param {string} input.place
     * @param {?string} [input.timestamp=null]
     * @param {number} [input.timeWindowInMs=3000] - timeWindowInMs must be an integer.
     * @param {number} [input.amrTimestampWindowInMs=200] - amrTimestampWindowInMs must be an integer.
     * @param {number} [input.amrWithoutRtlsMinTimestampThresholdInMs=2000] - amrWithoutRtlsMinTimestampThresholdInMs must be an integer.
     * @param {number} [input.amrRouteMinTimestampThresholdInMs=1000] - amrRouteMinTimestampThresholdInMs must be an integer.
     * @param {number} [input.amrRouteMaxTimestampThresholdInMs=200] - amrRouteMaxTimestampThresholdInMs must be an integer.
     * @returns {Promise<Object>} Unique object count of a place along with object locations is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * const kafka = new mdx.Utils.Kafka({brokers: ["kafka-broker-url"]}, kafkaConfigMap);
     * let input = {place: "building=abc/room=xyz"};
     * let mtmc = new mdx.Services.MTMC();
     * let uniqueObjectCountWithLocations = await mtmc.getUniqueObjectCountWithLocations(elastic,kafka,input);
     */async getUniqueObjectCountWithLocations(e,t,a){const r={type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{place:{type:"string",minLength:1,errorMessage:{minLength:"place should have atleast 1 character."}},timestamp:{type:["string","null"],default:null},timeWindowInMs:{type:"integer",minimum:1,default:MTMC.#m,errorMessage:{type:"timeWindowInMs is not an integer.",minimum:"timeWindowInMs can have a minimum value of 1."}},amrTimestampWindowInMs:{type:"integer",minimum:0,default:MTMC.#l,errorMessage:{type:"amrTimestampWindowInMs is not an integer.",minimum:"amrTimestampWindowInMs can have a minimum value of 0."}},amrWithoutRtlsMinTimestampThresholdInMs:{type:"integer",minimum:0,default:MTMC.#h,errorMessage:{type:"amrWithoutRtlsMinTimestampThresholdInMs is not an integer.",minimum:"amrWithoutRtlsMinTimestampThresholdInMs can have a minimum value of 0."}},amrRouteMinTimestampThresholdInMs:{type:"integer",minimum:0,default:MTMC.#u,errorMessage:{type:"amrRouteMinTimestampThresholdInMs is not an integer.",minimum:"amrRouteMinTimestampThresholdInMs can have a minimum value of 0."}},amrRouteMaxTimestampThresholdInMs:{type:"integer",minimum:0,default:MTMC.#d,errorMessage:{type:"amrRouteMaxTimestampThresholdInMs is not an integer.",minimum:"amrRouteMaxTimestampThresholdInMs can have a minimum value of 0."}}},required:["place"],errorMessage:{required:"Input should have required properties 'place'."}};let s=Validator.validateJsonSchema(a,r);if(!s.valid)throw new BadRequestError(s.reason);if(!Number.isFinite(a.timeWindowInMs))throw new InvalidInputError("timeWindowInMs is not a finite integer.");let i={place:a.place,timestamp:null,objectCounts:new Array,locationsOfObjects:new Array};if(null!=a.timestamp){if(!Validator.isValidISOTimestamp(a.timestamp))throw new InvalidInputError("Invalid timestamp.");if(i.timestamp=a.timestamp,"Elasticsearch"===e.getName()){let t=await this.#y(e,a);return t.indexAbsent||(t=Elasticsearch.searchResultFormatter(t.body),t.length>0&&(i=t[0])),i}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}{if(null==t)throw new InvalidInputError("A message broker like 'kafka' is required to consume messages which provide real time locations and object count.");let e=(new Date).getTime()/1e3,r=new Date(1e3*Math.ceil(e));a.timestamp=r.toISOString(),i.timestamp=a.timestamp;let s=this.#x(a);if(null!=s){i=s;let e=this.#E({place:a.place,timestampOfRTLSRecord:i.timestamp,amrTimestampWindowInMs:a.amrTimestampWindowInMs});if(null!=e){for(let t of e.objectCounts)i.objectCounts.push(t);for(let t of e.locationsOfObjects)i.locationsOfObjects.push(t)}let t=this.#O({place:a.place,amrRouteMinTimestampThresholdInMs:a.amrRouteMinTimestampThresholdInMs,amrRouteMaxTimestampThresholdInMs:a.amrRouteMaxTimestampThresholdInMs,timestampOfRTLSRecord:i.timestamp,timestamp:a.timestamp,amrWithoutRtlsMinTimestampThresholdInMs:a.amrWithoutRtlsMinTimestampThresholdInMs});t.length>0&&(i.events=t)}else{let e=this.#S({place:a.place,timestamp:a.timestamp,amrWithoutRtlsMinTimestampThresholdInMs:a.amrWithoutRtlsMinTimestampThresholdInMs});if(null!=e){i.timestamp=e.timestamp;for(let t of e.objectCounts)i.objectCounts.push(t);for(let t of e.locationsOfObjects)i.locationsOfObjects.push(t)}let t=this.#O({place:a.place,amrRouteMinTimestampThresholdInMs:a.amrRouteMinTimestampThresholdInMs,amrRouteMaxTimestampThresholdInMs:a.amrRouteMaxTimestampThresholdInMs,timestamp:a.timestamp,amrWithoutRtlsMinTimestampThresholdInMs:a.amrWithoutRtlsMinTimestampThresholdInMs});t.length>0&&(i.events=t)}return i}}}module.exports=MTMC;