AccruedInnovation commited on
Commit
42f206c
·
verified ·
1 Parent(s): 83787f0

Update gui.py

Browse files
Files changed (1) hide show
  1. gui.py +387 -385
gui.py CHANGED
@@ -1,386 +1,388 @@
1
- import panel as pn
2
- import pandas as pd
3
- import param
4
- from bokeh.models.formatters import PrintfTickFormatter
5
-
6
- from calculations import CannabinoidCalculations
7
- from config import slider_design, slider_style, slider_stylesheet, get_formatter
8
-
9
-
10
- class CannabinoidEstimatorGUI(CannabinoidCalculations):
11
- # DataFrame params for tables
12
- money_data_unit_df = param.DataFrame(
13
- pd.DataFrame(),
14
- precedence=-1, # precedence to hide from param pane if shown
15
- )
16
- money_data_time_df = param.DataFrame(pd.DataFrame(), precedence=-1)
17
- profit_data_df = param.DataFrame(pd.DataFrame(), precedence=-1)
18
- processing_data_df = param.DataFrame(pd.DataFrame(), precedence=-1)
19
-
20
- def __init__(self, **params):
21
- super().__init__(**params)
22
- self._create_sliders()
23
- self._create_tables_and_indicators()
24
- self._update_calculations() # Initial calculation and table update
25
-
26
- def _create_sliders(self):
27
- self.batch_frequency_radio = pn.widgets.RadioButtonGroup.from_param(
28
- self.param.batch_frequency,
29
- name=self.param.batch_frequency.label,
30
- options=["Shift", "Day", "Week"],
31
- button_type="primary",
32
- )
33
-
34
- def _create_tables_and_indicators(self):
35
- # Table for $/kg Biomass and $/kg Output
36
- self.money_unit_table = pn.widgets.Tabulator(
37
- self.money_data_unit_df, # Initial empty or pre-filled df
38
- formatters={
39
- "$/kg Biomass": get_formatter("$%.02f"),
40
- "$/kg Output": get_formatter("$%.02f"),
41
- },
42
- disabled=True,
43
- layout="fit_data",
44
- sizing_mode="fixed",
45
- align="center",
46
- show_index=False,
47
- text_align={
48
- " ": "right",
49
- "$/kg Biomass": "center",
50
- "$/kg Output": "center",
51
- },
52
- )
53
- # Table for Per Shift, Per Day, Per Week
54
- self.money_time_table = pn.widgets.Tabulator(
55
- self.money_data_time_df, # Initial empty or pre-filled df
56
- formatters={
57
- "Per Shift": get_formatter("$%.02f"),
58
- "Per Day": get_formatter("$%.02f"),
59
- "Per Week": get_formatter("$%.02f"),
60
- },
61
- disabled=True,
62
- layout="fit_data",
63
- sizing_mode="fixed",
64
- align="center",
65
- show_index=False,
66
- text_align={
67
- " ": "right",
68
- "Per Shift": "center",
69
- "Per Day": "center",
70
- "Per Week": "center",
71
- },
72
- )
73
- self.profit_table = pn.widgets.Tabulator(
74
- self.profit_data_df, # Initial empty or pre-filled df
75
- disabled=True,
76
- layout="fit_data_table",
77
- sizing_mode="fixed",
78
- align="center",
79
- show_index=False,
80
- text_align={"Metric": "right", "Value": "center"},
81
- )
82
- self.processing_table = pn.widgets.Tabulator(
83
- self.processing_data_df, # Initial empty or pre-filled df
84
- formatters={},
85
- disabled=True,
86
- layout="fit_data_table",
87
- sizing_mode="fixed",
88
- align="center",
89
- show_index=False,
90
- text_align={"Metric (Per Shift)": "right", "Value": "center"},
91
- )
92
- self.profit_weekly = pn.indicators.Number(
93
- name="Weekly Profit",
94
- value=0,
95
- format="$0 k",
96
- default_color="green",
97
- align="center",
98
- )
99
- self.profit_pct = pn.indicators.Number(
100
- name="Operating Profit",
101
- value=0,
102
- format="0.00%",
103
- default_color="green",
104
- align="center",
105
- )
106
-
107
- @param.depends("labour_hours_per_shift", watch=True)
108
- def _update_processing_hours_slider_constraints(self):
109
- new_max_processing_hours = self.labour_hours_per_shift
110
- # Ensure min bound is not greater than new max bound
111
- current_min_processing_hours = min(
112
- self.param.processing_hours_per_shift.bounds[0], new_max_processing_hours
113
- )
114
-
115
- self.param.processing_hours_per_shift.bounds = (
116
- current_min_processing_hours,
117
- new_max_processing_hours,
118
- )
119
- # Check if processing_hours_per_shift_slider exists before trying to update it
120
- if hasattr(self, "processing_hours_per_shift_slider"):
121
- self.processing_hours_per_shift_slider.end = new_max_processing_hours
122
- if self.processing_hours_per_shift > new_max_processing_hours:
123
- self.processing_hours_per_shift = new_max_processing_hours
124
- # Also update start if it's now greater than end
125
- if self.processing_hours_per_shift_slider.start > new_max_processing_hours:
126
- self.processing_hours_per_shift_slider.start = (
127
- current_min_processing_hours # or new_max_processing_hours
128
- )
129
-
130
- def _post_calculation_update(self):
131
- """Overrides the base class method to update GUI elements."""
132
- super()._post_calculation_update() # Call base class method if it has any logic
133
- self._update_tables_data()
134
-
135
- def _update_tables_data(self):
136
- metric_names = [
137
- "Biomass cost",
138
- "Processing cost",
139
- "Gross Revenue",
140
- "Net Revenue",
141
- ]
142
- money_data_unit_dict = {
143
- " ": metric_names,
144
- "$/kg Biomass": [
145
- self.bio_cost,
146
- self.internal_cogs_per_kg_bio,
147
- self.gross_rev_per_kg_bio,
148
- self.net_rev_per_kg_bio,
149
- ],
150
- "$/kg Output": [
151
- self.biomass_cost_per_kg_output,
152
- self.internal_cogs_per_kg_output,
153
- self.wholesale_cbx_price,
154
- self.net_rev_per_kg_output,
155
- ],
156
- }
157
- self.money_data_unit_df = pd.DataFrame(money_data_unit_dict)
158
- if hasattr(self, "money_unit_table"):
159
- self.money_unit_table.value = self.money_data_unit_df
160
-
161
- money_data_time_dict = {
162
- " ": metric_names,
163
- "Per Shift": [
164
- self.biomass_cost_per_shift,
165
- self.internal_cogs_per_shift,
166
- self.gross_rev_per_shift,
167
- self.net_rev_per_shift,
168
- ],
169
- "Per Day": [
170
- self.biomass_cost_per_day,
171
- self.internal_cogs_per_day,
172
- self.gross_rev_per_day,
173
- self.net_rev_per_day,
174
- ],
175
- "Per Week": [
176
- self.biomass_cost_per_week,
177
- self.internal_cogs_per_week,
178
- self.gross_rev_per_week,
179
- self.net_rev_per_week,
180
- ],
181
- }
182
- self.money_data_time_df = pd.DataFrame(money_data_time_dict)
183
- if hasattr(self, "money_time_table"):
184
- self.money_time_table.value = self.money_data_time_df
185
-
186
- profit_data_dict = {
187
- "Metric": ["Operating Profit", "Resin Spread"],
188
- "Value": [
189
- f"{self.operating_profit_pct * 100.0:.2f}%",
190
- f"{self.resin_spread_pct * 100.0:.2f}%",
191
- ],
192
- }
193
- self.profit_data_df = pd.DataFrame(profit_data_dict)
194
- if hasattr(self, "profit_table"):
195
- self.profit_table.value = self.profit_data_df
196
-
197
- processing_values_formatted_shift = [
198
- f"{self.kg_processed_per_shift:,.0f}",
199
- f"{self.saleable_kg_per_shift:,.0f}",
200
- f"${self.labour_cost_per_shift:,.2f}",
201
- f"${self.variable_cost_per_shift:,.2f}",
202
- f"${self.overhead_cost_per_shift:,.2f}",
203
- ]
204
- processing_values_formatted_day = [
205
- f"{self.kg_processed_per_shift * self.shifts_per_day:,.0f}",
206
- f"{self.saleable_kg_per_day:,.0f}",
207
- f"${self.labour_cost_per_shift * self.shifts_per_day:,.2f}",
208
- f"${self.variable_cost_per_shift * self.shifts_per_day:,.2f}",
209
- f"${self.overhead_cost_per_shift * self.shifts_per_day:,.2f}",
210
- ]
211
- processing_values_formatted_week = [
212
- f"{self.kg_processed_per_shift * self.shifts_per_week:,.0f}",
213
- f"{self.saleable_kg_per_week:,.0f}",
214
- f"${self.labour_cost_per_shift * self.shifts_per_week:,.2f}",
215
- f"${self.variable_cost_per_shift * self.shifts_per_week:,.2f}",
216
- f"${self.overhead_cost_per_shift * self.shifts_per_week:,.2f}",
217
- ]
218
- processing_data_dict = {
219
- "Metric Per": [
220
- "Kilograms Extracted",
221
- "Kg CBx Produced",
222
- "Labour Cost",
223
- "Variable Cost",
224
- "Overhead",
225
- ],
226
- "Shift": processing_values_formatted_shift,
227
- "Day": processing_values_formatted_day,
228
- "Week": processing_values_formatted_week,
229
- }
230
- self.processing_data_df = pd.DataFrame(processing_data_dict)
231
- if hasattr(self, "processing_table"):
232
- self.processing_table.value = self.processing_data_df
233
-
234
- if hasattr(self, "profit_weekly"):
235
- self.profit_weekly.value = self.net_rev_per_week
236
- # Ensure format updates if value changes significantly (e.g. from 0 to large number)
237
- self.profit_weekly.format = (
238
- f"${self.net_rev_per_week / 1000:.0f} k"
239
- if self.net_rev_per_week != 0
240
- else "$0 k"
241
- )
242
-
243
- if hasattr(self, "profit_pct"):
244
- self.profit_pct.value = self.operating_profit_pct
245
- self.profit_pct.format = f"{self.operating_profit_pct * 100.0:.2f}%"
246
-
247
- def view(self):
248
- input_col_max_width = 400
249
- extractionCol = pn.Column(
250
- "### Extraction",
251
- self.param.kg_processed_per_hour,
252
- self.param.finished_product_yield_pct,
253
- sizing_mode="stretch_width",
254
- max_width=input_col_max_width,
255
- )
256
- biomassCol = pn.Column(
257
- pn.pane.Markdown("### Biomass parameters", margin=0),
258
- self.param.bio_cbx_pct,
259
- self.param.bio_cost,
260
- sizing_mode="stretch_width",
261
- max_width=input_col_max_width,
262
- )
263
- consumableCol = pn.Column(
264
- pn.pane.Markdown("### Consumable rates", margin=0),
265
- self.param.kwh_rate,
266
- self.param.water_cost_per_1000l,
267
- self.param.consumables_per_kg_bio_rate,
268
- sizing_mode="stretch_width",
269
- max_width=input_col_max_width,
270
- )
271
- wholesaleCol = pn.Column(
272
- pn.pane.Markdown("### Wholesale details", margin=0),
273
- self.param.wholesale_cbx_price,
274
- self.param.wholesale_cbx_pct,
275
- sizing_mode="stretch_width",
276
- max_width=input_col_max_width,
277
- )
278
- variableCol = pn.Column(
279
- pn.pane.Markdown("### Variable processing costs", margin=0),
280
- self.param.kwh_per_kg_bio,
281
- self.param.water_liters_consumed_per_kg_bio,
282
- self.param.consumables_per_kg_output,
283
- sizing_mode="stretch_width",
284
- max_width=input_col_max_width,
285
- )
286
- complianceBatchCol = pn.Column(
287
- pn.pane.Markdown("### Compliance", margin=0),
288
- self.param.batch_test_cost,
289
- pn.pane.Markdown("New Batch Every:", margin=0),
290
- self.batch_frequency_radio,
291
- sizing_mode="stretch_width",
292
- max_width=input_col_max_width,
293
- )
294
- leechCol = pn.Column(
295
- pn.pane.Markdown("### Weekly Rent & Fixed Overheads", margin=0),
296
- self.param.weekly_rent,
297
- self.param.non_production_electricity_cost_weekly,
298
- self.param.property_insurance_weekly,
299
- self.param.general_liability_insurance_weekly,
300
- self.param.product_recall_insurance_weekly,
301
- sizing_mode="stretch_width",
302
- max_width=input_col_max_width,
303
- )
304
- workerCol = pn.Column(
305
- pn.pane.Markdown("### Worker Details", margin=0),
306
- self.param.workers_per_shift,
307
- self.param.worker_base_pay_rate,
308
- self.param.managers_per_shift,
309
- self.param.manager_base_pay_rate,
310
- self.param.direct_cost_pct,
311
- sizing_mode="stretch_width",
312
- max_width=input_col_max_width,
313
- )
314
- shiftCol = pn.Column(
315
- pn.pane.Markdown("### Shift details", margin=0),
316
- self.param.labour_hours_per_shift,
317
- self.param.processing_hours_per_shift,
318
- self.param.shifts_per_day,
319
- self.param.shifts_per_week,
320
- sizing_mode="stretch_width",
321
- max_width=input_col_max_width,
322
- )
323
-
324
- input_grid = pn.FlexBox(
325
- extractionCol,
326
- biomassCol,
327
- consumableCol,
328
- wholesaleCol,
329
- variableCol,
330
- complianceBatchCol,
331
- workerCol,
332
- shiftCol,
333
-
334
- leechCol,
335
- align_content="flex-start",
336
- align_items="flex-start",
337
- # valid options include: '[stretch, flex-start, flex-end, center, baseline, first baseline, last baseline, start, end, self-start, self-end]'
338
- flex_wrap="wrap",
339
- ) # Added flex_wrap
340
-
341
- money_unit_table_display = pn.Column(
342
- pn.pane.Markdown(
343
- "### Financial Summary (Per Unit)", styles={"text-align": "center"}
344
- ),
345
- self.money_unit_table,
346
- sizing_mode="stretch_width",
347
- max_width=input_col_max_width + 50,
348
- )
349
- money_time_table_display = pn.Column(
350
- pn.pane.Markdown(
351
- "### Financial Summary (Aggregated)", styles={"text-align": "center"}
352
- ),
353
- self.money_time_table,
354
- sizing_mode="stretch_width",
355
- max_width=500,
356
- )
357
- profit_table_display = pn.Column(
358
- pn.pane.Markdown("### Profitability", styles={"text-align": "center"}),
359
- self.profit_table,
360
- sizing_mode="stretch_width",
361
- max_width=input_col_max_width,
362
- )
363
- processing_table_display = pn.Column(
364
- pn.pane.Markdown("### Processing Summary", styles={"text-align": "center"}),
365
- self.processing_table,
366
- sizing_mode="stretch_width",
367
- max_width=input_col_max_width,
368
- )
369
-
370
- table_grid = pn.FlexBox(
371
- self.profit_weekly,
372
- self.profit_pct,
373
- processing_table_display,
374
- profit_table_display,
375
- money_unit_table_display,
376
- money_time_table_display,
377
- align_content="normal",
378
- flex_wrap="wrap",
379
- )
380
- main_layout = pn.Column(
381
- pn.Accordion(("Knobs & Dials",input_grid)),
382
- pn.layout.Divider(margin=(10, 0)),
383
- table_grid,
384
- styles={"margin": "0px 10px"},
385
- )
 
 
386
  return main_layout
 
