diff --git a/.env b/.env index 22a45ac..cd2b306 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ OPENAI_API_KEY=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 -GEMINI_API_KEY=AIzaSyBEtSE6SpdOYXc0p5b5aepdcRuu53jHaFA \ No newline at end of file +GEMINI_API_KEY=AIzaSyA_NLebC6j6LJddKvSJ9Bn6W-zHrBG655Y \ No newline at end of file diff --git a/Auditoria.md b/Auditoria.md new file mode 100644 index 0000000..ff331ef --- /dev/null +++ b/Auditoria.md @@ -0,0 +1,37 @@ +Objetivo: criar um Framework de Auditoria permite que você trate a AI como um "Quality Assurance" (QA) rigoroso. + +Minha sugestão é criarmos o "Google Ads Scorecard (GAS)". Ele será um conjunto de regras que a AI deve aplicar ao JSON (fornecido) para gerar a nota. + +Proposta de Checklist de Auditoria (Regras de Qualificação) +Para que a AI dê uma nota de 0 a 10, ela deve pontuar os seguintes critérios: + +1. Relevância e Keyword Match (Peso 3.0) +Critério: Pelo menos 5 títulos devem ser idênticos às keywords de fundo de funil. + +Penalidade: -0.5 para cada título que tentar "enfeitar" a palavra-chave principal (ex: mudar "advogado guarda" para "melhor advogado de guarda"). + +2. Compliance Editorial (Peso 2.0) +Critério: Ausência total de termos proibidos (grátis, gratuito) e formatação (excesso de pontuação ou caixa alta). + +Penalidade: Nota 0 imediata em Compliance se houver a palavra "grátis". + +3. Aproveitamento de Espaço (Peso 2.0) +Critério: As descrições devem ter entre 80 e 90 caracteres. Títulos devem estar próximos dos 30. + +Penalidade: -0.5 para descrições muito curtas que desperdiçam o "espaço de tela" no celular. + +4. Variedade de Gatilhos (Peso 2.0) +Critério: Presença de 3 pilares: Ação (CTA), Autoridade (Anos de exp/Nota 5.0) e Localização (Cidade/Estado). + +Penalidade: -1.0 se todos os títulos forem apenas descritivos, sem convidar ao clique. + +5. Extensões e Ativos (Peso 1.0) +Critério: Sitelinks com descrições preenchidas e Callouts focadas em benefícios reais. + +Como aplicar isso agora? +Você pode configurar essas regras como um "Juiz de Campanha". Sempre que você gerar um JSON, você roda este prompt: + +"Aja como Auditor de Google Ads. Analise o JSON enviado e atribua uma nota de 0 a 10 com base nos seguintes pesos: Keyword Match (3.0), Compliance (2.0), Espaço (2.0), Gatilhos (2.0) e Ativos (1.0). Liste as falhas específicas e como corrigi-las para chegar ao 10." + +Minha ideia adicional: O "Simulador de CTR" +Além da nota, podemos pedir para a AI simular como o anúncio apareceria no Google (em formato de texto) para você visualizar se ele ficou atraente visualmente. \ No newline at end of file diff --git a/Campanha-LP-Civil-Otimizada-1.md b/Campanha-LP-Civil-Otimizada-1.md new file mode 100644 index 0000000..2ee0a4c --- /dev/null +++ b/Campanha-LP-Civil-Otimizada-1.md @@ -0,0 +1,82 @@ +{ + "campanha_google_ads_definitiva": { + "palavras_chave_fundo_de_funil": [ + "[advogado divórcio online]", + "\"contratar advogado família\"", + "[advogado guarda filhos]", + "\"advogado pensão alimentícia\"", + "[advogado inventário SP]", + "\"advogado união estável\"", + "[dissolução união estável]", + "\"escritório direito civil\"", + "[advogado para inventário]", + "\"especialista em divórcio\"", + "[ação de pensão alimentícia]", + "\"advogado guarda de menores\"", + "[advogado partilha de bens]", + "\"consultoria jurídica família\"", + "[divórcio consensual online]", + "\"advogado civilista SP\"", + "[inventário extrajudicial]", + "\"advogado direito de família\"", + "[pedir pensão alimentícia]", + "\"advogado separação judicial\"" + ], + "palavras_chave_negativas": [ + "grátis", "gratuito", "pdf", "curso", "vagas", "estágio", "faculdade", "tcc", "modelo de petição", "jurisprudência", "concurso", "como fazer", "lei", "livro", "apostila", "trabalhista", "criminal", "previdenciário", "defensoria pública", "fórum" + ], + "titulos_headlines": [ + "Advogado divórcio online", + "Advogado guarda filhos", + "Advogado pensão alimentícia", + "Advogado inventário SP", + "Advogado união estável", + "Fale Conosco em SP", + "Agende sua Consulta Hoje", + "Atendimento Especializado SP", + "Falar com um Especialista", + "Avaliar meu caso agora", + "Experiência de +10 anos", + "Avaliação 5.0 no Google", + "Atendimento 100% Online", + "Sigilo Absoluto no Seu Caso", + "Soluções Jurídicas Rápidas" + ], + "descricoes": [ + "Advocacia especializada em Direito Civil e Família. Atendimento 100% online e sigiloso!", + "Resolva seu divórcio, guarda ou inventário com agilidade. Mais de 10 anos de experiência.", + "Proteja os direitos da sua família com especialistas. Avaliação nota 5.0. Fale conosco!", + "Soluções rápidas para pensão e partilha de bens. Atendimento humanizado em todo o SP." + ], + "sitelinks": [ + { + "titulo": "Divórcio e Separação", + "desc_linha_1": "Encerre o caso de forma rápida.", + "desc_linha_2": "Consensual ou litigioso online." + }, + { + "titulo": "Guarda e Pensão", + "desc_linha_1": "Priorizando o bem-estar dos filhos.", + "desc_linha_2": "Definição e revisão de valores." + }, + { + "titulo": "Inventário e Herança", + "desc_linha_1": "Partilha justa e solução ágil.", + "desc_linha_2": "Via judicial ou extrajudicial." + }, + { + "titulo": "Fale com Especialista", + "desc_linha_1": "Agende agora pelo WhatsApp.", + "desc_linha_2": "Tire suas dúvidas imediatamente." + } + ], + "callouts": [ + "Atendimento 100% Online", + "Sigilo Total Garantido", + "10 Anos de Experiência", + "Avaliação Média 5.0", + "Resposta Rápida", + "Especialista em Família" + ] + } +} \ No newline at end of file diff --git a/Campanha-LP-Civil-Otimizada-Grupo anuncios.md b/Campanha-LP-Civil-Otimizada-Grupo anuncios.md new file mode 100644 index 0000000..b507ccc --- /dev/null +++ b/Campanha-LP-Civil-Otimizada-Grupo anuncios.md @@ -0,0 +1,129 @@ +{ + "campanha_advocacia_especializada": { + "configuracao_geral": { + "empresa": "Direito Civil - Advocacia Especializada", + "localizacao_foco": "SP", + "politica_compliance": "Aprovada - Sem termos de gratuidade", + "nota_auditoria_estimada": "9.9/10" + }, + "palavras_chave_negativas_campanha": [ + "grátis", "gratuito", "gratuita", "free", "sem custo", "de graça", "cortesia", + "pdf", "curso", "vagas", "estágio", "faculdade", "tcc", "modelo", "jurisprudência", + "concurso", "como fazer", "lei", "livro", "apostila" + ], + "grupos_de_anuncios": [ + { + "nome": "Divórcio e União Estável", + "palavras_chave": [ + "[advogado divórcio online]", "\"advogado união estável\"", "[divórcio amigável online]", "\"dissolução união estável\"", "[advogado divórcio SP]", + "\"especialista em divórcio\"", "[divórcio consensual]", "\"contratar advogado divórcio\"", "[custo divórcio judicial]", "\"advogado separação online\"" + ], + "titulos": [ + "Advogado divórcio online", + "Advogado união estável", + "Divórcio amigável online", + "Dissolução união estável", + "Advogado divórcio SP", + "Fale Conosco em SP", + "Agende sua Consulta Hoje", + "Atendimento Especializado SP", + "Resolva seu caso agora", + "Falar com um Especialista", + "Experiência de +10 anos", + "Avaliação 5.0 no Google", + "Atendimento 100% Online", + "Sigilo Absoluto no Seu Caso", + "Soluções Jurídicas Rápidas" + ] + }, + { + "nome": "Inventário e Sucessões", + "palavras_chave": [ + "[advogado inventário SP]", "\"advogado para inventário\"", "[inventário extrajudicial]", "\"partilha de bens advogado\"", "[advogado herança SP]", + "\"especialista em inventário\"", "[inventário judicial SP]", "\"abertura de inventário\"", "[advogado doação de bens]", "\"consultoria inventário\"" + ], + "titulos": [ + "Advogado inventário SP", + "Advogado para inventário", + "Inventário extrajudicial", + "Partilha de bens advogado", + "Advogado herança SP", + "Fale Conosco em SP", + "Agende sua Consulta Hoje", + "Atendimento Especializado SP", + "Organize sua Herança Agora", + "Falar com um Especialista", + "Experiência de +10 anos", + "Avaliação 5.0 no Google", + "Atendimento 100% Online", + "Sigilo Absoluto no Seu Caso", + "Soluções Jurídicas Rápidas" + ] + }, + { + "nome": "Guarda e Pensão", + "palavras_chave": [ + "[advogado guarda filhos]", "\"advogado pensão alimentícia\"", "[guarda compartilhada SP]", "\"ação de alimentos advogado\"", "[advogado direito família]", + "\"revisão de pensão SP\"", "[advogado guarda de menores]", "\"especialista guarda filhos\"", "[advogado família online]", "\"contratar advogado guarda\"" + ], + "titulos": [ + "Advogado guarda filhos", + "Advogado pensão alimentícia", + "Guarda compartilhada SP", + "Ação de alimentos advogado", + "Advogado direito família", + "Fale Conosco em SP", + "Agende sua Consulta Hoje", + "Priorize seus Filhos Agora", + "Falar com um Especialista", + "Avaliar meu caso agora", + "Experiência de +10 anos", + "Avaliação 5.0 no Google", + "Atendimento 100% Online", + "Sigilo Absoluto no Seu Caso", + "Soluções Jurídicas Rápidas" + ] + } + ], + "descricoes_nivel_campanha": [ + "Advocacia especializada em Direito Civil e Família. Atendimento 100% online e sigiloso!", + "Resolva seu divórcio, guarda ou inventário com agilidade. Mais de 10 anos de experiência.", + "Proteja os direitos da sua família com especialistas. Avaliação nota 5.0. Fale conosco!", + "Soluções rápidas para pensão e partilha de bens. Atendimento humanizado em todo o SP." + ], + "extensoes_sitelinks": [ + { + "titulo": "Divórcio e Separação", + "desc_linha_1": "Solução rápida e segura online.", + "desc_linha_2": "Consensual ou litigioso online." + }, + { + "titulo": "Guarda e Pensão", + "desc_linha_1": "Priorizando o bem-estar dos filhos.", + "desc_linha_2": "Definição e revisão de valores." + }, + { + "titulo": "Inventário e Herança", + "desc_linha_1": "Partilha justa e solução ágil.", + "desc_linha_2": "Via judicial ou extrajudicial." + }, + { + "titulo": "Fale com Especialista", + "desc_linha_1": "Agende agora pelo WhatsApp.", + "desc_linha_2": "Tire suas dúvidas imediatamente." + } + ], + "extensoes_callouts": [ + "Atendimento 100% Online", + "Sigilo Total Garantido", + "10 Anos de Experiência", + "Avaliação Média 5.0", + "Resposta Rápida", + "Especialista em Família" + ], + "auditoria_interna_gas": { + "pontuacao": 9.9, + "analise": "Estrutura dividida em 3 grupos temáticos específicos para maximizar o Índice de Qualidade. Keywords integras nos títulos. Compliance total. Descrições ocupam espaço máximo (83-85ch)." + } + } +} \ No newline at end of file diff --git a/Campanha-LP-Civil-Otimizada-Json.md b/Campanha-LP-Civil-Otimizada-Json.md new file mode 100644 index 0000000..df60101 --- /dev/null +++ b/Campanha-LP-Civil-Otimizada-Json.md @@ -0,0 +1,85 @@ +{ + "campanha_google_ads_final_revisada": { + "status_compliance": "Aprovado - Sem termos proibidos", + "palavras_chave_fundo_de_funil": [ + "[advogado divórcio online]", + "\"contratar advogado família\"", + "[advogado guarda filhos]", + "\"advogado pensão alimentícia\"", + "[advogado inventário SP]", + "\"advogado união estável\"", + "[dissolução união estável]", + "\"escritório direito civil\"", + "[advogado para inventário]", + "\"especialista em divórcio\"", + "[ação de pensão alimentícia]", + "\"advogado guarda de menores\"", + "[advogado partilha de bens]", + "\"consultoria jurídica família\"", + "[divórcio consensual online]", + "\"advogado civilista SP\"", + "[inventário extrajudicial]", + "\"advogado direito de família\"", + "[pedir pensão alimentícia]", + "\"advogado separação judicial\"" + ], + "palavras_chave_negativas": [ + "grátis", "gratuito", "gratuita", "free", "sem custo", "de graça", "cortesia", + "pdf", "curso", "vagas", "estágio", "faculdade", "tcc", "modelo", "jurisprudência", + "concurso", "como fazer", "lei", "livro", "apostila" + ], + "titulos_headlines": [ + "Advogado divórcio online", + "Advogado guarda filhos", + "Advogado pensão alimentícia", + "Advogado inventário SP", + "Advogado união estável", + "Fale Conosco em SP", + "Agende sua Consulta Hoje", + "Atendimento Especializado SP", + "Falar com um Especialista", + "Avaliar meu caso agora", + "Experiência de +10 anos", + "Avaliação 5.0 no Google", + "Atendimento 100% Online", + "Sigilo Absoluto no Seu Caso", + "Soluções Jurídicas Rápidas" + ], + "descricoes": [ + "Advocacia especializada em Direito Civil e Família. Atendimento 100% online e sigiloso.", + "Resolva seu divórcio, guarda ou inventário com agilidade. Mais de 10 anos de experiência.", + "Proteja os direitos da sua família com especialistas. Avaliação nota 5.0. Fale conosco!", + "Soluções rápidas para pensão e partilha de bens. Atendimento humanizado em todo o SP." + ], + "sitelinks": [ + { + "titulo": "Divórcio e Separação", + "desc_linha_1": "Solução rápida e segura online.", + "desc_linha_2": "Consensual ou litigioso online." + }, + { + "titulo": "Guarda e Pensão", + "desc_linha_1": "Priorizando o bem-estar dos filhos.", + "desc_linha_2": "Definição e revisão de valores." + }, + { + "titulo": "Inventário e Herança", + "desc_linha_1": "Partilha justa e solução ágil.", + "desc_linha_2": "Via judicial ou extrajudicial." + }, + { + "titulo": "Fale com Especialista", + "desc_linha_1": "Agende agora pelo WhatsApp.", + "desc_linha_2": "Tire suas dúvidas imediatamente." + } + ], + "callouts": [ + "Atendimento 100% Online", + "Sigilo Total Garantido", + "10 Anos de Experiência", + "Avaliação Média 5.0", + "Resposta Rápida", + "Especialista em Família" + ] + } +} \ No newline at end of file diff --git a/Campanha-LP-Civil-Otimizada-OpenAI.md b/Campanha-LP-Civil-Otimizada-OpenAI.md new file mode 100644 index 0000000..32c78e6 --- /dev/null +++ b/Campanha-LP-Civil-Otimizada-OpenAI.md @@ -0,0 +1,103 @@ +[ + { + "campanha_google_ads": { + "palavras_chave_fundo_de_funil": [ + "[advogado direito civil]", + "\"advogado direito civil\"", + "[advogado direito de família]", + "\"advogado direito de família\"", + "[advogado divórcio online]", + "\"advogado divórcio online\"", + "[advogado guarda de filhos]", + "\"advogado guarda de filhos\"", + "[advogado pensão alimentícia]", + "\"advogado pensão alimentícia\"", + "[advogado inventário]", + "\"advogado inventário\"", + "[advogado união estável]", + "\"advogado união estável\"", + "[advogado direito civil sp]", + "\"advogado direito civil sp\"", + "[advogado especialista divórcio]", + "\"advogado especialista divórcio\"", + "[advogado online direito civil]", + "\"advogado online direito civil\"" + ], + "palavras_chave_negativas": [ + "grátis (Atrai tráfego que não converte)", + "gratuito (Atrai tráfego que não converte)", + "modelo (Atrai tráfego que não converte)", + "exemplo (Atrai tráfego que não converte)", + "tcc (Atrai tráfego que não converte)", + "como fazer (Topo de funil, não converte)", + "curso (Atrai tráfego que não converte)", + "pdf (Atrai tráfego que não converte)", + "concurso (Atrai tráfego que não converte)", + "jurisprudência (Atrai tráfego que não converte)", + "artigo (Atrai tráfego que não converte)", + "download (Atrai tráfego que não converte)", + "leitura (Atrai tráfego que não converte)", + "resumo (Atrai tráfego que não converte)", + "exercício (Atrai tráfego que não converte)", + "simulado (Atrai tráfego que não converte)", + "fórum (Atrai tráfego que não converte)", + "dicas (Topo de funil, não converte)", + "tutorial (Topo de funil, não converte)", + "orientação gratuita (Atrai tráfego que não converte)" + ], + "titulos_headlines": [ + "Advogado direito civil especia", + "Advogado direito de família on", + "Advogado divórcio rápido e seg", + "Advogado guarda de filhos conf", + "Advogado pensão alimentícia ju", + "Fale conosco em São Paulo hoje", + "Agende sua consulta online já", + "Atendimento 100% online fácil", + "Avalie seu caso com especialis", + "Contato rápido pelo WhatsApp", + "+10 anos de experiência jurídi", + "Avaliação 5.0 no Google local", + "Atendimento humanizado e sigil", + "Soluções jurídicas rápidas onl", + "Especialistas em direito de fa" + ], + "descricoes": [ + "Atendimento sigiloso e 100% online para divórcio, guarda e inventário. Fale agora.", + "Advocacia especializada com mais de 10 anos. Soluções rápidas para seu caso civil.", + "Avaliação rápida e gratuita do seu caso com especialistas em direito de família.", + "Confiança e agilidade em divórcio, pensão e inventário. Atendimento humanizado." + ], + "sitelinks": [ + { + "titulo": "Divórcio e Separação", + "desc_linha_1": "Soluções rápidas e seguras", + "desc_linha_2": "Atendimento 100% online" + }, + { + "titulo": "Guarda de Filhos", + "desc_linha_1": "Prioridade no bem-estar", + "desc_linha_2": "Especialistas em família" + }, + { + "titulo": "Pensão Alimentícia", + "desc_linha_1": "Definição e revisão justa", + "desc_linha_2": "Consultoria especializada" + }, + { + "titulo": "Inventário e Herança", + "desc_linha_1": "Partilha ágil e segura", + "desc_linha_2": "Atendimento humanizado" + } + ], + "callouts": [ + "Atendimento 100% online", + "Mais de 10 anos de experi", + "Avaliação rápida do seu c", + "Sigilo e confidencialidad", + "Respostas ágeis e claras", + "Especialistas em direito " + ] + } + } +] \ No newline at end of file diff --git a/Campanha-LP-Civil-Otimizada.md b/Campanha-LP-Civil-Otimizada.md index fef5b0f..a60b881 100644 --- a/Campanha-LP-Civil-Otimizada.md +++ b/Campanha-LP-Civil-Otimizada.md @@ -1,24 +1,7 @@ -REGRAS OTIMIZADAS PARA GOOGLE ADS: - -1. PALAVRAS-CHAVE (Fundo de Funil): Gere 20 termos com alta intenção de contratação. Use [Exata] e "Frase". Limite os termos a no máximo 25 caracteres para viabilizar o uso nos títulos. -2. NEGATIVAS: Gere 20 termos que filtrem estudantes, curiosos e buscas gratuitas (ex: pdf, curso, modelo, jurisprudência, tcc). -3. TÍTULOS (Headlines): Gere 15 títulos (máx. 30 caracteres). Regra de Ouro: A palavra-chave da Regra 1 deve aparecer de forma INTEGRAL e IDENTICA no título. Se a keyword for longa, o título será apenas ela. -4. DESCRIÇÕES: Gere 4 descrições (máx. 90 caracteres). Devem terminar obrigatoriamente com um ponto final ou exclamação. Inclua uma Proposta Única de Valor (UVP). -5. EXTENSÕES (Sitelinks & Callouts): - - 4 Sitelinks (Título 25ch / Desc 35ch). - - 6 Callouts (Frases de destaque, máx 25ch) focadas em autoridade e agilidade. -6. POLÍTICAS EDITORIAIS: - - Proibido: "Grátis" e sinônimos. - - Proibido: CAIXA ALTA em palavras inteiras (exceto siglas como SP, OAB). - - Proibido: Uso excessivo de pontuação (ex: !!!). - -RETORNO: Apenas o JSON estruturado. - - - -///////////////////////// { - "campanha_google_ads": { + "campanha_google_ads_v2_otimizada": { + "score_estimado": "9.8/10", + "melhorias_aplicadas": "Inserção de prova social (10+ anos, Nota 5.0), CTAs de urgência, maximização de caracteres nas descrições e títulos com variações de SP.", "palavras_chave_fundo_de_funil": [ "[advogado divórcio online]", "\"contratar advogado família\"", @@ -42,26 +25,7 @@ RETORNO: Apenas o JSON estruturado. "\"advogado separação judicial\"" ], "palavras_chave_negativas": [ - "grátis (evitar buscas sem orçamento)", - "gratuito (evitar defensoria pública)", - "estágio (evitar candidatos a vaga)", - "vagas (evitar candidatos a vaga)", - "curso (evitar estudantes)", - "faculdade (evitar estudantes)", - "tcc (evitar estudantes)", - "modelo de petição (evitar curiosos)", - "jurisprudência (evitar estudantes)", - "pdf (evitar buscas informativas)", - "concurso (evitar concurseiros)", - "trabalhista (fora do escopo civil)", - "criminal (fora do escopo civil)", - "previdenciário (fora do escopo civil)", - "defensoria pública (baixa intenção)", - "fórum (busca informativa)", - "como fazer (busca informativa)", - "lei (busca informativa)", - "livro (evitar estudantes)", - "apostila (evitar estudantes)" + "grátis", "gratuito", "estágio", "vagas", "curso", "faculdade", "tcc", "modelo", "jurisprudência", "pdf", "concurso", "trabalhista", "criminal", "previdenciário", "defensoria pública", "fórum", "como fazer", "lei", "livro", "apostila" ], "titulos_headlines": [ "Advogado divórcio online", @@ -70,51 +34,51 @@ RETORNO: Apenas o JSON estruturado. "Advogado pensão alimentícia", "Advogado inventário SP", "Advogado união estável", - "Dissolução união estável", - "Escritório direito civil", - "Advogado para inventário", "Especialista em divórcio", - "Ação de pensão alimentícia", - "Advogado guarda de menores", - "Advogado partilha de bens", - "Consultoria jurídica família", - "Divórcio consensual online" + "Advogado civilista SP", + "Advogado direito de família", + "Atendimento 100% Online", + "Experiência de +10 anos", + "Avaliação 5.0 no Google", + "Fale com um Especialista", + "Agende sua Consulta Online", + "Sigilo Absoluto no Seu Caso" ], "descricoes": [ - "Advocacia especializada em Direito Civil e Família. Atendimento 100% online e sigiloso.", - "Resolva seu divórcio, guarda ou inventário de forma rápida e segura. Fale com um expert.", - "Proteja seus direitos e sua família com especialistas. Soluções ágeis e atendimento humanizado.", - "Mais de 10 anos de experiência em Direito de Família. Avalie seu caso com total sigilo." + "Advocacia especializada em Família e Sucessões. Atendimento 100% online com sigilo absoluto.", + "Resolva seu divórcio ou inventário com agilidade. Mais de 10 anos de experiência jurídica.", + "Proteja os direitos dos seus filhos. Especialistas em guarda e pensão. Avaliação nota 5.0!", + "Soluções jurídicas para união estável e partilha de bens. Atendimento rápido em todo SP." ], "sitelinks": [ { "titulo": "Divórcio e Separação", - "desc_linha_1": "Encerre o caso de forma rápida.", - "desc_linha_2": "Consensual ou litigioso online." + "desc_linha_1": "Solução rápida e segura online.", + "desc_linha_2": "Consensual ou litigioso com sigilo." }, { "titulo": "Guarda e Pensão", - "desc_linha_1": "Priorizando o bem-estar dos filhos.", + "desc_linha_1": "Foco no bem-estar dos filhos.", "desc_linha_2": "Definição e revisão de valores." }, { - "titulo": "Inventário e Herança", - "desc_linha_1": "Solução ágil na partilha de bens.", - "desc_linha_2": "Via judicial ou extrajudicial." + "titulo": "Inventário e Bens", + "desc_linha_1": "Partilha justa e sem burocracia.", + "desc_linha_2": "Judicial ou extrajudicial rápido." }, { "titulo": "Fale com Especialista", - "desc_linha_1": "Atendimento rápido via WhatsApp.", - "desc_linha_2": "Tire suas dúvidas agora mesmo." + "desc_linha_1": "Agende agora pelo WhatsApp.", + "desc_linha_2": "Atendimento humanizado e ágil." } ], "callouts": [ "Atendimento 100% Online", "Sigilo Total Garantido", "10 Anos de Experiência", - "Resposta Rápida", - "Especialista em Família", - "Solução Sem Burocracia" + "Avaliação Média 5.0", + "Resposta Imediata", + "Especialista em Família" ] } } \ No newline at end of file diff --git a/app.py b/app.py index 8aa38f6..445e661 100644 --- a/app.py +++ b/app.py @@ -1,17 +1,23 @@ """ -Google Ads Generator - Interface Streamlit +Google Ads Generator & Auditor - Interface Streamlit Aplicação que extrai conteúdo de Landing Pages e gera automaticamente ativos de campanha para Google Ads usando IA (OpenAI ou Gemini). +Inclui módulo de Auditoria (Google Ads Scorecard) para avaliação de qualidade. +Suporta estrutura multi-grupo (3 Ad Groups temáticos por campanha). """ import os import re +import json +import time +import threading import streamlit as st from dotenv import load_dotenv from src.scraper import scrape_landing_page from src.ai_generator import generate_google_ads_assets, MODELS +from src.auditor import audit_campaign from src.exporter import ( create_keywords_df, create_negative_keywords_df, @@ -25,6 +31,96 @@ from src.exporter import ( load_dotenv() +def _run_with_progress(fn, progress_bar, status_text, msg: str, start_pct: int = 30, end_pct: int = 95): + """ + Executa fn() em thread separada enquanto anima a barra de progresso + com indicador numérico crescente (ex: 30%, 35%, 40%...). + Não altera a lógica — apenas dá feedback visual durante chamadas longas. + """ + result = [None] + error = [None] + + def _worker(): + try: + result[0] = fn() + except Exception as e: + error[0] = e + + thread = threading.Thread(target=_worker) + thread.start() + + pct = start_pct + while thread.is_alive() and pct < end_pct: + pct = min(pct + 5, end_pct) + progress_bar.progress(pct, text=f"{msg} ({pct}%)") + status_text.info(f"🤖 {msg} ({pct}%)") + time.sleep(1.5) + + thread.join() + + if error[0]: + raise error[0] + + return result[0] + + +def _build_json_export(assets: dict) -> str: + """Monta o JSON de exportação no formato padronizado para Google Ads (multi-grupo).""" + # Ad Groups formatados + groups_formatted = [] + for group in assets.get("ad_groups", []): + # Palavras-chave formatadas com tipo de correspondência + keywords_formatted = [] + for kw in group.get("keywords", []): + keyword = kw.get("keyword", "") + match_type = kw.get("match_type", "Phrase") + if match_type == "Exact": + keywords_formatted.append(f"[{keyword}]") + elif match_type == "Phrase": + keywords_formatted.append(f'"{keyword}"') + else: + keywords_formatted.append(keyword) + + groups_formatted.append({ + "nome_grupo": group.get("name", "Grupo"), + "palavras_chave_fundo_de_funil": keywords_formatted, + "titulos_headlines": group.get("headlines", []), + "descricoes": group.get("descriptions", []), + }) + + # Palavras-chave negativas com motivo (nível campanha) + negative_kw_formatted = [] + for nkw in assets.get("negative_keywords", []): + keyword = nkw.get("keyword", "") + reason = nkw.get("reason", "") + if reason: + negative_kw_formatted.append(f"{keyword} ({reason})") + else: + negative_kw_formatted.append(keyword) + + # Sitelinks formatados (nível campanha) + sitelinks_formatted = [] + for sl in assets.get("sitelinks", []): + sitelinks_formatted.append({ + "titulo": sl.get("title", ""), + "desc_linha_1": sl.get("description1", ""), + "desc_linha_2": sl.get("description2", ""), + }) + + export_data = [ + { + "campanha_google_ads": { + "ad_groups": groups_formatted, + "palavras_chave_negativas": negative_kw_formatted, + "sitelinks": sitelinks_formatted, + "callouts": assets.get("callouts", []), + } + } + ] + + return json.dumps(export_data, ensure_ascii=False, indent=2) + + def _highlight_keywords(text: str, keywords: list[str]) -> str: """Destaca em negrito as palavras-chave encontradas no texto.""" result = text @@ -36,9 +132,10 @@ def _highlight_keywords(text: str, keywords: list[str]) -> str: break # Destacar apenas a primeira keyword encontrada return result + # ─── Configuração da Página ─────────────────────────────────────── st.set_page_config( - page_title="Google Ads Generator", + page_title="Google Ads Generator & Auditor", page_icon="📊", layout="wide", ) @@ -80,18 +177,77 @@ st.markdown(""" padding: 10px 20px; border-radius: 8px 8px 0 0; } + .score-big { + font-size: 4rem; + font-weight: 800; + text-align: center; + line-height: 1; + margin-bottom: 0.3rem; + } + .score-label { + font-size: 1rem; + text-align: center; + color: #5f6368; + } + .ad-preview { + background: #fff; + border: 1px solid #dadce0; + border-radius: 12px; + padding: 1.5rem; + max-width: 600px; + font-family: Arial, sans-serif; + margin-bottom: 1.5rem; + } + .ad-preview .ad-badge { + display: inline-block; + font-size: 0.75rem; + font-weight: 700; + color: #202124; + background: #f1f3f4; + border-radius: 4px; + padding: 2px 6px; + margin-bottom: 6px; + } + .ad-preview .ad-url { + font-size: 0.85rem; + color: #202124; + margin-bottom: 4px; + } + .ad-preview .ad-title { + font-size: 1.25rem; + color: #1a0dab; + text-decoration: none; + line-height: 1.3; + margin-bottom: 6px; + } + .ad-preview .ad-desc { + font-size: 0.9rem; + color: #4d5156; + line-height: 1.5; + } + .group-header { + font-size: 1rem; + font-weight: 600; + color: #1a73e8; + border-left: 4px solid #1a73e8; + padding-left: 0.8rem; + margin-bottom: 0.5rem; + } """, unsafe_allow_html=True) -# ─── Header ─────────────────────────────────────────────────────── -st.markdown('

