How to fix Aggregate Measurements in order to display properly?

Cenk 1,021 Reputation points
2024-11-27T15:54:00.43+00:00

Hi,

I am working on a Blazor application for weight measurement. Data is coming from a third-party device in real time. I have this weighing results component on my dashboard that I am trying to display on a grid according to some rules. The data grid periodically loads data.

Aggregate Measurements will calculate the client's (Oğulbey, Yeşilköy) measurement and plate number (06GG111, 34HH222) on the current day. You can see the trend change from the 2-day sample data. It starts from 3000 for Oğulbey,06GG111and goes up to 4224 for 2024-11-27. (It is the first sequence) Then it goes down to 3000 for Oğulbey,06GG111 on the same day, and goes to 4068. (It is the second sequence) This is the definition of trend change. (Same as the decrease)

Here is a 2-day sample data in the database
3000	2024-11-26 16:45:49	Oğulbey		06GG111
3345	2024-11-26 16:45:56	Yeşilköy	34HH222
3426	2024-11-26 16:46:56	Oğulbey		06GG111
3771	2024-11-26 16:47:26	Yeşilköy	34HH222
3000	2024-11-27 16:59:27	Oğulbey		06GG111
3345	2024-11-27 16:59:48	Yeşilköy	34HH222
3661	2024-11-27 17:00:27	Oğulbey		06GG111
4006	2024-11-27 17:00:40	Yeşilköy	34HH222
4224	2024-11-27 17:01:27	Oğulbey		06GG111
3000	2024-11-27 17:04:11	Oğulbey		06GG111
3345	2024-11-27 17:04:30	Yeşilköy	34HH222
3530	2024-11-27 17:05:11	Oğulbey		06GG111
3875	2024-11-27 17:05:24	Yeşilköy	34HH222
4068	2024-11-27 17:06:11	Oğulbey		06GG111
expected output after Aggregate measurement:
(explanation not displayed: Second sequence because of trend change)
3000	4068	2024-11-27 17:06:11	Oğulbey		06GG111
3345	3875	2024-11-27 17:05:24	Yeşilköy	34HH222
(explanation not displayed: first sequence)
3000	4224	2024-11-27 17:01:27	Oğulbey		06GG111
3345	4006	2024-11-27 17:00:40	Yeşilköy	34HH222
(explanation not displayed: previous day)
3345	3771	2024-11-26 16:47:26	Yeşilköy	34HH222
3000	3426	2024-11-26 16:46:56	Oğulbey		06GG111


Here is the component but it does not give the expected output.

@page "/weighing-results"
@using OfficeOpenXml
@using OfficeOpenXml.Style
@using Radzen
@using Radzen.Blazor
@using WeighingDeviceDashboard.Service
@inject IMeasurementService MeasurementService
@inject IJSRuntime JS
<style>
    .highlight {
        animation: blink 1s alternate 5; /* Adjust duration, iterations, and timing as needed */
        /* animation-fill-mode: forwards; */
        animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
    }
    @@keyframes blink {
        0% {
            background-color: #ff0000; /* Yellow (highlight color) */
            font-weight: bold;
        }
        50% {
            background-color: #ffffff; /* White (default background color) */
        }
        100% {
            background-color: #ff0000; /* Yellow (highlight color) */
            font-weight: bold;
        }
    }
