|
import gradio as gr |
|
import datetime |
|
from typing import Dict, List, Any, Union, Optional |
|
import random |
|
import os |
|
import json |
|
|
|
|
|
from utils.storage import load_data, save_data, safe_get |
|
from utils.state import generate_id, get_timestamp, record_activity |
|
from utils.ai_models import analyze_sentiment, summarize_text |
|
from utils.config import FILE_PATHS |
|
from utils.logging import setup_logger |
|
from utils.error_handling import handle_exceptions |
|
from utils.data_analysis import ( |
|
filter_data_by_time_period, |
|
create_completion_rate_chart, |
|
create_status_distribution_chart, |
|
create_priority_distribution_chart, |
|
create_time_series_chart, |
|
create_completion_time_chart, |
|
create_tags_distribution_chart, |
|
create_activity_heatmap, |
|
create_calendar_heatmap, |
|
create_sentiment_chart, |
|
create_model_usage_distribution, |
|
create_model_usage_over_time |
|
) |
|
|
|
|
|
logger = setup_logger(__name__) |
|
|
|
@handle_exceptions |
|
def create_analytics_page(state: Dict[str, Any]) -> None: |
|
""" |
|
Create the Analytics page with data visualizations and insights |
|
|
|
Args: |
|
state: Application state |
|
""" |
|
logger.info("Creating analytics page") |
|
|
|
|
|
with gr.Column(elem_id="analytics-page"): |
|
gr.Markdown("# 📊 Analytics") |
|
gr.Markdown("*Insights and visualizations of your productivity data*") |
|
|
|
|
|
with gr.Row(): |
|
time_period = gr.Dropdown( |
|
choices=["Last 7 Days", "Last 30 Days", "Last 90 Days", "All Time"], |
|
value="Last 30 Days", |
|
label="Time Period", |
|
elem_id="time-period-selector" |
|
) |
|
refresh_btn = gr.Button("Refresh Data") |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
with gr.TabItem("Tasks"): |
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
task_completion_chart = gr.Plot( |
|
label="Task Completion Rate", |
|
elem_id="task-completion-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
task_status_chart = gr.Plot( |
|
label="Task Status Distribution", |
|
elem_id="task-status-chart" |
|
) |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
task_priority_chart = gr.Plot( |
|
label="Task Priority Distribution", |
|
elem_id="task-priority-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
task_creation_chart = gr.Plot( |
|
label="Task Creation Over Time", |
|
elem_id="task-creation-chart" |
|
) |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
task_completion_time_chart = gr.Plot( |
|
label="Average Completion Time", |
|
elem_id="task-completion-time-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
task_tags_chart = gr.Plot( |
|
label="Task Tags Distribution", |
|
elem_id="task-tags-chart" |
|
) |
|
|
|
|
|
with gr.TabItem("Notes"): |
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
notes_creation_chart = gr.Plot( |
|
label="Notes Creation Over Time", |
|
elem_id="notes-creation-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
notes_length_chart = gr.Plot( |
|
label="Notes Length Distribution", |
|
elem_id="notes-length-chart" |
|
) |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
notes_tags_chart = gr.Plot( |
|
label="Notes Tags Distribution", |
|
elem_id="notes-tags-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
notes_sentiment_chart = gr.Plot( |
|
label="Notes Sentiment Analysis", |
|
elem_id="notes-sentiment-chart" |
|
) |
|
|
|
|
|
with gr.TabItem("Goals"): |
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
goal_completion_chart = gr.Plot( |
|
label="Goal Completion Rate", |
|
elem_id="goal-completion-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
goal_progress_chart = gr.Plot( |
|
label="Goal Progress Distribution", |
|
elem_id="goal-progress-chart" |
|
) |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
goal_creation_chart = gr.Plot( |
|
label="Goal Creation Over Time", |
|
elem_id="goal-creation-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
goal_completion_time_chart = gr.Plot( |
|
label="Average Goal Completion Time", |
|
elem_id="goal-completion-time-chart" |
|
) |
|
|
|
|
|
with gr.TabItem("Activity"): |
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
activity_dow_chart = gr.Plot( |
|
label="Activity by Day of Week", |
|
elem_id="activity-dow-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
activity_hour_chart = gr.Plot( |
|
label="Activity by Hour of Day", |
|
elem_id="activity-hour-chart" |
|
) |
|
|
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
activity_type_chart = gr.Plot( |
|
label="Activity by Type", |
|
elem_id="activity-type-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
activity_time_chart = gr.Plot( |
|
label="Activity Over Time", |
|
elem_id="activity-time-chart" |
|
) |
|
|
|
|
|
with gr.TabItem("AI Usage"): |
|
with gr.Row(): |
|
|
|
with gr.Column(scale=1): |
|
ai_model_chart = gr.Plot( |
|
label="AI Model Usage Distribution", |
|
elem_id="ai-model-chart" |
|
) |
|
|
|
|
|
with gr.Column(scale=1): |
|
ai_usage_chart = gr.Plot( |
|
label="AI Usage Over Time", |
|
elem_id="ai-usage-chart" |
|
) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
gr.Markdown("### Key Metrics") |
|
metrics_md = gr.Markdown("*Loading metrics...*") |
|
|
|
|
|
@handle_exceptions |
|
def update_metrics(period): |
|
"""Update metrics based on selected time period""" |
|
logger.debug(f"Updating metrics for period: {period}") |
|
|
|
|
|
tasks = safe_get(state, "tasks", []) |
|
notes = safe_get(state, "notes", []) |
|
goals = safe_get(state, "goals", []) |
|
activity = safe_get(state, "activity_feed", []) |
|
|
|
|
|
total_tasks = len(tasks) |
|
completed_tasks = len([t for t in tasks if safe_get(t, "completed", False)]) |
|
total_notes = len(notes) |
|
total_goals = len(goals) |
|
completed_goals = len([g for g in goals if safe_get(g, "completed", False)]) |
|
total_activity = len(activity) |
|
|
|
|
|
metrics = [ |
|
f"**Total Tasks:** {total_tasks}", |
|
f"**Completed Tasks:** {completed_tasks}", |
|
f"**Task Completion Rate:** {(completed_tasks/total_tasks*100):.1f}% if total_tasks > 0 else '0%'", |
|
f"**Total Notes:** {total_notes}", |
|
f"**Total Goals:** {total_goals}", |
|
f"**Goal Completion Rate:** {(completed_goals/total_goals*100):.1f}% if total_goals > 0 else '0%'", |
|
f"**Total Activities:** {total_activity}" |
|
] |
|
|
|
return "\n\n".join(metrics) |
|
|
|
|
|
@handle_exceptions |
|
def update_charts(period): |
|
"""Update all charts based on selected time period""" |
|
logger.debug(f"Updating charts for period: {period}") |
|
|
|
|
|
tasks = safe_get(state, "tasks", []) |
|
notes = safe_get(state, "notes", []) |
|
goals = safe_get(state, "goals", []) |
|
activity = safe_get(state, "activity_feed", []) |
|
|
|
|
|
filtered_tasks = filter_data_by_time_period(tasks, period) |
|
filtered_notes = filter_data_by_time_period(notes, period) |
|
filtered_goals = filter_data_by_time_period(goals, period) |
|
filtered_activity = filter_data_by_time_period(activity, period) |
|
|
|
|
|
task_completion_fig = create_completion_rate_chart( |
|
filtered_tasks, |
|
title="Task Completion Rate", |
|
completed_key="completed" |
|
) |
|
|
|
task_status_fig = create_status_distribution_chart( |
|
filtered_tasks, |
|
title="Task Status Distribution" |
|
) |
|
|
|
task_priority_fig = create_priority_distribution_chart( |
|
filtered_tasks, |
|
title="Task Priority Distribution" |
|
) |
|
|
|
task_creation_fig = create_time_series_chart( |
|
filtered_tasks, |
|
title="Task Creation Over Time", |
|
timestamp_key="created_at" |
|
) |
|
|
|
task_completion_time_fig = create_completion_time_chart( |
|
filtered_tasks, |
|
title="Task Completion Time Distribution", |
|
created_key="created_at", |
|
completed_key="completed_at" |
|
) |
|
|
|
task_tags_fig = create_tags_distribution_chart( |
|
filtered_tasks, |
|
title="Task Tags Distribution", |
|
tags_key="tags" |
|
) |
|
|
|
|
|
notes_creation_fig = create_time_series_chart( |
|
filtered_notes, |
|
title="Notes Creation Over Time", |
|
timestamp_key="created_at" |
|
) |
|
|
|
|
|
notes_with_length = [] |
|
for note in filtered_notes: |
|
content = safe_get(note, "content", "") |
|
if content: |
|
notes_with_length.append({ |
|
**note, |
|
"length": len(content) |
|
}) |
|
|
|
|
|
notes_with_length.sort(key=lambda x: x["length"]) |
|
|
|
|
|
import plotly.graph_objects as go |
|
notes_length_fig = go.Figure(data=go.Bar( |
|
x=[i for i in range(len(notes_with_length))], |
|
y=[note["length"] for note in notes_with_length], |
|
marker_color="#4CAF50" |
|
)) |
|
|
|
notes_length_fig.update_layout( |
|
title="Notes Length Distribution", |
|
xaxis_title="Note Index", |
|
yaxis_title="Character Count", |
|
margin=dict(l=20, r=20, t=40, b=20), |
|
height=300 |
|
) |
|
|
|
notes_tags_fig = create_tags_distribution_chart( |
|
filtered_notes, |
|
title="Notes Tags Distribution", |
|
tags_key="tags" |
|
) |
|
|
|
notes_sentiment_fig = create_sentiment_chart( |
|
filtered_notes, |
|
title="Notes Sentiment Analysis", |
|
content_key="content", |
|
timestamp_key="created_at" |
|
) |
|
|
|
|
|
goal_completion_fig = create_completion_rate_chart( |
|
filtered_goals, |
|
title="Goal Completion Rate", |
|
completed_key="completed" |
|
) |
|
|
|
|
|
|
|
goal_progress_fig = create_status_distribution_chart( |
|
filtered_goals, |
|
title="Goal Progress Distribution", |
|
status_key="status" |
|
) |
|
|
|
goal_creation_fig = create_time_series_chart( |
|
filtered_goals, |
|
title="Goal Creation Over Time", |
|
timestamp_key="created_at" |
|
) |
|
|
|
goal_completion_time_fig = create_completion_time_chart( |
|
filtered_goals, |
|
title="Goal Completion Time Distribution", |
|
created_key="created_at", |
|
completed_key="completed_at" |
|
) |
|
|
|
|
|
activity_dow_hour_fig = create_activity_heatmap( |
|
filtered_activity, |
|
title="Activity by Day and Hour", |
|
timestamp_key="timestamp" |
|
) |
|
|
|
|
|
import numpy as np |
|
import plotly.graph_objects as go |
|
|
|
|
|
day_counts = np.zeros(7) |
|
for item in filtered_activity: |
|
timestamp = item.get("timestamp") |
|
if not timestamp: |
|
continue |
|
|
|
|
|
if isinstance(timestamp, str): |
|
try: |
|
date = datetime.datetime.fromisoformat(timestamp.replace('Z', '+00:00')) |
|
except ValueError: |
|
continue |
|
else: |
|
date = datetime.datetime.fromtimestamp(timestamp) |
|
|
|
|
|
day_of_week = date.weekday() |
|
day_counts[day_of_week] += 1 |
|
|
|
days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] |
|
activity_dow_fig = go.Figure(data=go.Bar( |
|
x=days, |
|
y=day_counts, |
|
marker_color="#2196F3" |
|
)) |
|
|
|
activity_dow_fig.update_layout( |
|
title="Activity by Day of Week", |
|
xaxis_title="Day", |
|
yaxis_title="Count", |
|
margin=dict(l=20, r=20, t=40, b=20), |
|
height=300 |
|
) |
|
|
|
|
|
hour_counts = np.zeros(24) |
|
for item in filtered_activity: |
|
timestamp = item.get("timestamp") |
|
if not timestamp: |
|
continue |
|
|
|
|
|
if isinstance(timestamp, str): |
|
try: |
|
date = datetime.datetime.fromisoformat(timestamp.replace('Z', '+00:00')) |
|
except ValueError: |
|
continue |
|
else: |
|
date = datetime.datetime.fromtimestamp(timestamp) |
|
|
|
|
|
hour = date.hour |
|
hour_counts[hour] += 1 |
|
|
|
hours = [f"{h:02d}:00" for h in range(24)] |
|
activity_hour_fig = go.Figure(data=go.Bar( |
|
x=hours, |
|
y=hour_counts, |
|
marker_color="#9C27B0" |
|
)) |
|
|
|
activity_hour_fig.update_layout( |
|
title="Activity by Hour of Day", |
|
xaxis_title="Hour", |
|
yaxis_title="Count", |
|
margin=dict(l=20, r=20, t=40, b=20), |
|
height=300 |
|
) |
|
|
|
|
|
activity_types = {} |
|
for item in filtered_activity: |
|
activity_type = safe_get(item, "type", "unknown") |
|
|
|
display_type = activity_type.replace("_", " ").title() |
|
activity_types[display_type] = activity_types.get(display_type, 0) + 1 |
|
|
|
|
|
sorted_types = sorted(activity_types.items(), key=lambda x: x[1], reverse=True) |
|
types = [t[0] for t in sorted_types[:10]] |
|
type_counts = [t[1] for t in sorted_types[:10]] |
|
|
|
activity_type_fig = go.Figure(data=go.Bar( |
|
x=types, |
|
y=type_counts, |
|
marker_color="#FF9800" |
|
)) |
|
|
|
activity_type_fig.update_layout( |
|
title="Activity by Type", |
|
xaxis_title="Type", |
|
yaxis_title="Count", |
|
margin=dict(l=20, r=20, t=40, b=20), |
|
height=300 |
|
) |
|
|
|
|
|
activity_time_fig = create_time_series_chart( |
|
filtered_activity, |
|
title="Activity Over Time", |
|
timestamp_key="timestamp" |
|
) |
|
|
|
|
|
ai_model_fig = create_model_usage_distribution( |
|
filtered_activity, |
|
title="AI Model Usage Distribution" |
|
) |
|
|
|
ai_usage_fig = create_model_usage_over_time( |
|
filtered_activity, |
|
title="AI Usage Over Time", |
|
timestamp_key="timestamp" |
|
) |
|
|
|
|
|
return ( |
|
task_completion_fig, task_status_fig, task_priority_fig, task_creation_fig, |
|
task_completion_time_fig, task_tags_fig, notes_creation_fig, notes_length_fig, |
|
notes_tags_fig, notes_sentiment_fig, goal_completion_fig, goal_progress_fig, |
|
goal_creation_fig, goal_completion_time_fig, activity_dow_fig, activity_hour_fig, |
|
activity_type_fig, activity_time_fig, ai_model_fig, ai_usage_fig |
|
) |
|
|
|
|
|
refresh_btn.click( |
|
fn=lambda period: (update_metrics(period), *update_charts(period)), |
|
inputs=[time_period], |
|
outputs=[ |
|
metrics_md, |
|
task_completion_chart, task_status_chart, task_priority_chart, task_creation_chart, |
|
task_completion_time_chart, task_tags_chart, notes_creation_chart, notes_length_chart, |
|
notes_tags_chart, notes_sentiment_chart, goal_completion_chart, goal_progress_chart, |
|
goal_creation_chart, goal_completion_time_chart, activity_dow_chart, activity_hour_chart, |
|
activity_type_chart, activity_time_chart, ai_model_chart, ai_usage_chart |
|
] |
|
) |
|
|
|
|
|
metrics_md.value = update_metrics("Last 30 Days") |
|
( |
|
task_completion_chart.value, task_status_chart.value, task_priority_chart.value, task_creation_chart.value, |
|
task_completion_time_chart.value, task_tags_chart.value, notes_creation_chart.value, notes_length_chart.value, |
|
notes_tags_chart.value, notes_sentiment_chart.value, goal_completion_chart.value, goal_progress_chart.value, |
|
goal_creation_chart.value, goal_completion_time_chart.value, activity_dow_chart.value, activity_hour_chart.value, |
|
activity_type_chart.value, activity_time_chart.value, ai_model_chart.value, ai_usage_chart.value |
|
) = update_charts("Last 30 Days") |