HEX
Server: Apache
System: Linux vpshost11508.publiccloud.com.br 5.15.179-grsec-vpshost-10.lc.el8.x86_64 #1 SMP Mon Apr 7 12:04:45 -03 2025 x86_64
User: wicomm2 (10002)
PHP: 8.3.0
Disabled: apache_child_terminate,dl,escapeshellarg,escapeshellcmd,exec,link,mail,openlog,passthru,pcntl_alarm,pcntl_exec,pcntl_fork,pcntl_get_last_error,pcntl_getpriority,pcntl_setpriority,pcntl_signal,pcntl_signal_dispatch,pcntl_sigprocmask,pcntl_sigtimedwait,pcntl_sigwaitinfo,pcntl_strerror,pcntl_wait,pcntl_waitpid,pcntl_wexitstatus,pcntl_wifexited,pcntl_wifsignaled,pcntl_wifstopped,pcntl_wstopsig,pcntl_wtermsig,php_check_syntax,php_strip_whitespace,popen,proc_close,proc_open,shell_exec,symlink,system
Upload Files
File: /home/storage/5/78/dd/wicomm2/public_html/blackfriday/api/evaluate.php
<?php
// ===== evaluate.php =====
// Classifica maturidade, monta roadmap (determinístico + IA) e
// BUSCA Lighthouse no Master Data (LH) por scan_id; fallback: storage local > cookie.

require_once __DIR__ . '/../config/config.php';

@date_default_timezone_set(defined('APP_TIMEZONE') ? APP_TIMEZONE : 'America/Sao_Paulo');
if (!is_dir(STORAGE_DIR)) @mkdir(STORAGE_DIR, 0775, true);

// -------------------- HELPERS --------------------
function json_out($data, $code = 200) {
  if (!headers_sent()) {
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
  }
  echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  exit;
}
function require_post_json() {
  $raw = file_get_contents('php://input');
  $j = json_decode($raw, true);
  if (!is_array($j)) json_out(['ok'=>false,'error'=>'Invalid JSON body'], 400);
  return $j;
}
function clamp100($v) {
  $n = is_numeric($v) ? floatval($v) : 0;
  return max(0, min(100, $n));
}
function summarize_answers_pairs($list, $maxItems = 10) {
  $out = [];
  foreach ((array)$list as $item) {
    $q = $item['pergunta'] ?? $item['title'] ?? $item['text'] ?? '';
    $r = $item['resposta'] ?? $item['answer'] ?? '';
    $q = trim((string)$q);
    $r = trim((string)$r);
    if ($q === '' && $r === '') continue;
    $out[] = "• ".$q." → ".$r;
    if (count($out) >= $maxItems) break;
  }
  return implode("\n", $out);
}

// -------------------- VTEX MD (LH): GET por scanId --------------------
function vtex_md_get_LH_by_scanId($scanId) {
  if (!$scanId) return [null, 'missing scanId'];
  $host = 'https://' . VTEX_ACCOUNT . '.' . VTEX_ENVIRONMENT . '.com.br';
  $url  = $host . '/api/dataentities/LH/search?_fields=scanId,payload&_where=scanId=' . rawurlencode($scanId) . '&_schema=1';

  $ch = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'Accept: application/json',
      'X-VTEX-API-AppKey: ' . VTEX_APPKEY,
      'X-VTEX-API-AppToken: ' . VTEX_APPTOKEN,
    ],
    CURLOPT_TIMEOUT => 15,
    CURLOPT_CONNECTTIMEOUT => 8,
  ]);
  $body = curl_exec($ch);
  $err  = curl_error($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);

  if ($code >= 400 || !$body) return [null, $body ?: $err ?: ("HTTP ".$code)];
  $arr = json_decode($body, true);
  if (!is_array($arr) || !count($arr)) return [null, 'not_found'];
  return [$arr[0], null];
}

