taotanapol commited on
Commit
50ffa30
Β·
verified Β·
1 Parent(s): 2b8dddd

Updated Summary Tab

Browse files
Files changed (1) hide show
  1. streamlit_app.py +103 -74
streamlit_app.py CHANGED
@@ -846,20 +846,18 @@ total_iqty = filtered_daily["ItemQty"].sum() if "ItemQty" in filtered_daily
846
  total_irev = filtered_daily["ItemRevenue"].sum() if "ItemRevenue" in filtered_daily.columns else 0
847
  rev_phead = (total_rev / total_cust) if total_cust else 0
848
 
849
- c1, c2, c3, c4, c5 = st.columns(5)
850
- c1.metric("Total Revenue", fmt_money(total_rev))
851
- c2.metric("Item Revenue (Grand Total)", fmt_money(total_irev))
852
- c3.metric("Total Customers", fmt_num(total_cust))
853
- c4.metric("Revenue / Head", fmt_money(rev_phead))
854
- c5.metric("Items Sold (Food)", fmt_num(total_iqty))
855
 
856
  st.divider()
857
 
858
  # ─────────────────────────────────────────────────────────────────────────────
859
  # Tabs
860
  # ─────────────────────────────────────────────────────────────────────────────
861
- tab_overview, tab_branch, tab_pl, tab_inv, tab_data = st.tabs(
862
- ["Overview", "Branches", "P&L", "Inventory", "Raw tables"]
863
  )
864
 
865
  # ── Overview ───────────────────────────────────────────────────────────────
@@ -944,63 +942,112 @@ with tab_overview:
944
  fig.update_yaxes(tickformat=",.0f")
945
  st.plotly_chart(style_plotly(fig, height=320), use_container_width=True)
946
 
947
- # ── Branches ────────────────────────────────────────────────────────────────
948
- with tab_branch:
949
- if filtered_daily.empty:
950
- st.info("No data for the current filters.")
951
- else:
952
- st.subheader("Revenue and Customers by Branch")
953
- br = (
954
- filtered_daily.groupby(["Restaurant", "Branch"], as_index=False)
955
- .agg(Revenue=("Revenue", "sum"),
956
- Customers=("Customers", "sum"),
957
- ItemRevenue=("ItemRevenue", "sum"))
958
- )
959
- br["Label"] = br["Restaurant"] + " / " + br["Branch"]
960
-
961
- col1, col2 = st.columns(2)
962
- with col1:
963
- ds = br.sort_values("Revenue")
964
- fig = px.bar(
965
- ds, x="Revenue", y="Label", orientation="h", color="Restaurant",
966
- color_discrete_map=RESTAURANT_COLOR,
967
- text=ds["Revenue"].apply(fmt_money),
968
- )
969
- fig.update_layout(xaxis_title="Revenue", yaxis_title=None, showlegend=False)
970
- fig.update_xaxes(tickformat=",.0f")
971
- st.plotly_chart(style_plotly(fig, height=420), use_container_width=True)
972
- with col2:
973
- ds = br.sort_values("Customers")
974
- fig = px.bar(
975
- ds, x="Customers", y="Label", orientation="h", color="Restaurant",
976
- color_discrete_map=RESTAURANT_COLOR,
977
- text=ds["Customers"].apply(fmt_num),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
978
  )
