Реализовать конвертацию и получение параметров для графика

This commit is contained in:
Roman Efremov 2024-12-06 11:40:18 +05:00
parent 632e2a9f60
commit 0638806ea4
12 changed files with 13151 additions and 87 deletions

View File

@ -6,6 +6,9 @@ using Persistence.Services.Interfaces;
namespace Persistence.API.Controllers;
/// <summary>
/// Работа с параметрами Wits
/// </summary>
[ApiController]
[Authorize]
[Route("api/[controller]")]
@ -51,15 +54,17 @@ public class WitsDataController : ControllerBase, IWitsDataApi
/// <summary>
/// Получить набор параметров (Wits) для построения графика
/// </summary>
/// <param name="dateFrom"></param>
/// <param name="dateTo"></param>
/// <param name="limit"></param>
/// <param name="discriminatorId">Дискриминатор системы</param>
/// <param name="dateFrom">Начало временного интервала</param>
/// <param name="dateTo">Конец временного интервала</param>
/// <param name="approxPointsCount">Количество точек</param>
/// <param name="token"></param>
/// <returns></returns>
[HttpGet("graph")]
public async Task<ActionResult<IEnumerable<WitsDataDto>>> GetValuesForGraph([FromQuery] DateTimeOffset dateFrom, [FromQuery] DateTimeOffset dateTo, [FromQuery] int limit, CancellationToken token)
public async Task<ActionResult<IEnumerable<WitsDataDto>>> GetValuesForGraph([FromQuery] int discriminatorId,
[FromQuery] DateTimeOffset dateFrom, [FromQuery] DateTimeOffset dateTo, [FromQuery] int approxPointsCount, CancellationToken token)
{
var result = await witsDataService.GetValuesForGraph(dateFrom, dateTo);
var result = await witsDataService.GetValuesForGraph(discriminatorId, dateFrom, dateTo, approxPointsCount, token);
return Ok(result);
}
@ -72,10 +77,10 @@ public class WitsDataController : ControllerBase, IWitsDataApi
/// <returns></returns>
[HttpPost]
[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
public async Task<IActionResult> InsertRange([FromBody] IEnumerable<WitsDataDto> dtos, CancellationToken token)
public async Task<IActionResult> AddRange([FromBody] IEnumerable<WitsDataDto> dtos, CancellationToken token)
{
var result = await witsDataService.InsertRange(dtos, token);
var result = await witsDataService.AddRange(dtos, token);
return CreatedAtAction(nameof(InsertRange), result);
return CreatedAtAction(nameof(AddRange), result);
}
}

View File

@ -7,10 +7,10 @@ public interface IWitsDataClient
private const string BaseRoute = "/api/witsData";
[Get($"{BaseRoute}/graph")]
Task<IApiResponse<IEnumerable<WitsDataDto>>> GetValuesForGraph([Query] DateTimeOffset dateFrom, [Query] DateTimeOffset dateTo, [Query] int limit, CancellationToken token);
Task<IApiResponse<IEnumerable<WitsDataDto>>> GetValuesForGraph([Query] int discriminatorId, [Query] DateTimeOffset dateFrom, [Query] DateTimeOffset dateTo, [Query] int approxPointsCount, CancellationToken token);
[Post($"{BaseRoute}/")]
Task<IApiResponse<int>> InsertRange([Body] IEnumerable<WitsDataDto> dtos, CancellationToken token);
Task<IApiResponse<int>> AddRange([Body] IEnumerable<WitsDataDto> dtos, CancellationToken token);
[Get($"{BaseRoute}/part")]
Task<IApiResponse<IEnumerable<WitsDataDto>>> GetPart([Query] int discriminatorId, [Query] DateTimeOffset dateBegin, [Query] int take = 24 * 60 * 60, CancellationToken token = default);

View File