// -------------------- Lighthouse summary a partir de fontes múltiplas --------------------
function lighthouse_summary_from_sources($scanId, $storageDir) {
  // 1) Tenta Master Data LH
  if ($scanId) {
    [$doc, $err] = vtex_md_get_LH_by_scanId($scanId);
    if ($doc && isset($doc['payload'])) {
      $raw = json_decode($doc['payload'], true);
      if (is_array($raw) && isset($raw['lighthouseResult'])) {
        return build_lh_summary($raw);
      }
    }
  }

  // 2) Storage local
  if ($scanId) {
    $path = rtrim($storageDir,'/').'/scan_'.$scanId.'.json';
    if (is_file($path)) {
      $raw = json_decode(file_get_contents($path), true);
      if (is_array($raw) && isset($raw['lighthouseResult'])) {
        return build_lh_summary($raw);
      }
    }
  }

  // 3) Cookie (resumo)
  if (!empty($_COOKIE['fridayup_lighthouse'])) {
    $sum = json_decode($_COOKIE['fridayup_lighthouse'], true);
    if (is_array($sum)) {
      return [
        'score' => $sum['score'] ?? null,
        'metrics' => $sum['metrics'] ?? [],
        'oportunidades' => $sum['opportunidades'] ?? []
      ];
    }
  }

  // Falhou → vazio
  return ['score'=>null, 'metrics'=>[], 'oportunidades'=>[]];
}

function build_lh_summary($raw) {
  $lh   = $raw['lighthouseResult'] ?? [];
  $aud  = $lh['audits'] ?? [];
  $cats = $lh['categories'] ?? [];
  $score = (int) round((($cats['performance']['score'] ?? 0) * 100));
  $disp = function($id) use ($aud) { return $aud[$id]['displayValue'] ?? ''; };

  $opps = [];
  foreach ($aud as $id => $a) {
    if (isset($a['details']['type']) && $a['details']['type'] === 'opportunity') {
      $opps[] = [
        'id'        => $id,
        'title'     => $a['title'] ?? $id,
        'savingsMs' => $a['details']['overallSavingsMs'] ?? 0
      ];
    }
  }
  usort($opps, fn($A,$B) => ($B['savingsMs'] ?? 0) <=> ($A['savingsMs'] ?? 0));
  $opps = array_slice($opps, 0, 10);

  return [
    'score' => clamp100($score),
    'metrics' => [
      'lcp'         => $disp('largest-contentful-paint'),
      'tbt'         => $disp('total-blocking-time'),
      'cls'         => $disp('cumulative-layout-shift'),
      'interactive' => $disp('interactive'),
    ],
    'oportunidades' => $opps
  ];
}

// -------------------- OPENAI: CLASSIFY MATURITY (1 palavra) --------------------
function openai_classify_maturity($answers, $apiKey, $model) {
  $pairs = summarize_answers_pairs($answers, 8);
  $payload = [
    'model' => $model ?: 'gpt-4o-mini',
    'temperature' => 0.0,
    'messages' => [
      ['role'=>'system','content'=>'Você é um avaliador de maturidade para Black Friday. Regra: responda apenas uma palavra: basico, intermediario ou avancado.'],
      ['role'=>'user','content'=>"Responda APENAS uma palavra (sem pontuação): basico, intermediario ou avancado.\n\nQuiz:\n".$pairs],
    ]
  ];
  $ch = curl_init('https://api.openai.com/v1/chat/completions');
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'Content-Type: application/json',
      'Authorization: Bearer '.OPENAI_API_KEY
    ],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 8,
    CURLOPT_TIMEOUT => 20,
  ]);
  $body = curl_exec($ch);
  $err  = curl_error($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);
  if ($code >= 400 || !$body) throw new Exception("OpenAI error $code: ".($body ?: $err ?: ''));
  $j = json_decode($body, true);
  $txt = trim($j['choices'][0]['message']['content'] ?? '');
  $txt = mb_strtolower($txt, 'UTF-8');
  if (strpos($txt, 'inter') === 0) return 'intermediario';
  if (strpos($txt, 'av') === 0)    return 'avancado';
  return 'basico';
}

