@@ -12,6 +12,11 @@ class NkCostValueType(Enum):
1212 COST = 1 # The costs that are billed
1313 USAGE = 2 # The usage that is billed (consumed energy, rental unit area, etc.)
1414 WEIGHT = 3 # The (internal) weight for the distribution of the costs
15+ COMMON_COST = (
16+ 4 # s Cost from common usage (e.g., Allgemeinstrom) that is split between all rental units
17+ )
18+ COMMON_USAGE = 5
19+ COMMON_WEIGHT = 6
1520
1621
1722@dataclass
@@ -106,25 +111,38 @@ def _normalize_monthly_amounts_for_dict(self, container: dict, value_required=Fa
106111 def split_costs (self ):
107112 self ._calculate_weights ()
108113 for kind in self .total_values :
109- if kind == NkCostValueType .WEIGHT :
110- continue
111- amount_per_weight = (
112- (self .total_values [kind ].amount / self .total_values [NkCostValueType .WEIGHT ].amount )
113- if self .total_values [NkCostValueType .WEIGHT ].amount
114- else 0
115- )
116- self ._calculate_amounts (self .total_values , kind , amount_per_weight )
117- for ru in self .generator .rental_units :
118- self ._calculate_amounts (self .rental_unit_values [ru .id ], kind , amount_per_weight )
119- for section in self .generator .sections :
120- self ._calculate_amounts (self .section_values [section .id ], kind , amount_per_weight )
114+ if kind in (NkCostValueType .COST , NkCostValueType .USAGE ):
115+ self ._split_cost (kind , NkCostValueType .WEIGHT )
121116
122117 def update (self ):
123118 pass
124119
125- def _calculate_amounts (self , values , kind : NkCostValueType , amount_per_weight ):
120+ def _split_cost (self , cost_type , weight_type ):
121+ amount_per_weight = self .generator .num_months * [0 ]
122+ for month in range (self .generator .num_months ):
123+ amount_per_weight [month ] = (
124+ (
125+ self .total_values [cost_type ].monthly_amounts [month ]
126+ / self .total_values [weight_type ].monthly_amounts [month ]
127+ )
128+ if self .total_values [weight_type ].monthly_amounts [month ]
129+ else 0
130+ )
131+ self ._calculate_amounts (self .total_values , cost_type , weight_type , amount_per_weight )
132+ for ru in self .generator .rental_units :
133+ self ._calculate_amounts (
134+ self .rental_unit_values [ru .id ], cost_type , weight_type , amount_per_weight
135+ )
136+ for section in self .generator .sections :
137+ self ._calculate_amounts (
138+ self .section_values [section .id ], cost_type , weight_type , amount_per_weight
139+ )
140+
141+ def _calculate_amounts (
142+ self , values , kind : NkCostValueType , weight_type : NkCostValueType , amount_per_weight
143+ ):
126144 for month in range (self .generator .num_months ):
127- amount = amount_per_weight * values [NkCostValueType . WEIGHT ].monthly_amounts [month ]
145+ amount = amount_per_weight [ month ] * values [weight_type ].monthly_amounts [month ]
128146 if (
129147 values [kind ].monthly_amounts [month ]
130148 and abs (values [kind ].monthly_amounts [month ] - amount ) > 0.01
@@ -146,14 +164,22 @@ def _calculate_amounts(self, values, kind: NkCostValueType, amount_per_weight):
146164 values [kind ].amount = total_amount
147165
148166 def _calculate_weights (self ):
149- self .add_value_type (NkCostValueType .WEIGHT , "Gewichtung" , "" )
167+ self ._calculate_weights_for_type (NkCostValueType .WEIGHT , "get_rental_unit_weights" )
168+
169+ def _calculate_weights_for_type (
170+ self , value_type : NkCostValueType , rental_unit_weights_function_name : str
171+ ):
172+ self .add_value_type (value_type , "Gewichtung" , "" )
150173 monthly_weights = self .get_monthly_weights ()
151174 section_weights = self .get_section_weights ()
152- total = self .total_values [NkCostValueType .WEIGHT ]
175+ total = self .total_values [value_type ]
176+ rental_unit_weights_function = getattr (self , rental_unit_weights_function_name )
177+ if not callable (rental_unit_weights_function ):
178+ raise ValueError (f"Invalid function name: { rental_unit_weights_function_name } " )
153179 for ru in self .generator .rental_units :
154- ru_weights = self . get_rental_unit_weights (ru .id )
155- values = self .rental_unit_values [ru .id ][NkCostValueType . WEIGHT ]
156- section = self .section_values [ru .section .id ][NkCostValueType . WEIGHT ]
180+ ru_weights = rental_unit_weights_function (ru .id )
181+ values = self .rental_unit_values [ru .id ][value_type ]
182+ section = self .section_values [ru .section .id ][value_type ]
157183 for month in range (self .generator .num_months ):
158184 weight = (
159185 monthly_weights [month ] * section_weights [ru .section .id ] * ru_weights [month ]
@@ -163,8 +189,8 @@ def _calculate_weights(self):
163189 total .monthly_amounts [month ] += weight
164190 values .amount = sum (values .monthly_amounts )
165191 for section in self .generator .sections :
166- self .section_values [section .id ][NkCostValueType . WEIGHT ].amount = sum (
167- self .section_values [section .id ][NkCostValueType . WEIGHT ].monthly_amounts
192+ self .section_values [section .id ][value_type ].amount = sum (
193+ self .section_values [section .id ][value_type ].monthly_amounts
168194 )
169195 total .amount = sum (total .monthly_amounts )
170196
@@ -217,6 +243,9 @@ def get_export_weight_row(self, include_percent=False):
217243 row = self ._get_export_row (NkCostValueType .WEIGHT , include_percent )
218244 return row
219245
246+ def get_export_extra_info (self , include_percent = False , formatter = None ):
247+ return None
248+
220249 def _get_export_row (self , kind , include_percent ):
221250 ## TODO: implement include_percent (if still needed)
222251 row = [self .name ]
@@ -230,7 +259,7 @@ def _get_export_row(self, kind, include_percent):
230259 row .append (self .rental_unit_values [ru .id ][kind ].amount )
231260 return row
232261
233- def get_assigned_amount (
262+ def _get_assigned_amount (
234263 self ,
235264 value_type : NkCostValueType ,
236265 contract : "NkContract" ,
@@ -239,12 +268,37 @@ def get_assigned_amount(
239268 ret = 0
240269 rental_units = [rental_unit ] if rental_unit else contract .rental_units
241270 for ru in rental_units :
242- for idx , amount in enumerate (
243- self .rental_unit_values [ru .id ][value_type ].monthly_amounts
244- ):
245- assigned_contract = ru .get_assigned_contract_for_month (idx )
246- if assigned_contract == contract :
247- ret += amount
271+ ret += self ._get_assigned_sum (
272+ self .rental_unit_values [ru .id ][value_type ].monthly_amounts , contract , ru
273+ )
274+ return ret
275+
276+ def get_assigned_cost (self , contract : "NkContract" , rental_unit : "NkRentalUnit | None" = None ):
277+ return self ._get_assigned_amount (NkCostValueType .COST , contract , rental_unit )
278+
279+ @classmethod
280+ def get_assigned_amounts (
281+ cls ,
282+ data : dict [str , list [float ]],
283+ contract : "NkContract" ,
284+ rental_unit : "NkRentalUnit" ,
285+ ):
286+ ret = {}
287+ for kind , monthly_values in data .items ():
288+ ret [kind ] = cls ._get_assigned_sum (monthly_values , contract , rental_unit )
289+ return ret
290+
291+ @staticmethod
292+ def _get_assigned_sum (
293+ monthly_values : list [float ],
294+ contract : "NkContract" ,
295+ rental_unit : "NkRentalUnit" ,
296+ ):
297+ ret = 0
298+ for idx , amount in enumerate (monthly_values ):
299+ assigned_contract = rental_unit .get_assigned_contract_for_month (idx )
300+ if assigned_contract == contract :
301+ ret += amount
248302 return ret
249303
250304 def get_building_amount (self , value_type : NkCostValueType ):
@@ -276,3 +330,44 @@ def load_input_data(self):
276330 print (warning )
277331 self .generator .add_warning (warning [0 ], warning [1 ])
278332 super ().load_input_data ()
333+
334+
335+ class NkCommonCostMixin :
336+ """Mixin for NkCosts that have a common usage part (e.g., Allgemeinstrom), which is distributed among all rental units."""
337+
338+ def __init__ (self , report_generator : "NkReportGenerator" , cost_config : dict ):
339+ super ().__init__ (report_generator , cost_config )
340+ self .add_value_type (NkCostValueType .COMMON_COST , "Allgemeinkosten" , "CHF" )
341+
342+ def get_assigned_cost (self , contract : "NkContract" , rental_unit : "NkRentalUnit | None" = None ):
343+ ret = super ().get_assigned_cost (contract , rental_unit )
344+ return ret + self ._get_assigned_amount (NkCostValueType .COMMON_COST , contract , rental_unit )
345+
346+ def get_rental_unit_common_weights (self , ru_id ):
347+ """Default is the rental unit area (per period)."""
348+ ru = self .generator .get_rental_unit_by_id (ru_id )
349+ if ru .is_virtual :
350+ return self .generator .num_months * [0.0 ]
351+ else :
352+ return self .generator .num_months * [ru .area / self .generator .num_months ]
353+
354+ def set_common_costs (self , cost : float | list [float ], usage : float | list [float ] | None ):
355+ if isinstance (cost , list ):
356+ self .total_values [NkCostValueType .COMMON_COST ].monthly_amounts = cost
357+ else :
358+ self .total_values [NkCostValueType .COMMON_COST ].amount = cost
359+ if usage :
360+ if isinstance (usage , list ):
361+ self .total_values [NkCostValueType .COMMON_USAGE ].monthly_amounts = usage
362+ else :
363+ self .total_values [NkCostValueType .COMMON_USAGE ].amount = usage
364+
365+ def _split_common_costs (self ):
366+ self ._calculate_common_weights ()
367+ for kind in (NkCostValueType .COMMON_COST , NkCostValueType .COMMON_USAGE ):
368+ self ._split_cost (kind , NkCostValueType .COMMON_WEIGHT )
369+
370+ def _calculate_common_weights (self ):
371+ self ._calculate_weights_for_type (
372+ NkCostValueType .COMMON_WEIGHT , "get_rental_unit_common_weights"
373+ )
0 commit comments