using Microsoft.EntityFrameworkCore; using Npgsql; using Tau.Acuvim.Portal.Data; using Tau.Acuvim.Portal.DTOs; namespace Tau.Acuvim.Portal.Services; public sealed class MeasurementQueryService(AppDbContext db) { private static readonly Dictionary AllowedBuckets = new(StringComparer.OrdinalIgnoreCase) { ["minute"] = "1 minute", ["5min"] = "5 minutes", ["15min"] = "15 minutes", ["hour"] = "1 hour", ["day"] = "1 day", }; public bool IsValidBucket(string bucket) => AllowedBuckets.ContainsKey(bucket); public async Task> GetBucketsAsync( Guid deviceId, DateTime fromUtc, DateTime toUtc, string bucket, CancellationToken ct = default) { if (!AllowedBuckets.TryGetValue(bucket, out var interval)) { throw new ArgumentException($"Unsupported bucket '{bucket}'.", nameof(bucket)); } var conn = db.Database.GetDbConnection(); if (conn.State != System.Data.ConnectionState.Open) { await conn.OpenAsync(ct); } await using var cmd = conn.CreateCommand(); cmd.CommandText = $""" SELECT time_bucket(INTERVAL '{interval}', "Time") AS bucket, avg("ActivePowerKw") AS avg_kw, max("ActivePowerKw") AS max_kw, min("ActivePowerKw") AS min_kw, max("EnergyImportedKwh") - min("EnergyImportedKwh") AS imp_delta, max("EnergyExportedKwh") - min("EnergyExportedKwh") AS exp_delta, count(*) AS samples FROM monitoring."PowerMeasurements" WHERE "DeviceId" = @deviceId AND "Time" >= @fromUtc AND "Time" < @toUtc GROUP BY bucket ORDER BY bucket; """; cmd.Parameters.Add(new NpgsqlParameter("@deviceId", deviceId)); cmd.Parameters.Add(new NpgsqlParameter("@fromUtc", DateTime.SpecifyKind(fromUtc, DateTimeKind.Utc))); cmd.Parameters.Add(new NpgsqlParameter("@toUtc", DateTime.SpecifyKind(toUtc, DateTimeKind.Utc))); var results = new List(); await using var reader = await cmd.ExecuteReaderAsync(ct); while (await reader.ReadAsync(ct)) { results.Add(new MeasurementBucketDto( BucketStart: reader.GetDateTime(0), AvgActivePowerKw: reader.GetDouble(1), MaxActivePowerKw: reader.GetDouble(2), MinActivePowerKw: reader.GetDouble(3), EnergyImportedKwhDelta: reader.IsDBNull(4) ? null : reader.GetDouble(4), EnergyExportedKwhDelta: reader.IsDBNull(5) ? null : reader.GetDouble(5), SampleCount: reader.GetInt32(6))); } return results; } }