Skip to content

Commit df812fc

Browse files
Add measurement data handling, extend test, and fixes.
1 parent 35d8516 commit df812fc

9 files changed

Lines changed: 681 additions & 231 deletions

File tree

django/report/nk/cost/base.py

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ class NkCost:
3232
is_meta = False
3333
is_special = False
3434

35-
def __init__(self, report: "NkReportGenerator", cost_config: dict):
36-
self.report = report
35+
def __init__(self, report_generator: "NkReportGenerator", cost_config: dict):
36+
self.generator = report_generator
3737
self.name = cost_config.get("name")
3838
self.billing_group = cost_config.get("billing_group", self.name)
3939
self.total_values = {}
@@ -44,11 +44,11 @@ def __init__(self, report: "NkReportGenerator", cost_config: dict):
4444

4545
def add_value_type(self, kind: NkCostValueType, name: str, unit: str):
4646
self._add_value_type_to_dict(self.total_values, kind, name, unit)
47-
for ru in self.report.rental_units:
47+
for ru in self.generator.rental_units:
4848
if ru.id not in self.rental_unit_values:
4949
self.rental_unit_values[ru.id] = {}
5050
self._add_value_type_to_dict(self.rental_unit_values[ru.id], kind, name, unit)
51-
for section in self.report.sections:
51+
for section in self.generator.sections:
5252
if section.id not in self.section_values:
5353
self.section_values[section.id] = {}
5454
self._add_value_type_to_dict(self.section_values[section.id], kind, name, unit)
@@ -58,29 +58,28 @@ def _add_value_type_to_dict(
5858
):
5959
if kind in container:
6060
raise ValueError(f"Es existiert bereits ein Wert vom gleichen Typ wie {name}")
61-
container[kind] = self.value_cls(name, unit, 0, self.report.num_months * [0])
61+
container[kind] = self.value_cls(name, unit, 0, self.generator.num_months * [0])
6262

6363
def load_input_data(self):
6464
pass
65-
# raise NotImplementedError("load_input_data() must be implemented by subclasses")
6665

6766
def normalize_monthly_amounts(self):
6867
"""Set monthly values from annual value, or vice versa, depending on which is available."""
6968
self._normalize_monthly_amounts_for_dict(self.total_values)
70-
for ru in self.report.rental_units:
69+
for ru in self.generator.rental_units:
7170
self._normalize_monthly_amounts_for_dict(self.rental_unit_values[ru.id])
72-
for section in self.report.sections:
71+
for section in self.generator.sections:
7372
self._normalize_monthly_amounts_for_dict(self.section_values[section.id])
7473

7574
def _normalize_monthly_amounts_for_dict(self, container: dict, value_required=False):
7675
for _kind, value in container.items():
7776
# pprint(value)
7877
total = None
7978
if value.monthly_amounts:
80-
if len(value.monthly_amounts) != self.report.num_months:
79+
if len(value.monthly_amounts) != self.generator.num_months:
8180
raise ValueError(
8281
f"Inkonsistente Anzahl der Monatswerte {len(value.monthly_amounts)} "
83-
f"und Anzahl der Monate {self.report.num_months} für {value.name}/{_kind}"
82+
f"und Anzahl der Monate {self.generator.num_months} für {value.name}/{_kind}"
8483
)
8584
total = sum(value.monthly_amounts)
8685
if value.amount:
@@ -94,8 +93,8 @@ def _normalize_monthly_amounts_for_dict(self, container: dict, value_required=Fa
9493
else:
9594
# Set monthly values from annual value
9695
value.monthly_amounts = [
97-
value.amount / self.report.num_months
98-
] * self.report.num_months
96+
value.amount / self.generator.num_months
97+
] * self.generator.num_months
9998
elif total:
10099
# Set annual value from monthly values
101100
value.amount = total
@@ -115,13 +114,16 @@ def split_costs(self):
115114
else 0
116115
)
117116
self._calculate_amounts(self.total_values, kind, amount_per_weight)
118-
for ru in self.report.rental_units:
117+
for ru in self.generator.rental_units:
119118
self._calculate_amounts(self.rental_unit_values[ru.id], kind, amount_per_weight)
120-
for section in self.report.sections:
119+
for section in self.generator.sections:
121120
self._calculate_amounts(self.section_values[section.id], kind, amount_per_weight)
122121