@ -62,7 +62,7 @@ public class WitsDataControllerTest : BaseIntegrationTest
dbContext.CleanupDbSet<ParameterData>();
//act
await InsertRange();
await AddRange();
}
[Fact]
@ -71,19 +71,27 @@ public class WitsDataControllerTest : BaseIntegrationTest
//arrange
dbContext.CleanupDbSet<ParameterData>();
var discriminatorId = 1;
var dateFrom = DateTimeOffset.UtcNow;
var dateTo = DateTimeOffset.UtcNow;
var approxPointCount = 12;
//act
var response = await witsDataClient.GetValuesForGraph(discriminatorId, dateFrom, dateTo, approxPointCount, new CancellationToken());
//assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content);
Assert.Empty(response.Content);
}
#region AfterSave
[Fact]
public async Task GetDatesRangeAsync_AfterSave_returns_success()
{
//arrange
dbContext.CleanupDbSet<ParameterData>();
var dtos = await InsertRange();
var dtos = await AddRange();
var discriminatorId = dtos.FirstOrDefault()!.DiscriminatorId;
//act
@ -116,7 +124,7 @@ public class WitsDataControllerTest : BaseIntegrationTest
//arrange
dbContext.CleanupDbSet<ParameterData>();
var dtos = await InsertRange();
var dtos = await AddRange();
var discriminatorId = dtos.FirstOrDefault()!.DiscriminatorId;
var dateBegin = dtos.FirstOrDefault()!.Timestamped;
var take = 1;
@ -134,11 +142,24 @@ public class WitsDataControllerTest : BaseIntegrationTest
[Fact]
public async Task GetValuesForGraph_AfterSave_returns_success()
{
//arrange
dbContext.CleanupDbSet<ParameterData>();
var dtos = await AddRange(37);
var discriminatorId = dtos.FirstOrDefault()!.DiscriminatorId;
var dateFrom = dtos.Select(e => e.Timestamped).Min();
var dateTo = dtos.Select(e => e.Timestamped).Max();
var approxPointCount = 12;
//act
var response = await witsDataClient.GetValuesForGraph(discriminatorId, dateFrom, dateTo, approxPointCount, new CancellationToken());
//assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(response.Content);
Assert.Equal(approxPointCount, response.Content.Count());
}
#endregion
#region BadRequest
[Fact]
public async Task InsertRange_returns_BadRequest()
{
@ -155,63 +176,43 @@ public class WitsDataControllerTest : BaseIntegrationTest
{
RecordId = -1, // < 0
ItemId = 101, // > 100
Value = "string value"
Value = string.Empty
}
}
}
};
//act
var response = await witsDataClient.InsertRange(dtos, new CancellationToken());
var response = await witsDataClient.AddRange(dtos, new CancellationToken());
//assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
#endregion
private async Task<IEnumerable<WitsDataDto>> InsertRange()
private async Task<IEnumerable<WitsDataDto>> AddRange(int countToCreate = 10)
{
//arrange
var dtos = new List<WitsDataDto>()
var dtos = new List<WitsDataDto>();
var timestamped = DateTimeOffset.UtcNow;
for (var i = 0; i < countToCreate && i < 100; i++)
{
new WitsDataDto()
dtos.Add(new WitsDataDto()
{
DiscriminatorId = 1,
Timestamped = DateTimeOffset.UtcNow,
Timestamped = timestamped.AddSeconds(i),
Values = new List<WitsValueDto>()
{
new WitsValueDto()
{
RecordId = 11,
ItemId = 22,
Value = "string value"
},
new WitsValueDto()
{
RecordId = 11,
ItemId = 27,
Value = 2.22
RecordId = i + 1,
ItemId = i + 1,
Value = new Random().Next(1, 100)
}
}
},
new WitsDataDto()
{
DiscriminatorId = 2,
Timestamped = DateTimeOffset.UtcNow,
Values = new List<WitsValueDto>()
{
new WitsValueDto()
{
RecordId = 13,
ItemId = 14,
Value = "string value"
});
}
}
}
};
//act
var response = await witsDataClient.InsertRange(dtos, new CancellationToken());
var response = await witsDataClient.AddRange(dtos, new CancellationToken());
//assert
var count = dtos.SelectMany(e => e.Values).Count();

View File

@ -1,5 +1,6 @@
using Mapster;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
using Persistence.Database.Entity;
using Persistence.Models;
using Persistence.Repositories;
@ -49,12 +50,31 @@ public class ParameterRepository : IParameterRepository
return dtos;
}
public Task<ParameterDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo)
public async Task<IEnumerable<ParameterDto>> GetValuesForGraph(int discriminatorId, DateTimeOffset dateFrom, DateTimeOffset dateTo,
int approxPointsCount, int? ratio, CancellationToken token)
{
throw new NotImplementedException();
var query = db.Set<ParameterData>().AsNoTracking();
var universalDateFrom = dateFrom.ToUniversalTime();
var universalDateTo = dateTo.ToUniversalTime();
query = query
.Where(e => e.DiscriminatorId == discriminatorId && e.Timestamp >= universalDateFrom && e.Timestamp <= universalDateTo)
.OrderBy(e => e.Timestamp);
if (ratio != null)
{
query = query.Where(e => ((int) (e.Timestamp - dateFrom).TotalSeconds) % ratio == 0);
}
public async Task<int> InsertRange(IEnumerable<ParameterDto> dtos, CancellationToken token)
var entities = await query
.Take((int)(2.5 * approxPointsCount))
.ToListAsync(token);
var dtos = entities.Select(e => e.Adapt<ParameterDto>());
return dtos;
}
public async Task<int> AddRange(IEnumerable<ParameterDto> dtos, CancellationToken token)
{
var entities = dtos.Select(e => e.Adapt<ParameterData>());

View File

@ -11,10 +11,14 @@ public interface IWitsDataApi : ISyncWithDiscriminatorApi<WitsDataDto>
/// <summary>
/// Получить набор параметров (Wits) для построения графика
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="dateFrom"></param>
/// <param name="dateTo"></param>
/// <param name="approxPointsCount"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<ActionResult<IEnumerable<WitsDataDto>>> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo, int limit, CancellationToken token);
Task<ActionResult<IEnumerable<WitsDataDto>>> GetValuesForGraph(int discriminatorId, DateTimeOffset dateFrom, DateTimeOffset dateTo,
int approxPointsCount, CancellationToken token);
/// <summary>
/// Сохранить набор параметров (Wits)
@ -22,5 +26,5 @@ public interface IWitsDataApi : ISyncWithDiscriminatorApi<WitsDataDto>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IActionResult> InsertRange(IEnumerable<WitsDataDto> dtos, CancellationToken token);
Task<IActionResult> AddRange(IEnumerable<WitsDataDto> dtos, CancellationToken token);
}