</style>
<RadzenButton Text="Export" Icon="grid_on" Click="@(args => ExportExcel())" Class="mb-4 mr-2" ButtonStyle="ButtonStyle.Secondary" Size="ButtonSize.Small" />
<RadzenDataGrid @ref=grid Data="@weighingResults" TItem="WeighingResult" AllowFiltering="true" AllowColumnResize="false" AllowAlternatingRows="true"
                FilterMode="FilterMode.Advanced" AllowSorting="true" PageSize="15" AllowPaging="true" AllowGrouping="true" PagerHorizontalAlign="HorizontalAlign.Left"
                ShowPagingSummary="true" ColumnWidth="300px" LogicalFilterOperator="LogicalFilterOperator.And"
                IsLoading=@isLoading Sort="@ShowLoading" Page="@ShowLoading" Group="@ShowLoading" Filter="@ShowLoading">
    <Columns>
        <RadzenDataGridColumn Property="@nameof(WeighingResult.ClientId)" Title="Warehouse" Width="150px" />
        <RadzenDataGridColumn Property="@nameof(WeighingResult.PlateNumber)" Title="Plate Number" Width="150px">
            <Template Context="result">
                <span class="@((result as WeighingResult).IsNew? "highlight" : "")">@result.PlateNumber</span>
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn Property="@nameof(WeighingResult.FirstWeighingValue)" Title="First Weighing (KG)" Width="200px">
            <Template Context="result">
                <span>@result.FirstWeighingValue.ToString("F0")</span>
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn Property="@nameof(WeighingResult.LastWeighingValue)" Title="Last Weighing (KG)" Width="200px">
            <Template Context="result">
                <span>@result.LastWeighingValue.ToString("F0")</span>
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn Property="@nameof(WeighingResult.WeightDifference)" Title="Weight Difference (KG)" Width="230px">
            <Template Context="result">
                <span class="@((result as WeighingResult).IsNew? "highlight" : "")">
                    @(result.WeightDifference!= null? 
                        (result.WeightDifference > 0? $"+{result.WeightDifference.Value.ToString("F0")}" : 
                            (result.WeightDifference < 0? $"{result.WeightDifference.Value.ToString("F0")}" : $"0")) 
                        : "-")
                </span>
            </Template>
        </RadzenDataGridColumn>
        <RadzenDataGridColumn Property="@nameof(WeighingResult.LastWeighingDateTime)" Title="Last Weighing Date Time" Width="200px" />
        <RadzenDataGridColumn Property="@nameof(WeighingResult.ReceiptNo)" Title="Receipt Number" Width="200px" />
    </Columns>
