marcellobeer/usage-abc
0
1import streamlit as st2import pandas as pd3import altair as alt4from typing import Tuple5 6# ----------------------------------------------------------------------------7# Session-state keys for each dataset8# ----------------------------------------------------------------------------9if "df_tokens" not in st.session_state:10 st.session_state["df_tokens"] = None11 12if "df_images" not in st.session_state:13 st.session_state["df_images"] = None14 15# NEW: store the Meetings dataset16if "df_meetings" not in st.session_state:17 st.session_state["df_meetings"] = None18 19# ----------------------------------------------------------------------------20# Classification function (6 thresholds)21# ----------------------------------------------------------------------------22def classify_new(cum_dist: float) -> str:23 if cum_dist <= 0.20:24 return "<= 20%"25 elif cum_dist <= 0.40:26 return "<= 40%"27 elif cum_dist <= 0.60:28 return "<= 60%"29 elif cum_dist <= 0.80:30 return "A (<= 80%)"31 elif cum_dist <= 0.95:32 return "B (<= 95%)"33 else:34 return "C (<= 100%)"35 36# ----------------------------------------------------------------------------37# Generic ABC chart generator38# ----------------------------------------------------------------------------39def generate_abc_chart(df: pd.DataFrame, usage_col: str, title: str) -> Tuple[alt.Chart, pd.DataFrame]:40 """41 Expects a DataFrame with columns: [email, <usage_col>].42 usage_col: 'total_tokens', 'total_images', 'total_minutes', 'num_recordings', etc.43 44 Returns:45 - alt.Chart object46 - processed DataFrame with columns: cumulative_distribution, category, row_rank, ...47 """48 usage_per_user = df.groupby("email", as_index=False)[usage_col].sum()49 50 # If there's no data, return an empty chart and DataFrame51 if usage_per_user.empty:52 return alt.Chart(pd.DataFrame()).mark_line(), usage_per_user53 54 # Sort descending55 usage_per_user = usage_per_user.sort_values(usage_col, ascending=False).reset_index(drop=True)56 usage_per_user["overall_sum"] = usage_per_user[usage_col].sum()57 usage_per_user["running_sum"] = usage_per_user[usage_col].cumsum()58 usage_per_user["cumulative_distribution"] = usage_per_user["running_sum"] / usage_per_user["overall_sum"]59 usage_per_user["category"] = usage_per_user["cumulative_distribution"].apply(classify_new)60 usage_per_user["row_rank"] = usage_per_user.index + 161 62 # Base line chart63 base_chart = (64 alt.Chart(usage_per_user)65 .mark_line(point=True)66 .encode(67 x=alt.X("row_rank:Q", title="User Rank"),68 y=alt.Y("cumulative_distribution:Q", title=f"Cumulative % of {usage_col}"),69 tooltip=[70 alt.Tooltip("email", title="User Email"),71 alt.Tooltip(usage_col, title=f"Total {usage_col}"),72 alt.Tooltip("row_rank", title="Rank"),73 alt.Tooltip("cumulative_distribution", title="Cumulative %", format=".2%"),74 alt.Tooltip("category", title="Category"),75 ],76 )77 )78 79 # Vertical lines for thresholds80 threshold_values = [81 (0.20, "20%"),82 (0.40, "40%"),83 (0.60, "60%"),84 (0.80, "80% (A)"),85 (0.95, "95% (B)"),86 ]87 thresholds_data = []88 for val, label in threshold_values:89 # Find the rank at which cumulative_distribution crosses `val`90 rank = usage_per_user.loc[usage_per_user["cumulative_distribution"] > val, "row_rank"].min()91 if pd.isnull(rank):92 rank = usage_per_user["row_rank"].max()93 thresholds_data.append({"x": rank, "label": label})94 95 thresholds_df = pd.DataFrame(thresholds_data)96 rules_layer = alt.Chart(thresholds_df).mark_rule(color="red", strokeDash=[5, 3]).encode(97 x="x:Q", tooltip=[alt.Tooltip("label", title="Boundary")]98 )99 100 final_chart = (base_chart + rules_layer).properties(101 width=700, height=400, title=title102 )103 104 return final_chart, usage_per_user105 106# ----------------------------------------------------------------------------107# Helper to subset by model_type (used in tokens/images pages)108# ----------------------------------------------------------------------------109def subset_by_model(df: pd.DataFrame, model_type: str):110 # case-insensitive filter111 return df[df["model_type"].str.lower() == model_type.lower()]112 113# ----------------------------------------------------------------------------114# Page: Token Usage Analysis (with Deep)115# ----------------------------------------------------------------------------116def token_usage_page():117 st.header("Chat Token Usage")118 119 # Upload a CSV for token usage120 uploaded_file = st.file_uploader(121 "Upload CSV:",122 type=["csv"],123 key="tokens_file_uploader"124 )125 126 if uploaded_file is not None:127 df_new = pd.read_csv(uploaded_file)128 st.session_state["df_tokens"] = df_new # store in session state129 130 # Retrieve from session state131 df_tokens = st.session_state["df_tokens"]132 133 if df_tokens is None:134 st.info("Please upload a CSV to analyze token usage.")135 return136 137 # Process data138 df_tokens["date"] = pd.to_datetime(df_tokens["date"], errors="coerce")139 df_tokens["month_str"] = df_tokens["date"].dt.strftime("%Y-%m")140 df_tokens["total_tokens"] = df_tokens["input_tokens"] + df_tokens["output_tokens"]141 142 all_months = sorted(df_tokens["month_str"].dropna().unique())143 144 def show_chart_and_summary(dataframe, month_label, model_filter=None):145 if model_filter:146 filtered_df = subset_by_model(dataframe, model_filter)147 title = f"{month_label} – {model_filter.capitalize()} Model"148 else:149 filtered_df = dataframe150 title = f"{month_label} – All Models"151 152 chart, processed_df = generate_abc_chart(filtered_df[["email","total_tokens"]], "total_tokens", title)153 if processed_df.empty:154 st.info(f"No data for {title}.")155 return156 157 st.altair_chart(chart, use_container_width=True)158 159 # Summarize160 category_groups = (161 processed_df162 .groupby("category", as_index=False)163 .agg(num_users=("email","count"), sum_tokens=("total_tokens","sum"))164 )165 166 total_users = processed_df["email"].nunique()167 total_tokens_sum = processed_df["total_tokens"].sum()168 category_groups["pct_users"] = category_groups["num_users"] / total_users * 100169 170 # Reorder & rename columns171 category_groups = category_groups[["category","num_users","pct_users","sum_tokens"]]172 category_groups.columns = ["Category","Total Users","% of Users","Total Tokens"]173 category_groups["% of Users"] = category_groups["% of Users"].round(2)174 category_groups["Total Tokens"] = category_groups["Total Tokens"].apply(lambda x: f"{x:,}")175 176 summary_row = pd.DataFrame([{177 "Category":"Total",178 "Total Users": total_users,179 "% of Users": "-",180 "Total Tokens": f"{total_tokens_sum:,}"181 }])182 category_groups = pd.concat([category_groups, summary_row], ignore_index=True)183 184 st.markdown("###### Summary")185 st.table(category_groups)186 187 # Loop through each month188 for month_str in all_months:189 st.subheader(f"{month_str}")190 191 df_month = df_tokens[df_tokens["month_str"] == month_str]192 193 # 1) All Models194 show_chart_and_summary(df_month, month_str)195 # 2) Fast196 show_chart_and_summary(df_month, month_str, "Fast")197 # 3) Advanced198 show_chart_and_summary(df_month, month_str, "Advanced")199 # 4) Deep200 show_chart_and_summary(df_month, month_str, "Deep")201 202# ----------------------------------------------------------------------------203# Page: Image Usage Analysis (Fast / Advanced only)204# ----------------------------------------------------------------------------205def image_usage_page():206 st.header("Image Generation Usage")207 208 # Upload a CSV for image usage209 uploaded_file = st.file_uploader(210 "Upload CSV:",211 type=["csv"],212 key="images_file_uploader"213 )214 215 if uploaded_file is not None:216 df_new = pd.read_csv(uploaded_file)217 st.session_state["df_images"] = df_new218 219 df_images = st.session_state["df_images"]220 if df_images is None:221 st.info("Please upload a CSV to analyze image usage.")222 return223 224 # Process data225 df_images["date"] = pd.to_datetime(df_images["date"], errors="coerce")226 df_images["month_str"] = df_images["date"].dt.strftime("%Y-%m")227 228 all_months = sorted(df_images["month_str"].dropna().unique())229 230 def show_chart_and_summary(dataframe, month_label, model_filter=None):231 if model_filter:232 filtered_df = subset_by_model(dataframe, model_filter)233 title = f"{month_label} – {model_filter.capitalize()} Model"234 else:235 filtered_df = dataframe236 title = f"{month_label} – All Models"237 238 chart, processed_df = generate_abc_chart(filtered_df[["email","total_images"]], "total_images", title)239 if processed_df.empty:240 st.info(f"No data for {title}.")241 return242 243 st.altair_chart(chart, use_container_width=True)244 245 category_groups = (246 processed_df247 .groupby("category", as_index=False)248 .agg(num_users=("email","count"), sum_images=("total_images","sum"))249 )250 251 total_users = processed_df["email"].nunique()252 total_images_sum = processed_df["total_images"].sum()253 category_groups["pct_users"] = category_groups["num_users"] / total_users * 100254 255 category_groups = category_groups[["category","num_users","pct_users","sum_images"]]256 category_groups.columns = ["Category","Total Users","% of Users","Total Images"]257 category_groups["% of Users"] = category_groups["% of Users"].round(2)258 category_groups["Total Images"] = category_groups["Total Images"].apply(lambda x: f"{x:,}")259 260 summary_row = pd.DataFrame([{261 "Category":"Total",262 "Total Users": total_users,263 "% of Users":"-",264 "Total Images":f"{total_images_sum:,}"265 }])266 category_groups = pd.concat([category_groups, summary_row], ignore_index=True)267 268 st.markdown("###### Summary")269 st.table(category_groups)270 271 # Loop months272 for month_str in all_months:273 st.subheader(f"{month_str}")274 275 df_month = df_images[df_images["month_str"] == month_str]276 277 # 1) All Models278 show_chart_and_summary(df_month, month_str)279 # 2) Fast280 show_chart_and_summary(df_month, month_str, "Fast")281 # 3) Advanced282 show_chart_and_summary(df_month, month_str, "Advanced")283 284# ----------------------------------------------------------------------------285# NEW PAGE: Meetings Usage286# ----------------------------------------------------------------------------287def meetings_usage_page():288 st.header("Meetings Usage")289 290 # Upload a CSV specifically for meetings usage291 uploaded_file = st.file_uploader(292 "Upload CSV (Meetings Data):",293 type=["csv"],294 key="meetings_file_uploader"295 )296 297 if uploaded_file is not None:298 df_new = pd.read_csv(uploaded_file)299 st.session_state["df_meetings"] = df_new300 301 df_meetings = st.session_state["df_meetings"]302 if df_meetings is None:303 st.info("Please upload a CSV to analyze meetings usage.")304 return305 306 # Parse start_time as date, create "month_str"307 df_meetings["start_time"] = pd.to_datetime(df_meetings["start_time"], errors="coerce")308 df_meetings["month_str"] = df_meetings["start_time"].dt.strftime("%Y-%m")309 310 # Group by email + month311 # total_minutes = sum(duration_minutes)312 # num_recordings = distinct count of id313 grouped = (314 df_meetings315 .groupby(["email", "month_str"], as_index=False)316 .agg(317 total_minutes=("duration_minutes", "sum"),318 num_recordings=("id", "nunique")319 )320 )321 322 all_months = sorted(grouped["month_str"].dropna().unique())323 324 def show_chart_and_summary(dataframe, month_label, usage_col, usage_label):325 """326 usage_col: 'total_minutes' or 'num_recordings'327 usage_label: String for chart title, e.g. "Total Minutes" or "Number of Recordings"328 """329 subset = dataframe[["email", usage_col]]330 chart, processed_df = generate_abc_chart(subset, usage_col, f"{month_label} – {usage_label}")331 if processed_df.empty:332 st.info(f"No data for {usage_label} in {month_label}.")333 return334 335 st.altair_chart(chart, use_container_width=True)336 337 # Summarize338 cat_groups = (339 processed_df340 .groupby("category", as_index=False)341 .agg(num_users=("email","count"), sum_usage=(usage_col,"sum"))342 )343 total_users = processed_df["email"].nunique()344 total_usage_sum = processed_df[usage_col].sum()345 cat_groups["pct_users"] = cat_groups["num_users"] / total_users * 100346 347 # We'll rename columns after building everything348 cat_groups["% of Users"] = cat_groups["pct_users"].round(2)349 350 # For the detail column name351 usage_col_label = f"Total {usage_col}"352 cat_groups = cat_groups.rename(columns={353 "category": "Category",354 "num_users": "Total Users",355 "sum_usage": usage_col_label356 })357 358 # If we are looking at num_recordings, add an extra column359 if usage_col == "num_recordings":360 # Compute average recordings per user in each ABC category361 cat_groups["Avg. Recordings per User"] = cat_groups[usage_col_label] / cat_groups["Total Users"]362 # Round to 2 decimals363 cat_groups["Avg. Recordings per User"] = cat_groups["Avg. Recordings per User"].round().astype(int).astype(str).str.replace('.0', '')364 365 # Convert numeric usage to a comma-separated string366 cat_groups[usage_col_label] = cat_groups[usage_col_label].apply(lambda x: f"{x:,}")367 368 # Build a summary row369 summary_data = {370 "Category": "Total",371 "Total Users": total_users,372 "% of Users": "-",373 usage_col_label: f"{total_usage_sum:,}"374 }375 # For the summary row, if we're dealing with num_recordings, add an average column376 if usage_col == "num_recordings":377 avg_recs = 0378 if total_users > 0:379 avg_recs = total_usage_sum / total_users380 summary_data["Avg. Recordings per User"] = round(avg_recs, 2)381 382 summary_row = pd.DataFrame([summary_data])383 cat_groups = pd.concat([cat_groups, summary_row], ignore_index=True)384 385 st.markdown("###### Summary")386 st.table(cat_groups)387 388 # For each month, we show 2 ABC charts:389 # 1) total_minutes390 # 2) num_recordings391 for month_str in all_months:392 st.subheader(f"{month_str}")393 df_month = grouped[grouped["month_str"] == month_str]394 395 # Chart for total_minutes396 show_chart_and_summary(df_month, month_str, "total_minutes", "Total Minutes")397 398 # Chart for num_recordings (includes Avg. Recordings per User)399 show_chart_and_summary(df_month, month_str, "num_recordings", "Number of Recordings")400 401# ----------------------------------------------------------------------------402# Main: choose which "page" to show403# ----------------------------------------------------------------------------404def main():405 st.title("ABC Analysis")406 407 page = st.sidebar.radio(408 "Select an analysis page:",409 ("Token Usage", "Image Usage", "Meetings Usage")410 )411 412 if page == "Token Usage":413 token_usage_page()414 elif page == "Image Usage":415 image_usage_page()416 else:417 meetings_usage_page()418 419if __name__ == "__main__":420 main()421 