Metrics/Occupancy.js

/**Copyright (c) 2009-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.**/
"use strict";const deepcopy=require("deepcopy"),Database=require("../Utils/Database"),filterTemplate=require("../queryTemplates/filter.json"),Elasticsearch=require("../Utils/Elasticsearch"),Validator=require("../Utils/Validator"),Histogram=require("../Utils/Histogram"),Utils=require("../Utils/Utils"),InternalServerError=require("../Errors/InternalServerError"),InvalidInputError=require("../Errors/InvalidInputError"),BadRequestError=require("../Errors/BadRequestError"),winston=require("winston"),logger=winston.createLogger({transports:[new winston.transports.Console({timestamp:!0})],exitOnError:!1});
/** 
 * Class which defines Occupancy
 * @memberof mdxWebApiCore.Metrics
 * */
class Occupancy{static#e=2e3;static#t=3;async#r(e,{place:t,timestamp:r,objectType:a}){const s=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("occupancyReset")}`;let i=deepcopy(filterTemplate);i.query.bool.must.push({range:{timestamp:{lte:r}}}),i.query.bool.must.push({term:{"place.keyword":t}}),i.query.bool.must.push({term:{"objectType.keyword":a}});let o={index:s,body:i,size:1,sort:"timestamp:desc"};return await Elasticsearch.getSearchResults(e.getClient(),o,!1)}async#a(e,t){let r={occupancyReset:null,timestamp:null};if("Elasticsearch"===e.getName()){let a=await this.#r(e,t);return a.indexAbsent||(a=Elasticsearch.searchResultFormatter(a.body),a.length>0&&(r.occupancyReset=a[0].occupancyReset,r.timestamp=a[0].timestamp)),r}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#s(e,{place:t,objectType:r,fromTimestamp:a,toTimestamp:s}){const i=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("tripwire")}`;let o=deepcopy(filterTemplate);o.query.bool.must.push({term:{"object.type.keyword":r}}),o.query.bool.must.push({term:{"place.name.keyword":t}}),o.query.bool.must.push({range:{timestamp:{lte:s}}}),null!=a&&o.query.bool.must.push({range:{end:{gte:a}}}),o.aggs={eventTypes:{terms:{field:"event.type.keyword"}}};let n={index:i,body:o,size:0};return await Elasticsearch.getSearchResults(e.getClient(),n,!1)}async#i(e,t){let r=0;if("Elasticsearch"===e.getName()){let a=new Map([["IN",0],["OUT",0]]),s=await this.#s(e,t);if(!s.indexAbsent)for(let e of s.body.aggregations.eventTypes.buckets)a.set(e.key,e.doc_count);r=a.get("IN")-a.get("OUT"),r<0&&(logger.warn("[DATA] Occupancy based on events is less than 0 (i.e. there are more 'OUT' than 'IN ' events). Resetting occupancy based on events to 0."),r=0);let i=new Array;for(let[e,t]of a)i.push({type:e,count:t});return{occupancy:r,events:i}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}
/** 
     * returns an object containing occupancy based on tripwire events.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.place
     * @param {string} input.timestamp
     * @param {string} [input.objectType="Person"]
     * @returns {Promise<Object>} Occupancy based on tripwire events is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {place: "building=abc/room=xyz", timestamp: "2023-01-12T11:20:10.000Z"};
     * let occupancyObject = new mdx.Metrics.Occupancy();
     * let occupancyResult = await occupancyObject.getOccupancyBasedOnTripwireEvents(elastic,input);
     */async getOccupancyBasedOnTripwireEvents(e,t){let r=Validator.validateJsonSchema(t,{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"},objectType:{type:"string",minLength:1,default:"Person",errorMessage:{minLength:"objectType should have atleast 1 character."}}},required:["place","timestamp"],errorMessage:{required:"Input should have required properties 'place' and 'timestamp'."}});if(!r.valid)throw new BadRequestError(r.reason);if(!Validator.isValidISOTimestamp(t.timestamp))throw new InvalidInputError("Invalid timestamp.");let a=await this.#a(e,t),s=0,i=a.occupancyReset,o=null;null!=i&&(o=a.timestamp,s=i);let n=await this.#i(e,{place:t.place,fromTimestamp:o,toTimestamp:t.timestamp,objectType:t.objectType});return s+=n.occupancy,{occupancy:s,details:{occupancyReset:null!=i?{resetValue:i,resetTimestamp:o}:null,events:n.events,objectType:t.objectType}}}async#o(e,{place:t,timestamp:r,occupancyReset:a,objectType:s}){let i={index:`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("occupancyReset")}`,body:{place:t,objectType:s,timestamp:r,occupancyReset:a}};return await e.getClient().index(i)}
/** 
     * returns a success message if reset value was inserted successfully.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.place
     * @param {string} input.timestamp
     * @param {number} input.occupancyReset - occupancyReset must be an integer. 
     * @param {string} [input.objectType="Person"]
     * @returns {Promise<Object>} A success message is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {place: "building=abc/room=xyz", timestamp: "2023-01-12T11:20:10.000Z", occupancyReset: 4};
     * let occupancyObject = new mdx.Metrics.Occupancy();
     * let result = await occupancyObject.resetOccupancy(elastic,input);
     */async resetOccupancy(e,t){let r=Validator.validateJsonSchema(t,{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"},occupancyReset:{type:"integer",minimum:0,errorMessage:{type:"occupancyReset is not an integer.",minimum:"occupancyReset can have a minimum value of 0."}},objectType:{type:"string",minLength:1,default:"Person",errorMessage:{minLength:"objectType should have atleast 1 character."}}},required:["place","timestamp","occupancyReset"],errorMessage:{required:"Input should have required properties 'place', 'timestamp' and 'occupancyReset'."}},!1);if(!r.valid)throw new BadRequestError(r.reason);if(!Validator.isValidISOTimestamp(t.timestamp))throw new InvalidInputError("Invalid timestamp.");if(!Number.isFinite(t.occupancyReset))throw new InvalidInputError("occupancyReset is not a finite integer.");if("Elasticsearch"===e.getName())return await this.#o(e,t),{success:!0};throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#n(e,{sensorId:t,fromTimestamp:r,toTimestamp:a,objectType:s}){const i=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("frames")}`;let o=deepcopy(filterTemplate);o.query.bool.must.push({range:{timestamp:{lte:a,gte:r}}}),o.query.bool.must.push({term:{"sensorId.keyword":t}}),o.aggs={fov:{nested:{path:"fov"},aggs:{searchAggFilter:{filter:{bool:{filter:[]}},aggs:{objectType:{terms:{field:"fov.type.keyword",size:1e3},aggs:{avgCount:{avg:{field:"fov.count"}}}}}}}}},null!=s&&o.aggs.fov.aggs.searchAggFilter.filter.bool.filter.push({term:{"fov.type.keyword":s}});let n={index:i,body:o,size:0};return await Elasticsearch.getSearchResults(e.getClient(),n,!1)}
/** 
     * returns an object containing average fov occupancy.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.sensorId
     * @param {string} input.fromTimestamp
     * @param {string} input.toTimestamp
     * @param {?string} [input.objectType=null]
     * @returns {Promise<Object>} Average Fov Occupancy 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 occupancyObject = new mdx.Metrics.Occupancy();
     * let occupancyResult = await occupancyObject.getAverageFovOccupancy(elastic,input);
     */async getAverageFovOccupancy(e,t){let r=Validator.validateJsonSchema(t,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:"string",minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},fromTimestamp:{type:"string"},toTimestamp:{type:"string"},objectType:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectType should have atleast 1 character."}}},required:["sensorId","fromTimestamp","toTimestamp"],errorMessage:{required:"Input should have required properties 'sensorId', 'fromTimestamp' and 'toTimestamp'."}});if(!r.valid)throw new BadRequestError(r.reason);let a=Validator.isValidTimeRange(t.fromTimestamp,t.toTimestamp);if(!a.valid)throw new InvalidInputError(a.reason);let s=new Array;if("Elasticsearch"===e.getName()){let r=await this.#n(e,t);if(!r.indexAbsent)for(let e of r.body.aggregations.fov.searchAggFilter.objectType.buckets)s.push({type:e.key,averageCount:parseFloat(e.avgCount.value.toFixed(2))});return{fovOccupancy:s}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#l(e,{sensorId:t,fromTimestamp:r,toTimestamp:a,bucketSizeInSec:s,objectType:i}){const o=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("frames")}`;let n=deepcopy(filterTemplate);n.query.bool.must.push({range:{timestamp:{gte:r,lte:a}}}),n.query.bool.must.push({term:{"sensorId.keyword":t}}),n.aggs={eventsOverTime:{date_histogram:{field:"timestamp",fixed_interval:`${s}s`},aggs:{fov:{nested:{path:"fov"},aggs:{searchAggFilter:{filter:{bool:{filter:[]}},aggs:{objectType:{terms:{field:"fov.type.keyword",size:1e3},aggs:{avgCount:{avg:{field:"fov.count"}}}}}}}}}}},null!=i&&n.aggs.eventsOverTime.aggs.fov.aggs.searchAggFilter.filter.bool.filter.push({term:{"fov.type.keyword":i}});let l={index:o,body:n,size:0};return await Elasticsearch.getSearchResults(e.getClient(),l,!1)}#m(e,t){let r=new Set;for(let t of e.objects)r.add(t.type);let a=Utils.setDifference(t,r);for(let t of a)e.objects.push({type:t,averageCount:0});return e}
/** 
     * returns an object containing histogram of average fov occupancy.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.sensorId
     * @param {string} [input.fromTimestamp] - Either fromTimestamp and toTimestamp should be present together or minutesAgo should be present.
     * @param {string} [input.toTimestamp] - Either fromTimestamp and toTimestamp should be present together or minutesAgo should be present.
     * @param {number} [input.minutesAgo] - minutesAgo must be an integer. Either fromTimestamp and toTimestamp should be present together or minutesAgo should be present.
     * @param {number} [input.bucketCount=20] - bucketCount must be an integer.
     * @param {?string} [input.objectType=null]
     * @returns {Promise<Object>} Histogram of Average Fov Occupancy 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",bucketCount:24};
     * let occupancyObject = new mdx.Metrics.Occupancy();
     * let fovHistogramResult = await occupancyObject.getHistogramOfAverageFovOccupancy(elastic,input);
     */async getHistogramOfAverageFovOccupancy(e,t){const 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."}},fromTimestamp:{type:["string","null"],default:null},toTimestamp:{type:["string","null"],default:null},minutesAgo:{type:["integer","null"],minimum:1,default:null,errorMessage:{type:"minutesAgo is not an integer.",minimum:"minutesAgo can have a minimum value of 1."}},bucketCount:{type:"integer",minimum:1,default:Histogram.getDefaultHistogramBucketCount(),maximum:Histogram.getMaxHistogramBucketCount(),errorMessage:{type:"bucketCount is not an integer.",minimum:"bucketCount can have a minimum value of 1.",maximum:`bucketCount can have a maximum value of ${Histogram.getMaxHistogramBucketCount()}.`}},objectType:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectType should have atleast 1 character."}}},required:["sensorId"],oneOf:[{required:["minutesAgo"],not:{anyOf:[{required:["fromTimestamp","minutesAgo"]},{required:["toTimestamp","minutesAgo"]}]}},{required:["fromTimestamp","toTimestamp"],not:{required:["minutesAgo"]}}],errorMessage:{required:"Input should have required properties 'sensorId'.",oneOf:"One of the following combinations should be present in input: ('fromTimestamp', 'toTimestamp') or ('minutesAgo')."}};let a=Validator.validateJsonSchema(t,r);if(!a.valid)throw new BadRequestError(a.reason);if(null!=t.fromTimestamp&&null!=t.toTimestamp){let e=Validator.isValidTimeRange(t.fromTimestamp,t.toTimestamp);if(!e.valid)throw new InvalidInputError(e.reason)}else if(null!=t.minutesAgo){if(!Number.isFinite(t.minutesAgo))throw new InvalidInputError("minutesAgo is not a finite integer.");t.toTimestamp=(new Date).toISOString(),t.fromTimestamp=new Date(new Date(t.toTimestamp)-60*t.minutesAgo*1e3).toISOString()}if(!Number.isFinite(t.bucketCount))throw new InvalidInputError("bucketCount is not a finite integer.");t.bucketSizeInSec=Histogram.computeBucketSizeInSec({bucketCount:t.bucketCount,fromTimestamp:t.fromTimestamp,toTimestamp:t.toTimestamp});let s=new Array;if("Elasticsearch"===e.getName()){let r=await this.#l(e,t),a=new Set,i=new Map;if(!r.indexAbsent)for(let e of r.body.aggregations.eventsOverTime.buckets){let r=e.key_as_string,s={start:r,end:new Date(new Date(r).valueOf()+1e3*t.bucketSizeInSec).toISOString(),objects:new Array};for(let t of e.fov.searchAggFilter.objectType.buckets)s.objects.push({type:t.key,averageCount:parseFloat(t.avgCount.value.toFixed(2))}),a.add(t.key);i.set(r,s)}let o=Histogram.getEmptyHistogram({bucketSizeInSec:t.bucketSizeInSec,fromTimestamp:t.fromTimestamp,toTimestamp:t.toTimestamp});if(0===i.size){s=o;for(let e of s)e.objects=new Array}else for(let e of o)i.has(e.start)?e=i.get(e.start):e.objects=new Array,e=this.#m(e,a),s.push(e);return Utils.tsCompare(s[0].start,"<",t.fromTimestamp)&&(s[0].start=t.fromTimestamp),Utils.tsCompare(s[s.length-1].end,">",t.toTimestamp)&&(s[s.length-1].end=t.toTimestamp),{bucketSizeInSec:t.bucketSizeInSec,histogram:s}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#u(e,{sensorId:t,roiId:r,fromTimestamp:a,toTimestamp:s,objectType:i}){const o=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("frames")}`;let n=deepcopy(filterTemplate);n.query.bool.must.push({range:{timestamp:{lte:s,gte:a}}}),n.query.bool.must.push({term:{"sensorId.keyword":t}}),null!=r&&n.query.bool.must.push({nested:{path:"rois",query:{bool:{must:[{term:{"rois.id.keyword":r}}]}}}}),n.aggs={rois:{nested:{path:"rois"},aggs:{searchAggFilter:{filter:{bool:{filter:[]}},aggs:{roiIds:{terms:{field:"rois.id.keyword",size:40},aggs:{objectType:{terms:{field:"rois.type.keyword",size:1e3},aggs:{avgCount:{avg:{field:"rois.count"}}}}}}}}}}},null!=r&&n.aggs.rois.aggs.searchAggFilter.filter.bool.filter.push({term:{"rois.id.keyword":r}}),null!=i&&n.aggs.rois.aggs.searchAggFilter.filter.bool.filter.push({term:{"rois.type.keyword":i}});let l={index:o,body:n,size:0};return await Elasticsearch.getSearchResults(e.getClient(),l,!1)}
/** 
     * returns an object containing average roi occupancy.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.sensorId
     * @param {?string} [input.roiId=null]
     * @param {string} input.fromTimestamp
     * @param {string} input.toTimestamp
     * @param {?string} [input.objectType=null]
     * @returns {Promise<Object>} Average Roi Occupancy 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 occupancyObject = new mdx.Metrics.Occupancy();
     * let occupancyResult = await occupancyObject.getAverageRoiOccupancy(elastic,input);
     */async getAverageRoiOccupancy(e,t){let r=Validator.validateJsonSchema(t,{type:"object",additionalProperties:{not:!0,errorMessage:"Invalid additional Input ${0#}."},properties:{sensorId:{type:"string",minLength:1,errorMessage:{minLength:"sensorId should have atleast 1 character."}},roiId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"roiId should have atleast 1 character."}},fromTimestamp:{type:"string"},toTimestamp:{type:"string"},objectType:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectType should have atleast 1 character."}}},required:["sensorId","fromTimestamp","toTimestamp"],errorMessage:{required:"Input should have required properties 'sensorId', 'fromTimestamp' and 'toTimestamp'."}});if(!r.valid)throw new BadRequestError(r.reason);let a=Validator.isValidTimeRange(t.fromTimestamp,t.toTimestamp);if(!a.valid)throw new InvalidInputError(a.reason);let s=new Array;if("Elasticsearch"===e.getName()){let r=await this.#u(e,t);if(!r.indexAbsent)for(let e of r.body.aggregations.rois.searchAggFilter.roiIds.buckets){let t={roiId:e.key,objects:new Array};for(let r of e.objectType.buckets)t.objects.push({type:r.key,averageCount:parseFloat(r.avgCount.value.toFixed(2))});s.push(t)}return{roiOccupancy:s}}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#c(e,{sensorId:t,roiId:r,fromTimestamp:a,toTimestamp:s,bucketSizeInSec:i,objectType:o}){const n=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("frames")}`;let l=deepcopy(filterTemplate);l.query.bool.must.push({range:{timestamp:{lte:s,gte:a}}}),l.query.bool.must.push({term:{"sensorId.keyword":t}}),null!=r&&l.query.bool.must.push({nested:{path:"rois",query:{bool:{must:[{term:{"rois.id.keyword":r}}]}}}}),l.aggs={eventsOverTime:{date_histogram:{field:"timestamp",fixed_interval:`${i}s`},aggs:{rois:{nested:{path:"rois"},aggs:{searchAggFilter:{filter:{bool:{filter:[]}},aggs:{roiIds:{terms:{field:"rois.id.keyword",size:40},aggs:{objectType:{terms:{field:"rois.type.keyword",size:1e3},aggs:{avgCount:{avg:{field:"rois.count"}}}}}}}}}}}}},null!=r&&l.aggs.eventsOverTime.aggs.rois.aggs.searchAggFilter.filter.bool.filter.push({term:{"rois.id.keyword":r}}),null!=o&&l.aggs.eventsOverTime.aggs.rois.aggs.searchAggFilter.filter.bool.filter.push({term:{"rois.type.keyword":o}});let m={index:n,body:l,size:0};return await Elasticsearch.getSearchResults(e.getClient(),m,!1)}
/** 
     * returns an object containing histogram of average roi occupancy.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.sensorId
     * @param {?string} [input.roiId=null]
     * @param {string} [input.fromTimestamp] - Either fromTimestamp and toTimestamp should be present together or minutesAgo should be present.
     * @param {string} [input.toTimestamp] - Either fromTimestamp and toTimestamp should be present together or minutesAgo should be present.
     * @param {number} [input.minutesAgo] - minutesAgo must be an integer. Either fromTimestamp and toTimestamp should be present together or minutesAgo should be present.
     * @param {number} [input.bucketCount=20] - bucketCount must be an integer.
     * @param {?string} [input.objectType=null]
     * @returns {Promise<Object>} Histogram of Average Roi Occupancy 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",bucketCount:24};
     * let occupancyObject = new mdx.Metrics.Occupancy();
     * let roiHistogramResult = await occupancyObject.getHistogramOfAverageRoiOccupancy(elastic,input);
     */async getHistogramOfAverageRoiOccupancy(e,t){const 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."}},roiId:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"roiId should have atleast 1 character."}},fromTimestamp:{type:["string","null"],default:null},toTimestamp:{type:["string","null"],default:null},minutesAgo:{type:["integer","null"],minimum:1,default:null,errorMessage:{type:"minutesAgo is not an integer.",minimum:"minutesAgo can have a minimum value of 1."}},bucketCount:{type:"integer",minimum:1,default:Histogram.getDefaultHistogramBucketCount(),maximum:Histogram.getMaxHistogramBucketCount(),errorMessage:{type:"bucketCount is not an integer.",minimum:"bucketCount can have a minimum value of 1.",maximum:`bucketCount can have a maximum value of ${Histogram.getMaxHistogramBucketCount()}.`}},objectType:{type:["string","null"],default:null,minLength:1,errorMessage:{minLength:"objectType should have atleast 1 character."}}},required:["sensorId"],oneOf:[{required:["minutesAgo"],not:{anyOf:[{required:["fromTimestamp","minutesAgo"]},{required:["toTimestamp","minutesAgo"]}]}},{required:["fromTimestamp","toTimestamp"],not:{required:["minutesAgo"]}}],errorMessage:{required:"Input should have required properties 'sensorId'.",oneOf:"One of the following combinations should be present in input: ('fromTimestamp', 'toTimestamp') or ('minutesAgo')."}};let a=Validator.validateJsonSchema(t,r);if(!a.valid)throw new BadRequestError(a.reason);if(null!=t.fromTimestamp&&null!=t.toTimestamp){let e=Validator.isValidTimeRange(t.fromTimestamp,t.toTimestamp);if(!e.valid)throw new InvalidInputError(e.reason)}else if(null!=t.minutesAgo){if(!Number.isFinite(t.minutesAgo))throw new InvalidInputError("minutesAgo is not a finite integer.");t.toTimestamp=(new Date).toISOString(),t.fromTimestamp=new Date(new Date(t.toTimestamp)-60*t.minutesAgo*1e3).toISOString()}if(!Number.isFinite(t.bucketCount))throw new InvalidInputError("bucketCount is not a finite integer.");t.bucketSizeInSec=Histogram.computeBucketSizeInSec({bucketCount:t.bucketCount,fromTimestamp:t.fromTimestamp,toTimestamp:t.toTimestamp});let s={bucketSizeInSec:t.bucketSizeInSec,rois:new Array};if("Elasticsearch"===e.getName()){let r=await this.#c(e,t);if(!r.indexAbsent){let e=new Set;for(let t of r.body.aggregations.eventsOverTime.buckets)for(let r of t.rois.searchAggFilter.roiIds.buckets)e.add(r.key);let a=new Map,i=new Set;for(let s of r.body.aggregations.eventsOverTime.buckets){let r=s.key_as_string,o=new Date(new Date(r).valueOf()+1e3*t.bucketSizeInSec).toISOString(),n=new Set;for(let e of s.rois.searchAggFilter.roiIds.buckets){let t=e.key,s={start:r,end:o,objects:new Array};for(let t of e.objectType.buckets)s.objects.push({type:t.key,averageCount:parseFloat(t.avgCount.value.toFixed(2))}),i.add(t.key);let l=new Map;a.has(t)&&(l=a.get(t)),l.set(r,s),a.set(t,l),n.add(t)}let l=Utils.setDifference(e,n);for(let e of l){let t=new Map;a.has(e)&&(t=a.get(e)),t.set(r,{start:r,end:o,objects:new Array}),a.set(e,t)}}let o=Histogram.getEmptyHistogram({bucketSizeInSec:t.bucketSizeInSec,fromTimestamp:t.fromTimestamp,toTimestamp:t.toTimestamp});for(let[e,r]of a){let a=new Array;for(let e of o)r.has(e.start)?e=r.get(e.start):e.objects=new Array,e=this.#m(e,i),a.push(e);Utils.tsCompare(a[0].start,"<",t.fromTimestamp)&&(a[0].start=t.fromTimestamp),Utils.tsCompare(a[a.length-1].end,">",t.toTimestamp)&&(a[a.length-1].end=t.toTimestamp),s.rois.push({id:e,histogram:a})}}return s}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}async#p(e,{place:t,timestamp:r,timeWindowInMs:a,objectType:s,objectDetails:i}){const o=`${e.getConfigs().get("indexPrefix")}${Elasticsearch.getIndex("frames")}`;let n=deepcopy(filterTemplate);n.query.bool.must.push({range:{timestamp:{lte:r,gte:new Date(new Date(r)-a).toISOString()}}}),n.query.bool.must.push({exists:{field:"info.place"}}),n.query.bool.must.push({term:{"info.place.keyword":t}}),n.query.bool.must.push({nested:{path:"rois",query:{bool:{must:[{term:{"rois.type.keyword":s}}]}}}}),n.aggs={sensorIds:{terms:{field:"sensorId.keyword",size:1e4},aggs:{lastProcessedRecord:{top_hits:{size:1,sort:[{timestamp:{order:"desc"}}],_source:{includes:["rois"],excludes:i?[]:["rois.coordinates","rois.id"]}}}}}};let l={index:o,body:n,size:0};return await Elasticsearch.getSearchResults(e.getClient(),l,!1)}
/** 
     * returns an object containing occupancy of a place which is calculated based on mutually exclusive rois.
     * @public
     * @async
     * @param {Database} documentDb - Database Object
     * @param {Object} input - Input object.
     * @param {string} input.place
     * @param {?string} [input.timestamp=null]
     * @param {string} [input.objectType="Person"]
     * @param {boolean} [input.objectDetails=false]
     * @param {number} [input.timestampDelayInSec=3] - timestampDelayInSec must be an integer.
     * @param {number} [input.timeWindowInMs=2000] - timeWindowInMs must be an integer.
     * @returns {Promise<Object>} Occupancy of a place which is calculated based on mutually exclusive rois is returned
     * @example
     * const mdx = require("@nvidia-mdx/web-api-core");
     * const elastic = new mdx.Utils.Elasticsearch({node: "elasticsearch-url"},databaseConfigMap);
     * let input = {place: "building=abc/room=xyz"};
     * let occupancyObject = new mdx.Metrics.Occupancy();
     * let placeOccupancy = await occupancyObject.getUniqueObjectCountInMutuallyExclusiveRois(elastic,input);
     */async getUniqueObjectCountInMutuallyExclusiveRois(e,t){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},objectType:{type:"string",default:"Person",minLength:1,errorMessage:{minLength:"objectType should have atleast 1 character."}},objectDetails:{type:"boolean",default:!1,errorMessage:{type:"objectDetails doesn't have a boolean value."}},timestampDelayInSec:{type:"integer",minimum:1,default:Occupancy.#t,errorMessage:{type:"timestampDelayInSec is not an integer.",minimum:"timestampDelayInSec can have a minimum value of 1."}},timeWindowInMs:{type:"integer",minimum:0,default:Occupancy.#e,errorMessage:{type:"timeWindowInMs is not an integer.",minimum:"timeWindowInMs can have a minimum value of 0."}}},required:["place"],errorMessage:{required:"Input should have required properties 'place'."}};let a=Validator.validateJsonSchema(t,r);if(!a.valid)throw new BadRequestError(a.reason);if(null!=t.timestamp){if(!Validator.isValidISOTimestamp(t.timestamp))throw new InvalidInputError("Invalid timestamp.");t.timestampDelayInSec=null}else{if(!Number.isFinite(t.timestampDelayInSec))throw new InvalidInputError("timestampDelayInSec is not a finite integer.");let e=(new Date).getTime()/1e3,r=new Date(1e3*Math.ceil(e));t.timestamp=new Date(r-1e3*t.timestampDelayInSec).toISOString()}if(!Number.isFinite(t.timeWindowInMs))throw new InvalidInputError("timeWindowInMs is not a finite integer.");let s={place:t.place,timestamp:t.timestamp,occupancy:0,objectType:t.objectType};if(t.objectDetails&&(s.objectDetails=new Array),"Elasticsearch"===e.getName()){let r=await this.#p(e,t);if(!r.indexAbsent)for(let e of r.body.aggregations.sensorIds.buckets){let r=e.key,a=e.lastProcessedRecord.hits.hits[0]._source;for(let e of a.rois)e.type===t.objectType&&(s.occupancy+=e.count,t.objectDetails&&s.objectDetails.push({sensorId:r,roiId:e.id,objects:e.ids.map((function(t,r){return{id:t,coordinates:e.coordinates[r]}}))}))}return s}throw new InternalServerError(`Invalid database: ${e.getName()}.`)}}module.exports=Occupancy;