namespace MeterVault.Infrastructure.Dashboard; /// KPI card figures for a period plus the delta versus the previous comparable period. public sealed record CostKpi(double Current, double Previous) { public double Delta => Current - Previous; public double DeltaPercent => Previous == 0 ? 0 : (Current - Previous) / Math.Abs(Previous) * 100.0; /// +1 up, -1 down, 0 flat. public int Direction => Math.Sign(Math.Round(Delta, 2)); } /// The overview KPIs: month and year cost, each with its previous-period comparison. public sealed record DashboardSummary(DateOnly AsOf, CostKpi Month, CostKpi Year, double LatestMonthCost); /// One slice of the cost breakdown / "what costs most" view. public sealed record CategorySlice(string Name, string? ColorHex, double Cost); /// The first thing missing before the dashboard can show a cost, in the order they are set up. public enum CostSetupGap { None, NoMeters, NoCategories, NoMembers, NoTariffs, } /// /// What exists of the chain a cost figure needs: a meter, a category that counts it, and a price. /// /// /// Manual costs stand in for the meter side only on an instance without meters. Once there are meters, /// their chain is diagnosed on its own: the diagnosis only runs when this year shows no cost, so any /// manual cost there is from another year and explains nothing about the meters. /// public sealed record CostSetup(bool HasMeters, bool HasCategories, bool HasMembers, bool HasTariffs, bool HasManualCosts) { public CostSetupGap FirstGap => !HasMeters && !HasManualCosts ? CostSetupGap.NoMeters : !HasCategories ? CostSetupGap.NoCategories : !HasMeters ? CostSetupGap.None : !HasMembers ? CostSetupGap.NoMembers : !HasTariffs ? CostSetupGap.NoTariffs : CostSetupGap.None; } /// A point on a monthly cost/consumption trend. public sealed record TrendPoint(DateOnly Period, double Cost); /// A row of the "what cost more / less" difference view. public sealed record DifferenceRow(string Name, double Current, double Previous) { public double Delta => Current - Previous; public double DeltaPercent => Previous == 0 ? 0 : (Current - Previous) / Math.Abs(Previous) * 100.0; }