1
+ import panel as pn
2
+ import pandas as pd
3
+ import param
4
+ from bokeh.models.formatters import PrintfTickFormatter
5
+
6
+ from calculations import CannabinoidCalculations
7
+ from config import slider_design, slider_style, slider_stylesheet, get_formatter
8
+
9
+
10
+ class CannabinoidEstimatorGUI(CannabinoidCalculations):
11
+ # DataFrame params for tables
12
+ money_data_unit_df = param.DataFrame(
13
+ pd.DataFrame(),
14
+ precedence=-1, # precedence to hide from param pane if shown
15
+ )
16
+ money_data_time_df = param.DataFrame(pd.DataFrame(), precedence=-1)
17
+ profit_data_df = param.DataFrame(pd.DataFrame(), precedence=-1)
18
+ processing_data_df = param.DataFrame(pd.DataFrame(), precedence=-1)
19
+
20
+ def __init__(self, **params):
21
+ super().__init__(**params)
22
+ self._create_sliders()
23
+ self._create_tables_and_indicators()
24
+ self._update_calculations() # Initial calculation and table update
25
+
26
+ def _create_sliders(self):
27
+ self.batch_frequency_radio = pn.widgets.RadioButtonGroup.from_param(
28
+ self.param.batch_frequency,
29
+ name=self.param.batch_frequency.label,
30
+ options=["Shift", "Day", "Week"],
31
+ button_type="primary",
32
+ )
33
+
34
+ def _create_tables_and_indicators(self):
35
+ # Table for $/kg Biomass and $/kg Output
36
+ self.money_unit_table = pn.widgets.Tabulator(
37
+ self.money_data_unit_df, # Initial empty or pre-filled df
38
+ formatters={
39
+ "$/kg Biomass": get_formatter("$%.02f"),
40
+ "$/kg Output": get_formatter("$%.02f"),
41
+ },
42
+ disabled=True,
43
+ layout="fit_data",
44
+ sizing_mode="fixed",
45
+ align="center",
46
+ show_index=False,
47
+ text_align={
48
+ " ": "right",
49
+ "$/kg Biomass": "center",
50
+ "$/kg Output": "center",
51
+ },
52
+ )
53
+ # Table for Per Shift, Per Day, Per Week
54
+ self.money_time_table = pn.widgets.Tabulator(
55
+ self.money_data_time_df, # Initial empty or pre-filled df
56
+ formatters={
57
+ "Per Shift": get_formatter("$%.02f"),
58
+ "Per Day": get_formatter("$%.02f"),
59
+ "Per Week": get_formatter("$%.02f"),
60
+ },
61
+ disabled=True,
62
+ layout="fit_data",
63
+ sizing_mode="fixed",
64
+ align="center",
65
+ show_index=False,
66
+ text_align={
67
+ " ": "right",
68
+ "Per Shift": "center",
69
+ "Per Day": "center",
70
+ "Per Week": "center",
71
+ },
72
+ )
73
+ self.profit_table = pn.widgets.Tabulator(
74
+ self.profit_data_df, # Initial empty or pre-filled df
75
+ disabled=True,
76
+ layout="fit_data_table",
77
+ sizing_mode="fixed",
78
+ align="center",
79
+ show_index=False,
80
+ text_align={"Metric": "right", "Value": "center"},
81
+ )
82
+ self.processing_table = pn.widgets.Tabulator(
83
+ self.processing_data_df, # Initial empty or pre-filled df
84
+ formatters={},
85
+ disabled=True,
86
+ layout="fit_data_table",
87
+ sizing_mode="fixed",
88
+ align="center",
89
+ show_index=False,
90
+ text_align={"Metric (Per Shift)": "right", "Value": "center"},
91
+ )
92
+ self.profit_weekly = pn.indicators.Number(
93
+ name="Weekly Profit",
94
+ value=0,
95
+ format="$0 k",
96
+ default_color="green",
97
+ align="center",
98
+ )
99
+ self.profit_pct = pn.indicators.Number(
100
+ name="Operating Profit",
101
+ value=0,
102
+ format="0.00%",
103
+ default_color="green",
104
+ align="center",
105
+ )
106
+
107
+ @param.depends("labour_hours_per_shift", watch=True)
108
+ def _update_processing_hours_slider_constraints(self):
109
+ new_max_processing_hours = self.labour_hours_per_shift
110
+ # Ensure min bound is not greater than new max bound
111
+ current_min_processing_hours = min(
112
+ self.param.processing_hours_per_shift.bounds[0], new_max_processing_hours
113
+ )
114
+
115
+ self.param.processing_hours_per_shift.bounds = (
116
+ current_min_processing_hours,
117
+ new_max_processing_hours,
118
+ )
119
+ # Check if processing_hours_per_shift_slider exists before trying to update it
120
+ if hasattr(self, "processing_hours_per_shift_slider"):
121
+ self.processing_hours_per_shift_slider.end = new_max_processing_hours
122
+ if self.processing_hours_per_shift > new_max_processing_hours:
123
+ self.processing_hours_per_shift = new_max_processing_hours
124
+ # Also update start if it's now greater than end
125
+ if self.processing_hours_per_shift_slider.start > new_max_processing_hours:
126
+ self.processing_hours_per_shift_slider.start = (
127
+ current_min_processing_hours # or new_max_processing_hours
128
+ )
129
+
130
+ def _post_calculation_update(self):
131
+ """Overrides the base class method to update GUI elements."""
132
+ super()._post_calculation_update() # Call base class method if it has any logic
133
+ self._update_tables_data()
134
+
135
+ def _update_tables_data(self):
136
+ metric_names = [
137
+ "Biomass cost",
138
+ "Processing cost",
139
+ "Gross Revenue",
140
+ "Net Revenue",
141
+ ]
142
+ money_data_unit_dict = {
143
+ " ": metric_names,
144
+ "$/kg Biomass": [
145
+ self.bio_cost,
146
+ self.internal_cogs_per_kg_bio,
147
+ self.gross_rev_per_kg_bio,
148
+ self.net_rev_per_kg_bio,
149
+ ],
150
+ "$/kg Output": [
151
+ self.biomass_cost_per_kg_output,
152
+ self.internal_cogs_per_kg_output,
153
+ self.wholesale_cbx_price,
154
+ self.net_rev_per_kg_output,
155
+ ],
156
+ }
157
+ self.money_data_unit_df = pd.DataFrame(money_data_unit_dict)
158
+ if hasattr(self, "money_unit_table"):
159
+ self.money_unit_table.value = self.money_data_unit_df
160
+
161
+ money_data_time_dict = {
162
+ " ": metric_names,
163
+ "Per Shift": [
164
+ self.biomass_cost_per_shift,
165
+ self.internal_cogs_per_shift,
166
+ self.gross_rev_per_shift,
167
+ self.net_rev_per_shift,
168
+ ],
169
+ "Per Day": [
170
+ self.biomass_cost_per_day,
171
+ self.internal_cogs_per_day,
172
+ self.gross_rev_per_day,
173
+ self.net_rev_per_day,
174
+ ],
175
+ "Per Week": [
176
+ self.biomass_cost_per_week,
177
+ self.internal_cogs_per_week,
178
+ self.gross_rev_per_week,
179
+ self.net_rev_per_week,
180
+ ],
181
+ }
182
+ self.money_data_time_df = pd.DataFrame(money_data_time_dict)
183
+ if hasattr(self, "money_time_table"):
184
+ self.money_time_table.value = self.money_data_time_df
185
+
186
+ profit_data_dict = {
187
+ "Metric": ["Operating Profit", "Resin Spread"],
188
+ "Value": [
189
+ f"{self.operating_profit_pct * 100.0:.2f}%",
190
+ f"{self.resin_spread_pct * 100.0:.2f}%",
191
+ ],
192
+ }
193
+ self.profit_data_df = pd.DataFrame(profit_data_dict)
194
+ if hasattr(self, "profit_table"):
195
+ self.profit_table.value = self.profit_data_df
196
+
197
+ processing_values_formatted_shift = [
198
+ f"{self.kg_processed_per_shift:,.0f}",
199
+ f"{self.saleable_kg_per_shift:,.0f}",
200
+ f"${self.labour_cost_per_shift:,.2f}",
201
+ f"${self.variable_cost_per_shift:,.2f}",
202
+ f"${self.overhead_cost_per_shift:,.2f}",
203
+ ]
204
+ processing_values_formatted_day = [
205
+ f"{self.kg_processed_per_shift * self.shifts_per_day:,.0f}",
206
+ f"{self.saleable_kg_per_day:,.0f}",
207
+ f"${self.labour_cost_per_shift * self.shifts_per_day:,.2f}",
208
+ f"${self.variable_cost_per_shift * self.shifts_per_day:,.2f}",
209
+ f"${self.overhead_cost_per_shift * self.shifts_per_day:,.2f}",
210
+ ]
211
+ processing_values_formatted_week = [
212
+ f"{self.kg_processed_per_shift * self.shifts_per_week:,.0f}",
213
+ f"{self.saleable_kg_per_week:,.0f}",
214
+ f"${self.labour_cost_per_shift * self.shifts_per_week:,.2f}",
215
+ f"${self.variable_cost_per_shift * self.shifts_per_week:,.2f}",
216
+ f"${self.overhead_cost_per_shift * self.shifts_per_week:,.2f}",
217
+ ]
218
+ processing_data_dict = {
219
+ "Metric Per": [
220
+ "Kilograms Extracted",
221
+ "Kg CBx Produced",
222
+ "Labour Cost",
223
+ "Variable Cost",
224
+ "Overhead",
225
+ ],
226
+ "Shift": processing_values_formatted_shift,
227
+ "Day": processing_values_formatted_day,
228
+ "Week": processing_values_formatted_week,
229
+ }
230
+ self.processing_data_df = pd.DataFrame(processing_data_dict)
231
+ if hasattr(self, "processing_table"):
232
+ self.processing_table.value = self.processing_data_df
233
+
234
+ if hasattr(self, "profit_weekly"):
235
+ self.profit_weekly.value = self.net_rev_per_week
236
+ # Ensure format updates if value changes significantly (e.g. from 0 to large number)
237
+ self.profit_weekly.format = (
238
+ f"${self.net_rev_per_week / 1000:.0f} k"
239
+ if self.net_rev_per_week != 0
240
+ else "$0 k"
241
+ )
242
+
243
+ if hasattr(self, "profit_pct"):
244
+ self.profit_pct.value = self.operating_profit_pct
245
+ self.profit_pct.format = f"{self.operating_profit_pct * 100.0:.2f}%"
246
+
247
+ def view(self):
248
+ input_col_max_width = 400
249
+ extractionCol = pn.Column(
250
+ "### Extraction",
251
+ self.param.kg_processed_per_hour,
252
+ self.param.finished_product_yield_pct,
253
+ sizing_mode="stretch_width",
254
+ max_width=input_col_max_width,
255
+ )
256
+ biomassCol = pn.Column(
257
+ pn.pane.Markdown("### Biomass parameters", margin=0),
258
+ self.param.bio_cbx_pct,
259
+ self.param.bio_cost,
260
+ sizing_mode="stretch_width",
261
+ max_width=input_col_max_width,
262
+ )
263
+ consumableCol = pn.Column(
264
+ pn.pane.Markdown("### Consumable rates", margin=0),
265
+ self.param.kwh_rate,
266
+ self.param.water_cost_per_1000l,
267
+ self.param.consumables_per_kg_bio_rate,
268
+ sizing_mode="stretch_width",
269
+ max_width=input_col_max_width,
270
+ )
271
+ wholesaleCol = pn.Column(
272
+ pn.pane.Markdown("### Wholesale details", margin=0),
273
+ self.param.wholesale_cbx_price,
274
+ self.param.wholesale_cbx_pct,
275
+ sizing_mode="stretch_width",
276
+ max_width=input_col_max_width,
277
+ )
278
+ variableCol = pn.Column(
279
+ pn.pane.Markdown("### Variable processing costs", margin=0),
280
+ self.param.kwh_per_kg_bio,
281
+ self.param.water_liters_consumed_per_kg_bio,
282
+ self.param.consumables_per_kg_output,
283
+ sizing_mode="stretch_width",
284
+ max_width=input_col_max_width,
285
+ )
286
+ complianceBatchCol = pn.Column(
287
+ pn.pane.Markdown("### Compliance", margin=0),
288
+ self.param.batch_test_cost,
289
+ pn.pane.Markdown("New Batch Every:", margin=0),
290
+ self.batch_frequency_radio,
291
+ sizing_mode="stretch_width",
292
+ max_width=input_col_max_width,
293
+ )
294
+ leechCol = pn.Column(
295
+ pn.pane.Markdown("### Weekly Rent & Fixed Overheads", margin=0),
296
+ self.param.weekly_rent,
297
+ self.param.non_production_electricity_cost_weekly,
298
+ self.param.property_insurance_weekly,
299
+ self.param.general_liability_insurance_weekly,
300
+ self.param.product_recall_insurance_weekly,
301
+ sizing_mode="stretch_width",
302
+ max_width=input_col_max_width,
303
+ )
304
+ workerCol = pn.Column(
305
+ pn.pane.Markdown("### Worker Details", margin=0),
306
+ self.param.workers_per_shift,
307
+ self.param.worker_base_pay_rate,
308
+ self.param.managers_per_shift,
309
+ self.param.manager_base_pay_rate,
310
+ self.param.direct_cost_pct,
311
+ sizing_mode="stretch_width",
312
+ max_width=input_col_max_width,
313
+ )
314
+ shiftCol = pn.Column(
315
+ pn.pane.Markdown("### Shift details", margin=0),
316
+ self.param.labour_hours_per_shift,
317
+ self.param.processing_hours_per_shift,
318
+ self.param.shifts_per_day,
319
+ self.param.shifts_per_week,
320
+ sizing_mode="stretch_width",
321
+ max_width=input_col_max_width,
322
+ )
323
+
324
+ input_grid = pn.FlexBox(
325
+ extractionCol,
326
+ biomassCol,
327
+ consumableCol,
328
+ wholesaleCol,
329
+ variableCol,
330
+ complianceBatchCol,
331
+ workerCol,
332
+ shiftCol,
333
+
334
+ leechCol,
335
+ align_content="flex-start",
336
+ align_items="flex-start",
337
+ # valid options include: '[stretch, flex-start, flex-end, center, baseline, first baseline, last baseline, start, end, self-start, self-end]'
338
+ flex_wrap="wrap",
339
+ ) # Added flex_wrap
340
+
341
+ money_unit_table_display = pn.Column(
342
+ pn.pane.Markdown(
343
+ "### Financial Summary (Per Unit)", styles={"text-align": "center"}
344
+ ),
345
+ self.money_unit_table,
346
+ sizing_mode="stretch_width",
347
+ max_width=input_col_max_width + 50,
348
+ )
349
+ money_time_table_display = pn.Column(
350
+ pn.pane.Markdown(
351
+ "### Financial Summary (Aggregated)", styles={"text-align": "center"}
352
+ ),
353
+ self.money_time_table,
354
+ sizing_mode="stretch_width",
355
+ max_width=500,
356
+ )
357
+ profit_table_display = pn.Column(
358
+ pn.pane.Markdown("### Profitability", styles={"text-align": "center"}),
359
+ self.profit_table,
360
+ sizing_mode="stretch_width",
361
+ max_width=input_col_max_width,
362
+ )
363
+ processing_table_display = pn.Column(
364
+ pn.pane.Markdown("### Processing Summary", styles={"text-align": "center"}),
365
+ self.processing_table,
366
+ sizing_mode="stretch_width",
367
+ max_width=input_col_max_width,
368
+ )
369
+
370
+ table_grid = pn.FlexBox(
371
+ self.profit_weekly,
372
+ self.profit_pct,
373
+ processing_table_display,
374
+ profit_table_display,
375
+ money_unit_table_display,
376
+ money_time_table_display,
377
+ align_content="normal",
378
+ flex_wrap="wrap",
379
+ )
380
+ knobs = pn.Accordion(("Knobs & Dials",input_grid))
381
+ knobs.active = [0]
382
+ main_layout = pn.Column(
383
+ knobs,
384
+ pn.layout.Divider(margin=(10, 0)),
385
+ table_grid,
386
+ styles={"margin": "0px 10px"},
387
+ )
388
  return main_layout