sentiment-dashboard.service.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { sentimentDashboardMockSource } from '../data/sentiment-dashboard.mock.js';
  2. const summaryCardsConfig = [
  3. { key: 'atRiskClients', label: 'Clientes em risco' },
  4. { key: 'opportunities', label: 'Oportunidades' },
  5. { key: 'recentInteractions', label: 'Interacoes recentes' },
  6. { key: 'netTrend', label: 'Tendencia liquida' }
  7. ];
  8. const priorityLabel = {
  9. high: 'Alta prioridade',
  10. medium: 'Media prioridade',
  11. low: 'Baixa prioridade'
  12. };
  13. export function getSentimentDashboardViewModel() {
  14. return {
  15. summaryCards: mapSummaryCards(sentimentDashboardMockSource.summary),
  16. alerts: mapAlerts(sentimentDashboardMockSource.alerts),
  17. timeline: sentimentDashboardMockSource.timeline,
  18. aspects: mapAspects(sentimentDashboardMockSource.aspects)
  19. };
  20. }
  21. export function getSummaryInsight(cardId, viewModel) {
  22. const card = viewModel.summaryCards.find((item) => item.id === cardId);
  23. if (!card) return createFallbackInsight();
  24. const insightByCard = {
  25. atRiskClients: {
  26. title: 'Clientes em risco em destaque',
  27. context: `${card.value} clientes apresentam sinais de queda de engajamento ou resposta tardia.`,
  28. actions: [
  29. 'Priorizar contato em ate 24h para os casos com maior ticket.',
  30. 'Acionar playbook de retencao com oferta de recuperacao.',
  31. 'Distribuir os casos criticos para vendedores com melhor conversao.'
  32. ]
  33. },
  34. opportunities: {
  35. title: 'Oportunidades comerciais ativas',
  36. context: `${card.value} clientes estao com indicios de compra em aberto.`,
  37. actions: [
  38. 'Executar abordagem consultiva nos clientes com intencao alta.',
  39. 'Enviar proposta com prazo curto para acelerar fechamento.',
  40. 'Marcar follow-up com dono da conta ainda hoje.'
  41. ]
  42. },
  43. recentInteractions: {
  44. title: 'Volume recente de interacoes',
  45. context: `${card.value} interacoes recentes exigem triagem por impacto no resultado.`,
  46. actions: [
  47. 'Separar interacoes por risco, oportunidade e neutras.',
  48. 'Garantir SLA para mensagens sem retorno.',
  49. 'Acompanhar taxa de resolucao no primeiro contato.'
  50. ]
  51. },
  52. netTrend: {
  53. title: 'Tendencia liquida de resultado',
  54. context: `Tendencia atual de ${card.value}, indicando saldo positivo entre ganhos e perdas.`,
  55. actions: [
  56. 'Escalar abordagem que gerou maior crescimento nas ultimas semanas.',
  57. 'Manter monitoramento de contas em risco para evitar reversao.',
  58. 'Transformar casos de sucesso em rotina para todo o time.'
  59. ]
  60. }
  61. };
  62. return insightByCard[cardId] ?? createFallbackInsight();
  63. }
  64. export function getAlertInsight(alertId, viewModel) {
  65. const alert = viewModel.alerts.find((item) => item.id === alertId);
  66. if (!alert) return createFallbackInsight();
  67. const categoryGuidance = {
  68. churn_risk: [
  69. 'Fazer contato humano com proposta de recuperacao em ate 24h.',
  70. 'Identificar causa raiz da perda de interesse no ultimo ciclo.',
  71. 'Negociar condicoes para reativar a conta com menor atrito.'
  72. ],
  73. frustration: [
  74. 'Responder com empatia e reconhecer o problema sem delay.',
  75. 'Resolver o ponto de frustracao com responsavel e prazo claro.',
  76. 'Fazer retorno ativo confirmando que o problema foi resolvido.'
  77. ],
  78. buying_intent: [
  79. 'Acionar vendedor responsavel para proposta objetiva imediata.',
  80. 'Remover bloqueios de compra (prazo, condicao, produto).',
  81. 'Registrar proximo passo com data de fechamento prevista.'
  82. ]
  83. };
  84. return {
  85. title: alert.title,
  86. context: alert.description,
  87. actions: categoryGuidance[alert.category] ?? createFallbackInsight().actions
  88. };
  89. }
  90. export function getTimelineInsight(period, viewModel) {
  91. const timelinePoint = viewModel.timeline.find((item) => item.period === period);
  92. if (!timelinePoint) return createFallbackInsight();
  93. const balance = timelinePoint.gains - timelinePoint.losses;
  94. const trendText =
  95. balance >= 0
  96. ? `saldo positivo de ${balance} pontos entre ganhos e perdas`
  97. : `saldo negativo de ${Math.abs(balance)} pontos entre ganhos e perdas`;
  98. return {
  99. title: `Leitura da ${timelinePoint.period}`,
  100. context: `Na ${timelinePoint.period}, houve ${timelinePoint.gains} ganhos e ${timelinePoint.losses} perdas, com ${trendText}.`,
  101. actions: [
  102. 'Replicar o comportamento das contas com ganho na semana.',
  103. 'Atuar nos principais motivos de perda identificados no periodo.',
  104. 'Definir meta da proxima semana com base no saldo atual.'
  105. ]
  106. };
  107. }
  108. function mapSummaryCards(summary) {
  109. return summaryCardsConfig.map((config) => ({
  110. id: config.key,
  111. label: config.label,
  112. value: summary[config.key]?.value ?? summary[config.key],
  113. image: summary[config.key]?.image ?? null
  114. }));
  115. }
  116. function mapAlerts(alerts) {
  117. return alerts.map((alert) => ({
  118. ...alert,
  119. priorityLabel: priorityLabel[alert.priority] ?? priorityLabel.low
  120. }));
  121. }
  122. function mapAspects(aspects) {
  123. return aspects.map((aspect) => ({
  124. ...aspect,
  125. volume: aspect.positive.length + aspect.neutral.length + aspect.negative.length
  126. }));
  127. }
  128. function createFallbackInsight() {
  129. return {
  130. title: 'Detalhes da analise',
  131. context: 'Selecione um item para visualizar diagnostico e recomendacoes.',
  132. actions: ['Priorizar casos de alto impacto.', 'Executar proximo passo comercial.', 'Monitorar resultado diariamente.']
  133. };
  134. }