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
87 changes: 85 additions & 2 deletions app/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -575,8 +575,7 @@ msgid ""
"Please provide either a number within %(boundaries) s or upload a timeseries "
"from a file"
msgstr ""
"Please provide either a number within %(boundaries) s or upload a timeseries "
"from a file. aus einer Datei"
"Bitte geben Sie entweder eine Zahl innerhalb von %(boundaries) s ein oder laden Sie eine Zeitreihe aus einer Datei hoch."

#: projects/helpers.py:287 projects/helpers.py:474
#, python-format
Expand Down Expand Up @@ -5075,3 +5074,87 @@ msgstr "Aufgrund der laufenden Umstrukturierung funktioniert die Verwendung von

msgid "Further information"
msgstr "Weitere Informationen"

msgid "Outdoor Temperature"
msgstr "Außentemperatur"

msgid "Profile Type"
msgstr "Bedarfstyp"

msgid "Select from one of the available BDEW heat profiles"
msgstr "Wählen Sie eines der verfügbaren BDEW-Wärmeprofile aus"

msgid "Annual Heat Demand"
msgstr "Jährlicher Wärmebedarf"

msgid "Total heat demand in the chosen timeperiod"
msgstr "Gesamtwärmebedarf im ausgewählten Zeitraum"

msgid "Building Year"
msgstr "Baujahr"

msgid "Only for residential buildings, used for estimating insulation"
msgstr "Nur für Wohngebäude, zur Berechnung der Wärmedämmung"

msgid "Wind class"
msgstr "Windklasse"

msgid "Windy"
msgstr "Windig"

msgid "Not Windy"
msgstr "Nicht windig"

msgid "Windy for exposed buildings on free fields, near coast or high ground. Not windy for unexposed buildings in villages/cities"
msgstr "Windig bei exponierten Gebäuden auf freiem Feld, in Küstennähe oder in höher gelegenen Lagen. Nicht windig bei nicht exponierten Gebäuden in Dörfern/Städten"

msgid "Single-family house"
msgstr "Einfamilienhaus"

msgid "Apartment building"
msgstr "Mehrfamilienhaus"

msgid "Commerce/Services general"
msgstr "Handel/Dienstleistungen allgemein"

msgid "Household-like business enterprises"
msgstr "Haushaltsähnliche Unternehmen"

msgid "Restaurants"
msgstr "Gastronomie"

msgid "Retail and wholesale"
msgstr "Einzel- und Großhandel"

msgid "Metal and automotive"
msgstr "Metall und Automobil"

msgid "Accommodation"
msgstr "Beherbergung"

msgid "Local authorities, credit institutions and insurance companies"
msgstr "Kommunen, Kreditinstitute und Versicherungsgesellschaften"

msgid "Other operational services"
msgstr "Sonstige operative Dienstleistungen"

msgid "Laundries, dry cleaning"
msgstr "Wäschereien, chemische Reinigung"

msgid "Horticulture"
msgstr "Gartenbau"

msgid "Bakery"
msgstr "Bäckerei"

msgid "Paper and printing"
msgstr "Papier und Druck"

msgid "Create timeseries from parameters"
msgstr "Zeitreihe aus Eingabeparametern erstellen"

msgid "Compute Timeseries"
msgstr "Zeitreihe berechnen"

msgid "Constant Temperature or Timeseries"
msgstr "Konstante Temperatur oder Temperaturzeitreihe"
125 changes: 54 additions & 71 deletions app/projects/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for fieldname, field in self.fields.items():
set_parameter_info(fieldname, field)
add_help_text_icon(field, fieldname, RTD_link=False)