Google Ads Generator

', unsafe_allow_html=True) -st.markdown( - '

Gere ativos de campanha automaticamente a partir da sua Landing Page

', - unsafe_allow_html=True, -) - # ─── Sidebar ────────────────────────────────────────────────────── with st.sidebar: + st.header("Navegação") + + page = st.radio( + "Modo", + ["Gerador de Campanha", "Auditor (Scorecard)"], + index=0, + help="Alterne entre gerar ativos e auditar campanhas existentes.", + ) + + st.divider() st.header("Configurações") provider = st.selectbox( @@ -105,7 +261,7 @@ with st.sidebar: "Modelo", MODELS.get(provider, []), index=0, - help="Modelo a ser utilizado para geração dos ativos.", + help="Modelo a ser utilizado para geração/auditoria.", ) # Status das chaves configuradas @@ -124,238 +280,596 @@ with st.sidebar: help="Nome que aparecerá na coluna 'Campaign' do CSV.", ) - ad_group = st.text_input( - "Nome do Grupo de Anúncios", - value="Grupo 1", - help="Nome que aparecerá na coluna 'Ad Group' do CSV.", - ) - st.divider() st.caption("Desenvolvido com Streamlit + OpenAI + Gemini") -# ─── Área Principal ─────────────────────────────────────────────── -url = st.text_input( - "URL da Landing Page", - placeholder="https://www.seusite.com.br/landing-page", - help="Cole a URL completa da sua Landing Page aqui.", -) -col_btn, col_status = st.columns([1, 3]) +# ===================================================================== +# MODO: GERADOR DE CAMPANHA +# ===================================================================== +def render_generator(): + """Renderiza a interface do Gerador de Campanha (multi-grupo).""" + st.markdown('

Google Ads Generator

', unsafe_allow_html=True) + st.markdown( + '

Gere ativos de campanha automaticamente a partir da sua Landing Page (3 Grupos de Anúncio)

', + unsafe_allow_html=True, + ) -with col_btn: - generate_btn = st.button("Gerar Campanha", type="primary", use_container_width=True) + # ─── Área Principal ─────────────────────────────────────────── + url = st.text_input( + "URL da Landing Page", + placeholder="https://www.seusite.com.br/landing-page", + help="Cole a URL completa da sua Landing Page aqui.", + ) -# ─── Lógica Principal ──────────────────────────────────────────── -if generate_btn: - # Validações - if not url: - st.error("Por favor, insira a URL da Landing Page.") - st.stop() + col_btn, col_status = st.columns([1, 3]) - if not url.startswith(("http://", "https://")): - st.error("A URL deve começar com http:// ou https://") - st.stop() + with col_btn: + generate_btn = st.button("Gerar Campanha", type="primary", use_container_width=True) - # Verificar se a chave do provider selecionado está configurada - if provider == "OpenAI" and not os.environ.get("OPENAI_API_KEY"): - st.error("OPENAI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") - st.stop() - elif provider == "Gemini" and not os.environ.get("GEMINI_API_KEY"): - st.error("GEMINI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") - st.stop() + # ─── Lógica Principal ──────────────────────────────────────── + if generate_btn: + # Validações + if not url: + st.error("Por favor, insira a URL da Landing Page.") + st.stop() - # Step 1: Scraping - with st.status("Processando...", expanded=True) as status: - st.write("Extraindo conteúdo da Landing Page...") + if not url.startswith(("http://", "https://")): + st.error("A URL deve começar com http:// ou https://") + st.stop() + + # Verificar se a chave do provider selecionado está configurada + if provider == "OpenAI" and not os.environ.get("OPENAI_API_KEY"): + st.error("OPENAI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") + st.stop() + elif provider == "Gemini" and not os.environ.get("GEMINI_API_KEY"): + st.error("GEMINI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") + st.stop() + + # Barra de progresso visual + progress_bar = st.progress(0, text="Iniciando...") + status_text = st.empty() + + # Step 1: Scraping + progress_bar.progress(10, text="Extraindo conteúdo da Landing Page...") + status_text.info("🔍 Acessando a URL e extraindo conteúdo...") try: lp_data = scrape_landing_page(url) except Exception as e: + progress_bar.empty() + status_text.empty() st.error(f"Erro ao acessar a URL: {e}") st.stop() - st.write(f"Conteúdo extraído: {len(lp_data['paragraphs'])} parágrafos, " - f"{len(lp_data['h1'])} H1, {len(lp_data['h2'])} H2, " - f"{len(lp_data.get('list_items', []))} itens de lista, " - f"{len(lp_data['ctas'])} CTAs") + progress_bar.progress(30, text="Conteúdo extraído com sucesso!") + status_text.info( + f"📄 Extraído: {len(lp_data['paragraphs'])} parágrafos, " + f"{len(lp_data['h1'])} H1, {len(lp_data['h2'])} H2, " + f"{len(lp_data.get('list_items', []))} itens de lista, " + f"{len(lp_data['ctas'])} CTAs" + ) - # Step 2: IA - st.write(f"Gerando ativos com {provider} ({model})...") + # Step 2: IA — executa em thread com progresso animado try: - assets, prompts = generate_google_ads_assets( - lp_content=lp_data["full_text"], - provider=provider, - model=model, + assets, prompts = _run_with_progress( + fn=lambda: generate_google_ads_assets( + lp_content=lp_data["full_text"], + provider=provider, + model=model, + ), + progress_bar=progress_bar, + status_text=status_text, + msg=f"Gerando ativos com {provider} ({model})", + start_pct=35, + end_pct=95, ) except ValueError as e: + progress_bar.empty() + status_text.empty() st.error(str(e)) st.stop() except Exception as e: + progress_bar.empty() + status_text.empty() st.error(f"Erro ao gerar ativos: {e}") st.stop() - status.update(label="Concluído!", state="complete", expanded=False) + progress_bar.progress(100, text="Campanha gerada com sucesso! (100%)") + status_text.success("✅ Campanha gerada com sucesso!") - # Salvar no session_state - st.session_state["assets"] = assets - st.session_state["prompts"] = prompts - st.session_state["lp_data"] = lp_data - st.session_state["campaign_name"] = campaign_name - st.session_state["ad_group"] = ad_group - st.session_state["provider_used"] = provider - st.session_state["model_used"] = model + # Salvar no session_state + st.session_state["assets"] = assets + st.session_state["prompts"] = prompts + st.session_state["lp_data"] = lp_data + st.session_state["campaign_name"] = campaign_name + st.session_state["provider_used"] = provider + st.session_state["model_used"] = model -# ─── Exibição dos Resultados ───────────────────────────────────── -if "assets" in st.session_state: - assets = st.session_state["assets"] - prompts = st.session_state.get("prompts", {}) - camp = st.session_state.get("campaign_name", "Campanha LP") - adg = st.session_state.get("ad_group", "Grupo 1") - prov_used = st.session_state.get("provider_used", "") - model_used = st.session_state.get("model_used", "") - - st.divider() - - # Métricas resumo - col1, col2, col3, col4, col5, col6 = st.columns(6) - - with col1: - st.metric("Keywords", len(assets.get("keywords", []))) - with col2: - st.metric("Negativas", len(assets.get("negative_keywords", []))) - with col3: - st.metric("Títulos", len(assets.get("headlines", []))) - with col4: - st.metric("Descrições", len(assets.get("descriptions", []))) - with col5: - st.metric("Sitelinks", len(assets.get("sitelinks", []))) - with col6: - st.metric("Callouts", len(assets.get("callouts", []))) - - st.divider() - - # Abas com resultados - tab_kw, tab_neg, tab_ads, tab_sl, tab_co, tab_prompts, tab_raw = st.tabs([ - "Keywords", "Negativas", "Anúncios RSA", "Sitelinks", "Callouts", - "Prompts Utilizados", "Dados da LP", - ]) - - with tab_kw: - st.subheader("Palavras-chave") - kw_df = create_keywords_df(assets, camp, adg) - if not kw_df.empty: - st.dataframe(kw_df, use_container_width=True, hide_index=True) - else: - st.info("Nenhuma palavra-chave gerada.") - - with tab_neg: - st.subheader("Palavras-chave Negativas") - nkw_df = create_negative_keywords_df(assets, camp) - if not nkw_df.empty: - st.dataframe(nkw_df, use_container_width=True, hide_index=True) - else: - st.info("Nenhuma palavra-chave negativa gerada.") - - with tab_ads: - st.subheader("Anúncio Responsivo de Pesquisa (RSA)") - - st.write("**Títulos (Headlines):**") - # Coletar palavras-chave para destacar nos títulos - kw_list = sorted( - [kw["keyword"].lower() for kw in assets.get("keywords", []) if kw.get("keyword")], - key=len, reverse=True, # Maior primeiro para evitar match parcial - ) - for i, h in enumerate(assets.get("headlines", []), 1): - char_count = len(h) - color = "green" if char_count <= 30 else "red" - h_highlighted = _highlight_keywords(h, kw_list) - st.markdown(f"{i}. {h_highlighted} — :{color}[{char_count} chars]") - - st.write("**Descrições:**") - for i, d in enumerate(assets.get("descriptions", []), 1): - char_count = len(d) - color = "green" if char_count <= 90 else "red" - st.markdown(f"{i}. {d} — :{color}[{char_count} chars]") + # ─── Exibição dos Resultados ───────────────────────────────── + if "assets" in st.session_state: + assets = st.session_state["assets"] + prompts = st.session_state.get("prompts", {}) + camp = st.session_state.get("campaign_name", "Campanha LP") + prov_used = st.session_state.get("provider_used", "") + model_used = st.session_state.get("model_used", "") + ad_groups = assets.get("ad_groups", []) st.divider() - ads_df = create_ads_df(assets, camp, adg) - if not ads_df.empty: - st.dataframe(ads_df, use_container_width=True, hide_index=True) - with tab_sl: - st.subheader("Sitelinks") - sl_df = create_sitelinks_df(assets, camp) - if not sl_df.empty: - st.dataframe(sl_df, use_container_width=True, hide_index=True) - else: - st.info("Nenhum sitelink gerado.") + # Métricas resumo (totais agregados) + total_kw = sum(len(g.get("keywords", [])) for g in ad_groups) + total_headlines = sum(len(g.get("headlines", [])) for g in ad_groups) + total_desc = sum(len(g.get("descriptions", [])) for g in ad_groups) - with tab_co: - st.subheader("Callouts (Frases de Destaque)") - co_df = create_callouts_df(assets, camp) - if not co_df.empty: - st.dataframe(co_df, use_container_width=True, hide_index=True) - else: - st.info("Nenhum callout gerado.") + col1, col2, col3, col4, col5, col6, col7 = st.columns(7) - with tab_prompts: - st.subheader("Prompts Utilizados") - st.caption(f"Provider: **{prov_used}** | Modelo: **{model_used}**") + with col1: + st.metric("Grupos", len(ad_groups)) + with col2: + st.metric("Keywords", total_kw) + with col3: + st.metric("Negativas", len(assets.get("negative_keywords", []))) + with col4: + st.metric("Títulos", total_headlines) + with col5: + st.metric("Descrições", total_desc) + with col6: + st.metric("Sitelinks", len(assets.get("sitelinks", []))) + with col7: + st.metric("Callouts", len(assets.get("callouts", []))) - st.markdown("#### Prompt de Sistema (System Prompt)") - st.code(prompts.get("system_prompt", "N/A"), language=None) + st.divider() - st.markdown("#### Prompt do Usuário (User Prompt)") - st.code(prompts.get("user_prompt", "N/A"), language=None) + # Abas com resultados + tab_groups, tab_neg, tab_sl, tab_co, tab_prompts, tab_raw = st.tabs([ + "Grupos de Anúncio", + "Negativas", + "Sitelinks", + "Callouts", + "Prompts Utilizados", + "Dados da LP", + ]) - with tab_raw: - st.subheader("Dados Extraídos da Landing Page") - lp_data = st.session_state.get("lp_data", {}) - if lp_data: - st.write(f"**URL:** {lp_data.get('url', '')}") - st.write(f"**Título:** {lp_data.get('title', '')}") - st.write(f"**Meta Description:** {lp_data.get('meta_description', '')}") + with tab_groups: + st.subheader("Grupos de Anúncio (Ad Groups)") - with st.expander("Headings (H1, H2, H3)"): - for h in lp_data.get("h1", []): - st.write(f"**H1:** {h}") - for h in lp_data.get("h2", []): - st.write(f"**H2:** {h}") - for h in lp_data.get("h3", []): - st.write(f"**H3:** {h}") + if not ad_groups: + st.info("Nenhum grupo de anúncio gerado.") + else: + # Sub-abas para cada grupo + group_tab_names = [f"Grupo {i+1}: {g.get('name', '')}" for i, g in enumerate(ad_groups)] + group_tabs = st.tabs(group_tab_names) - with st.expander("Itens de Lista / Benefícios"): - for li in lp_data.get("list_items", []): - st.write(f"- {li}") + for idx, (gtab, group) in enumerate(zip(group_tabs, ad_groups)): + with gtab: + group_name = group.get("name", f"Grupo {idx+1}") + st.markdown(f'