979
- fig.update_layout(xaxis_title="Customers", yaxis_title=None, showlegend=False)
980
- fig.update_xaxes(tickformat=",.0f")
981
- st.plotly_chart(style_plotly(fig, height=420), use_container_width=True)
982
-
983
- st.subheader("Monthly Summary")
984
- mf = apply_filters(kpi_monthly, use_date=False)
985
- if not mf.empty:
986
- mf = mf.sort_values(
987
- ["Year", "Month", "Restaurant", "Branch"],
988
- ascending=[False, False, True, True],
989
- )
990
- display_cols = [c for c in
991
- ["Year", "Month", "Restaurant", "Branch",
992
- "Revenue", "ItemRevenue", "Customers", "Rev_Per_Head"]
993
- if c in mf.columns]
994
  st.dataframe(
995
- mf[display_cols],
996
  use_container_width=True, hide_index=True,
997
  column_config={
 
 
998
  "Revenue": st.column_config.NumberColumn(format="ΰΈΏ%.0f"),
999
- "ItemRevenue": st.column_config.NumberColumn("Item Revenue", format="ΰΈΏ%.0f"),
1000
  "Customers": st.column_config.NumberColumn(format="%.0f"),
1001
  "Rev_Per_Head": st.column_config.NumberColumn("Rev / Head", format="ΰΈΏ%.0f"),
 
1002
  },
1003
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
 
1005
  # ── P&L ─────────────────────────────────────────────────────────────────────
1006
  with tab_pl:
@@ -1108,21 +1155,3 @@ with tab_inv:
1108
  fig.update_xaxes(tickformat=",.0f")
1109
  st.plotly_chart(style_plotly(fig, height=380), use_container_width=True)
1110
 
1111
- # ── Raw tables (browser) ────────────────────────────────────────────────────
1112
- with tab_data:
1113
- st.markdown("Quick browser for every sheet in the workbook.")
1114
- sheet_choice = st.selectbox("Sheet", list(sheets.keys()))
1115
- df = sheets[sheet_choice]
1116
- apply_to_table = st.checkbox(
1117
- "Apply current sidebar filters", value=True,
1118
- help="When checked, the table respects the Restaurant / Branch / date filters above.",
1119
- )
1120
- if apply_to_table:
1121
- df = apply_filters(df)
1122
- st.caption(f"{len(df):,} rows Γ— {len(df.columns)} columns")
1123
- # Keep the table snappy by capping the rendered length
1124
- cap = 5000
1125
- if len(df) > cap:
1126
- st.caption(f"Showing first {cap:,} rows (use filters to narrow the view).")
1127
- df = df.head(cap)
1128
- st.dataframe(df, use_container_width=True, hide_index=True)
 
846
  total_irev = filtered_daily["ItemRevenue"].sum() if "ItemRevenue" in filtered_daily.columns else 0
847
  rev_phead = (total_rev / total_cust) if total_cust else 0
848
 
849
+ c1, c2, c3 = st.columns(3)
850
+ c1.metric("Total Revenue", fmt_money(total_rev))
851
+ c2.metric("Total Customers", fmt_num(total_cust))
852
+ c3.metric("Revenue / Head", fmt_money(rev_phead))
 
 
853
 
854
  st.divider()
855
 
856
  # ─────────────────────────────────────────────────────────────────────────────
857
  # Tabs
858
  # ─────────────────────────────────────────────────────────────────────────────
859
+ tab_overview, tab_summary, tab_pl, tab_inv = st.tabs(
860
+ ["Overview", "Summary", "P&L", "Inventory"]
861
  )
862
 
863
  # ── Overview ───────────────────────────────────────────────────────────────
 
942
  fig.update_yaxes(tickformat=",.0f")
943
  st.plotly_chart(style_plotly(fig, height=320), use_container_width=True)
944
 
945
+ # ── Summary ─────────────────────────────────────────────────────────────────
946
+ # Restaurant-by-restaurant summary tables, mirroring the layout of the
947
+ # original Summary.xlsx (Copper Buffet) and Summary_Tiew.xlsx (Tiew Copper).
948
+ # Restaurant filter is intentionally ignored here so both restaurants are
949
+ # always shown; Branch / date / year filters still apply.
950
+ with tab_summary:
951
+ def _summary_filter(df: pd.DataFrame, *, use_date: bool = True) -> pd.DataFrame:
952
+ """Like apply_filters() but skips the Restaurant filter."""
953
+ if df.empty:
954
+ return df
955
+ out = df
956
+ if "Branch" in out.columns and sel_branches:
957
+ out = out[out["Branch"].isin(sel_branches)]
958
+ if use_date and "Date" in out.columns:
959
+ if date_from is not None:
960
+ out = out[out["Date"] >= pd.to_datetime(date_from)]
961
+ if date_to is not None:
962
+ out = out[out["Date"] <= pd.to_datetime(date_to)]
963
+ if sel_year != "All years":
964
+ if "Year" in out.columns:
965
+ out = out[out["Year"] == sel_year]
966
+ elif "Date" in out.columns:
967
+ out = out[out["Date"].dt.year == sel_year]
968
+ return out
969
+
970
+ def _render_restaurant_summary(restaurant_name: str) -> None:
971
+ st.subheader(restaurant_name)
972
+
973
+ monthly = _summary_filter(kpi_monthly, use_date=False)
974
+ monthly = monthly[monthly.get("Restaurant", "") == restaurant_name] \
975
+ if "Restaurant" in monthly.columns else monthly.iloc[0:0]
976
+ daily = _summary_filter(kpi_daily)
977
+ daily = daily[daily.get("Restaurant", "") == restaurant_name] \
978
+ if "Restaurant" in daily.columns else daily.iloc[0:0]
979
+
980
+ if monthly.empty and daily.empty:
981
+ st.info(f"No data for {restaurant_name} in the current filters.")
982
+ return
983
+
984
+ # ── Top-line totals for this restaurant ──────────────────────────
985
+ tot_rev = daily["Revenue"].sum() if "Revenue" in daily.columns else 0
986
+ tot_cust = daily["Customers"].sum() if "Customers" in daily.columns else 0
987
+ tot_iqty = daily["ItemQty"].sum() if "ItemQty" in daily.columns else 0
988
+ tot_irev = daily["ItemRevenue"].sum() if "ItemRevenue" in daily.columns else 0
989
+ rev_ph = (tot_rev / tot_cust) if tot_cust else 0
990
+
991
+ kc1, kc2, kc3, kc4 = st.columns(4)
992
+ kc1.metric("Revenue", fmt_money(tot_rev))
993
+ kc2.metric("Customers", fmt_num(tot_cust))
994
+ kc3.metric("Rev / Head", fmt_money(rev_ph))
995
+ kc4.metric("Item Revenue (Grand Total)", fmt_money(tot_irev))
996
+
997
+ # ── Monthly summary table ────────────────────────────────────────
998
+ st.markdown("**Monthly summary**")
999
+ if not monthly.empty:
1000
+ m = monthly.copy().sort_values(
1001
+ ["Year", "Month", "Branch"], ascending=[False, False, True]
1002
  )
1003
+ cols = [c for c in
1004
+ ["Year", "Month", "Branch", "Revenue", "Customers",
1005
+ "Rev_Per_Head", "ItemRevenue"]
1006
+ if c in m.columns]
 
 
 
 
 
 
 
 
 
 
 
1007
  st.dataframe(
1008
+ m[cols],
1009
  use_container_width=True, hide_index=True,
1010
  column_config={
1011
+ "Year": st.column_config.NumberColumn(format="%d"),
1012
+ "Month": st.column_config.NumberColumn(format="%d"),
1013
  "Revenue": st.column_config.NumberColumn(format="ΰΈΏ%.0f"),
 
1014
  "Customers": st.column_config.NumberColumn(format="%.0f"),
1015
  "Rev_Per_Head": st.column_config.NumberColumn("Rev / Head", format="ΰΈΏ%.0f"),
1016
+ "ItemRevenue": st.column_config.NumberColumn("Item Revenue", format="ΰΈΏ%.0f"),
1017
  },
1018
  )
1019
+ else:
1020
+ st.caption("No monthly rows in this filter window.")
1021
+
1022
+ # ── Daily detail (collapsed by default β€” can be long) ────────────
1023
+ with st.expander("Daily detail", expanded=False):
1024
+ if not daily.empty:
1025
+ d = daily.copy().sort_values(["Date", "Branch"], ascending=[False, True])
1026
+ # Compute Rev/Head on the fly so daily rows have the same column.
1027
+ if "Revenue" in d.columns and "Customers" in d.columns:
1028
+ d["Rev_Per_Head"] = d["Revenue"] / d["Customers"].replace(0, np.nan)
1029
+ cols = [c for c in
1030
+ ["Date", "Branch", "DayType", "Revenue", "Customers",
1031
+ "Rev_Per_Head", "ItemRevenue", "ItemQty"]
1032
+ if c in d.columns]
1033
+ st.dataframe(
1034
+ d[cols],
1035
+ use_container_width=True, hide_index=True,
1036
+ column_config={
1037
+ "Date": st.column_config.DateColumn(format="YYYY-MM-DD"),
1038
+ "Revenue": st.column_config.NumberColumn(format="ΰΈΏ%.0f"),
1039
+ "Customers": st.column_config.NumberColumn(format="%.0f"),
1040
+ "Rev_Per_Head": st.column_config.NumberColumn("Rev / Head", format="ΰΈΏ%.0f"),
1041
+ "ItemRevenue": st.column_config.NumberColumn("Item Revenue", format="ΰΈΏ%.0f"),
1042
+ "ItemQty": st.column_config.NumberColumn("Items Sold", format="%.0f"),
1043
+ },
1044
+ )
1045
+ else:
1046
+ st.caption("No daily rows in this filter window.")
1047
+
1048
+ _render_restaurant_summary("Copper Buffet")
1049
+ st.divider()
1050
+ _render_restaurant_summary("Tiew Copper")
1051
 
1052
  # ── P&L ─────────────────────────────────────────────────────────────────────
1053
  with tab_pl:
 
1155
  fig.update_xaxes(tickformat=",.0f")
1156
  st.plotly_chart(style_plotly(fig, height=380), use_container_width=True)
1157