Files
adas-core/adas-core.Test/Services/GroupedObservationServiceTest.cs
2026-06-26 10:29:23 +02:00

529 lines
25 KiB
C#

using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Moq;
using static adas_core.Domain.Enums.GroupedObservationEnum;
namespace adas_core.Test.Services;
[TestFixture]
public class GroupedObservationServiceTest
{
/// <summary>
/// Initializes the test fixture before each test by creating mocked dependencies
/// (configuration observation service, observation repository, logger, and cache service)
/// and instantiating the <see cref="GroupedObservationService"/> under test with default API and cache settings.
/// </summary>
[SetUp]
public void Setup()
{
var configObservationService = new Mock<IConfigObservationService>();
var observationRepository = new Mock<IObservationRepository>();
_logger = new Mock<ILogger<GroupedObservationService>>();
_groupedObservationService = new GroupedObservationService(
observationRepository.Object, configObservationService.Object,
_logger.Object, Mock.Of<ICacheService>(), Options.Create(new ApiSettings()), Options.Create(new CacheSettings()));
}
private GroupedObservationService _groupedObservationService;
private Mock<ILogger<GroupedObservationService>> _logger;
/// <summary>
/// Tests that shift observations are correctly generated and grouped by shift time intervals (08:00, 15:00, 22:00) across multiple days,
/// verifying the distribution of observations across shifts for the current day, the previous day, and two days prior when the
/// Result.Last aggregation is applied with a maximum count of 36.
/// </summary>
[Test]
public void Generate_Shift_Observations()
{
var dateTime = DateTime.Now;
if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Last, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Last, null)
};
var groupedField = new GroupedField
{
StartTimeShift = ["08:00", "15:00", "22:00"],
Max = 36,
Result = [Result.Last]
};
var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
var shift1ObsToday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
var shift1ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
var shift2ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
var shif3ObsYesterday = shiftObservations.Count(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
var shiftTwoDaysAgo = shiftObservations.Count(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
using (Assert.EnterMultipleScope())
{
Assert.That(shift1ObsToday, Is.EqualTo(3));
Assert.That(shif3ObsYesterday, Is.EqualTo(3));
Assert.That(shift2ObsYesterday, Is.EqualTo(2));
Assert.That(shift1ObsYesterday, Is.EqualTo(2));
Assert.That(shiftTwoDaysAgo, Is.EqualTo(1));
};
}
/// <summary>
/// Verifies that the grouped observation service correctly calculates sum values for shift observations
/// across multiple days and shift periods (08:00, 15:00, 22:00), including observations that fall within
/// the current day, the previous day, and two days prior, ensuring that data is aggregated into the
/// correct shift and day buckets.
/// </summary>
[Test]
public void Calculate_Shift_Observations()
{
var dateTime = DateTime.Now;
if (dateTime.Day <= 2) dateTime = dateTime.AddDays(2);
var groupedField = new GroupedField
{
StartTimeShift = ["08:00", "15:00", "22:00"],
Max = 36,
Result = [Result.Sum]
};
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 30, "FC", 100, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 30, "FC", 108, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 30, "FC", 106, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 5, 30, "FC", 112, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 4, 30, "FC", 105, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 23, 30, "FC", 190, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 19, 30, "FC", 120, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 15, 30, "FC", 130, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 12, 30, "FC", 140, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 1, 09, 30, "FC", 150, Result.Sum, null),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day - 2, 15, 30, "FC", 150, Result.Sum, null)
};
var shiftObservations = _groupedObservationService.GenerateShiftObservations(result, groupedField);
_groupedObservationService.CalculateShiftObservations(shiftObservations, groupedField);
var shift1ObsToday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day);
var shift1ObsYesterday = shiftObservations.First(s => s.Get("shift") == 0 && s.Get("day") == dateTime.Day - 1);
var shift2ObsYesterday = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 1);
var shif3ObsYesterday = shiftObservations.First(s => s.Get("shift") == 2 && s.Get("day") == dateTime.Day - 1);
var shiftTwoDaysAgo = shiftObservations.First(s => s.Get("shift") == 1 && s.Get("day") == dateTime.Day - 2);
using (Assert.EnterMultipleScope())
{
Assert.That(shift1ObsToday.Get("sum")?.ToInt32(), Is.EqualTo(314));
Assert.That(shif3ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(407));
Assert.That(shift2ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(250));
Assert.That(shift1ObsYesterday.Get("sum")?.ToInt32(), Is.EqualTo(290));
Assert.That(shiftTwoDaysAgo.Get("sum")?.ToInt32(), Is.EqualTo(150));
};
}
// [Test]
// public void Calculate_Half_Time_Observations()
// {
// /*
// 09:18 - Toma 1: 50/100/75
// 09:38 - Toma 2: 52/102/76
// 10:07 - Toma 3: 54/101/72
// 10:18 - Toma 4: 51/102/73
// 10:58 - Toma 5: 52/107/79
// 12:04 - Toma 6: 60/120/90
//
// El api convierte a
//
// 09h - Toma 2: 52/102/76
// 10h - Toma 4: 51/102/73
// 11h - Toma 5: 52/107/79
// 12h- Toma 6: 60/120/90
// */
//
//
// var dateTime = DateTime.Now;
//
// var values9 = new BsonArray {
// new BsonDocument
// {
// { "value", 50},
// { "time", new BsonDateTime(new DateTime(2021,3,10,9,18,0))},
//
// },
// new BsonDocument
// {
// { "value", 100},
// { "time", new BsonDateTime(new DateTime(2021,3,10,9,38,0))}
// }
// };
//
// var values10 = new BsonArray {
// new BsonDocument
// {
// { "value", 50},
// { "time", new BsonDateTime(new DateTime(2021,3,10,10,7,0))}
// },
// new BsonDocument
// {
// { "value", 60},
// { "time", new BsonDateTime(new DateTime(2021,3,10,10,18,0))}
// },
// new BsonDocument
// {
// { "value", 70},
// { "time", new BsonDateTime(new DateTime(2021,3,10,10,58,0))}
// }
// };
//
// var values11 = new BsonArray
// {
//
// };
//
// var values12 = new BsonArray
// {
// new BsonDocument
// {
// { "value", 50},
// { "time", new BsonDateTime(new DateTime(2021,3,10,12,04,0))}
// }
// };
//
//
// var result = new List<BsonDocument>()
// {
// GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,9,0,"TAM", 100, Result.HalfHour,values9),
// GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,10,0,"TAM", 100, Result.HalfHour,values10),
// GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,11,0,"TAM", 100, Result.HalfHour,values11),
// GenerateBsonDocument(dateTime.Year,dateTime.Month,dateTime.Day,12,0,"TAM", 100, Result.HalfHour,values12)
// };
//
// var halfObservations = _groupedObservationService.CalculateHalfHourObservations(result);
//
// Assert.That(halfObservations, Has.Count.EqualTo(4));
// Assert.Multiple(() =>
// {
// Assert.That(halfObservations[0].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(100));
// Assert.That(halfObservations[1].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(60));
// Assert.That(halfObservations[2].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(70));
// Assert.That(halfObservations[3].GetByCodeSysAndCode("halfhour")?.AsBsonDocument.GetByCodeSysAndCode("value")?.AsInt32, Is.EqualTo(50));
// });
// }
/// <summary>
/// Verifies that <c>CalculateLastFilledObservations</c> fills the missing hourly slot at <c>now - 4 hours</c>
/// so that the returned sequence contains a complete set of five consecutive hourly observations
/// (covering the last five hours relative to the current time) for a group with hourly regularity and a maximum of five entries.
/// </summary>
[Test]
public async Task Calculate_Last_Filled_Observations_should_add_Minus_4_hour_to_complete_last_five_hours()
{
var dateTime = DateTime.Now;
//Falta el -4 tiene que crearlo el calculate last filled observation
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-1).Year, dateTime.AddHours(-1).Month, dateTime.AddHours(-1).Day,
dateTime.AddHours(-1).Hour, 0, "TAM", 100, Result.LastFilled, null)
};
var groupedField = new GroupedField
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var patientId = ObjectId.GenerateNewId();
var lastFilledObservations =
await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
using (Assert.EnterMultipleScope())
{
Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
Is.True);
};
}
/// <summary>
/// Verifies that <c>CalculateLastFilledObservations</c> fills missing hourly entries in the last five hours,
/// adding the <c>-1 hour</c> observation when it is not present in the provided list so that the returned
/// collection always contains a complete five-hour window of last filled observations.
/// </summary>
[Test]
public async Task Calculate_Last_Filled_Observations_should_add_Minus_1_hour_to_complete_last_five_hours()
{
var dateTime = DateTime.Now;
//Falta el -4 tiene que crearlo el calculate last filled observation
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.AddHours(-5).Year, dateTime.AddHours(-5).Month, dateTime.AddHours(-5).Day,
dateTime.AddHours(-5).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-4).Year, dateTime.AddHours(-4).Month, dateTime.AddHours(-4).Day,
dateTime.AddHours(-4).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-3).Year, dateTime.AddHours(-3).Month, dateTime.AddHours(-3).Day,
dateTime.AddHours(-3).Hour, 0, "TAM", 100, Result.LastFilled, null),
GenerateBsonDocument(dateTime.AddHours(-2).Year, dateTime.AddHours(-2).Month, dateTime.AddHours(-2).Day,
dateTime.AddHours(-2).Hour, 0, "TAM", 100, Result.LastFilled, null)
};
var groupedField = new GroupedField
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var patientId = ObjectId.GenerateNewId();
var lastFilledObservations =
await _groupedObservationService.CalculateLastFilledObservations(result, groupedField, patientId);
using (Assert.EnterMultipleScope())
{
Assert.That(lastFilledObservations, Has.Count.EqualTo(5));
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-5).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-4).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-3).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-2).Hour),
Is.True);
Assert.That(
lastFilledObservations.Exists(i => i.Get("time")?.ToLocalTime().Hour == dateTime.AddHours(-1).Hour),
Is.True);
};
}
/// <summary>
/// Verifies that the service correctly fills hourly observations from half-hour observation data, grouping timestamped values by hour and forwarding missing slots up to the configured maximum (5) per hour.
/// </summary>
[Test]
public void FillHoursObservations()
{
/*
09:18 - Toma 1: 50/100/75
09:38 - Toma 2: 52/102/76
10:07 - Toma 3: 54/101/72
10:18 - Toma 4: 51/102/73
10:58 - Toma 5: 52/107/79
12:04 - Toma 6: 60/120/90
El api convierte a
09h - Toma 2: 52/102/76
10h - Toma 4: 51/102/73
11h - Toma 5: 52/107/79
12h- Toma 6: 60/120/90
*/
var groupedField = new GroupedField
{
Max = 5,
Name = "TAM",
Regularity = Regularity.Hour
};
var dateTime =
new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 12, 30, 0).ToUniversalTime();
var values9 = new BsonArray
{
new BsonDocument
{
{ "value", 50 },
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 18, 0).ToUniversalTime())
}
},
new BsonDocument
{
{ "value", 100 },
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 9, 38, 0).ToUniversalTime())
}
}
};
var values10 = new BsonArray
{
new BsonDocument
{
{ "value", 50 },
{
"time",
new BsonDateTime(
new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 7, 0).ToUniversalTime())
}
},
new BsonDocument
{
{ "value", 60 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 18, 0)
.ToUniversalTime())
}
},
new BsonDocument
{
{ "value", 70 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 10, 58, 0)
.ToUniversalTime())
}
}
};
var values12 = new BsonArray
{
new BsonDocument
{
{ "value", 50 },
{
"time",
new BsonDateTime(new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, 12, 04, 0)
.ToUniversalTime())
}
}
};
var result = new List<BsonDocument>
{
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 9, 0, "TAM", 100, Result.HalfHour,
values9),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 10, 0, "TAM", 100, Result.HalfHour,
values10),
GenerateBsonDocument(dateTime.Year, dateTime.Month, dateTime.Day, 12, 0, "TAM", 100, Result.HalfHour,
values12)
};
var halfObservations = _groupedObservationService.CalculateHalfHourObservations(result);
var fillHoursObservations = _groupedObservationService.FillHours(halfObservations, groupedField);
Assert.That(fillHoursObservations, Is.Not.Empty);
}
/// <summary>
/// Generates a BsonDocument representing a time-stamped result entry, with date component fallbacks to the current date when null and conditional payload structure based on the result type.
/// </summary>
/// <param name="year">The year component of the entry timestamp, or null to default to the current year.</param>
/// <param name="month">The month component of the entry timestamp, or null to default to the current month.</param>
/// <param name="day">The day component of the entry timestamp, or null to default to the current day.</param>
/// <param name="hour">The hour component of the entry timestamp, or null to default to the current hour.</param>
/// <param name="minute">The minute component of the entry timestamp, or null to default to the current minute.</param>
/// <param name="name">The name identifier included in the document's _id.</param>
/// <param name="value">The raw value associated with the result.</param>
/// <param name="resultType">The result type that determines how the value is stored; aggregate types (HalfHour, Sum, Count, Min, Average, Max) are stored directly, while others are wrapped in a sub-document with time and stringified value.</param>
/// <param name="all">Optional BsonArray of additional entries; when provided, it is appended to the result under the "all" key.</param>
/// <returns>A BsonDocument containing the _id composite key, a time field, the result-type-specific payload, and optionally the "all" array.</returns>
private static BsonDocument GenerateBsonDocument(int? year, int? month, int? day, int? hour, int? minute,
string name, object value, Result resultType, BsonArray? all)
{
var result = new BsonDocument
{
{
"_id", new BsonDocument
{
{ "year", year ?? new DateTime().Year },
{ "month", month ?? new DateTime().Month },
{ "day", day ?? new DateTime().Day },
{ "hour", hour ?? new DateTime().Hour },
{ "minute", minute ?? new DateTime().Minute },
{ "name", name }
}
},
{ "time", new BsonDateTime(new DateTime((int)year!, (int)month!, (int)day!, (int)hour!, (int)minute!, 0)) }
};
/*
{resultType.ToString().ToLower(), new BsonDocument{
{"time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
{"value", value.ToString() }
}},
{ "time", new BsonDateTime(new System.DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute,0)) },
};
*/
if (resultType != Result.HalfHour && resultType != Result.Sum && resultType != Result.Count
&& resultType != Result.Min && resultType != Result.Average && resultType != Result.Max)
result.Add(resultType.ToString().ToLower(), new BsonDocument
{
{ "time", new BsonDateTime(new DateTime((int)year, (int)month, (int)day, (int)hour, (int)minute, 0)) },
{ "value", value.ToString() }
});
else
result.AddRange(new Dictionary<string, object> { { resultType.ToString().ToLower(), value } });
if (all != null) result.Add("all", all);
return result;
}
}