using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection.PortableExecutable; using System.Threading.Tasks; using BattSim.Models; using Microsoft.AspNetCore.Components.Forms; namespace BattSim.Services { public static class FluviusDataHandler { public static async Task> LoadAndProcessFile(IBrowserFile file) { var lines = await ReadCsvFile(file); var energyData = new ConcurrentDictionary(); // Thread-safe collection // Process lines in parallel Parallel.ForEach(lines, line => { try { var parts = SplitLine(line); DateTime time = ParseDateTime(parts[..2]); double volume = ParseVolume(parts[8]); string register = parts[7].Trim(); bool dayTarif = register.Contains("Dag"); var entry = new EnergyData { Time = time, DayTariff = dayTarif }; if (register.Contains("Afname")) entry.Consumption = volume; else if (register.Contains("Injectie")) entry.Production = volume; else throw new Exception("Unknown volume register"); energyData.AddOrUpdate(entry.Time, entry, (_, existing) => { existing.Consumption += entry.Consumption; existing.Production += entry.Production; return existing; }); } catch (Exception e) { Console.WriteLine($"Error parsing line: {line}. Skipping to next line. Exception: {e}"); } }); return energyData.ToDictionary(); } public static Dictionary GenerateDailyData(Dictionary energyData) { var dailyEnergyData = new ConcurrentDictionary(); Parallel.ForEach(energyData, entry => { var date = DateOnly.FromDateTime(entry.Key); var energy = entry.Value; // Use AddOrUpdate to avoid double lookup dailyEnergyData.AddOrUpdate( date, energy, // If key doesn't exist, add this value (_, existing) => { // If key exists, aggregate the values existing.Consumption += energy.Consumption; existing.Production += energy.Production; return existing; } ); }); return dailyEnergyData.ToDictionary(); } private static async Task> ReadCsvFile(IBrowserFile file) { await using var stream = file.OpenReadStream(maxAllowedSize: (int)1.0e9); using var reader = new StreamReader(stream, bufferSize: (int)1.0e6); // Skip header await reader.ReadLineAsync(); // Read all lines into memory var lines = new List(); string line; while ((line = await reader.ReadLineAsync()) is not null) { lines.Add(line); } return lines; } private static string[] SplitLine(string line) { var parts = line.Split(';'); if (parts.Length < 9) throw new Exception($"Malformed line (too many parts): {line}"); return parts; } private static DateTime ParseDateTime(string[] dateTimeStrings) { string dateTimeString = dateTimeStrings[0] + " " + dateTimeStrings[1]; return DateTime.ParseExact(dateTimeString, "dd-MM-yyyy HH:mm:ss", CultureInfo.GetCultureInfo("nl-BE")); } private static double ParseVolume(string volumeString) { var volumeStr = volumeString.Trim(); return string.IsNullOrEmpty(volumeStr) ? 0 : double.Parse(volumeStr.Replace(",", "."), CultureInfo.InvariantCulture); } } }