View File

@ -0,0 +1,12 @@
using Persistence.Models.Enumerations;
namespace Persistence.Models.Configurations;
public class WitsInfo
{
public int RecordId { get; set; }
public int ItemId { get; set; }
public WitsType ValueType { get; set; }
}

View File

@ -0,0 +1,24 @@
namespace Persistence.Models.Enumerations;
/// <summary>
/// WITS Data Type Codes
/// </summary>
public enum WitsType
{
/// <summary>
/// Alphanumeric, Rep Code = 65
/// </summary>
A,
/// <summary>
/// 32 bit 2's complement signed integer, Rep Code = 73
/// </summary>
L,
/// <summary>
/// 16 bit 2's complement signed integer, Rep Code = 79
/// </summary>
S,
/// <summary>
/// 32 bit IEEE single precision floating point, Rep Code = 128
/// </summary>
F,
}

View File

@ -6,6 +6,14 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Services\Config\WitsConfig.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Services\Config\WitsConfig.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />

View File

@ -22,10 +22,15 @@ public interface IParameterRepository
/// <summary>
/// Получить набор параметров (Wits) для построения графика
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="dateFrom"></param>
/// <param name="dateTo"></param>
/// <param name="approxPointsCount"></param>
/// <param name="ratio"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<ParameterDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo);
Task<IEnumerable<ParameterDto>> GetValuesForGraph(int discriminatorId, DateTimeOffset dateFrom, DateTimeOffset dateTo,
int approxPointsCount, int? ratio, CancellationToken token);
/// <summary>
/// Сохранить набор параметров (Wits)
@ -34,5 +39,5 @@ public interface IParameterRepository
/// <param name="witsIds"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> InsertRange(IEnumerable<ParameterDto> dtos, CancellationToken token);
Task<int> AddRange(IEnumerable<ParameterDto> dtos, CancellationToken token);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,46 @@
using Persistence.Models;
namespace Persistence.Services.Interfaces;
/// <summary>
/// Сервис для работы с параметрами Wits
/// </summary>
public interface IWitsDataService
{
/// <summary>
/// Получить набор параметров для построения графика
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<DatesRangeDto> GetDatesRangeAsync(int idDiscriminator, CancellationToken token);
/// <summary>
/// Получить порцию записей, начиная с заданной даты
/// </summary>
/// <param name="idDiscriminator"></param>
/// <param name="dateBegin"></param>
/// <param name="take"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<WitsDataDto>> GetPart(int idDiscriminator, DateTimeOffset dateBegin, int take, CancellationToken token);
Task<WitsDataDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo);
Task<int> InsertRange(IEnumerable<WitsDataDto> dtos, CancellationToken token);
/// <summary>
/// Получить диапазон дат, для которых есть данные в репозитории
/// </summary>
/// <param name="discriminatorId"></param>
/// <param name="dateFrom"></param>
/// <param name="dateTo"></param>
/// <param name="approxPointsCount"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<IEnumerable<WitsDataDto>> GetValuesForGraph(int discriminatorId, DateTimeOffset dateFrom, DateTimeOffset dateTo, int approxPointsCount, CancellationToken token);
/// <summary>
/// Сохранить набор параметров
/// </summary>
/// <param name="dtos"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<int> AddRange(IEnumerable<WitsDataDto> dtos, CancellationToken token);
}

View File

