sukiboo commited on
Commit
00a80b6
·
1 Parent(s): 7d72039

add top products section

Browse files
Files changed (1) hide show
  1. app.py +69 -8
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import zipfile
2
  from pathlib import Path
3
 
@@ -19,6 +20,21 @@ SMA_COLOR = "#ff4b4b"
19
  SMA_LINE_WIDTH = 4
20
  TOOLTIP_FONT_SIZE = 16
21
  TARGET_X_TICKS = 24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
 
24
  @st.cache_data
@@ -85,9 +101,9 @@ items_slot = c4.empty()
85
 
86
  chart_slot = st.empty()
87
 
88
- month_options = [d.strftime("%Y-%m") for d in full_idx]
89
- default_start_month = pd.Timestamp(default_start).to_period("M").strftime("%Y-%m")
90
- default_end_month = pd.Timestamp(max_date).to_period("M").strftime("%Y-%m")
91
 
92
  start_label, end_label = st.select_slider(
93
  "Date range",
@@ -124,14 +140,14 @@ sma_v = sma.loc[(sma.index >= start_month) & (sma.index <= end_month)]
124
  # categorical — otherwise the 28–31-day variation between month-starts shows
125
  # up as uneven gaps on a continuous datetime axis.
126
  monthly = full_net.loc[start_month:end_month].rename_axis("Month").reset_index(name="Net")
127
- monthly["Label"] = monthly["Month"].dt.strftime("%b %Y")
128
  sma_df = sma_v.rename_axis("Month").reset_index(name="Avg")
129
- sma_df["Label"] = sma_df["Month"].dt.strftime("%b %Y")
130
 
131
  fig = px.bar(monthly, x="Label", y="Net", custom_data=["Month"])
132
  fig.update_traces(
133
  marker_color=BAR_COLOR,
134
- hovertemplate="<b>$%{y:,.2f}</b><br>%{customdata[0]|%B %Y}<extra></extra>",
135
  )
136
  fig.add_scatter(
137
  x=sma_df["Label"],
@@ -141,7 +157,7 @@ fig.add_scatter(
141
  line=dict(color=SMA_COLOR, width=SMA_LINE_WIDTH),
142
  customdata=sma_df[["Month"]],
143
  hovertemplate=(
144
- f"<b>$%{{y:,.2f}}</b><br>%{{customdata[0]|%B %Y}}<br>"
145
  f"{SMA_WINDOW_MONTHS}-mo average<extra></extra>"
146
  ),
147
  )
@@ -151,7 +167,7 @@ tick_angle = -45 if len(tick_labels) > 12 else 0
151
  fig.update_layout(
152
  xaxis_title=None,
153
  yaxis_title="USD",
154
- margin=dict(l=0, r=0, t=10, b=0),
155
  showlegend=False,
156
  hoverlabel=dict(font_size=TOOLTIP_FONT_SIZE),
157
  )
@@ -161,3 +177,48 @@ fig.update_xaxes(
161
  tickangle=tick_angle,
162
  )
163
  chart_slot.plotly_chart(fig, width="stretch")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import textwrap
2
  import zipfile
3
  from pathlib import Path
4
 
 
20
  SMA_LINE_WIDTH = 4
21
  TOOLTIP_FONT_SIZE = 16
22
  TARGET_X_TICKS = 24
23
+ CHART_MARGIN = dict(l=0, r=0, t=10, b=0)
24
+ MONTH_KEY_FORMAT = "%Y-%m"
25
+ MONTH_LABEL_FORMAT = "%b %Y"
26
+ MONTH_FULL_FORMAT = "%B %Y"
27
+ DATE_LONG_FORMAT = "%d %B %Y"
28
+
29
+ TOP_N_PRODUCTS = 20
30
+ PRODUCT_LABEL_MAX = 32
31
+ PRODUCT_WRAP_WIDTH = 64
32
+ PRODUCT_ROW_HEIGHT = 24
33
+ PRODUCT_CHART_PAD = 60
34
+ PRODUCT_BAR_COLOR = "#a78bfa"
35
+ PRODUCT_BAR_GAP = 0.6
36
+ PRODUCT_LABEL_FONT_SIZE = 14
37
+ PRODUCT_TOOLTIP_FONT_SIZE = 16
38
 
39
 
40
  @st.cache_data
 
101
 
102
  chart_slot = st.empty()
103
 
104
+ month_options = [d.strftime(MONTH_KEY_FORMAT) for d in full_idx]
105
+ default_start_month = pd.Timestamp(default_start).to_period("M").strftime(MONTH_KEY_FORMAT)
106
+ default_end_month = pd.Timestamp(max_date).to_period("M").strftime(MONTH_KEY_FORMAT)
107
 
108
  start_label, end_label = st.select_slider(
109
  "Date range",
 
140
  # categorical — otherwise the 28–31-day variation between month-starts shows
141
  # up as uneven gaps on a continuous datetime axis.
142
  monthly = full_net.loc[start_month:end_month].rename_axis("Month").reset_index(name="Net")
143
+ monthly["Label"] = monthly["Month"].dt.strftime(MONTH_LABEL_FORMAT)
144
  sma_df = sma_v.rename_axis("Month").reset_index(name="Avg")
145
+ sma_df["Label"] = sma_df["Month"].dt.strftime(MONTH_LABEL_FORMAT)
146
 
147
  fig = px.bar(monthly, x="Label", y="Net", custom_data=["Month"])
148
  fig.update_traces(
149
  marker_color=BAR_COLOR,
150
+ hovertemplate=(f"<b>$%{{y:,.2f}}</b><br>%{{customdata[0]|{MONTH_FULL_FORMAT}}}<extra></extra>"),
151
  )
152
  fig.add_scatter(
153
  x=sma_df["Label"],
 
157
  line=dict(color=SMA_COLOR, width=SMA_LINE_WIDTH),
158
  customdata=sma_df[["Month"]],
159
  hovertemplate=(
160
+ f"<b>$%{{y:,.2f}}</b><br>%{{customdata[0]|{MONTH_FULL_FORMAT}}}<br>"
161
  f"{SMA_WINDOW_MONTHS}-mo average<extra></extra>"
162
  ),
163
  )
 
167
  fig.update_layout(
168
  xaxis_title=None,
169
  yaxis_title="USD",
170
+ margin=CHART_MARGIN,
171
  showlegend=False,
172
  hoverlabel=dict(font_size=TOOLTIP_FONT_SIZE),
173
  )
 
177
  tickangle=tick_angle,
178
  )
179
  chart_slot.plotly_chart(fig, width="stretch")
180
+
181
+ # Top products: gross spend per ASIN over the selected range. Refunds aren't
182
+ # netted here because Refund Details.csv has Order ID but no ASIN, so refunds
183
+ # on multi-item orders can't be cleanly attributed to a product.
184
+ with st.expander("Top products", expanded=False):
185
+ top = (
186
+ orders_v.groupby("ASIN", as_index=False)
187
+ .agg(
188
+ Spent=("Total Amount", "sum"),
189
+ Product=("Product Name", "last"),
190
+ LastDate=("Order Date", "max"),
191
+ )
192
+ .nlargest(TOP_N_PRODUCTS, "Spent")
193
+ .iloc[::-1]
194
+ )
195
+ top["Label"] = top["Product"].where(
196
+ top["Product"].str.len() <= PRODUCT_LABEL_MAX,
197
+ top["Product"].str.slice(0, PRODUCT_LABEL_MAX - 1) + "…",
198
+ )
199
+ top["Wrapped"] = top["Product"].apply(
200
+ lambda p: textwrap.fill(p, width=PRODUCT_WRAP_WIDTH).replace("\n", "<br>")
201
+ )
202
+ top["DateFmt"] = top["LastDate"].dt.strftime(DATE_LONG_FORMAT)
203
+ top_fig = px.bar(top, x="Spent", y="ASIN", orientation="h", custom_data=["Wrapped", "DateFmt"])
204
+ top_fig.update_traces(
205
+ marker_color=PRODUCT_BAR_COLOR,
206
+ hovertemplate=(
207
+ "<b>$%{x:,.2f}</b>" "<br><i>%{customdata[1]}</i>" "<br>%{customdata[0]}<extra></extra>"
208
+ ),
209
+ )
210
+ top_fig.update_layout(
211
+ xaxis_title="USD",
212
+ yaxis_title=None,
213
+ margin=CHART_MARGIN,
214
+ hoverlabel=dict(font_size=PRODUCT_TOOLTIP_FONT_SIZE, align="left"),
215
+ height=len(top) * PRODUCT_ROW_HEIGHT + PRODUCT_CHART_PAD,
216
+ bargap=PRODUCT_BAR_GAP,
217
+ )
218
+ top_fig.update_yaxes(
219
+ tickmode="array",
220
+ tickvals=top["ASIN"],
221
+ ticktext=top["Label"],
222
+ tickfont=dict(size=PRODUCT_LABEL_FONT_SIZE),
223
+ )
224
+ st.plotly_chart(top_fig, width="stretch")