Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/static/scss/layouts/_moo.scss
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions app/static/scss/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
51 changes: 51 additions & 0 deletions app/templates/wefe/steps/moo_setup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{% extends 'wefe/steps/step_progression.html' %}
{% load crispy_forms_tags %}
{% load i18n %}

{% block progression_content %}
<div class="padding-1">
<div class="center-text">
<input id="inp-moo" type="checkbox" class="inp-default" onchange="update_weights()" {% if custom_weights or form.errors %}checked{% endif %}>
<label for="inp-moo">{% translate 'Use Multi-Objective Optimization' %}</label>
</div>
<form id="form" method="post">
{% csrf_token %}
<div>
{{ form | crispy }}
</div>
</form>
</div>
{% endblock progression_content %}

{% block next_btn %}
<button class="btn btn--medium" onclick="document.getElementById('form').submit()">{% translate "Next" %}</button>
{% endblock next_btn %}

{% block end_body_scripts %}
<script>
// init weights
var weights = {};
for (let inp of $(':input[type="number"]'))
weights[inp.id] = inp.value;

function update_weights() {
let form = document.getElementById('form');
let check = document.getElementById('inp-moo');
let inputs = $(':input[type="number"]');
form.style.display = check.checked ? '' : 'none';
for (let inp of inputs) {
if (check.checked) {
// multi-objective: restore weight
inp.value = weights[inp.id];
} else {
// single objective: reset weight
weights[inp.id] = inp.value;
inp.value = inp.getAttribute('default');
}
}
}
// initialize state
update_weights();

</script>
{% endblock end_body_scripts %}
37 changes: 36 additions & 1 deletion app/wefe/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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")
26 changes: 26 additions & 0 deletions app/wefe/migrations/0004_mooweights.py
Original file line number Diff line number Diff line change
@@ -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")),
],
),
]
9 changes: 9 additions & 0 deletions app/wefe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
45 changes: 30 additions & 15 deletions app/wefe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,8 +35,6 @@
from wefe.survey import SURVEY_CATEGORIES, SURVEY_QUESTIONS_CATEGORIES, get_survey_question_by_id

import logging


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -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()
Comment on lines +597 to +604
Copy link
Collaborator

@paulapreuss paulapreuss Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this works as it is, but I don't know about using a except: for a condition that is very normal within the code like no MOO set yet (not an unexpected exception). Instead of waiting for an Exception to be raised I think it would be cleaner/less computationally expensive to formulate this as:

weights_qs = MOOWeights.Objects.filter(scenario=scenario)
if weights_qs.exists():
     weights = scenario.mooweights
     # ...
else:
    # no weights yet...

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]))


Expand All @@ -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")
Expand Down