File: /home/storage/5/78/dd/wicomm2/public_html/clientes/performance-healthcheck/config.php
<?php
declare(strict_types=1);
@set_time_limit(0);
ini_set('max_execution_time', '0');
ini_set('default_socket_timeout', '60');
ignore_user_abort(true);
while (ob_get_level() > 0) { ob_end_flush(); }
ob_implicit_flush(true);
function app_config(): array
{
return [
'gemini' => [
'api_key' => getenv('GEMINI_API_KEY') ?: 'AIzaSyCsYoQR76n32MCinh8eI53CwDiPaDZOMHk',
// Modelo padrão atualizado (o alias gemini-1.5-flash pode retornar 404 no v1beta).
'model' => getenv('GEMINI_MODEL') ?: 'gemini-2.5-flash',
'base_url' => 'https://generativelanguage.googleapis.com/v1beta',
],
'lighthouse' => [
'api_key' => getenv('LIGHTHOUSE_API_KEY') ?: 'AIzaSyB_kZFiOw7jqqatZe7yv1SWfoEvx6QWvCE',
'base_url' => 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed',
],
'vtex' => [
'account' => getenv('VTEX_ACCOUNT') ?: 'wicommpartnerbr',
'app_key' => getenv('VTEX_APP_KEY') ?: 'vtexappkey-wicommpartnerbr-VMNRIQ',
'app_token' => getenv('VTEX_APP_TOKEN') ?: 'EOIOUIUHNYKOAHIWYPKBPZRHIDVMWAFSJHEZSVEKXWCGYVAXJWOUQWQZNKIFQPJFGODHPDBAFLKTCETJSZTXJEBHEHYZOGGKLTSGBSVPXELLPGUWUQKVUHZMIEWFRWAE',
'data_entity' => getenv('VTEX_DATA_ENTITY') ?: 'AP',
],
'app' => [
'timezone' => getenv('APP_TIMEZONE') ?: 'America/Sao_Paulo',
],
];
}
// ===============
// Helpers
// ===============
function now_iso(string $tz = 'America/Sao_Paulo'): string
{
return (new DateTimeImmutable('now', new DateTimeZone($tz)))->format(DateTimeInterface::ATOM);
}
function json_out(int $code, array $payload): void
{
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
exit;
}
function http_request(string $method, string $url, array $headers = [], ?string $body = null, int $timeout = 180): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 25,
]);
if ($body !== null)
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
$raw = curl_exec($ch);
$err = curl_error($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($raw === false) {
return ['ok' => false, 'code' => $code ?: 0, 'error' => $err ?: 'Falha', 'data' => null, 'raw' => null];
}
$data = json_decode($raw, true);
$parsed = is_array($data) ? $data : null;
$errVal = null;
if (is_array($parsed)) {
if (isset($parsed['message'])) {
$errVal = $parsed['message'];
} elseif (isset($parsed['error'])) {
$errVal = $parsed['error'];
}
}
if (is_array($errVal)) {
$errVal = json_encode($errVal, JSON_UNESCAPED_UNICODE);
}
if ($errVal === null && !($code >= 200 && $code < 300)) {
$errVal = 'Erro';
}
return [
'ok' => $code >= 200 && $code < 300,
'code' => $code,
'error' => $errVal,
'data' => $parsed,
'raw' => $raw,
];
}
// ===============
// VTEX Master Data
// ===============
function vtex_base(array $config): string
{
$acc = (string) ($config['vtex']['account'] ?? '');
if ($acc === '')
throw new RuntimeException('VTEX_ACCOUNT ausente');
return 'https://' . $acc . '.myvtex.com';
}
function vtex_headers(array $config): array
{
$k = (string) ($config['vtex']['app_key'] ?? '');
$t = (string) ($config['vtex']['app_token'] ?? '');
if ($k === '' || $t === '')
throw new RuntimeException('VTEX_APP_KEY ou VTEX_APP_TOKEN ausente');
return [
'Accept: application/json',
'Content-Type: application/json',
'X-VTEX-API-AppKey: ' . $k,
'X-VTEX-API-AppToken: ' . $t,
];
}
function md_entity(array $config): string
{
$e = (string) ($config['vtex']['data_entity'] ?? 'AP');
return $e !== '' ? $e : 'AP';
}
function md_search_clients(array $config, int $size = 200): array
{
$entity = md_entity($config);
$fields = 'id,client_name,client_url,client_plataform,client_plataforma,performance_history';
$url = vtex_base($config) . "/api/dataentities/{$entity}/search?_fields={$fields}&_size={$size}";
$r = http_request('GET', $url, vtex_headers($config), null, 60);
if (!$r['ok'])
throw new RuntimeException('Master Data search falhou');
$items = $r['data'];
return is_array($items) ? $items : [];
}
function md_get_client(array $config, string $id): array
{
$entity = md_entity($config);
// Em algumas contas, o endpoint de documents retorna um doc “mínimo” sem `_fields`.
$fields = 'id,client_name,client_url,client_plataform,client_plataforma,performance_history';
$url = vtex_base($config) . "/api/dataentities/{$entity}/documents/{$id}?_fields={$fields}";
$r = http_request('GET', $url, vtex_headers($config), null, 60);
if (!$r['ok'] || !is_array($r['data']))
throw new RuntimeException('Master Data get falhou');
return $r['data'];
}
function md_patch_client(array $config, string $id, array $partial): array
{
$entity = md_entity($config);
$url = vtex_base($config) . "/api/dataentities/{$entity}/documents/{$id}";
// PATCH primeiro, se não aceitar, faz PUT
$r = http_request('PATCH', $url, vtex_headers($config), json_encode($partial, JSON_UNESCAPED_UNICODE), 60);
if ($r['ok'] && is_array($r['data']))
return $r['data'];
$r2 = http_request('PUT', $url, vtex_headers($config), json_encode($partial, JSON_UNESCAPED_UNICODE), 60);
if (!$r2['ok'] || !is_array($r2['data']))
throw new RuntimeException('Master Data update falhou');
return $r2['data'];
}
function md_history_as_array(array $client): array
{
$h = $client['performance_history'] ?? [];
if (is_string($h)) {
$decoded = json_decode($h, true);
return is_array($decoded) ? $decoded : [];
}
return is_array($h) ? $h : [];
}
// ===============
// Lighthouse (PageSpeed Insights v5)
// ===============
function run_lighthouse(array $config, string $targetUrl, string $strategy = 'mobile'): array
{
$key = (string) ($config['lighthouse']['api_key'] ?? '');
if ($key === '')
throw new RuntimeException('LIGHTHOUSE_API_KEY ausente');
$base = (string) ($config['lighthouse']['base_url'] ?? '');
if ($base === '')
throw new RuntimeException('Lighthouse base_url ausente');
$q = [
'url' => $targetUrl,
'key' => $key,
'strategy' => $strategy,
'category' => ['performance', 'accessibility', 'best-practices', 'seo', 'pwa'],
];
$url = $base . '?' . http_build_query($q);
$r = http_request('GET', $url, ['Accept: application/json'], null, 180);
if (!$r['ok'] || !is_array($r['data']))
throw new RuntimeException('Lighthouse falhou');
return $r['data'];
}
function extract_lighthouse_summary(array $psi): array
{
$lr = $psi['lighthouseResult'] ?? [];
$cats = $lr['categories'] ?? [];
$audits = $lr['audits'] ?? [];
$score = static function (string $key) use ($cats): ?int {
$v = $cats[$key]['score'] ?? null;
if ($v === null)
return null;
return (int) round(((float) $v) * 100);
};
$metrics = [
'fcp' => $audits['first-contentful-paint']['displayValue'] ?? null,
'lcp' => $audits['largest-contentful-paint']['displayValue'] ?? null,
'cls' => $audits['cumulative-layout-shift']['displayValue'] ?? null,
'tbt' => $audits['total-blocking-time']['displayValue'] ?? null,
'tti' => $audits['interactive']['displayValue'] ?? null,
'speed_index' => $audits['speed-index']['displayValue'] ?? null,
];
return [
'scores' => [
'performance' => $score('performance'),
'accessibility' => $score('accessibility'),
'best-practices' => $score('best-practices'),
'seo' => $score('seo'),
'pwa' => $score('pwa'),
],
'metrics' => $metrics,
'lighthouse_raw' => $lr,
];
}
// ===============
// Gemini
// ===============
function gemini_report(array $config, array $client, array $summary, string $strategy = 'mobile'): array
{
$key = (string) ($config['gemini']['api_key'] ?? '');
if ($key === '')
throw new RuntimeException('GEMINI_API_KEY ausente');
$model = (string) ($config['gemini']['model'] ?? 'gemini-2.5-flash');
$base = (string) ($config['gemini']['base_url'] ?? 'https://generativelanguage.googleapis.com/v1beta');
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent";
$schemaHint = [
'summary' => 'string',
'priority_actions' => [
['title' => 'string', 'why' => 'string', 'how' => 'string', 'expected_impact' => 'string', 'scope' => 'performance|accessibility|best-practices|seo|pwa']
],
'risks' => [
['title' => 'string', 'context' => 'string']
],
'notes' => ['string']
];
$clientName = (string) ($client['client_name'] ?? '');
$clientUrl = (string) ($client['client_url'] ?? '');
$platformRaw = $client['client_plataform'] ?? ($client['client_plataforma'] ?? '');
$platform = is_array($platformRaw) ? json_encode($platformRaw, JSON_UNESCAPED_UNICODE) : (string) $platformRaw;
$system = 'Você é um analista técnico de performance web. Você recebe o JSON do Lighthouse e devolve um relatório em JSON estrito, sem markdown, sem texto fora do JSON, seguindo o schema sugerido. Você não inventa métricas, você usa apenas o que existe no JSON e quando faltar dado você explicita a ausência. A escrita deve ser específica e orientada a implementação, evitando generalidades.';
$user = [
'context' => [
'client_name' => $clientName,
'client_url' => $clientUrl,
'client_plataform' => $platform,
'strategy' => $strategy,
'scores' => $summary['scores'] ?? [],
'metrics' => $summary['metrics'] ?? [],
'schema' => $schemaHint,
],
'lighthouse' => $summary['lighthouse_raw'] ?? new stdClass(),
];
$payload = [
'systemInstruction' => ['parts' => [['text' => $system]]],
'contents' => [
[
'role' => 'user',
'parts' => [['text' => json_encode($user, JSON_UNESCAPED_UNICODE)]],
]
],
'generationConfig' => [
'temperature' => 0.2,
],
];
$r = http_request('POST', $url, ['Content-Type: application/json', 'Accept: application/json'], json_encode($payload, JSON_UNESCAPED_UNICODE), 180);
if (!$r['ok'] || !is_array($r['data'])) {
$code = (int) ($r['code'] ?? 0);
$raw = (string) ($r['raw'] ?? '');
$raw = mb_substr($raw, 0, 1400);
$msgRaw = $r['error'] ?? '';
$msg = is_array($msgRaw) ? json_encode($msgRaw, JSON_UNESCAPED_UNICODE) : (string) $msgRaw;
throw new RuntimeException('Gemini falhou (HTTP ' . $code . ')' . ($msg !== '' ? ' | ' . $msg : '') . ($raw !== '' ? "\n\nResposta:\n" . $raw : ''));
}
$text = $r['data']['candidates'][0]['content']['parts'][0]['text'] ?? '';
$json = json_decode((string) $text, true);
if (!is_array($json)) {
return [
'summary' => 'Falha ao interpretar JSON do Gemini, o conteúdo foi salvo como texto.',
'priority_actions' => [],
'risks' => [],
'notes' => [mb_substr((string) $text, 0, 800)],
'_raw_text' => (string) $text,
];
}
return $json;
}