Loja de Barreiro Baterias BH
Baterias em - Belo Horizonte / MGInformaçõ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;}}
Rua Servilo de Moro, loja 2, Barreiro / Santa Helena - Belo Horizonte - MG - 30640-490
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
Coloque sua empresa nessa pagina : Fale Conosco: WhatsApp: (31)98687-1399
TOP
DO BAIRRO
DO BAIRRO
GUIA LOCAL ATUALIZADO
Top do Bairro
Busca local
Informações da região
Contato rápido
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
Destaques patrocinados
Empresas verificadas
Dados de localização:
OpenStreetMap contributors
e
Geoapify.
Anúncios mais Acessados
Churrasco Fest - Buffet de Churrasco no Barreiro - BH - MG / Buffet de Churrasco - Barreiro - Belo Horizonte - MG / Churrasqueiro no Barreiro - MG – BH
ATUAL COMUNICAÇÃO VISUAL
Bellomari - Salão de Festas - Barreiro BH - Salão de Festas Barreiro Belo Horizonte / MG salao-de-festas-barreiro-bh
Camilla cabeleireiros
Coopev / GNH Brasil - Proteção Veicular no Barreiro BH
Coworking Barreiro
Dentista no Barreiro BH
Fabrica de Laje Barreiro BH
GNH Brasil Proteçao Veicular
Infortech - Vendas e Assistência Técnica (31) 3384-7784
Lavagem de Sofá - Limpeza e Impermeabilização de sofás e estofados Barreiro MG
Leal Marcas - Loja de Roupas Atacado e Varejo - Barreiro BH
Loja de Barreiro Baterias BH
Lotes em Igarapé - MG / Lote em Igarape - MG / Loteamentos em CondomÃnio Fechado - Igarapé - MG / Terrenos em Igarapé - MG / Vendas de Loteamentos - Igarapé - MG - Preços de Lote em Igarapé - MG
Nude Lingerie - Barreiro BH
PowerCar Proteção Veicular (31)99745-4129 WhatsApp
Rubi Confeitaria - (31) 3011-6027
Salão Ellos - Barreiro BH -
Salão de Festas infantil no Barreiro BH
Scap Som Centro Automotivo
Mais Procurados
--
2026
AUTO
Buffet
Churrasqueiro
Comida
DE
Go
Limpeza
MMA
Massagem
Pedreiro
Podologia
RUBI
Som
Veicular
acampamento
aces
acessorios
acompanhantes
advogados
agua
and
ao
assistência
atacado
automoveis
bairro
barreiro
bebidas
beleza
bellomari
bolo
cabeleireiros
cama
camilla
casa
clinica
confeitaria
contato
corte
coworking
cross
curso
da
decoração
dedetizador
desentupidoras
dia
diarista
distribuidora
dobra
e
ellos
em
es
escola
estofados
festa
festas
fit
gravata
herbalife
infortech
isopor
japonesa
laje
lavar
leal
lindeia
lingerie
log
lote
maquiagem
marcas
mineral
no
noiva
nova
nude
or
panfletagem
pet
powercar
programa
protecao
próximo
puffs
quadro
roupa
roupas
salao
scap
seguro
select
sex
shop
show
sofa
veterinaria




