Daily LLM classification of every chat (topics + product tags + deal won/lost/undecided) aggregated into immutable daily metrics, with a filterable admin report dashboard, product-catalog import (text/CSV/XLSX), weekly persona summary, and an approval flow (LINE -> Telegram -> webhook) for applying persona recommendations. - Llm::AnalyticsClassifier: per-account openai -> Captain fallback cascade - AccountDailyProcessor + Conversation/CustomerDailyMetric aggregation - ReportService + DrilldownService (summary + deep filterable drilldown) - AnalyticsReports.vue + productCatalog import UI (admin-only) - WeeklyPersonaEvaluator + PersonaApprovalService (LINE/Telegram/webhook) - Weekly cron (Mon 10:00) + daily cron (02:30)
36 lines
1.3 KiB
Ruby
36 lines
1.3 KiB
Ruby
# Daily analytics aggregation driver (part2b-iii).
|
|
#
|
|
# Runs once daily (see config/schedule.yml) and pushes each account's closed-day
|
|
# conversation metrics through Analytics::AccountDailyProcessor: classify every
|
|
# active conversation via the LLM, write the classification tags onto the live
|
|
# conversation (re-tagging prior M2-managed tags), and aggregate into the
|
|
# immutable daily metrics tables.
|
|
#
|
|
# One account failing (e.g. an LLM/credential error) never blocks the others.
|
|
class Analytics::DailyMetricsJob < ApplicationJob
|
|
queue_as :scheduled_jobs
|
|
|
|
def perform
|
|
Account.find_each do |account|
|
|
process_account(account)
|
|
rescue StandardError => e
|
|
Rails.logger.error("[Analytics::DailyMetricsJob] account=#{account.id} #{e.class}: #{e.message}")
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def process_account(account)
|
|
date = closed_day_in_account_tz(account)
|
|
Analytics::AccountDailyProcessor.perform(account: account, date: date)
|
|
end
|
|
|
|
# The most recent fully-completed day in the account's reporting timezone.
|
|
def closed_day_in_account_tz(account)
|
|
tz = account.reporting_timezone.presence
|
|
zone = tz ? ActiveSupport::TimeZone[tz] : Time.zone
|
|
current_in_zone = zone ? Time.current.in_time_zone(zone) : Time.current
|
|
(current_in_zone - 1.day).to_date
|
|
end
|
|
end
|