XRachel commited on
Commit
0adae5e
verified
1 Parent(s): a4f69d0

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -32
app.py CHANGED
@@ -282,7 +282,7 @@ def executive_overview(df: pd.DataFrame):
282
  c4.metric("Units wasted", f"{units_wasted:,.0f}")
283
 
284
  st.markdown(
285
- '<div class="note-box"><b>Executive logic:</b> this page gives a clean monthly view of revenue, profit, sold units, and wasted units for the current filter selection. It also flags the months where the revenue-profit gap looks unusually wide.</div>',
286
  unsafe_allow_html=True,
287
  )
288
 
@@ -373,7 +373,7 @@ def executive_overview(df: pd.DataFrame):
373
  def category_intelligence(df: pd.DataFrame):
374
  st.subheader("FRESHIE 路 Category Intelligence")
375
  st.markdown(
376
- '<div class="note-box"><b>Coverage:</b> this page covers all visible categories from the current sidebar filters. It keeps the story chart-led and focuses on category, region, store, and demand forecast signals.</div>',
377
  unsafe_allow_html=True,
378
  )
379
 
@@ -513,7 +513,7 @@ def category_intelligence(df: pd.DataFrame):
513
 
514
  def inventory_page(df: pd.DataFrame):
515
  st.subheader("FRESHIE 路 Inventory & Replenishment")
516
- st.markdown('<div class="note-box"><b>Audience:</b> supply chain lead. Use this page for reorder, transfer, expiry control, and stock-balancing decisions.</div>', unsafe_allow_html=True)
517
 
518
  work = df.copy()
519
  work["recommended_order_qty"] = (1.15 * work["daily_demand"] - work["leftover_units"]).clip(lower=0).round()
@@ -644,7 +644,7 @@ def inventory_page(df: pd.DataFrame):
644
 
645
  def promotion_page(df: pd.DataFrame):
646
  st.subheader("FRESHIE 路 Promotion Designer")
647
- st.markdown('<div class="note-box"><b>Audience:</b> marketing lead. Use this page to test markdown depth, bundle logic, and campaign copy.</div>', unsafe_allow_html=True)
648
  st.caption("Promotion designer logic: business-rule simulator. Demand lift is estimated from a base uplift + discount effect + optional bundle effect.")
649
 
650
  left, right = st.columns([1, 1.2])
@@ -676,24 +676,27 @@ def promotion_page(df: pd.DataFrame):
676
  st.success(f"Run a {discount}% {campaign_type} for {promo_category} items in {expiry_target}.")
677
 
678
  with right:
679
- promo_base = df.groupby(["expiry_bucket"])[["discount_pct", "waste_pct", "sell_through_pct"]].mean().reset_index()
680
- fig = px.bar(
681
- promo_base,
682
- x="expiry_bucket",
683
- y=["discount_pct", "waste_pct"],
684
- barmode="group",
685
- title="Current discount vs waste by expiry",
686
- )
687
- st.plotly_chart(fig, use_container_width=True)
688
-
689
- fig2 = px.line(
690
- promo_base,
691
- x="expiry_bucket",
692
- y="sell_through_pct",
693
- markers=True,
694
- title="Sell-through by expiry bucket",
695
- )
696
- st.plotly_chart(fig2, use_container_width=True)
 
 
 
697
 
698
  st.markdown("### Recommended promotion copy")
