Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f9bad16
adjusted parsing tests to new timeseries set up
josihoppe Mar 26, 2026
80acf2f
added try/except to timeseries field to catch error for not supported…
josihoppe Apr 8, 2026
9c12c3c
updated Asset Models way of getting the values from the timeseries Ob…
josihoppe Apr 8, 2026
3c89f21
readded positions for the tests for smoother running
josihoppe Apr 8, 2026
d7b26bf
fixed parser with chatgpt, tests should not fail anymore
josihoppe Apr 8, 2026
ecd5c9b
added new tests for more cases
josihoppe Apr 14, 2026
63b5211
fixed parser for case of point for decimal and comma as delimiter
josihoppe Apr 14, 2026
345c76b
changed parser so that single column comma-decimal files work and als…
josihoppe Apr 20, 2026
0fa4c6c
added test for file with value first and timeseries second format
josihoppe Apr 20, 2026
c6462e7
added test for checking that headers are not accepted
josihoppe Apr 21, 2026
ae1124d
renamed all tests and files to match them better to each other
josihoppe Apr 21, 2026
bc48ced
Display form validation errors for timeseries input
paulapreuss Apr 22, 2026
3a519cd
added timeseries length check to assetcreate form clean method
josihoppe Apr 22, 2026
e3b0c42
changed upoad test setup to accomodate for length check
josihoppe Apr 22, 2026
4d6f7ef
added test for timeseries length mismatch
josihoppe Apr 22, 2026
1eddffa
parser throws error when file has header, header test checks if it ge…
josihoppe Apr 22, 2026
c6641f6
fixed header check, so that decimal values dont throw errors as well
josihoppe Apr 22, 2026
7cb8036
added check for 2 or less columns in parser
josihoppe Apr 22, 2026
6980253
added checks for different error messages to tests
josihoppe Apr 22, 2026
cb4e0c5
added error for timeseries in wrong place to parser
josihoppe Apr 22, 2026
3a88f2d
fixed displaying of uploaded csv in demand
josihoppe Apr 28, 2026
5ba4d95
remove console log
josihoppe Apr 28, 2026
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
12 changes: 9 additions & 3 deletions app/projects/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,9 +961,15 @@ def clean(self):
input_method = ts_data["input_method"]["type"]
if input_method == TS_UPLOAD_TYPE or input_method == TS_MANUAL_TYPE:
# replace the dict with a new timeseries instance
cleaned_data["input_timeseries"] = self.assign_timeseries_from_input(
ts_data
)
timeseries_obj = self.assign_timeseries_from_input(ts_data)
num_timestamps = self.scenario.get_num_timesteps
if len(timeseries_obj.values) != num_timestamps:
msg = _(
f"The length of the timeseries ({len(timeseries_obj.values)}) does not match the lentgh of the selected timesteps ({num_timestamps}). You can check your timeseries file or change the timesteps in step 1."
)
self.add_error("input_timeseries", msg)
else:
cleaned_data["input_timeseries"] = timeseries_obj
if input_method == TS_SELECT_TYPE:
# return the timeseries instance
timeseries_id = ts_data["input_method"]["extra_info"]
Expand Down
75 changes: 66 additions & 9 deletions app/projects/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,15 @@ def clean(self, values):
timeseries_id = ""

if timeseries_file is not None:
input_timeseries_values = parse_input_timeseries(timeseries_file)
try:
input_timeseries_values = parse_input_timeseries(timeseries_file)
except (ValueError, TypeError) as e:
self.set_widget_error()
raise ValidationError(str(e))
except Exception as e:
self.set_widget_error()
raise ValidationError(f"Could not parse uploaded file: {e}")

answer = input_timeseries_values
input_dict = dict(type=TS_UPLOAD_TYPE, extra_info=timeseries_file.name)
elif timeseries_id != "":
Expand Down Expand Up @@ -534,27 +542,76 @@ def set_widget_error(self):
def parse_csv_timeseries(file_str):
io_string = io.StringIO(file_str)
delimiter = ","
is_comma_decimal = False
msg = "The uploaded file has an invalid format. Please provide a CSV with 1 or 2 columns containing only numeric values (no header). If two columns are used, the first must be the index (e.g., timestamps) and the second the corresponding values."

lines = file_str.splitlines()
# check if there are timestamps
has_timestamp = any(":" in line or "-" in line for line in lines)

# --- delimiter detection ---
if file_str.count(";") > 0:
delimiter = ";"

# check if the number of , is an integer time the number of line return
# if not, the , is probably not a column separator and a decimal separator indeed
if file_str.count(",") % (file_str.count("\n") + 1) != 0:
delimiter = ";"
# --- comma ambiguity ---
# If commas exist, we need to distinguish:
# (A) CSV with comma delimiter
# (B) decimal comma (single column numeric data)

if delimiter == ",":
comma_per_line = [line.count(",") for line in lines if line.strip()]

if comma_per_line and all(c == 1 for c in comma_per_line):
if file_str.count(".") > 0:
# if all lines contain a "," AND there is a "." in the file, strong indicator for "," delimiter
delimiter = ","
elif not has_timestamp:
raise ValidationError(msg)
else:
# safe to assume decimal comma in single-column case
if comma_per_line and all(c <= 1 for c in comma_per_line):
is_comma_decimal = True
delimiter = ";"

# check for number of columns, throw error if more then 2
if any(len(line.split(delimiter)) > 2 for line in lines if line.strip()):
raise ValidationError(msg)

# --- parsing ---
reader = csv.reader(io_string, delimiter=delimiter)
timeseries_values = []

for row in reader:
if not row:
continue

if len(row) == 1:
value = row[0]
else:
# assumes the first row is timestamps and read the second one, ignore any other row
value = row[1]
# convert potential comma used as decimal point to decimal point
timeseries_values.append(float(value.replace(",", ".")))
# since we have more than 1 col, check if timeseries is only in the first, if not raise error
if is_timestamp(row[-1]):
raise ValidationError(msg)
value = row[-1]
value = value.strip()

# --- decimal normalization ---
if is_comma_decimal:
value = value.replace(",", ".")
else:
if "," in value and "." not in value:
value = value.replace(",", ".")
if value.isalpha():
# catch if there is a header, then the file cannot be parsed
raise ValidationError(msg)
timeseries_values.append(float(value))
return timeseries_values


def is_timestamp(values):
# checks if there is a timestamp in the given value/values
return ":" in values or "-" in values


def parse_xlsx_timeseries(file_buffer):
wb = load_workbook(filename=file_buffer)
worksheet = wb.active
Expand Down
4 changes: 2 additions & 2 deletions app/projects/models/base_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ def timestamps(self):
@property
def input_timeseries_values(self):
if self.is_input_timeseries_empty() is False:
answer = json.loads(self.input_timeseries)
answer = self.input_timeseries.get_values
else:
answer = []
return answer
Expand Down Expand Up @@ -928,7 +928,7 @@ def export(self, connections=False):
return dm

def is_input_timeseries_empty(self):
return self.input_timeseries == ""
return self.input_timeseries is None


class COPCalculator(models.Model):
Expand Down
Loading
Loading