Spaces:
Running
Running
Update app.py
Browse filesadding batch costs, fixing indicators
app.py
CHANGED
@@ -135,6 +135,9 @@ class CannabinoidEstimator(param.Parameterized):
|
|
135 |
shifts_per_week = param.Number(
|
136 |
default=21.0, bounds=(1, 28), step=1.0, label="Shifts per week"
|
137 |
)
|
|
|
|
|
|
|
138 |
|
139 |
kg_processed_per_shift = 0.0
|
140 |
labour_cost_per_shift = 0.0
|
@@ -215,6 +218,20 @@ class CannabinoidEstimator(param.Parameterized):
|
|
215 |
"Value": "center",
|
216 |
},
|
217 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
218 |
self._update_calculations()
|
219 |
|
220 |
def _create_sliders(self):
|
@@ -456,6 +473,13 @@ class CannabinoidEstimator(param.Parameterized):
|
|
456 |
format="0",
|
457 |
)
|
458 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
459 |
@param.depends(
|
460 |
"kg_processed_per_hour",
|
461 |
"finished_product_yield_pct",
|
@@ -470,6 +494,7 @@ class CannabinoidEstimator(param.Parameterized):
|
|
470 |
"wholesale_cbx_price",
|
471 |
"wholesale_cbx_pct",
|
472 |
"batch_test_cost",
|
|
|
473 |
"fixed_overhead_per_week",
|
474 |
"workers_per_shift",
|
475 |
"worker_hourly_rate",
|
@@ -555,10 +580,34 @@ class CannabinoidEstimator(param.Parameterized):
|
|
555 |
else 0.0
|
556 |
)
|
557 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
558 |
shift_cogs_before_output_specific = (
|
559 |
self.labour_cost_per_shift
|
560 |
+ self.variable_cost_per_shift
|
561 |
+ self.overhead_cost_per_shift
|
|
|
562 |
)
|
563 |
shift_output_specific_cogs = (
|
564 |
self.consumables_per_kg_output * self.saleable_kg_per_shift
|
@@ -698,6 +747,14 @@ class CannabinoidEstimator(param.Parameterized):
|
|
698 |
self.processing_data_df = pd.DataFrame(processing_data_dict)
|
699 |
if hasattr(self, "processing_table"):
|
700 |
self.processing_table.value = self.processing_data_df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
701 |
|
702 |
def _get_money_formatters(self):
|
703 |
return {
|
@@ -718,14 +775,14 @@ class CannabinoidEstimator(param.Parameterized):
|
|
718 |
max_width=input_col_max_width,
|
719 |
)
|
720 |
col2 = pn.Column(
|
721 |
-
pn.pane.Markdown("### Biomass parameters"),
|
722 |
self.bio_cbx_pct_slider,
|
723 |
self.bio_cost_slider,
|
724 |
sizing_mode="stretch_width",
|
725 |
max_width=input_col_max_width,
|
726 |
)
|
727 |
col3 = pn.Column(
|
728 |
-
pn.pane.Markdown("### Consumable rates"),
|
729 |
self.kwh_rate_slider,
|
730 |
self.water_cost_per_1000l_slider,
|
731 |
self.consumables_per_kg_bio_rate_slider,
|
@@ -733,14 +790,14 @@ class CannabinoidEstimator(param.Parameterized):
|
|
733 |
max_width=input_col_max_width,
|
734 |
)
|
735 |
col4 = pn.Column(
|
736 |
-
pn.pane.Markdown("### Wholesale details"),
|
737 |
self.wholesale_cbx_price_slider,
|
738 |
self.wholesale_cbx_pct_slider,
|
739 |
sizing_mode="stretch_width",
|
740 |
max_width=input_col_max_width,
|
741 |
)
|
742 |
col5 = pn.Column(
|
743 |
-
pn.pane.Markdown("### Variable costs"),
|
744 |
self.kwh_per_kg_bio_slider,
|
745 |
self.water_liters_consumed_per_kg_bio_slider,
|
746 |
self.consumables_per_kg_output_slider,
|
@@ -748,15 +805,17 @@ class CannabinoidEstimator(param.Parameterized):
|
|
748 |
max_width=input_col_max_width,
|
749 |
)
|
750 |
col6 = pn.Column(
|
751 |
-
pn.pane.Markdown("### Compliance"),
|
752 |
self.batch_test_cost_slider,
|
753 |
-
pn.pane.Markdown("
|
|
|
|
|
754 |
self.fixed_overhead_per_week_slider,
|
755 |
sizing_mode="stretch_width",
|
756 |
max_width=input_col_max_width,
|
757 |
)
|
758 |
col8 = pn.Column(
|
759 |
-
pn.pane.Markdown("### Worker Details"),
|
760 |
self.workers_per_shift_slider,
|
761 |
self.worker_hourly_rate_slider,
|
762 |
self.managers_per_shift_slider,
|
@@ -765,7 +824,7 @@ class CannabinoidEstimator(param.Parameterized):
|
|
765 |
max_width=input_col_max_width,
|
766 |
)
|
767 |
col9 = pn.Column(
|
768 |
-
pn.pane.Markdown("### Shift details"),
|
769 |
self.labour_hours_per_shift_slider,
|
770 |
self.processing_hours_per_shift_slider,
|
771 |
self.shifts_per_day_slider,
|
@@ -773,7 +832,12 @@ class CannabinoidEstimator(param.Parameterized):
|
|
773 |
sizing_mode="stretch_width",
|
774 |
max_width=input_col_max_width,
|
775 |
)
|
776 |
-
|
|
|
|
|
|
|
|
|
|
|
777 |
input_grid = pn.FlexBox(
|
778 |
col1, col2, col3, col4, col5, col8, col9, col6, align_content="normal"
|
779 |
)
|
@@ -799,25 +863,30 @@ class CannabinoidEstimator(param.Parameterized):
|
|
799 |
max_width=input_col_max_width,
|
800 |
)
|
801 |
|
802 |
-
profit_weekly = pn.indicators.Number(
|
803 |
-
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
)
|
809 |
|
810 |
-
profit_pct = pn.indicators.Number(
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
)
|
817 |
-
|
|
|
|
|
|
|
|
|
|
|
818 |
table_grid = pn.FlexBox(
|
819 |
-
profit_weekly,
|
820 |
-
profit_pct,
|
821 |
processing_table_display,
|
822 |
profit_table_display,
|
823 |
money_table_display,
|
@@ -839,6 +908,16 @@ estimator_app = CannabinoidEstimator()
|
|
839 |
# pn.config.raw_css = custom_themes.get_base_css(custom_themes.DARK_THEME_VARS)
|
840 |
estimator_app.view().servable(title="CBx Revenue Estimator")
|
841 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
842 |
if __name__ == "__main__":
|
843 |
pn.serve(
|
844 |
estimator_app.view(),
|
|
|
135 |
shifts_per_week = param.Number(
|
136 |
default=21.0, bounds=(1, 28), step=1.0, label="Shifts per week"
|
137 |
)
|
138 |
+
batch_frequency = param.String(
|
139 |
+
default = "Day", label="New batch frequency"
|
140 |
+
)
|
141 |
|
142 |
kg_processed_per_shift = 0.0
|
143 |
labour_cost_per_shift = 0.0
|
|
|
218 |
"Value": "center",
|
219 |
},
|
220 |
)
|
221 |
+
self.profit_weekly = pn.indicators.Number(
|
222 |
+
name="Weekly Profit",
|
223 |
+
value=self.net_rev_per_week,
|
224 |
+
format=f"${self.net_rev_per_week / 1000:.0f} k",
|
225 |
+
default_color="green",
|
226 |
+
align="center",
|
227 |
+
)
|
228 |
+
self.profit_pct = pn.indicators.Number(
|
229 |
+
name="Operating Profit",
|
230 |
+
value=self.operating_profit_pct,
|
231 |
+
format=f"{self.operating_profit_pct * 100.0:.2f}%",
|
232 |
+
default_color="green",
|
233 |
+
align="center",
|
234 |
+
)
|
235 |
self._update_calculations()
|
236 |
|
237 |
def _create_sliders(self):
|
|
|
473 |
format="0",
|
474 |
)
|
475 |
|
476 |
+
self.batch_frequency_radio = pn.widgets.RadioButtonGroup.from_param(
|
477 |
+
self.param.batch_frequency,
|
478 |
+
name=self.param.batch_frequency.label,
|
479 |
+
options=["Shift", "Day", "Week"],
|
480 |
+
button_type="primary",
|
481 |
+
)
|
482 |
+
|
483 |
@param.depends(
|
484 |
"kg_processed_per_hour",
|
485 |
"finished_product_yield_pct",
|
|
|
494 |
"wholesale_cbx_price",
|
495 |
"wholesale_cbx_pct",
|
496 |
"batch_test_cost",
|
497 |
+
"batch_frequency",
|
498 |
"fixed_overhead_per_week",
|
499 |
"workers_per_shift",
|
500 |
"worker_hourly_rate",
|
|
|
580 |
else 0.0
|
581 |
)
|
582 |
|
583 |
+
# Calculate batch_test_cost_per_shift based on batch_frequency
|
584 |
+
self.batch_test_cost_per_shift = 0.0
|
585 |
+
if self.batch_frequency == "Shift":
|
586 |
+
self.batch_test_cost_per_shift = self.batch_test_cost
|
587 |
+
elif self.batch_frequency == "Day":
|
588 |
+
# Ensure self.shifts_per_day is defined and positive
|
589 |
+
if hasattr(self, 'shifts_per_day') and self.shifts_per_day > 0:
|
590 |
+
self.batch_test_cost_per_shift = self.batch_test_cost / self.shifts_per_day
|
591 |
+
else:
|
592 |
+
# If no shifts per day, or attribute not defined, cost per shift is 0
|
593 |
+
# Or, this could be an error condition if shifts_per_day is expected
|
594 |
+
self.batch_test_cost_per_shift = 0.0
|
595 |
+
elif self.batch_frequency == "Week":
|
596 |
+
# self.shifts_per_week is used above, so it should be available
|
597 |
+
if self.shifts_per_week > 0:
|
598 |
+
self.batch_test_cost_per_shift = self.batch_test_cost / self.shifts_per_week
|
599 |
+
else:
|
600 |
+
# If no shifts per week, cost per shift is 0
|
601 |
+
self.batch_test_cost_per_shift = 0.0
|
602 |
+
# else: # Optional: handle invalid self.batch_frequency value
|
603 |
+
# For example, raise ValueError or log a warning
|
604 |
+
# print(f"Warning: Unknown batch_frequency: {self.batch_frequency}")
|
605 |
+
|
606 |
shift_cogs_before_output_specific = (
|
607 |
self.labour_cost_per_shift
|
608 |
+ self.variable_cost_per_shift
|
609 |
+ self.overhead_cost_per_shift
|
610 |
+
+ self.batch_test_cost_per_shift # Added batch test cost
|
611 |
)
|
612 |
shift_output_specific_cogs = (
|
613 |
self.consumables_per_kg_output * self.saleable_kg_per_shift
|
|
|
747 |
self.processing_data_df = pd.DataFrame(processing_data_dict)
|
748 |
if hasattr(self, "processing_table"):
|
749 |
self.processing_table.value = self.processing_data_df
|
750 |
+
|
751 |
+
if hasattr(self, "profit_weekly"):
|
752 |
+
self.profit_weekly.value = self.net_rev_per_week
|
753 |
+
self.profit_weekly.format = f"${self.net_rev_per_week / 1000:.0f} k"
|
754 |
+
|
755 |
+
if hasattr(self, "profit_pct"):
|
756 |
+
self.profit_pct.value = self.operating_profit_pct
|
757 |
+
self.profit_pct.format=f"{self.operating_profit_pct * 100.0:.2f}%"
|
758 |
|
759 |
def _get_money_formatters(self):
|
760 |
return {
|
|
|
775 |
max_width=input_col_max_width,
|
776 |
)
|
777 |
col2 = pn.Column(
|
778 |
+
pn.pane.Markdown("### Biomass parameters", margin=0),
|
779 |
self.bio_cbx_pct_slider,
|
780 |
self.bio_cost_slider,
|
781 |
sizing_mode="stretch_width",
|
782 |
max_width=input_col_max_width,
|
783 |
)
|
784 |
col3 = pn.Column(
|
785 |
+
pn.pane.Markdown("### Consumable rates", margin=0),
|
786 |
self.kwh_rate_slider,
|
787 |
self.water_cost_per_1000l_slider,
|
788 |
self.consumables_per_kg_bio_rate_slider,
|
|
|
790 |
max_width=input_col_max_width,
|
791 |
)
|
792 |
col4 = pn.Column(
|
793 |
+
pn.pane.Markdown("### Wholesale details", margin=0),
|
794 |
self.wholesale_cbx_price_slider,
|
795 |
self.wholesale_cbx_pct_slider,
|
796 |
sizing_mode="stretch_width",
|
797 |
max_width=input_col_max_width,
|
798 |
)
|
799 |
col5 = pn.Column(
|
800 |
+
pn.pane.Markdown("### Variable costs", margin=0),
|
801 |
self.kwh_per_kg_bio_slider,
|
802 |
self.water_liters_consumed_per_kg_bio_slider,
|
803 |
self.consumables_per_kg_output_slider,
|
|
|
805 |
max_width=input_col_max_width,
|
806 |
)
|
807 |
col6 = pn.Column(
|
808 |
+
pn.pane.Markdown("### Compliance", margin=0),
|
809 |
self.batch_test_cost_slider,
|
810 |
+
pn.pane.Markdown("New Batch Every:", margin=0),
|
811 |
+
self.batch_frequency_radio,
|
812 |
+
pn.pane.Markdown("### Overhead", margin=0),
|
813 |
self.fixed_overhead_per_week_slider,
|
814 |
sizing_mode="stretch_width",
|
815 |
max_width=input_col_max_width,
|
816 |
)
|
817 |
col8 = pn.Column(
|
818 |
+
pn.pane.Markdown("### Worker Details", margin=0),
|
819 |
self.workers_per_shift_slider,
|
820 |
self.worker_hourly_rate_slider,
|
821 |
self.managers_per_shift_slider,
|
|
|
824 |
max_width=input_col_max_width,
|
825 |
)
|
826 |
col9 = pn.Column(
|
827 |
+
pn.pane.Markdown("### Shift details", margin=0),
|
828 |
self.labour_hours_per_shift_slider,
|
829 |
self.processing_hours_per_shift_slider,
|
830 |
self.shifts_per_day_slider,
|
|
|
832 |
sizing_mode="stretch_width",
|
833 |
max_width=input_col_max_width,
|
834 |
)
|
835 |
+
|
836 |
+
# input_grid = pn.GridSpec(sizing_mode="stretch_width", max_width=1800, margin=10)
|
837 |
+
# input_grid[0, 0] = col1
|
838 |
+
# input_grid[0, 1] = col2
|
839 |
+
# input_grid[0, 2] = col3
|
840 |
+
# input_grid[0, 3] = col4
|
841 |
input_grid = pn.FlexBox(
|
842 |
col1, col2, col3, col4, col5, col8, col9, col6, align_content="normal"
|
843 |
)
|
|
|
863 |
max_width=input_col_max_width,
|
864 |
)
|
865 |
|
866 |
+
# profit_weekly = pn.indicators.Number(
|
867 |
+
# name="Weekly Profit",
|
868 |
+
# value=self.net_rev_per_week,
|
869 |
+
# format=f"${self.net_rev_per_week / 1000:.0f} k",
|
870 |
+
# default_color="green",
|
871 |
+
# align="center",
|
872 |
+
# )
|
873 |
|
874 |
+
# profit_pct = pn.indicators.Number(
|
875 |
+
# name="Operating Profit",
|
876 |
+
# value=self.operating_profit_pct,
|
877 |
+
# format=f"{self.operating_profit_pct * 100.0:.2f}%",
|
878 |
+
# default_color="green",
|
879 |
+
# align="center",
|
880 |
+
# )
|
881 |
+
|
882 |
+
# indicator_layout = pn.Column(profit_pct, profit_weekly, align="center")
|
883 |
+
|
884 |
+
# table_grid = pn.GridSpec(sizing_mode="stretch_width", max_width=1800, margin=10)
|
885 |
+
# table_grid[:, 0:2] = tables_layout
|
886 |
+
# table_grid[:, 2] = indicator_layout
|
887 |
table_grid = pn.FlexBox(
|
888 |
+
self.profit_weekly,
|
889 |
+
self.profit_pct,
|
890 |
processing_table_display,
|
891 |
profit_table_display,
|
892 |
money_table_display,
|
|
|
908 |
# pn.config.raw_css = custom_themes.get_base_css(custom_themes.DARK_THEME_VARS)
|
909 |
estimator_app.view().servable(title="CBx Revenue Estimator")
|
910 |
|
911 |
+
# Instantiate the template with widgets displayed in the sidebar
|
912 |
+
# template = pn.template.FastListTemplate(
|
913 |
+
# title="CBx Revenue Estimator (FastList Panel)",
|
914 |
+
# #theme = custom_themes.DarkTheme,
|
915 |
+
# #sidebar=[freq, phase],
|
916 |
+
# )
|
917 |
+
|
918 |
+
# template.main.append(estimator_app.view())
|
919 |
+
# template.servable()
|
920 |
+
|
921 |
if __name__ == "__main__":
|
922 |
pn.serve(
|
923 |
estimator_app.view(),
|