122+
def update(self):
123+
pass
124+
123125
def _calculate_amounts(self, values, kind: NkCostValueType, amount_per_weight):
124-
for month in range(self.report.num_months):
126+
for month in range(self.generator.num_months):
125127
amount = amount_per_weight * values[NkCostValueType.WEIGHT].monthly_amounts[month]
126128
if (
127129
values[kind].monthly_amounts[month]
@@ -148,35 +150,51 @@ def _calculate_weights(self):
148150
monthly_weights = self.get_monthly_weights()
149151
section_weights = self.get_section_weights()
150152
total = self.total_values[NkCostValueType.WEIGHT]
151-
for ru in self.report.rental_units:
153+
for ru in self.generator.rental_units:
152154
ru_weights = self.get_rental_unit_weights(ru.id)
153155
values = self.rental_unit_values[ru.id][NkCostValueType.WEIGHT]
154156
section = self.section_values[ru.section.id][NkCostValueType.WEIGHT]
155-
for month in range(self.report.num_months):
157+
for month in range(self.generator.num_months):
156158
weight = (
157159
monthly_weights[month] * section_weights[ru.section.id] * ru_weights[month]
158160
)
159161
values.monthly_amounts[month] = weight
160162
section.monthly_amounts[month] += weight
161163
total.monthly_amounts[month] += weight
162164
values.amount = sum(values.monthly_amounts)
163-
for section in self.report.sections:
165+
for section in self.generator.sections:
164166
self.section_values[section.id][NkCostValueType.WEIGHT].amount = sum(
165167
self.section_values[section.id][NkCostValueType.WEIGHT].monthly_amounts
166168
)
167169
total.amount = sum(total.monthly_amounts)
168170

171+
def _aggregate_monthly_amounts(self, value_type: NkCostValueType = NkCostValueType.COST):
172+
"""Aggregate pre-calculated monthly per-rental-unit costs up to sections and total."""
173+
for ru in self.generator.rental_units:
174+
for month in range(self.generator.num_months):
175+
amount = self.rental_unit_values[ru.id][value_type].monthly_amounts[month]
176+
self.section_values[ru.section.id][value_type].monthly_amounts[month] += amount
177+
self.total_values[value_type].monthly_amounts[month] += amount
178+
179+
for section in self.generator.sections:
180+
self.section_values[section.id][value_type].amount = sum(
181+
self.section_values[section.id][value_type].monthly_amounts
182+
)
183+
self.total_values[value_type].amount = sum(self.total_values[value_type].monthly_amounts)
184+
169185
def get_monthly_weights(self):
170186
"""Default with equal weights for all months."""
171-
return self.report.num_months * [1.0]
187+
return self.generator.num_months * [1.0]
172188

173189
def get_section_weights(self):
174190
"""Return weights per section, using the configured section_weights profile if available."""
175191
weight_profile = (
176-
self.report.section_weights.get(self.section_weights) if self.section_weights else None
192+
self.generator.section_weights.get(self.section_weights)
193+
if self.section_weights
194+
else None
177195
)
178196
weights = {}
179-
for section in self.report.sections:
197+
for section in self.generator.sections:
180198
if weight_profile is not None:
181199
weights[section.id] = weight_profile.get(section.id.capitalize())
182200
else:
@@ -185,11 +203,11 @@ def get_section_weights(self):
185203

186204
def get_rental_unit_weights(self, ru_id):
187205
"""Default with equal weights for all rental units."""
188-
ru = self.report.get_rental_unit_by_id(ru_id)
206+
ru = self.generator.get_rental_unit_by_id(ru_id)
189207
if ru.is_virtual:
190-
return self.report.num_months * [0.0]
208+
return self.generator.num_months * [0.0]
191209
else:
192-
return self.report.num_months * [1.0]
210+
return self.generator.num_months * [1.0]
193211

194212
def get_export_cost_row(self, include_percent=False):
195213
row = self._get_export_row(NkCostValueType.COST, include_percent)
@@ -206,9 +224,9 @@ def _get_export_row(self, kind, include_percent):
206224
row.append("") # No total
207225
else:
208226
row.append(self.total_values[kind].amount)
209-
for section in self.report.sections:
227+
for section in self.generator.sections:
210228
row.append(self.section_values[section.id][kind].amount)
211-
for ru in self.report.rental_units:
229+
for ru in self.generator.rental_units:
212230
row.append(self.rental_unit_values[ru.id][kind].amount)
213231
return row
214232

@@ -237,3 +255,24 @@ def get_extra_context(self, ru: "NkRentalUnit", contract: "NkContract") -> dict:
237255
return {}
238256

239257
# def update_context(self, context, ru, contract):
258+
259+
260+
class NkMeasurementDataMixin:
261+
"""Mixin for NkCosts that require measurement data."""
262+
263+
def __init__(self, report_generator: "NkReportGenerator", cost_config: dict):
264+
super().__init__(report_generator, cost_config)
265+
self.measurements = {}
266+
measurements_configs = cost_config.get("measurement_data")
267+
if measurements_configs:
268+
for key, config in measurements_configs.items():
269+
self.measurements[key] = config["class"](report_generator, config)
270+
271+
def load_input_data(self):
272+
if self.measurements:
273+
for m in self.measurements.values():
274+
m.load()
275+
for warning in m.warnings:
276+
print(warning)
277+
self.generator.add_warning(warning[0], warning[1])
278+
super().load_input_data()

django/report/nk/cost/general.py

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ class NkTotalCost(NkCost):
1111

1212
cost_type_id = "simple_total"
1313

14-
def __init__(self, report: "NkReportGenerator", cost_config: dict):
15-
super().__init__(report, cost_config)
14+
def __init__(self, report_generator: "NkReportGenerator", cost_config: dict):
15+
super().__init__(report_generator, cost_config)
1616
self.rental_unit_usage = cost_config.get("object_weights", "area")
1717
## TODO: Get name and unit also from config?
1818
if self.rental_unit_usage == "area":
@@ -24,14 +24,14 @@ def __init__(self, report: "NkReportGenerator", cost_config: dict):
2424

2525
def load_input_data(self):
2626
super().load_input_data()
27-
self.total_values[NkCostValueType.COST].amount = self.report.config.get(
27+
self.total_values[NkCostValueType.COST].amount = self.generator.config.get(
2828
f"Kosten:{self.name}"
2929
)
3030
self.load_rental_unit_usage()
3131
self.normalize_monthly_amounts()
3232

3333
def load_rental_unit_usage(self):
34-
for ru in self.report.rental_units:
34+
for ru in self.generator.rental_units:
3535
weight = getattr(ru, self.rental_unit_usage)
3636
self.rental_unit_values[ru.id][NkCostValueType.USAGE].amount = weight
3737
self.section_values[ru.section.id][NkCostValueType.USAGE].amount += weight
@@ -53,8 +53,8 @@ class NkTotalEnergyCost(NkCost):
5353

5454
cost_type_id = "energy"
5555

56-
def __init__(self, report: "NkReportGenerator", cost_config: dict):
57-
super().__init__(report, cost_config)
56+
def __init__(self, report_generator: "NkReportGenerator", cost_config: dict):
57+
super().__init__(report_generator, cost_config)
5858
self.base_cost_factor = 0.3 # default 30/70% split
5959
self.base_cost_object_weights = None
6060
self.usage_cost_object_weights = None
@@ -77,8 +77,8 @@ class NkPerRentalUnitCost(NkCost):
7777

7878
cost_type_id = "per_rental_unit"
7979

80-
def __init__(self, report: "NkReportGenerator", cost_config: dict):
81-
super().__init__(report, cost_config)
80+
def __init__(self, report_generator: "NkReportGenerator", cost_config: dict):
81+
super().__init__(report_generator, cost_config)
8282
self.fee_per_unit_key = cost_config.get("fee_per_unit_key")
8383
self.fee_per_person_key = cost_config.get("fee_per_person_key")
8484
self.fixed_fees_key = cost_config.get("fixed_fees_key")
@@ -87,16 +87,18 @@ def __init__(self, report: "NkReportGenerator", cost_config: dict):
8787
def load_input_data(self):
8888
super().load_input_data()
8989
fee_per_unit = (
90-
self.report.config.get(self.fee_per_unit_key, 0) if self.fee_per_unit_key else 0
90+
self.generator.config.get(self.fee_per_unit_key, 0) if self.fee_per_unit_key else 0
9191
)
9292
fee_per_person = (
93-
self.report.config.get(self.fee_per_person_key, 0) if self.fee_per_person_key else 0
93+
self.generator.config.get(self.fee_per_person_key, 0) if self.fee_per_person_key else 0
94+
)
95+
fixed_fees = (
96+
self.generator.config.get(self.fixed_fees_key, {}) if self.fixed_fees_key else {}
9497
)
95-
fixed_fees = self.report.config.get(self.fixed_fees_key, {}) if self.fixed_fees_key else {}
9698

9799
monthly_weights = self.get_monthly_weights()
98100

99-
for ru in self.report.rental_units:
101+
for ru in self.generator.rental_units:
100102
if ru.is_virtual:
101103
chf_per_month = 0
102104
elif ru.name in fixed_fees:
@@ -111,22 +113,5 @@ def load_input_data(self):
111113
self.rental_unit_values[ru.id][NkCostValueType.COST].amount = sum(monthly_amounts)
112114

113115
def split_costs(self):
114-
"""Aggregate pre-calculated per-rental-unit costs up to sections and total."""
115116
self._calculate_weights()
116-
for ru in self.report.rental_units:
117-
for month in range(self.report.num_months):
118-
amount = self.rental_unit_values[ru.id][NkCostValueType.COST].monthly_amounts[
119-
month
120-
]
121-
self.section_values[ru.section.id][NkCostValueType.COST].monthly_amounts[
122-
month
123-
] += amount
124-
self.total_values[NkCostValueType.COST].monthly_amounts[month] += amount
125-
126-
for section in self.report.sections:
127-
self.section_values[section.id][NkCostValueType.COST].amount = sum(
128-
self.section_values[section.id][NkCostValueType.COST].monthly_amounts
129-
)
130-
self.total_values[NkCostValueType.COST].amount = sum(
131-
self.total_values[NkCostValueType.COST].monthly_amounts
132-
)
117+
self._aggregate_monthly_amounts()

0 commit comments

Comments
 (0)