Loja de Barreiro Baterias BH

Baterias em - Belo Horizonte / MG
  • img
  • img
  • img
  • img
  • img
  • img
  • img
  • img
img img img img
img img img img
Informações do Anunciante

['host'=>'localhost','name'=>'SEU_BANCO','user'=>'SEU_USUARIO','pass'=>'SUA_SENHA','charset'=>'utf8mb4'], 'site'=>['base_url'=>'https://www.topdobairro.com','timezone'=>'America/Sao_Paulo'], 'brain'=>['mode'=>'aggressive','auto_apply'=>true,'auto_rollback'=>true,'rollback_threshold'=>.12,'min_lift'=>.05,'min_evidence_confidence'=>.80,'min_experiment_days'=>7,'protect_winners'=>true,'max_auto_risk'=>70,'cluster_concurrency_rate'=>.07,'cron_key'=>'TROQUE_POR_UMA_CHAVE_FORTE','min_query_impressions'=>50,'gsc_days_back'=>5], 'ttl_days'=>['telefone'=>90,'whatsapp'=>90,'horario'=>60,'endereco'=>180,'cnpj'=>365,'razao_social'=>365,'cep'=>365,'bairro'=>730,'cidade'=>730,'segmento'=>365,'site'=>180,'nome'=>365,'servicos'=>180,'area_atendimento'=>180], 'gsc'=>['enabled'=>false,'client_id'=>'','client_secret'=>'','redirect_uri'=>'https://www.topdobairro.com/brain.php?tdb_action=gsc_callback','site_url'=>'https://www.topdobairro.com/','scope'=>'https://www.googleapis.com/auth/webmasters.readonly','token_file'=>__DIR__.'/.tdb_gsc_token.json'], 'llm'=>['enabled'=>false,'endpoint'=>'','bearer_token'=>'','timeout_seconds'=>20,'explore_ratio'=>.20] ]; final class TopDoBairroBrain{ private PDO $db;private array $c; function __construct(PDO $db,array $c){$this->db=$db;$this->c=$c;$this->install();} private function q(string $s,array $p=[]):PDOStatement{$q=$this->db->prepare($s);$q->execute($p);return $q;} private function one(string $s,array $p=[]){return $this->q($s,$p)->fetchColumn();} private function row(string $s,array $p=[]):array{return $this->q($s,$p)->fetch(PDO::FETCH_ASSOC)?:[];} private function all(string $s,array $p=[]):array{return $this->q($s,$p)->fetchAll(PDO::FETCH_ASSOC);} private function j($v):string{return json_encode($v,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES)?:'{}';} private function n(string $s):string{$s=mb_strtolower(trim($s),'UTF-8');$a=@iconv('UTF-8','ASCII//TRANSLIT//IGNORE',$s);if($a!==false)$s=$a;return trim(preg_replace('/[^a-z0-9]+/','_',$s)??'','_');} private function install():void{ $sql=[ "CREATE TABLE IF NOT EXISTS tdb_urls(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,url VARCHAR(700) NOT NULL UNIQUE,cidade VARCHAR(160),estado CHAR(2),bairro VARCHAR(160),categoria VARCHAR(180),segmento VARCHAR(180),intencao VARCHAR(80),cluster_key VARCHAR(255),created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,INDEX(cluster_key),INDEX(cidade,bairro,categoria)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_empresas(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,nome VARCHAR(255) NOT NULL,razao_social VARCHAR(255),cnpj VARCHAR(30),categoria VARCHAR(180),segmento VARCHAR(180),telefone VARCHAR(50),whatsapp VARCHAR(50),site VARCHAR(700),endereco VARCHAR(255),numero VARCHAR(50),bairro VARCHAR(160),cidade VARCHAR(160),estado CHAR(2),cep VARCHAR(20),latitude DECIMAL(10,7),longitude DECIMAL(10,7),horario_json JSON,servicos_json JSON,area_atendimento_json JSON,ativo TINYINT(1) DEFAULT 1,confidence DECIMAL(6,5) DEFAULT .5,ultima_validacao DATETIME,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,INDEX(cidade,bairro,categoria),INDEX(cnpj)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_profissionais_publicos(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,nome_publico VARCHAR(255) NOT NULL,empresa_id BIGINT UNSIGNED,funcao_publica VARCHAR(180),perfil_publico_url VARCHAR(700),cidade VARCHAR(160),estado CHAR(2),confidence DECIMAL(6,5) DEFAULT .5,ultima_validacao DATETIME,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX(empresa_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_evidencias(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,entity_type VARCHAR(50) NOT NULL,entity_id BIGINT UNSIGNED NOT NULL,atributo VARCHAR(100) NOT NULL,valor TEXT NOT NULL,fonte VARCHAR(700),fonte_tipo VARCHAR(80),confidence DECIMAL(6,5) DEFAULT .5,validado_usuario TINYINT(1) DEFAULT 0,validado_empresa TINYINT(1) DEFAULT 0,coletado_em DATETIME DEFAULT CURRENT_TIMESTAMP,validado_em DATETIME,INDEX(entity_type,entity_id),INDEX(atributo),INDEX(coletado_em)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_eventos(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,url_id BIGINT UNSIGNED NOT NULL,session_id VARCHAR(100),evento VARCHAR(80) NOT NULL,valor DECIMAL(14,5),experiment_id BIGINT UNSIGNED,variant ENUM('A','B'),metadata JSON,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX(url_id,evento),INDEX(created_at),INDEX(session_id),INDEX(experiment_id,variant)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_metricas_url(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,url_id BIGINT UNSIGNED NOT NULL,data DATE NOT NULL,impressoes INT DEFAULT 0,cliques INT DEFAULT 0,ctr DECIMAL(10,7) DEFAULT 0,posicao DECIMAL(10,4),pageviews INT DEFAULT 0,sessions INT DEFAULT 0,engaged_sessions INT DEFAULT 0,engagement_rate DECIMAL(10,7) DEFAULT 0,scroll_avg DECIMAL(8,3),active_time_avg DECIMAL(12,3),whatsapp_clicks INT DEFAULT 0,phone_clicks INT DEFAULT 0,leads INT DEFAULT 0,UNIQUE KEY uq(url_id,data),INDEX(data)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_gsc_queries(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,url_id BIGINT UNSIGNED NOT NULL,data DATE NOT NULL,query_text VARCHAR(500) NOT NULL,impressions INT DEFAULT 0,clicks INT DEFAULT 0,ctr DECIMAL(10,7) DEFAULT 0,position DECIMAL(10,4),intent_json JSON,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,UNIQUE KEY uq(url_id,query_text,data),INDEX(url_id),INDEX(query_text(191)),INDEX(data)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_score_url(url_id BIGINT UNSIGNED PRIMARY KEY,opportunity_score DECIMAL(8,3) DEFAULT 0,seo_score DECIMAL(8,3) DEFAULT 0,engagement_score DECIMAL(8,3) DEFAULT 0,conversion_score DECIMAL(8,3) DEFAULT 0,data_quality_score DECIMAL(8,3) DEFAULT 0,volatility DECIMAL(8,5) DEFAULT 0,status VARCHAR(40),decision VARCHAR(40),calculated_at DATETIME DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_versoes_url(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,url_id BIGINT UNSIGNED NOT NULL,version_number INT NOT NULL,title TEXT,meta_description TEXT,h1 TEXT,content LONGTEXT,cta_json JSON,modules_json JSON,hypothesis_json JSON,generated_by VARCHAR(100),active TINYINT(1) DEFAULT 0,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX(url_id,active)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_experimentos(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,url_id BIGINT UNSIGNED NOT NULL,tipo ENUM('seo_temporal','cro_split') NOT NULL,change_type VARCHAR(80),pattern_key VARCHAR(255),hypothesis LONGTEXT,control_version_id BIGINT UNSIGNED,test_version_id BIGINT UNSIGNED,metric_primary VARCHAR(80),status ENUM('running','winner','loser','inconclusive','cancelled') DEFAULT 'running',phase ENUM('control','test','split') DEFAULT 'control',traffic_split DECIMAL(5,2) DEFAULT 50,baseline DECIMAL(14,7),result DECIMAL(14,7),improvement DECIMAL(14,7),confidence DECIMAL(8,5) DEFAULT 0,started_at DATETIME DEFAULT CURRENT_TIMESTAMP,phase_changed_at DATETIME,ended_at DATETIME,INDEX(status),INDEX(url_id),INDEX(pattern_key)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_memoria_padroes(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,pattern_key VARCHAR(255) NOT NULL UNIQUE,pattern_type VARCHAR(80),scope ENUM('local','cluster','global') DEFAULT 'local',conditions_json JSON,context_json JSON,action_json JSON,wins INT DEFAULT 0,losses INT DEFAULT 0,inconclusive INT DEFAULT 0,samples BIGINT DEFAULT 0,avg_lift DECIMAL(10,7) DEFAULT 0,cities_tested INT DEFAULT 0,categories_tested INT DEFAULT 0,confidence DECIMAL(8,5) DEFAULT 0,active TINYINT(1) DEFAULT 0,last_win_at DATETIME,created_at DATETIME DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", "CREATE TABLE IF NOT EXISTS tdb_feedback(id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,empresa_id BIGINT UNSIGNED,url_id BIGINT UNSIGNED,tipo VARCHAR(80),atributo VARCHAR(100),mensagem TEXT,suggested_value TEXT,confidence DECIMAL(6,5) DEFAULT .7,status ENUM('pending','approved','rejected','applied') DEFAULT 'pending',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX(status)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" ];foreach($sql as $s)$this->db->exec($s); } public function registerUrl(string $url,string $cidade='',string $estado='',string $bairro='',string $categoria='',string $segmento='',string $intencao='transacional'):int{ $cl=$this->generateCluster($categoria,$segmento,$intencao); $this->q("INSERT INTO tdb_urls(url,cidade,estado,bairro,categoria,segmento,intencao,cluster_key) VALUES(?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE cidade=VALUES(cidade),estado=VALUES(estado),bairro=VALUES(bairro),categoria=VALUES(categoria),segmento=VALUES(segmento),intencao=VALUES(intencao),cluster_key=VALUES(cluster_key)",[$url,$cidade,$estado,$bairro,$categoria,$segmento,$intencao,$cl]); return(int)$this->one("SELECT id FROM tdb_urls WHERE url=? LIMIT 1",[$url]); } private function generateCluster(string $c,string $s,string $i):string{return implode(':',array_map(fn($v)=>$this->n((string)$v),array_merge($this->getCategoryTree($c,$s),[$i?:'geral'])));} public function getCategoryTree(string $c,string $s=''):array{ $m=['pizzaria'=>['Alimentação','Pizzaria'],'restaurante'=>['Alimentação','Restaurante'],'hamburgueria'=>['Alimentação','Hamburgueria'],'lanchonete'=>['Alimentação','Lanchonete'],'padaria'=>['Alimentação','Padaria'],'eletricista'=>['Serviços urgentes','Eletricista'],'chaveiro'=>['Serviços urgentes','Chaveiro'],'encanador'=>['Serviços urgentes','Encanador'],'desentupidora'=>['Serviços urgentes','Desentupidora'],'dentista'=>['Saúde','Dentista'],'clinica'=>['Saúde','Clínica'],'advogado'=>['Serviços profissionais','Advocacia'],'contador'=>['Serviços profissionais','Contabilidade'],'hotel'=>['Hospedagem','Hotel'],'pousada'=>['Hospedagem','Pousada'],'supermercado'=>['Comércio','Supermercado'],'material_de_construcao'=>['Comércio','Materiais de construção']]; $n=$this->n($c);return$m[$n]??($s!==''?[$s,$c?:'Geral']:['Geral',$c?:'Geral']); } public function upsertCompany(array $x,array $src=[]):int{ $n=trim((string)($x['nome']??''));if($n==='')throw new InvalidArgumentException('Empresa sem nome.'); $id=0;$cnpj=trim((string)($x['cnpj']??'')); if($cnpj!=='')$id=(int)$this->one("SELECT id FROM tdb_empresas WHERE cnpj=? LIMIT 1",[$cnpj]); if(!$id)$id=(int)$this->one("SELECT id FROM tdb_empresas WHERE nome=? AND cidade=? AND COALESCE(bairro,'')=? LIMIT 1",[$n,(string)($x['cidade']??''),(string)($x['bairro']??'')]); $f=['nome','razao_social','cnpj','categoria','segmento','telefone','whatsapp','site','endereco','numero','bairro','cidade','estado','cep','latitude','longitude']; if($id){$set=[];$p=[];foreach($f as $k)if(array_key_exists($k,$x)){$set[]="$k=?";$p[]=$x[$k];}foreach(['horario_json','servicos_json','area_atendimento_json']as$k)if(isset($x[$k])){$set[]="$k=?";$p[]=is_array($x[$k])?$this->j($x[$k]):$x[$k];}if($set){$p[]=$id;$this->q("UPDATE tdb_empresas SET ".implode(',',$set)." WHERE id=?",$p);}} else{$this->q("INSERT INTO tdb_empresas(nome,razao_social,cnpj,categoria,segmento,telefone,whatsapp,site,endereco,numero,bairro,cidade,estado,cep,latitude,longitude,horario_json,servicos_json,area_atendimento_json) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[$n,$x['razao_social']??null,$x['cnpj']??null,$x['categoria']??null,$x['segmento']??null,$x['telefone']??null,$x['whatsapp']??null,$x['site']??null,$x['endereco']??null,$x['numero']??null,$x['bairro']??null,$x['cidade']??null,$x['estado']??null,$x['cep']??null,$x['latitude']??null,$x['longitude']??null,isset($x['horario_json'])?(is_array($x['horario_json'])?$this->j($x['horario_json']):$x['horario_json']):null,isset($x['servicos_json'])?(is_array($x['servicos_json'])?$this->j($x['servicos_json']):$x['servicos_json']):null,isset($x['area_atendimento_json'])?(is_array($x['area_atendimento_json'])?$this->j($x['area_atendimento_json']):$x['area_atendimento_json']):null]);$id=(int)$this->db->lastInsertId();} $sn=(string)($src['name']??'cadastro');$st=(string)($src['type']??'cadastro');$cf=(float)($src['confidence']??.8); foreach(['nome','razao_social','cnpj','categoria','segmento','telefone','whatsapp','site','endereco','bairro','cidade','estado','cep']as$k)if(!empty($x[$k]))$this->addEvidence('empresa',$id,$k,(string)$x[$k],$sn,$st,$cf,(bool)($src['validated_user']??false),(bool)($src['validated_company']??false)); if(!empty($x['horario_json']))$this->addEvidence('empresa',$id,'horario',is_array($x['horario_json'])?$this->j($x['horario_json']):(string)$x['horario_json'],$sn,$st,$cf,(bool)($src['validated_user']??false),(bool)($src['validated_company']??false)); return$id; } public function addEvidence(string $t,int $id,string $a,string $v,string $f,string $ft,float $cf=.8,bool $vu=false,bool $ve=false):void{ $cf=min(1,max(0,$cf));$this->q("INSERT INTO tdb_evidencias(entity_type,entity_id,atributo,valor,fonte,fonte_tipo,confidence,validado_usuario,validado_empresa,validado_em) VALUES(?,?,?,?,?,?,?,?,?,NOW())",[$t,$id,$a,$v,$f,$ft,$cf,$vu?1:0,$ve?1:0]);if($t==='empresa')$this->recalculateCompanyConfidence($id); } private function evidenceIsValid(array $f):bool{$ttl=(int)($this->c['ttl_days'][$f['atributo']]??180);$ts=strtotime((string)$f['coletado_em']);return$ts&&((time()-$ts)/86400)<=$ttl&&(float)$f['confidence']>=(float)$this->c['brain']['min_evidence_confidence'];} public function getEvidence(string $t,int $id,string $a):?array{$r=$this->all("SELECT * FROM tdb_evidencias WHERE entity_type=? AND entity_id=? AND atributo=? ORDER BY validado_empresa DESC,validado_usuario DESC,confidence DESC,validado_em DESC,coletado_em DESC",[$t,$id,$a]);foreach($r as$f)if($this->evidenceIsValid($f))return$f;return null;} public function mayPublish(string $t,int $id,string $a):bool{return$this->getEvidence($t,$id,$a)!==null;} public function safeFact(string $t,int $id,string $a,string $fb=''):string{$f=$this->getEvidence($t,$id,$a);return htmlspecialchars($f?(string)$f['valor']:$fb,ENT_QUOTES|ENT_SUBSTITUTE,'UTF-8');} private function recalculateCompanyConfidence(int $id):void{$cf=(float)$this->one("SELECT AVG(confidence) FROM tdb_evidencias WHERE entity_type='empresa' AND entity_id=?",[$id]);$this->q("UPDATE tdb_empresas SET confidence=?,ultima_validacao=NOW() WHERE id=?",[$cf,$id]);} public function companiesForPage(string $c,string $cat,?string $b=null,int $l=30):array{$s="SELECT * FROM tdb_empresas WHERE ativo=1 AND cidade=? AND categoria=? AND confidence>=?";$p=[$c,$cat,$this->c['brain']['min_evidence_confidence']];if($b){$s.=" AND bairro=?";$p[]=$b;}$s.=" ORDER BY confidence DESC,ultima_validacao DESC LIMIT ".max(1,min(100,$l));return$this->all($s,$p);} public function assignCROVariant(int $e,string $sid):string{return(hexdec(substr(hash('sha256',$e.'|'.$sid),0,8))%100)<50?'A':'B';} public function getRunningCROExperiment(int $id):?array{$r=$this->row("SELECT * FROM tdb_experimentos WHERE url_id=? AND tipo='cro_split' AND status='running' ORDER BY id DESC LIMIT 1",[$id]);return$r?:null;} public function tracker(int $id):string{$ep=strtok($_SERVER['REQUEST_URI']??'/','?').'?tdb_action=event';$ep=$this->j($ep);return"";} public function receiveEvent():never{$x=json_decode(file_get_contents('php://input')?:'',true);if(!is_array($x)||empty($x['url_id'])||empty($x['evento'])){http_response_code(400);exit;}$ok=['pageview','scroll','active_time','whatsapp_click','phone_click','internal_search','lead','feedback','exit_intent','copy_text'];if(!in_array($x['evento'],$ok,true)){http_response_code(400);exit;}$v=$x['variant']??null;if($v!==null&&!in_array($v,['A','B'],true))$v=null;$this->q("INSERT INTO tdb_eventos(url_id,session_id,evento,valor,experiment_id,variant,metadata) VALUES(?,?,?,?,?,?,?)",[(int)$x['url_id'],substr((string)($x['session_id']??''),0,100),(string)$x['evento'],isset($x['valor'])?(float)$x['valor']:null,isset($x['experiment_id'])&&$x['experiment_id']!==''?(int)$x['experiment_id']:null,$v,$this->j(is_array($x['metadata']??null)?$x['metadata']:[])]);http_response_code(204);exit;} public function aggregateEventsToMetrics(int $id,?string $d=null):void{$d=$d??date('Y-m-d',strtotime('-1 day'));if($d>=date('Y-m-d'))throw new InvalidArgumentException('Só dias fechados.');$a=$d.' 00:00:00';$b=$d.' 23:59:59';$e=$this->row("SELECT SUM(evento='pageview') pageviews,SUM(evento='whatsapp_click') whatsapp,SUM(evento='phone_click') phone,SUM(evento='lead') leads,AVG(CASE WHEN evento='scroll' THEN valor END) scroll_avg,AVG(CASE WHEN evento='active_time' THEN valor END) active_time_avg,COUNT(DISTINCT session_id) sessions FROM tdb_eventos WHERE url_id=? AND created_at BETWEEN ? AND ?",[$id,$a,$b]);$g=(int)$this->one("SELECT COUNT(*) FROM(SELECT session_id FROM tdb_eventos WHERE url_id=? AND created_at BETWEEN ? AND ? AND session_id IS NOT NULL AND session_id<>'' GROUP BY session_id HAVING MAX(evento='active_time' AND valor>=20)=1 OR MAX(evento='scroll' AND valor>=40)=1 OR MAX(evento IN('whatsapp_click','phone_click','lead','internal_search'))=1)x",[$id,$a,$b]);$s=(int)($e['sessions']??0);$r=$s?$g/$s:0;$this->q("INSERT INTO tdb_metricas_url(url_id,data,pageviews,sessions,engaged_sessions,engagement_rate,scroll_avg,active_time_avg,whatsapp_clicks,phone_clicks,leads) VALUES(?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE pageviews=VALUES(pageviews),sessions=VALUES(sessions),engaged_sessions=VALUES(engaged_sessions),engagement_rate=VALUES(engagement_rate),scroll_avg=VALUES(scroll_avg),active_time_avg=VALUES(active_time_avg),whatsapp_clicks=VALUES(whatsapp_clicks),phone_clicks=VALUES(phone_clicks),leads=VALUES(leads)",[$id,$d,(int)($e['pageviews']??0),$s,$g,$r,(float)($e['scroll_avg']??0),(float)($e['active_time_avg']??0),(int)($e['whatsapp']??0),(int)($e['phone']??0),(int)($e['leads']??0)]);} public function aggregateAllYesterday():int{$d=date('Y-m-d',strtotime('-1 day'));$x=$this->db->query("SELECT id FROM tdb_urls")->fetchAll(PDO::FETCH_COLUMN);foreach($x as$id)$this->aggregateEventsToMetrics((int)$id,$d);return count($x);} private function h(string $u,array $o=[]):array{$ch=curl_init($u);curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_CONNECTTIMEOUT=>10,CURLOPT_TIMEOUT=>(int)($o['timeout']??30),CURLOPT_FOLLOWLOCATION=>false,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2]);$m=strtoupper($o['method']??'GET');if($m!=='GET')curl_setopt($ch,CURLOPT_CUSTOMREQUEST,$m);if(array_key_exists('body',$o))curl_setopt($ch,CURLOPT_POSTFIELDS,$o['body']);if(!empty($o['headers']))curl_setopt($ch,CURLOPT_HTTPHEADER,$o['headers']);$raw=curl_exec($ch);$er=curl_error($ch);$no=curl_errno($ch);$http=(int)curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);if($no)throw new RuntimeException("cURL $no: $er");$d=json_decode((string)$raw,true);return['http'=>$http,'data'=>is_array($d)?$d:['raw'=>(string)$raw]];} public function gscAuthStart():never{if(empty($this->c['gsc']['enabled']))throw new RuntimeException('GSC desativado.');if(session_status()!==PHP_SESSION_ACTIVE)session_start();$s=bin2hex(random_bytes(24));$_SESSION['tdb_gsc_state']=$s;$g=$this->c['gsc'];header('Location: https://accounts.google.com/o/oauth2/v2/auth?'.http_build_query(['client_id'=>$g['client_id'],'redirect_uri'=>$g['redirect_uri'],'response_type'=>'code','scope'=>$g['scope'],'access_type'=>'offline','prompt'=>'consent','include_granted_scopes'=>'true','state'=>$s]));exit;} public function gscCallback():never{if(session_status()!==PHP_SESSION_ACTIVE)session_start();if(isset($_GET['error'])){http_response_code(400);exit('OAuth negado.');}$s=(string)($_GET['state']??'');$e=(string)($_SESSION['tdb_gsc_state']??'');if(!$s||!$e||!hash_equals($e,$s)){http_response_code(403);exit('State inválido.');}unset($_SESSION['tdb_gsc_state']);$code=(string)($_GET['code']??'');if(!$code){http_response_code(400);exit('Código ausente.');}$g=$this->c['gsc'];$r=$this->h('https://oauth2.googleapis.com/token',['method'=>'POST','headers'=>['Content-Type: application/x-www-form-urlencoded'],'body'=>http_build_query(['code'=>$code,'client_id'=>$g['client_id'],'client_secret'=>$g['client_secret'],'redirect_uri'=>$g['redirect_uri'],'grant_type'=>'authorization_code'])]);if($r['http']!==200||empty($r['data']['refresh_token'])){http_response_code(500);exit('Falha OAuth.');}$f=$g['token_file'];if(file_put_contents($f,$this->j(['refresh_token'=>$r['data']['refresh_token'],'created_at'=>date('c')]),LOCK_EX)===false)throw new RuntimeException('Falha ao gravar token.');@chmod($f,0600);exit('Google Search Console conectado.');} private function getGSCRefreshToken():string{$f=$this->c['gsc']['token_file'];if(!is_file($f))return'';$x=json_decode((string)file_get_contents($f),true);return is_array($x)?(string)($x['refresh_token']??''):'';} private function getGSCAccessToken():string{$g=$this->c['gsc'];$r=$this->getGSCRefreshToken();if(!$r)throw new RuntimeException('Refresh token ausente.');$x=$this->h('https://oauth2.googleapis.com/token',['method'=>'POST','headers'=>['Content-Type: application/x-www-form-urlencoded'],'body'=>http_build_query(['client_id'=>$g['client_id'],'client_secret'=>$g['client_secret'],'refresh_token'=>$r,'grant_type'=>'refresh_token'])]);if($x['http']!==200||empty($x['data']['access_token']))throw new RuntimeException('Falha token GSC.');return(string)$x['data']['access_token'];} public function fetchGSC(int $n=5):array{if(empty($this->c['gsc']['enabled']))return['rows'=>0,'disabled'=>true];$t=$this->getGSCAccessToken();$a=date('Y-m-d',strtotime("-$n days"));$b=date('Y-m-d',strtotime('-1 day'));$r=$this->gscQueryPaginated($t,$this->c['gsc']['site_url'],$a,$b);$k=$this->ingestGSCData($r);$this->rebuildUrlMetricsFromGSC($a,$b);return['rows'=>$k,'start'=>$a,'end'=>$b];} private function gscQueryPaginated(string $t,string $site,string $a,string $b):array{$u='https://www.googleapis.com/webmasters/v3/sites/'.rawurlencode($site).'/searchAnalytics/query';$out=[];$st=0;$l=25000;do{$x=$this->h($u,['method'=>'POST','headers'=>['Authorization: Bearer '.$t,'Content-Type: application/json'],'body'=>$this->j(['startDate'=>$a,'endDate'=>$b,'dimensions'=>['page','query','date'],'rowLimit'=>$l,'startRow'=>$st,'type'=>'web']),'timeout'=>60]);if($x['http']!==200)throw new RuntimeException('GSC HTTP '.$x['http']);$rr=$x['data']['rows']??[];if(!is_array($rr))$rr=[];foreach($rr as$r){$k=$r['keys']??[];if(count($k)<3)continue;$q=(string)$k[1];$out[]=['url'=>(string)$k[0],'query'=>$q,'date'=>(string)$k[2],'impressions'=>(int)($r['impressions']??0),'clicks'=>(int)($r['clicks']??0),'ctr'=>(float)($r['ctr']??0),'position'=>(float)($r['position']??0),'intent_json'=>$this->j($this->classifyIntentProfile($q))];}$st+=$l;}while(count($rr)===$l);return$out;} private function getUrlId(string $u):int{$id=(int)$this->one("SELECT id FROM tdb_urls WHERE url=? LIMIT 1",[$u]);if(!$id){$a=str_ends_with($u,'/')?rtrim($u,'/'):$u.'/';$id=(int)$this->one("SELECT id FROM tdb_urls WHERE url=? LIMIT 1",[$a]);}return$id;} public function ingestGSCData(array $rows):int{$s="INSERT INTO tdb_gsc_queries(url_id,data,query_text,impressions,clicks,ctr,position,intent_json) VALUES(?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE impressions=VALUES(impressions),clicks=VALUES(clicks),ctr=VALUES(ctr),position=VALUES(position),intent_json=VALUES(intent_json)";$n=0;foreach($rows as$r){$id=$this->getUrlId((string)$r['url']);if(!$id)continue;$this->q($s,[$id,$r['date'],$r['query'],(int)$r['impressions'],(int)$r['clicks'],(float)$r['ctr'],(float)$r['position'],$r['intent_json']]);$n++;}return$n;} private function rebuildUrlMetricsFromGSC(string $a,string $b):void{$r=$this->all("SELECT url_id,data,SUM(impressions) impressions,SUM(clicks) clicks,IF(SUM(impressions)>0,SUM(clicks)/SUM(impressions),0) ctr,IF(SUM(impressions)>0,SUM(position*impressions)/SUM(impressions),NULL) position FROM tdb_gsc_queries WHERE data BETWEEN ? AND ? GROUP BY url_id,data",[$a,$b]);foreach($r as$x)$this->q("INSERT INTO tdb_metricas_url(url_id,data,impressoes,cliques,ctr,posicao) VALUES(?,?,?,?,?,?) ON DUPLICATE KEY UPDATE impressoes=VALUES(impressoes),cliques=VALUES(cliques),ctr=VALUES(ctr),posicao=VALUES(posicao)",[(int)$x['url_id'],$x['data'],(int)$x['impressions'],(int)$x['clicks'],(float)$x['ctr'],$x['position']!==null?(float)$x['position']:null]);} public function classifyIntentProfile(string $q):array{$q=mb_strtolower(trim($q),'UTF-8');$p=['local'=>0.,'transactional'=>0.,'urgent'=>0.,'informational'=>0.,'navigational'=>0.];if(preg_match('/\b(perto de mim|próximo|proximo|na minha região|na minha regiao)\b/u',$q))$p['local']=.99;if(preg_match('/\b(em|no|na|de)\s+[\p{L}][\p{L}\s\-]{2,}\b/u',$q))$p['local']=max($p['local'],.92);if(preg_match('/\b(agora|24h|24 horas|aberto|aberta|urgente|emergência|emergencia)\b/u',$q))$p['urgent']=.96;if(preg_match('/\b(preço|preco|valor|comprar|delivery|entrega|contratar|orçamento|orcamento|reservar|agendar|pedido)\b/u',$q))$p['transactional']=.93;if(preg_match('/\b(como|o que é|o que e|guia|tutorial|dicas|qual|quando|por que|porque)\b/u',$q))$p['informational']=.86;if(preg_match('/\b(site oficial|login|entrar|telefone de|whatsapp de)\b/u',$q))$p['navigational']=.9;if($p['local']>=.9&&$p['transactional']<.5&&$p['informational']<.5)$p['transactional']=.84;if(preg_match('/\b(pizzaria|restaurante|hotel|eletricista|chaveiro|encanador|dentista|advogado|contador|loja|supermercado|oficina|farmácia|farmacia)\b/u',$q))$p['transactional']=max($p['transactional'],.82);return$p;} public function analyzeQueryGaps(int $id):array{$r=$this->all("SELECT query_text,SUM(impressions) imp,SUM(clicks) clicks,IF(SUM(impressions)>0,SUM(clicks)/SUM(impressions),0) ctr,IF(SUM(impressions)>0,SUM(position*impressions)/SUM(impressions),99) pos,intent_json FROM tdb_gsc_queries WHERE url_id=? AND data>=DATE_SUB(CURDATE(),INTERVAL 28 DAY) GROUP BY query_text,intent_json HAVING imp>=? ORDER BY imp DESC LIMIT 100",[$id,(int)$this->c['brain']['min_query_impressions']]);$g=[];foreach($r as$q){$i=json_decode((string)($q['intent_json']??'{}'),true)?:[];$p=(float)$q['pos'];$c=(float)$q['ctr'];$m=(int)$q['imp'];if(($i['urgent']??0)>.8&&$p>5)$g[]=['query'=>$q['query_text'],'intent_profile'=>$i,'hypothesis'=>['type'=>'dynamic_block','pattern'=>'availability_now','action'=>'Mostrar empresas abertas agora usando horário com evidência válida.','reason'=>"Intenção urgente: $m impressões, posição ".round($p,1),'target_query'=>$q['query_text'],'expected_metric'=>'ctr','risk'=>20,'evidence_required'=>['horario','cidade','nome']]];if(($i['transactional']??0)>.8&&$c<.03&&$m>=100)$g[]=['query'=>$q['query_text'],'intent_profile'=>$i,'hypothesis'=>['type'=>'cta','pattern'=>'transactional_cta_local','action'=>'Testar CTA contextual de contato/orçamento.','reason'=>'Intenção transacional alta e CTR baixo.','target_query'=>$q['query_text'],'expected_metric'=>'whatsapp','risk'=>10,'evidence_required'=>[]]];if(($i['informational']??0)>.75&&$p>6&&$m>=100)$g[]=['query'=>$q['query_text'],'intent_profile'=>$i,'hypothesis'=>['type'=>'faq','pattern'=>'evidence_faq','action'=>'Adicionar resposta curta baseada apenas em evidências.','reason'=>'Demanda informacional não coberta.','target_query'=>$q['query_text'],'expected_metric'=>'engagement','risk'=>20,'evidence_required'=>[]]];}return['main_query'=>$r[0]['query_text']??'','main_metrics'=>$r[0]??[],'gaps'=>$g];} public function calculateScore(int $id):array{$m=$this->row("SELECT SUM(impressoes) impressoes,SUM(cliques) cliques,IF(SUM(impressoes)>0,SUM(cliques)/SUM(impressoes),0) ctr,IF(SUM(impressoes)>0,SUM(posicao*impressoes)/SUM(impressoes),99) posicao,SUM(pageviews) pageviews,SUM(sessions) sessions,SUM(engaged_sessions) engaged_sessions,IF(SUM(sessions)>0,SUM(engaged_sessions)/SUM(sessions),0) engagement_rate,SUM(whatsapp_clicks) whatsapp,SUM(leads) leads FROM tdb_metricas_url WHERE url_id=? AND data>=DATE_SUB(CURDATE(),INTERVAL 28 DAY)",[$id]);$im=max(1,(int)($m['impressoes']??0));$se=max(1,(int)($m['sessions']??0));$ctr=(float)($m['ctr']??0);$pos=(float)($m['posicao']??99);$lr=(int)($m['leads']??0)/$se;$wr=(int)($m['whatsapp']??0)/$se;$er=(float)($m['engagement_rate']??0);$vis=min(100,log10($im+10)*25);$ex=match(true){$pos<=1=>.28,$pos<=3=>.15,$pos<=5=>.08,$pos<=10=>.04,$pos<=20=>.02,default=>.01};$cp=max(0,min(100,(($ex-$ctr)/max(.001,$ex))*100));$cv=max(0,min(100,100-$lr*1000));$ro=match(true){$pos<=3=>10,$pos<=7=>50,$pos<=15=>90,$pos<=30=>100,default=>70};$v=$this->calculateVolatility($id);$sc=($vis*.22+$cp*.23+$cv*.18+$ro*.27+max(0,100-$er*100)*.10)*$this->volatilityConfidence($v);$sc=min(100,max(0,$sc));$r=['opportunity_score'=>round($sc,2),'volatility'=>round($v,5),'metrics'=>['impressions'=>$im,'sessions'=>$se,'ctr'=>$ctr,'position'=>$pos,'lead_rate'=>$lr,'whatsapp_rate'=>$wr,'engagement_rate'=>$er]];$r['decision']=$this->decisionGate($r);$this->q("INSERT INTO tdb_score_url(url_id,opportunity_score,engagement_score,conversion_score,volatility,status,decision,calculated_at) VALUES(?,?,?,?,?,'active',?,NOW()) ON DUPLICATE KEY UPDATE opportunity_score=VALUES(opportunity_score),engagement_score=VALUES(engagement_score),conversion_score=VALUES(conversion_score),volatility=VALUES(volatility),status='active',decision=VALUES(decision),calculated_at=NOW()",[$id,$sc,$er*100,min(100,$lr*1000),$v,$r['decision']]);return$r;} private function calculateVolatility(int $id):float{$v=array_map('floatval',$this->q("SELECT ctr FROM tdb_metricas_url WHERE url_id=? AND data>=DATE_SUB(CURDATE(),INTERVAL 28 DAY) AND impressoes>0 ORDER BY data",[$id])->fetchAll(PDO::FETCH_COLUMN));if(count($v)<4)return 0;$m=array_sum($v)/count($v);if($m<=0)return 0;$s=0.;foreach($v as$x)$s+=($x-$m)**2;return sqrt($s/count($v))/$m;} private function volatilityConfidence(float $v):float{return$v<.15?1:($v<.35?.7:.3);} private function decisionGate(array $r):string{$m=$r['metrics'];if($this->c['brain']['protect_winners']&&$m['position']>0&&$m['position']<=3&&$m['ctr']>=.08)return'PROTECT';if($r['volatility']>=.5)return'OBSERVE';if($r['opportunity_score']>=75)return'AGGRESSIVE';if($r['opportunity_score']>=45)return'EXPERIMENT';return'OBSERVE';} public function riskScore(string $x,array $r):float{$b=['cta'=>10,'bloco'=>15,'dynamic_block'=>15,'faq'=>20,'meta'=>30,'h1'=>45,'title'=>55,'content_30pct'=>65,'url'=>100][$x]??50;$m=$r['metrics'];$t=min(1.8,max(.7,log10(($m['impressions']??0)+10)/3));$k=$m['position']<=3?1.6:($m['position']<=10?1.2:.9);return min(100,$b*$t*$k*(1+min(1,(float)$r['volatility'])));} public function canAutoApply(string $x,array $r):bool{if(!$this->c['brain']['auto_apply'])return false;$k=$this->riskScore($x,$r);if($r['decision']==='PROTECT'&&$k>30)return false;return$r['opportunity_score']>=70&&$k<=(float)$this->c['brain']['max_auto_risk'];} public function generateHypotheses(int $id,array $s):array{$o=[];$g=$this->analyzeQueryGaps($id);foreach($g['gaps']as$x)$o[]=$x['hypothesis'];$m=$s['metrics'];if($m['position']<=10&&$m['ctr']<.025)$o[]=['type'=>'title','pattern'=>'title_intent_local','reason'=>'Bom ranking com CTR baixo.','action'=>'Testar title aderente à intenção dominante sem inventar fatos.','target_query'=>$g['main_query']??'','expected_metric'=>'ctr','risk'=>55,'evidence_required'=>[]];if($m['whatsapp_rate']<.01)$o[]=['type'=>'cta','pattern'=>'cta_local_contextual','reason'=>'Baixa taxa de WhatsApp.','action'=>'Testar CTA contextual.','target_query'=>$g['main_query']??'','expected_metric'=>'whatsapp','risk'=>10,'evidence_required'=>[]];$u=$this->getUrlRecord($id);$mem=$this->getPatternsForCategory((string)($u['categoria']??''),(string)($u['segmento']??''),20);if(!empty($this->c['llm']['enabled']))try{foreach($this->generateLLMHypotheses(['url'=>$u,'score'=>$s,'query_intelligence'=>$g,'memory'=>$mem,'category_tree'=>$this->getCategoryTree((string)$u['categoria'],(string)$u['segmento'])])as$h)if($this->validateHypothesis($h))$o[]=$h;}catch(Throwable $e){error_log($e->getMessage());}$z=[];foreach($o as$h)if($this->validateHypothesis($h)){$k=($h['type']??'').'|'.($h['pattern']??'').'|'.($h['target_query']??'');$z[$k]??=$h;}return array_values($z);} private function generateLLMHypotheses(array $ctx):array{$g=$this->c['llm'];if(empty($g['endpoint']))return[];$h=['Content-Type: application/json'];if(!empty($g['bearer_token']))$h[]='Authorization: Bearer '.$g['bearer_token'];$r=$this->h($g['endpoint'],['method'=>'POST','headers'=>$h,'body'=>$this->j(['task'=>'generate_hypotheses','rules'=>['return_max'=>3,'never_invent_facts'=>true,'facts_require_evidence'=>true,'prefer_low_risk_changes'=>true,'output_json_only'=>true],'context'=>$ctx]),'timeout'=>(int)$g['timeout_seconds']]);$x=$r['data']['hypotheses']??[];return is_array($x)?array_slice($x,0,3):[];} private function validateHypothesis(array $h):bool{return in_array((string)($h['type']??''),['cta','bloco','dynamic_block','faq','meta','h1','title','content_30pct'],true)&&trim((string)($h['action']??''))!==''&&trim((string)($h['reason']??''))!=='';} public function getPatternsForCategory(string $c,string $s='',int $l=20):array{$t=$this->getCategoryTree($c,$s);$a=$this->all("SELECT * FROM tdb_memoria_padroes WHERE active=1 ORDER BY confidence DESC,avg_lift DESC,samples DESC LIMIT 200");$o=[];foreach($a as$p){$x=json_decode((string)($p['context_json']??'{}'),true)?:[];$pt=is_array($x['category_tree']??null)?$x['category_tree']:[];if(array_intersect($t,$pt)||$p['scope']==='global'){$o[]=$p;if(count($o)>=$l)break;}}return$o;} public function recordPatternResult(string $p,string $st,float $lift,int $sm,string $city,string $cat,string $seg='',array $x=[]):void{$r=$this->row("SELECT * FROM tdb_memoria_padroes WHERE pattern_key=? LIMIT 1",[$p]);$tree=$this->getCategoryTree($cat,$seg);if(!$r){$ctx=['cities'=>[$city],'categories'=>[$cat],'segments'=>$seg!==''?[$seg]:[],'category_tree'=>$tree,'last_context'=>$x];$this->q("INSERT INTO tdb_memoria_padroes(pattern_key,pattern_type,conditions_json,context_json,action_json) VALUES(?,'autolearn','{}',?,'{}')",[$p,$this->j($ctx)]);$r=$this->row("SELECT * FROM tdb_memoria_padroes WHERE pattern_key=?",[$p]);}$c=json_decode((string)$r['context_json'],true)?:[];$ci=$c['cities']??[];$ca=$c['categories']??[];$se=$c['segments']??[];$ct=$c['category_tree']??[];if($city!==''&&!in_array($city,$ci,true))$ci[]=$city;if($cat!==''&&!in_array($cat,$ca,true))$ca[]=$cat;if($seg!==''&&!in_array($seg,$se,true))$se[]=$seg;foreach($tree as$n)if(!in_array($n,$ct,true))$ct[]=$n;$w=(int)$r['wins']+($st==='winner');$l=(int)$r['losses']+($st==='loser');$in=(int)$r['inconclusive']+($st==='inconclusive');$os=(int)$r['samples'];$ns=$os+max(0,$sm);$al=$ns?(float($r['avg_lift'])*$os+$lift*max(0,$sm))/$ns:$lift;$cf=($w+1)/max(2,$w+$l+2);$sc=$this->evaluateMemoryScope($w,$l,$ns,$al,count($ci),count($ca));$ac=$w>=3&&$cf>=.7&&$al>0?1:0;$ctx=['cities'=>$ci,'categories'=>$ca,'segments'=>$se,'category_tree'=>$ct,'last_context'=>$x];$this->q("UPDATE tdb_memoria_padroes SET wins=?,losses=?,inconclusive=?,samples=?,avg_lift=?,cities_tested=?,categories_tested=?,confidence=?,scope=?,active=?,context_json=?,last_win_at=IF(?='winner',NOW(),last_win_at) WHERE pattern_key=?",[$w,$l,$in,$ns,$al,count($ci),count($ca),$cf,$sc,$ac,$this->j($ctx),$st,$p]);} private function evaluateMemoryScope(int $w,int $l,int $s,float $a,int $c,int $k):string{$x=$w*2-$l*3+$s/100+$a*1000+$c*10+$k*10;return$x>200&&$c>=5&&$k>=3?'global':($x>80&&$k>=2?'cluster':'local');} public function createVersion(int $id,array $d,string $by='brain'):int{$v=(int)$this->one("SELECT COALESCE(MAX(version_number),0)+1 FROM tdb_versoes_url WHERE url_id=?",[$id]);$this->q("INSERT INTO tdb_versoes_url(url_id,version_number,title,meta_description,h1,content,cta_json,modules_json,hypothesis_json,generated_by) VALUES(?,?,?,?,?,?,?,?,?,?)",[$id,$v,$d['title']??'',$d['meta_description']??'',$d['h1']??'',$d['content']??'',$this->j(is_array($d['cta']??null)?$d['cta']:[]),$this->j(is_array($d['modules']??null)?$d['modules']:[]),$this->j(is_array($d['hypothesis']??null)?$d['hypothesis']:[]),$by]);return(int)$this->db->lastInsertId();} public function activeVersion(int $id):?array{$r=$this->row("SELECT * FROM tdb_versoes_url WHERE url_id=? AND active=1 ORDER BY id DESC LIMIT 1",[$id]);return$r?:null;} public function promoteVersion(int $v):void{$id=(int)$this->one("SELECT url_id FROM tdb_versoes_url WHERE id=?",[$v]);if(!$id)return;$this->db->beginTransaction();try{$this->q("UPDATE tdb_versoes_url SET active=0 WHERE url_id=?",[$id]);$this->q("UPDATE tdb_versoes_url SET active=1 WHERE id=?",[$v]);$this->db->commit();}catch(Throwable $e){$this->db->rollBack();throw$e;}} public function createExperiment(int $id,string $t,string $ch,string $p,string $h,int $a,int $b,string $m='ctr'):int{if(!in_array($t,['seo_temporal','cro_split'],true))throw new InvalidArgumentException('Experimento inválido.');$ph=$t==='cro_split'?'split':'control';$this->q("INSERT INTO tdb_experimentos(url_id,tipo,change_type,pattern_key,hypothesis,control_version_id,test_version_id,metric_primary,status,phase,phase_changed_at) VALUES(?,?,?,?,?,?,?,?,'running',?,NOW())",[$id,$t,$ch,$p,$h,$a,$b,$m,$ph]);return(int)$this->db->lastInsertId();} private function canStartExperimentInCluster(string $k):bool{if(!$k)return true;$t=(int)$this->one("SELECT COUNT(*) FROM tdb_urls WHERE cluster_key=?",[$k]);$r=(int)$this->one("SELECT COUNT(*) FROM tdb_experimentos e JOIN tdb_urls u ON u.id=e.url_id WHERE u.cluster_key=? AND e.status='running'",[$k]);return$rc['brain']['cluster_concurrency_rate']));} private function sampleConfidence(int $s,float $v):array{$r=$v<.15?750:($v<.35?1500:3000);return['confidence'=>min(.99,$s/$r),'required'=>$r,'force_inconclusive'=>$v>=.5&&$s<$r];} private function metricForPeriod(int $id,string $m,string $a,string $b):float{$x=['ctr'=>'IF(SUM(impressoes)>0,SUM(cliques)/SUM(impressoes),0)','position'=>'IF(SUM(impressoes)>0,SUM(posicao*impressoes)/SUM(impressoes),99)','impressions'=>'SUM(impressoes)','clicks'=>'SUM(cliques)','leads'=>'SUM(leads)','whatsapp'=>'SUM(whatsapp_clicks)','pageviews'=>'SUM(pageviews)','engagement'=>'IF(SUM(sessions)>0,SUM(engaged_sessions)/SUM(sessions),0)','scroll'=>'AVG(scroll_avg)','active_time'=>'AVG(active_time_avg)'];$e=$x[$m]??$x['ctr'];return(float)$this->one("SELECT $e FROM tdb_metricas_url WHERE url_id=? AND data>=DATE(?) AND data<=DATE(?)",[$id,$a,$b]);} private function croVariantMetrics(int $e,string $v,string $m):array{$r=$this->row("SELECT COUNT(DISTINCT IF(evento='pageview',session_id,NULL)) sessions,SUM(evento='whatsapp_click') whatsapp,SUM(evento='phone_click') phone,SUM(evento='lead') leads,COUNT(DISTINCT IF((evento='active_time' AND valor>=20)OR(evento='scroll' AND valor>=40)OR evento IN('whatsapp_click','phone_click','lead','internal_search'),session_id,NULL)) engaged FROM tdb_eventos WHERE experiment_id=? AND variant=?",[$e,$v]);$s=max(1,(int)($r['sessions']??0));$z=match($m){'leads'=>(int)($r['leads']??0)/$s,'engagement'=>(int)($r['engaged']??0)/$s,default=>(int)($r['whatsapp']??0)/$s};return['value'=>$z,'sessions'=>$s,'raw'=>$r];} public function analyze():array{$rep=['urls_analyzed'=>0,'experiments_created'=>0,'experiments_checked'=>0,'winners'=>0,'losers'=>0,'inconclusive'=>0,'rollbacks'=>0];foreach($this->all("SELECT * FROM tdb_urls")as$u){$s=$this->calculateScore((int)$u['id']);$rep['urls_analyzed']++;if(!in_array($s['decision'],['AGGRESSIVE','EXPERIMENT'],true)||!$this->canStartExperimentInCluster((string)$u['cluster_key'])||(int)$this->one("SELECT COUNT(*) FROM tdb_experimentos WHERE url_id=? AND status='running'",[$u['id']]))continue;foreach($this->generateHypotheses((int)$u['id'],$s)as$h){if(!$this->canAutoApply((string)$h['type'],$s))continue;$a=$this->activeVersion((int)$u['id']);if(!$a)continue;$b=$this->createVersion((int)$u['id'],$this->buildVariantFromHypothesis($a,$h,$u),'brain_v1.8.6');$cro=in_array((string)$h['type'],['cta','bloco','dynamic_block'],true);$this->createExperiment((int)$u['id'],$cro?'cro_split':'seo_temporal',(string)$h['type'],(string)($h['pattern']??'autonomous'),(string)$h['reason'],(int)$a['id'],$b,(string)($h['expected_metric']??($cro?'whatsapp':'ctr')));$rep['experiments_created']++;break;}} $e=$this->all("SELECT e.*,u.cidade,u.categoria,u.segmento,u.cluster_key,s.volatility FROM tdb_experimentos e JOIN tdb_urls u ON u.id=e.url_id LEFT JOIN tdb_score_url s ON s.url_id=e.url_id WHERE e.status='running'");foreach($e as$x){$rep['experiments_checked']++;$d=(time()-strtotime((string)$x['started_at']))/86400;$md=(int)$this->c['brain']['min_experiment_days'];if($x['tipo']==='seo_temporal'){if($x['phase']==='control'&&$d>=$md){$b=$this->metricForPeriod((int)$x['url_id'],(string)$x['metric_primary'],(string)$x['started_at'],date('Y-m-d H:i:s'));$this->promoteVersion((int)$x['test_version_id']);$this->q("UPDATE tdb_experimentos SET baseline=?,phase='test',phase_changed_at=NOW() WHERE id=?",[$b,$x['id']]);continue;}if($x['phase']==='test'){if((time()-strtotime((string)$x['phase_changed_at']))/86400<$md)continue;$r=$this->metricForPeriod((int)$x['url_id'],(string)$x['metric_primary'],(string)$x['phase_changed_at'],date('Y-m-d H:i:s'));$this->finishExperiment($x,(float)$x['baseline'],$r,$this->periodSample((int)$x['url_id'],(string)$x['phase_changed_at'],date('Y-m-d H:i:s')),$rep);}}elseif($d>=$md){$a=$this->croVariantMetrics((int)$x['id'],'A',(string)$x['metric_primary']);$b=$this->croVariantMetrics((int)$x['id'],'B',(string)$x['metric_primary']);$sm=min((int)$a['sessions'],(int)$b['sessions']);if($sm>=50)$this->finishExperiment($x,(float)$a['value'],(float)$b['value'],$sm,$rep);}}return$rep;} private function buildVariantFromHypothesis(array $a,array $h,array $u):array{$v=['title'=>$a['title'],'meta_description'=>$a['meta_description'],'h1'=>$a['h1'],'content'=>$a['content'],'cta'=>json_decode((string)($a['cta_json']??'{}'),true)?:[],'modules'=>json_decode((string)($a['modules_json']??'{}'),true)?:[],'hypothesis'=>$h];$c=(string)($u['cidade']??'');$cat=(string)($u['categoria']??'');switch($h['type']){case'cta':$v['cta']['primary_text']=$this->ctaForCategory($cat,$c);break;case'dynamic_block':case'bloco':$v['modules']['intent_block']=['enabled'=>true,'target_query'=>$h['target_query']??'','title'=>$cat.' abertos agora em '.$c,'requires_evidence'=>$h['evidence_required']??[]];break;case'faq':$v['modules']['faq_intent'][]=['query'=>$h['target_query']??'','evidence_only'=>true];break;case'meta':$v['meta_description']=trim("$cat em $c. Encontre opções locais, contato e informações verificadas.");break;case'h1':$v['h1']=trim("$cat em $c");break;case'title':$v['title']=trim("$cat em $c | Top do Bairro");break;case'content_30pct':$v['modules']['semantic_enrichment']=['enabled'=>true,'target_query'=>$h['target_query']??'','evidence_only'=>true,'category_tree'=>$this->getCategoryTree($cat,(string)($u['segmento']??''))];}return$v;} private function ctaForCategory(string $c,string $city):string{$n=$this->n($c);if(in_array($n,['eletricista','chaveiro','encanador','desentupidora'],true))return"Solicitar atendimento em $city";if(in_array($n,['pizzaria','restaurante','hamburgueria','lanchonete'],true))return"Ver opções em $city";if(in_array($n,['dentista','clinica'],true))return"Ver contatos em $city";return"Encontrar opções em $city";} private function periodSample(int $id,string $a,string $b):int{return max(0,(int)$this->one("SELECT GREATEST(COALESCE(SUM(sessions),0),COALESCE(SUM(impressoes),0)) FROM tdb_metricas_url WHERE url_id=? AND data>=DATE(?) AND data<=DATE(?)",[$id,$a,$b]));} private function finishExperiment(array $e,float $a,float $b,int $sm,array &$rep):void{$lift=$a>0?($b-$a)/$a:0;$cf=$this->sampleConfidence($sm,(float)($e['volatility']??0));$ml=(float)$this->c['brain']['min_lift'];$st=$cf['force_inconclusive']?'inconclusive':($lift>=$ml&&$cf['confidence']>=.8?'winner':($lift<=-$ml&&$cf['confidence']>=.8?'loser':'inconclusive'));if($st==='winner'){$this->promoteVersion((int)$e['test_version_id']);$rep['winners']++;}elseif($st==='loser'){if($this->c['brain']['auto_rollback']&&$lift<=-(float)$this->c['brain']['rollback_threshold']){$this->promoteVersion((int)$e['control_version_id']);$rep['rollbacks']++;}$rep['losers']++;}else{if($e['tipo']==='seo_temporal')$this->promoteVersion((int)$e['control_version_id']);$rep['inconclusive']++;}$this->recordPatternResult((string)($e['pattern_key']?:'unknown'),$st,$lift,$sm,(string)($e['cidade']??''),(string)($e['categoria']??''),(string)($e['segmento']??''),['experiment_id'=>(int)$e['id'],'change_type'=>$e['change_type'],'metric'=>$e['metric_primary'],'volatility'=>(float)($e['volatility']??0)]);$this->q("UPDATE tdb_experimentos SET status=?,result=?,improvement=?,confidence=?,ended_at=NOW() WHERE id=?",[$st,$b,$lift,$cf['confidence'],$e['id']]);} public function saveFeedback(array $f):int{$this->q("INSERT INTO tdb_feedback(empresa_id,url_id,tipo,atributo,mensagem,suggested_value,confidence) VALUES(?,?,?,?,?,?,?)",[$f['empresa_id']??null,$f['url_id']??null,$f['tipo']??'correcao',$f['atributo']??null,$f['mensagem']??'',$f['suggested_value']??null,(float)($f['confidence']??.7)]);return(int)$this->db->lastInsertId();} public function getUrlRecord(int $id):array{return$this->row("SELECT * FROM tdb_urls WHERE id=?",[$id]);} public function ensureInitialVersion(int $id,array $p):int{$a=$this->activeVersion($id);if($a)return(int)$a['id'];$v=$this->createVersion($id,$p,'template_original');$this->promoteVersion($v);return$v;} public function pageState(int $id):array{$a=$this->activeVersion($id);$e=$this->getRunningCROExperiment($id);$s=['version'=>$a,'experiment'=>null,'variant'=>null];if($e){if(session_status()!==PHP_SESSION_ACTIVE)session_start();$_SESSION['tdb_sid']??=bin2hex(random_bytes(16));$v=$this->assignCROVariant((int)$e['id'],(string)$_SESSION['tdb_sid']);$s['experiment']=$e;$s['variant']=$v;$x=$this->row("SELECT * FROM tdb_versoes_url WHERE id=?",[(int)($v==='B'?$e['test_version_id']:$e['control_version_id'])]);if($x)$s['version']=$x;}return$s;} public function htmlExperimentAttributes(array $s):string{return empty($s['experiment'])?'':' data-experiment-id="'.(int)$s['experiment']['id'].'" data-variant="'.htmlspecialchars((string)$s['variant'],ENT_QUOTES,'UTF-8').'"';} public function dashboard(int $l=100):array{return$this->all("SELECT u.url,u.cidade,u.categoria,u.cluster_key,s.opportunity_score,s.volatility,s.decision,s.calculated_at FROM tdb_urls u LEFT JOIN tdb_score_url s ON s.url_id=u.id ORDER BY s.opportunity_score DESC LIMIT ".max(1,min(500,$l)));} } $TDB_BRAIN=null; try{$d=$TDB_CONFIG['db'];$pdo=new PDO("mysql:host={$d['host']};dbname={$d['name']};charset={$d['charset']}",$d['user'],$d['pass'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC,PDO::ATTR_EMULATE_PREPARES=>false]);$TDB_BRAIN=new TopDoBairroBrain($pdo,$TDB_CONFIG);}catch(Throwable $e){error_log('TDB Brain: '.$e->getMessage());} if($TDB_BRAIN&&isset($_GET['tdb_action'])){$a=(string)$_GET['tdb_action'];try{ if($a==='event')$TDB_BRAIN->receiveEvent(); if($a==='gsc_auth')$TDB_BRAIN->gscAuthStart(); if($a==='gsc_callback')$TDB_BRAIN->gscCallback(); if(in_array($a,['cron_events','cron_gsc','cron_analyze','dashboard'],true)){$k=(string)($_GET['key']??'');if(!$k||!hash_equals((string)$TDB_CONFIG['brain']['cron_key'],$k)){http_response_code(403);exit('Forbidden');}header('Content-Type: application/json; charset=utf-8');$r=match($a){'cron_events'=>['ok'=>true,'aggregated'=>$TDB_BRAIN->aggregateAllYesterday()],'cron_gsc'=>['ok'=>true,'gsc'=>$TDB_BRAIN->fetchGSC((int)$TDB_CONFIG['brain']['gsc_days_back'])],'cron_analyze'=>['ok'=>true,'analysis'=>$TDB_BRAIN->analyze()],default=>['ok'=>true,'dashboard'=>$TDB_BRAIN->dashboard()]};echo json_encode($r,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);exit;} }catch(Throwable $e){error_log('TDB Brain route: '.$e->getMessage());http_response_code(500);header('Content-Type: application/json');echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);exit;}}

baterias barreiro bh , baterias barreiro , baterias barreiro de baixo , baterias barreiro de cima


Rua Servilo de Moro, loja 2, Barreiro / Santa Helena - Belo Horizonte - MG - 30640-490

Baterias em Barreiro, Belo Horizonte - MG Anuncie Aqui!

Coloque sua empresa nessa pagina : Fale Conosco: WhatsApp: (31)98687-1399

TOP
DO BAIRRO
GUIA LOCAL ATUALIZADO

Top do Bairro

Busca local
Informações da região
Contato rápido
Falar no WhatsApp Anunciar minha empresa
Mais visibilidade Presença local no Google
Clientes reais Pessoas da região
Conteúdo atualizado Página relevante
Empresa local Foco no bairro/cidade

Referências locais

Links relacionados

Destaque sua empresa no Top do Bairro

Quero anunciar agora

© Top do Bairro — páginas leves, organizadas e atualizadas.
Top do Bairro® Marca registrada - todos os direitos reservados | Quinta 03/09/2026