AQUATHISTLE / analytics.py
ckharche's picture
Update analytics.py
bb2126e verified
"""
analytics.py
Analytics helper functions for Aqua Thistle prototype
Handles exam/test score analysis, progress tracking, insights generation
Added:
- Scenario comparison
- Query parsing for what-if analysis
- Proactive insights generation
- Interactive chart support
"""
import re
def analyze_exam_performance(profile):
"""Analyze user's last exam performance and return insights"""
scores = profile.get("last_scores", {})
recent_exam = profile.get("recent_exam", 0)
goal = profile.get("goal", 1600)
total_points = sum(scores.values())
score_percentages = {k: round((v/total_points)*100, 1) if total_points > 0 else 0 for k, v in scores.items()}
weakest_subject = min(scores, key=scores.get) if scores else None
strongest_subject = max(scores, key=scores.get) if scores else None
progress_pct = round((recent_exam/goal)*100, 1) if goal > 0 else 0
points_needed = max(0, goal - recent_exam)
insights = []
if profile.get("rushing"):
insights.append("You're rushing questions near the end. Practice fundamentals to save time.")
if profile.get("math_weakness"):
insights.append("Math scores have most room for growth. Focus practice here.")
if recent_exam >= 1100:
insights.append(f"Reading score improved 15% over last 3 months! 🎉")
return {
"total_score": recent_exam,
"goal_score": goal,
"progress_pct": progress_pct,
"points_needed": points_needed,
"weakest_subject": weakest_subject,
"strongest_subject": strongest_subject,
"score_breakdown": scores,
"score_percentages": score_percentages,
"insights": insights,
"expected_range": (max(1150, recent_exam-50), min(1320, recent_exam+120))
}
def calculate_budget_metrics(profile):
"""Calculate financial/budget metrics for dashboard"""
budget = profile.get("budget", 2000)
spend = profile.get("spend", 0)
remaining = budget - spend
spend_pct = round((spend/budget)*100, 1) if budget > 0 else 0
categories = {
"Savings": 1000,
"Food": 400,
"Leisure": 176
}
potential_savings = []
if categories["Food"] > 300:
potential_savings.append("Cut 15% on food costs (meal prep)")
if categories["Leisure"] > 150:
potential_savings.append("Reduce leisure spending by $50/month")
return {
"budget": budget,
"spent": spend,
"remaining": remaining,
"spend_percentage": spend_pct,
"categories": categories,
"potential_savings": potential_savings,
"on_track": spend_pct <= 75
}
def parse_budget_query(query):
"""
Parse user queries for budget changes.
Returns dict with changes or None if no budget query detected.
Examples:
- "What if I save $200 more?" → {"savings": 200}
- "Show me if I spend $500 less on food" → {"food": -500}
- "How about $300 extra income?" → {"budget": 300}
"""
query_lower = query.lower()
# Check if it's a what-if/scenario query
scenario_keywords = ["what if", "show me if", "how about", "suppose", "imagine", "if i"]
if not any(keyword in query_lower for keyword in scenario_keywords):
return None
changes = {}
# Parse amounts (e.g., $200, 200 dollars, $1,000)
amount_patterns = [
r'\$(\d{1,3}(?:,\d{3})*(?:\.\d{2})?)', # $200 or $1,000.00
r'(\d{1,3}(?:,\d{3})*)\s*dollars?', # 200 dollars
]
amounts = []
for pattern in amount_patterns:
matches = re.findall(pattern, query_lower)
amounts.extend([float(m.replace(',', '')) for m in matches])
if not amounts:
return None
# Determine direction (increase/decrease)
is_increase = any(word in query_lower for word in ["more", "extra", "increase", "add", "save"])
is_decrease = any(word in query_lower for word in ["less", "reduce", "cut", "decrease", "lower"])
multiplier = 1 if is_increase else -1 if is_decrease else 0
# Determine category
if any(word in query_lower for word in ["food", "eating", "meal", "grocery"]):
changes["food"] = amounts[0] * multiplier
elif any(word in query_lower for word in ["leisure", "entertainment", "fun", "hobby"]):
changes["leisure"] = amounts[0] * multiplier
elif any(word in query_lower for word in ["save", "saving"]):
changes["savings"] = amounts[0] * multiplier
elif any(word in query_lower for word in ["budget", "income", "earn"]):
changes["budget"] = amounts[0] * multiplier
elif any(word in query_lower for word in ["spend", "spending"]):
changes["total_spend"] = amounts[0] * multiplier
else:
# Generic change
changes["general"] = amounts[0] * multiplier
return changes if changes else None
def apply_scenario_changes(profile, changes):
"""
Apply scenario changes to profile and return modified copy.
Original profile is NOT modified.
"""
scenario_profile = profile.copy()
# Deep copy nested dicts
if "last_scores" in scenario_profile:
scenario_profile["last_scores"] = profile["last_scores"].copy()
if "goals_today" in scenario_profile:
scenario_profile["goals_today"] = profile["goals_today"].copy()
if "budget" in changes:
scenario_profile["budget"] = profile["budget"] + changes["budget"]
if "total_spend" in changes:
scenario_profile["spend"] = profile["spend"] + changes["total_spend"]
if "savings" in changes:
# More savings = less spending
scenario_profile["spend"] = profile["spend"] - changes["savings"]
if "food" in changes:
scenario_profile["spend"] = profile["spend"] + changes["food"]
if "leisure" in changes:
scenario_profile["spend"] = profile["spend"] + changes["leisure"]
# Ensure spend doesn't go negative
scenario_profile["spend"] = max(0, scenario_profile["spend"])
return scenario_profile
def compare_scenarios(current_profile, scenario_profile):
"""
Compare current vs scenario profiles.
Returns dict with differences and insights.
"""
current_metrics = calculate_budget_metrics(current_profile)
scenario_metrics = calculate_budget_metrics(scenario_profile)
diff_spend = scenario_profile["spend"] - current_profile["spend"]
diff_budget = scenario_profile["budget"] - current_profile["budget"]
diff_remaining = scenario_metrics["remaining"] - current_metrics["remaining"]
insights = []
if diff_spend < 0:
insights.append(f"✅ You'd save {abs(diff_spend):.0f} dollars monthly")
elif diff_spend > 0:
insights.append(f"⚠️ Spending would increase by {diff_spend:.0f} dollars")
if diff_remaining > 0:
insights.append(f"💰 {diff_remaining:.0f} dollars more remaining at month end")
elif diff_remaining < 0:
insights.append(f"📉 {abs(diff_remaining):.0f} dollars less remaining")
if scenario_metrics["on_track"] and not current_metrics["on_track"]:
insights.append("🎯 This would put you back on track!")
elif not scenario_metrics["on_track"] and current_metrics["on_track"]:
insights.append("⚠️ This would put you over budget")
return {
"current": current_metrics,
"scenario": scenario_metrics,
"differences": {
"spend": diff_spend,
"budget": diff_budget,
"remaining": diff_remaining
},
"insights": insights
}
def generate_proactive_insights(profile, category):
"""
Generate proactive insights based on profile analysis.
These appear automatically, not just in response to queries.
"""
insights = []
if category == "Finance":
budget_metrics = calculate_budget_metrics(profile)
spend_pct = budget_metrics["spend_percentage"]
# Critical threshold
if spend_pct > 90:
insights.append({
"type": "error",
"icon": "🚨",
"text": f"You've spent {spend_pct}% of budget - urgent action needed",
"action": "What can I cut immediately?"
})
# Warning threshold
elif spend_pct > 75:
insights.append({
"type": "warning",
"icon": "⚠️",
"text": f"At {spend_pct}% of budget - pace yourself",
"action": "Show me where I can save"
})
# Good standing
elif spend_pct < 60:
insights.append({
"type": "success",
"icon": "✅",
"text": f"Great job! Only {spend_pct}% spent - you're ahead",
"action": "How much could I save this month?"
})
# Savings opportunity
remaining = budget_metrics["remaining"]
if remaining > 500:
insights.append({
"type": "opportunity",
"icon": "💡",
"text": f"You have {remaining:.0f} dollars left - invest in savings?",
"action": "Best high-yield accounts?"
})
elif category == "Education":
exam_data = analyze_exam_performance(profile)
progress = exam_data["progress_pct"]
if progress < 70:
insights.append({
"type": "warning",
"icon": "📚",
"text": f"At {progress}% of goal - need {exam_data['points_needed']} more points",
"action": "How can I improve fastest?"
})
elif progress >= 90:
insights.append({
"type": "success",
"icon": "🎯",
"text": f"Almost there! {progress}% to goal",
"action": "Final push strategies?"
})
if exam_data["weakest_subject"]:
weak_score = exam_data["score_breakdown"][exam_data["weakest_subject"]]
if weak_score < 120:
insights.append({
"type": "opportunity",
"icon": "💪",
"text": f"{exam_data['weakest_subject']} is weakest - most room for growth",
"action": f"Practice {exam_data['weakest_subject']} questions"
})
return insights
def get_daily_goals_summary(profile):
"""Summarize daily goal progress"""
goals = profile.get("goals_today", {})
completed = sum(1 for v in goals.values() if v >= 100)
in_progress = sum(1 for v in goals.values() if 50 <= v < 100)
needs_attention = sum(1 for v in goals.values() if v < 50)
avg_completion = round(sum(goals.values()) / len(goals), 1) if goals else 0
return {
"total_goals": len(goals),
"completed": completed,
"in_progress": in_progress,
"needs_attention": needs_attention,
"avg_completion": avg_completion,
"goals": goals
}
def generate_practice_questions(subject, difficulty="medium"):
"""Generate practice questions based on weak subject areas"""
question_bank = {
"Reading": [
"Read passage about climate change and identify main argument",
"Analyze author's tone in historical narrative",
"Compare two texts on same topic, find common themes"
],
"Writing": [
"Fix grammar errors in paragraph about economics",
"Improve sentence structure for clarity",
"Choose best transition word for essay flow"
],
"Reasoning": [
"Solve logic puzzle with 3 variables",
"Identify pattern in number sequence",
"Draw conclusion from given premises"
],
"Algebra": [
"Solve quadratic equation: x² + 5x - 14 = 0",
"Find slope of line through points (2,3) and (5,9)",
"Simplify expression: (3x² - 2x + 1) - (x² + 4x - 3)"
],
"Geometry": [
"Calculate area of triangle given base=8, height=6",
"Find angle measure in complementary angle pair",
"Determine volume of cylinder with r=3, h=10"
]
}
return question_bank.get(subject, ["No questions available for this subject"])
def get_time_improvement_suggestions(profile):
"""Generate time management suggestions based on performance"""
suggestions = []
if profile.get("rushing"):
suggestions.append("Practice timed sections: 15 min sprints")
suggestions.append("Skip hard questions first, return later")
suggestions.append("Use process of elimination to save time")
if profile.get("math_weakness"):
suggestions.append("Memorize common formulas before test")
suggestions.append("Do 10 quick math drills daily")
suggestions.append("Take full-length practice test this weekend")
suggestions.append("Review mistakes immediately after practice")
return suggestions
def calculate_projected_improvement(current_score, weeks_until_test, study_hours_per_week):
"""Project score improvement based on study plan"""
total_study_hours = weeks_until_test * study_hours_per_week
projected_gain = (total_study_hours / 10) * 10
if current_score > 1400:
projected_gain *= 0.7
elif current_score > 1200:
projected_gain *= 0.85
projected_score = min(1600, current_score + int(projected_gain))
return {
"current": current_score,
"projected": projected_score,
"gain": projected_score - current_score,
"study_hours_needed": total_study_hours,
"confidence": "high" if study_hours_per_week >= 10 else "medium"
}
def get_category_insights(category, profile):
"""Generate category-specific insights"""
insights = []
if category == "Finance":
budget_data = calculate_budget_metrics(profile)
if not budget_data["on_track"]:
insights.append("⚠️ Spending at {}%, consider budget review".format(budget_data["spend_percentage"]))
insights.extend(budget_data["potential_savings"])
elif category == "Education":
exam_data = analyze_exam_performance(profile)
insights.extend(exam_data["insights"])
if exam_data["points_needed"] > 0:
insights.append(f"Need {exam_data['points_needed']} more points to reach goal")
elif category == "Family":
insights.append("Schedule weekly check-in calls")
insights.append("Send updates about school progress")
elif category == "Friends":
insights.append("Plan low-cost hangouts to stay connected")
insights.append("Join study groups for accountability")
elif category == "Weekend/Vacation":
insights.append("Research free local events")
insights.append("Budget $150-200 for weekend activities")
return insights