Upload app.py
Browse files
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
|
| 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>
|
| 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
|
| 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
|
| 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 =
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 718 |
preferred_category = c2.selectbox("Preferred category", ["All"] + sorted(store_df["category"].unique()))
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
deals = store_df[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 761 |
theme = c2.selectbox("Bundle theme", ["Quick dinner", "Healthy protein", "Family breakfast", "Budget saver"])
|
| 762 |
-
|
| 763 |
|
| 764 |
-
work = store_df[
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"] <=
|
| 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 |
-
|
| 812 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|