Advanced Journal Assistant¶
Difficulty: Intermediate-Advanced
Time: 60-90 minutes
Learning Focus: Data structures, file I/O, data visualisation, natural language processing, user experience design
Module: chat
Overview¶
Create a comprehensive journaling application that combines structured mood tracking with flexible reflection prompts, offering users deep insights through data visualisation and AI-powered analysis.
Instructions¶
from hands_on_ai.chat import get_response
import datetime
import os
import json
import matplotlib.pyplot as plt
from collections import Counter
import re
class AdvancedJournalAssistant:
def __init__(self):
"""Initialize the journal assistant with necessary directories and files"""
# Set up storage directories
self.journal_dir = os.path.expanduser("~/.advanced_journal")
self.visualization_dir = os.path.join(self.journal_dir, "visualizations")
os.makedirs(self.journal_dir, exist_ok=True)
os.makedirs(self.visualization_dir, exist_ok=True)
# Define file paths
self.journal_file = os.path.join(self.journal_dir, "journal_entries.json")
self.prompt_file = os.path.join(self.journal_dir, "custom_prompts.json")
# Initialize data structures
self.entries = self._load_entries()
self.custom_prompts = self._load_custom_prompts()
# Default prompts
self.default_prompts = [
"What's something that happened today that you'd like to reflect on?",
"What's something you learned today?",
"What's something you're grateful for today?",
"What's something that challenged you today?",
"What's something you're looking forward to?",
"How did you take care of yourself today?",
"Was there a moment today that stood out? Why?",
"What's something you'd like to remember about today?"
]
# Define mood labels for better interpretation
self.mood_labels = {
1: "Very Low", 2: "Low", 3: "Somewhat Low", 4: "Below Average", 5: "Neutral",
6: "Slightly Positive", 7: "Good", 8: "Very Good", 9: "Excellent", 10: "Outstanding"
}
# Track user preferences
self.preferences = self._load_preferences()
def _load_entries(self):
"""Load existing journal entries or return an empty list"""
if os.path.exists(self.journal_file):
try:
with open(self.journal_file, 'r') as f:
return json.load(f)
except json.JSONDecodeError:
return []
return []
def _load_custom_prompts(self):
"""Load custom prompts or return an empty list"""
if os.path.exists(self.prompt_file):
try:
with open(self.prompt_file, 'r') as f:
return json.load(f)
except json.JSONDecodeError:
return []
return []
def _load_preferences(self):
"""Load user preferences or set defaults"""
pref_file = os.path.join(self.journal_dir, "preferences.json")
defaults = {
"auto_insights": True,
"daily_reminder": False,
"reminder_time": "20:00",
"favorite_prompts": [],
"theme": "standard",
"insight_frequency": "weekly"
}
if os.path.exists(pref_file):
try:
with open(pref_file, 'r') as f:
stored_prefs = json.load(f)
# Update defaults with stored preferences
defaults.update(stored_prefs)
except json.JSONDecodeError:
pass
return defaults
def _save_entries(self):
"""Save journal entries to file"""
with open(self.journal_file, 'w') as f:
json.dump(self.entries, f, indent=2)
def _save_custom_prompts(self):
"""Save custom prompts to file"""
with open(self.prompt_file, 'w') as f:
json.dump(self.custom_prompts, f, indent=2)
def _save_preferences(self):
"""Save user preferences to file"""
pref_file = os.path.join(self.journal_dir, "preferences.json")
with open(pref_file, 'w') as f:
json.dump(self.preferences, f, indent=2)
def display_menu(self):
"""Display the main menu and handle user choices"""
print("\n======================================")
print("=== Advanced Journal Assistant ===")
print("======================================")
print("1. Create new journal entry")
print("2. View past entries")
print("3. Get insights and analytics")
print("4. Manage custom prompts")
print("5. Set goals and intentions")
print("6. Preferences")
print("7. Export journal")
print("8. Exit")
try:
choice = input("\nWhat would you like to do? (1-8): ")
menu_actions = {
"1": self.create_entry,
"2": self.view_entries,
"3": self.get_insights,
"4": self.manage_prompts,
"5": self.set_goals,
"6": self.set_preferences,
"7": self.export_journal,
"8": self.exit_app
}
# Execute the chosen action or show error
if choice in menu_actions:
menu_actions[choice]()
else:
print("Invalid choice. Please try again.")
self.display_menu()
except KeyboardInterrupt:
self.exit_app()
def create_entry(self):
"""Create a new journal entry with structured and free-form components"""
print("\n=== New Journal Entry ===")
# Get date/time
today = datetime.datetime.now()
date_str = today.strftime("%Y-%m-%d")
time_str = today.strftime("%H:%M")
# Check if we already have an entry for today
existing_entries = [e for e in self.entries if e["date"] == date_str]
if existing_entries:
print(f"You already have {len(existing_entries)} entries for today.")
add_another = input("Would you like to add another entry? (y/n): ").lower()
if add_another != 'y':
self.display_menu()
return
# Collect mood data
try:
mood = int(input("\nHow would you rate your mood right now (1-10)? "))
if not 1 <= mood <= 10:
print("Please enter a number between 1 and 10.")
mood = 5 # Default to neutral if invalid
mood_label = self.mood_labels[mood]
print(f"Mood: {mood}/10 - {mood_label}")
except ValueError:
print("Invalid input. Setting mood to neutral (5/10).")
mood = 5
mood_label = self.mood_labels[mood]
# Get energy level
try:
energy = int(input("\nHow is your energy level (1-10)? "))
if not 1 <= energy <= 10:
energy = 5 # Default if invalid
except ValueError:
energy = 5
# Get prompt for reflection
print("\nChoose a prompt for reflection:")
all_prompts = self.default_prompts + self.custom_prompts
# Show prompts
for i, prompt in enumerate(all_prompts, 1):
print(f"{i}. {prompt}")
print(f"{len(all_prompts) + 1}. Create a custom prompt")
try:
prompt_choice = int(input("\nSelect a prompt (number): "))
if 1 <= prompt_choice <= len(all_prompts):
selected_prompt = all_prompts[prompt_choice - 1]
else:
custom_prompt = input("Enter your custom prompt: ")
selected_prompt = custom_prompt
# Ask if they want to save this prompt for future use
save_prompt = input("Would you like to save this prompt for future use? (y/n): ").lower()
if save_prompt == 'y':
self.custom_prompts.append(custom_prompt)
self._save_custom_prompts()
print("Custom prompt saved.")
except (ValueError, IndexError):
# Default to first prompt if invalid
selected_prompt = all_prompts[0]
print(f"Using default prompt: {selected_prompt}")
# Display the selected prompt and get reflection
print(f"\n> {selected_prompt}")
reflection = input("Your reflection: ")
# Get activities
activities = input("\nWhat activities did you do today? (comma-separated): ")
activity_list = [a.strip() for a in activities.split(",") if a.strip()]
# Get any tags the user wants to associate with this entry
tags = input("\nAdd any tags to help categorize this entry (comma-separated): ")
tag_list = [t.strip() for t in tags.split(",") if t.strip()]
# Create the entry object
entry = {
"date": date_str,
"time": time_str,
"mood": mood,
"mood_label": mood_label,
"energy": energy,
"prompt": selected_prompt,
"reflection": reflection,
"activities": activity_list,
"tags": tag_list,
"ai_insights": None # Will be filled in by AI
}
# Get AI reflection if user wants it
if self.preferences["auto_insights"]:
print("\nGenerating insights for your entry...")
entry["ai_insights"] = self._generate_entry_insight(entry)
print("\n=== AI Reflection ===")
print(entry["ai_insights"])
# Add entry to the list and save
self.entries.append(entry)
self._save_entries()
print("\nJournal entry saved successfully!")
# Return to menu
input("\nPress Enter to continue...")
self.display_menu()
def view_entries(self):
"""View and search past journal entries"""
if not self.entries:
print("\nNo journal entries found.")
input("\nPress Enter to continue...")
self.display_menu()
return
print("\n=== View Journal Entries ===")
print("1. View recent entries")
print("2. Search by date")
print("3. Search by mood")
print("4. Search by tag")
print("5. Search by text")
print("6. Return to main menu")
choice = input("\nWhat would you like to do? (1-6): ")
if choice == "1":
# Show recent entries
recent = self.entries[-10:] # Last 10 entries
recent.reverse() # Most recent first
self._display_entry_list(recent, "Recent Entries")
elif choice == "2":
# Search by date
date_query = input("\nEnter date (YYYY-MM-DD) or month (YYYY-MM): ")
matching = [e for e in self.entries if e["date"].startswith(date_query)]
self._display_entry_list(matching, f"Entries for {date_query}")
elif choice == "3":
# Search by mood
try:
mood_min = int(input("\nEnter minimum mood (1-10): "))
mood_max = int(input("Enter maximum mood (1-10): "))
matching = [e for e in self.entries
if mood_min <= e["mood"] <= mood_max]
self._display_entry_list(matching, f"Entries with mood {mood_min}-{mood_max}")
except ValueError:
print("Invalid input. Please enter numbers for mood range.")
elif choice == "4":
# Search by tag
tag = input("\nEnter tag to search for: ").strip().lower()
matching = [e for e in self.entries
if any(t.lower() == tag for t in e["tags"])]
self._display_entry_list(matching, f"Entries tagged with '{tag}'")
elif choice == "5":
# Search by text
text = input("\nEnter text to search for: ").strip().lower()
matching = [e for e in self.entries
if text in e["reflection"].lower() or
text in e["prompt"].lower() or
any(text in a.lower() for a in e["activities"])]
self._display_entry_list(matching, f"Entries containing '{text}'")
elif choice == "6":
self.display_menu()
return
else:
print("Invalid choice.")
# After any search, go back to view menu
self.view_entries()
def _display_entry_list(self, entries, title):
"""Display a list of entries and allow user to select one to view in detail"""
if not entries:
print(f"\nNo entries found for {title}.")
input("\nPress Enter to continue...")
return
print(f"\n=== {title} ===")
for i, entry in enumerate(entries, 1):
date_str = entry["date"]
mood = entry["mood"]
# Get a preview of the reflection (first 40 chars)
preview = entry["reflection"][:40] + "..." if len(entry["reflection"]) > 40 else entry["reflection"]
print(f"{i}. {date_str} - Mood: {mood}/10 - {preview}")
print(f"{len(entries) + 1}. Back")
try:
choice = int(input("\nSelect an entry to view (number): "))
if 1 <= choice <= len(entries):
self._display_entry_detail(entries[choice - 1])
elif choice == len(entries) + 1:
return
else:
print("Invalid choice.")
except ValueError:
print("Invalid input. Please enter a number.")
def _display_entry_detail(self, entry):
"""Display details of a single entry"""
print("\n" + "=" * 50)
print(f"Date: {entry['date']} at {entry['time']}")
print(f"Mood: {entry['mood']}/10 - {entry['mood_label']}")
print(f"Energy: {entry['energy']}/10")
print("\nPrompt:")
print(f"{entry['prompt']}")
print("\nReflection:")
print(f"{entry['reflection']}")
if entry["activities"]:
print("\nActivities:")
for activity in entry["activities"]:
print(f"- {activity}")
if entry["tags"]:
print("\nTags:")
print(", ".join(entry["tags"]))
if entry.get("ai_insights"):
print("\nAI Insights:")
print(entry["ai_insights"])
print("=" * 50)
# Options after viewing an entry
print("\n1. Edit this entry")
print("2. Delete this entry")
print("3. Generate AI insights for this entry")
print("4. Back")
choice = input("\nWhat would you like to do? (1-4): ")
if choice == "1":
self._edit_entry(entry)
elif choice == "2":
self._delete_entry(entry)
elif choice == "3":
if not entry.get("ai_insights"):
print("\nGenerating insights...")
entry["ai_insights"] = self._generate_entry_insight(entry)
self._save_entries()
print("\n=== AI Insights ===")
print(entry["ai_insights"])
input("\nPress Enter to continue...")
elif choice == "4":
return
else:
print("Invalid choice.")
self._display_entry_detail(entry)
def _edit_entry(self, entry):
"""Edit an existing journal entry"""
print("\n=== Edit Entry ===")
print("What would you like to modify?")
print("1. Mood rating")
print("2. Energy rating")
print("3. Reflection text")
print("4. Activities")
print("5. Tags")
print("6. Cancel edit")
choice = input("\nEnter your choice (1-6): ")
if choice == "1":
try:
new_mood = int(input("Enter new mood rating (1-10): "))
if 1 <= new_mood <= 10:
entry["mood"] = new_mood
entry["mood_label"] = self.mood_labels[new_mood]
print("Mood updated.")
else:
print("Invalid mood rating. Must be between 1 and 10.")
except ValueError:
print("Invalid input. Mood not changed.")
elif choice == "2":
try:
new_energy = int(input("Enter new energy rating (1-10): "))
if 1 <= new_energy <= 10:
entry["energy"] = new_energy
print("Energy updated.")
else:
print("Invalid energy rating. Must be between 1 and 10.")
except ValueError:
print("Invalid input. Energy not changed.")
elif choice == "3":
print(f"Current reflection: {entry['reflection']}")
new_text = input("Enter new reflection (or press Enter to keep current): ")
if new_text:
entry["reflection"] = new_text
print("Reflection updated.")
elif choice == "4":
print(f"Current activities: {', '.join(entry['activities'])}")
new_activities = input("Enter new activities (comma-separated): ")
if new_activities:
entry["activities"] = [a.strip() for a in new_activities.split(",") if a.strip()]
print("Activities updated.")
elif choice == "5":
print(f"Current tags: {', '.join(entry['tags'])}")
new_tags = input("Enter new tags (comma-separated): ")
if new_tags:
entry["tags"] = [t.strip() for t in new_tags.split(",") if t.strip()]
print("Tags updated.")
elif choice == "6":
print("Edit canceled.")
self._display_entry_detail(entry)
return
else:
print("Invalid choice.")
self._edit_entry(entry)
return
# After any edit, regenerate insights if auto-insights is enabled
if self.preferences["auto_insights"]:
print("Regenerating insights for updated entry...")
entry["ai_insights"] = self._generate_entry_insight(entry)
# Save changes
self._save_entries()
print("Entry updated successfully.")
# Show the updated entry
self._display_entry_detail(entry)
def _delete_entry(self, entry):
"""Delete a journal entry"""
confirm = input("\nAre you sure you want to delete this entry? (y/n): ").lower()
if confirm == 'y':
self.entries.remove(entry)
self._save_entries()
print("Entry deleted successfully.")
else:
print("Deletion canceled.")
self._display_entry_detail(entry)
def _generate_entry_insight(self, entry):
"""Generate AI insights for a journal entry"""
prompt = f"""
The user wrote a journal entry with the following details:
- Date: {entry['date']}
- Mood: {entry['mood']}/10 ({entry['mood_label']})
- Energy: {entry['energy']}/10
- Prompt: "{entry['prompt']}"
- Reflection: "{entry['reflection']}"
- Activities: {', '.join(entry['activities']) if entry['activities'] else 'None mentioned'}
- Tags: {', '.join(entry['tags']) if entry['tags'] else 'None'}
Please provide a thoughtful, empathetic reflection on this entry. Include:
1. An observation about their mood and energy
2. A meaningful insight about their reflection
3. A gentle question to deepen their self-awareness
Keep your response concise and supportive (150 words max).
"""
return get_response(prompt,
system="You are an empathetic journaling assistant who helps users gain deeper insights from their reflections.")
def get_insights(self):
"""Generate analytics and AI insights from journal entries"""
if len(self.entries) < 3:
print("\nYou need at least 3 journal entries to generate insights.")
input("\nPress Enter to continue...")
self.display_menu()
return
print("\n=== Journal Insights & Analytics ===")
print("1. Mood trends visualization")
print("2. Activity impact analysis")
print("3. Word cloud and themes")
print("4. Weekly summary")
print("5. Monthly review")
print("6. Custom date range analysis")
print("7. Return to main menu")
choice = input("\nWhat would you like to see? (1-7): ")
if choice == "1":
self._visualize_mood_trends()
elif choice == "2":
self._analyze_activity_impact()
elif choice == "3":
self._generate_word_cloud()
elif choice == "4":
self._generate_weekly_summary()
elif choice == "5":
self._generate_monthly_review()
elif choice == "6":
self._custom_date_analysis()
elif choice == "7":
self.display_menu()
return
else:
print("Invalid choice.")
# Return to insights menu
input("\nPress Enter to continue...")
self.get_insights()
def _visualize_mood_trends(self):
"""Visualize mood trends over time"""
# Extract dates and moods
dates = [datetime.datetime.strptime(e["date"], "%Y-%m-%d") for e in self.entries]
moods = [e["mood"] for e in self.entries]
energy = [e["energy"] for e in self.entries]
# Create the visualization
plt.figure(figsize=(12, 6))
plt.plot(dates, moods, 'b-o', label='Mood')
plt.plot(dates, energy, 'r-o', label='Energy')
plt.axhline(y=5, color='g', linestyle='--', alpha=0.3, label='Neutral')
plt.title('Mood and Energy Trends')
plt.xlabel('Date')
plt.ylabel('Rating (1-10)')
plt.grid(True, alpha=0.3)
plt.legend()
# Format the x-axis to show dates nicely
plt.gcf().autofmt_xdate()
# Save the visualization
viz_path = os.path.join(self.visualization_dir, f"mood_trends_{datetime.datetime.now().strftime('%Y%m%d')}.png")
plt.savefig(viz_path)
print(f"\nMood trend visualization saved to: {viz_path}")
print("\nInsights about your mood trends:")
# Calculate some basic statistics
avg_mood = sum(moods) / len(moods)
avg_energy = sum(energy) / len(energy)
mood_trend = "improving" if moods[-1] > moods[0] else "declining" if moods[-1] < moods[0] else "stable"
print(f"- Your average mood is {avg_mood:.1f}/10")
print(f"- Your average energy level is {avg_energy:.1f}/10")
print(f"- Your overall mood trend appears to be {mood_trend}")
# Generate AI insights on mood trends
if len(self.entries) >= 5: # Need enough data for meaningful trends
entries_text = "\n".join([
f"Date: {e['date']}, Mood: {e['mood']}/10, Energy: {e['energy']}/10, Activities: {', '.join(e['activities'])}"
for e in self.entries[-10:] # Last 10 entries
])
prompt = f"""
Here are the user's recent journal entries with mood and energy ratings:
{entries_text}
Based on this data, provide:
1. Any patterns you notice in their mood and energy levels
2. Potential correlations between activities and mood
3. A gentle suggestion based on these patterns
Keep your response concise (150 words max).
"""
insights = get_response(prompt,
system="You are an analytical journaling assistant who helps identify patterns in mood, energy, and behavior.")
print("\nAI Analysis:")
print(insights)
def _analyze_activity_impact(self):
"""Analyze how different activities impact mood"""
if not any(e.get("activities") for e in self.entries):
print("\nNot enough activity data found in your entries.")
return
# Create a dictionary to track activities and associated moods
activity_moods = {}
# Collect data
for entry in self.entries:
mood = entry["mood"]
for activity in entry.get("activities", []):
activity = activity.lower().strip()
if activity:
if activity not in activity_moods:
activity_moods[activity] = []
activity_moods[activity].append(mood)
# Filter to activities with at least 2 data points
activity_moods = {k: v for k, v in activity_moods.items() if len(v) >= 2}
if not activity_moods:
print("\nNot enough repeated activities found to analyze impact.")
return
# Calculate average mood for each activity
activity_avg_moods = {activity: sum(moods)/len(moods)
for activity, moods in activity_moods.items()}
# Sort activities by average mood (highest first)
sorted_activities = sorted(activity_avg_moods.items(),
key=lambda x: x[1], reverse=True)
# Display results
print("\n=== Activity Impact Analysis ===")
print("Activities sorted by average mood impact:")
for activity, avg_mood in sorted_activities:
count = len(activity_moods[activity])
print(f"- {activity.title()}: {avg_mood:.1f}/10 (mentioned {count} times)")
# Visualize top activities
top_activities = sorted_activities[:min(8, len(sorted_activities))]
activities = [a[0].title() for a in top_activities]
avg_moods = [a[1] for a in top_activities]
plt.figure(figsize=(10, 6))
bars = plt.bar(activities, avg_moods, color='skyblue')
# Add value labels on top of bars
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height + 0.1,
f'{height:.1f}', ha='center', va='bottom')
plt.title('Activities and Their Impact on Mood')
plt.xlabel('Activities')
plt.ylabel('Average Mood (1-10)')
plt.ylim(0, 10.5) # Set y-axis limit with some padding
plt.grid(axis='y', alpha=0.3)
# Rotate x-axis labels for better readability
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
# Save the visualization
viz_path = os.path.join(self.visualization_dir, f"activity_impact_{datetime.datetime.now().strftime('%Y%m%d')}.png")
plt.savefig(viz_path)
print(f"\nActivity impact visualization saved to: {viz_path}")
# Generate AI insights
activities_text = "\n".join([
f"Activity: {activity}, Average Mood: {avg_mood:.1f}/10, Occurrences: {len(activity_moods[activity])}"
for activity, avg_mood in sorted_activities
])
prompt = f"""
Here's an analysis of how different activities affect the user's mood:
{activities_text}
Based on this data, provide:
1. Observations about which activities seem to have the most positive impact
2. Suggestions for which activities they might want to prioritize
3. A gentle question about their activity patterns
Keep your response concise (150 words max).
"""
insights = get_response(prompt,
system="You are an analytical journaling assistant who helps identify patterns between activities and well-being.")
print("\nAI Analysis:")
print(insights)
def _generate_word_cloud(self):
"""Generate a word frequency analysis of journal entries"""
if not self.entries:
print("\nNo journal entries found.")
return
# Combine all reflections
all_text = " ".join([e["reflection"] for e in self.entries])
# Remove common stop words (simplified)
stop_words = ["the", "and", "a", "to", "of", "in", "i", "it", "is", "that",
"was", "for", "on", "you", "he", "be", "with", "as", "by", "at",
"have", "are", "this", "but", "not", "from", "had", "has", "was",
"were", "they", "will", "would", "could", "should", "did", "do",
"does", "their", "there", "then", "than", "them", "these", "those"]
# Extract words, convert to lowercase, and remove punctuation
words = re.findall(r'\b[a-zA-Z]+\b', all_text.lower())
# Filter out stop words
filtered_words = [word for word in words if word not in stop_words and len(word) > 2]
# Count word frequencies
word_counts = Counter(filtered_words)
# Get the top 20 most frequent words
top_words = word_counts.most_common(20)
# Display results
print("\n=== Word Frequency Analysis ===")
print("Most common words in your journal:")
for word, count in top_words:
print(f"- {word}: {count} occurrences")
# Visualize word frequencies
words = [w[0] for w in top_words]
counts = [w[1] for w in top_words]
plt.figure(figsize=(12, 6))
bars = plt.barh(words[::-1], counts[::-1], color='lightgreen') # Reverse to show highest at top
# Add value labels
for bar in bars:
width = bar.get_width()
plt.text(width + 0.5, bar.get_y() + bar.get_height()/2.,
f'{width}', ha='left', va='center')
plt.title('Most Common Words in Journal Entries')
plt.xlabel('Frequency')
plt.tight_layout()
# Save the visualization
viz_path = os.path.join(self.visualization_dir, f"word_frequency_{datetime.datetime.now().strftime('%Y%m%d')}.png")
plt.savefig(viz_path)
print(f"\nWord frequency visualization saved to: {viz_path}")
# Generate AI insights on themes
prompt = f"""
These are the most common words in the user's journal entries:
{', '.join([f"{word} ({count})" for word, count in top_words])}
Based on these words, please:
1. Identify potential themes or patterns in their journaling
2. Suggest areas for deeper reflection
3. Provide a gentle observation about what these themes might indicate
Keep your response concise (150 words max).
"""
insights = get_response(prompt,
system="You are an insightful journaling assistant who helps identify themes and patterns in journal entries.")
print("\nAI Theme Analysis:")
print(insights)
def _generate_weekly_summary(self):
"""Generate a weekly summary of journal entries"""
# Get entries from the past 7 days
today = datetime.datetime.now().date()
week_ago = today - datetime.timedelta(days=7)
weekly_entries = [e for e in self.entries
if datetime.datetime.strptime(e["date"], "%Y-%m-%d").date() >= week_ago]
if len(weekly_entries) < 2:
print("\nNot enough entries in the past week for a meaningful summary.")
return
# Compile weekly data
weekly_moods = [e["mood"] for e in weekly_entries]
avg_mood = sum(weekly_moods) / len(weekly_moods)
weekly_energy = [e["energy"] for e in weekly_entries]
avg_energy = sum(weekly_energy) / len(weekly_energy)
# Get all activities
all_activities = []
for entry in weekly_entries:
all_activities.extend(entry.get("activities", []))
# Count activity frequencies
activity_counts = Counter(all_activities)
most_common = activity_counts.most_common(5)
# Display weekly summary
print("\n=== Weekly Summary ===")
print(f"Period: {week_ago.strftime('%Y-%m-%d')} to {today.strftime('%Y-%m-%d')}")
print(f"Number of entries: {len(weekly_entries)}")
print(f"Average mood: {avg_mood:.1f}/10")
print(f"Average energy: {avg_energy:.1f}/10")
if most_common:
print("\nMost common activities:")
for activity, count in most_common:
print(f"- {activity}: {count} times")
# Generate AI summary
entries_text = "\n\n".join([
f"Date: {e['date']}\nMood: {e['mood']}/10\nEnergy: {e['energy']}/10\n"
f"Activities: {', '.join(e.get('activities', []))}\nReflection: {e['reflection']}"
for e in weekly_entries
])
prompt = f"""
Here's a summary of the user's journal entries for the past week:
{entries_text}
Please provide a weekly reflection that includes:
1. Notable patterns or trends in their mood and energy
2. Observations about activities and their potential impact
3. A gentle suggestion for the coming week
4. A thoughtful reflection question
Keep your response concise and supportive (200 words max).
"""
insights = get_response(prompt,
system="You are an empathetic journaling assistant who helps provide weekly summaries and insights.")
print("\nAI Weekly Reflection:")
print(insights)
def _generate_monthly_review(self):
"""Generate a monthly review of journal entries"""
# Get entries from the current month
today = datetime.datetime.now()
first_day = datetime.datetime(today.year, today.month, 1)
# For previous month, use:
# prev_month = first_day - datetime.timedelta(days=1)
# first_day = datetime.datetime(prev_month.year, prev_month.month, 1)
monthly_entries = [e for e in self.entries
if datetime.datetime.strptime(e["date"], "%Y-%m-%d") >= first_day]
if len(monthly_entries) < 3:
print("\nNot enough entries this month for a meaningful review.")
return
# Compile monthly data
monthly_moods = [e["mood"] for e in monthly_entries]
avg_mood = sum(monthly_moods) / len(monthly_moods)
high_mood = max(monthly_moods)
low_mood = min(monthly_moods)
high_entry = next(e for e in monthly_entries if e["mood"] == high_mood)
low_entry = next(e for e in monthly_entries if e["mood"] == low_mood)
# Get all activities and tags
all_activities = []
all_tags = []
for entry in monthly_entries:
all_activities.extend(entry.get("activities", []))
all_tags.extend(entry.get("tags", []))
# Count frequencies
activity_counts = Counter(all_activities)
tag_counts = Counter(all_tags)
# Display monthly review
print("\n=== Monthly Review ===")
print(f"Month: {first_day.strftime('%B %Y')}")
print(f"Number of entries: {len(monthly_entries)}")
print(f"Average mood: {avg_mood:.1f}/10")
print(f"Highest mood: {high_mood}/10 on {high_entry['date']}")
print(f"Lowest mood: {low_mood}/10 on {low_entry['date']}")
if activity_counts:
print("\nTop activities this month:")
for activity, count in activity_counts.most_common(5):
print(f"- {activity}: {count} times")
if tag_counts:
print("\nTop themes/tags this month:")
for tag, count in tag_counts.most_common(5):
print(f"- {tag}: {count} times")
# Generate AI review
entries_summary = "\n".join([
f"Date: {e['date']}, Mood: {e['mood']}/10, Energy: {e['energy']}/10, "
f"Activities: {', '.join(e.get('activities', []))}, Tags: {', '.join(e.get('tags', []))}"
for e in monthly_entries
])
prompt = f"""
Here's a summary of the user's journal entries for {first_day.strftime('%B %Y')}:
{entries_summary}
Please provide a monthly review that includes:
1. Overall patterns in mood and energy
2. Key achievements or challenges that stood out
3. Activities that seemed most beneficial
4. Gentle suggestions for next month
5. A thoughtful reflection question
Keep your response supportive and actionable (250 words max).
"""
insights = get_response(prompt,
system="You are an insightful journaling assistant who helps provide monthly reviews and insights.")
print("\nAI Monthly Review:")
print(insights)
def _custom_date_analysis(self):
"""Generate insights for a custom date range"""
print("\n=== Custom Date Range Analysis ===")
# Get start date
start_date_str = input("Enter start date (YYYY-MM-DD): ")
try:
start_date = datetime.datetime.strptime(start_date_str, "%Y-%m-%d").date()
except ValueError:
print("Invalid date format. Using 30 days ago as default.")
start_date = datetime.datetime.now().date() - datetime.timedelta(days=30)
# Get end date
end_date_str = input("Enter end date (YYYY-MM-DD) or press Enter for today: ")
if end_date_str:
try:
end_date = datetime.datetime.strptime(end_date_str, "%Y-%m-%d").date()
except ValueError:
print("Invalid date format. Using today as default.")
end_date = datetime.datetime.now().date()
else:
end_date = datetime.datetime.now().date()
# Validate date range
if start_date > end_date:
print("Start date must be before end date. Swapping dates.")
start_date, end_date = end_date, start_date
# Filter entries by date range
filtered_entries = [e for e in self.entries
if start_date <= datetime.datetime.strptime(e["date"], "%Y-%m-%d").date() <= end_date]
if len(filtered_entries) < 2:
print(f"\nNot enough entries between {start_date} and {end_date} for analysis.")
return
# Display basic stats
moods = [e["mood"] for e in filtered_entries]
avg_mood = sum(moods) / len(moods)
print(f"\nPeriod: {start_date} to {end_date}")
print(f"Number of entries: {len(filtered_entries)}")
print(f"Average mood: {avg_mood:.1f}/10")
# Create mood trend visualization
dates = [datetime.datetime.strptime(e["date"], "%Y-%m-%d") for e in filtered_entries]
moods = [e["mood"] for e in filtered_entries]
energy = [e["energy"] for e in filtered_entries]
plt.figure(figsize=(12, 6))
plt.plot(dates, moods, 'b-o', label='Mood')
plt.plot(dates, energy, 'r-o', label='Energy')
plt.axhline(y=5, color='g', linestyle='--', alpha=0.3, label='Neutral')
plt.title(f'Mood and Energy Trends ({start_date} to {end_date})')
plt.xlabel('Date')
plt.ylabel('Rating (1-10)')
plt.grid(True, alpha=0.3)
plt.legend()
# Format the x-axis to show dates nicely
plt.gcf().autofmt_xdate()
plt.tight_layout()
# Save the visualization
viz_path = os.path.join(self.visualization_dir,
f"custom_trend_{start_date}_{end_date}.png")
plt.savefig(viz_path)
print(f"\nCustom trend visualization saved to: {viz_path}")
# Generate AI insights
entries_summary = "\n".join([
f"Date: {e['date']}, Mood: {e['mood']}/10, Energy: {e['energy']}/10, "
f"Activities: {', '.join(e.get('activities', []))}, Reflection: {e['reflection'][:100]}..."
for e in filtered_entries
])
prompt = f"""
Here's a summary of the user's journal entries from {start_date} to {end_date}:
{entries_summary}
Please provide a custom period analysis that includes:
1. Notable trends or patterns in mood and energy
2. Key themes or recurring topics
3. Activities that appeared to influence mood
4. A thoughtful observation about this period
Keep your response insightful and supportive (200 words max).
"""
insights = get_response(prompt,
system="You are an analytical journaling assistant who helps identify patterns across custom time periods.")
print("\nAI Period Analysis:")
print(insights)
def manage_prompts(self):
"""Manage custom prompts"""
print("\n=== Manage Custom Prompts ===")
print("1. View all prompts")
print("2. Add new prompt")
print("3. Delete prompt")
print("4. Return to main menu")
choice = input("\nWhat would you like to do? (1-4): ")
if choice == "1":
# View all prompts
print("\n=== Available Prompts ===")
print("Default prompts:")
for i, prompt in enumerate(self.default_prompts, 1):
print(f"{i}. {prompt}")
if self.custom_prompts:
print("\nCustom prompts:")
for i, prompt in enumerate(self.custom_prompts, 1):
print(f"{i}. {prompt}")
else:
print("\nNo custom prompts yet.")
elif choice == "2":
# Add new prompt
new_prompt = input("\nEnter a new journal prompt: ")
if new_prompt:
self.custom_prompts.append(new_prompt)
self._save_custom_prompts()
print("Custom prompt added successfully!")
elif choice == "3":
# Delete prompt
if not self.custom_prompts:
print("\nNo custom prompts to delete.")
self.manage_prompts()
return
print("\nCustom prompts:")
for i, prompt in enumerate(self.custom_prompts, 1):
print(f"{i}. {prompt}")
try:
idx = int(input("\nEnter the number of the prompt to delete: ")) - 1
if 0 <= idx < len(self.custom_prompts):
deleted = self.custom_prompts.pop(idx)
self._save_custom_prompts()
print(f"Deleted prompt: {deleted}")
else:
print("Invalid prompt number.")
except ValueError:
print("Invalid input. Please enter a number.")
elif choice == "4":
self.display_menu()
return
else:
print("Invalid choice.")
# Return to prompt management menu
self.manage_prompts()
def set_goals(self):
"""Set goals and intentions"""
print("\n=== Goals and Intentions ===")
print("1. View current goals")
print("2. Add new goal")
print("3. Update goal progress")
print("4. Delete goal")
print("5. Generate goal suggestions")
print("6. Return to main menu")
# Load goals from file
goals_file = os.path.join(self.journal_dir, "goals.json")
if os.path.exists(goals_file):
try:
with open(goals_file, 'r') as f:
goals = json.load(f)
except json.JSONDecodeError:
goals = []
else:
goals = []
choice = input("\nWhat would you like to do? (1-6): ")
if choice == "1":
# View current goals
if not goals:
print("\nNo goals found.")
else:
print("\n=== Current Goals ===")
for i, goal in enumerate(goals, 1):
status = f"{goal['progress']}% complete" if 'progress' in goal else "Not started"
target_date = goal.get('target_date', 'No target date')
print(f"{i}. {goal['description']} - {status} - Target: {target_date}")
if 'milestones' in goal and goal['milestones']:
print(" Milestones:")
for m in goal['milestones']:
check = "✓" if m.get('completed', False) else "○"
print(f" {check} {m['description']}")
elif choice == "2":
# Add new goal
print("\n=== Add New Goal ===")
description = input("Goal description: ")
if not description:
print("Goal description cannot be empty.")
self.set_goals()
return
target_date = input("Target date (YYYY-MM-DD) or leave blank: ")
if target_date:
try:
# Validate date format
datetime.datetime.strptime(target_date, "%Y-%m-%d")
except ValueError:
print("Invalid date format. Using no target date.")
target_date = ""
# Create goal object
goal = {
"description": description,
"created_date": datetime.datetime.now().strftime("%Y-%m-%d"),
"progress": 0
}
if target_date:
goal["target_date"] = target_date
# Ask for milestones
add_milestones = input("Would you like to add milestones? (y/n): ").lower()
if add_milestones == 'y':
milestones = []
while True:
milestone = input("Enter milestone (or leave blank to finish): ")
if not milestone:
break
milestones.append({"description": milestone, "completed": False})
if milestones:
goal["milestones"] = milestones
# Add goal to list and save
goals.append(goal)
with open(goals_file, 'w') as f:
json.dump(goals, f, indent=2)
print("Goal added successfully!")
elif choice == "3":
# Update goal progress
if not goals:
print("\nNo goals found.")
self.set_goals()
return
print("\n=== Update Goal Progress ===")
for i, goal in enumerate(goals, 1):
status = f"{goal['progress']}% complete" if 'progress' in goal else "Not started"
print(f"{i}. {goal['description']} - {status}")
try:
idx = int(input("\nEnter the number of the goal to update: ")) - 1
if 0 <= idx < len(goals):
goal = goals[idx]
print(f"\nUpdating goal: {goal['description']}")
# Update progress
try:
new_progress = int(input(f"Enter new progress (0-100) [current: {goal.get('progress', 0)}%]: "))
if 0 <= new_progress <= 100:
goal['progress'] = new_progress
else:
print("Progress must be between 0 and 100.")
except ValueError:
print("Invalid input. Progress not updated.")
# Update milestones if they exist
if 'milestones' in goal and goal['milestones']:
print("\nUpdate milestones:")
for i, milestone in enumerate(goal['milestones'], 1):
status = "Completed" if milestone.get('completed', False) else "Not completed"
print(f"{i}. {milestone['description']} - {status}")
update = input(f"Mark as {'not ' if milestone.get('completed', False) else ''}completed? (y/n): ").lower()
if update == 'y':
milestone['completed'] = not milestone.get('completed', False)
# Save updated goals
with open(goals_file, 'w') as f:
json.dump(goals, f, indent=2)
print("Goal updated successfully!")
else:
print("Invalid goal number.")
except ValueError:
print("Invalid input. Please enter a number.")
elif choice == "4":
# Delete goal
if not goals:
print("\nNo goals found.")
self.set_goals()
return
print("\n=== Delete Goal ===")
for i, goal in enumerate(goals, 1):
print(f"{i}. {goal['description']}")
try:
idx = int(input("\nEnter the number of the goal to delete: ")) - 1
if 0 <= idx < len(goals):
goal = goals.pop(idx)
# Save updated goals
with open(goals_file, 'w') as f:
json.dump(goals, f, indent=2)
print(f"Goal '{goal['description']}' deleted successfully!")
else:
print("Invalid goal number.")
except ValueError:
print("Invalid input. Please enter a number.")
elif choice == "5":
# Generate goal suggestions
if not self.entries or len(self.entries) < 5:
print("\nNot enough journal entries to generate meaningful goal suggestions.")
self.set_goals()
return
print("\nGenerating goal suggestions based on your journal entries...")
# Get recent entries
recent_entries = self.entries[-15:] # Last 15 entries
entries_text = "\n\n".join([
f"Date: {e['date']}\nMood: {e['mood']}/10\nActivities: {', '.join(e.get('activities', []))}\n"
f"Tags: {', '.join(e.get('tags', []))}\nReflection: {e['reflection']}"
for e in recent_entries
])
prompt = f"""
Here are some recent journal entries from the user:
{entries_text}
Based on these entries, suggest 3-5 meaningful goals or intentions that might help the user's wellbeing or personal growth. For each goal:
1. Provide a clear, actionable description
2. Explain briefly why this goal might be beneficial (based on patterns in their journal)
3. Suggest 2-3 possible milestones for the goal
Make the goals specific, measurable, and aligned with what seems to matter to the user.
"""
suggestions = get_response(prompt,
system="You are a supportive goal-setting assistant who helps users identify meaningful goals based on their journal patterns.")
print("\n=== Goal Suggestions ===")
print(suggestions)
elif choice == "6":
self.display_menu()
return
else:
print("Invalid choice.")
# Return to goals menu
self.set_goals()
def set_preferences(self):
"""Set user preferences"""
print("\n=== Preferences ===")
print(f"1. Auto-generate insights: {self.preferences['auto_insights']}")
print(f"2. Daily reminder: {self.preferences['daily_reminder']}")
print(f"3. Reminder time: {self.preferences['reminder_time']}")
print(f"4. Theme: {self.preferences['theme']}")
print(f"5. Insight frequency: {self.preferences['insight_frequency']}")
print("6. Return to main menu")
choice = input("\nWhat would you like to change? (1-6): ")
if choice == "1":
# Toggle auto-insights
self.preferences['auto_insights'] = not self.preferences['auto_insights']
print(f"Auto-generate insights: {self.preferences['auto_insights']}")
elif choice == "2":
# Toggle daily reminder
self.preferences['daily_reminder'] = not self.preferences['daily_reminder']
print(f"Daily reminder: {self.preferences['daily_reminder']}")
elif choice == "3":
# Set reminder time
time_str = input("Enter reminder time (HH:MM): ")
try:
# Validate time format
datetime.datetime.strptime(time_str, "%H:%M")
self.preferences['reminder_time'] = time_str
print(f"Reminder time set to: {time_str}")
except ValueError:
print("Invalid time format. Using previous setting.")
elif choice == "4":
# Set theme
print("\nAvailable themes:")
themes = ["standard", "dark", "light", "colorful"]
for i, theme in enumerate(themes, 1):
print(f"{i}. {theme}")
try:
idx = int(input("\nSelect theme number: ")) - 1
if 0 <= idx < len(themes):
self.preferences['theme'] = themes[idx]
print(f"Theme set to: {themes[idx]}")
else:
print("Invalid theme number.")
except ValueError:
print("Invalid input. Theme not changed.")
elif choice == "5":
# Set insight frequency
print("\nInsight frequency options:")
frequencies = ["daily", "weekly", "monthly"]
for i, freq in enumerate(frequencies, 1):
print(f"{i}. {freq}")
try:
idx = int(input("\nSelect frequency number: ")) - 1
if 0 <= idx < len(frequencies):
self.preferences['insight_frequency'] = frequencies[idx]
print(f"Insight frequency set to: {frequencies[idx]}")
else:
print("Invalid frequency number.")
except ValueError:
print("Invalid input. Frequency not changed.")
elif choice == "6":
self._save_preferences()
self.display_menu()
return
else:
print("Invalid choice.")
# Save preferences after any change
self._save_preferences()
# Return to preferences menu
self.set_preferences()
def export_journal(self):
"""Export journal to different formats"""
if not self.entries:
print("\nNo journal entries to export.")
input("\nPress Enter to continue...")
self.display_menu()
return
print("\n=== Export Journal ===")
print("1. Export as text file")
print("2. Export as CSV")
print("3. Export as PDF (plain)")
print("4. Export with visualizations")
print("5. Return to main menu")
choice = input("\nChoose export format (1-5): ")
if choice == "1":
# Export as text
export_path = os.path.join(self.journal_dir, f"journal_export_{datetime.datetime.now().strftime('%Y%m%d')}.txt")
with open(export_path, 'w') as f:
f.write("===== JOURNAL EXPORT =====\n\n")
for entry in sorted(self.entries, key=lambda e: e["date"]):
f.write(f"Date: {entry['date']} at {entry.get('time', '00:00')}\n")
f.write(f"Mood: {entry['mood']}/10 - {entry.get('mood_label', '')}\n")
f.write(f"Energy: {entry.get('energy', 'N/A')}/10\n\n")
f.write(f"Prompt: {entry.get('prompt', 'No prompt')}\n\n")
f.write(f"Reflection:\n{entry['reflection']}\n\n")
if entry.get('activities'):
f.write(f"Activities: {', '.join(entry['activities'])}\n")
if entry.get('tags'):
f.write(f"Tags: {', '.join(entry['tags'])}\n")
f.write("\n" + "=" * 50 + "\n\n")
print(f"\nJournal exported to: {export_path}")
elif choice == "2":
# Export as CSV
export_path = os.path.join(self.journal_dir, f"journal_export_{datetime.datetime.now().strftime('%Y%m%d')}.csv")
with open(export_path, 'w') as f:
# Write header
f.write("date,time,mood,energy,prompt,reflection,activities,tags\n")
# Write entries
for entry in self.entries:
date = entry['date']
time = entry.get('time', '')
mood = entry['mood']
energy = entry.get('energy', '')
prompt = entry.get('prompt', '').replace('"', '""')
reflection = entry['reflection'].replace('"', '""').replace('\n', ' ')
activities = "|".join(entry.get('activities', []))
tags = "|".join(entry.get('tags', []))
f.write(f'"{date}","{time}",{mood},{energy},"{prompt}","{reflection}","{activities}","{tags}"\n')
print(f"\nJournal exported to: {export_path}")
elif choice == "3" or choice == "4":
# Export as PDF (would require additional libraries like reportlab)
print("\nPDF export would require additional libraries not included in this example.")
print("In a full implementation, this would generate a formatted PDF document.")
elif choice == "5":
self.display_menu()
return
else:
print("Invalid choice.")
# Return to export menu
input("\nPress Enter to continue...")
self.export_journal()
def exit_app(self):
"""Exit the application"""
print("\nThank you for using Advanced Journal Assistant. Goodbye!")
exit()
def main():
"""Main function to run the application"""
journal = AdvancedJournalAssistant()
journal.display_menu()
if __name__ == "__main__":
main()
Extension Ideas¶
- Data Visualisation Enhancements: Add interactive visualisations using libraries like Plotly or Bokeh
- Natural Language Processing: Implement sentiment analysis to automatically detect the emotional tone of entries
- Machine Learning Integration: Build a recommendation system that suggests activities based on past mood correlations
- Multiple Journaling Modes: Add specialised templates for gratitude journaling, goal tracking, habit formation, etc.
- Social Features: Add optional sharing of insights (anonymised) with trusted friends or mentors
- Integrations: Connect with other applications like calendar, fitness trackers, or meditation apps
- Mobile Compatibility: Create a companion mobile app for on-the-go journaling
- Voice Journaling: Add speech-to-text functionality for verbal journaling
- Export Options: Enhanced export formats including PDF with embedded visualisations, interactive web formats
- Cloud Sync: Add secure cloud synchronisation for access across multiple devices
- Guided Journaling Sessions: AI-guided journaling sessions focused on specific topics or goals