mirror of
https://github.com/we-promise/sure.git
synced 2026-05-07 12:54:04 +00:00
- Add `Setting.insights_enabled` feature gate at top of GenerateInsightsJob#perform - Fix race condition in upsert_insight with recursive retry on RecordNotUnique - Use Provider::Openai.effective_model instead of hardcoded DEFAULT_MODEL - Rescue Money::Currency::UnknownCurrency in currency_symbol fallback - Fix budget dedup key to stable "budget_pacing:<date>" (prevents flip-flop between at_risk/on_track) - Fix cash flow projection off-by-one: iterate (1..PROJECTION_DAYS) not (0..PROJECTION_DAYS) - Eliminate N+1 in IdleCashGenerator by precomputing active_account_ids in one SQL query - Rename all_time_high -> thirty_day_high (series is 30-day, not truly all-time) - Update net_worth_milestone i18n: title_ath -> title_30d_high, "30-day high" language - Anchor SpendingAnomalyGenerator baseline to current_period.start_date (not calendar months) - Fix spending_anomaly dedup key to use current_period.start_date instead of Date.current - Add .order(last_occurrence_date: :asc) to SubscriptionAuditGenerator for deterministic results - Stub Setting.insights_enabled in job tests; add generated_at assertion to upsert test https://claude.ai/code/session_014vY9xohpm3abSAxVxRF27a
67 lines
1.8 KiB
Ruby
67 lines
1.8 KiB
Ruby
# Base class for all insight generators.
|
|
#
|
|
# Subclasses must implement #generate, returning an Array<GeneratedInsight>.
|
|
# Financial reasoning is done in pure Ruby using existing analytics infrastructure.
|
|
# The LLM is invoked only to write the human-readable body text.
|
|
class Insight::Generator
|
|
GeneratedInsight = Data.define(
|
|
:insight_type,
|
|
:priority,
|
|
:title,
|
|
:body,
|
|
:metadata,
|
|
:currency,
|
|
:period_start,
|
|
:period_end,
|
|
:dedup_key
|
|
)
|
|
|
|
attr_reader :family
|
|
|
|
def initialize(family)
|
|
@family = family
|
|
end
|
|
|
|
def generate
|
|
raise NotImplementedError, "#{self.class.name} must implement #generate"
|
|
end
|
|
|
|
private
|
|
def llm
|
|
@llm ||= Provider::Registry.get_provider(:openai)
|
|
end
|
|
|
|
# Generates a 1-2 sentence natural-language explanation using the LLM.
|
|
# Falls back to a bare template string if no LLM is configured.
|
|
def generate_body(prompt)
|
|
return prompt unless llm
|
|
|
|
response = llm.chat_response(
|
|
prompt,
|
|
model: Provider::Openai.effective_model,
|
|
instructions: system_instructions
|
|
)
|
|
|
|
response.messages.first&.output_text&.strip.presence || prompt
|
|
rescue => e
|
|
Rails.logger.warn("[Insight::Generator] LLM body generation failed: #{e.message}")
|
|
prompt
|
|
end
|
|
|
|
def system_instructions
|
|
sym = currency_symbol
|
|
<<~PROMPT
|
|
You are a concise financial insights writer for a personal finance app.
|
|
Write exactly 1-2 sentences in plain, conversational English.
|
|
Be specific with numbers. Use #{sym} for currency amounts.
|
|
Do not use jargon, emoji, or give investment advice.
|
|
PROMPT
|
|
end
|
|
|
|
def currency_symbol
|
|
Money::Currency.new(family.currency).symbol
|
|
rescue Money::Currency::UnknownCurrency
|
|
family.currency
|
|
end
|
|
end
|