mirror of
https://github.com/we-promise/sure.git
synced 2026-05-12 23:25:00 +00:00
Shifts the AI assistant from reactive (users ask questions) to proactive (the system surfaces personalized insights automatically). A nightly job analyzes every family's financial data across 7 insight types, writes natural-language explanations via Claude, and surfaces them in a feed on the dashboard and a standalone /insights page. Feature is behind a flag: off by default, enable with INSIGHTS_ENABLED=1 (or via Setting.insights_enabled in the admin UI). Insight types: - spending_anomaly: category spend >25% above/below 3-month rolling average - cash_flow_warning: projected cash balance drops below $500 in 30 days (uses RecurringTransaction + statistical daily baseline) - net_worth_milestone: crossed a round-number milestone or all-time high - subscription_audit: recurring transaction overdue by 45+ days - savings_rate_change: savings rate changed >5 percentage points vs last month - idle_cash: $5k+ sitting in depository account with no activity in 60 days - budget_at_risk / budget_on_track: spending pace vs monthly budget Architecture: - Insight model with dedup_key unique index (upsert, not re-create daily) - Insight::Generator base class + Insight::GeneratorRegistry orchestrator - LLM used as a writer only — financial math runs in pure Ruby - GenerateInsightsJob runs at 6 AM UTC daily via sidekiq-cron - InsightsController with read/dismiss Turbo Stream actions - Dashboard section gated by Current.user.insights_enabled? https://claude.ai/code/session_014vY9xohpm3abSAxVxRF27a
29 lines
813 B
Ruby
29 lines
813 B
Ruby
module InsightsHelper
|
|
INSIGHT_ICONS = {
|
|
"spending_anomaly" => "trending-up",
|
|
"cash_flow_warning" => "alert-triangle",
|
|
"net_worth_milestone" => "trophy",
|
|
"subscription_audit" => "credit-card",
|
|
"savings_rate_change" => "piggy-bank",
|
|
"idle_cash" => "clock",
|
|
"budget_on_track" => "check-circle",
|
|
"budget_at_risk" => "alert-circle"
|
|
}.freeze
|
|
|
|
def insight_icon(insight_type)
|
|
INSIGHT_ICONS.fetch(insight_type.to_s, "zap")
|
|
end
|
|
|
|
def insight_icon_color_class(priority)
|
|
case priority.to_s
|
|
when "high" then "text-destructive"
|
|
when "medium" then "text-warning"
|
|
else "text-secondary"
|
|
end
|
|
end
|
|
|
|
def insight_priority_label(priority)
|
|
I18n.t("insights.priority.#{priority}", default: priority.to_s.humanize)
|
|
end
|
|
end
|