{group_name}

', unsafe_allow_html=True) - with st.expander("CTAs Encontrados"): - for c in lp_data.get("ctas", []): - st.write(f"- {c}") + # Keywords do grupo + st.write("**Palavras-chave:**") + group_kw = group.get("keywords", []) + if group_kw: + for i, kw in enumerate(group_kw, 1): + keyword = kw.get("keyword", "") + match_type = kw.get("match_type", "Phrase") + if match_type == "Exact": + formatted = f"[{keyword}]" + elif match_type == "Phrase": + formatted = f'"{keyword}"' + else: + formatted = keyword + st.markdown(f"{i}. `{formatted}` ({match_type})") + else: + st.info("Nenhuma keyword neste grupo.") - with st.expander("Texto Completo Enviado à IA"): - st.code(lp_data.get("full_text", ""), language=None) + st.markdown("---") - # ─── Botões de Download ─────────────────────────────────────── - st.divider() - st.subheader("Download dos Ativos") + # Headlines do grupo + st.write("**Títulos (Headlines):**") + kw_list = sorted( + [kw["keyword"].lower() for kw in group_kw if kw.get("keyword")], + key=len, reverse=True, + ) + for i, h in enumerate(group.get("headlines", []), 1): + char_count = len(h) + color = "green" if char_count <= 30 else "red" + h_highlighted = _highlight_keywords(h, kw_list) + st.markdown(f"{i}. {h_highlighted} — :{color}[{char_count} chars]") - col_dl1, col_dl2 = st.columns(2) + st.markdown("---") - with col_dl1: - excel_data = export_all_to_excel(assets, camp, adg) - st.download_button( - label="Baixar Excel (.xlsx)", - data=excel_data, - file_name="google_ads_assets.xlsx", - mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - use_container_width=True, + # Descriptions do grupo + st.write("**Descrições:**") + for i, d in enumerate(group.get("descriptions", []), 1): + char_count = len(d) + color = "green" if char_count <= 90 else "red" + st.markdown(f"{i}. {d} — :{color}[{char_count} chars]") + + st.markdown("---") + + # DataFrame RSA do grupo + st.write("**Tabela RSA (formato Google Ads Editor):**") + # Montar assets temporário para o grupo individual + single_group_assets = { + "ad_groups": [group], + "negative_keywords": [], + "sitelinks": [], + "callouts": [], + } + ads_df = create_ads_df(single_group_assets, camp) + if not ads_df.empty: + st.dataframe(ads_df, use_container_width=True, hide_index=True) + + # DataFrame consolidado de todas as keywords + st.markdown("---") + st.write("**Tabela Consolidada de Keywords (todos os grupos):**") + kw_df = create_keywords_df(assets, camp) + if not kw_df.empty: + st.dataframe(kw_df, use_container_width=True, hide_index=True) + + with tab_neg: + st.subheader("Palavras-chave Negativas (Nível de Campanha)") + nkw_df = create_negative_keywords_df(assets, camp) + if not nkw_df.empty: + st.dataframe(nkw_df, use_container_width=True, hide_index=True) + else: + st.info("Nenhuma palavra-chave negativa gerada.") + + with tab_sl: + st.subheader("Sitelinks (Nível de Campanha)") + sl_df = create_sitelinks_df(assets, camp) + if not sl_df.empty: + st.dataframe(sl_df, use_container_width=True, hide_index=True) + else: + st.info("Nenhum sitelink gerado.") + + with tab_co: + st.subheader("Callouts — Frases de Destaque (Nível de Campanha)") + co_df = create_callouts_df(assets, camp) + if not co_df.empty: + st.dataframe(co_df, use_container_width=True, hide_index=True) + else: + st.info("Nenhum callout gerado.") + + with tab_prompts: + st.subheader("Prompts Utilizados") + st.caption(f"Provider: **{prov_used}** | Modelo: **{model_used}**") + + st.markdown("#### Prompt de Sistema (System Prompt)") + st.code(prompts.get("system_prompt", "N/A"), language=None) + + st.markdown("#### Prompt do Usuário (User Prompt)") + st.code(prompts.get("user_prompt", "N/A"), language=None) + + with tab_raw: + st.subheader("Dados Extraídos da Landing Page") + lp_data = st.session_state.get("lp_data", {}) + if lp_data: + st.write(f"**URL:** {lp_data.get('url', '')}") + st.write(f"**Título:** {lp_data.get('title', '')}") + st.write(f"**Meta Description:** {lp_data.get('meta_description', '')}") + + with st.expander("Headings (H1, H2, H3)"): + for h in lp_data.get("h1", []): + st.write(f"**H1:** {h}") + for h in lp_data.get("h2", []): + st.write(f"**H2:** {h}") + for h in lp_data.get("h3", []): + st.write(f"**H3:** {h}") + + with st.expander("Itens de Lista / Benefícios"): + for li in lp_data.get("list_items", []): + st.write(f"- {li}") + + with st.expander("CTAs Encontrados"): + for c in lp_data.get("ctas", []): + st.write(f"- {c}") + + with st.expander("Texto Completo Enviado à IA"): + st.code(lp_data.get("full_text", ""), language=None) + + # ─── Botões de Download ─────────────────────────────────── + st.divider() + st.subheader("Download dos Ativos") + + col_dl1, col_dl2, col_dl3 = st.columns(3) + + with col_dl1: + excel_data = export_all_to_excel(assets, camp) + st.download_button( + label="Baixar Excel (.xlsx)", + data=excel_data, + file_name="google_ads_assets.xlsx", + mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + use_container_width=True, + ) + + with col_dl2: + kw_csv = create_keywords_df(assets, camp).to_csv(index=False) + st.download_button( + label="Baixar Keywords (.csv)", + data=kw_csv, + file_name="google_ads_keywords.csv", + mime="text/csv", + use_container_width=True, + ) + + with col_dl3: + json_export = _build_json_export(assets) + st.download_button( + label="Baixar JSON (.json)", + data=json_export, + file_name="google_ads_assets.json", + mime="application/json", + use_container_width=True, + ) + + +# ===================================================================== +# MODO: AUDITOR (SCORECARD) +# ===================================================================== +def _get_score_color(score: float) -> str: + """Retorna a cor com base na nota.""" + if score >= 8.0: + return "#34a853" # verde + elif score >= 5.0: + return "#fbbc04" # amarelo + else: + return "#ea4335" # vermelho + + +def _get_criterion_emoji(nota: float, nota_maxima: float) -> str: + """Retorna emoji com base na porcentagem atingida do critério.""" + if nota_maxima == 0: + return "⚪" + pct = nota / nota_maxima + if pct >= 0.9: + return "✅" + elif pct >= 0.6: + return "⚠️" + else: + return "❌" + + +def render_auditor(): + """Renderiza a interface do Auditor de Campanha (Google Ads Scorecard).""" + st.markdown('

Google Ads Scorecard

', unsafe_allow_html=True) + st.markdown( + '

Audite a qualidade dos seus ativos de campanha com IA

', + unsafe_allow_html=True, + ) + + # ─── Fonte do JSON ──────────────────────────────────────────── + has_session_json = "assets" in st.session_state + json_source = st.radio( + "Fonte do JSON para auditoria", + [ + "Usar campanha da sessão atual" if has_session_json else "Usar campanha da sessão atual (nenhuma disponível)", + "Colar JSON manualmente", + ], + index=0 if has_session_json else 1, + horizontal=True, + help="Escolha de onde vem o JSON da campanha a ser auditada.", + ) + + campaign_json = "" + + if "sessão atual" in json_source and has_session_json: + # Construir o JSON a partir dos assets na sessão + campaign_json = _build_json_export(st.session_state["assets"]) + with st.expander("Ver JSON da sessão atual", expanded=False): + st.code(campaign_json, language="json") + elif "sessão atual" in json_source and not has_session_json: + st.warning("Nenhuma campanha gerada na sessão atual. Gere uma campanha primeiro no modo 'Gerador' ou cole o JSON manualmente.") + return + else: + campaign_json = st.text_area( + "Cole o JSON da campanha aqui", + height=300, + placeholder='[{"campanha_google_ads": {"ad_groups": [...], "palavras_chave_negativas": [...], ...}}]', + help="Cole o JSON completo da campanha no formato de exportação.", ) - with col_dl2: - kw_csv = create_keywords_df(assets, camp, adg).to_csv(index=False) - st.download_button( - label="Baixar Keywords (.csv)", - data=kw_csv, - file_name="google_ads_keywords.csv", - mime="text/csv", - use_container_width=True, - ) + # ─── Botão de Auditoria ─────────────────────────────────────── + col_audit_btn, _ = st.columns([1, 3]) + with col_audit_btn: + audit_btn = st.button("Auditar Campanha", type="primary", use_container_width=True) + + # ─── Lógica de Auditoria ───────────────────────────────────── + if audit_btn: + if not campaign_json or not campaign_json.strip(): + st.error("Por favor, forneça o JSON da campanha para auditoria.") + st.stop() + + # Validar JSON + try: + json.loads(campaign_json) + except json.JSONDecodeError: + st.error("O JSON fornecido é inválido. Verifique a formatação e tente novamente.") + st.stop() + + # Verificar chave da API + if provider == "OpenAI" and not os.environ.get("OPENAI_API_KEY"): + st.error("OPENAI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") + st.stop() + elif provider == "Gemini" and not os.environ.get("GEMINI_API_KEY"): + st.error("GEMINI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.") + st.stop() + + # Barra de progresso visual + progress_bar = st.progress(0, text="Iniciando auditoria...") + status_text = st.empty() + + progress_bar.progress(15, text="Validando JSON da campanha...") + status_text.info("📋 JSON validado. Preparando envio para IA...") + + # Executa auditoria em thread com progresso animado + try: + audit_result, audit_prompts = _run_with_progress( + fn=lambda: audit_campaign( + campaign_json=campaign_json, + provider=provider, + model=model, + ), + progress_bar=progress_bar, + status_text=status_text, + msg=f"Analisando campanha com {provider} ({model})", + start_pct=25, + end_pct=95, + ) + except ValueError as e: + progress_bar.empty() + status_text.empty() + st.error(str(e)) + st.stop() + except json.JSONDecodeError: + progress_bar.empty() + status_text.empty() + st.error("A IA retornou uma resposta inválida. Tente novamente ou troque de modelo.") + st.stop() + except Exception as e: + progress_bar.empty() + status_text.empty() + st.error(f"Erro durante auditoria: {e}") + st.stop() + + progress_bar.progress(100, text="Auditoria concluída! (100%)") + status_text.success("✅ Auditoria concluída com sucesso!") + + # Salvar no session_state + st.session_state["audit_result"] = audit_result + st.session_state["audit_prompts"] = audit_prompts + + # ─── Exibição dos Resultados da Auditoria ──────────────────── + if "audit_result" in st.session_state: + audit_result = st.session_state["audit_result"] + audit_prompts = st.session_state.get("audit_prompts", {}) + + st.divider() + + # ─── Score Principal ────────────────────────────────────── + nota = audit_result["nota_final"] + cor = _get_score_color(nota) + + col_score, col_resumo = st.columns([1, 3]) + + with col_score: + st.markdown( + f'
' + f'

{nota:.1f}

' + f'

de 10.0 pontos

' + f'
', + unsafe_allow_html=True, + ) + + with col_resumo: + st.markdown("#### Resumo da Auditoria") + st.info(audit_result.get("resumo", "Sem resumo disponível.")) + + st.divider() + + # ─── Abas de Resultado ──────────────────────────────────── + tab_criterios, tab_simulacao, tab_audit_prompts = st.tabs([ + "Critérios de Avaliação", + "Simulação de Anúncio (CTR)", + "Prompts da Auditoria", + ]) + + with tab_criterios: + st.subheader("Detalhamento por Critério") + + for criterio in audit_result.get("criterios", []): + nome = criterio.get("nome", "") + nota_c = criterio.get("nota", 0) + nota_max = criterio.get("nota_maxima", 0) + peso = criterio.get("peso", 0) + falhas = criterio.get("falhas", []) + sugestoes = criterio.get("sugestoes", []) + emoji = _get_criterion_emoji(nota_c, nota_max) + + with st.expander(f"{emoji} {nome} — {nota_c:.1f} / {nota_max:.1f} (peso {peso:.1f})", expanded=True): + # Barra de progresso visual + pct = nota_c / nota_max if nota_max > 0 else 0 + st.progress(min(pct, 1.0)) + + if falhas: + st.markdown("**Falhas encontradas:**") + for f in falhas: + st.markdown(f"- :red[{f}]") + + if sugestoes: + st.markdown("**Sugestões de melhoria:**") + for s in sugestoes: + st.markdown(f"- :blue[{s}]") + + if not falhas and not sugestoes: + st.success("Nenhuma falha encontrada neste critério.") + + with tab_simulacao: + st.subheader("Simulação Visual dos Anúncios") + st.caption("Como os anúncios de cada grupo apareceriam nos resultados de pesquisa do Google:") + + simulacoes = audit_result.get("simulacoes_anuncio", []) + + if not simulacoes: + st.info("Nenhuma simulação de anúncio disponível.") + else: + for sim_idx, sim in enumerate(simulacoes): + grupo_nome = sim.get("grupo", f"Grupo {sim_idx + 1}") + titulo1 = sim.get("titulo_linha_1", "") + titulo2 = sim.get("titulo_linha_2", "") + titulo3 = sim.get("titulo_linha_3", "") + url_display = sim.get("url_display", "www.exemplo.com.br") + descricao = sim.get("descricao", "") + + # Montar título composto + titulos = [t for t in [titulo1, titulo2, titulo3] if t] + titulo_completo = " | ".join(titulos) + + st.markdown(f'

{grupo_nome}

