public async Task HandleAsync(CreateVisitor message, IRequestInfo requestInfo) { if (!await _userServiceClient.ContainsUserAsync(message.VisitingId)) { _logger.LogWarning($"No user with id: {message.VisitingId} could be found"); PublishFailure(requestInfo); return; } var site = await _siteServiceClient.GetSiteAsync(message.SiteId); if (site is null) { _logger.LogInformation($"No site found with id: {message.SiteId}"); PublishFailure(requestInfo); return; } try { await _validatorService.Validate(site.BusinessId, message.Data); } catch (VmsException e) { _messagePublisher.PublishEvent(new CreateVisitorRejected(e.Code, e.Message), requestInfo); return; } //TODO: create service to validate data entrys var visitorData = new List <VisitorData>(); foreach (var visitorDataEntry in message.Data) { visitorData.Add(_visitorAggregate.CreateData(visitorDataEntry.FieldId, visitorDataEntry.Value)); } var visitor = _visitorAggregate.Create(message.VisitingId, site.BusinessId, message.SiteId, visitorData); await _visitorsRepository.AddAsync(visitor); _messagePublisher.PublishEvent(new VisitorCreated(), requestInfo); }
public async Task <bool> UpdateDemographics() { var start = DateTime.UtcNow; //A flag to know if we would update/insert the demographics record bool isNewDemographics = false; //A flag to know that and updated demographics are needed bool isDemographicsChanged = false; var analysisTakenAt = frameAnalysis.Request.TakenAt; //As the system designed to handle multiple devices, analysis also should be conducted in //the context of DeviceId var deviceId = frameAnalysis.Request.DeviceId; //We need to retain our analysis relevant to the frame taken date var visitDate = frameAnalysis.Request.TakenAt; // aggregate window start from begging of the hour and continue aggregating till 59min:59sec. var demographicsWindowMins = double.Parse(GlobalSettings.GetKeyValue("demographicsWindowMins")); //Default to 60mins var currentAggregateWindowStart = new DateTime(analysisTakenAt.Year, analysisTakenAt.Month, analysisTakenAt.Day, analysisTakenAt.Hour, 0, 0); var currentAggregateWindowEnd = currentAggregateWindowStart.AddMinutes(demographicsWindowMins); //var currentTime = DateTime.UtcNow; var passedTimeOfCurrentWindowMins = (currentAggregateWindowEnd - currentAggregateWindowStart).TotalMinutes; string crowdDemographicsId = null; if (passedTimeOfCurrentWindowMins > demographicsWindowMins) { // Move the demographics to the next window currentAggregateWindowStart = currentAggregateWindowStart.AddMinutes(demographicsWindowMins); currentAggregateWindowEnd = currentAggregateWindowStart.AddMinutes(demographicsWindowMins); } crowdDemographicsId = $"{currentAggregateWindowStart.ToString("yyyy-MM-dd-HH-mm-ss")}|{currentAggregateWindowEnd.ToString("yyyy-MM-dd-HH-mm-ss")}:{deviceId}"; try { Demographics = await crowdDemographicsRepo.GetByIdAsync(crowdDemographicsId); } catch (Exception ex) { if (ex.Message == "Entity not found") { Demographics = null; } else { throw; } } if (Demographics == null) { isNewDemographics = true; isDemographicsChanged = true; Demographics = new CrowdDemographics { Id = crowdDemographicsId, DeviceId = deviceId, WindowStart = currentAggregateWindowStart, WindowEnd = currentAggregateWindowEnd, CreatedAt = DateTime.UtcNow, LastUpdatedAt = DateTime.UtcNow, Origin = origin, IsDeleted = false }; } if (frameAnalysis.SimilarFaces != null) { // Update the Visitor collection (either add new entry or update existing) log.LogWarning($"FUNC (CrowdAnalyzer): ({frameAnalysis.SimilarFaces.Count()}) detected faces is being processed."); foreach (var item in frameAnalysis.SimilarFaces) { //Do we have that visitor in our db Guid persistedFaceId = item.SimilarPersistedFace.PersistedFaceId.GetValueOrDefault(); Visitor visitor = null; try { visitor = await visitorRepo.GetByIdAsync($"{persistedFaceId}:{item.Face.FaceAttributes.Gender}"); } catch (Exception ex) { if (ex.Message == "Entity not found") { visitor = null; } else { throw; } } //Visitor exists: if (visitor != null) { //Check if the visit was already recorded before (new date is within the visit window) var lastVisit = visitor.LastVisits.Where(v => v.DetectedOnDeviceId == frameAnalysis.Request.DeviceId).FirstOrDefault(); if (lastVisit != null) { if (!(lastVisit.VisitDate >= currentAggregateWindowStart && lastVisit.VisitDate <= currentAggregateWindowEnd)) { //New visit of a returning customer :) update the counts visitor.VisitsCount++; lastVisit.Count++; lastVisit.VisitDate = analysisTakenAt; //This means also that the demographic for this window is changed as well. isDemographicsChanged = true; await visitorRepo.UpdateAsync(visitor); } } else { visitor.VisitsCount++; var newVisit = new Visit { Count = 1, DetectedOnDeviceId = deviceId, VisitDate = analysisTakenAt }; visitor.LastVisits.Add(newVisit); isDemographicsChanged = true; await visitorRepo.UpdateAsync(visitor); } } //New Visitor else { isDemographicsChanged = true; visitor = new Visitor { Id = $"{persistedFaceId}:{item.Face.FaceAttributes.Gender}", VisitsCount = 1, Age = (int)item.Face.FaceAttributes.Age, AgeGroup = GetAgeGroupDescription((int)item.Face.FaceAttributes.Age), Gender = item.Face.FaceAttributes.Gender.ToString(), CreatedAt = DateTime.UtcNow, IsDeleted = false, Origin = origin, LastVisits = new List <Visit> { new Visit { Count = 1, DetectedOnDeviceId = deviceId, VisitDate = analysisTakenAt } } }; await visitorRepo.AddAsync(visitor); isDemographicsChanged = true; if (item.Face.FaceAttributes.Gender == Gender.Male) { Demographics.TotalNewMaleVisitors++; } else { Demographics.TotalNewFemaleVisitors++; } } if (isDemographicsChanged) { UpdateVisitorDemographics(item); } } Demographics.TotalVisitors = Demographics.TotalMales + Demographics.TotalFemales; Demographics.TotalProcessingTime = (int)(DateTime.UtcNow - start).TotalMilliseconds; //if (isNewDemographics) //{ // await crowdDemographicsRepo.AddAsync(Demographics); // return true; //} //else if (isDemographicsChanged) //{ // await crowdDemographicsRepo.UpdateAsync(Demographics); // return true; //} } //No faces in the analysis request else { log.LogWarning($"FUNC (CrowdAnalyzer): crowd-analysis found no faces in the analysis request"); } // Prepare Identified persons analytics try { if (frameAnalysis.IdentifiedPersons != null) { log.LogWarning($"FUNC (CrowdAnalyzer): ({frameAnalysis.SimilarFaces.Count()}) identified persons is being processed."); int identifiedPersonsCount = 0; Demographics.IdentifiedPersonsIds = new List <string>(); foreach (var item in frameAnalysis.IdentifiedPersons) { //Check if the identification passed with acceptable confidence if (item.Item2.Confidence >= AppConstants.IdentificationConfidence) { var result = await identifiedVisitorRepo.QueryDocuments( "visitor", "visitor.PersonDetails.personId=@personId", new SqlParameterCollection { new SqlParameter { Name = "@personId", Value = item.Item2.Person.PersonId.ToString() } }); if (result.Any()) { var identifiedVisitor = result[0]; // Ignore already detected identified visitor if exists in the same window if (Demographics.IdentifiedPersonsIds.Contains(identifiedVisitor.Id)) { continue; } Demographics.IdentifiedPersonsIds.Add(identifiedVisitor.Id); //Update visit last visit date identifiedVisitor.LastVisits.Add(new PersonIdentificationLib.Models.Visit { Count = 1, DetectedOnDeviceId = Demographics.DeviceId, VisitDate = DateTime.UtcNow }); identifiedPersonsCount++; } } } Demographics.TotalIdentifiedPersons = identifiedPersonsCount; } } catch (Exception ex) { log.LogError($"FUNC (CrowdAnalyzer): crowd-analysis couldn't process identified visitors: {ex.Message}"); } // Frame do not have any faces or identified persons if (frameAnalysis.SimilarFaces == null && frameAnalysis.IdentifiedPersons == null) { return(false); } if (isNewDemographics) { await crowdDemographicsRepo.AddAsync(Demographics); return(true); } else if (isDemographicsChanged) { await crowdDemographicsRepo.UpdateAsync(Demographics); return(true); } return(false); }