feat: implement EmployeeFaceEmbedding model and application logic for managing embeddings

This commit is contained in:
2025-11-05 14:50:44 +03:30
parent 752c7c72ab
commit 6ab418c4ab
11 changed files with 633 additions and 36 deletions

View File

@@ -1,44 +1,175 @@
using System;
using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Company.Domain.EmployeeFaceEmbeddingAgg;
public class EmployeeFaceEmbedding
{
public string Id { get; set; }
public string EmployeeFullName { get; set; }
public int EmployeeId { get; set; }
public int WorkshopId { get; set; }
public List<double> Embeddings { get; set; }
public EmployeeFaceEmbeddingMetadata Metadata { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
{
public EmployeeFaceEmbedding()
{
EmbeddingHistory = new List<EmbeddingHistoryItem>();
MetadataHistory = new List<MetadataHistoryItem>();
}
public class EmployeeFaceEmbeddingMetadata
{
public double AvgEyeDistanceNormalized { get; set; }
public double AvgEyeToFaceRatio { get; set; }
public double AvgFaceAspectRatio { get; set; }
public double AvgDetectionConfidence { get; set; }
public EmployeeFaceEmbeddingKeypoints AvgKeypointsNormalized { get; set; }
public List<ImageMetadata> PerImageMetadata { get; set; }
}
public EmployeeFaceEmbedding(string employeeFullName, long employeeId, long workshopId,
List<double> embeddings, EmployeeFaceEmbeddingMetadata metadata)
{
Id = Guid.NewGuid().ToString();
EmployeeFullName = employeeFullName;
EmployeeId = employeeId;
WorkshopId = workshopId;
Embeddings = embeddings;
Metadata = metadata;
EmbeddingHistory = new List<EmbeddingHistoryItem>();
MetadataHistory = new List<MetadataHistoryItem>();
CreatedAt = DateTime.UtcNow;
UpdatedAt = DateTime.UtcNow;
}
public class EmployeeFaceEmbeddingKeypoints
{
public double[] LeftEye { get; set; }
public double[] RightEye { get; set; }
public double[] Nose { get; set; }
public double[] MouthLeft { get; set; }
public double[] MouthRight { get; set; }
}
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public class ImageMetadata
{
public double FaceAspectRatio { get; set; }
public double EyeDistanceNormalized { get; set; }
public double EyeToFaceRatio { get; set; }
public EmployeeFaceEmbeddingKeypoints KeypointsNormalized { get; set; }
public double DetectionConfidence { get; set; }
}
public string EmployeeFullName { get; set; }
public long EmployeeId { get; set; }
public long WorkshopId { get; set; }
public List<double> Embeddings { get; set; }
public EmployeeFaceEmbeddingMetadata Metadata { get; set; }
public List<EmbeddingHistoryItem> EmbeddingHistory { get; set; }
public List<MetadataHistoryItem> MetadataHistory { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public void UpdateEmbedding(List<double> newEmbedding, double confidence, double refinementPercentage)
{
if (Embeddings != null)
{
EmbeddingHistory.Add(new EmbeddingHistoryItem
{
Embedding = new List<double>(Embeddings),
Timestamp = DateTime.UtcNow,
Confidence = confidence,
RefinementPercentage = refinementPercentage
});
}
Embeddings = newEmbedding;
UpdatedAt = DateTime.UtcNow;
}
public void UpdateMetadata(EmployeeFaceEmbeddingMetadata newMetadata, double confidence, double refinementPercentage)
{
if (Metadata != null)
{
MetadataHistory.Add(new MetadataHistoryItem
{
Metadata = Metadata,
Timestamp = DateTime.UtcNow,
Confidence = confidence,
RefinementPercentage = refinementPercentage
});
}
Metadata = newMetadata;
UpdatedAt = DateTime.UtcNow;
}
public void UpdateEmployeeInfo(string employeeFullName, long workshopId)
{
EmployeeFullName = employeeFullName;
WorkshopId = workshopId;
UpdatedAt = DateTime.UtcNow;
}
}
public class EmployeeFaceEmbeddingMetadata
{
[BsonElement("avg_eye_distance_normalized")]
public double AvgEyeDistanceNormalized { get; set; }
[BsonElement("avg_eye_to_face_ratio")]
public double AvgEyeToFaceRatio { get; set; }
[BsonElement("avg_face_aspect_ratio")]
public double AvgFaceAspectRatio { get; set; }
[BsonElement("avg_detection_confidence")]
public double AvgDetectionConfidence { get; set; }
[BsonElement("avg_keypoints_normalized")]
public EmployeeFaceEmbeddingKeypoints AvgKeypointsNormalized { get; set; }
[BsonElement("per_image_metadata")]
public List<ImageMetadata> PerImageMetadata { get; set; }
}
public class EmployeeFaceEmbeddingKeypoints
{
[BsonElement("left_eye")]
public double[] LeftEye { get; set; }
[BsonElement("right_eye")]
public double[] RightEye { get; set; }
[BsonElement("nose")]
public double[] Nose { get; set; }
[BsonElement("mouth_left")]
public double[] MouthLeft { get; set; }
[BsonElement("mouth_right")]
public double[] MouthRight { get; set; }
}
public class ImageMetadata
{
[BsonElement("face_aspect_ratio")]
public double FaceAspectRatio { get; set; }
[BsonElement("eye_distance_normalized")]
public double EyeDistanceNormalized { get; set; }
[BsonElement("eye_to_face_ratio")]
public double EyeToFaceRatio { get; set; }
[BsonElement("detection_confidence")]
public double DetectionConfidence { get; set; }
[BsonElement("keypoints_normalized")]
public EmployeeFaceEmbeddingKeypoints KeypointsNormalized { get; set; }
}
public class EmbeddingHistoryItem
{
[BsonElement("embedding")]
public List<double> Embedding { get; set; }
[BsonElement("timestamp")]
public DateTime Timestamp { get; set; }
[BsonElement("confidence")]
public double Confidence { get; set; }
[BsonElement("refinementPercentage")]
public double RefinementPercentage { get; set; }
}
public class MetadataHistoryItem
{
[BsonElement("metadata")]
public EmployeeFaceEmbeddingMetadata Metadata { get; set; }
[BsonElement("timestamp")]
public DateTime Timestamp { get; set; }
[BsonElement("confidence")]
public double Confidence { get; set; }
[BsonElement("refinementPercentage")]
public double RefinementPercentage { get; set; }
}

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Company.Domain.EmployeeFaceEmbeddingAgg;
public interface IEmployeeFaceEmbeddingRepository
{
Task CreateAsync(EmployeeFaceEmbedding employeeFaceEmbedding);
Task UpdateAsync(EmployeeFaceEmbedding employeeFaceEmbedding);
Task<EmployeeFaceEmbedding> GetByIdAsync(string id);
Task<EmployeeFaceEmbedding> GetByEmployeeIdAsync(long employeeId);
Task<List<EmployeeFaceEmbedding>> GetByWorkshopIdAsync(long workshopId);
Task<List<EmployeeFaceEmbedding>> GetByWorkshopIdsAsync(List<long> workshopIds);
Task DeleteAsync(string id);
}

View File

@@ -0,0 +1,60 @@
using Company.Domain.EmployeeFaceEmbeddingAgg;
using MongoDB.Driver;
namespace CompanyManagement.Infrastructure.Mongo.EmployeeFaceEmbeddingRepo;
public class EmployeeFaceEmbeddingRepository : IEmployeeFaceEmbeddingRepository
{
private readonly IMongoCollection<EmployeeFaceEmbedding> _employeeFaceEmbeddings;
public EmployeeFaceEmbeddingRepository(IMongoDatabase database)
{
_employeeFaceEmbeddings = database.GetCollection<EmployeeFaceEmbedding>("EmployeeFaceEmbeddings");
}
public async Task CreateAsync(EmployeeFaceEmbedding employeeFaceEmbedding)
{
await _employeeFaceEmbeddings.InsertOneAsync(employeeFaceEmbedding);
}
public async Task UpdateAsync(EmployeeFaceEmbedding employeeFaceEmbedding)
{
await _employeeFaceEmbeddings.ReplaceOneAsync(
x => x.Id == employeeFaceEmbedding.Id,
employeeFaceEmbedding);
}
public async Task<EmployeeFaceEmbedding> GetByIdAsync(string id)
{
return await _employeeFaceEmbeddings
.Find(x => x.Id == id)
.FirstOrDefaultAsync();
}
public async Task<EmployeeFaceEmbedding> GetByEmployeeIdAsync(long employeeId)
{
return await _employeeFaceEmbeddings
.Find(x => x.EmployeeId == employeeId)
.FirstOrDefaultAsync();
}
public async Task<List<EmployeeFaceEmbedding>> GetByWorkshopIdAsync(long workshopId)
{
return await _employeeFaceEmbeddings
.Find(x => x.WorkshopId == workshopId)
.ToListAsync();
}
public async Task<List<EmployeeFaceEmbedding>> GetByWorkshopIdsAsync(List<long> workshopIds)
{
return await _employeeFaceEmbeddings
.Find(x => workshopIds.Contains(x.WorkshopId))
.ToListAsync();
}
public async Task DeleteAsync(string id)
{
await _employeeFaceEmbeddings.DeleteOneAsync(x => x.Id == id);
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
public class CreateEmployeeFaceEmbedding
{
public string EmployeeFullName { get; set; }
public long EmployeeId { get; set; }
public long WorkshopId { get; set; }
public List<double> Embeddings { get; set; }
public EmployeeFaceEmbeddingMetadataViewModel Metadata { get; set; }
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
namespace CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
public class EmployeeFaceEmbeddingViewModel
{
public string Id { get; set; }
public string EmployeeFullName { get; set; }
public long EmployeeId { get; set; }
public long WorkshopId { get; set; }
public List<double> Embeddings { get; set; }
public EmployeeFaceEmbeddingMetadataViewModel Metadata { get; set; }
public List<EmbeddingHistoryItemViewModel> EmbeddingHistory { get; set; }
public List<MetadataHistoryItemViewModel> MetadataHistory { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class EmployeeFaceEmbeddingMetadataViewModel
{
public double AvgEyeDistanceNormalized { get; set; }
public double AvgEyeToFaceRatio { get; set; }
public double AvgFaceAspectRatio { get; set; }
public double AvgDetectionConfidence { get; set; }
public EmployeeFaceEmbeddingKeypointsViewModel AvgKeypointsNormalized { get; set; }
public List<ImageMetadataViewModel> PerImageMetadata { get; set; }
}
public class EmployeeFaceEmbeddingKeypointsViewModel
{
public double[] LeftEye { get; set; }
public double[] RightEye { get; set; }
public double[] Nose { get; set; }
public double[] MouthLeft { get; set; }
public double[] MouthRight { get; set; }
}
public class ImageMetadataViewModel
{
public double FaceAspectRatio { get; set; }
public double EyeDistanceNormalized { get; set; }
public double EyeToFaceRatio { get; set; }
public double DetectionConfidence { get; set; }
public EmployeeFaceEmbeddingKeypointsViewModel KeypointsNormalized { get; set; }
}
public class EmbeddingHistoryItemViewModel
{
public List<double> Embedding { get; set; }
public DateTime Timestamp { get; set; }
public double Confidence { get; set; }
public double RefinementPercentage { get; set; }
}
public class MetadataHistoryItemViewModel
{
public EmployeeFaceEmbeddingMetadataViewModel Metadata { get; set; }
public DateTime Timestamp { get; set; }
public double Confidence { get; set; }
public double RefinementPercentage { get; set; }
}

View File

@@ -0,0 +1,68 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
/// <summary>
/// رابط اپلیکیشن Embedding چهره کارکنان
/// مدیریت عملیات مربوط به ذخیره و مدیریت embedding های چهره کارکنان
/// </summary>
public interface IEmployeeFaceEmbeddingApplication
{
/// <summary>
/// ایجاد embedding جدید برای کارمند
/// </summary>
/// <param name="command">اطلاعات embedding</param>
/// <returns>شناسه embedding ایجاد شده</returns>
Task<string> CreateAsync(CreateEmployeeFaceEmbedding command);
/// <summary>
/// به‌روزرسانی embedding کارمند
/// </summary>
/// <param name="command">اطلاعات جدید embedding</param>
/// <returns>نتیجه عملیات</returns>
Task UpdateEmbeddingAsync(UpdateEmployeeFaceEmbedding command);
/// <summary>
/// به‌روزرسانی metadata کارمند
/// </summary>
/// <param name="command">اطلاعات جدید metadata</param>
/// <returns>نتیجه عملیات</returns>
Task UpdateMetadataAsync(UpdateEmployeeFaceEmbeddingMetadata command);
/// <summary>
/// به‌روزرسانی اطلاعات کارمند
/// </summary>
/// <param name="command">اطلاعات جدید کارمند</param>
/// <returns>نتیجه عملیات</returns>
Task UpdateEmployeeInfoAsync(UpdateEmployeeInfo command);
/// <summary>
/// دریافت embedding بر اساس شناسه کارمند
/// </summary>
/// <param name="employeeId">شناسه کارمند</param>
/// <returns>اطلاعات embedding</returns>
Task<EmployeeFaceEmbeddingViewModel> GetByEmployeeIdAsync(long employeeId);
/// <summary>
/// دریافت لیست embeddings بر اساس شناسه کارگاه
/// </summary>
/// <param name="workshopId">شناسه کارگاه</param>
/// <returns>لیست embeddings</returns>
Task<List<EmployeeFaceEmbeddingViewModel>> GetByWorkshopIdAsync(long workshopId);
/// <summary>
/// دریافت لیست embeddings بر اساس لیست شناسه کارگاه‌ها
/// </summary>
/// <param name="workshopIds">لیست شناسه کارگاه‌ها</param>
/// <returns>لیست embeddings</returns>
Task<List<EmployeeFaceEmbeddingViewModel>> GetByWorkshopIdsAsync(List<long> workshopIds);
/// <summary>
/// حذف embedding کارمند
/// </summary>
/// <param name="id">شناسه embedding</param>
/// <returns>نتیجه عملیات</returns>
Task DeleteAsync(string id);
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
public class UpdateEmployeeFaceEmbedding
{
public string Id { get; set; }
public List<double> Embeddings { get; set; }
public double Confidence { get; set; }
public double RefinementPercentage { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
public class UpdateEmployeeFaceEmbeddingMetadata
{
public string Id { get; set; }
public EmployeeFaceEmbeddingMetadataViewModel Metadata { get; set; }
public double Confidence { get; set; }
public double RefinementPercentage { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
public class UpdateEmployeeInfo
{
public string Id { get; set; }
public string EmployeeFullName { get; set; }
public long WorkshopId { get; set; }
}

View File

@@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Company.Domain.EmployeeFaceEmbeddingAgg;
using CompanyManagment.App.Contracts.EmployeeFaceEmbedding;
namespace CompanyManagment.Application;
public class EmployeeFaceEmbeddingApplication : IEmployeeFaceEmbeddingApplication
{
private readonly IEmployeeFaceEmbeddingRepository _repository;
public EmployeeFaceEmbeddingApplication(IEmployeeFaceEmbeddingRepository repository)
{
_repository = repository;
}
public async Task<string> CreateAsync(CreateEmployeeFaceEmbedding command)
{
var metadata = MapMetadataFromViewModel(command.Metadata);
var employeeFaceEmbedding = new EmployeeFaceEmbedding(
command.EmployeeFullName,
command.EmployeeId,
command.WorkshopId,
command.Embeddings,
metadata
);
await _repository.CreateAsync(employeeFaceEmbedding);
return employeeFaceEmbedding.Id;
}
public async Task UpdateEmbeddingAsync(UpdateEmployeeFaceEmbedding command)
{
var employeeFaceEmbedding = await _repository.GetByIdAsync(command.Id);
if (employeeFaceEmbedding == null)
throw new Exception($"EmployeeFaceEmbedding با شناسه {command.Id} یافت نشد");
employeeFaceEmbedding.UpdateEmbedding(
command.Embeddings,
command.Confidence,
command.RefinementPercentage
);
await _repository.UpdateAsync(employeeFaceEmbedding);
}
public async Task UpdateMetadataAsync(UpdateEmployeeFaceEmbeddingMetadata command)
{
var employeeFaceEmbedding = await _repository.GetByIdAsync(command.Id);
if (employeeFaceEmbedding == null)
throw new Exception($"EmployeeFaceEmbedding با شناسه {command.Id} یافت نشد");
var metadata = MapMetadataFromViewModel(command.Metadata);
employeeFaceEmbedding.UpdateMetadata(
metadata,
command.Confidence,
command.RefinementPercentage
);
await _repository.UpdateAsync(employeeFaceEmbedding);
}
public async Task UpdateEmployeeInfoAsync(UpdateEmployeeInfo command)
{
var employeeFaceEmbedding = await _repository.GetByIdAsync(command.Id);
if (employeeFaceEmbedding == null)
throw new Exception($"EmployeeFaceEmbedding با شناسه {command.Id} یافت نشد");
employeeFaceEmbedding.UpdateEmployeeInfo(
command.EmployeeFullName,
command.WorkshopId
);
await _repository.UpdateAsync(employeeFaceEmbedding);
}
public async Task<EmployeeFaceEmbeddingViewModel> GetByEmployeeIdAsync(long employeeId)
{
var employeeFaceEmbedding = await _repository.GetByEmployeeIdAsync(employeeId);
return employeeFaceEmbedding == null ? null : MapToViewModel(employeeFaceEmbedding);
}
public async Task<List<EmployeeFaceEmbeddingViewModel>> GetByWorkshopIdAsync(long workshopId)
{
var employeeFaceEmbeddings = await _repository.GetByWorkshopIdAsync(workshopId);
return employeeFaceEmbeddings.Select(MapToViewModel).ToList();
}
public async Task<List<EmployeeFaceEmbeddingViewModel>> GetByWorkshopIdsAsync(List<long> workshopIds)
{
var employeeFaceEmbeddings = await _repository.GetByWorkshopIdsAsync(workshopIds);
return employeeFaceEmbeddings.Select(MapToViewModel).ToList();
}
public async Task DeleteAsync(string id)
{
await _repository.DeleteAsync(id);
}
#region Mapping Methods
private EmployeeFaceEmbeddingViewModel MapToViewModel(EmployeeFaceEmbedding entity)
{
return new EmployeeFaceEmbeddingViewModel
{
Id = entity.Id,
EmployeeFullName = entity.EmployeeFullName,
EmployeeId = entity.EmployeeId,
WorkshopId = entity.WorkshopId,
Embeddings = entity.Embeddings,
Metadata = MapMetadataToViewModel(entity.Metadata),
EmbeddingHistory = entity.EmbeddingHistory?.Select(x => new EmbeddingHistoryItemViewModel
{
Embedding = x.Embedding,
Timestamp = x.Timestamp,
Confidence = x.Confidence,
RefinementPercentage = x.RefinementPercentage
}).ToList(),
MetadataHistory = entity.MetadataHistory?.Select(x => new MetadataHistoryItemViewModel
{
Metadata = MapMetadataToViewModel(x.Metadata),
Timestamp = x.Timestamp,
Confidence = x.Confidence,
RefinementPercentage = x.RefinementPercentage
}).ToList(),
CreatedAt = entity.CreatedAt,
UpdatedAt = entity.UpdatedAt
};
}
private EmployeeFaceEmbeddingMetadataViewModel MapMetadataToViewModel(EmployeeFaceEmbeddingMetadata metadata)
{
if (metadata == null) return null;
return new EmployeeFaceEmbeddingMetadataViewModel
{
AvgEyeDistanceNormalized = metadata.AvgEyeDistanceNormalized,
AvgEyeToFaceRatio = metadata.AvgEyeToFaceRatio,
AvgFaceAspectRatio = metadata.AvgFaceAspectRatio,
AvgDetectionConfidence = metadata.AvgDetectionConfidence,
AvgKeypointsNormalized = MapKeypointsToViewModel(metadata.AvgKeypointsNormalized),
PerImageMetadata = metadata.PerImageMetadata?.Select(x => new ImageMetadataViewModel
{
FaceAspectRatio = x.FaceAspectRatio,
EyeDistanceNormalized = x.EyeDistanceNormalized,
EyeToFaceRatio = x.EyeToFaceRatio,
DetectionConfidence = x.DetectionConfidence,
KeypointsNormalized = MapKeypointsToViewModel(x.KeypointsNormalized)
}).ToList()
};
}
private EmployeeFaceEmbeddingKeypointsViewModel MapKeypointsToViewModel(EmployeeFaceEmbeddingKeypoints keypoints)
{
if (keypoints == null) return null;
return new EmployeeFaceEmbeddingKeypointsViewModel
{
LeftEye = keypoints.LeftEye,
RightEye = keypoints.RightEye,
Nose = keypoints.Nose,
MouthLeft = keypoints.MouthLeft,
MouthRight = keypoints.MouthRight
};
}
private EmployeeFaceEmbeddingMetadata MapMetadataFromViewModel(EmployeeFaceEmbeddingMetadataViewModel viewModel)
{
if (viewModel == null) return null;
return new EmployeeFaceEmbeddingMetadata
{
AvgEyeDistanceNormalized = viewModel.AvgEyeDistanceNormalized,
AvgEyeToFaceRatio = viewModel.AvgEyeToFaceRatio,
AvgFaceAspectRatio = viewModel.AvgFaceAspectRatio,
AvgDetectionConfidence = viewModel.AvgDetectionConfidence,
AvgKeypointsNormalized = MapKeypointsFromViewModel(viewModel.AvgKeypointsNormalized),
PerImageMetadata = viewModel.PerImageMetadata?.Select(x => new ImageMetadata
{
FaceAspectRatio = x.FaceAspectRatio,
EyeDistanceNormalized = x.EyeDistanceNormalized,
EyeToFaceRatio = x.EyeToFaceRatio,
DetectionConfidence = x.DetectionConfidence,
KeypointsNormalized = MapKeypointsFromViewModel(x.KeypointsNormalized)
}).ToList()
};
}
private EmployeeFaceEmbeddingKeypoints MapKeypointsFromViewModel(EmployeeFaceEmbeddingKeypointsViewModel viewModel)
{
if (viewModel == null) return null;
return new EmployeeFaceEmbeddingKeypoints
{
LeftEye = viewModel.LeftEye,
RightEye = viewModel.RightEye,
Nose = viewModel.Nose,
MouthLeft = viewModel.MouthLeft,
MouthRight = viewModel.MouthRight
};
}
#endregion
}

View File

@@ -19,7 +19,7 @@
"sqlDebugging": true,
"dotnetRunMessages": "true",
"nativeDebugging": true,
"applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.117:8080;http://192.168.0.117:8081",
"applicationUrl": "https://localhost:5004;http://localhost:5003;https://192.168.0.118:8080;http://192.168.0.118:8081",
"jsWebView2Debugging": false,
"hotReloadEnabled": true
},