Compare commits

..

2 Commits

5 changed files with 425 additions and 268 deletions

8
Models/PipelineStep.cs Normal file
View File

@@ -0,0 +1,8 @@
namespace BattSim.Models;
public struct PipelineStep
{
public bool Expanded;
public bool Completed;
public bool isProcessing;
}

View File

@@ -3,268 +3,44 @@
@using Radzen.Blazor
@using BattSim.Models
@using BattSim.Services
@using BattSim.Pages.Nodes
<PageTitle>Thuisbatterij Simulator</PageTitle>
<PageTitle>Wattif</PageTitle>
<h1 style="color: white">Thuisbatterij Simulator</h1>
<h1 style="color: white">Wattif - Simuleer verbeteringen aan je energiesysteem</h1>
<div class="pipeline-container">
<!-- Pipeline with 3 connected boxes -->
<div class="pipeline-row">
<div class="pipeline-connector"></div>
<!-- Box 1: Data Upload -->
<div class="pipeline-box @GetPipelineStepClasses(0)">
<div class="box-header" @onclick="() => ToggleBox(0)">
<div class="step-number">1</div>
<div class="step-title">Fluvius Data</div>
<div class="status-indicator"></div>
</div>
<div class="box-content">
<div class="box-content-inner">
<p class="box-description">Upload je Fluvius kwartierdata om te beginnen</p>
<div class="form-section">
<div class="file-input-wrapper">
<InputFile type="file" id="fileUpload" accept=".csv" onchange="async (e) => await OnFileUploaded(e)" />
<label for="fileUpload" class="file-input-label">
@if (StepData[0].isProcessing)
{
<div class="loading-indicator">
<div class="spinner"></div>
<span style="margin-left: 10px;">Data aan het inladen...</span>
</div>
}
else
{
@if (FluviusDataRaw.Length == 0)
{
<text>Kies CSV bestand</text>
}
else
{
<text>OK @FluviusDataRaw.Length kwartieren geladen</text>
}
}
</label>
@if (FluviusDataRaw.Length > 0 && !StepData[0].isProcessing)
{
<p class="file-name">@_uploadedFileName</p>
}
</div>
</div>
@if (StepData[0].Completed)
{
<div class="success-message">
Data succesvol ingeladen en verwerkt!
</div>
}
@if (_uploadError != null)
{
<div class="error-message">
@_uploadError
</div>
}
<div class="info-text">
<p><strong>Tip:</strong> Download een volledig jaar aan kwartierdata voor de meest accurate resultaten.</p>
</div>
</div>
</div>
</div>
<!-- Box 2: Battery Simulation -->
<div class="pipeline-box @GetPipelineStepClasses(1)">
<div class="box-header" @onclick="() => ToggleBox(1)">
<div class="step-number">2</div>
<div class="step-title">Batterij Simulatie</div>
<div class="status-indicator"></div>
</div>
<div class="box-content">
<div class="box-content-inner">
<p class="box-description">Test verschillende batterijconfiguraties</p>
@if (FluviusDataRaw.Length == 0)
{
<div class="info-text">
<p>Upload eerst je Fluvius data (stap 1) om deze stap te kunnen uitvoeren.</p>
</div>
}
else
{
<div class="form-section">
<div style="display: flex; gap: 1rem;">
<div class="input-group" style="flex: 1;">
<label>Batterijcapaciteit (kWh):</label>
<InputNumber @bind-value="BatteryCapacity" T="double" min="0.1" max="100" step="0.1"/>
</div>
<div class="input-group" style="flex: 1;">
<label>Round-trip Efficiëntie (%):</label>
<InputNumber @bind-value="Efficiency" T="double" min="0" max="100" step="1"/>
</div>
</div>
<p style="font-size: 0.85rem; color: var(--text-clr); margin-top: 1rem;">
Efficiëntie is het percentage van opgeslagen energie dat terug kan worden gebruikt.
</p>
</div>
<div style="display: flex; justify-content: center; margin-top: 1.5rem;">
<button @onclick="SimulateBattery" @onclick:stopPropagation disabled="@(StepData[1].isProcessing || FluviusDataRaw.Length == 0)">
@if (StepData[1].isProcessing)
{
<text>Simuleren...</text>
}
else
{
<text>Simuleer Batterij</text>
}
</button>
</div>
@if (StepData[1].Completed)
{
<div class="success-message">
Simulatie voltooid! @SimulationData.Length kwartieren gesimuleerd.
</div>
}
}
</div>
</div>
</div>
<!-- Box 3: Cost Calculation -->
<div class="pipeline-box @GetPipelineStepClasses(2)">
<div class="box-header" @onclick="() => ToggleBox(2)">
<div class="step-number">3</div>
<div class="step-title">Kosten Berekenen</div>
<div class="status-indicator"></div>
</div>
<div class="box-content">
<div class="box-content-inner">
<p class="box-description">Vergelijk kosten met en zonder batterij</p>
<!-- Contractuele Kosten -->
<div style="margin-bottom: 1.5rem;">
<h4 style="color: var(--green-clr); margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-clr); padding-bottom: 0.5rem;">
Contractuele Kosten
</h4>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Vaste vergoeding (€/jaar):</label>
<InputNumber @bind-value="calculator.BasePayment" T="double" min="0" step="1"/>
</div>
<div class="input-group" style="flex: 1;">
<label>Databeheer tarief (€/jaar):</label>
<InputNumber @bind-value="calculator.DataManagementCost" T="double" min="0" step="0.01"/>
</div>
</div>
<!-- Vaste Kosten Subbox (Collapsed by Default) -->
<details class="collapsible-section" style="margin-top: 1rem;">
<summary class="collapsible-header">Vaste Kosten</summary>
<div class="collapsible-content">
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<!-- Vlaamse energieheffing (moved here) -->
<div class="input-group" style="flex: 1;">
<label>Vlaamse energieheffing (€/jaar):</label>
<InputNumber @bind-value="calculator.FlemishEnergyTariff" T="double" min="0" step="0.01"/>
</div>
</div>
<!-- Heffingen Groene Stroom -->
<div style="margin-top: 1rem;">
<h5 style="color: var(--green-clr); margin-bottom: 0.5rem;">Heffingen Groene Stroom</h5>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Groene stroomcertificaten (c€/kWh):</label>
<InputNumber @bind-value="calculator.GreenCertificateCost" T="double" min="0" step="0.0001"/>
</div>
<div class="input-group" style="flex: 1;">
<label>Warmtekracht certificaten (c€/kWh):</label>
<InputNumber @bind-value="calculator.HeatingCertificateCost" T="double" min="0" step="0.0001"/>
</div>
</div>
</div>
<!-- Net- en distributiekosten -->
<div style="margin-top: 1rem;">
<h5 style="color: var(--green-clr); margin-bottom: 0.5rem;">Net- en distributiekosten</h5>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Capaciteitstarief (€/kWh/jaar):</label>
<InputNumber @bind-value="calculator.CapacityCost" T="double" min="0" step="0.01"/>
</div>
<div class="input-group" style="flex: 1;">
<label>Afnametarief (c€/kWh):</label>
<InputNumber @bind-value="calculator.UsageTariff" T="double" min="0" step="0.0001"/>
</div>
</div>
</div>
<!-- Heffingen en Toeslagen -->
<div style="margin-top: 1rem;">
<h5 style="color: var(--green-clr); margin-bottom: 0.5rem;">Heffingen en Toeslagen</h5>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Energiebijdrage (c€/kWh):</label>
<InputNumber @bind-value="calculator.EnergyContribution" T="double" min="0" step="0.0001"/>
</div>
<div class="input-group" style="flex: 1;">
<label>Bijzondere accijns (c€/kWh):</label>
<InputNumber @bind-value="calculator.SpecialTariffs" T="double" min="0" step="0.0001"/>
</div>
</div>
</div>
</div>
</details>
</div>
<!-- Energiekosten -->
<div style="margin-bottom: 1.5rem;">
<h4 style="color: var(--green-clr); margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-clr); padding-bottom: 0.5rem;">
Energiekosten
</h4>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Energiekost (c€/kWh):</label>
<InputNumber @bind-value="calculator.EnergyCost" T="double" min="0" step="0.0001"/>
</div>
<div class="input-group" style="flex: 1;">
<label>Terugleveringsvergoeding (c€/kWh):</label>
<InputNumber @bind-value="calculator.ReturnCost" T="double" min="0" step="0.0001"/>
</div>
</div>
</div>
<div style="display: flex; justify-content: center; margin-top: 1.5rem;">
<button @onclick="Calculate" @onclick:stopPropagation disabled="@(StepData[2].isProcessing || SimulationData.Length == 0)">
@if (StepData[2].isProcessing)
{
<text>Berekenen...</text>
}
else
{
<text>Bereken Kosten</text>
}
</button>
</div>
@if (StepData[2].Completed)
{
<div class="results">
<p><strong>Originele kosten:</strong> <span class="result-value">€@normalCost.ToString("0.00")</span></p>
<p><strong>Gesimuleerde kosten:</strong> <span class="result-value">€@simulatedCost.ToString("0.00")</span></p>
<p><strong>Besparing:</strong> <span class="result-value">€@((normalCost - simulatedCost).ToString("0.00"))</span></p>
@if (normalCost > 0)
{
<p><strong>Besparingspercent:</strong> <span class="result-value">@(((normalCost - simulatedCost) / normalCost * 100).ToString("0.00"))%</span></p>
}
</div>
}
</div>
</div>
</div>
<!-- Node Components -->
<FluviusUploadNode
FluviusDataRaw="FluviusDataRaw"
StepData="StepData[0]"
UploadedFileName="_uploadedFileName"
UploadError="_uploadError"
OnFileUploadedCallback="OnFileUploaded"
OnToggleBoxCallback="() => ToggleBox(0)" />
<BatterySimulationNode
FluviusDataRaw="FluviusDataRaw"
SimulationData="SimulationData"
StepData="StepData[1]"
BatteryCapacity="BatteryCapacity"
Efficiency="Efficiency"
OnSimulateBatteryCallback="SimulateBattery"
OnToggleBoxCallback="() => ToggleBox(1)" />
<CostCalculationNode
SimulationData="SimulationData"
FluviusDataRaw="FluviusDataRaw"
StepData="StepData[2]"
Calculator="calculator"
NormalCost="normalCost"
SimulatedCost="simulatedCost"
OnCalculateCallback="Calculate"
OnToggleBoxCallback="() => ToggleBox(2)" />
</div>
</div>
@@ -338,12 +114,6 @@
double normalCost = 0.0;
double simulatedCost = 0.0;
struct PipelineStep{
public bool Expanded;
public bool Completed;
public bool isProcessing;
}
PipelineStep[] StepData = [
new PipelineStep(), // Step 1
new PipelineStep(), // Step 2
@@ -358,14 +128,6 @@
double BatteryCapacity = 7.5;
double Efficiency = 90;
private string GetPipelineStepClasses(int step){
var classes = new List<string>();
if (StepData[step].Expanded) classes.Add("expanded");
if (StepData[step].Completed) classes.Add("completed");
if (StepData[step].isProcessing) classes.Add("running");
return string.Join(" ", classes);
}
private string GetTimeButtonClass(string period)
{
var baseClass = "time-toggle-button";

View File

@@ -0,0 +1,104 @@
@using Radzen
@using Radzen.Blazor
@using BattSim.Models
@using BattSim.Services
<div class="pipeline-box @GetPipelineStepClasses(StepData)">
<div class="box-header" @onclick="ToggleBox">
<div class="step-number">2</div>
<div class="step-title">Batterij Simulatie</div>
<div class="status-indicator"></div>
</div>
<div class="box-content">
<div class="box-content-inner">
<p class="box-description">Test verschillende batterijconfiguraties</p>
@if (FluviusDataRaw.Length == 0)
{
<div class="info-text">
<p>Upload eerst je Fluvius data (stap 1) om deze stap te kunnen uitvoeren.</p>
</div>
}
else
{
<div class="form-section">
<div style="display: flex; gap: 1rem;">
<div class="input-group" style="flex: 1;">
<label>Batterijcapaciteit (kWh):</label>
<InputNumber @bind-value="BatteryCapacity" T="double" min="0.1" max="100" step="0.1" />
</div>
<div class="input-group" style="flex: 1;">
<label>Round-trip Efficiëntie (%):</label>
<InputNumber @bind-value="Efficiency" T="double" min="0" max="100" step="1" />
</div>
</div>
<p style="font-size: 0.85rem; color: var(--text-clr); margin-top: 1rem;">
Efficiëntie is het percentage van opgeslagen energie dat terug kan worden gebruikt.
</p>
</div>
<div style="display: flex; justify-content: center; margin-top: 1.5rem;">
<button @onclick="SimulateBattery" @onclick:stopPropagation disabled="@(StepData.isProcessing || FluviusDataRaw.Length == 0)">
@if (StepData.isProcessing)
{
<text>Simuleren...</text>
}
else
{
<text>Simuleer Batterij</text>
}
</button>
</div>
@if (StepData.Completed)
{
<div class="success-message">
Simulatie voltooid! @SimulationData.Length kwartieren gesimuleerd.
</div>
}
}
</div>
</div>
</div>
@code {
[Parameter]
public EnergyData[] FluviusDataRaw { get; set; } = [];
[Parameter]
public EnergyData[] SimulationData { get; set; } = [];
[Parameter]
public PipelineStep StepData { get; set; }
[Parameter]
public double BatteryCapacity { get; set; } = 7.5;
[Parameter]
public double Efficiency { get; set; } = 90;
[Parameter]
public EventCallback OnSimulateBatteryCallback { get; set; }
[Parameter]
public EventCallback OnToggleBoxCallback { get; set; }
private string GetPipelineStepClasses(PipelineStep step)
{
var classes = new List<string>();
if (step.Expanded) classes.Add("expanded");
if (step.Completed) classes.Add("completed");
if (step.isProcessing) classes.Add("running");
return string.Join(" ", classes);
}
private void SimulateBattery()
{
OnSimulateBatteryCallback.InvokeAsync();
}
private void ToggleBox()
{
OnToggleBoxCallback.InvokeAsync();
}
}

View File

@@ -0,0 +1,180 @@
@using Radzen
@using Radzen.Blazor
@using BattSim.Models
@using BattSim.Services
<div class="pipeline-box @GetPipelineStepClasses(StepData)">
<div class="box-header" @onclick="ToggleBox">
<div class="step-number">3</div>
<div class="step-title">Kosten Berekenen</div>
<div class="status-indicator"></div>
</div>
<div class="box-content">
<div class="box-content-inner">
<p class="box-description">Vergelijk kosten met en zonder batterij</p>
<!-- Contractuele Kosten -->
<div style="margin-bottom: 1.5rem;">
<h4 style="color: var(--green-clr); margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-clr); padding-bottom: 0.5rem;">
Contractuele Kosten
</h4>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Vaste vergoeding (€/jaar):</label>
<InputNumber @bind-value="Calculator.BasePayment" T="double" min="0" step="1" />
</div>
<div class="input-group" style="flex: 1;">
<label>Databeheer tarief (€/jaar):</label>
<InputNumber @bind-value="Calculator.DataManagementCost" T="double" min="0" step="0.01" />
</div>
</div>
<!-- Vaste Kosten Subbox (Collapsed by Default) -->
<details class="collapsible-section" style="margin-top: 1rem;">
<summary class="collapsible-header">Vaste Kosten</summary>
<div class="collapsible-content">
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Vlaamse energieheffing (€/jaar):</label>
<InputNumber @bind-value="Calculator.FlemishEnergyTariff" T="double" min="0" step="0.01" />
</div>
</div>
<!-- Heffingen Groene Stroom -->
<div style="margin-top: 1rem;">
<h5 style="color: var(--green-clr); margin-bottom: 0.5rem;">Heffingen Groene Stroom</h5>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Groene stroomcertificaten (c€/kWh):</label>
<InputNumber @bind-value="Calculator.GreenCertificateCost" T="double" min="0" step="0.0001" />
</div>
<div class="input-group" style="flex: 1;">
<label>Warmtekracht certificaten (c€/kWh):</label>
<InputNumber @bind-value="Calculator.HeatingCertificateCost" T="double" min="0" step="0.0001" />
</div>
</div>
</div>
<!-- Net- en distributiekosten -->
<div style="margin-top: 1rem;">
<h5 style="color: var(--green-clr); margin-bottom: 0.5rem;">Net- en distributiekosten</h5>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Capaciteitstarief (€/kWh/jaar):</label>
<InputNumber @bind-value="Calculator.CapacityCost" T="double" min="0" step="0.01" />
</div>
<div class="input-group" style="flex: 1;">
<label>Afnametarief (c€/kWh):</label>
<InputNumber @bind-value="Calculator.UsageTariff" T="double" min="0" step="0.0001" />
</div>
</div>
</div>
<!-- Heffingen en Toeslagen -->
<div style="margin-top: 1rem;">
<h5 style="color: var(--green-clr); margin-bottom: 0.5rem;">Heffingen en Toeslagen</h5>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Energiebijdrage (c€/kWh):</label>
<InputNumber @bind-value="Calculator.EnergyContribution" T="double" min="0" step="0.0001" />
</div>
<div class="input-group" style="flex: 1;">
<label>Bijzondere accijns (c€/kWh):</label>
<InputNumber @bind-value="Calculator.SpecialTariffs" T="double" min="0" step="0.0001" />
</div>
</div>
</div>
</div>
</details>
</div>
<!-- Energiekosten -->
<div style="margin-bottom: 1.5rem;">
<h4 style="color: var(--green-clr); margin-bottom: 0.75rem; border-bottom: 1px solid var(--border-clr); padding-bottom: 0.5rem;">
Energiekosten
</h4>
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<div class="input-group" style="flex: 1;">
<label>Energiekost (c€/kWh):</label>
<InputNumber @bind-value="Calculator.EnergyCost" T="double" min="0" step="0.0001" />
</div>
<div class="input-group" style="flex: 1;">
<label>Terugleveringsvergoeding (c€/kWh):</label>
<InputNumber @bind-value="Calculator.ReturnCost" T="double" min="0" step="0.0001" />
</div>
</div>
</div>
<div style="display: flex; justify-content: center; margin-top: 1.5rem;">
<button @onclick="Calculate" @onclick:stopPropagation disabled="@(StepData.isProcessing || SimulationData.Length == 0)">
@if (StepData.isProcessing)
{
<text>Berekenen...</text>
}
else
{
<text>Bereken Kosten</text>
}
</button>
</div>
@if (StepData.Completed)
{
<div class="results">
<p><strong>Originele kosten:</strong> <span class="result-value">€@NormalCost.ToString("0.00")</span></p>
<p><strong>Gesimuleerde kosten:</strong> <span class="result-value">€@SimulatedCost.ToString("0.00")</span></p>
<p><strong>Besparing:</strong> <span class="result-value">€@((NormalCost - SimulatedCost).ToString("0.00"))</span></p>
@if (NormalCost > 0)
{
<p><strong>Besparingspercent:</strong> <span class="result-value">@(((NormalCost - SimulatedCost) / NormalCost * 100).ToString("0.00"))%</span></p>
}
</div>
}
</div>
</div>
</div>
@code {
[Parameter]
public EnergyData[] SimulationData { get; set; } = [];
[Parameter]
public EnergyData[] FluviusDataRaw { get; set; } = [];
[Parameter]
public PipelineStep StepData { get; set; }
[Parameter]
public EnergyCostCalculator Calculator { get; set; } = new();
[Parameter]
public double NormalCost { get; set; } = 0.0;
[Parameter]
public double SimulatedCost { get; set; } = 0.0;
[Parameter]
public EventCallback OnCalculateCallback { get; set; }
[Parameter]
public EventCallback OnToggleBoxCallback { get; set; }
private string GetPipelineStepClasses(PipelineStep step)
{
var classes = new List<string>();
if (step.Expanded) classes.Add("expanded");
if (step.Completed) classes.Add("completed");
if (step.isProcessing) classes.Add("running");
return string.Join(" ", classes);
}
private void Calculate()
{
OnCalculateCallback.InvokeAsync();
}
private void ToggleBox()
{
OnToggleBoxCallback.InvokeAsync();
}
}

View File

@@ -0,0 +1,103 @@
@using Radzen
@using Radzen.Blazor
@using BattSim.Models
@using BattSim.Services
<div class="pipeline-box @GetPipelineStepClasses(StepData)">
<div class="box-header" @onclick="ToggleBox">
<div class="step-number">1</div>
<div class="step-title">Fluvius Data</div>
<div class="status-indicator"></div>
</div>
<div class="box-content">
<div class="box-content-inner">
<p class="box-description">Upload je Fluvius kwartierdata om te beginnen</p>
<div class="form-section">
<div class="file-input-wrapper">
<InputFile type="file" id="fileUpload" accept=".csv" onchange="async (e) => await OnFileUploaded(e)" />
<label for="fileUpload" class="file-input-label">
@if (StepData.isProcessing)
{
<div class="loading-indicator">
<div class="spinner"></div>
<span style="margin-left: 10px;">Data aan het inladen...</span>
</div>
}
else
{
@if (FluviusDataRaw.Length == 0)
{
<text>Kies CSV bestand</text>
}
else
{
<text>OK @FluviusDataRaw.Length kwartieren geladen</text>
}
}
</label>
@if (FluviusDataRaw.Length > 0 && !StepData.isProcessing)
{
<p class="file-name">@UploadedFileName</p>
}
</div>
</div>
@if (StepData.Completed)
{
<div class="success-message">
Data succesvol ingeladen en verwerkt!
</div>
}
@if (UploadError != null)
{
<div class="error-message">
@UploadError
</div>
}
<div class="info-text">
<p><strong>Tip:</strong> Download een volledig jaar aan kwartierdata voor de meest accurate resultaten.</p>
</div>
</div>
</div>
</div>
@code {
[Parameter]
public EnergyData[] FluviusDataRaw { get; set; } = [];
[Parameter]
public PipelineStep StepData { get; set; }
[Parameter]
public string UploadedFileName { get; set; } = string.Empty;
[Parameter]
public string? UploadError { get; set; }
[Parameter]
public EventCallback<InputFileChangeEventArgs> OnFileUploadedCallback { get; set; }
[Parameter]
public EventCallback OnToggleBoxCallback { get; set; }
private string GetPipelineStepClasses(PipelineStep step)
{
var classes = new List<string>();
if (step.Expanded) classes.Add("expanded");
if (step.Completed) classes.Add("completed");
if (step.isProcessing) classes.Add("running");
return string.Join(" ", classes);
}
private async Task OnFileUploaded(InputFileChangeEventArgs e)
{
await OnFileUploadedCallback.InvokeAsync(e);
}
private void ToggleBox()
{
OnToggleBoxCallback.InvokeAsync();
}
}