class FeedbackForm(ModelForm):
Expand Down Expand Up @@ -203,114 +204,92 @@ class Meta:
class ProjectCreateForm(OpenPlanForm):
name = forms.CharField(
label=_("Project Name"),
help_text=_("A self explanatory name for the project."),
widget=forms.TextInput(
attrs={
"placeholder": _("Name..."),
"data-bs-toggle": "tooltip",
"title": _("A self explanatory name for the project."),
}
),
)
description = forms.CharField(
label=_("Project Description"),
help_text=_("A description of what this project objectives or test cases."),
widget=forms.Textarea(
attrs={
"placeholder": _("More detailed description here..."),
"data-bs-toggle": "tooltip",
"title": _(
"A description of what this project objectives or test cases."
),
}
),
)
country = forms.ChoiceField(
label=_("Country"),
help_text=_("Name of the country where the project is being deployed"),
choices=COUNTRY,
widget=forms.Select(
attrs={
"data-bs-toggle": "tooltip",
"title": _("Name of the country where the project is being deployed"),
}
),
widget=forms.Select(),
)
longitude = forms.FloatField(
label=_("Location, longitude"),
help_text=_("Longitude coordinate of the project's geographical location."),
widget=forms.NumberInput(
attrs={
"placeholder": _("click on the map"),
"readonly": "",
"data-bs-toggle": "tooltip",
"title": _(
"Longitude coordinate of the project's geographical location."
),
}
),
)
latitude = forms.FloatField(
label=_("Location, latitude"),
help_text=_("Latitude coordinate of the project's geographical location."),
widget=forms.NumberInput(
attrs={
"placeholder": _("click on the map"),
"readonly": "",
"data-bs-toggle": "tooltip",
"title": _(
"Latitude coordinate of the project's geographical location."
),
}
),
)
duration = forms.IntegerField(
label=_("Project Duration"),
help_text=_(
"The number of years the project is intended to be operational. The project duration also sets the installation time of the assets used in the simulation. After the project ends these assets are 'sold' and the refund is charged against the initial investment costs."
),
widget=forms.NumberInput(
attrs={
"placeholder": _("eg. 1"),
"min": "0",
"max": "100",
"step": "1",
"data-bs-toggle": "tooltip",
"title": _(
"The number of years the project is intended to be operational. The project duration also sets the installation time of the assets used in the simulation. After the project ends these assets are 'sold' and the refund is charged against the initial investment costs."
),
}
),
)
currency = forms.ChoiceField(
label=_("Currency"),
choices=CURRENCY,
widget=forms.Select(
attrs={
"data-bs-toggle": "tooltip",
"title": _(
"The currency of the country where the project is implemented."
),
}
),
help_text=_("The currency of the country where the project is implemented."),
widget=forms.Select(),
)
discount = forms.FloatField(
label=_("Discount Factor"),
help_text=_(
"Discount factor is the factor which accounts for the depreciation in the value of money in the future, compared to the current value of the same money. The common method is to calculate the weighted average cost of capital (WACC) and use it as the discount rate."
),
widget=forms.NumberInput(
attrs={
"placeholder": _("eg. 0.1"),
"min": "0.0",
"max": "1.0",
"step": "0.0001",
"data-bs-toggle": "tooltip",
"title": _(
"Discount factor is the factor which accounts for the depreciation in the value of money in the future, compared to the current value of the same money. The common method is to calculate the weighted average cost of capital (WACC) and use it as the discount rate."
),
}
),
)
tax = forms.FloatField(
label=_("Tax"),
help_text=_("Tax factor"),
widget=forms.HiddenInput(
attrs={
"placeholder": _("eg. 0.3"),
"min": "0.0",
"max": "1.0",
"step": "0.0001",
"data-bs-toggle": "tooltip",
"title": _("Tax factor"),
"value": 0,
}
),
Expand Down Expand Up @@ -1322,62 +1301,54 @@ def __init__(self, *args, **kwargs):

class CreateHeatDemandForm(OpenPlanForm):
profile_type_choices = (
("EFH", "Single-family house"),
("MFH", "Apartment building"),
("GHD", "Commerce/Services general"),
("GMF", "Household-like business enterprises"),
("GGA", "Restaurants"),
("GBH", "Retail and wholesale"),
("GMK", "Metal and automotive"),
("GBH", "Accommodation"),
("GKO", "Local authorities, credit institutions and insurance companies"),
("GBD", "Other operational services"),
("GWA", "Laundries, dry cleaning"),
("GGB", "Horticulture"),
("GBA", "Bakery"),
("GPD", "Paper and printing"),
("EFH", _("Single-family house")),
("MFH", _("Apartment building")),
("GHD", _("Commerce/services general")),
("GMF", _("Household-like business enterprises")),
("GGA", _("Restaurants")),
("GBH", _("Retail and wholesale")),
("GMK", _("Metal and automotive")),
("GBH", _("Accommodation")),
("GKO", _("Local authorities, credit institutions and insurance companies")),
("GBD", _("Other operational services")),
("GWA", _("Laundries, dry cleaning")),
("GGB", _("Horticulture")),
("GBA", _("Bakery")),
("GPD", _("Paper and printing")),
)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

outdoor_temperature = DualNumberField(
label=_("Outdoor Temperature"),
help_text=_("Constant Temperature or Timeseries"),
param_name="outdoor_temperature",
)

profile_type = forms.ChoiceField(
choices=profile_type_choices,
label=_("Profile Type"),
widget=forms.Select(
attrs={
"data-bs-toggle": "tooltip",
"title": _("Select from one of the available BDEW heat profiles"),
}
),
help_text=_("Select from one of the available BDEW heat profiles"),
widget=forms.Select(),
)

annual_heat_demand = forms.FloatField(
label=_("Annual Heat Demand"),
help_text=_("Total heat demand in the chosen timeperiod"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 1000"),
"data-bs-toggle": "tooltip",
"title": _("Total heat demand in the chosen timeperiod"),
}
),
)

