using ClosedXML.Excel; using InnovEnergy.App.Backend.DataTypes; namespace InnovEnergy.App.Backend.Services; public static class ExcelDataParser { // Column headers from the ESS Link Cloud Excel export private const string ColDateTime = "Data time"; private const string ColPvToday = "PV Generated Energy Today"; private const string ColLoadToday = "Load Consumption Today"; private const string ColGridImportToday = "Purchased Energy Today"; private const string ColGridExportToday = "Feed in energy Today"; private const string ColBattChargedToday = "Daily Battery Charged"; private const string ColBattDischargedToday = "Battery Discharged Today"; /// /// Parses an ESS Link Cloud Excel export file and returns one DailyEnergyData per day. /// Takes the last row of each day (where "Today" cumulative values are highest). /// public static List Parse(string filePath) { if (!File.Exists(filePath)) throw new FileNotFoundException($"Excel file not found: {filePath}"); using var workbook = new XLWorkbook(filePath); var worksheet = workbook.Worksheet(1); var lastRow = worksheet.LastRowUsed()?.RowNumber() ?? 0; if (lastRow < 2) throw new InvalidOperationException("Excel file has no data rows."); // Find column indices by header name (row 1) var headerRow = worksheet.Row(1); var colMap = new Dictionary(); for (var col = 1; col <= worksheet.LastColumnUsed()?.ColumnNumber(); col++) { var header = headerRow.Cell(col).GetString().Trim(); if (!string.IsNullOrEmpty(header)) colMap[header] = col; } // Validate required columns exist var requiredCols = new[] { ColDateTime, ColPvToday, ColLoadToday, ColGridImportToday, ColGridExportToday, ColBattChargedToday, ColBattDischargedToday }; foreach (var rc in requiredCols) { if (!colMap.ContainsKey(rc)) throw new InvalidOperationException($"Required column '{rc}' not found in Excel file. Available: {string.Join(", ", colMap.Keys)}"); } // Read all rows, group by date, keep last row per day var dailyLastRows = new SortedDictionary(); for (var row = 2; row <= lastRow; row++) { var dateTimeStr = worksheet.Row(row).Cell(colMap[ColDateTime]).GetString().Trim(); if (string.IsNullOrEmpty(dateTimeStr)) continue; // Extract date portion (first 10 chars: "2026-02-10") var date = dateTimeStr.Length >= 10 ? dateTimeStr[..10] : dateTimeStr; var data = new DailyEnergyData { Date = date, PvProduction = GetDouble(worksheet, row, colMap[ColPvToday]), LoadConsumption = GetDouble(worksheet, row, colMap[ColLoadToday]), GridImport = GetDouble(worksheet, row, colMap[ColGridImportToday]), GridExport = GetDouble(worksheet, row, colMap[ColGridExportToday]), BatteryCharged = GetDouble(worksheet, row, colMap[ColBattChargedToday]), BatteryDischarged = GetDouble(worksheet, row, colMap[ColBattDischargedToday]), }; // Always overwrite — last row of the day has the final cumulative values dailyLastRows[date] = data; } Console.WriteLine($"[ExcelDataParser] Parsed {dailyLastRows.Count} days from {filePath}"); return dailyLastRows.Values.ToList(); } private static double GetDouble(IXLWorksheet ws, int row, int col) { var cell = ws.Row(row).Cell(col); if (cell.IsEmpty()) return 0; return cell.TryGetValue(out var val) ? Math.Round(val, 4) : 0; } }