iachat/enterprise/app/jobs/captain/reports/ceo_digest_job.rb
Rodribm10 cfffea9c16 feat(captain): semantic memory fixes + roleta + reclamações + analytics
Consolida o trabalho desta branch de abril/2026 em um bloco pronto pra
testar em staging antes do merge pra main.

## Correções de memória semântica
- ExtractionService: Princípio Zero + Regra de Ouro (ação consumada vs intenção).
- Cenário Daniela_Reservas: Passo 0 de classificação (consulta/intenção/fora).

## Roleta da Sorte (end-to-end)
- Schema Supabase + 7 RPCs atômicas (server-side, idempotentes).
- Services: Offer, Redeem, WeeklyReport.
- Jobs: OfferRouletteJob (hook em ConfirmationService após Pix pago),
  NotifyRevealed + Scheduler de fallback.
- Tool manual GenerateRoletaLinkTool + endpoint público /roleta/notify.
- Dashboard /captain/roleta com Resgate + Relatório + anomaly detection.

## Cenário Reclamacoes_Ouvidoria
- Triagem P1-P4, framework LAST, Three-level listening, Self-check.
- Sem compensação material, detecção de cliente frustrado eleva prioridade.

## Analytics
- Funil de conversão /captain/funnel: 5 etapas via regex, zero LLM.
- Detector de churn via ChurnOutreach* (cron dias úteis 10h-17h BRT).

## Trabalho pré-existente incluído
- Captain Executive Reports (ceo_digest, mattermost_delivery).
- get_reserva_preco_tool, Lifecycle ajustes, Reservations UI polimentos.

## Outros
- .gitignore: patterns pra credenciais.
- Migrations de scenarios idempotentes.
- i18n completa pt_BR+en pra roleta/funnel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:36:25 -03:00

71 lines
2.4 KiB
Ruby

# Gera e entrega o CEO Digest semanal para cada conta que tiver configurado.
#
# Config por conta em account.custom_attributes['ceo_digest']:
# {
# "enabled": true,
# "mattermost_webhook_url": "https://mm.example.com/hooks/xxxxx",
# "mattermost_channel": "#executivo" # opcional
# }
#
# Fallback global via ENV (caso a conta não tenha config própria):
# CEO_DIGEST_MATTERMOST_WEBHOOK_URL
class Captain::Reports::CeoDigestJob < ApplicationJob
queue_as :scheduled_jobs
def perform(account_id = nil, period_start = nil, period_end = nil)
period_end = (period_end || Date.yesterday).to_date
period_start = (period_start || (period_end - 6.days)).to_date
scope = account_id ? Account.where(id: account_id) : Account.all
scope.find_each { |account| deliver_for(account, period_start, period_end) }
end
private
def deliver_for(account, period_start, period_end)
config = digest_config(account)
return log_skip(account, 'no webhook_url configured') if config[:webhook_url].blank?
return log_skip(account, 'disabled in custom_attributes') if config[:enabled] == false
digest = build_digest(account, period_start, period_end)
result = deliver_to_mattermost(digest, config)
log_result(account, result)
rescue StandardError => e
Rails.logger.error "[CeoDigest] unexpected error for account ##{account.id}: #{e.class} #{e.message}"
end
def build_digest(account, period_start, period_end)
Captain::Reports::CeoDigestService.new(
account: account, period_start: period_start, period_end: period_end
).call
end
def deliver_to_mattermost(digest, config)
Captain::Reports::MattermostDeliveryService.new(
digest: digest, webhook_url: config[:webhook_url], channel: config[:channel]
).call
end
def log_result(account, result)
if result[:success]
Rails.logger.info "[CeoDigest] delivered for account ##{account.id}"
else
Rails.logger.error "[CeoDigest] failed for account ##{account.id}: #{result.inspect}"
end
end
def digest_config(account)
raw = account.custom_attributes&.dig('ceo_digest') || {}
{
enabled: raw.fetch('enabled', true),
webhook_url: raw['mattermost_webhook_url'].presence || ENV.fetch('CEO_DIGEST_MATTERMOST_WEBHOOK_URL', nil),
channel: raw['mattermost_channel'].presence
}
end
def log_skip(account, reason)
Rails.logger.info "[CeoDigest] skipping account ##{account.id}: #{reason}"
end
end