diff --git a/app/static/scss/layouts/_moo.scss b/app/static/scss/layouts/_moo.scss
new file mode 100644
index 00000000..c80afca0
--- /dev/null
+++ b/app/static/scss/layouts/_moo.scss
@@ -0,0 +1,22 @@
+.padding-1 {
+ padding: 1rem;
+ width: 35%;
+ align-self: center;
+}
+
+.center-text {
+ text-align: center;
+}
+
+.inp-default {
+ width: initial;
+}
+
+.no-bullet ul {
+ list-style-type: none;
+}
+
+.error {
+ color: red;
+ font-weight: bold;
+}
diff --git a/app/static/scss/main.scss b/app/static/scss/main.scss
index 681eba20..84b8d82e 100644
--- a/app/static/scss/main.scss
+++ b/app/static/scss/main.scss
@@ -21,6 +21,7 @@
@import 'layouts/general-layout';
@import 'layouts/gui';
@import 'layouts/homepage';
+@import 'layouts/moo';
@import 'layouts/project';
@import 'layouts/scenario-create';
@import 'layouts/signup';
diff --git a/app/templates/wefe/steps/moo_setup.html b/app/templates/wefe/steps/moo_setup.html
new file mode 100644
index 00000000..ca7c903c
--- /dev/null
+++ b/app/templates/wefe/steps/moo_setup.html
@@ -0,0 +1,51 @@
+{% extends 'wefe/steps/step_progression.html' %}
+{% load crispy_forms_tags %}
+{% load i18n %}
+
+{% block progression_content %}
+
+
+
+
+
+
+
+{% endblock progression_content %}
+
+{% block next_btn %}
+
+{% endblock next_btn %}
+
+{% block end_body_scripts %}
+
+{% endblock end_body_scripts %}
diff --git a/app/wefe/forms.py b/app/wefe/forms.py
index 2220401f..acddf3ca 100644
--- a/app/wefe/forms.py
+++ b/app/wefe/forms.py
@@ -9,7 +9,7 @@
from projects.forms import OpenPlanForm, OpenPlanModelForm
from projects.models import Project, EconomicData, Scenario
from projects.requests import request_exchange_rate
-from wefe.models import SurveyQuestion
+from wefe.models import MOOWeights, SurveyQuestion
from wefe.survey import SURVEY_STRUCTURE, SURVEY_CATEGORIES, TYPE_STRING
@@ -367,3 +367,38 @@ def clean(self):
else:
raise ValidationError("This form cannot be blank")
return cleaned_data
+
+
+class MOOForm(forms.ModelForm):
+ # multi-objective optimization setup
+ class Meta:
+ model = MOOWeights
+ exclude = ["scenario"]
+
+ total_cost = forms.FloatField(
+ min_value=0, max_value=1, initial=1,
+ widget=forms.NumberInput(attrs={'step': 0.1, 'default': 1})
+ )
+ co2_emissions = forms.FloatField(
+ min_value=0, max_value=1, initial=0,
+ widget=forms.NumberInput(attrs={'step': 0.1, 'default': 0})
+ )
+ land_requirements = forms.FloatField(
+ min_value=0, max_value=1, initial=0,
+ widget=forms.NumberInput(attrs={'step': 0.1, 'default': 0})
+ )
+ water_footprint = forms.FloatField(
+ min_value=0, max_value=1, initial=0,
+ widget=forms.NumberInput(attrs={'step': 0.1, 'default': 0})
+ )
+
+ def clean(self):
+ # check that weights add up to 1
+ cleaned_data = super().clean()
+ cost = cleaned_data.get("total_cost")
+ co2 = cleaned_data.get("co2_emissions")
+ land = cleaned_data.get("land_requirements")
+ water = cleaned_data.get("water_footprint")
+ if cost is not None and co2 is not None and land is not None and water is not None:
+ if round(cost + co2 + land + water, 4) != 1:
+ raise ValidationError("Weights must add up to 1")
diff --git a/app/wefe/migrations/0004_mooweights.py b/app/wefe/migrations/0004_mooweights.py
new file mode 100644
index 00000000..81034063
--- /dev/null
+++ b/app/wefe/migrations/0004_mooweights.py
@@ -0,0 +1,26 @@
+# Generated by Django 5.1.3 on 2025-11-04 14:49
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("projects", "0027_alter_economicdata_currency"),
+ ("wefe", "0003_wefesimulation"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="MOOWeights",
+ fields=[
+ ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ ("total_cost", models.FloatField(default=1)),
+ ("co2_emissions", models.FloatField(default=0)),
+ ("land_requirements", models.FloatField(default=0)),
+ ("water_footprint", models.FloatField(default=0)),
+ ("scenario", models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to="projects.scenario")),
+ ],
+ ),
+ ]
diff --git a/app/wefe/models.py b/app/wefe/models.py
index 1a670d40..fe552358 100644
--- a/app/wefe/models.py
+++ b/app/wefe/models.py
@@ -80,3 +80,12 @@ def copy_energy_system_from_usecase(usecase_name, scenario):
# assign the assets and busses to the given scenario
assign_assets(scenario, assets)
assign_busses(scenario, busses)
+
+
+class MOOWeights(models.Model):
+ # multi-objective optimization weights
+ scenario = models.OneToOneField(Scenario, on_delete=models.CASCADE)
+ total_cost = models.FloatField(default=1)
+ co2_emissions = models.FloatField(default=0)
+ land_requirements = models.FloatField(default=0)
+ water_footprint = models.FloatField(default=0)
diff --git a/app/wefe/views.py b/app/wefe/views.py
index c00dc450..5d7d4cad 100644
--- a/app/wefe/views.py
+++ b/app/wefe/views.py
@@ -5,27 +5,26 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
+from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
+from django.db.models import Q, F, Avg, Max
from django.http import JsonResponse
-from django.utils.translation import gettext_lazy as _
from django.shortcuts import *
from django.urls import reverse
-from django.core.exceptions import PermissionDenied
+from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_http_methods
-from django.db.models import Q, F, Avg, Max
from jsonview.decorators import json_view
-from epa.settings import WEFESIM_GET_URL
-from projects.constants import DONE, ERROR
-from .forms import *
-from .helpers import *
from business_model.forms import *
+from business_model.models import *
+from projects.constants import DONE, ERROR
+from projects.forms import UploadFileForm, ProjectShareForm, ProjectRevokeForm, UseCaseForm
from projects.models import *
from projects.models.base_models import Timeseries
from projects.views import project_duplicate, project_delete
-from business_model.models import *
-from projects.forms import UploadFileForm, ProjectShareForm, ProjectRevokeForm, UseCaseForm
-from wefe.models import SurveyAnswer, WEFESimulation
+from wefe.forms import *
+from wefe.helpers import *
+from wefe.models import MOOWeights, SurveyAnswer, WEFESimulation
from wefe.requests import (
fetch_wefedemand_simulation_results,
wefedemand_simulation_request,
@@ -36,8 +35,6 @@
from wefe.survey import SURVEY_CATEGORIES, SURVEY_QUESTIONS_CATEGORIES, get_survey_question_by_id
import logging
-
-
logger = logging.getLogger(__name__)
@@ -597,10 +594,28 @@ def wefe_optimization_weighting(request, proj_id, step_id=STEP_MAPPING["optimiza
}
if request.method == "GET":
- return render(request, "wefe/steps/step_progression.html", context)
+ try:
+ # weights already defined: get values
+ weights = scenario.mooweights
+ context["form"] = MOOForm(initial=vars(weights))
+ context["custom_weights"] = weights.total_cost != 1
+ except ObjectDoesNotExist:
+ # no weights yet: use defaults
+ context["form"] = MOOForm()
+ return render(request, "wefe/steps/moo_setup.html", context)
if request.method == "POST":
- # TODO
+ form = MOOForm(request.POST)
+ if not form.is_valid():
+ context["form"] = form
+ return render(request, "wefe/steps/moo_setup.html", context)
+
+ # save form data (1 to 1 relation between scenario and weights)
+ # weights might already exist -> maybe just update -> saving ModelForm does not work
+ MOOWeights.objects.update_or_create(
+ scenario=scenario,
+ defaults=form.cleaned_data,
+ )
return HttpResponseRedirect(reverse("wefe_steps", args=[proj_id, step_id + 1]))
@@ -626,7 +641,7 @@ def wefe_simulation(request, proj_id, step_id=STEP_MAPPING["simulation"]):
"step_id": step_id,
"step_list": WEFE_STEP_VERBOSE,
"page_information": page_information,
- "WEFESIM_GET_URL": WEFESIM_GET_URL,
+ "WEFESIM_GET_URL": settings.WEFESIM_GET_URL,
}
qs = WEFESimulation.objects.filter(scenario=project.scenario, app="wefesim")