// -------------------- OPENAI: EXPLICAÇÃO CURTA DA MATURIDADE --------------------
function openai_explain_maturity($maturidade, $answersCore, $answersExtra, $lhSummary) {
  // Resumo compacto das respostas
  $core  = summarize_answers_pairs($answersCore, 8);
  $extra = summarize_answers_pairs($answersExtra, 12);

  // Compacta Lighthouse (se houver)
  $lh_str = '';
  if (is_array($lhSummary) && !empty($lhSummary)) {
    $score = $lhSummary['score'] ?? null;
    $m = $lhSummary['metrics'] ?? [];
    $lh_str =
      "Score: ".($score === null ? 'n/a' : $score)."; ".
      "LCP: ".($m['lcp'] ?? 'n/a')."; ".
      "TBT: ".($m['tbt'] ?? 'n/a')."; ".
      "CLS: ".($m['cls'] ?? 'n/a')."; ".
      "Interactive: ".($m['interactive'] ?? 'n/a');
  }

  $system = <<<SYS
Você é um consultor sênior de e-commerce. Explique em 3 a 5 linhas, em português do Brasil, por que a operação foi classificada no nível de maturidade informado. 
Regras:
- Seja específico, cite 2–3 motivos objetivos (ex.: existência/ausência de processos, governança por canal/coorte, consistência de metas, preparação para Black Friday, sinais de performance do Lighthouse).
- Não cite tecnologias ou plataformas.
- Não faça lista; escreva apenas um parágrafo corrido, claro e direto.
SYS;

  $user = "NÍVEL: {$maturidade}\nRESPOSTAS-CORE:\n{$core}\nRESPOSTAS-EXTRA:\n{$extra}\nLIGHTHOUSE:\n{$lh_str}\n\nEscreva a explicação (2–3 linhas).";

  $payload = [
    'model' => OPENAI_MODEL,
    'temperature' => 0.7,
    'messages' => [
      ['role'=>'system','content'=>$system],
      ['role'=>'user','content'=>$user],
    ]
  ];

  $ch = curl_init('https://api.openai.com/v1/chat/completions');
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'Content-Type: application/json',
      'Authorization: Bearer '.OPENAI_API_KEY
    ],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 8,
    CURLOPT_TIMEOUT => 20,
  ]);
  $body = curl_exec($ch);
  $err  = curl_error($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);
  if ($code >= 400 || !$body) {
    // Em caso de falha, devolve uma frase curta padrão
    return "Classificação como ".ucfirst($maturidade).": explicação automatizada indisponível no momento.";
  }
  $j = json_decode($body, true);
  $txt = trim($j['choices'][0]['message']['content'] ?? '');
  // Remove quebras excessivas
  $txt = preg_replace('/\s+/', ' ', $txt);
  return $txt !== '' ? $txt : ("Classificação como ".ucfirst($maturidade).".");
}