# TODO: here also check the validation of when the field is required
building_year = forms.FloatField(
label=_("Building Year"),
help_text=_("Only for residential buildings, used for estimating insulation"),
widget=forms.NumberInput(
attrs={
"placeholder": _("e.g. 1970"),
"data-bs-toggle": "tooltip",
"title": _(
"Only for residential buildings, used for estimating insulation"
),
}
),
required=False,
Expand All @@ -1386,16 +1357,28 @@ def __init__(self, *args, **kwargs):
wind_class = forms.ChoiceField(
label=_("Wind class"),
choices=(("Windy", _("Windy")), ("Not windy", _("Not Windy"))),
widget=forms.Select(
attrs={
"data-bs-toggle": "tooltip",
"title": _(
"Windy for exposed buildings on free fields, near coast or high ground. Not windy for unexposed buildings in villages/cities"
),
}
help_text=_(
"Windy for exposed buildings on free fields, near coast or high ground. Not windy for unexposed buildings in villages/cities"
),
widget=forms.Select(),
)

def clean_building_year(self):
building_year = self.cleaned_data["building_year"]
profile_type = self.cleaned_data["profile_type"]
if (
profile_type
in [
"EFH",
"MFH",
]
and not building_year
):
raise ValidationError(
_("Building year is required for residential buildings")
)
return building_year


CUSTOM_TIMESERIES_FORMS = {
# TODO: re-enable PV timeseries creation when weather data handling is settled
Expand Down
19 changes: 18 additions & 1 deletion app/projects/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@ def clean(self, values):
answer = json.loads(scalar_value)
if not isinstance(answer, list):
scalar_value = ""
elif not all(isinstance(v, (int, float)) for v in answer):
self.set_widget_error()
raise ValidationError(
_(
"The uploaded timeseries contains a "
"non-numeric value, likely from a header "
"row. Please remove it and try again."
),
)
except json.decoder.JSONDecodeError:
scalar_value = ""

Expand Down Expand Up @@ -610,7 +619,7 @@ def parse_csv_timeseries(file_str):
# --- decimal normalization ---
if is_comma_decimal or ("," in value and "." not in value):
value = value.replace(",", ".")
if value.isalpha():
if not is_number(value):
# catch if there is a header, then the file cannot be parsed
raise ValidationError(msg)
timeseries_values.append(float(value))
Expand All @@ -622,6 +631,14 @@ def is_timestamp(values):
return ":" in values or "-" in values


def is_number(value):
try:
float(value)
except ValueError:
return False
return True


def parse_xlsx_timeseries(file_buffer):
wb = load_workbook(filename=file_buffer)
worksheet = wb.active
Expand Down
Loading
Loading