From 3215ef9945fecc923a03337c362c2141e227da64 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Thu, 27 Jun 2024 14:43:12 +0530 Subject: [PATCH] fix test_stats_get_tabular_data_invalid_sorting_options --- bots/models.py | 2 +- bots/tests.py | 25 +- recipes/VideoBotsStats.py | 1014 +++++++++++++++++++------------------ 3 files changed, 529 insertions(+), 512 deletions(-) diff --git a/bots/models.py b/bots/models.py index c53f825b3..9c207bf16 100644 --- a/bots/models.py +++ b/bots/models.py @@ -1124,7 +1124,7 @@ def to_df_analysis_format( "Question (Local)": message.get_previous_by_created_at().display_content, "Answer (Local)": message.display_content, "Analysis JSON": message.analysis_result, - "Run URL": message.saved_run.get_app_url(), + "Run URL": (message.saved_run and message.saved_run.get_app_url()), } rows.append(row) df = pd.DataFrame.from_records( diff --git a/bots/tests.py b/bots/tests.py index 9c7159b2e..ccda40425 100644 --- a/bots/tests.py +++ b/bots/tests.py @@ -3,6 +3,7 @@ from app_users.models import AppUser from daras_ai_v2.functional import map_parallel +from recipes.VideoBotsStats import get_tabular_data from .models import ( Message, Conversation, @@ -115,8 +116,12 @@ def test_stats_get_tabular_data_invalid_sorting_options(transactional_db): msgs = Message.objects.filter(conversation__in=convos) # valid option but no data - df = page.get_tabular_data( - bi, run_url, convos, msgs, "Answered Successfully", "Name" + df = get_tabular_data( + bi=bi, + conversations=convos, + messages=msgs, + details="Answered Successfully", + sort_by="Name", ) assert df.shape[0] == 0 assert "Name" in df.columns @@ -143,15 +148,23 @@ def test_stats_get_tabular_data_invalid_sorting_options(transactional_db): convos = Conversation.objects.filter(bot_integration=bi) msgs = Message.objects.filter(conversation__in=convos) assert msgs.count() == 2 - df = page.get_tabular_data( - bi, run_url, convos, msgs, "Answered Successfully", "Name" + df = get_tabular_data( + bi=bi, + conversations=convos, + messages=msgs, + details="Answered Successfully", + sort_by="Name", ) assert df.shape[0] == 1 assert "Name" in df.columns # invalid sort option should be ignored - df = page.get_tabular_data( - bi, run_url, convos, msgs, "Answered Successfully", "Invalid" + df = get_tabular_data( + bi=bi, + conversations=convos, + messages=msgs, + details="Answered Successfully", + sort_by="Invalid", ) assert df.shape[0] == 1 assert "Name" in df.columns diff --git a/recipes/VideoBotsStats.py b/recipes/VideoBotsStats.py index f871deff4..a550c368e 100644 --- a/recipes/VideoBotsStats.py +++ b/recipes/VideoBotsStats.py @@ -175,8 +175,8 @@ def render(self): col1, col2 = st.columns([1, 2]) with col1: - conversations, messages = self.calculate_overall_stats( - bi, run_title, run_url + conversations, messages = calculate_overall_stats( + bi=bi, run_title=run_title, run_url=run_url ) ( @@ -187,8 +187,12 @@ def render(self): trunc_fn, ) = self.render_date_view_inputs(bi) - df = self.calculate_stats_binned_by_time( - bi, start_date, end_date, factor, trunc_fn + df = calculate_stats_binned_by_time( + bi=bi, + start_date=start_date, + end_date=end_date, + factor=factor, + trunc_fn=trunc_fn, ) if df.empty or "date" not in df.columns: @@ -203,7 +207,7 @@ def render(self): return with col2: - self.plot_graphs(view, df) + plot_graphs(view, df) st.write("---") st.session_state.setdefault("details", self.request.query_params.get("details")) @@ -274,12 +278,12 @@ def render(self): ) sort_by = st.session_state["sort_by"] - df = self.get_tabular_data( - bi, - conversations, - messages, - details, - sort_by, + df = get_tabular_data( + bi=bi, + conversations=conversations, + messages=messages, + details=details, + sort_by=sort_by, rows=500, start_date=start_date, end_date=end_date, @@ -304,12 +308,12 @@ def render(self): # download as csv button st.html("
") if st.checkbox("Export"): - df = self.get_tabular_data( - bi, - conversations, - messages, - details, - sort_by, + df = get_tabular_data( + bi=bi, + conversations=conversations, + messages=messages, + details=details, + sort_by=sort_by, start_date=start_date, end_date=end_date, ) @@ -399,520 +403,520 @@ def render_date_view_inputs(self, bi): trunc_fn = TruncYear return start_date, end_date, view, factor, trunc_fn - def calculate_overall_stats(self, bi, run_title, run_url): - conversations: ConversationQuerySet = Conversation.objects.filter( - bot_integration=bi - ).order_by() # type: ignore - # due to things like personal convos for slack, each user can have multiple conversations - users = conversations.get_unique_users().order_by() - messages: MessageQuerySet = Message.objects.filter(conversation__in=conversations).order_by() # type: ignore - user_messages = messages.filter(role=CHATML_ROLE_USER).order_by() - bot_messages = messages.filter(role=CHATML_ROLE_ASSISTANT).order_by() - num_active_users_last_7_days = ( - user_messages.filter( - conversation__in=users, - created_at__gte=timezone.now() - timedelta(days=7), - ) - .distinct( - *ID_COLUMNS, - ) - .count() + +def calculate_overall_stats(*, bi, run_title, run_url): + conversations: ConversationQuerySet = Conversation.objects.filter( + bot_integration=bi + ).order_by() # type: ignore + # due to things like personal convos for slack, each user can have multiple conversations + users = conversations.get_unique_users().order_by() + messages: MessageQuerySet = Message.objects.filter(conversation__in=conversations).order_by() # type: ignore + user_messages = messages.filter(role=CHATML_ROLE_USER).order_by() + bot_messages = messages.filter(role=CHATML_ROLE_ASSISTANT).order_by() + num_active_users_last_7_days = ( + user_messages.filter( + conversation__in=users, + created_at__gte=timezone.now() - timedelta(days=7), ) - num_active_users_last_30_days = ( - user_messages.filter( - conversation__in=users, - created_at__gte=timezone.now() - timedelta(days=30), - ) - .distinct( - *ID_COLUMNS, - ) - .count() + .distinct( + *ID_COLUMNS, ) - positive_feedbacks = Feedback.objects.filter( - message__conversation__bot_integration=bi, - rating=Feedback.Rating.RATING_THUMBS_UP, - ).count() - negative_feedbacks = Feedback.objects.filter( - message__conversation__bot_integration=bi, - rating=Feedback.Rating.RATING_THUMBS_DOWN, - ).count() - run_link = f'Powered By: {run_title}' - connection_detail = bi.get_display_name() - st.markdown( - f""" - - Platform: {Platform(bi.platform).name.capitalize()} - - Created on: {bi.created_at.strftime("%b %d, %Y")} - - Last Updated: {bi.updated_at.strftime("%b %d, %Y")} - - {run_link} - - Connected to: {connection_detail} - * {users.count()} Users - * {num_active_users_last_7_days} Active Users (Last 7 Days) - * {num_active_users_last_30_days} Active Users (Last 30 Days) - * {conversations.count()} Conversations - * {user_messages.count()} User Messages - * {bot_messages.count()} Bot Messages - * {messages.count()} Total Messages - * {positive_feedbacks} Positive Feedbacks - * {negative_feedbacks} Negative Feedbacks - """, - unsafe_allow_html=True, + .count() + ) + num_active_users_last_30_days = ( + user_messages.filter( + conversation__in=users, + created_at__gte=timezone.now() - timedelta(days=30), ) - - return conversations, messages - - def calculate_stats_binned_by_time( - self, bi, start_date, end_date, factor, trunc_fn - ): - import pandas as pd - - messages_received = ( - Message.objects.filter( - created_at__date__gte=start_date, - created_at__date__lte=end_date, - conversation__bot_integration=bi, - role=CHATML_ROLE_ASSISTANT, - ) - .order_by() - .annotate(date=trunc_fn("created_at")) - .values("date") - .annotate(Messages_Sent=Count("id")) - .annotate(Convos=Count("conversation_id", distinct=True)) - .annotate( - Senders=Count( - Concat( - *ID_COLUMNS, - ), - distinct=True, - ) - ) - .annotate(Average_runtime=Avg("saved_run__run_time")) - .annotate(Unique_feedback_givers=Count("feedbacks", distinct=True)) - .values( - "date", - "Messages_Sent", - "Convos", - "Senders", - "Unique_feedback_givers", - "Average_runtime", - ) + .distinct( + *ID_COLUMNS, ) - - positive_feedbacks = ( - Feedback.objects.filter( - created_at__date__gte=start_date, - created_at__date__lte=end_date, - message__conversation__bot_integration=bi, - rating=Feedback.Rating.RATING_THUMBS_UP, - ) - .order_by() - .annotate(date=trunc_fn("created_at")) - .values("date") - .annotate(Pos_feedback=Count("id")) - .values("date", "Pos_feedback") + .count() + ) + positive_feedbacks = Feedback.objects.filter( + message__conversation__bot_integration=bi, + rating=Feedback.Rating.RATING_THUMBS_UP, + ).count() + negative_feedbacks = Feedback.objects.filter( + message__conversation__bot_integration=bi, + rating=Feedback.Rating.RATING_THUMBS_DOWN, + ).count() + run_link = f'Powered By: {run_title}' + connection_detail = bi.get_display_name() + st.markdown( + f""" + - Platform: {Platform(bi.platform).name.capitalize()} + - Created on: {bi.created_at.strftime("%b %d, %Y")} + - Last Updated: {bi.updated_at.strftime("%b %d, %Y")} + - {run_link} + - Connected to: {connection_detail} + * {users.count()} Users + * {num_active_users_last_7_days} Active Users (Last 7 Days) + * {num_active_users_last_30_days} Active Users (Last 30 Days) + * {conversations.count()} Conversations + * {user_messages.count()} User Messages + * {bot_messages.count()} Bot Messages + * {messages.count()} Total Messages + * {positive_feedbacks} Positive Feedbacks + * {negative_feedbacks} Negative Feedbacks + """, + unsafe_allow_html=True, + ) + + return conversations, messages + + +def calculate_stats_binned_by_time(*, bi, start_date, end_date, factor, trunc_fn): + import pandas as pd + + messages_received = ( + Message.objects.filter( + created_at__date__gte=start_date, + created_at__date__lte=end_date, + conversation__bot_integration=bi, + role=CHATML_ROLE_ASSISTANT, ) - - negative_feedbacks = ( - Feedback.objects.filter( - created_at__date__gte=start_date, - created_at__date__lte=end_date, - message__conversation__bot_integration=bi, - rating=Feedback.Rating.RATING_THUMBS_DOWN, + .order_by() + .annotate(date=trunc_fn("created_at")) + .values("date") + .annotate(Messages_Sent=Count("id")) + .annotate(Convos=Count("conversation_id", distinct=True)) + .annotate( + Senders=Count( + Concat( + *ID_COLUMNS, + ), + distinct=True, ) - .order_by() - .annotate(date=trunc_fn("created_at")) - .values("date") - .annotate(Neg_feedback=Count("id")) - .values("date", "Neg_feedback") ) - - successfully_answered = ( - Message.objects.filter( - conversation__bot_integration=bi, - created_at__date__gte=start_date, - created_at__date__lte=end_date, - ) - .filter( - Q(analysis_result__contains={"Answered": True}) - | Q(analysis_result__contains={"assistant": {"answer": "Found"}}), - ) - .order_by() - .annotate(date=trunc_fn("created_at")) - .values("date") - .annotate(Successfully_Answered=Count("id")) - .values("date", "Successfully_Answered") + .annotate(Average_runtime=Avg("saved_run__run_time")) + .annotate(Unique_feedback_givers=Count("feedbacks", distinct=True)) + .values( + "date", + "Messages_Sent", + "Convos", + "Senders", + "Unique_feedback_givers", + "Average_runtime", ) + ) - df = pd.DataFrame( - messages_received, - columns=[ - "date", - "Messages_Sent", - "Convos", - "Senders", - "Unique_feedback_givers", - "Average_runtime", - ], - ) - df = df.merge( - pd.DataFrame(positive_feedbacks, columns=["date", "Pos_feedback"]), - how="outer", - left_on="date", - right_on="date", - ) - df = df.merge( - pd.DataFrame(negative_feedbacks, columns=["date", "Neg_feedback"]), - how="outer", - left_on="date", - right_on="date", + positive_feedbacks = ( + Feedback.objects.filter( + created_at__date__gte=start_date, + created_at__date__lte=end_date, + message__conversation__bot_integration=bi, + rating=Feedback.Rating.RATING_THUMBS_UP, ) - df = df.merge( - pd.DataFrame( - successfully_answered, columns=["date", "Successfully_Answered"] - ), - how="outer", - left_on="date", - right_on="date", + .order_by() + .annotate(date=trunc_fn("created_at")) + .values("date") + .annotate(Pos_feedback=Count("id")) + .values("date", "Pos_feedback") + ) + + negative_feedbacks = ( + Feedback.objects.filter( + created_at__date__gte=start_date, + created_at__date__lte=end_date, + message__conversation__bot_integration=bi, + rating=Feedback.Rating.RATING_THUMBS_DOWN, ) - df["Messages_Sent"] = df["Messages_Sent"] * factor - df["Convos"] = df["Convos"] * factor - df["Senders"] = df["Senders"] * factor - df["Unique_feedback_givers"] = df["Unique_feedback_givers"] * factor - df["Pos_feedback"] = df["Pos_feedback"] * factor - df["Neg_feedback"] = df["Neg_feedback"] * factor - df["Percentage_positive_feedback"] = ( - df["Pos_feedback"] / df["Messages_Sent"] - ) * 100 - df["Percentage_negative_feedback"] = ( - df["Neg_feedback"] / df["Messages_Sent"] - ) * 100 - df["Percentage_successfully_answered"] = ( - df["Successfully_Answered"] / df["Messages_Sent"] - ) * 100 - df["Msgs_per_convo"] = df["Messages_Sent"] / df["Convos"] - df["Msgs_per_user"] = df["Messages_Sent"] / df["Senders"] - df.fillna(0, inplace=True) - df = df.round(0).astype("int32", errors="ignore") - df = df.sort_values(by=["date"], ascending=True).reset_index() - return df - - def plot_graphs(self, view, df): - import plotly.graph_objects as go - - dateformat = "%B %Y" if view == "Monthly" else "%x" - xaxis_ticks = dict( - tickmode="array", - tickvals=list(df["date"]), - ticktext=list(df["date"].apply(lambda x: x.strftime(dateformat))), + .order_by() + .annotate(date=trunc_fn("created_at")) + .values("date") + .annotate(Neg_feedback=Count("id")) + .values("date", "Neg_feedback") + ) + + successfully_answered = ( + Message.objects.filter( + conversation__bot_integration=bi, + created_at__date__gte=start_date, + created_at__date__lte=end_date, ) - - fig = go.Figure( - data=[ - go.Bar( - x=list(df["date"]), - y=list(df["Messages_Sent"]), - text=list(df["Messages_Sent"]), - texttemplate="%{text}", - insidetextanchor="middle", - insidetextfont=dict(size=24), - hovertemplate="Messages Sent: %{y:.0f}", - ), - ], - layout=dict( - margin=dict(l=0, r=0, t=28, b=0), - yaxis=dict( - title="User Messages Sent", - range=[ - 0, - df["Messages_Sent"].max() + 1, - ], - tickvals=[ - *range( - int(df["Messages_Sent"].max() / 10), - int(df["Messages_Sent"].max()) + 1, - int(df["Messages_Sent"].max() / 10) + 1, - ) - ], - ), - xaxis=xaxis_ticks, - title=dict( - text=f"{view} Messages Sent", - ), - height=300, - template="plotly_white", - ), + .filter( + Q(analysis_result__contains={"Answered": True}) + | Q(analysis_result__contains={"assistant": {"answer": "Found"}}), ) - st.plotly_chart(fig) - st.write("---") - fig = go.Figure( - data=[ - go.Scatter( - name="Senders", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Senders"]), - text=list(df["Senders"]), - hovertemplate="Active Users: %{y:.0f}", - ), - go.Scatter( - name="Conversations", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Convos"]), - text=list(df["Convos"]), - hovertemplate="Conversations: %{y:.0f}", - ), - go.Scatter( - name="Feedback Givers", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Unique_feedback_givers"]), - text=list(df["Unique_feedback_givers"]), - hovertemplate="Feedback Givers: %{y:.0f}", - ), - go.Scatter( - name="Positive Feedbacks", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Pos_feedback"]), - text=list(df["Pos_feedback"]), - hovertemplate="Positive Feedbacks: %{y:.0f}", - ), - go.Scatter( - name="Negative Feedbacks", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Neg_feedback"]), - text=list(df["Neg_feedback"]), - hovertemplate="Negative Feedbacks: %{y:.0f}", - ), - go.Scatter( - name="Messages per Convo", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Msgs_per_convo"]), - text=list(df["Msgs_per_convo"]), - hovertemplate="Messages per Convo: %{y:.0f}", - ), - go.Scatter( - name="Messages per Sender", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Msgs_per_user"]), - text=list(df["Msgs_per_user"]), - hovertemplate="Messages per User: %{y:.0f}", - ), - ], - layout=dict( - margin=dict(l=0, r=0, t=28, b=0), - yaxis=dict( - title="Unique Count", - ), - title=dict( - text=f"{view} Usage Trends", - ), - xaxis=xaxis_ticks, - height=300, - template="plotly_white", + .order_by() + .annotate(date=trunc_fn("created_at")) + .values("date") + .annotate(Successfully_Answered=Count("id")) + .values("date", "Successfully_Answered") + ) + + df = pd.DataFrame( + messages_received, + columns=[ + "date", + "Messages_Sent", + "Convos", + "Senders", + "Unique_feedback_givers", + "Average_runtime", + ], + ) + df = df.merge( + pd.DataFrame(positive_feedbacks, columns=["date", "Pos_feedback"]), + how="outer", + left_on="date", + right_on="date", + ) + df = df.merge( + pd.DataFrame(negative_feedbacks, columns=["date", "Neg_feedback"]), + how="outer", + left_on="date", + right_on="date", + ) + df = df.merge( + pd.DataFrame(successfully_answered, columns=["date", "Successfully_Answered"]), + how="outer", + left_on="date", + right_on="date", + ) + df["Messages_Sent"] = df["Messages_Sent"] * factor + df["Convos"] = df["Convos"] * factor + df["Senders"] = df["Senders"] * factor + df["Unique_feedback_givers"] = df["Unique_feedback_givers"] * factor + df["Pos_feedback"] = df["Pos_feedback"] * factor + df["Neg_feedback"] = df["Neg_feedback"] * factor + df["Percentage_positive_feedback"] = ( + df["Pos_feedback"] / df["Messages_Sent"] + ) * 100 + df["Percentage_negative_feedback"] = ( + df["Neg_feedback"] / df["Messages_Sent"] + ) * 100 + df["Percentage_successfully_answered"] = ( + df["Successfully_Answered"] / df["Messages_Sent"] + ) * 100 + df["Msgs_per_convo"] = df["Messages_Sent"] / df["Convos"] + df["Msgs_per_user"] = df["Messages_Sent"] / df["Senders"] + df.fillna(0, inplace=True) + df = df.round(0).astype("int32", errors="ignore") + df = df.sort_values(by=["date"], ascending=True).reset_index() + return df + + +def plot_graphs(view, df): + import plotly.graph_objects as go + + dateformat = "%B %Y" if view == "Monthly" else "%x" + xaxis_ticks = dict( + tickmode="array", + tickvals=list(df["date"]), + ticktext=list(df["date"].apply(lambda x: x.strftime(dateformat))), + ) + + fig = go.Figure( + data=[ + go.Bar( + x=list(df["date"]), + y=list(df["Messages_Sent"]), + text=list(df["Messages_Sent"]), + texttemplate="%{text}", + insidetextanchor="middle", + insidetextfont=dict(size=24), + hovertemplate="Messages Sent: %{y:.0f}", ), - ) - max_value = ( - df[ - [ - "Senders", - "Convos", - "Unique_feedback_givers", - "Pos_feedback", - "Neg_feedback", - "Msgs_per_convo", - "Msgs_per_user", - ] - ] - .max() - .max() - ) - if max_value < 10: - # only set fixed axis scale if the data doesn't have enough values to make it look good - # otherwise default behaviour is better since it adapts when user deselects a line - fig.update_yaxes( + ], + layout=dict( + margin=dict(l=0, r=0, t=28, b=0), + yaxis=dict( + title="User Messages Sent", range=[ - -0.2, - max_value + 10, + 0, + df["Messages_Sent"].max() + 1, ], tickvals=[ *range( - int(max_value / 10), - int(max_value) + 10, - int(max_value / 10) + 1, + int(df["Messages_Sent"].max() / 10), + int(df["Messages_Sent"].max()) + 1, + int(df["Messages_Sent"].max() / 10) + 1, ) ], - ) - st.plotly_chart(fig) - st.write("---") - fig = go.Figure( - data=[ - go.Scatter( - name="Average Run Time", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Average_runtime"]), - text=list(df["Average_runtime"]), - hovertemplate="Average Runtime: %{y:.0f}", - ), - ], - layout=dict( - margin=dict(l=0, r=0, t=28, b=0), - yaxis=dict( - title="Seconds", - ), - title=dict( - text=f"{view} Performance Metrics", - ), - xaxis=xaxis_ticks, - height=300, - template="plotly_white", ), - ) - st.plotly_chart(fig) - st.write("---") - fig = go.Figure( - data=[ - go.Scatter( - name="Positive Feedback", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Percentage_positive_feedback"]), - text=list(df["Percentage_positive_feedback"]), - hovertemplate="Positive Feedback: %{y:.0f}%", - ), - go.Scatter( - name="Negative Feedback", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Percentage_negative_feedback"]), - text=list(df["Percentage_negative_feedback"]), - hovertemplate="Negative Feedback: %{y:.0f}%", - ), - go.Scatter( - name="Successfully Answered", - mode="lines+markers", - x=list(df["date"]), - y=list(df["Percentage_successfully_answered"]), - text=list(df["Percentage_successfully_answered"]), - hovertemplate="Successfully Answered: %{y:.0f}%", - ), + xaxis=xaxis_ticks, + title=dict( + text=f"{view} Messages Sent", + ), + height=300, + template="plotly_white", + ), + ) + st.plotly_chart(fig) + st.write("---") + fig = go.Figure( + data=[ + go.Scatter( + name="Senders", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Senders"]), + text=list(df["Senders"]), + hovertemplate="Active Users: %{y:.0f}", + ), + go.Scatter( + name="Conversations", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Convos"]), + text=list(df["Convos"]), + hovertemplate="Conversations: %{y:.0f}", + ), + go.Scatter( + name="Feedback Givers", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Unique_feedback_givers"]), + text=list(df["Unique_feedback_givers"]), + hovertemplate="Feedback Givers: %{y:.0f}", + ), + go.Scatter( + name="Positive Feedbacks", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Pos_feedback"]), + text=list(df["Pos_feedback"]), + hovertemplate="Positive Feedbacks: %{y:.0f}", + ), + go.Scatter( + name="Negative Feedbacks", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Neg_feedback"]), + text=list(df["Neg_feedback"]), + hovertemplate="Negative Feedbacks: %{y:.0f}", + ), + go.Scatter( + name="Messages per Convo", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Msgs_per_convo"]), + text=list(df["Msgs_per_convo"]), + hovertemplate="Messages per Convo: %{y:.0f}", + ), + go.Scatter( + name="Messages per Sender", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Msgs_per_user"]), + text=list(df["Msgs_per_user"]), + hovertemplate="Messages per User: %{y:.0f}", + ), + ], + layout=dict( + margin=dict(l=0, r=0, t=28, b=0), + yaxis=dict( + title="Unique Count", + ), + title=dict( + text=f"{view} Usage Trends", + ), + xaxis=xaxis_ticks, + height=300, + template="plotly_white", + ), + ) + max_value = ( + df[ + [ + "Senders", + "Convos", + "Unique_feedback_givers", + "Pos_feedback", + "Neg_feedback", + "Msgs_per_convo", + "Msgs_per_user", + ] + ] + .max() + .max() + ) + if max_value < 10: + # only set fixed axis scale if the data doesn't have enough values to make it look good + # otherwise default behaviour is better since it adapts when user deselects a line + fig.update_yaxes( + range=[ + -0.2, + max_value + 10, ], - layout=dict( - margin=dict(l=0, r=0, t=28, b=0), - yaxis=dict( - title="Percentage", - range=[0, 100], - tickvals=[ - *range( - 0, - 101, - 10, - ) - ], - ), - xaxis=xaxis_ticks, - title=dict( - text=f"{view} Feedback Distribution", - ), - height=300, - template="plotly_white", + tickvals=[ + *range( + int(max_value / 10), + int(max_value) + 10, + int(max_value / 10) + 1, + ) + ], + ) + st.plotly_chart(fig) + st.write("---") + fig = go.Figure( + data=[ + go.Scatter( + name="Average Run Time", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Average_runtime"]), + text=list(df["Average_runtime"]), + hovertemplate="Average Runtime: %{y:.0f}", + ), + ], + layout=dict( + margin=dict(l=0, r=0, t=28, b=0), + yaxis=dict( + title="Seconds", + ), + title=dict( + text=f"{view} Performance Metrics", + ), + xaxis=xaxis_ticks, + height=300, + template="plotly_white", + ), + ) + st.plotly_chart(fig) + st.write("---") + fig = go.Figure( + data=[ + go.Scatter( + name="Positive Feedback", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Percentage_positive_feedback"]), + text=list(df["Percentage_positive_feedback"]), + hovertemplate="Positive Feedback: %{y:.0f}%", + ), + go.Scatter( + name="Negative Feedback", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Percentage_negative_feedback"]), + text=list(df["Percentage_negative_feedback"]), + hovertemplate="Negative Feedback: %{y:.0f}%", ), + go.Scatter( + name="Successfully Answered", + mode="lines+markers", + x=list(df["date"]), + y=list(df["Percentage_successfully_answered"]), + text=list(df["Percentage_successfully_answered"]), + hovertemplate="Successfully Answered: %{y:.0f}%", + ), + ], + layout=dict( + margin=dict(l=0, r=0, t=28, b=0), + yaxis=dict( + title="Percentage", + range=[0, 100], + tickvals=[ + *range( + 0, + 101, + 10, + ) + ], + ), + xaxis=xaxis_ticks, + title=dict( + text=f"{view} Feedback Distribution", + ), + height=300, + template="plotly_white", + ), + ) + st.plotly_chart(fig) + + +def get_tabular_data( + *, + bi, + conversations, + messages, + details, + sort_by, + rows=10000, + start_date=None, + end_date=None, +): + import pandas as pd + + df = pd.DataFrame() + if details == "Conversations": + if start_date and end_date: + messages = messages.filter( + created_at__date__gte=start_date, created_at__date__lte=end_date + ) + conversations = conversations.filter(messages__in=messages).distinct() + df = conversations.to_df_format(row_limit=rows) + elif details == "Messages": + if start_date and end_date: + messages = messages.filter( + created_at__date__gte=start_date, created_at__date__lte=end_date + ) + df = messages.order_by("-created_at", "conversation__id").to_df_format( + row_limit=rows ) - st.plotly_chart(fig) - - def get_tabular_data( - self, - bi, - conversations, - messages, - details, - sort_by, - rows=10000, - start_date=None, - end_date=None, - ): - import pandas as pd - - df = pd.DataFrame() - if details == "Conversations": - if start_date and end_date: - messages = messages.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) - conversations = conversations.filter(messages__in=messages).distinct() - df = conversations.to_df_format(row_limit=rows) - elif details == "Messages": - if start_date and end_date: - messages = messages.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) - df = messages.order_by("-created_at", "conversation__id").to_df_format( - row_limit=rows + most_recent = ( + df.groupby("Name")["Sent"] + .max() + .reset_index() + .rename(columns={"Sent": "Last Sent"}) + ) + df = df.merge( + most_recent, + how="left", + left_on="Name", + right_on="Name", + ) + df = df.sort_values( + by=["Last Sent", "Name", "Sent"], ascending=False + ).reset_index() + df.drop(columns=["index", "Last Sent"], inplace=True) + elif details == "Feedback Positive": + pos_feedbacks: FeedbackQuerySet = Feedback.objects.filter( + message__conversation__bot_integration=bi, + rating=Feedback.Rating.RATING_THUMBS_UP, + ) # type: ignore + if start_date and end_date: + pos_feedbacks = pos_feedbacks.filter( + created_at__date__gte=start_date, created_at__date__lte=end_date ) - most_recent = ( - df.groupby("Name")["Sent"] - .max() - .reset_index() - .rename(columns={"Sent": "Last Sent"}) + df = pos_feedbacks.to_df_format(row_limit=rows) + elif details == "Feedback Negative": + neg_feedbacks: FeedbackQuerySet = Feedback.objects.filter( + message__conversation__bot_integration=bi, + rating=Feedback.Rating.RATING_THUMBS_DOWN, + ) # type: ignore + if start_date and end_date: + neg_feedbacks = neg_feedbacks.filter( + created_at__date__gte=start_date, created_at__date__lte=end_date ) - df = df.merge( - most_recent, - how="left", - left_on="Name", - right_on="Name", + df = neg_feedbacks.to_df_format(row_limit=rows) + elif details == "Answered Successfully": + successful_messages: MessageQuerySet = Message.objects.filter( + Q(analysis_result__contains={"Answered": True}) + | Q(analysis_result__contains={"assistant": {"answer": "Found"}}), + conversation__bot_integration=bi, + ) # type: ignore + if start_date and end_date: + successful_messages = successful_messages.filter( + created_at__date__gte=start_date, created_at__date__lte=end_date ) - df = df.sort_values( - by=["Last Sent", "Name", "Sent"], ascending=False - ).reset_index() - df.drop(columns=["index", "Last Sent"], inplace=True) - elif details == "Feedback Positive": - pos_feedbacks: FeedbackQuerySet = Feedback.objects.filter( - message__conversation__bot_integration=bi, - rating=Feedback.Rating.RATING_THUMBS_UP, - ) # type: ignore - if start_date and end_date: - pos_feedbacks = pos_feedbacks.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) - df = pos_feedbacks.to_df_format(row_limit=rows) - elif details == "Feedback Negative": - neg_feedbacks: FeedbackQuerySet = Feedback.objects.filter( - message__conversation__bot_integration=bi, - rating=Feedback.Rating.RATING_THUMBS_DOWN, - ) # type: ignore - if start_date and end_date: - neg_feedbacks = neg_feedbacks.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) - df = neg_feedbacks.to_df_format(row_limit=rows) - elif details == "Answered Successfully": - successful_messages: MessageQuerySet = Message.objects.filter( - Q(analysis_result__contains={"Answered": True}) - | Q(analysis_result__contains={"assistant": {"answer": "Found"}}), - conversation__bot_integration=bi, - ) # type: ignore - if start_date and end_date: - successful_messages = successful_messages.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) - df = successful_messages.to_df_analysis_format(row_limit=rows) - elif details == "Answered Unsuccessfully": - unsuccessful_messages: MessageQuerySet = Message.objects.filter( - Q(analysis_result__contains={"Answered": False}) - | Q(analysis_result__contains={"assistant": {"answer": "Missing"}}), - conversation__bot_integration=bi, - ) # type: ignore - if start_date and end_date: - unsuccessful_messages = unsuccessful_messages.filter( - created_at__date__gte=start_date, created_at__date__lte=end_date - ) - df = unsuccessful_messages.to_df_analysis_format(row_limit=rows) + df = successful_messages.to_df_analysis_format(row_limit=rows) + elif details == "Answered Unsuccessfully": + unsuccessful_messages: MessageQuerySet = Message.objects.filter( + Q(analysis_result__contains={"Answered": False}) + | Q(analysis_result__contains={"assistant": {"answer": "Missing"}}), + conversation__bot_integration=bi, + ) # type: ignore + if start_date and end_date: + unsuccessful_messages = unsuccessful_messages.filter( + created_at__date__gte=start_date, created_at__date__lte=end_date + ) + df = unsuccessful_messages.to_df_analysis_format(row_limit=rows) - if sort_by and sort_by in df.columns: - df.sort_values(by=[sort_by], ascending=False, inplace=True) + if sort_by and sort_by in df.columns: + df.sort_values(by=[sort_by], ascending=False, inplace=True) - return df + return df