// -------------------- OPENAI: ROADMAP (estratégico, sem plataforma) --------------------
function openai_tailored_roadmap($maturidade, $answersCore, $answersExtra, $lhSummary, $maxItems = 18) {
  $core = summarize_answers_pairs($answersCore, 8);
  $extra = summarize_answers_pairs($answersExtra, 24);

  $lh_str = '';
  if (!empty($lhSummary)) {
    $lh_str =
      "Score: ".($lhSummary['score'] ?? 'n/a')."\n".
      "LCP: ".($lhSummary['metrics']['lcp'] ?? 'n/a')." | ".
      "TBT: ".($lhSummary['metrics']['tbt'] ?? 'n/a')." | ".
      "CLS: ".($lhSummary['metrics']['cls'] ?? 'n/a')." | ".
      "Interactive: ".($lhSummary['metrics']['interactive'] ?? 'n/a')."\n".
      "Oportunidades (top):\n".
      implode("\n", array_map(function($o){
        return "- ".$o['title']." (~".$o['savingsMs']."ms)";
      }, $lhSummary['oportunidades'] ?? []));
  }

  $system = <<<SYS
Você é um estrategista sênior de e-commerce para Black Friday. Gere um roadmap extremamente objetivo, sem genéricos e sem dependência de plataforma (não citar VTEX, Shopify, etc). Foque em decisões estratégicas e governança, você deve retornar exatamente 3 itens por area.

Regras:
- Sem detalhes técnicos de implementação.
- Cada item com título, por_que, acao_estrategica, kpi, area (CRM|UI/UX|SEO|Performance|Dados).
- O por_que precisa ser um hook para fazer o gestor que fez o quiz continuar lendo
- A acao_estrategica precisa ser uma estrategia de ponta a ponta, sem ser generico ou gerar uma analise basica que ele pode encontrar em qualquer lugar, você precisa realmente desenvolver uma estrategia de acordo com a {$maturidade} adaptando sua linguagem com essa maturidade a e estrategia sugerida. Não sugira nenhum, repito, nenhuma estratégica básica, tudo precisa ser detalhado e no final de cada acao_estrategica, precisa de mais duas linhas fazendo a explicação técnica avançada do que precisa ser feito.
- Retorne SOMENTE um JSON (array) com até {$maxItems} itens.
SYS;

  $user = "MATURIDADE: {$maturidade}\n\nRESPOSTAS-CORE:\n{$core}\n\nRESPOSTAS-EXTRA:\n{$extra}\n\nLIGHTHOUSE:\n{$lh_str}\n\nSomente o JSON.";

  $payload = [
    'model' => OPENAI_MODEL,
    'temperature' => 0.7,
    'messages' => [
      ['role'=>'system','content'=>$system],
      ['role'=>'user','content'=>$user],
    ]
  ];
  $ch = curl_init('https://api.openai.com/v1/chat/completions');
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
      'Content-Type: application/json',
      'Authorization: Bearer '.OPENAI_API_KEY
    ],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 45,
  ]);
  $body = curl_exec($ch);
  $err  = curl_error($ch);
  $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  curl_close($ch);
  if ($code >= 400 || !$body) throw new Exception("OpenAI error $code: ".($body ?: $err ?: ''));

  $j = json_decode($body, true);
  $txt = trim($j['choices'][0]['message']['content'] ?? '');
  $items = json_decode($txt, true);
  if (!is_array($items) && preg_match('/\[[\s\S]*\]/', $txt, $m)) $items = json_decode($m[0], true);
  if (!is_array($items)) throw new Exception('Resposta da IA inválida (JSON não parseável)');

  // saneia
  $validAreas = ['CRM','UI/UX','SEO','Performance','Dados','Operacao'];
  foreach ($items as &$it) {
    $it['titulo'] = trim((string)($it['titulo'] ?? ''));
    $it['por_que'] = trim((string)($it['por_que'] ?? ''));
    $it['acao_estrategica'] = trim((string)($it['acao_estrategica'] ?? ''));
    $it['kpi'] = trim((string)($it['kpi'] ?? ''));
    $it['impacto'] = in_array(($it['impacto'] ?? ''), ['alto','medio','baixo'], true) ? $it['impacto'] : 'medio';
    $it['prazo'] = in_array(($it['prazo'] ?? ''), ['imediato','antes da BF','H1','H2'], true) ? $it['prazo'] : 'antes da BF';
    $it['area'] = in_array(($it['area'] ?? ''), $validAreas, true) ? $it['area'] : 'Operacao';
  }
  unset($it);

  if (count($items) > $maxItems) $items = array_slice($items, 0, $maxItems);
  return $items;
}

