Class: GitFit::Export::AI
- Inherits:
-
Object
- Object
- GitFit::Export::AI
- Defined in:
- lib/git_fit/export/ai.rb
Overview
Builds insights.json — the AI narrative layer on top of stats (#40, git-fit#93). Contract (wiki/AI): generated_at / period / source_digest / model / language / summary_markdown / insights / coaching
Cost guardrails:
- input is summary-level (~2-5k tokens, ≤200 activity lines)
- source_digest unchanged vs existing file -> zero LLM calls
- per-scenario retry 1 -> warn + omit that section (never raises to caller)
Defined Under Namespace
Classes: ValidationError
Constant Summary collapse
- SCENARIOS =
%i[monthly_report insights coaching].freeze
- ACTIVITY_LIMIT =
200- INSIGHT_TYPES =
%w[trend anomaly pb consistency season].freeze
- INSIGHT_SEVERITIES =
%w[info positive warning].freeze
- COACHING_PRIORITIES =
%w[low medium high].freeze
Instance Method Summary collapse
-
#call ⇒ Object
Returns the written doc hash, or nil when skipped (empty db / digest match).
-
#initialize(db:, config:, client:, output:, period: 'monthly', language: nil) ⇒ AI
constructor
A new instance of AI.
Constructor Details
#initialize(db:, config:, client:, output:, period: 'monthly', language: nil) ⇒ AI
Returns a new instance of AI.
27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/git_fit/export/ai.rb', line 27 def initialize(db:, config:, client:, output:, period: 'monthly', language: nil) @db = db @config = config @client = client @output = output unless %w[monthly yearly].include?(period.to_s) raise ArgumentError, "AI: unknown period '#{period}'\n Fix: use --period monthly|yearly" end @period = period.to_s @language = language || config.ai_config['language'] || 'zh' end |
Instance Method Details
#call ⇒ Object
Returns the written doc hash, or nil when skipped (empty db / digest match).
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/git_fit/export/ai.rb', line 40 def call stats = Calculator.new(db: @db).call rows = activity_rows return warn_skip('no activities in database') if rows.empty? digest = source_digest(stats, rows) return warn_skip('insights unchanged (source_digest match)') if unchanged?(digest) sections = {} SCENARIOS.each do |scenario| sections[scenario] = generate_scenario(scenario, stats, rows) end doc = base_doc(digest) doc['summary_markdown'] = sections[:monthly_report]['summary_markdown'] if sections[:monthly_report] doc['insights'] = sections[:insights]['insights'] if sections[:insights] doc['coaching'] = sections[:coaching]['coaching'] if sections[:coaching] File.write(@output, "#{::JSON.pretty_generate(doc)}\n") doc end |