@ -1,15 +1,28 @@
using Persistence.Models;
using System.Text.Json.Serialization;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Persistence.Models;
using Persistence.Repositories;
using Persistence.Services.Interfaces;
using Persistence.Models.Configurations;
using Persistence.Models.Enumerations;
using System.Globalization;
namespace Persistence.Services;
public class WitsDataService : IWitsDataService
{
private readonly IParameterRepository witsDataRepository;
private readonly WitsInfo[] witsInfo;
private const int multiplier = 1000;
public WitsDataService(IParameterRepository witsDataRepository)
private const string witsConfigPath = "Persistence.Services.Config.WitsConfig.json";
public WitsDataService(IParameterRepository witsDataRepository, IConfiguration configuration)
{
this.witsDataRepository = witsDataRepository;
this.witsInfo = GetWitsInfo();
}
public Task<DatesRangeDto> GetDatesRangeAsync(int idDiscriminator, CancellationToken token)
@ -23,37 +36,30 @@ public class WitsDataService : IWitsDataService
{
var dtos = await witsDataRepository.GetPart(idDiscriminator, dateBegin, take, token);
var result = new List<WitsDataDto>();
foreach (var dto in dtos)
{
var witsDataDto = result.FirstOrDefault(e => e.DiscriminatorId == dto.DiscriminatorId && e.Timestamped == dto.Timestamp);
if (witsDataDto == null)
{
witsDataDto = new WitsDataDto()
{
DiscriminatorId = dto.DiscriminatorId,
Timestamped = dto.Timestamp
};
result.Add(witsDataDto);
}
var witsValueDto = new WitsValueDto()
{
RecordId = DecodeRecordId(dto.ParameterId),
ItemId = DecodeItemId(dto.ParameterId),
Value = dto.Value
};
witsDataDto.Values.Append(witsValueDto);
}
var result = AdaptToWitsData(dtos);
return result;
}
public Task<WitsDataDto> GetValuesForGraph(DateTimeOffset dateFrom, DateTimeOffset dateTo)
public async Task<IEnumerable<WitsDataDto>> GetValuesForGraph(int discriminatorId, DateTimeOffset dateFrom, DateTimeOffset dateTo,
int approxPointsCount, CancellationToken token)
{
throw new NotImplementedException();
var intervalSec = (dateTo - dateFrom).TotalSeconds;
int? ratio = null;
if (intervalSec > 2 * approxPointsCount)
{
ratio = (int) intervalSec / approxPointsCount;
}
public async Task<int> InsertRange(IEnumerable<WitsDataDto> dtos, CancellationToken token)
var dtos = await witsDataRepository.GetValuesForGraph(discriminatorId, dateFrom, dateTo, approxPointsCount, ratio, token);
var result = AdaptToWitsData(dtos);
return result;
}
public async Task<int> AddRange(IEnumerable<WitsDataDto> dtos, CancellationToken token)
{
var parameterDtos = dtos.SelectMany(e => e.Values.Select(t => new ParameterDto()
{
@ -62,7 +68,7 @@ public class WitsDataService : IWitsDataService
Value = t.Value.ToString()!,
Timestamp = e.Timestamped
}));
var result = await witsDataRepository.InsertRange(parameterDtos, token);
var result = await witsDataRepository.AddRange(parameterDtos, token);
return result;
}
@ -86,4 +92,81 @@ public class WitsDataService : IWitsDataService
return resultId;
}
private IEnumerable<WitsDataDto> AdaptToWitsData(IEnumerable<ParameterDto> dtos)
{
var result = new List<WitsDataDto>();
foreach (var dto in dtos)
{
var witsDataDto = result.FirstOrDefault(e => e.DiscriminatorId == dto.DiscriminatorId && e.Timestamped == dto.Timestamp);
if (witsDataDto == null)
{
witsDataDto = new WitsDataDto()
{
DiscriminatorId = dto.DiscriminatorId,
Timestamped = dto.Timestamp
};
result.Add(witsDataDto);
}
var recordId = DecodeRecordId(dto.ParameterId);
var itemId = DecodeItemId(dto.ParameterId);
var witsValueDto = new WitsValueDto()
{
RecordId = recordId,
ItemId = itemId,
Value = ConvertValue(recordId, itemId, dto.Value)
};
witsDataDto.Values.Append(witsValueDto);
}
return result;
}
private object ConvertValue(int recordId, int itemId, string value)
{
var witsType = witsInfo.FirstOrDefault(e => e.ItemId == itemId
&& e.RecordId == recordId)?.ValueType;
switch(witsType)
{
default:
{
return value;
}
case WitsType.S:
{
var result = Int16.Parse(value);
return result;
}
case WitsType.L:
{
var result = Int32.Parse(value);
return result;
}
case WitsType.F:
{
var result = float.Parse(value, CultureInfo.InvariantCulture);
return result;
}
}
}
private WitsInfo[] GetWitsInfo()
{
var stream = System.Reflection.Assembly.GetExecutingAssembly()
.GetManifestResourceStream(witsConfigPath);
var options = new JsonSerializerOptions
{
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
},
};
var records = JsonSerializer.Deserialize<WitsInfo[]>(stream!, options) ?? [];
return records;
}
}