// -------------------- ROADMAP determinístico (backup) --------------------
function roadmap_by_maturity($maturidade) {
  $areas = [
    'Performance' => [
      'basico' => [
        'Ativar compressão gzip/brotli e cache estático com cabeçalhos de longo prazo.',
        'Otimizar imagens (WebP/AVIF) e dimensionamento responsivo (srcset/sizes).',
        'Remover scripts não usados e adiar JS não crítico (defer/async).'
      ],
      'intermediario' => [
        'Implementar Critical CSS e pré-carregar fontes/recursos essenciais.',
        'Split de bundles JS por rota e lazy-load de componentes pesados.',
        'Configurar CDN com HTTP/2/3 e otimização de TLS.'
      ],
      'avancado' => [
        'Server-Side Rendering/Edge Rendering com hidratação seletiva.',
        'RUM (Real User Monitoring) para LCP/CLS/TBT e regressões por release.',
        'Imagem on-the-fly (thumbor/imgproxy) com cache por variantes.'
      ],
    ],
    'SEO' => [
      'basico' => [
        'Garantir títulos (15–65) e meta descriptions (50–165) únicas por página.',
        'Definir canonical absoluto e sitemap.xml referenciado no robots.txt.',
        'Corrigir status HTTP (evitar 4xx/5xx) e links quebrados.'
      ],
      'intermediario' => [
        'Estruturar dados (JSON-LD) para PDP (Product), PLP (ItemList) e breadcrumbs.',
        'Hreflang completo (incluindo x-default) para variações de idioma/região.',
        'Otimizar facetas/indexação (noindex/nofollow) e parâmetros de URL.'
      ],
      'avancado' => [
        'Arquitetura de conteúdo suportada por pesquisa de intenção e clusters.',
        'Automação de metadados e internal linking com regras por template.',
        'Monitoramento técnico contínuo (log analysis, crawl budget).'
      ],
    ],
    'CRO/UX' => [
      'basico' => [
        'Checkout simplificado, eliminar campos desnecessários e fricções.',
        'PDP com hierarquia clara (título, preço, frete, call-to-action visível).',
        'PLP com filtros ordenados e indicador de estoque/preço atualizado.'
      ],
      'intermediario' => [
        'Testes A/B contínuos (CTA, layout, prova social) com meta de uplift.',
        'Personalização básica (recentes, recomendados) por comportamento.',
        'Recuperação de abandono (email/SMS/web push) com ofertas dinâmicas.'
      ],
      'avancado' => [
        'Segmentação preditiva e recomendações por ML (cross/up-sell em tempo real).',
        'Orquestração omnichannel (loja física/marketplace) e preços dinâmicos.',
        'Experimentação avançada (multi-armed bandit, guardrails métricos).'
      ],
    ],
    'Dados/Analytics' => [
      'basico' => [
        'GA4 corretamente implementado (page_view, view_item, add_to_cart, purchase).',
        'Mapear eventos essenciais do funil e validar via DebugView.',
        'UTMs padronizadas e integração com Search Console.'
      ],
      'intermediario' => [
        'Server-side tagging e BigQuery export para análises de coorte.',
        'Métricas de produto (Margem, LTV, Churn) no painel de e-commerce.',
        'Alertas automáticos de anomalia (queda de receita, taxa de erro).'
      ],
      'avancado' => [
        'Modelos de atribuição próprios e MMM simplificado.',
        'Feature store para personalização e campanhas em tempo real.',
        'Ciclos de feedback entre BI e CRO (insight → experimento → rollout).'
      ],
    ],
    'Infra/Operacao' => [
      'basico' => [
        'Monitoramento de uptime e erros (Sentry/Datadog) e plano de contingência.',
        'Política de cache/CDN definida e invalidação por deploy.',
        'Checklist de Black Friday (carga, estoque, prazos de entrega).'
      ],
      'intermediario' => [
        'Testes de carga e chaos testing em endpoints críticos.',
        'Blue/Green deploy e rollback automático.',
        'SLOs por rota (p95 LCP/CLS/TBT) e alertas por canal.'
      ],
      'avancado' => [
        'Edge functions para personalização de baixa latência.',
        'Escalonamento elástico e prewarming para picos de tráfego.',
        'DRP (Disaster Recovery Plan) com testes trimestrais.'
      ],
    ],
  ];

  $result = [];
  foreach ($areas as $area => $levels) {
    $melhorias = [];
    if ($maturidade === 'basico') {
      $melhorias = array_merge($levels['basico'], $levels['intermediario'], $levels['avancado']);
    } elseif ($maturidade === 'intermediario') {
      $melhorias = array_merge($levels['intermediario'], $levels['avancado']);
    } else {
      $melhorias = $levels['avancado'];
    }
    $result[] = ['area'=>$area, 'melhorias'=>$melhorias];
  }
  return $result;
}