699
  st.info(
@@ -714,11 +717,16 @@ def consumer_deals(df: pd.DataFrame):
714
  store_df = df[df["store_id"] == chosen_store].copy()
715
 
716
  c1, c2, c3 = st.columns(3)
717
- max_budget = c1.slider("Budget (EUR)", 5, 60, 20)
718
  preferred_category = c2.selectbox("Preferred category", ["All"] + sorted(store_df["category"].unique()))
719
- max_expiry = c3.slider("Maximum days until expiry", 1, 14, 5)
720
-
721
- deals = store_df[store_df["days_until_expiry"] <= max_expiry].copy()
 
 
 
 
 
722
  if preferred_category != "All":
723
  deals = deals[deals["category"] == preferred_category]
724
  deals["savings"] = (deals["base_price"] - deals["selling_price"]).clip(lower=0)
@@ -757,11 +765,15 @@ def consumer_bundles(df: pd.DataFrame):
757
  store_df = df[df["store_id"] == chosen_store].copy()
758
 
759
  c1, c2, c3 = st.columns(3)
760
- budget = c1.slider("Bundle budget (EUR)", 8, 80, 25)
761
  theme = c2.selectbox("Bundle theme", ["Quick dinner", "Healthy protein", "Family breakfast", "Budget saver"])
762
- max_expiry = c3.slider("Use items expiring within days", 1, 10, 5, key="bundle_exp")
763
 
764
- work = store_df[store_df["days_until_expiry"] <= max_expiry].copy()
 
 
 
 
765
  theme_map = {
766
  "Quick dinner": ["Ready_to_Eat", "Produce", "Bakery", "Dairy"],
767
  "Healthy protein": ["Meat", "Seafood", "Dairy", "Produce"],
@@ -774,7 +786,7 @@ def consumer_bundles(df: pd.DataFrame):
774
 
775
  picked, total, used_categories = [], 0.0, set()
776
  for _, row in work.iterrows():
777
- if total + row["selling_price"] <= budget:
778
  if theme != "Budget saver" and row["category"] in used_categories:
779
  continue
780
  picked.append(row)
@@ -808,8 +820,15 @@ def consumer_personal(df: pd.DataFrame):
808
  store_df = df[df["store_id"] == chosen_store].copy()
809
 
810
  favorite = st.selectbox("Favorite category", sorted(store_df["category"].unique()), key="cp_fav")
811
- price_cap = st.slider("Max item price (EUR)", 1, 30, 10, key="cp_cap")
812
- recs = store_df[(store_df["category"] == favorite) & (store_df["selling_price"] <= price_cap)].copy()
 
 
 
 
 
 
 
813
  recs["score"] = recs["discount_pct"] * 0.55 + recs["sell_through_pct"] * 0.20 + (1 - recs["waste_pct"]) * 0.25
814
  recs = recs.sort_values("score", ascending=False).head(12)
815
 
 
282
  c4.metric("Units wasted", f"{units_wasted:,.0f}")
283
 
284
  st.markdown(
285
+ '<div class="note-box"><b>Executive view:</b> monthly performance plus a short diagnosis of unusual revenue-profit gaps.</div>',
286
  unsafe_allow_html=True,
287
  )
288
 
 
373
  def category_intelligence(df: pd.DataFrame):
374
  st.subheader("FRESHIE 路 Category Intelligence")
375
  st.markdown(
376
+ '<div class="note-box"><b>Category view:</b> chart-led category, region, store, and forecast insights for the current filters.</div>',
377
  unsafe_allow_html=True,
378
  )
379
 
 
513
 
514
  def inventory_page(df: pd.DataFrame):
515
  st.subheader("FRESHIE 路 Inventory & Replenishment")
516
+ st.markdown('<div class="note-box"><b>Audience:</b> supply chain. Use this page for reorder, transfer, expiry control, and stock-balancing decisions.</div>', unsafe_allow_html=True)
517
 
518
  work = df.copy()
519
  work["recommended_order_qty"] = (1.15 * work["daily_demand"] - work["leftover_units"]).clip(lower=0).round()
 
644
 
645
  def promotion_page(df: pd.DataFrame):
646
  st.subheader("FRESHIE 路 Promotion Designer")
647
+ st.markdown('<div class="note-box"><b>Audience:</b> marketing. Use this page to test markdown depth, bundle logic, and campaign copy.</div>', unsafe_allow_html=True)
648
  st.caption("Promotion designer logic: business-rule simulator. Demand lift is estimated from a base uplift + discount effect + optional bundle effect.")
649
 
650
  left, right = st.columns([1, 1.2])
 
676
  st.success(f"Run a {discount}% {campaign_type} for {promo_category} items in {expiry_target}.")
677
 
678
  with right:
679
+ promo_base = sub.groupby("expiry_bucket")[["discount_pct", "waste_pct", "sell_through_pct"]].mean().reset_index() if len(sub) else pd.DataFrame(columns=["expiry_bucket","discount_pct","waste_pct","sell_through_pct"])
680
+ if not promo_base.empty:
681
+ fig = px.bar(
682
+ promo_base,
683
+ x="expiry_bucket",
684
+ y=["discount_pct", "waste_pct"],
685
+ barmode="group",
686
+ title=f"Discount vs waste for {promo_category}",
687
+ )
688
+ st.plotly_chart(fig, use_container_width=True)
689
+
690
+ fig2 = px.line(
691
+ promo_base,
692
+ x="expiry_bucket",
693
+ y="sell_through_pct",
694
+ markers=True,
695
+ title=f"Sell-through for {promo_category}",
696
+ )
697
+ st.plotly_chart(fig2, use_container_width=True)
698
+ else:
699
+ st.info("No records match the current promotion settings.")
700
 
701
  st.markdown("### Recommended promotion copy")
702
  st.info(
 
717
  store_df = df[df["store_id"] == chosen_store].copy()
718
 
719
  c1, c2, c3 = st.columns(3)
720
+ budget_range = c1.slider("Budget range (EUR)", 1, 60, (1, 20))
721
  preferred_category = c2.selectbox("Preferred category", ["All"] + sorted(store_df["category"].unique()))
722
+ expiry_range = c3.slider("Days until expiry", 1, 14, (1, 5))
723
+
724
+ deals = store_df[
725
+ (store_df["days_until_expiry"] >= expiry_range[0]) &
726
+ (store_df["days_until_expiry"] <= expiry_range[1]) &
727
+ (store_df["selling_price"] >= budget_range[0]) &
728
+ (store_df["selling_price"] <= budget_range[1])
729
+ ].copy()
730
  if preferred_category != "All":
731
  deals = deals[deals["category"] == preferred_category]
732
  deals["savings"] = (deals["base_price"] - deals["selling_price"]).clip(lower=0)
 
765
  store_df = df[df["store_id"] == chosen_store].copy()
766
 
767
  c1, c2, c3 = st.columns(3)
768
+ budget_range = c1.slider("Bundle budget range (EUR)", 8, 80, (8, 25))
769
  theme = c2.selectbox("Bundle theme", ["Quick dinner", "Healthy protein", "Family breakfast", "Budget saver"])
770
+ expiry_range = c3.slider("Use items expiring within days", 1, 10, (1, 5), key="bundle_exp")
771
 
772
+ work = store_df[
773
+ (store_df["days_until_expiry"] >= expiry_range[0]) &
774
+ (store_df["days_until_expiry"] <= expiry_range[1]) &
775
+ (store_df["selling_price"] <= budget_range[1])
776
+ ].copy()
777
  theme_map = {
778
  "Quick dinner": ["Ready_to_Eat", "Produce", "Bakery", "Dairy"],
779
  "Healthy protein": ["Meat", "Seafood", "Dairy", "Produce"],
 
786
 
787
  picked, total, used_categories = [], 0.0, set()
788
  for _, row in work.iterrows():
789
+ if total + row["selling_price"] <= budget_range[1]:
790
  if theme != "Budget saver" and row["category"] in used_categories:
791
  continue
792
  picked.append(row)
 
820
  store_df = df[df["store_id"] == chosen_store].copy()
821
 
822
  favorite = st.selectbox("Favorite category", sorted(store_df["category"].unique()), key="cp_fav")
823
+ price_range = st.slider("Price range (EUR)", 1, 30, (1, 10), key="cp_cap")
824
+ expiry_range = st.slider("Days until expiry", 1, 14, (1, 7), key="cp_days")
825
+ recs = store_df[
826
+ (store_df["category"] == favorite) &
827
+ (store_df["selling_price"] >= price_range[0]) &
828
+ (store_df["selling_price"] <= price_range[1]) &
829
+ (store_df["days_until_expiry"] >= expiry_range[0]) &
830
+ (store_df["days_until_expiry"] <= expiry_range[1])
831
+ ].copy()
832
  recs["score"] = recs["discount_pct"] * 0.55 + recs["sell_through_pct"] * 0.20 + (1 - recs["waste_pct"]) * 0.25
833
  recs = recs.sort_values("score", ascending=False).head(12)
834