</RadzenDataGrid>
@code {
    RadzenDataGrid<WeighingResult> grid = new();
    List<WeighingResult> weighingResults = new List<WeighingResult>();
    private readonly SemaphoreSlim _measurementLock = new SemaphoreSlim(1, 1);
    bool isLoading = false;
    private CancellationTokenSource _cancellationTokenSource;
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        _cancellationTokenSource = new CancellationTokenSource();
        await LoadDataPeriodically(_cancellationTokenSource.Token);
    }
    private async Task LoadDataPeriodically(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            await ShowLoading();
            weighingResults = await GetWeighingResultsAsync();
            await grid.Reload();
            StateHasChanged(); // Ensure UI updates
            try
            {
                await Task.Delay(60000, cancellationToken); // 1,2 minutes delay
            }
            catch (TaskCanceledException)
            {
                // Task was canceled, exit the loop
                break;
            }
        }
    }
    private async Task ShowLoading()
    {
        isLoading = true;
        await Task.Yield();
        isLoading = false;
    }
    private async Task<List<WeighingResult>> GetWeighingResultsAsync()
    {
        await _measurementLock.WaitAsync();
        try
        {
            var measurements = await MeasurementService.GetMeasurementsAsync();
            return AggregateMeasurements(measurements);
        }
        finally
        {
            _measurementLock.Release();
        }
    }
    private List<WeighingResult> AggregateMeasurements(IEnumerable<Measurement> measurements)
    {
        var results = new List<WeighingResult>();
        var current_date = DateTime.Now.Date;
        var measurementGroups = measurements
            .GroupBy(m => (m.ClientId, m.PlateNumber, m.Timestamp.Date))
            .Select(g => new
            {
                Group = g.OrderBy(m => m.Timestamp).ToList(),
                ClientId = g.Key.ClientId,
                PlateNumber = g.Key.PlateNumber,
                Date = g.Key.Date
            });
        foreach (var groupInfo in measurementGroups)
        {
            int sequence = 1;
            Trend? currentTrend = null;
            Measurement? previousMeasurement = null;
            WeighingResult? currentResult = null;
            foreach (var measurement in groupInfo.Group)
            {
                var currentValue = (float)measurement.Value;
                // Determine trend
                Trend? newTrend = null;
                if (previousMeasurement != null)
                {
                    var previousValue = (float)previousMeasurement.Value;
                    newTrend = GetTrend(previousValue, currentValue);
                }
                // Check for trend change
                if (previousMeasurement != null && newTrend != currentTrend)
                {
                    // Create a new result when trend changes
                    sequence++;
                    currentResult = new WeighingResult
                        {
                            ClientId = measurement.ClientId,
                            PlateNumber = measurement.PlateNumber,
                            FirstWeighingValue = currentValue,
                            LastWeighingValue = currentValue,
                            LastWeighingDateTime = measurement.Timestamp,
                            ReceiptNo = measurement.ReceiptNo,
                            WeighingSequence = sequence,
                            IsNew = true // Mark as new for highlighting
                        };
                    results.Add(currentResult);
                }
                else if (currentResult == null)
                {
                    // Create the first result
                    currentResult = new WeighingResult
                        {
                            ClientId = measurement.ClientId,
                            PlateNumber = measurement.PlateNumber,
                            FirstWeighingValue = currentValue,
                            LastWeighingValue = currentValue,
                            LastWeighingDateTime = measurement.Timestamp,
                            ReceiptNo = measurement.ReceiptNo,
                            WeighingSequence = sequence,
                            IsNew = true // Mark as new for highlighting
                        };
                    results.Add(currentResult);
                }
                else
                {
                    // Update existing result
                    if (currentValue < currentResult.FirstWeighingValue)
                    {
                        currentResult.FirstWeighingValue = currentValue;
                    }
                    if (currentValue > currentResult.LastWeighingValue)
                    {
                        currentResult.LastWeighingValue = currentValue;
                        currentResult.LastWeighingDateTime = measurement.Timestamp;
                        currentResult.ReceiptNo = measurement.ReceiptNo;
                    }
                }
                // Update trend tracking
                currentTrend = newTrend;
                previousMeasurement = measurement;
            }
        }
        // Sort results by date and sequence
        return results
            .OrderByDescending(r => r.LastWeighingDateTime)
            .ThenBy(r => r.WeighingSequence)
            .ToList();
    }
    private Trend? GetTrend(float previousValue, float currentValue)
    {
        if (currentValue > previousValue) return Trend.Increasing;
        if (currentValue < previousValue) return Trend.Decreasing;
        return null;
    }
    private enum Trend
    {
        Increasing,
        Decreasing
    }
    private async Task ExportExcel()
    {
        var filter = grid.View;
        ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
        var stream = new MemoryStream();
        using (var package = new ExcelPackage(stream))
        {
            var workSheet = package.Workbook.Worksheets.Add("Weighing Results");
            var recordIndex = 2;
            workSheet.Row(1).Style.Font.Bold = true;
            var headerCells = workSheet.Cells["A1:G1"];
            headerCells.Style.Font.Bold = true;
            headerCells.Style.Font.Size = 13;
            headerCells.Style.Border.BorderAround(ExcelBorderStyle.Thin);
            headerCells.Style.Border.Top.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Border.Left.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Border.Right.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
            headerCells.Style.Font.Color.SetColor(System.Drawing.Color.Black);
            headerCells.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
            headerCells.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);
            workSheet.Cells[1, 1].Value = "Warehouse";
            workSheet.Cells[1, 2].Value = "Plate Number";
            workSheet.Cells[1, 3].Value = "First Weighing Value";
            workSheet.Cells[1, 4].Value = "Last Weighing Value";
            workSheet.Cells[1, 5].Value = "Weight Difference";
            workSheet.Cells[1, 6].Value = "Last Weighing Date Time";
            workSheet.Cells[1, 7].Value = "Receipt Number";
            foreach (var result in filter)
            {
                workSheet.Cells[recordIndex, 1].Value = result.ClientId;
                workSheet.Cells[recordIndex, 2].Value = result.PlateNumber;
                workSheet.Cells[recordIndex, 3].Value = result.FirstWeighingValue.ToString("F0");
                workSheet.Cells[recordIndex, 4].Value = result.LastWeighingValue.ToString("F0");
                workSheet.Cells[recordIndex, 5].Value = result.WeightDifference?.ToString("F0");
                workSheet.Cells[recordIndex, 6].Value = result.LastWeighingDateTime.ToString("g");
                workSheet.Cells[recordIndex, 7].Value = result.ReceiptNo;
                recordIndex++;
            }
            workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns();
            await package.SaveAsync();
        }
        stream.Position = 0;
        var excelName = $"Weighing Results-{DateTime.Now.ToString("ddMMyyyyHHmm")}.xlsx";
        using var streamRef = new DotNetStreamReference(stream: stream);
        await JS.InvokeVoidAsync("downloadFileFromStream", excelName, streamRef);
    }
    public void Dispose()
    {
        _cancellationTokenSource?.Cancel();
        _cancellationTokenSource?.Dispose();
    }
}
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,612 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,093 questions
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.