// -------------------- MAIN --------------------
try {
  $data = require_post_json();

  $answersCore  = $data['answers'] ?? [];
  $answersExtra = $data['answers_extra'] ?? [];
  $scanId       = isset($data['scan_id']) ? trim((string)$data['scan_id']) : '';

  if (!is_array($answersCore) || count($answersCore) < 5) {
    json_out(['ok'=>false,'error'=>'5 respostas são necessárias em "answers"'], 400);
  }

  // 1) Obter Lighthouse de LH (fallbacks)
  $lighthouse = lighthouse_summary_from_sources($scanId, STORAGE_DIR);

  // 2) Classificação de maturidade + explicação breve
  $maturidade = 'basico';
  $explanation = '';
  if (!defined('OPENAI_API_KEY') || !OPENAI_API_KEY) {
    // Sem OpenAI: usa heurística pelo score do Lighthouse + pistas das respostas
    $score = (int)($lighthouse['score'] ?? 0);
    $maturidade = ($score >= 80) ? 'avancado' : (($score >= 50) ? 'intermediario' : 'basico');

    // Constrói uma explicação heurística (2–3 linhas)
    $motivos = [];
    if ($score) { $motivos[] = "score de performance {$score}"; }
    $negativos = 0;
    foreach ($answersCore as $a) {
      $r = mb_strtolower(trim((string)($a['resposta'] ?? '')), 'UTF-8');
      if ($r === 'não' || $r === 'nao' || $r === 'em discussão' || $r === 'em discussao') $negativos++;
    }
    if ($negativos >= 3) $motivos[] = "processos ainda pouco padronizados nas respostas do quiz";
    $opps = $lighthouse['oportunidades'] ?? [];
    if (!empty($opps)) {
      $titulos = array_map(fn($o) => $o['title'] ?? $o['id'] ?? '', array_slice($opps, 0, 2));
      $titulos = array_filter($titulos);
      if ($titulos) $motivos[] = "oportunidades como ".implode(' e ', $titulos);
    }
    $explanation = "Classificação como ".ucfirst($maturidade).": ".implode('; ', $motivos).".";
  } else {
    // Com OpenAI: além de classificar, gera explicação curta e específica
    $maturidade = openai_classify_maturity($answersCore, OPENAI_API_KEY, OPENAI_MODEL);
    if (!in_array($maturidade, ['basico','intermediario','avancado'], true)) $maturidade = 'basico';
    $explanation = openai_explain_maturity($maturidade, $answersCore, $answersExtra, $lighthouse);
  }

  // 3) Roadmap determinístico (backup)
  $roadmapDet = roadmap_by_maturity($maturidade);

  // 4) Roadmap otimizado por IA
  $roadmapAI = [];
  if (defined('OPENAI_API_KEY') && OPENAI_API_KEY) {
    try {
      $roadmapAI = openai_tailored_roadmap(
        $maturidade,
        $answersCore,
        $answersExtra,
        $lighthouse,
        12
      );
    } catch (Throwable $e) {
      $roadmapAI = [];
    }
  }

  json_out([
    'ok'           => true,
    'maturidade'   => $maturidade,
    'explanation'  => $explanation,
    'lighthouse'   => $lighthouse,
    'roadmap'      => $roadmapDet,
    'roadmap_ai'   => $roadmapAI
  ]);

} catch (Throwable $e) {
  json_out(['ok'=>false,'error'=>'evaluate: '.$e->getMessage()], 500);
}