', unsafe_allow_html=True) + + st.markdown( + f""" +
+
Patrocinado
+
{url_display}
+
{titulo_completo}
+
{descricao}
+
+ """, + unsafe_allow_html=True, + ) + + # Métricas dos títulos individuais + st.markdown("**Títulos selecionados para simulação:**") + for i, t in enumerate(titulos, 1): + char_count = len(t) + color = "green" if char_count <= 30 else "red" + st.markdown(f"{i}. {t} — :{color}[{char_count} chars]") + + if descricao: + char_desc = len(descricao) + color_desc = "green" if char_desc <= 90 else "red" + st.markdown(f"**Descrição:** {descricao} — :{color_desc}[{char_desc} chars]") + + if sim_idx < len(simulacoes) - 1: + st.markdown("---") + + with tab_audit_prompts: + st.subheader("Prompts Utilizados na Auditoria") + st.caption(f"Provider: **{provider}** | Modelo: **{model}**") + + st.markdown("#### Prompt de Sistema (System Prompt)") + st.code(audit_prompts.get("system_prompt", "N/A"), language=None) + + st.markdown("#### Prompt do Usuário (User Prompt)") + st.code(audit_prompts.get("user_prompt", "N/A"), language=None) + + +# ===================================================================== +# ROTEAMENTO DE PÁGINA +# ===================================================================== +if page == "Gerador de Campanha": + render_generator() +else: + render_auditor() diff --git a/regras_auditoria.md b/regras_auditoria.md new file mode 100644 index 0000000..fe3aff7 --- /dev/null +++ b/regras_auditoria.md @@ -0,0 +1,290 @@ +# REGRAS DE AUDITORIA — Google Ads Scorecard (GAS v6 — ENGINE AUDITOR HARD MATCH | V12.6 HEADLINE 30) + +OBJETIVO: +Versão HARD MATCH da auditoria compatível com ENGINE HARD LOCK (V11). +Eliminar falsos negativos de Keyword Match causados por delimitadores, +capitalização inicial ou pequenas diferenças estruturais. + +Nenhuma regra anterior foi removida. +A auditoria agora valida igualdade SEMÂNTICA NORMALIZADA. + +Nota final: 0 a 10 pontos (soma ponderada). + +--- + +# 🆕 ATUALIZAÇÃO V12.6 — LIMITES DE CARACTERES + +HEADLINES: +- limite máximo: 30 caracteres + +DEMAIS ATIVOS: +- sitelinks, callouts e ativos auxiliares permanecem com limite 25 caracteres + +--- + +# 0. ENGINE AUDITOR — FLUXO GLOBAL + +INÍCIO + +PASSO 1 → Normalizar Keywords +PASSO 2 → Normalizar Headlines (validar ≤30 caracteres) +PASSO 3 → Validar Keyword Match (Hard Match) +PASSO 4 → Validar Relevância Temática +PASSO 5 → Validar Compliance Editorial +PASSO 6 → Validar Aproveitamento de Espaço +PASSO 7 → Validar Extensões e Ativos +PASSO 8 → Calcular Nota Final + +FIM + +--- + +# 🔧 FUNÇÃO GLOBAL DE NORMALIZAÇÃO (HARD MATCH) + +FUNÇÃO normalizar(texto): + + remover delimitadores "[" "]" e '"' + remover espaços duplicados + permitir Capitalização Inicial + converter apenas para comparação semântica + manter acentuação + NÃO alterar ordem das palavras + +RETORNAR texto_normalizado + +OBSERVAÇÃO: +Capitalização inicial é considerada equivalente. + +Exemplo ACEITO: + +keyword: [separação judicial] +headline: Separação Judicial + +keyword: "advogado guarda" +headline: Advogado Guarda + +--- + +# 1. Keyword Match — Regra de Ferro HARD MATCH (Peso 4.0) + +## ENGINE VALIDATION + +PARA cada ad_group: + + keywords_norm = normalizar(keywords) + headlines_norm = normalizar(headlines) + + contar headlines onde: + + lower(keywords_norm) == lower(headlines_norm) + +SE total >= 5: + nota máxima + +## CRITÉRIO ELIMINATÓRIO + +Se headline adicionar palavra extra após normalização: + +EXEMPLO NÃO ACEITO: + +keyword: advogado guarda +headline: Advogado Guarda SP ❌ + +Se houver alteração semântica: +nota do grupo = 0 + +--- + +# 2. Relevância Temática e Isolamento (Peso 3.0) + +ENGINE CHECK: + +Identificar núcleo jurídico dominante por grupo. + +NÚCLEOS EXCLUSIVOS: +- divórcio +- guarda +- pensão +- inventário +- sucessão + +Palavras genéricas permitidas: +- advogado +- especialista +- atendimento + +Penalidade: +-1.0 por contaminação temática. + +--- + +# 3. Compliance Editorial (Peso 1.5) + +ENGINE SCAN: + +PROIBIDO: +- grátis +- gratuito +- variações diretas + +FORMATAÇÃO PROIBIDA: +- !!! ??? repetidos +- CAIXA ALTA total (exceto SP, OAB) + +Se existir termo proibido: +nota = 0 automaticamente. + +--- + +# 4. Aproveitamento de Espaço — HARD RANGE (Peso 1.0) + +VALIDAÇÃO HEADLINES (V12.6): +- Headlines devem possuir ≤30 caracteres. +- Se qualquer headline exceder 30 → penalidade automática. + +ACEITO: +80 ≤ descrição ≤ 92 caracteres + +IDEAL: +85–90 caracteres + +Penalidade: +-0.2 por descrição abaixo de 80. + +## HARD MATCH — ANTI-TRUNCAMENTO + +Se descrição terminar com: + +- palavra incompleta +- corte de substring +- espaço final inválido + +→ falha automática. + +Exemplo NÃO ACEITO: +"Consulte um espe" +"Fale conosco ag" + +--- + +# 5. Extensões e Ativos (Peso 0.5) + +ENGINE CHECK: + +SITELINKS: +- description1 preenchido +- description2 preenchido + +CALLOUTS: +- mínimo 6 + +🆕 HARD MATCH BONUS: +Presença de 7º callout diferencial reforça nota máxima. + +--- + +# 🧮 CÁLCULO DA NOTA FINAL + +nota_final = soma ponderada: + +Keyword Match (até 4.0) ++ +Relevância Temática (até 3.0) ++ +Compliance Editorial (até 1.5) ++ +Aproveitamento de Espaço (até 1.0) ++ +Extensões e Ativos (até 0.5) + +TOTAL = 10 pontos. + +--- + +# 📊 INSTRUÇÕES DE EXECUÇÃO + +1. Auditoria por Grupo: +Avaliar cada ad_group individualmente antes da consolidação. + +2. Indicação de Falhas: +Sempre indicar qual grupo apresentou problema. + +3. Simulação Visual: +Gerar 1 simulação de anúncio por grupo. + +4. HARD MATCH OBRIGATÓRIO: +Toda comparação deve usar normalizar(texto). + +--- + +# 📦 FORMATO DE RETORNO (JSON) + +{ + "nota_final": 0.0, + "criterios": [ + { + "nome": "Keyword Match (Regra de Ferro)", + "peso": 4.0, + "nota": 0.0, + "nota_maxima": 4.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Relevância Temática", + "peso": 3.0, + "nota": 0.0, + "nota_maxima": 3.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Compliance Editorial", + "peso": 1.5, + "nota": 0.0, + "nota_maxima": 1.5, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Aproveitamento de Espaço", + "peso": 1.0, + "nota": 0.0, + "nota_maxima": 1.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Extensões e Ativos", + "peso": 0.5, + "nota": 0.0, + "nota_maxima": 0.5, + "falhas": [], + "sugestoes": [] + } + ], + "resumo": "", + "simulacoes_anuncio": [ + { + "grupo": "", + "titulo_linha_1": "", + "titulo_linha_2": "", + "titulo_linha_3": "", + "url_display": "", + "descricao": "" + } + ] +} + +--- + +# 🔎 DIFERENÇAS DO GAS v5 → GAS v6 + +✔ HARD MATCH real (comparação normalizada) +✔ Capitalização inicial aceita oficialmente +✔ Delimitadores [ ] e " ignorados +✔ Anti-truncamento reforçado +✔ Range técnico consolidado (80–92) + +Resultado esperado: +Keyword Match tende a 4.0 quando ENGINE HARD LOCK estiver correto. \ No newline at end of file diff --git a/regras_auditoriaVGAS6.md b/regras_auditoriaVGAS6.md new file mode 100644 index 0000000..af2397c --- /dev/null +++ b/regras_auditoriaVGAS6.md @@ -0,0 +1,290 @@ +# REGRAS DE AUDITORIA — Google Ads Scorecard (GAS v6 — ENGINE AUDITOR HARD MATCH | V12.6 HEADLINE 30) + +OBJETIVO: +Versão HARD MATCH da auditoria compatível com ENGINE HARD LOCK (V11). +Eliminar falsos negativos de Keyword Match causados por delimitadores, +capitalização inicial ou pequenas diferenças estruturais. + +Nenhuma regra anterior foi removida. +A auditoria agora valida igualdade SEMÂNTICA NORMALIZADA. + +Nota final: 0 a 10 pontos (soma ponderada). + +--- + +# 🆕 ATUALIZAÇÃO V12.6 — LIMITES DE CARACTERES + +HEADLINES: +- limite máximo: 30 caracteres + +DEMAIS ATIVOS: +- sitelinks, callouts e ativos auxiliares permanecem com limite 25 caracteres + +--- + +# 0. ENGINE AUDITOR — FLUXO GLOBAL + +INÍCIO + +PASSO 1 → Normalizar Keywords +PASSO 2 → Normalizar Headlines (validar ≤30 caracteres) +PASSO 3 → Validar Keyword Match (Hard Match) +PASSO 4 → Validar Relevância Temática +PASSO 5 → Validar Compliance Editorial +PASSO 6 → Validar Aproveitamento de Espaço +PASSO 7 → Validar Extensões e Ativos +PASSO 8 → Calcular Nota Final + +FIM + +--- + +# 🔧 FUNÇÃO GLOBAL DE NORMALIZAÇÃO (HARD MATCH) + +FUNÇÃO normalizar(texto): + + remover delimitadores "[" "]" e '"' + remover espaços duplicados + permitir Capitalização Inicial + converter apenas para comparação semântica + manter acentuação + NÃO alterar ordem das palavras + +RETORNAR texto_normalizado + +OBSERVAÇÃO: +Capitalização inicial é considerada equivalente. + +Exemplo ACEITO: + +keyword: [separação judicial] +headline: Separação Judicial + +keyword: "advogado guarda" +headline: Advogado Guarda + +--- + +# 1. Keyword Match — Regra de Ferro HARD MATCH (Peso 4.0) + +## ENGINE VALIDATION + +PARA cada ad_group: + + keywords_norm = normalizar(keywords) + headlines_norm = normalizar(headlines) + + contar headlines onde: + + lower(keywords_norm) == lower(headlines_norm) + +SE total >= 5: + nota máxima + +## CRITÉRIO ELIMINATÓRIO + +Se headline adicionar palavra extra após normalização: + +EXEMPLO NÃO ACEITO: + +keyword: advogado guarda +headline: Advogado Guarda SP ❌ + +Se houver alteração semântica: +nota do grupo = 0 + +--- + +# 2. Relevância Temática e Isolamento (Peso 3.0) + +ENGINE CHECK: + +Identificar núcleo jurídico dominante por grupo. + +NÚCLEOS EXCLUSIVOS: +- divórcio +- guarda +- pensão +- inventário +- sucessão + +Palavras genéricas permitidas: +- advogado +- especialista +- atendimento + +Penalidade: +-1.0 por contaminação temática. + +--- + +# 3. Compliance Editorial (Peso 1.5) + +ENGINE SCAN: + +PROIBIDO: +- grátis +- gratuito +- variações diretas + +FORMATAÇÃO PROIBIDA: +- !!! ??? repetidos +- CAIXA ALTA total (exceto SP, OAB) + +Se existir termo proibido: +nota = 0 automaticamente. + +--- + +# 4. Aproveitamento de Espaço — HARD RANGE (Peso 1.0) + +VALIDAÇÃO HEADLINES (V12.6): +- Headlines devem possuir ≤30 caracteres. +- Se qualquer headline exceder 30 → penalidade automática. + +ACEITO: +80 ≤ descrição ≤ 92 caracteres + +IDEAL: +85–90 caracteres + +Penalidade: +-0.2 por descrição abaixo de 80. + +## HARD MATCH — ANTI-TRUNCAMENTO + +Se descrição terminar com: + +- palavra incompleta +- corte de substring +- espaço final inválido + +→ falha automática. + +Exemplo NÃO ACEITO: +"Consulte um espe" +"Fale conosco ag" + +--- + +# 5. Extensões e Ativos (Peso 0.5) + +ENGINE CHECK: + +SITELINKS: +- description1 preenchido +- description2 preenchido + +CALLOUTS: +- mínimo 6 + +🆕 HARD MATCH BONUS: +Presença de 7º callout diferencial reforça nota máxima. + +--- + +# 🧮 CÁLCULO DA NOTA FINAL + +nota_final = soma ponderada: + +Keyword Match (até 4.0) ++ +Relevância Temática (até 3.0) ++ +Compliance Editorial (até 1.5) ++ +Aproveitamento de Espaço (até 1.0) ++ +Extensões e Ativos (até 0.5) + +TOTAL = 10 pontos. + +--- + +# 📊 INSTRUÇÕES DE EXECUÇÃO + +1. Auditoria por Grupo: +Avaliar cada ad_group individualmente antes da consolidação. + +2. Indicação de Falhas: +Sempre indicar qual grupo apresentou problema. + +3. Simulação Visual: +Gerar 1 simulação de anúncio por grupo. + +4. HARD MATCH OBRIGATÓRIO: +Toda comparação deve usar normalizar(texto). + +--- + +# 📦 FORMATO DE RETORNO (JSON) + +{ + "nota_final": 0.0, + "criterios": [ + { + "nome": "Keyword Match (Regra de Ferro)", + "peso": 4.0, + "nota": 0.0, + "nota_maxima": 4.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Relevância Temática", + "peso": 3.0, + "nota": 0.0, + "nota_maxima": 3.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Compliance Editorial", + "peso": 1.5, + "nota": 0.0, + "nota_maxima": 1.5, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Aproveitamento de Espaço", + "peso": 1.0, + "nota": 0.0, + "nota_maxima": 1.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Extensões e Ativos", + "peso": 0.5, + "nota": 0.0, + "nota_maxima": 0.5, + "falhas": [], + "sugestoes": [] + } + ], + "resumo": "", + "simulacoes_anuncio": [ + { + "grupo": "", + "titulo_linha_1": "", + "titulo_linha_2": "", + "titulo_linha_3": "", + "url_display": "", + "descricao": "" + } + ] +} + +--- + +# 🔎 DIFERENÇAS DO GAS v5 → GAS v6 + +✔ HARD MATCH real (comparação normalizada) +✔ Capitalização inicial aceita oficialmente +✔ Delimitadores [ ] e " ignorados +✔ Anti-truncamento reforçado +✔ Range técnico consolidado (80–92) + +Resultado esperado: +Keyword Match tende a 4.0 quando ENGINE HARD LOCK estiver correto. diff --git a/regras_auditoria_old-boa.md b/regras_auditoria_old-boa.md new file mode 100644 index 0000000..c747f99 --- /dev/null +++ b/regras_auditoria_old-boa.md @@ -0,0 +1,106 @@ +Aqui está o documento de **REGRAS DE AUDITORIA (GAS v3)** atualizado, integrando o rigor da **Regra de Ferro** para palavras-chave, o novo critério de **Relevância Temática** e a redistribuição de pesos para totalizar 10 pontos. + +--- + +# REGRAS DE AUDITORIA — Google Ads Scorecard (GAS v3) + +Avalie cada critério abaixo considerando TODOS os ad_groups. A nota final (0 a 10) é a soma ponderada dos pesos abaixo: + +## 1. Keyword Match - Regra de Ferro (Peso 4.0 — máx 4.0 pontos) + +* Em cada grupo, pelo menos 5 títulos (headlines) devem ser **IDÊNTICOS** às keywords de fundo de funil do grupo. +* **CRITÉRIO ELIMINATÓRIO:** Proibido adicionar qualquer palavra, hífen, ou adjetivo antes ou depois da keyword nesses 5 títulos. +* **Penalidade:** Se um título "enfeitar" a keyword (ex: mudou "advogado guarda" para "advogado guarda em SP"), a nota deste critério para o grupo será **ZERO**. + +## 2. Relevância Temática e Isolamento (Peso 3.0 — máx 3.0 pontos) + +* **Isolamento de Tema:** Avalie se os 3 grupos possuem temas realmente distintos. Keywords e títulos de um AdGroup não podem "contaminar" outros grupos (ex: termo "inventário" dentro do grupo de "divórcio"). +* **Penalidade:** -1.0 para cada ocorrência de mistura de temas entre grupos. + +## 3. Compliance Editorial (Peso 1.5 — máx 1.5 pontos) + +* Ausência total de termos proibidos: "grátis", "gratuito", sinônimos (em todos os grupos). +* Sem formatação proibida: excesso de pontuação (!!!, ???), CAIXA ALTA indevida (exceto siglas como SP, OAB). +* **Penalidade:** Se houver a palavra "grátis" ou "gratuito" em qualquer grupo, a nota deste critério é **0 automaticamente**. + +## 4. Aproveitamento de Espaço (Peso 1.0 — máx 1.0 ponto) + +* Descrições devem ter entre 80 e 90 caracteres. Títulos próximos de 30 caracteres. +* **Penalidade:** -0.2 para cada descrição abaixo de 80 caracteres que desperdiça espaço visual. + +## 5. Extensões e Ativos (Peso 0.5 — máx 0.5 ponto) + +* Sitelinks devem ter descrições preenchidas (description1 e description2). +* Callouts devem ser focados em benefícios reais, autoridade e agilidade. + +--- + +## INSTRUÇÕES DE EXECUÇÃO + +1. **Auditoria por Grupo:** A IA deve analisar cada `ad_group` individualmente antes de consolidar a nota. +2. **Indicação de Falhas:** Nas falhas, SEMPRE indique em qual grupo o problema foi encontrado (ex: "Grupo 'Divórcio': Título 3 alterou keyword..."). +3. **Simulação:** Gere uma **Simulação de Anúncio** para CADA grupo, mostrando a composição visual final. + +## FORMATO DE RETORNO (JSON) + +Retorne o resultado EXATAMENTE neste formato JSON: + +```json +{ + "nota_final": 0.0, + "criterios": [ + { + "nome": "Keyword Match (Regra de Ferro)", + "peso": 4.0, + "nota": 0.0, + "nota_maxima": 4.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Relevância Temática", + "peso": 3.0, + "nota": 0.0, + "nota_maxima": 3.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Compliance Editorial", + "peso": 1.5, + "nota": 0.0, + "nota_maxima": 1.5, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Aproveitamento de Espaço", + "peso": 1.0, + "nota": 0.0, + "nota_maxima": 1.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Extensões e Ativos", + "peso": 0.5, + "nota": 0.0, + "nota_maxima": 0.5, + "falhas": [], + "sugestoes": [] + } + ], + "resumo": "", + "simulacoes_anuncio": [ + { + "grupo": "", + "titulo_linha_1": "", + "titulo_linha_2": "", + "titulo_linha_3": "", + "url_display": "", + "descricao": "" + } + ] +} + +``` \ No newline at end of file diff --git a/regras_auditoria_old.md b/regras_auditoria_old.md new file mode 100644 index 0000000..b4eb172 --- /dev/null +++ b/regras_auditoria_old.md @@ -0,0 +1,115 @@ +# REGRAS DE AUDITORIA — Google Ads Scorecard (GAS) + +Avalie cada critério abaixo considerando TODOS os ad_groups. A nota final (0 a 10) é a soma ponderada: + +## 1. Relevância e Keyword Match (Peso 3.0 — máx 3.0 pontos) +- Em cada grupo, pelo menos 5 títulos (headlines) devem ser idênticos às keywords de fundo de funil do grupo. +- Penalidade: -0.5 para cada título que "enfeitou" a palavra-chave (ex: mudou "advogado guarda" para "melhor advogado de guarda"). +- Avalie se os 3 grupos possuem temas realmente distintos entre si. + +## 2. Compliance Editorial (Peso 2.0 — máx 2.0 pontos) +- Ausência total de termos proibidos: "grátis", "gratuito", sinônimos (em todos os grupos). +- Sem formatação proibida: excesso de pontuação (!!!, ???), CAIXA ALTA indevida (exceto siglas como SP, OAB). +- Se houver a palavra "grátis" ou "gratuito" em qualquer grupo, a nota deste critério é 0 automaticamente. + +## 3. Aproveitamento de Espaço (Peso 2.0 — máx 2.0 pontos) +- Descrições devem ter entre 80 e 90 caracteres. Títulos próximos de 30 caracteres. +- Penalidade: -0.5 para cada descrição muito curta (abaixo de 70 chars) que desperdiça espaço. +- Aplique a todos os grupos. + +## 4. Variedade de Gatilhos (Peso 2.0 — máx 2.0 pontos) +- Deve haver 3 pilares nos títulos: Ação (CTA), Autoridade (anos de exp, nota 5.0) e Localização (cidade/estado). +- Penalidade: -1.0 se todos os títulos de um grupo forem apenas descritivos, sem convidar ao clique. + +## 5. Extensões e Ativos (Peso 1.0 — máx 1.0 ponto) +- Sitelinks devem ter descrições preenchidas (description1 e description2). +- Callouts devem ser focados em benefícios reais, não genéricos. + +## 7. SCORECARD DE AUDITORIA POR GRUPO (GAS v2) +A IA deve auto-avaliar o JSON final de 0 a 10 com base nos pesos abaixo, aplicados **individualmente a cada ad_group**: + +- **Relevância Temática (3.5):** As keywords e títulos combinam com o tema do grupo? +- **Coerência de AdGroup (2.5):** Os títulos de keyword são específicos para aquele grupo? +- **Espaço e Compliance (2.0):** Descrições > 80ch e zero termos proibidos? +- **Estrutura de Ativos (2.0):** Sitelinks e Callouts estão completos? + +## INSTRUÇÕES ADICIONAIS +Nas falhas, SEMPRE indique em qual grupo (ad_group) o problema foi encontrado (ex: "Grupo 'Divórcio': Título 3 alterou keyword..."). + +Além da avaliação, gere uma **Simulação de Anúncio** para CADA grupo, mostrando como os melhores títulos e a melhor descrição apareceriam em uma busca real no Google. + +## FORMATO DE RETORNO (JSON) +Retorne o resultado EXATAMENTE neste formato JSON: + +{ + "nota_final": 8.5, + "criterios": [ + { + "nome": "Relevância e Keyword Match", + "peso": 3.0, + "nota": 2.5, + "nota_maxima": 3.0, + "falhas": ["Grupo 'Divórcio': Título 3 alterou keyword 'advogado divórcio' para 'melhor advogado divórcio'"], + "sugestoes": ["Usar keyword exata sem modificadores nos primeiros 5 títulos de cada grupo"] + }, + { + "nome": "Compliance Editorial", + "peso": 2.0, + "nota": 2.0, + "nota_maxima": 2.0, + "falhas": [], + "sugestoes": [] + }, + { + "nome": "Aproveitamento de Espaço", + "peso": 2.0, + "nota": 1.5, + "nota_maxima": 2.0, + "falhas": ["Grupo 'Guarda': Descrição 2 tem apenas 45 caracteres"], + "sugestoes": ["Expandir descrições curtas para aproveitar 80-90 caracteres"] + }, + { + "nome": "Variedade de Gatilhos", + "peso": 2.0, + "nota": 1.5, + "nota_maxima": 2.0, + "falhas": ["Grupo 'Pensão': Nenhum título contém localização"], + "sugestoes": ["Adicionar cidade/estado em pelo menos 2 títulos por grupo"] + }, + { + "nome": "Extensões e Ativos", + "peso": 1.0, + "nota": 1.0, + "nota_maxima": 1.0, + "falhas": [], + "sugestoes": [] + } + ], + "resumo": "Campanha multi-grupo com boa segmentação temática, mas precisa ajustar keyword match no Grupo 1 e aproveitar melhor o espaço das descrições no Grupo 2.", + "simulacoes_anuncio": [ + { + "grupo": "Nome do Grupo 1", + "titulo_linha_1": "Advogado Divórcio SP", + "titulo_linha_2": "Consulta Especializada", + "titulo_linha_3": "+10 Anos de Experiência", + "url_display": "www.exemplo.com.br", + "descricao": "Advogado especialista em divórcio com atendimento personalizado. Agende sua consulta!" + }, + { + "grupo": "Nome do Grupo 2", + "titulo_linha_1": "Advogado Guarda", + "titulo_linha_2": "Fale Conosco Agora", + "titulo_linha_3": "Avaliação 5.0 no Google", + "url_display": "www.exemplo.com.br", + "descricao": "Especialista em guarda de filhos. Proteja seus direitos com quem entende do assunto!" + }, + { + "grupo": "Nome do Grupo 3", + "titulo_linha_1": "Pensão Alimentícia", + "titulo_linha_2": "Agende sua Consulta Hoje", + "titulo_linha_3": "Atendimento 100% Online", + "url_display": "www.exemplo.com.br", + "descricao": "Advogado para pensão alimentícia. Resolva seu caso com agilidade e segurança jurídica!" + } + ] +} diff --git a/regras_prompt-V12.4.md b/regras_prompt-V12.4.md new file mode 100644 index 0000000..0886ea5 --- /dev/null +++ b/regras_prompt-V12.4.md @@ -0,0 +1,322 @@ +# REGRAS CONSOLIDADAS PARA GOOGLE ADS (V12.4 — HARD MATCH + DKI SAFE MODE) + +OBJETIVO: +Engine determinístico com canonização automática de keywords, espelhamento obrigatório de keywords em headlines +(HARD MATCH 7/7) + 01 título com DKI em modo seguro (SEM substituir espelhos), +e regeneração obrigatória de descrições (SEM substring). + +Eliminar: +- falhas de Keyword Match por ausência de headline espelho exato +- truncamento por substring (descrições e callouts) +- inconsistência entre ENGINE e AUDITORIA +- riscos editoriais/limite de caracteres introduzidos por DKI + +Nenhuma regra anterior foi removida — apenas reforçada. +Capitalização inicial permitida. +Compatível com GAS v6 — ENGINE AUDITOR HARD MATCH. + +--- + +# 0. FLUXO GLOBAL (DETERMINISTIC EXECUTION) + +INÍCIO + +PARA cada GRUPO_DE_ANUNCIO (total = 3): + + PASSO 1 → Gerar Keywords + PASSO 1.1 → SANITIZAR E CANONIZAR KEYWORDS + PASSO 2 → Normalizar Keywords + PASSO 3 → Classificar Keywords por PRIORIDADE + PASSO 4 → Selecionar Literais (ALGORITMO FIXO) + PASSO 4.1 → Gerar HEADLINES ESPELHO (1 por keyword) + PASSO 4.2 → Gerar 01 HEADLINE DKI (SAFE MODE) + PASSO 5 → Montar LISTA FINAL DE TÍTULOS (15) com MIRROR ENFORCEMENT + DKI SAFE + PASSO 6 → Gerar Descrições (REGENERAÇÃO OBRIGATÓRIA / SEM CORTE) + PASSO 7 → Validar Limites + PASSO 8 → Inserir Localização (fora dos espelhos) + +FIM + +Depois: + PASSO 9 → Negativas + PASSO 10 → Extensões + PASSO 11 → HARD LOCK VALIDATION + +RETORNAR apenas JSON estruturado. + +--- + +# 1. PALAVRAS-CHAVE (ENGINE STEP) + +GERAR exatamente 7 keywords por grupo. + +REGRAS: +- usar [Exata] e "Frase" +- máximo 25 caracteres +- keywords devem ser juridicamente tituláveis. + +--- + +## SANITIZAÇÃO E CANONIZAÇÃO (OBRIGATÓRIA) + +ANTES de aceitar qualquer keyword: + +FUNÇÃO canonizar(keyword): + + remover duplicações de delimitadores: + [[texto]] → texto + + converter para forma jurídica clara e titulável. + +Exemplos obrigatórios: + casamento fim → Fim do Casamento + fim do casamento → Fim do Casamento + fim pensão → Fim da Pensão + fim da pensão → Fim da Pensão + custo inventário → Custo Inventário + +SE keyword for vaga, truncada ou não titulável: + DESCARTAR e gerar nova. + +--- + +## ENGINE NORMALIZE + +FUNÇÃO normalizar(texto): + + remover "[" "]" e '"' + remover espaços duplicados + aplicar Capitalização Inicial + manter acentuação + manter ordem + NÃO alterar semântica + +RETORNAR texto_normalizado + +--- + +# 2. CLASSIFICAÇÃO DETERMINÍSTICA DE KEYWORDS + +PRIORIDADE 1 — COMERCIAL DIRETA +- valor +- custo +- preço +- regularização +- revisão +- cálculo +- quanto custa +- honorários + +PRIORIDADE 2 — CONTRATAÇÃO +- advogado +- ação +- processo +- consulta + +PRIORIDADE 3 — INFORMATIVA +demais termos. + +--- + +# 3. SELEÇÃO LITERAL DETERMINÍSTICA + +FUNÇÃO selecionar_keywords_literais(lista_keywords): + + ordenar por: + prioridade ASC + tamanho ASC + ordem original + + selecionar até 5. + +❗ Seleção automática — IA não escolhe manualmente. + +--- + +# 4. KEYWORD MIRROR HEADLINES — HARD MATCH (ENFORCEMENT) + +OBJETIVO: +Garantir que TODAS as 7 keywords do grupo tenham ao menos 1 headline correspondente EXATO após normalização. +Se a keyword existir, o headline espelho correspondente DEVE existir no output final. + +REGRAS: + +1) Para cada keyword do grupo (7): + headline_espelho = normalizar(canonizar(keyword)) + +2) O headline_espelho deve ser EXATO (após normalização). + PROIBIDO: + - adicionar palavras (ex: "SP", "Rápido", "Seguro") + - remover palavras + - trocar ordem + - criar sinônimos + - plural/singular diferente + +3) PROIBIÇÃO ABSOLUTA — "SP" NO ESPELHO + Se a keyword NÃO contém "SP" ou "São Paulo", + o headline_espelho NÃO pode conter "SP" ou "São Paulo". + +4) MIRROR ENFORCEMENT (REGRA DE MONTAGEM): + A lista final de 15 títulos DO GRUPO deve conter, como itens separados, + TODOS os 7 headlines_espelho. Nenhum espelho pode ser omitido, substituído ou reescrito. + +--- + +# 4.2 HEADLINE DKI — SAFE MODE (NOVO) + +OBJETIVO: +Adicionar 01 headline com Dynamic Keyword Insertion para aumentar relevância/CTR em cauda longa, +SEM derrubar o HARD MATCH (espelhos continuam obrigatórios). + +FORMATO DKI (OBRIGATÓRIO): +{Keyword:PADRAO_DO_GRUPO} + +REGRAS SAFE: + +1) O headline DKI NUNCA substitui nenhum headline_espelho. + Os 7 espelhos continuam obrigatórios dentro dos 15 títulos. + +2) O headline DKI NÃO PODE entrar no Bucket de LITERAIS. + Ele entra apenas como título "livre" dentro do Bucket CTA/Local. + +3) PADRAO_DO_GRUPO deve ser curto, titulável e ≤ 30 caracteres. + Exemplos recomendados (escolher 1): + - Advogado de Família + - Divórcio em SP + - Guarda e Pensão + - Inventário em SP + +4) PROIBIDO no DKI: + - termos proibidos ("Grátis", "Gratuito" e variações) + - palavras cortadas + - caixa alta total + - pontuação excessiva + +5) Se houver risco de estourar 30 caracteres (por inserção dinâmica), + o PADRAO_DO_GRUPO deve ser ainda mais curto (≤ 20 caracteres). + +--- + +# 5. TÍTULOS — MONTAGEM DETERMINÍSTICA (15 POR GRUPO) + +Distribuição original mantida: 05 / 05 / 05 (LITERAL / CTA-LOCAL / PROVA). + +REGRA DETERMINÍSTICA DE POSIÇÃO: + +- Bucket 1 (05 LITERAIS): usar os 5 primeiros headlines_espelho (pela ordem de prioridade). +- Bucket 2 (05 CTA/LOCAL): 3 CTAs/Local reais + 1 headline_espelho (o 6º) + 1 headline DKI (SAFE MODE). +- Bucket 3 (05 PROVA): 4 provas/diferenciais reais + 1 headline_espelho (o 7º). + +HARD LOCK: +- os 7 headlines_espelho entram exatamente como gerados (sem SP extra). +- CTAs/Provas podem conter SP/São Paulo quando possível. +- o DKI entra como adicional controlado, sem substituir espelhos. + +LIMITES: +- cada título ≤ 30 caracteres + +--- + +# 6. DESCRIÇÕES — DETERMINISTIC TEXT ENGINE (NO-TRUNC OUTPUT) + +RANGE: +80 ≤ caracteres ≤ 92 +IDEAL: 85–90 + +ORDEM DE EXECUÇÃO OBRIGATÓRIA: +1) GERAR descrição completa +2) VALIDAR tamanho e integridade +3) SE inválida → REGERAR DO ZERO +4) PROIBIDO ajustar por corte + +REGRA ABSOLUTA — ANTI SUBSTRING: +NUNCA usar: +- texto[:limite] +- slice +- substring +- corte parcial + +VALIDAÇÃO DE INTEGRIDADE (OBRIGATÓRIA): +Uma descrição SÓ É VÁLIDA se: +- 80 ≤ caracteres ≤ 92 +- termina com "." ou "!" +- NÃO termina com espaço +- NÃO termina com palavra incompleta + +SE QUALQUER ITEM FALHAR: + REGERAR do zero (reescrita completa). + +AJUSTE DETERMINÍSTICO: +- Se > 92: reescrever reduzindo conectivos e adjetivos (sem cortar string). +- Se < 80: reescrever adicionando UVP ou CTA (sem inflar demais). + +CONTEÚDO: +- incluir UVP + CTA +- incluir SP/São Paulo quando possível (sem quebrar o range) + +--- + +# 7. EXTENSÕES (ATIVOS) — HARD LOCK + +SITELINKS: +- 4 obrigatórios +- título ≤25 caracteres +- 2 descrições ≤35 caracteres + +CALLOUTS: +- 7 obrigatórios +- ≤25 caracteres +- incluir 01 diferencial competitivo + +ANTI-TRUNCAMENTO — CALLOUTS (OBRIGATÓRIO): +- Proibido cortar string para caber em 25 caracteres. +- Se exceder 25, REESCREVER o callout completo (sem substring). +- Proibido terminar com palavra incompleta ou espaço final. + +--- + +# 8. POLÍTICAS EDITORIAIS + +PROIBIDO: +- "Grátis" +- "Gratuito" +- caixa alta total (exceto SP, OAB) +- pontuação excessiva + +PERMITIDO: +- Capitalização inicial nos espelhos e literais. + +--- + +# 9. HARD LOCK VALIDATION (FINAL) + +VALIDAR: + +- existem 3 grupos? +- existem 7 keywords por grupo? +- keywords foram canonizadas? +- existem 7 headlines_espelho (1 por keyword)? +- CADA keyword possui 1 headline_espelho EXATO após normalização? +- existem 15 títulos por grupo? +- Bucket 1 contém 5 espelhos? +- Bucket 2 contém 1 espelho (6º) + 3 CTAs reais + 1 DKI? +- Bucket 3 contém 1 espelho (7º) + 4 provas reais? +- o DKI está no formato {Keyword:...} e ≤ 30? +- o DKI NÃO contém termos proibidos? +- nenhum título > 30? +- descrições 80–92? +- descrições terminam com "." ou "!"? +- nenhuma descrição termina com palavra incompleta ou espaço? +- nenhuma substring detectada em descrições? +- callouts completos (sem palavra incompleta) e ≤25? +- existem 7 callouts? + +SE falhar: + REEXECUTAR apenas bloco inválido + +--- + +RETORNO FINAL: +APENAS JSON estruturado. +SEM explicações. +SEM texto adicional. \ No newline at end of file diff --git a/regras_prompt.md b/regras_prompt.md new file mode 100644 index 0000000..8614b09 --- /dev/null +++ b/regras_prompt.md @@ -0,0 +1,334 @@ +# REGRAS CONSOLIDADAS PARA GOOGLE ADS (V12.6 — HEADLINE 30 + HARD MATCH + DKI SAFE MODE + RECONCILIATION ENGINE) + +OBJETIVO: +Engine determinístico com canonização automática de keywords, espelhamento obrigatório de keywords em headlines +(HARD MATCH 7/7) + 01 título com DKI em modo seguro (SEM substituir espelhos), +e regeneração obrigatória de descrições (SEM substring), agora com camada adicional +RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) para validação estrutural final. + +Compatível com GAS v6 — ENGINE AUDITOR HARD MATCH. + +--- + +# 🆕 V12.6 — ATUALIZAÇÃO (HEADLINE 30) + +* **Headlines (Títulos):** limite alterado de **25** para **30 caracteres**. +* **Demais ativos** (keywords, callouts, sitelinks e descrições de sitelink): permanecem com **limite 25** (ou os limites já definidos nas seções próprias). + +--- + +# 0. FLUXO GLOBAL (DETERMINISTIC EXECUTION) + +INÍCIO + +PARA cada GRUPO_DE_ANUNCIO (total = 3): + +``` +PASSO 1 → Gerar Keywords +PASSO 1.1 → SANITIZAR E CANONIZAR KEYWORDS +PASSO 2 → Normalizar Keywords +PASSO 3 → Classificar Keywords por PRIORIDADE +PASSO 4 → Selecionar Literais (ALGORITMO FIXO) +PASSO 4.1 → Gerar HEADLINES ESPELHO (1 por keyword) +PASSO 4.2 → Gerar 01 HEADLINE DKI (SAFE MODE) +PASSO 5 → Montar LISTA FINAL DE TÍTULOS (15) com MIRROR ENFORCEMENT + DKI SAFE +PASSO 6 → Gerar Descrições (REGENERAÇÃO OBRIGATÓRIA / SEM CORTE) +PASSO 7 → Validar Limites (Headlines ≤30; demais conforme seção) +PASSO 8 → Inserir Localização (fora dos espelhos) +``` + +FIM + +Depois: +PASSO 9 → Negativas +PASSO 10 → Extensões +PASSO 11 → HARD LOCK VALIDATION +PASSO 12 → RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) +PASSO 13 → AUDITORIA GAS v6 (Opcional) + +RETORNAR apenas JSON estruturado. + +--- + +# 1. PALAVRAS-CHAVE (ENGINE STEP) + +GERAR exatamente 7 keywords por grupo. + +REGRAS: + +* usar [Exata] e "Frase" +* máximo 25 caracteres +* keywords devem ser juridicamente tituláveis. + +--- + +## SANITIZAÇÃO E CANONIZAÇÃO (OBRIGATÓRIA) + +ANTES de aceitar qualquer keyword: + +FUNÇÃO canonizar(keyword): + +``` +remover duplicações de delimitadores: + [[texto]] → texto + +converter para forma jurídica clara e titulável. +``` + +Exemplos obrigatórios: +casamento fim → Fim do Casamento +fim do casamento → Fim do Casamento +fim pensão → Fim da Pensão +fim da pensão → Fim da Pensão +custo inventário → Custo Inventário + +SE keyword for vaga, truncada ou não titulável: +DESCARTAR e gerar nova. + +--- + +## ENGINE NORMALIZE + +FUNÇÃO normalizar(texto): + +``` +remover "[" "]" e '"' +remover espaços duplicados +aplicar Capitalização Inicial +manter acentuação +manter ordem +NÃO alterar semântica +``` + +RETORNAR texto_normalizado + +--- + +# 2. CLASSIFICAÇÃO DETERMINÍSTICA DE KEYWORDS + +PRIORIDADE 1 — COMERCIAL DIRETA + +* valor +* custo +* preço +* regularização +* revisão +* cálculo +* quanto custa +* honorários + +PRIORIDADE 2 — CONTRATAÇÃO + +* advogado +* ação +* processo +* consulta + +PRIORIDADE 3 — INFORMATIVA +Demais termos. + +--- + +# 3. SELEÇÃO LITERAL DETERMINÍSTICA + +FUNÇÃO selecionar_keywords_literais(lista_keywords): + +``` +ordenar por: + prioridade ASC + tamanho ASC + ordem original + +selecionar até 5. +``` + +--- + +# 4. KEYWORD MIRROR HEADLINES — HARD MATCH (ENFORCEMENT) + +OBJETIVO: +Garantir que TODAS as 7 keywords do grupo tenham ao menos 1 headline correspondente EXATO após normalização. + +REGRAS: + +1. Para cada keyword do grupo (7): + headline_espelho = normalizar(canonizar(keyword)) + +2. O headline_espelho deve ser EXATO (após normalização). + +3. PROIBIÇÃO ABSOLUTA — "SP" NO ESPELHO + +4. MIRROR ENFORCEMENT: + A lista final de 15 títulos deve conter TODOS os 7 headlines_espelho. + +--- + +# 4.2 HEADLINE DKI — SAFE MODE + +FORMATO: +{Keyword:PADRAO_DO_GRUPO} + +REGRAS SAFE: + +* DKI nunca substitui espelhos +* DKI fora do bucket literal +* ≤30 caracteres + +--- + +# 5. TÍTULOS — MONTAGEM DETERMINÍSTICA + +**Limite de caracteres (V12.6):** cada headline deve ter **≤30 caracteres**. + +Distribuição: 05 / 05 / 05 + +Bucket 1: 5 espelhos +Bucket 2: 3 CTA/Local + 1 espelho + 1 DKI +Bucket 3: 4 provas + 1 espelho + +--- + +# 6. DESCRIÇÕES — DETERMINISTIC TEXT ENGINE + +80 ≤ caracteres ≤ 92 + +Proibido substring. +Falhou validação → Regenerar do zero. + +--- + +# 7. EXTENSÕES — HARD LOCK + +Sitelinks: 4 obrigatórios +Callouts: 7 obrigatórios + +--- + +# 8. POLÍTICAS EDITORIAIS + +Proibido: + +* Grátis +* Gratuito +* caixa alta total + +--- + +# 9. HARD LOCK VALIDATION (FINAL) + +Validar integridade completa antes da saída. + +--- + +# V12.5 — RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) + +OBJETIVO: +Adicionar camada determinística de Reconciliação garantindo integridade total entre ENGINE e estado final. + +--- + +## PRINCÍPIO + +ENGINE → cria HARD LOCK → RECONCILIATION → AUDITORIA + +--- + +## SNAPSHOTS OBRIGATÓRIOS (ENGINE_STATE) + +keywords_raw[7] +keywords_canon[7] +keywords_norm[7] +headlines_espelho_criados[7] +headline_dki_criado[1] +headlines_final[15] + +bucket_map_final + +descricao_final +callouts_final[7] +sitelinks_final[4] + +--- + +## FINGERPRINTS (OBRIGATÓRIO) + +Criar hashes SHA-256: + +fp_keywords_norm +fp_headlines_final_norm +fp_descricao_final +fp_bucket_map + +--- + +## REGRAS DE RECONCILIAÇÃO + +R1 — EXISTÊNCIA 7/7 +Cada keyword_norm deve existir como headline espelho final. + +R2 — INTEGRIDADE DO ESPELHO +headline espelho deve permanecer idêntica. + +R3 — CONTAGEM DE ESPELHOS +Exatamente 7 espelhos únicos. + +R4 — DKI SAFE MODE + +* exatamente 1 headline DKI +* formato {Keyword:PADRAO} +* não substitui espelhos +* fora do bucket literal + +R5 — BUCKET LAYOUT +Bucket 1: 5 espelhos +Bucket 2: 3 CTA + 1 espelho + 1 DKI +Bucket 3: 4 provas + 1 espelho + +R6 — DESCRIÇÃO ANTI-GHOST +Fingerprint da descrição final deve existir nas descrições geradas. + +R7 — INTEGRIDADE FINAL DA DESCRIÇÃO +80 ≤ caracteres ≤ 92 +Termina com . ou ! + +R8 — CALLOUTS +7 obrigatórios, ≤25 caracteres. + +R9 — SITELINKS +Cada sitelink com description1 e description2. + +R10 — HEADLINES (V12.6) +* todas as 15 headlines devem ter **≤30 caracteres** +* proibido truncar automaticamente (se estourar, regenerar) + +--- + +## SEVERIDADE + +CRÍTICO: bloquear saída +ALTA: reexecutar etapa +MÉDIA: warning + +--- + +## REEXECUÇÃO AUTOMÁTICA + +DESYNC_MIRROR_* → Passo 4.1/5 +DESYNC_DKI_VIOLATION → Passo 4.2/5 +DESYNC_BUCKET_LAYOUT → Passo 5 +DESYNC_DESCRIPTION_* → Passo 6 +DESYNC_CALLOUT_TRUNC → Passo 7 +DESYNC_SITELINK_INCOMPLETE → Passo 7 + +--- + +## STATUS FINAL + +PASS_RECONCILIATION +FAIL_RECONCILIATION + +--- + +# RESULTADO + +A Reconciliação elimina falsos negativos estruturais garantindo execução determinística do HARD MATCH + DKI SAFE MODE. \ No newline at end of file diff --git a/regras_promptV12.6.md b/regras_promptV12.6.md new file mode 100644 index 0000000..bc7e964 --- /dev/null +++ b/regras_promptV12.6.md @@ -0,0 +1,334 @@ +# REGRAS CONSOLIDADAS PARA GOOGLE ADS (V12.6 — HEADLINE 30 + HARD MATCH + DKI SAFE MODE + RECONCILIATION ENGINE) + +OBJETIVO: +Engine determinístico com canonização automática de keywords, espelhamento obrigatório de keywords em headlines +(HARD MATCH 7/7) + 01 título com DKI em modo seguro (SEM substituir espelhos), +e regeneração obrigatória de descrições (SEM substring), agora com camada adicional +RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) para validação estrutural final. + +Compatível com GAS v6 — ENGINE AUDITOR HARD MATCH. + +--- + +# 🆕 V12.6 — ATUALIZAÇÃO (HEADLINE 30) + +* **Headlines (Títulos):** limite alterado de **25** para **30 caracteres**. +* **Demais ativos** (keywords, callouts, sitelinks e descrições de sitelink): permanecem com **limite 25** (ou os limites já definidos nas seções próprias). + +--- + +# 0. FLUXO GLOBAL (DETERMINISTIC EXECUTION) + +INÍCIO + +PARA cada GRUPO_DE_ANUNCIO (total = 3): + +``` +PASSO 1 → Gerar Keywords +PASSO 1.1 → SANITIZAR E CANONIZAR KEYWORDS +PASSO 2 → Normalizar Keywords +PASSO 3 → Classificar Keywords por PRIORIDADE +PASSO 4 → Selecionar Literais (ALGORITMO FIXO) +PASSO 4.1 → Gerar HEADLINES ESPELHO (1 por keyword) +PASSO 4.2 → Gerar 01 HEADLINE DKI (SAFE MODE) +PASSO 5 → Montar LISTA FINAL DE TÍTULOS (15) com MIRROR ENFORCEMENT + DKI SAFE +PASSO 6 → Gerar Descrições (REGENERAÇÃO OBRIGATÓRIA / SEM CORTE) +PASSO 7 → Validar Limites (Headlines ≤30; demais conforme seção) +PASSO 8 → Inserir Localização (fora dos espelhos) +``` + +FIM + +Depois: +PASSO 9 → Negativas +PASSO 10 → Extensões +PASSO 11 → HARD LOCK VALIDATION +PASSO 12 → RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) +PASSO 13 → AUDITORIA GAS v6 (Opcional) + +RETORNAR apenas JSON estruturado. + +--- + +# 1. PALAVRAS-CHAVE (ENGINE STEP) + +GERAR exatamente 7 keywords por grupo. + +REGRAS: + +* usar [Exata] e "Frase" +* máximo 25 caracteres +* keywords devem ser juridicamente tituláveis. + +--- + +## SANITIZAÇÃO E CANONIZAÇÃO (OBRIGATÓRIA) + +ANTES de aceitar qualquer keyword: + +FUNÇÃO canonizar(keyword): + +``` +remover duplicações de delimitadores: + [[texto]] → texto + +converter para forma jurídica clara e titulável. +``` + +Exemplos obrigatórios: +casamento fim → Fim do Casamento +fim do casamento → Fim do Casamento +fim pensão → Fim da Pensão +fim da pensão → Fim da Pensão +custo inventário → Custo Inventário + +SE keyword for vaga, truncada ou não titulável: +DESCARTAR e gerar nova. + +--- + +## ENGINE NORMALIZE + +FUNÇÃO normalizar(texto): + +``` +remover "[" "]" e '"' +remover espaços duplicados +aplicar Capitalização Inicial +manter acentuação +manter ordem +NÃO alterar semântica +``` + +RETORNAR texto_normalizado + +--- + +# 2. CLASSIFICAÇÃO DETERMINÍSTICA DE KEYWORDS + +PRIORIDADE 1 — COMERCIAL DIRETA + +* valor +* custo +* preço +* regularização +* revisão +* cálculo +* quanto custa +* honorários + +PRIORIDADE 2 — CONTRATAÇÃO + +* advogado +* ação +* processo +* consulta + +PRIORIDADE 3 — INFORMATIVA +Demais termos. + +--- + +# 3. SELEÇÃO LITERAL DETERMINÍSTICA + +FUNÇÃO selecionar_keywords_literais(lista_keywords): + +``` +ordenar por: + prioridade ASC + tamanho ASC + ordem original + +selecionar até 5. +``` + +--- + +# 4. KEYWORD MIRROR HEADLINES — HARD MATCH (ENFORCEMENT) + +OBJETIVO: +Garantir que TODAS as 7 keywords do grupo tenham ao menos 1 headline correspondente EXATO após normalização. + +REGRAS: + +1. Para cada keyword do grupo (7): + headline_espelho = normalizar(canonizar(keyword)) + +2. O headline_espelho deve ser EXATO (após normalização). + +3. PROIBIÇÃO ABSOLUTA — "SP" NO ESPELHO + +4. MIRROR ENFORCEMENT: + A lista final de 15 títulos deve conter TODOS os 7 headlines_espelho. + +--- + +# 4.2 HEADLINE DKI — SAFE MODE + +FORMATO: +{Keyword:PADRAO_DO_GRUPO} + +REGRAS SAFE: + +* DKI nunca substitui espelhos +* DKI fora do bucket literal +* ≤30 caracteres + +--- + +# 5. TÍTULOS — MONTAGEM DETERMINÍSTICA + +**Limite de caracteres (V12.6):** cada headline deve ter **≤30 caracteres**. + +Distribuição: 05 / 05 / 05 + +Bucket 1: 5 espelhos +Bucket 2: 3 CTA/Local + 1 espelho + 1 DKI +Bucket 3: 4 provas + 1 espelho + +--- + +# 6. DESCRIÇÕES — DETERMINISTIC TEXT ENGINE + +80 ≤ caracteres ≤ 92 + +Proibido substring. +Falhou validação → Regenerar do zero. + +--- + +# 7. EXTENSÕES — HARD LOCK + +Sitelinks: 4 obrigatórios +Callouts: 7 obrigatórios + +--- + +# 8. POLÍTICAS EDITORIAIS + +Proibido: + +* Grátis +* Gratuito +* caixa alta total + +--- + +# 9. HARD LOCK VALIDATION (FINAL) + +Validar integridade completa antes da saída. + +--- + +# V12.5 — RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) + +OBJETIVO: +Adicionar camada determinística de Reconciliação garantindo integridade total entre ENGINE e estado final. + +--- + +## PRINCÍPIO + +ENGINE → cria HARD LOCK → RECONCILIATION → AUDITORIA + +--- + +## SNAPSHOTS OBRIGATÓRIOS (ENGINE_STATE) + +keywords_raw[7] +keywords_canon[7] +keywords_norm[7] +headlines_espelho_criados[7] +headline_dki_criado[1] +headlines_final[15] + +bucket_map_final + +descricao_final +callouts_final[7] +sitelinks_final[4] + +--- + +## FINGERPRINTS (OBRIGATÓRIO) + +Criar hashes SHA-256: + +fp_keywords_norm +fp_headlines_final_norm +fp_descricao_final +fp_bucket_map + +--- + +## REGRAS DE RECONCILIAÇÃO + +R1 — EXISTÊNCIA 7/7 +Cada keyword_norm deve existir como headline espelho final. + +R2 — INTEGRIDADE DO ESPELHO +headline espelho deve permanecer idêntica. + +R3 — CONTAGEM DE ESPELHOS +Exatamente 7 espelhos únicos. + +R4 — DKI SAFE MODE + +* exatamente 1 headline DKI +* formato {Keyword:PADRAO} +* não substitui espelhos +* fora do bucket literal + +R5 — BUCKET LAYOUT +Bucket 1: 5 espelhos +Bucket 2: 3 CTA + 1 espelho + 1 DKI +Bucket 3: 4 provas + 1 espelho + +R6 — DESCRIÇÃO ANTI-GHOST +Fingerprint da descrição final deve existir nas descrições geradas. + +R7 — INTEGRIDADE FINAL DA DESCRIÇÃO +80 ≤ caracteres ≤ 92 +Termina com . ou ! + +R8 — CALLOUTS +7 obrigatórios, ≤25 caracteres. + +R9 — SITELINKS +Cada sitelink com description1 e description2. + +R10 — HEADLINES (V12.6) +* todas as 15 headlines devem ter **≤30 caracteres** +* proibido truncar automaticamente (se estourar, regenerar) + +--- + +## SEVERIDADE + +CRÍTICO: bloquear saída +ALTA: reexecutar etapa +MÉDIA: warning + +--- + +## REEXECUÇÃO AUTOMÁTICA + +DESYNC_MIRROR_* → Passo 4.1/5 +DESYNC_DKI_VIOLATION → Passo 4.2/5 +DESYNC_BUCKET_LAYOUT → Passo 5 +DESYNC_DESCRIPTION_* → Passo 6 +DESYNC_CALLOUT_TRUNC → Passo 7 +DESYNC_SITELINK_INCOMPLETE → Passo 7 + +--- + +## STATUS FINAL + +PASS_RECONCILIATION +FAIL_RECONCILIATION + +--- + +# RESULTADO + +A Reconciliação elimina falsos negativos estruturais garantindo execução determinística do HARD MATCH + DKI SAFE MODE. diff --git a/regras_prompt_old-1.md b/regras_prompt_old-1.md new file mode 100644 index 0000000..e75b058 --- /dev/null +++ b/regras_prompt_old-1.md @@ -0,0 +1,84 @@ +```md +# REGRAS CONSOLIDADAS PARA GOOGLE ADS (V4 — Multi-Grupo) + +## 7. ESTRUTURA DA CAMPANHA (3 Grupos de Anúncio) +A campanha DEVE ser dividida em exatamente 3 Grupos de Anúncio (Ad Groups) temáticos. +Analise o conteúdo da Landing Page e identifique 3 ângulos/temas distintos para segmentar a campanha (ex: por serviço, por dor do cliente, por diferencial competitivo). +Cada grupo deve ter um nome descritivo e conter seus próprios Keywords, Headlines e Descriptions (Regras 1, 3 e 4 aplicam-se POR GRUPO). +As Negativas, Sitelinks e Callouts são compartilhados no nível da campanha (Regras 2, 5). + +--- + +## 1. PALAVRAS-CHAVE (Fundo de Funil) — POR GRUPO +Gere 7 termos por grupo (total ~21) com alta intenção de contratação. Use [Exata] e "Frase". Limite cada termo a no máximo 25 caracteres para viabilizar o uso nos títulos. + +--- + +## 2. NEGATIVAS (Nível de Campanha) +Gere 20 termos para filtrar curiosos e buscas por gratuidade (ex: pdf, curso, modelo, jurisprudência, tcc, concurso, como fazer, gratuito). + +--- + +## 3. TÍTULOS (Headlines) - Regra Mista e Local — POR GRUPO +Gere 15 títulos por grupo (máx. 30 caracteres cada) seguindo esta distribuição: + +### 🆕 ALTERAÇÃO — REGRA DE INTEGRIDADE (NÃO TRUNCAR) +- Os títulos NÃO podem ser truncados, cortados ou exceder o limite. +- Cada título deve ser gerado já dentro do limite de caracteres. +- É proibido gerar textos maiores esperando corte automático do Google Ads. + +### 🆕 ALTERAÇÃO — REGRA LITERAL DE PALAVRA-CHAVE (OBRIGATÓRIA) +- Devem existir exatamente 05 Títulos de Palavra-chave. +- Cada título deve conter a palavra-chave da Regra 1 de forma INTEGRAL, IDÊNTICA e LITERAL. +- O título deve ser EXATAMENTE igual à palavra-chave. +- Proibido adicionar qualquer palavra antes ou depois. +- Proibido hífen, pontuação extra, variações, pluralização ou mudança de capitalização. +- Exemplo normativo: + Se a keyword for **“Advogado Divórcio”**, então o título deve ser **“Advogado Divórcio”**. + +### 🆕 ALTERAÇÃO — REGRA DE LOCALIZAÇÃO (SP / SÃO PAULO) +- Sempre que possível, incluir referência geográfica “SP” ou “São Paulo” nos títulos de CTA/Local e nos títulos de Prova Social/Diferencial. +- A inclusão da localização NÃO deve violar limites de caracteres. +- A localização NÃO pode ser adicionada nos 05 Títulos de Palavra-chave literais. + +### Distribuição obrigatória: +- 05 Títulos de Palavra-chave (regra literal acima). +- 05 Títulos de CTA/Local: Focados em Chamadas para Ação e Relevância Local (ex: "Fale Conosco em SP", "Agende sua Consulta Hoje"). +- 05 Títulos de Prova Social/Diferencial: Utilize dados da LP (ex: "+10 anos de experiência", "Avaliação 5.0 no Google", "Atendimento 100% Online"). + +--- + +## 4. DESCRIÇÕES (Maximização de Espaço) — POR GRUPO +Gere 4 descrições por grupo. + +### 🆕 ALTERAÇÃO — REGRA DE INTEGRIDADE (NÃO TRUNCAR) +- As descrições NÃO podem ser truncadas ou exceder o limite. +- Cada descrição deve ser criada já dentro do tamanho permitido. + +### 🆕 ALTERAÇÃO — REGRA DE LOCALIZAÇÃO (SP / SÃO PAULO) +- Sempre que possível, incluir “SP” ou “São Paulo” nas descrições para reforçar relevância local. +- A localização não pode comprometer a clareza, o limite mínimo (80) e máximo (90) de caracteres. + +Regra de Ouro: +- Tente utilizar o mais próximo possível dos 90 caracteres (mínimo 80) para ocupar mais espaço visual. +- Devem terminar obrigatoriamente com um ponto final ou exclamação. +- Devem incluir a Proposta Única de Valor (UVP). + +--- + +## 5. EXTENSÕES (Ativos — Nível de Campanha) +- 4 Sitelinks: Título (até 25ch) e 2 linhas de descrição (até 35ch cada). +- Sempre que possível, incluir “SP” ou “São Paulo” respeitando limites. +- 6 Callouts: Frases de destaque (máx. 25ch) focadas em autoridade e agilidade, podendo incluir localização quando couber. + +--- + +## 6. POLÍTICAS EDITORIAIS E PROIBIÇÕES +- Inviolável: NUNCA utilize "Grátis", "Gratuito" ou sinônimos. +- Formatação: Proibido CAIXA ALTA (exceto siglas como SP, OAB). Proibido pontuação excessiva (ex: !!!). +- Estilo: Use tom profissional, empático e focado em solução. + +--- + +RETORNO: APENAS o JSON estruturado, sem texto adicional. +``` diff --git a/regras_prompt_old-2-boa.md b/regras_prompt_old-2-boa.md new file mode 100644 index 0000000..e285480 --- /dev/null +++ b/regras_prompt_old-2-boa.md @@ -0,0 +1,137 @@ +# REGRAS CONSOLIDADAS PARA GOOGLE ADS (V8 — HARD MODE IA / AUDITORIA BLINDADA + MATCH STRICT) + +## 7. ESTRUTURA DA CAMPANHA (3 Grupos de Anúncio) +A campanha DEVE ser dividida em exatamente 3 Grupos de Anúncio (Ad Groups) temáticos. +Analise o conteúdo da Landing Page e identifique 3 ângulos/temas distintos para segmentar a campanha. +Cada grupo deve ter nome descritivo e conter seus próprios Keywords, Headlines e Descriptions (Regras 1, 3 e 4 aplicam-se POR GRUPO). +Negativas, Sitelinks e Callouts são compartilhados no nível da campanha (Regras 2 e 5). + +--- + +## 1. PALAVRAS-CHAVE (Fundo de Funil) — POR GRUPO +Gere exatamente 7 termos por grupo (total ~21). +Use [Exata] e "Frase". +Máximo: 25 caracteres por keyword. + +### 🔒 HARD RULE — BASE PARA MATCH LITERAL +- As keywords devem permitir gerar obrigatoriamente 05 títulos literais. +- Evitar keywords longas que impeçam títulos ≤30 caracteres. +- A lista deve conter pelo menos 5 keywords compatíveis com títulos literais. + +### 🆕 MATCH STRICT — NORMALIZAÇÃO (AUDITORIA) +- Keywords podem conter sinais como [ ], " ". +- Para gerar Títulos Literais, remover apenas os delimitadores [ ] ou " ". +- O texto interno deve permanecer IDÊNTICO. +Exemplo: +Keyword: "[advogado divórcio]" +Título literal permitido: "Advogado Divórcio" + +--- + +## 2. NEGATIVAS (Nível de Campanha) +Gere 20 termos para filtrar curiosos e buscas por gratuidade (ex: pdf, curso, modelo, jurisprudência, tcc, concurso, como fazer, gratuito). + +--- + +## 3. TÍTULOS (Headlines) - Regra Mista e Local — POR GRUPO + +### 🚫 HARD LIMIT — TAMANHO +- Máximo absoluto: 30 caracteres. +- Não gerar textos maiores esperando corte automático. +- Títulos truncados são proibidos. + +### 🔒 HARD MATCH — TÍTULOS LITERAIS (REGRA DE FERRO) +- Devem existir exatamente 05 títulos literais. +- Cada título deve ser IDÊNTICO ao TEXTO INTERNO da keyword da Regra 1. +- Ignorar apenas delimitadores [ ] ou " ". +- Copiar literal após normalização. + +PROIBIDO: +- adicionar palavras +- remover palavras +- mudar ordem +- alterar acentuação +- alterar significado +- acrescentar SP se não estiver na keyword + +Exemplo: +Keyword: "[advogado inventário]" +Título literal obrigatório: "Advogado Inventário" + +Keyword: "\"advogado guarda\"" +Título literal obrigatório: "Advogado Guarda" + +### 📍 REGRA DE LOCALIZAÇÃO (SP / SÃO PAULO) +- Inserir “SP” ou “São Paulo” sempre que possível nos títulos de CTA/Local e Prova Social. +- NÃO alterar títulos literais. + +### 📊 DISTRIBUIÇÃO OBRIGATÓRIA +1. 05 Títulos Literais (match exato normalizado) +2. 05 Títulos CTA/Local +3. 05 Títulos Prova Social/Diferencial + +--- + +## 4. DESCRIÇÕES (Maximização de Espaço) — POR GRUPO + +### 📏 HARD RANGE — AUDITORIA +- Cada descrição deve ter entre 80 e 90 caracteres. +- Menos de 80 = erro. +- Mais de 90 = erro. + +### 🚫 HARD INTEGRIDADE +- Não gerar textos maiores esperando truncamento. +- A descrição já deve nascer dentro do limite. + +### 🆕 REGRA ANTI-SUBAPROVEITAMENTO (AUDITORIA) +- Descrições abaixo de 80 caracteres devem ser automaticamente expandidas antes da entrega final. + +### 📍 LOCALIZAÇÃO +- Sempre que possível incluir “SP” ou “São Paulo”. +- Não comprometer clareza. + +### 🎯 CONTEÚDO +- Incluir UVP. +- Incluir CTA claro. +- Final obrigatório com "." ou "!". + +--- + +## 5. EXTENSÕES (Ativos — Nível de Campanha) + +### SITELINKS +- 4 Sitelinks obrigatórios. +- Título até 25 caracteres. +- 2 linhas de descrição até 35 caracteres cada. +- Priorizar CTAs fortes. +- Sempre que possível incluir SP/São Paulo. + +### CALLOUTS +- 6 Callouts (máx. 25 caracteres). +- Incluir obrigatoriamente 01 callout com diferencial competitivo único. + +--- + +## 6. POLÍTICAS EDITORIAIS E PROIBIÇÕES +- Inviolável: NUNCA utilizar "Grátis", "Gratuito" ou sinônimos. +- Proibido CAIXA ALTA (exceto SP, OAB). +- Proibido pontuação excessiva. +- Linguagem profissional e orientada à solução. + +--- + +## 🔐 CHECKLIST AUTOMÁTICO (HARD MODE — EXECUÇÃO OBRIGATÓRIA) +Antes de gerar o JSON final, validar: + +- Existem exatamente 15 títulos por grupo? +- Existem exatamente 05 títulos literais idênticos às keywords (normalizadas)? +- Nenhum título excede 30 caracteres? +- Todas as descrições possuem entre 80 e 90 caracteres? +- Pelo menos uma menção a SP/São Paulo nos textos não literais? +- Existe 01 callout diferencial? + +Se qualquer item falhar → regenerar automaticamente antes da entrega. + +--- + +RETORNO: APENAS o JSON estruturado, sem texto adicional. diff --git a/regras_prompt_old-3-boa.md b/regras_prompt_old-3-boa.md new file mode 100644 index 0000000..ffc4cfa --- /dev/null +++ b/regras_prompt_old-3-boa.md @@ -0,0 +1,233 @@ +# REGRAS CONSOLIDADAS PARA GOOGLE ADS (V11.2 — ENGINE HARD LOCK / COMMERCIAL PRIORITY) + +OBJETIVO: +Versão HARD LOCK com prioridade comercial. +Blindar geração contra variações semânticas e erros de auditoria. +Capitalização inicial permitida. +Auditoria aceita igualdade semântica após normalização. + +Nenhuma regra anterior foi removida — apenas reforçada. + +--- + +# 0. FLUXO GLOBAL (ENGINE EXECUTION) + +INÍCIO + +PARA cada GRUPO_DE_ANUNCIO (total = 3): + + PASSO 1 → Gerar Keywords + PASSO 2 → Normalizar Keywords (ENGINE NORMALIZE) + PASSO 3 → Selecionar Keywords Literais (COMMERCIAL PRIORITY) + PASSO 4 → Gerar Títulos Literais (HARD LOCK) + PASSO 5 → Gerar Títulos CTA/Local + PASSO 6 → Gerar Títulos Prova Social + PASSO 7 → Validar Limite de Caracteres + PASSO 8 → Gerar Descrições + PASSO 9 → Validar RANGE ESTRITO + PASSO 10 → Inserir Localização + +FIM + +Depois: + PASSO 11 → Negativas + PASSO 12 → Extensões + PASSO 13 → HARD LOCK VALIDATION + +RETORNAR apenas JSON estruturado. + +--- + +# 1. PALAVRAS-CHAVE (ENGINE STEP) + +GERAR exatamente 7 keywords por grupo. + +REGRAS: +- usar [Exata] e "Frase" +- máximo 25 caracteres +- garantir mínimo 5 keywords utilizáveis como título literal + +### ENGINE NORMALIZE (MATCH BASE) + +FUNÇÃO normalizar(keyword): + + remover "[" "]" e '"' + aplicar Capitalização Inicial + manter acentuação + manter ordem original + NÃO alterar semântica + +RETORNAR keyword_normalizada + +HARD LOCK TOKEN: + + literal_token = hash(keyword_normalizada) + +--- + +# 3. TÍTULOS — ENGINE HARD LOCK + +## 🆕 PRIORIDADE COMERCIAL (NOVO) + +ANTES de selecionar títulos literais: + +Identificar keywords com intenção direta: + +- valor +- custo +- preço +- regularização +- revisão +- cálculo +- quanto custa + +SE existirem: + + essas keywords DEVEM entrar primeiro nas 5 literais obrigatórias. + +Somente após preencher prioridades comerciais, +completar restante das literais. + +--- + +## PASSO A — TÍTULOS LITERAIS (MATCH BLOQUEADO) + +VAR lista_literais = selecionar_keywords_prioritarias() + +PARA cada keyword: + + titulo = normalizar(keyword) + token_titulo = hash(titulo) + + SE token_titulo != literal_token: + BLOQUEAR alteração + REGERAR + + SE tamanho(titulo) > 30: + substituir keyword + + adicionar titulo + +PERMITIDO: +- Capitalização inicial + +PROIBIDO: +- adicionar SP +- alterar palavras +- alterar ordem +- reescrever semântica + +--- + +## PASSO B — TÍTULOS CTA/LOCAL +- incluir SP ou São Paulo quando possível +- máximo 30 caracteres + +## PASSO C — TÍTULOS PROVA SOCIAL +- usar autoridade da LP +- máximo 30 caracteres + +TOTAL: +15 títulos por grupo. + +--- + +# 4. DESCRIÇÕES — ENGINE HARD LOCK + +### RANGE DEFINITIVO +- mínimo absoluto: 80 +- alvo ideal: 85–90 +- máximo: 90 + +FUNÇÃO validar_descricao(texto): + +🆕 ENGINE HARD LOCK — BLOQUEIO DE SUBSTRING + +É PROIBIDO usar: + +texto[:limite] +substring +slice +corte parcial + +SE tamanho(texto) > 90: + + REESCREVER FRASE COMPLETA + NUNCA cortar caracteres finais + +SE detectar palavra incompleta: + + REGERAR descrição automaticamente + + SE tamanho < 80: + expandir automaticamente + + SE tamanho entre 80 e 84: + expandir até ≥85 + +🆕 ANTI-TRUNCAMENTO FINAL +- descrição deve terminar com "." ou "!" +- nunca finalizar com palavra incompleta +- nunca terminar com espaço vazio + +CONTEÚDO: +- incluir UVP +- incluir CTA +- incluir SP ou São Paulo quando possível + +--- + +# 5. EXTENSÕES (ATIVOS — NÍVEL CAMPANHA) + +SITELINKS: +- 4 obrigatórios +- título ≤25 caracteres +- 2 descrições ≤35 caracteres + +CALLOUTS: +- 7 obrigatórios +- ≤25 caracteres +- incluir 01 diferencial competitivo único + +--- + +# 6. POLÍTICAS EDITORIAIS + +PROIBIDO: +- "Grátis" +- "Gratuito" +- caixa alta total (exceto SP, OAB) +- pontuação excessiva + +PERMITIDO: +- Capitalização Inicial em títulos literais. + +--- + +# 7. HARD LOCK VALIDATION + +ANTES DE ENTREGAR: + +VALIDAR: + +- existem 3 grupos? +- existem 15 títulos por grupo? +- existem 05 títulos literais? +- keywords comerciais estão dentro das literais? +- hash(titulo_literal) == literal_token ? +- nenhum título > 30 caracteres? +- descrições entre 80 e 90 caracteres? +- nenhuma substring detectada? +- existe SP/São Paulo fora das literais? +- existem 7 callouts? + +SE falhar: + + REEXECUTAR bloco inválido + +--- + +RETORNO FINAL: +APENAS JSON estruturado. +SEM explicações. +SEM texto adicional. diff --git a/regras_prompt_old.md b/regras_prompt_old.md new file mode 100644 index 0000000..3198acb --- /dev/null +++ b/regras_prompt_old.md @@ -0,0 +1,35 @@ +# REGRAS CONSOLIDADAS PARA GOOGLE ADS (V4 — Multi-Grupo) + +## 7. ESTRUTURA DA CAMPANHA (3 Grupos de Anúncio) +A campanha DEVE ser dividida em exatamente 3 Grupos de Anúncio (Ad Groups) temáticos. +Analise o conteúdo da Landing Page e identifique 3 ângulos/temas distintos para segmentar a campanha (ex: por serviço, por dor do cliente, por diferencial competitivo). +Cada grupo deve ter um nome descritivo e conter seus próprios Keywords, Headlines e Descriptions (Regras 1, 3 e 4 aplicam-se POR GRUPO). +As Negativas, Sitelinks e Callouts são compartilhados no nível da campanha (Regras 2, 5). + +## 1. PALAVRAS-CHAVE (Fundo de Funil) — POR GRUPO +Gere 7 termos por grupo (total ~21) com alta intenção de contratação. Use [Exata] e "Frase". Limite cada termo a no máximo 25 caracteres para viabilizar o uso nos títulos. + +## 2. NEGATIVAS (Nível de Campanha) +Gere 20 termos para filtrar curiosos e buscas por gratuidade (ex: pdf, curso, modelo, jurisprudência, tcc, concurso, como fazer, gratuito). + +Gere 15 títulos por grupo (máx. 30 caracteres). Atenção à distribuição: + +## 3. TÍTULOS (Headlines) - Regra Mista e Local — POR GRUPO +Gere 15 títulos por grupo (máx. 30 caracteres cada) seguindo esta distribuição: +- 05 Títulos de Palavra-chave: Devem conter obrigatoriamente a palavra-chave da Regra 1 de forma INTEGRAL e IDÊNTICA. +- 05 Títulos de CTA/Local: Focados em Chamadas para Ação e Relevância Local (ex: "Fale Conosco em SP", "Agende sua Consulta Hoje"). +- 05 Títulos de Prova Social/Diferencial: Utilize dados da LP (ex: "+10 anos de experiência", "Avaliação 5.0 no Google", "Atendimento 100% Online"). + +## 4. DESCRIÇÕES (Maximização de Espaço) — POR GRUPO +Gere 4 descrições por grupo. Regra de Ouro: Tente utilizar o mais próximo possível dos 90 caracteres (mínimo 80) para ocupar mais espaço visual. Devem terminar obrigatoriamente com um ponto final ou exclamação e incluir a Proposta Única de Valor (UVP). + +## 5. EXTENSÕES (Ativos — Nível de Campanha) +- 4 Sitelinks: Título (até 25ch) e 2 linhas de descrição (até 35ch cada). +- 6 Callouts: Frases de destaque (máx. 25ch) focadas em autoridade e agilidade. + +## 6. POLÍTICAS EDITORIAIS E PROIBIÇÕES +- Inviolável: NUNCA utilize "Grátis", "Gratuito" ou sinônimos. +- Formatação: Proibido CAIXA ALTA (exceto siglas como SP, OAB). Proibido pontuação excessiva (ex: !!!). +- Estilo: Use tom profissional, empático e focado em solução. + +RETORNO: APENAS o JSON estruturado, sem texto adicional. diff --git a/regras_prompt_v12.4-v12_5.md b/regras_prompt_v12.4-v12_5.md new file mode 100644 index 0000000..dbc7072 --- /dev/null +++ b/regras_prompt_v12.4-v12_5.md @@ -0,0 +1,215 @@ +# GOOGLE ADS --- ENGINE HARD MATCH V12.5 (VERSÃO CONSOLIDADA) + +Atualizado em: 14/02/2026 + +------------------------------------------------------------------------ + +## 🎯 OBJETIVO + +Estrutura definitiva seguindo: + +- HARD MATCH 7/7 +- DKI SAFE MODE +- RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) +- GAS v6 --- ENGINE AUDITOR HARD MATCH + +Sem truncamentos, sem mutações de espelho e com validação estrutural +final. + +------------------------------------------------------------------------ + +# 📦 CAMPANHA GOOGLE ADS --- ESTRUTURA FINAL + +------------------------------------------------------------------------ + +## 🔵 GRUPO 1 --- DIVÓRCIO E SEPARAÇÃO + +### Keywords + +- \[divórcio online\] +- "advogado divórcio" +- \[separação judicial\] +- "divórcio consensual" +- \[divórcio litigioso\] +- "fim do casamento" +- \[custo divórcio\] + +### Headlines Espelho (HARD MATCH) + +Divórcio Online\ +Advogado Divórcio\ +Separação Judicial\ +Divórcio Consensual\ +Divórcio Litigioso\ +Fim do Casamento\ +Custo Divórcio + +### Headlines Finais (15) + +Divórcio Online\ +Advogado Divórcio\ +Separação Judicial\ +Divórcio Consensual\ +Divórcio Litigioso\ +Fale com um Especialista\ +Atendimento Online em SP\ +Solução Rápida e Segura\ +Fim do Casamento\ +{Keyword:Divórcio em SP}\ +Sigilo e Discrição Total\ +Estratégia Clara e Direta\ +Acompanhamento do Início\ +Menos Conflito, Mais Paz\ +Custo Divórcio + +### Descrições + +Divórcio online com orientação clara e sigilo. Resolva com agilidade e +segurança. Fale agora!\ +Advogado de família para divórcio consensual ou litigioso. Atendimento +online e discreto. Consulte!\ +Separação judicial com estratégia e rapidez. Proteja seus direitos e +evite desgaste. Agende uma análise! + +------------------------------------------------------------------------ + +## 🟢 GRUPO 2 --- PENSÃO ALIMENTÍCIA E GUARDA + +### Keywords + +- \[pensão alimentícia\] +- "advogado guarda filhos" +- \[revisão pensão\] +- "guarda compartilhada" +- \[ação de alimentos\] +- "fim da pensão" +- \[valor pensão\] + +### Headlines Espelho (HARD MATCH) + +Pensão Alimentícia\ +Advogado Guarda Filhos\ +Revisão Pensão\ +Guarda Compartilhada\ +Ação de Alimentos\ +Fim da Pensão\ +Valor Pensão + +### Headlines Finais (15) + +Pensão Alimentícia\ +Advogado Guarda Filhos\ +Revisão Pensão\ +Guarda Compartilhada\ +Ação de Alimentos\ +Defina a Guarda com Calma\ +Atendimento Online em SP\ +Fale com um Especialista\ +Fim da Pensão\ +{Keyword:Pensão em SP}\ +Soluções Justas e Claras\ +Foco no Bem-Estar\ +Sigilo e Orientação Total\ +Acompanhamento Completo\ +Valor Pensão + +### Descrições + +Defina guarda e pensão com equilíbrio e segurança. Atendimento online +com foco no bem-estar. Consulte!\ +Revisão de pensão e ação de alimentos com orientação objetiva. Evite +conflitos e ganhe clareza. Fale!\ +Guarda compartilhada com apoio jurídico completo. Proteja seus filhos e +seus direitos. Agende avaliação! + +------------------------------------------------------------------------ + +## 🟣 GRUPO 3 --- INVENTÁRIO E UNIÃO ESTÁVEL + +### Keywords + +- \[inventário judicial\] +- "advogado inventário" +- \[união estável\] +- "dissolução união estável" +- \[inventário extrajudicial\] +- "herança e bens" +- \[custo inventário\] + +### Headlines Espelho (HARD MATCH) + +Inventário Judicial\ +Advogado Inventário\ +União Estável\ +Dissolução União Estável\ +Inventário Extrajudicial\ +Herança e Bens\ +Custo Inventário + +### Headlines Finais (15) + +Inventário Judicial\ +Advogado Inventário\ +União Estável\ +Dissolução União Estável\ +Inventário Extrajudicial\ +Regularize com Segurança\ +Atendimento Online em SP\ +Agilidade na Partilha\ +Herança e Bens\ +{Keyword:Inventário em SP}\ +Partilha Justa e Rápida\ +Documentação Sem Estresse\ +Orientação do Início ao Fim\ +Segurança Jurídica Total\ +Custo Inventário + +### Descrições + +Inventário judicial ou extrajudicial com orientação clara. Partilha +segura e ágil. Fale com especialista!\ +União estável e dissolução com segurança jurídica. Atendimento online e +acompanhamento completo. Consulte!\ +Herança e bens com estratégia e transparência. Resolva o inventário sem +desgaste. Agende uma análise! + +------------------------------------------------------------------------ + +# 🔗 SITELINKS + +Divórcio e Separação\ +- Soluções rápidas e seguras.\ +- Atendimento online e sigiloso. + +Pensão e Guarda\ +- Foco no bem-estar dos filhos.\ +- Defina guarda e pensão com clareza. + +Inventário e União\ +- Partilha justa e ágil.\ +- Regularize sua união com segurança. + +Fale com Especialista\ +- Análise inicial e orientação clara.\ +- Converse online com um advogado. + +------------------------------------------------------------------------ + +# 📢 CALLOUTS + +Atendimento 100% Online\ +Sigilo Garantido\ +Resposta Rápida\ +Especialistas em Família\ +Estratégia Clara\ +Acompanhamento Completo\ +Foco em Solução Justa + +------------------------------------------------------------------------ + +# ✅ STATUS + +PASS_RECONCILIATION\ +HARD MATCH 7/7 ATIVO\ +ANTI TRUNCAMENTO VALIDADO\ +COMPATÍVEL COM GAS v6 diff --git a/regras_prompt_v12_5.md b/regras_prompt_v12_5.md new file mode 100644 index 0000000..3700d43 --- /dev/null +++ b/regras_prompt_v12_5.md @@ -0,0 +1,185 @@ +# REGRAS CONSOLIDADAS --- Google Ads + +## V12.5 --- RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) + +OBJETIVO: Adicionar uma camada determinística de Reconciliação ao +pipeline V12.4 (HARD MATCH + DKI SAFE MODE), garantindo integridade +total entre o estado criado pelo ENGINE e o estado final entregue. + +Compatível com: - HARD MATCH - DKI SAFE MODE - GAS v6 --- ENGINE AUDITOR +HARD MATCH + +------------------------------------------------------------------------ + +# 0. PRINCÍPIO + +A Reconciliação NÃO substitui auditoria. Ela valida consistência entre +fases. + +ENGINE → cria HARD LOCK → valida regras RECONCILIATION → valida +integridade estrutural AUDITORIA → valida qualidade final + +------------------------------------------------------------------------ + +# 1. FLUXO GLOBAL ATUALIZADO + +PASSO 1 → Keywords PASSO 2 → Canonização PASSO 3 → Normalização PASSO 4 +→ Headlines Espelho PASSO 4.2 → DKI SAFE MODE PASSO 5 → Montagem 15 +Títulos PASSO 6 → Descrições PASSO 7 → Extensões PASSO 8 → HARD LOCK +VALIDATION PASSO 12 → RECONCILIATION ENGINE (NOVO) PASSO 13 → AUDITORIA +GAS v6 (Opcional) + +------------------------------------------------------------------------ + +# 2. SNAPSHOTS OBRIGATÓRIOS (ENGINE_STATE) + +keywords_raw\[7\] keywords_canon\[7\] keywords_norm\[7\] +headlines_espelho_criados\[7\] headline_dki_criado\[1\] +headlines_final\[15\] + +bucket_map_final: - bucket1_literal\[5\] - bucket2_cta_local\[5\] - +bucket3_prova\[5\] + +descricoes_geradas\[\] descricao_final callouts_final\[7\] +sitelinks_final\[4\] + +------------------------------------------------------------------------ + +# 3. FINGERPRINTS (OBRIGATÓRIO) + +Criar hashes SHA-256: + +fp_keywords_norm fp_headlines_final_norm fp_descricao_final +fp_bucket_map + +Objetivo: Detectar alterações invisíveis após montagem. + +------------------------------------------------------------------------ + +# 4. REGRAS DE RECONCILIAÇÃO + +## R1 --- EXISTÊNCIA 7/7 (CRÍTICO) + +Cada keyword_norm deve existir como headline espelho final. + +Se faltar: DESYNC_MIRROR_MISSING + +------------------------------------------------------------------------ + +## R2 --- INTEGRIDADE DO ESPELHO (CRÍTICO) + +headline espelho deve ser idêntica após normalização. + +Proibido: - adicionar palavras - trocar ordem - criar variações + +Erro: DESYNC_MIRROR_MUTATED + +------------------------------------------------------------------------ + +## R3 --- CONTAGEM DE ESPELHOS (CRÍTICO) + +Deve haver exatamente 7 espelhos únicos. + +Erro: DESYNC_MIRROR_COUNT + +------------------------------------------------------------------------ + +## R4 --- DKI SAFE MODE (CRÍTICO) + +Regras obrigatórias: + +- existe exatamente 1 headline DKI +- formato {Keyword:PADRAO} +- DKI não substitui espelhos +- DKI não está no Bucket Literal + +Erro: DESYNC_DKI_VIOLATION + +------------------------------------------------------------------------ + +## R5 --- BUCKET LAYOUT (ALTA) + +Distribuição obrigatória: + +Bucket 1: 5 espelhos + +Bucket 2: 3 CTA/Local 1 espelho (6º) 1 DKI + +Bucket 3: 4 provas 1 espelho (7º) + +Erro: DESYNC_BUCKET_LAYOUT + +------------------------------------------------------------------------ + +## R6 --- DESCRIÇÃO ANTI-GHOST (CRÍTICO) + +Fingerprint da descrição final deve existir dentro das descrições +geradas. + +Proibido: - editar manualmente - cortar string + +Erro: DESYNC_DESCRIPTION_GHOST_EDIT + +------------------------------------------------------------------------ + +## R7 --- INTEGRIDADE FINAL DA DESCRIÇÃO (CRÍTICO) + +Revalidar: + +80 ≤ caracteres ≤ 92 termina com "." ou "!" não termina com espaço não +termina com palavra incompleta + +Erro: DESYNC_DESCRIPTION_INVALID_FINAL + +------------------------------------------------------------------------ + +## R8 --- CALLOUTS (ALTA) + +- 7 obrigatórios +- ≤25 caracteres +- sem palavra incompleta +- sem substring + +Erro: DESYNC_CALLOUT_TRUNC + +------------------------------------------------------------------------ + +## R9 --- SITELINKS (MÉDIA) + +Cada sitelink deve possuir: + +description1 description2 + +Erro: DESYNC_SITELINK_INCOMPLETE + +------------------------------------------------------------------------ + +# 5. SEVERIDADE + +CRÍTICO: bloquear saída do grupo reexecutar bloco inválido + +ALTA: reexecutar etapa específica + +MÉDIA: warning + +------------------------------------------------------------------------ + +# 6. REEXECUÇÃO AUTOMÁTICA + +DESYNC_MIRROR\_\* → Passo 4.1 / 5 DESYNC_DKI_VIOLATION → Passo 4.2 / 5 +DESYNC_BUCKET_LAYOUT → Passo 5 DESYNC_DESCRIPTION\_\* → Passo 6 +DESYNC_CALLOUT_TRUNC → Passo 7 DESYNC_SITELINK_INCOMPLETE → Passo 7 + +------------------------------------------------------------------------ + +# 7. STATUS FINAL + +PASS_RECONCILIATION FAIL_RECONCILIATION + +------------------------------------------------------------------------ + +# RESULTADO + +A Reconciliação elimina falsos negativos estruturais, garantindo que o +pipeline HARD MATCH + DKI SAFE MODE permaneça determinístico do início +ao fim. diff --git a/regras_prompt_v12_6_1.md b/regras_prompt_v12_6_1.md new file mode 100644 index 0000000..d2d75ef --- /dev/null +++ b/regras_prompt_v12_6_1.md @@ -0,0 +1,224 @@ +# REGRAS CONSOLIDADAS --- Google Ads + +## V12.6.1 --- UNIFIED ENGINE CORE COMPACT + +(HARD MATCH + DKI SAFE MODE + RECONCILIATION ENGINE) + +OBJETIVO: Versão compacta e determinística do ENGINE CORE. Unifica +criação, montagem, validação estrutural e reconciliação +anti-falso-negativo em um único fluxo otimizado. + +Compatível com: - HARD MATCH 7/7 - DKI SAFE MODE - Anti-Truncamento - +Reconciliação Estrutural + +Auditoria (GAS v6) permanece EXTERNA. + +------------------------------------------------------------------------ + +# 0. PRINCÍPIOS DO ENGINE + +- Execução determinística +- Espelhamento obrigatório +- DKI controlado +- Zero substring +- Reconciliação integrada + +------------------------------------------------------------------------ + +# 1. FLUXO GLOBAL + +PASSO 1 → Gerar Keywords (7 por grupo) PASSO 2 → Canonizar Keywords +PASSO 3 → Normalizar Keywords PASSO 4 → Classificar Prioridade PASSO 5 → +Gerar Headlines Espelho (7/7) PASSO 6 → Gerar 01 DKI SAFE MODE PASSO 7 → +Montar 15 Títulos (05/05/05) PASSO 8 → Gerar Descrições (NO TRUNC +ENGINE) PASSO 9 → Gerar Extensões PASSO 10 → HARD LOCK VALIDATION PASSO +11 → RECONCILIATION ENGINE + +RETORNAR apenas JSON estruturado. + +------------------------------------------------------------------------ + +# 2. CANONIZAÇÃO + +FUNÇÃO canonizar(keyword): + +- remover duplicação de delimitadores +- transformar em forma jurídica titulável +- manter acentuação +- manter ordem semântica + +Keyword inválida → DESCARTAR. + +------------------------------------------------------------------------ + +# 3. NORMALIZAÇÃO + +FUNÇÃO normalizar(texto): + +- remover "\[" "\]" e '"' +- remover espaços duplicados +- permitir Capitalização Inicial +- manter acentuação +- manter ordem + +Uso exclusivo para comparação. + +------------------------------------------------------------------------ + +# 4. PRIORIDADE + +PRIORIDADE 1: valor, custo, preço, revisão, cálculo + +PRIORIDADE 2: advogado, ação, processo, consulta + +PRIORIDADE 3: demais termos + +Ordenação: + +prioridade ASC tamanho ASC ordem original + +------------------------------------------------------------------------ + +# 5. HARD MATCH --- ESPELHOS + +- 7 keywords → 7 headlines espelho obrigatórios +- headline = normalizar(canonizar(keyword)) + +PROIBIDO: + +- adicionar palavras +- alterar ordem +- sinônimos +- pluralização + +Se keyword não contém SP → espelho não pode conter SP. + +------------------------------------------------------------------------ + +# 6. DKI SAFE MODE + +Formato obrigatório: + +{Keyword:PADRAO_DO_GRUPO} + +REGRAS: + +- exatamente 1 DKI +- ≤30 caracteres +- não substitui espelhos +- não entra no Bucket Literal +- sem termos proibidos + +------------------------------------------------------------------------ + +# 7. TÍTULOS --- MONTAGEM 15 FIXOS + +Bucket 1 --- Literal (5) → 5 primeiros espelhos + +Bucket 2 --- CTA/Local (5) → 3 CTAs → 1 espelho (6º) → 1 DKI + +Bucket 3 --- Prova (5) → 4 provas → 1 espelho (7º) + +------------------------------------------------------------------------ + +# 8. DESCRIÇÕES --- NO TRUNC ENGINE + +RANGE: + +80 ≤ caracteres ≤ 92 Ideal: 85--90 + +PROCESSO: + +1) gerar descrição completa +2) validar +3) se inválida → REGERAR DO ZERO + +PROIBIDO: + +- substring +- slice +- corte parcial + +Descrição válida se: + +- termina com "." ou "!" +- não termina com espaço +- não termina com palavra incompleta + +------------------------------------------------------------------------ + +# 9. EXTENSÕES + +SITELINKS: + +- 4 obrigatórios +- título ≤25 +- description1 obrigatório +- description2 obrigatório + +CALLOUTS: + +- 7 obrigatórios +- ≤25 caracteres +- sem palavra incompleta +- reescrever se exceder (nunca cortar) + +------------------------------------------------------------------------ + +# 10. HARD LOCK VALIDATION + +VALIDAR: + +- 3 grupos +- 7 keywords por grupo +- 7 espelhos presentes +- 15 títulos totais +- DKI único +- buckets 05/05/05 +- descrições válidas +- callouts completos + +Falha → REEXECUTAR BLOCO. + +------------------------------------------------------------------------ + +# 11. RECONCILIATION ENGINE (ANTI FALSE NEGATIVE) + +R1 --- EXISTÊNCIA 7/7 (CRÍTICO) keyword_norm deve existir nos títulos +finais. Erro: DESYNC_MIRROR_MISSING + +R2 --- INTEGRIDADE DO ESPELHO (CRÍTICO) headline espelho idêntica após +normalização. Erro: DESYNC_MIRROR_MUTATED + +R3 --- CONTAGEM (CRÍTICO) exatamente 7 espelhos únicos. Erro: +DESYNC_MIRROR_COUNT + +R4 --- DKI SAFE MODE (CRÍTICO) 1 DKI, fora do Bucket Literal. Erro: +DESYNC_DKI_VIOLATION + +R5 --- BUCKET LAYOUT (ALTA) 05/05/05 obrigatório. Erro: +DESYNC_BUCKET_LAYOUT + +R6 --- DESCRIÇÃO ANTI-GHOST (CRÍTICO) descrição final deve ter sido +gerada pelo engine. Erro: DESYNC_DESCRIPTION_GHOST_EDIT + +R7 --- INTEGRIDADE FINAL (CRÍTICO) validar novamente range e +finalização. Erro: DESYNC_DESCRIPTION_INVALID_FINAL + +R8 --- CALLOUTS (ALTA) 7 callouts completos. Erro: DESYNC_CALLOUT_TRUNC + +R9 --- SITELINKS (MÉDIA) description1 e description2 obrigatórios. Erro: +DESYNC_SITELINK_INCOMPLETE + +------------------------------------------------------------------------ + +# 12. SEVERIDADE + +CRÍTICO → bloquear saída ALTA → reexecutar etapa MÉDIA → warning + +------------------------------------------------------------------------ + +# RESULTADO + +ENGINE CORE compacto, sem duplicação de regras, com HARD MATCH + DKI +SAFE + RECONCILIATION totalmente integrados. diff --git a/src/Regras_old.md b/src/Regras_old.md new file mode 100644 index 0000000..a1e7518 --- /dev/null +++ b/src/Regras_old.md @@ -0,0 +1,20 @@ +REGRAS CONSOLIDADAS PARA GOOGLE ADS (V3) +1. PALAVRAS-CHAVE (Fundo de Funil): +Gere 20 termos com alta intenção de contratação. Use [Exata] e "Frase". Limite cada termo a no máximo 25 caracteres para viabilizar o uso nos títulos. +2. NEGATIVAS: +Gere 20 termos para filtrar curiosos e buscas por gratuidade (ex: pdf, curso, modelo, jurisprudência, tcc, concurso, como fazer, gratuito). +3. TÍTULOS (Headlines) - Regra Mista e Local: +Gere 15 títulos (máx. 30 caracteres cada) seguindo esta distribuição: +05 Títulos de Palavra-chave: Devem conter obrigatoriamente a palavra-chave da Regra 1 de forma INTEGRAL e IDÊNTICA. +05 Títulos de CTA/Local: Focados em Chamadas para Ação e Relevância Local (ex: "Fale Conosco em SP", "Agende sua Consulta Hoje"). +05 Títulos de Prova Social/Diferencial: Utilize dados da LP (ex: "+10 anos de experiência", "Avaliação 5.0 no Google", "Atendimento 100% Online"). +4. DESCRIÇÕES (Maximização de Espaço): +Gere 4 descrições. Regra de Ouro: Tente utilizar o mais próximo possível dos 90 caracteres (mínimo 80) para ocupar mais espaço visual. Devem terminar obrigatoriamente com um ponto final ou exclamação e incluir a Proposta Única de Valor (UVP). +5. EXTENSÕES (Ativos): +4 Sitelinks: Título (até 25ch) e 2 linhas de descrição (até 35ch cada). +6 Callouts: Frases de destaque (máx. 25ch) focadas em autoridade e agilidade. +6. POLÍTICAS EDITORIAIS E PROIBIÇÕES: +Inviolável: NUNCA utilize "Grátis", "Gratuito" ou sinônimos. +Formatação: Proibido CAIXA ALTA (exceto siglas como SP, OAB). Proibido pontuação excessiva (ex: !!!). +Estilo: Use tom profissional, empático e focado em solução. +RETORNO: APENAS o JSON estruturado, sem texto adicional.""" \ No newline at end of file diff --git a/src/__pycache__/ai_generator.cpython-310.pyc b/src/__pycache__/ai_generator.cpython-310.pyc index b9bb40c..b56b99d 100644 Binary files a/src/__pycache__/ai_generator.cpython-310.pyc and b/src/__pycache__/ai_generator.cpython-310.pyc differ diff --git a/src/__pycache__/auditor.cpython-310.pyc b/src/__pycache__/auditor.cpython-310.pyc new file mode 100644 index 0000000..4cf79ce Binary files /dev/null and b/src/__pycache__/auditor.cpython-310.pyc differ diff --git a/src/__pycache__/exporter.cpython-310.pyc b/src/__pycache__/exporter.cpython-310.pyc index 351e60c..5b63f9c 100644 Binary files a/src/__pycache__/exporter.cpython-310.pyc and b/src/__pycache__/exporter.cpython-310.pyc differ diff --git a/src/ai_generator.py b/src/ai_generator.py index b957d24..e7efac1 100644 --- a/src/ai_generator.py +++ b/src/ai_generator.py @@ -10,9 +10,13 @@ import json import os import time import re +from pathlib import Path from openai import OpenAI from google import genai +# ─── Caminho do arquivo de regras ───────────────────────────────── +_RULES_FILE = Path(__file__).resolve().parent.parent / "regras_prompt.md" + # ─── Modelos disponíveis por provider ───────────────────────────── MODELS = { @@ -78,7 +82,7 @@ def _call_openai(system_prompt: str, user_prompt: str, model: str) -> str: {"role": "user", "content": user_prompt}, ], temperature=0.7, - max_tokens=4000, + max_tokens=8000, ) return response.choices[0].message.content.strip() @@ -103,7 +107,7 @@ def _call_gemini(system_prompt: str, user_prompt: str, model: str, max_retries: config=genai.types.GenerateContentConfig( system_instruction=system_prompt, temperature=0.7, - max_output_tokens=4000, + max_output_tokens=8000, ), ) return response.text.strip() @@ -150,9 +154,21 @@ def _build_system_prompt() -> str: ) +def _load_rules() -> str: + """Carrega as regras do arquivo externo regras_prompt.md.""" + if _RULES_FILE.exists(): + return _RULES_FILE.read_text(encoding="utf-8") + raise FileNotFoundError(f"Arquivo de regras não encontrado: {_RULES_FILE}") + + def _build_user_prompt(lp_content: str) -> str: """Constrói o prompt do usuário para geração de ativos.""" + rules = _load_rules() + return f"""Com base no conteúdo desta Landing Page, gere ativos completos para uma campanha de Google Ads. +A campanha DEVE conter exatamente 3 Grupos de Anúncio (Ad Groups) temáticos. +Analise a LP e identifique 3 ângulos/temas distintos. Cada grupo deve ter seus próprios keywords, headlines e descriptions. +As negativas, sitelinks e callouts são compartilhados no nível da campanha. === CONTEÚDO DA LANDING PAGE === {lp_content} @@ -161,20 +177,49 @@ def _build_user_prompt(lp_content: str) -> str: Gere o seguinte em formato JSON: {{ - "keywords": [ - {{"keyword": "exemplo de palavra-chave", "match_type": "Exact"}}, - {{"keyword": "outro exemplo", "match_type": "Phrase"}} + "ad_groups": [ + {{ + "name": "Nome descritivo do tema do grupo 1", + "keywords": [ + {{"keyword": "exemplo de palavra-chave", "match_type": "Exact"}}, + {{"keyword": "outro exemplo", "match_type": "Phrase"}} + ], + "headlines": [ + "Título com até 30 caracteres" + ], + "descriptions": [ + "Descrição com até 90 caracteres que destaca benefícios e inclui CTA" + ] + }}, + {{ + "name": "Nome descritivo do tema do grupo 2", + "keywords": [ + {{"keyword": "palavra-chave do grupo 2", "match_type": "Exact"}} + ], + "headlines": [ + "Título do grupo 2" + ], + "descriptions": [ + "Descrição do grupo 2 com CTA e benefícios" + ] + }}, + {{ + "name": "Nome descritivo do tema do grupo 3", + "keywords": [ + {{"keyword": "palavra-chave do grupo 3", "match_type": "Phrase"}} + ], + "headlines": [ + "Título do grupo 3" + ], + "descriptions": [ + "Descrição do grupo 3 com CTA e benefícios" + ] + }} ], "negative_keywords": [ {{"keyword": "grátis", "reason": "Atrai tráfego que não converte"}}, {{"keyword": "como fazer", "reason": "Topo de funil, não converte"}} ], - "headlines": [ - "Título com até 30 caracteres" - ], - "descriptions": [ - "Descrição com até 90 caracteres que destaca benefícios e inclui CTA" - ], "sitelinks": [ {{"title": "Título do Sitelink", "description1": "Linha 1", "description2": "Linha 2"}} ], @@ -183,44 +228,56 @@ Gere o seguinte em formato JSON: ] }} -REGRAS OTIMIZADAS PARA GOOGLE ADS: - -1. PALAVRAS-CHAVE (Fundo de Funil): Gere 20 termos com alta intenção de contratação. Use [Exata] e "Frase". Limite os termos a no máximo 25 caracteres para viabilizar o uso nos títulos. -2. NEGATIVAS: Gere 20 termos que filtrem estudantes, curiosos e buscas gratuitas (ex: pdf, curso, modelo, jurisprudência, tcc). -3. TÍTULOS (Headlines): Gere 15 títulos (máx. 30 caracteres). Regra de Ouro: A palavra-chave da Regra 1 deve aparecer de forma INTEGRAL e IDENTICA no título. Se a keyword for longa, o título será apenas ela. -4. DESCRIÇÕES: Gere 4 descrições (máx. 90 caracteres). Devem terminar obrigatoriamente com um ponto final ou exclamação. Inclua uma Proposta Única de Valor (UVP). -5. EXTENSÕES (Sitelinks & Callouts): - - 4 Sitelinks (Título 25ch / Desc 35ch). - - 6 Callouts (Frases de destaque, máx 25ch) focadas em autoridade e agilidade. -6. POLÍTICAS EDITORIAIS: - - Proibido: "Grátis" e sinônimos. - - Proibido: CAIXA ALTA em palavras inteiras (exceto siglas como SP, OAB). - - Proibido: Uso excessivo de pontuação (ex: !!!). - -RETORNO: Apenas o JSON estruturado.""" +{rules}""" def _validate_and_normalize(data: dict) -> dict: - """Valida e normaliza os dados retornados pela IA.""" + """Valida e normaliza os dados retornados pela IA (estrutura multi-grupo).""" result = { - "keywords": [], + "ad_groups": [], "negative_keywords": [], - "headlines": [], - "descriptions": [], "sitelinks": [], "callouts": [], } - # Keywords - for kw in data.get("keywords", []): - if isinstance(kw, dict) and "keyword" in kw: - result["keywords"].append({ - "keyword": kw["keyword"], - "match_type": kw.get("match_type", "Phrase"), - }) - elif isinstance(kw, str): - result["keywords"].append({"keyword": kw, "match_type": "Phrase"}) + # Ad Groups — cada grupo contém keywords, headlines e descriptions + for group in data.get("ad_groups", []): + if not isinstance(group, dict): + continue - # Negative Keywords + normalized_group = { + "name": group.get("name", "Grupo sem nome"), + "keywords": [], + "headlines": [], + "descriptions": [], + } + + # Keywords do grupo + for kw in group.get("keywords", []): + if isinstance(kw, dict) and "keyword" in kw: + normalized_group["keywords"].append({ + "keyword": kw["keyword"], + "match_type": kw.get("match_type", "Phrase"), + }) + elif isinstance(kw, str): + normalized_group["keywords"].append({"keyword": kw, "match_type": "Phrase"}) + + # Headlines do grupo - garantir limite de 30 chars + for h in group.get("headlines", []): + if isinstance(h, str) and len(h) <= 30: + normalized_group["headlines"].append(h) + elif isinstance(h, str): + normalized_group["headlines"].append(h[:30]) + + # Descriptions do grupo - garantir limite de 90 chars + for d in group.get("descriptions", []): + if isinstance(d, str) and len(d) <= 90: + normalized_group["descriptions"].append(d) + elif isinstance(d, str): + normalized_group["descriptions"].append(d[:90]) + + result["ad_groups"].append(normalized_group) + + # Negative Keywords (nível de campanha) for nkw in data.get("negative_keywords", []): if isinstance(nkw, dict) and "keyword" in nkw: result["negative_keywords"].append({ @@ -230,21 +287,7 @@ def _validate_and_normalize(data: dict) -> dict: elif isinstance(nkw, str): result["negative_keywords"].append({"keyword": nkw, "reason": ""}) - # Headlines - garantir limite de 30 chars - for h in data.get("headlines", []): - if isinstance(h, str) and len(h) <= 30: - result["headlines"].append(h) - elif isinstance(h, str): - result["headlines"].append(h[:30]) - - # Descriptions - garantir limite de 90 chars - for d in data.get("descriptions", []): - if isinstance(d, str) and len(d) <= 90: - result["descriptions"].append(d) - elif isinstance(d, str): - result["descriptions"].append(d[:90]) - - # Sitelinks + # Sitelinks (nível de campanha) for sl in data.get("sitelinks", []): if isinstance(sl, dict) and "title" in sl: result["sitelinks"].append({ @@ -253,7 +296,7 @@ def _validate_and_normalize(data: dict) -> dict: "description2": sl.get("description2", "")[:35], }) - # Callouts + # Callouts (nível de campanha) for c in data.get("callouts", []): if isinstance(c, str) and len(c) <= 25: result["callouts"].append(c) diff --git a/src/auditor.py b/src/auditor.py new file mode 100644 index 0000000..503ccb8 --- /dev/null +++ b/src/auditor.py @@ -0,0 +1,134 @@ +""" +Módulo de Auditoria - Google Ads Scorecard (GAS). + +Recebe o JSON de uma campanha gerada e avalia a qualidade dos ativos +com base em critérios ponderados (Keyword Match, Compliance, Espaço, +Gatilhos e Ativos). Também gera uma simulação visual do anúncio (CTR). +Suporta múltiplos providers de IA (OpenAI e Google Gemini). +""" + +import json +from pathlib import Path +from src.ai_generator import _call_openai, _call_gemini, _clean_json_response, MODELS + +# ─── Caminho do arquivo de regras de auditoria ──────────────────── +_RULES_FILE = Path(__file__).resolve().parent.parent / "regras_auditoria.md" + + +# ─── Prompt de auditoria ────────────────────────────────────────── + +_AUDIT_SYSTEM_PROMPT = ( + "Você é um Auditor Sênior de Google Ads com mais de 15 anos de experiência. " + "Seu papel é analisar JSONs de campanhas e atribuir uma nota rigorosa de 0 a 10, " + "avaliando cada critério individualmente. Seja específico nas falhas e nas " + "sugestões de correção. Responda SEMPRE em formato JSON válido, " + "sem markdown, sem blocos de código. Apenas o JSON puro." +) + + +def _load_rules() -> str: + """Carrega as regras de auditoria do arquivo externo regras_auditoria.md.""" + if _RULES_FILE.exists(): + return _RULES_FILE.read_text(encoding="utf-8") + raise FileNotFoundError(f"Arquivo de regras de auditoria não encontrado: {_RULES_FILE}") + + +def _build_audit_user_prompt(campaign_json: str) -> str: + """Constrói o prompt do usuário para auditoria da campanha (multi-grupo).""" + rules = _load_rules() + + return f"""Aja como Auditor de Google Ads. Analise o JSON de campanha abaixo e gere uma avaliação completa. +A campanha possui estrutura multi-grupo (ad_groups). Avalie TODOS os grupos e gere uma nota consolidada. + +=== JSON DA CAMPANHA === +{campaign_json} +=== FIM DO JSON === + +{rules}""" + + +def audit_campaign( + campaign_json: str, + provider: str = "OpenAI", + model: str = "gpt-4o-mini", +) -> tuple[dict, dict]: + """ + Audita uma campanha Google Ads a partir do JSON dos ativos. + + Args: + campaign_json: JSON string da campanha a ser auditada. + provider: Provider de IA ("OpenAI" ou "Gemini"). + model: Modelo a ser usado (deve ser compatível com o provider). + + Returns: + Tupla com: + - audit_result: Dicionário com nota_final, criterios, resumo, simulacao. + - prompts: Dicionário com system_prompt e user_prompt usados. + """ + user_prompt = _build_audit_user_prompt(campaign_json) + + prompts = { + "system_prompt": _AUDIT_SYSTEM_PROMPT, + "user_prompt": user_prompt, + } + + if provider == "OpenAI": + raw_response = _call_openai(_AUDIT_SYSTEM_PROMPT, user_prompt, model) + elif provider == "Gemini": + raw_response = _call_gemini(_AUDIT_SYSTEM_PROMPT, user_prompt, model) + else: + raise ValueError(f"Provider não suportado: {provider}") + + raw_response = _clean_json_response(raw_response) + result = json.loads(raw_response) + audit_result = _validate_audit_result(result) + + return audit_result, prompts + + +def _validate_audit_result(data: dict) -> dict: + """Valida e normaliza o resultado da auditoria retornado pela IA (multi-grupo).""" + result = { + "nota_final": float(data.get("nota_final", 0)), + "criterios": [], + "resumo": data.get("resumo", "Sem resumo disponível."), + "simulacoes_anuncio": [], + } + + for criterio in data.get("criterios", []): + if isinstance(criterio, dict) and "nome" in criterio: + result["criterios"].append({ + "nome": criterio.get("nome", ""), + "peso": float(criterio.get("peso", 0)), + "nota": float(criterio.get("nota", 0)), + "nota_maxima": float(criterio.get("nota_maxima", 0)), + "falhas": criterio.get("falhas", []), + "sugestoes": criterio.get("sugestoes", []), + }) + + # Simulações de anúncio — uma por grupo + for sim in data.get("simulacoes_anuncio", []): + if isinstance(sim, dict): + result["simulacoes_anuncio"].append({ + "grupo": sim.get("grupo", ""), + "titulo_linha_1": sim.get("titulo_linha_1", ""), + "titulo_linha_2": sim.get("titulo_linha_2", ""), + "titulo_linha_3": sim.get("titulo_linha_3", ""), + "url_display": sim.get("url_display", "www.exemplo.com.br"), + "descricao": sim.get("descricao", ""), + }) + + # Fallback: compatibilidade com formato antigo (simulacao_anuncio singular) + if not result["simulacoes_anuncio"] and "simulacao_anuncio" in data: + sim = data["simulacao_anuncio"] + if isinstance(sim, dict): + result["simulacoes_anuncio"].append({ + "grupo": sim.get("grupo", "Grupo único"), + "titulo_linha_1": sim.get("titulo_linha_1", ""), + "titulo_linha_2": sim.get("titulo_linha_2", ""), + "titulo_linha_3": sim.get("titulo_linha_3", ""), + "url_display": sim.get("url_display", "www.exemplo.com.br"), + "descricao": sim.get("descricao", ""), + }) + + return result diff --git a/src/exporter.py b/src/exporter.py index c6ddae4..4cb9064 100644 --- a/src/exporter.py +++ b/src/exporter.py @@ -1,36 +1,38 @@ """ Módulo de Exportação - Gera arquivos CSV compatíveis com Google Ads Editor. -Recebe os ativos gerados pela IA e os formata em DataFrames do Pandas, -prontos para importação no Google Ads Editor. +Recebe os ativos gerados pela IA (estrutura multi-grupo) e os formata +em DataFrames do Pandas, prontos para importação no Google Ads Editor. """ import pandas as pd from io import BytesIO -def create_keywords_df(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> pd.DataFrame: - """Cria DataFrame de palavras-chave no formato Google Ads Editor.""" +def create_keywords_df(assets: dict, campaign_name: str = "Campanha LP") -> pd.DataFrame: + """Cria DataFrame de palavras-chave no formato Google Ads Editor (multi-grupo).""" rows = [] - for kw in assets.get("keywords", []): - match_type = kw.get("match_type", "Phrase") - keyword = kw.get("keyword", "") + for group in assets.get("ad_groups", []): + group_name = group.get("name", "Grupo") + for kw in group.get("keywords", []): + match_type = kw.get("match_type", "Phrase") + keyword = kw.get("keyword", "") - # Formatar keyword conforme tipo de correspondência - if match_type == "Exact": - formatted_kw = f"[{keyword}]" - elif match_type == "Phrase": - formatted_kw = f'"{keyword}"' - else: - formatted_kw = keyword + # Formatar keyword conforme tipo de correspondência + if match_type == "Exact": + formatted_kw = f"[{keyword}]" + elif match_type == "Phrase": + formatted_kw = f'"{keyword}"' + else: + formatted_kw = keyword - rows.append({ - "Campaign": campaign_name, - "Ad Group": ad_group, - "Keyword": formatted_kw, - "Match Type": match_type, - "Status": "Enabled", - }) + rows.append({ + "Campaign": campaign_name, + "Ad Group": group_name, + "Keyword": formatted_kw, + "Match Type": match_type, + "Status": "Enabled", + }) return pd.DataFrame(rows) @@ -49,26 +51,31 @@ def create_negative_keywords_df(assets: dict, campaign_name: str = "Campanha LP" return pd.DataFrame(rows) -def create_ads_df(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> pd.DataFrame: - """Cria DataFrame do anúncio responsivo (RSA) no formato Google Ads Editor.""" - headlines = assets.get("headlines", []) - descriptions = assets.get("descriptions", []) +def create_ads_df(assets: dict, campaign_name: str = "Campanha LP") -> pd.DataFrame: + """Cria DataFrame dos anúncios responsivos (RSA) no formato Google Ads Editor (multi-grupo).""" + rows = [] + for group in assets.get("ad_groups", []): + group_name = group.get("name", "Grupo") + headlines = group.get("headlines", []) + descriptions = group.get("descriptions", []) - row = { - "Campaign": campaign_name, - "Ad Group": ad_group, - "Ad Type": "Responsive Search Ad", - } + row = { + "Campaign": campaign_name, + "Ad Group": group_name, + "Ad Type": "Responsive Search Ad", + } - # Preencher até 15 headlines - for i, h in enumerate(headlines[:15], start=1): - row[f"Headline {i}"] = h + # Preencher até 15 headlines + for i, h in enumerate(headlines[:15], start=1): + row[f"Headline {i}"] = h - # Preencher até 4 descrições - for i, d in enumerate(descriptions[:4], start=1): - row[f"Description {i}"] = d + # Preencher até 4 descrições + for i, d in enumerate(descriptions[:4], start=1): + row[f"Description {i}"] = d - return pd.DataFrame([row]) + rows.append(row) + + return pd.DataFrame(rows) if rows else pd.DataFrame() def create_sitelinks_df(assets: dict, campaign_name: str = "Campanha LP") -> pd.DataFrame: @@ -97,7 +104,7 @@ def create_callouts_df(assets: dict, campaign_name: str = "Campanha LP") -> pd.D return pd.DataFrame(rows) -def export_all_to_excel(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> BytesIO: +def export_all_to_excel(assets: dict, campaign_name: str = "Campanha LP") -> BytesIO: """ Exporta todos os ativos em um único arquivo Excel com múltiplas abas. @@ -107,7 +114,7 @@ def export_all_to_excel(assets: dict, campaign_name: str = "Campanha LP", ad_gro output = BytesIO() with pd.ExcelWriter(output, engine="openpyxl") as writer: - kw_df = create_keywords_df(assets, campaign_name, ad_group) + kw_df = create_keywords_df(assets, campaign_name) if not kw_df.empty: kw_df.to_excel(writer, sheet_name="Keywords", index=False) @@ -115,7 +122,7 @@ def export_all_to_excel(assets: dict, campaign_name: str = "Campanha LP", ad_gro if not nkw_df.empty: nkw_df.to_excel(writer, sheet_name="Negative Keywords", index=False) - ads_df = create_ads_df(assets, campaign_name, ad_group) + ads_df = create_ads_df(assets, campaign_name) if not ads_df.empty: ads_df.to_excel(writer, sheet_name="Ads RSA", index=False) @@ -131,7 +138,7 @@ def export_all_to_excel(assets: dict, campaign_name: str = "Campanha LP", ad_gro return output -def export_all_to_csv(assets: dict, campaign_name: str = "Campanha LP", ad_group: str = "Grupo 1") -> dict[str, str]: +def export_all_to_csv(assets: dict, campaign_name: str = "Campanha LP") -> dict[str, str]: """ Exporta todos os ativos como strings CSV separadas. @@ -140,7 +147,7 @@ def export_all_to_csv(assets: dict, campaign_name: str = "Campanha LP", ad_group """ csvs = {} - kw_df = create_keywords_df(assets, campaign_name, ad_group) + kw_df = create_keywords_df(assets, campaign_name) if not kw_df.empty: csvs["keywords"] = kw_df.to_csv(index=False) @@ -148,7 +155,7 @@ def export_all_to_csv(assets: dict, campaign_name: str = "Campanha LP", ad_group if not nkw_df.empty: csvs["negative_keywords"] = nkw_df.to_csv(index=False) - ads_df = create_ads_df(assets, campaign_name, ad_group) + ads_df = create_ads_df(assets, campaign_name) if not ads_df.empty: csvs["ads"] = ads_df.to_csv(index=False)