Adiciona ao repo os scripts e configs reais do motor que estavam fora do git,
e remove arquivos legados/abandonados.
Adicionados (30):
- scripts/docker-compose.m5.yml, openwa-compose.yml, minio-compose.yml
- scripts/openwa.env (sem secrets)
- scripts/{entrypoint,saleor-entrypoint,saleor-start}.sh
- scripts/{chatwoot-setup,compliance-check,health-check,resumo-diario}.sh
- scripts/{insert_workflow,telegram_user_login,telegram_user_monitor}.py
- scripts/requirements.txt
- scripts/traefik-conf/{traefik.yml,dynamic-config.yml}
- scripts/compliance/, scripts/calculators/, scripts/backups/
- scripts/{README.md,README-INSTALL.md,WHATSAPP-SETUP.md,.gitignore}
- .gitignore (atualizado)
Removidos (ja' em backup/untracked-lixo-2026-07-06/):
- agents/, crm/, hermes_skills_backup/, api-complive/, docs/ (legados)
- scripts/{wrapper.py,wrapper.sh}
- scripts/docker-compose.m5.yml.backup, traefik-conf/routes.yml.bak2
- scripts/saleor-media/RSA_*.{bak,old_*}
- scripts/n8n-workflows/webhook-saleor.json.bak2
- scripts/backups/20260622_0300/saleor_db.sql
- integracao_completa.sh, monitoramento_vps.sh
- ruvector.db (vazio)
Ignorados via .gitignore:
- vault/ (Obsidian KB)
- skills/ (duplicado de ~/.hermes/skills/)
- scripts/{saleor-media,traefik-acme,yt-pub-livesx,minio,n8n-workflows,skills}/
- scripts/backups/2026*_*/
407 lines
15 KiB
Python
Executable File
407 lines
15 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
compliance-checker.py — Motor Ecommerce M5 Brasil
|
|
Detecta palavras proibidas e verifica conformidade regulatória.
|
|
Usage: python3 compliance-checker.py --title "..." --description "..."
|
|
python3 compliance-checker.py --interactive
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
import unicodedata
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 1. LISTAS DE PALAVRAS PROIBIDAS
|
|
# ─────────────────────────────────────────────
|
|
|
|
PROHIBITED_ML = {
|
|
"replica", "clone de", "versão falsa", "cópia 1:1", "original fake",
|
|
"fake", "falsificado", "pirata", "genérico", "igual ao original",
|
|
"similar a", "semelhante a",
|
|
"sem nota", "notaomitida", "semnf", "não emito nota", "sem nota fiscal",
|
|
"nf free", "nota livre", "não emito", "isento de nota",
|
|
"remédio", "cura", "trata covid", "combate vírus", "cura naturalmente",
|
|
"tarja preta", "tarja vermelha", "antibiótico", "anti-inflamatório",
|
|
"analgésico", "receita médica", "com receita", "sem receita",
|
|
"combate bactérias", "elimina fungos", "trata pele",
|
|
"arma", "munição", "bala", "revólver", "pistola", "cartucho",
|
|
"espingarda", "carabina", "fuzil", "metralhadora",
|
|
"explosivo", "bomba", "dinamite", "pólvora",
|
|
"droga", "entorpecente", "haixe", "maconha", "cocaine", "crack",
|
|
"heroína", " LSD", "ecstasy", "MDMA", "substância controlada", "psicotrópico",
|
|
"lençol motel", "uso adulto", "18+", "erótico",
|
|
"animais silvestres", "fauna nativa", "ave silvestre",
|
|
"tartaruga marinha", "mico-leão", "peixe ornamental",
|
|
}
|
|
|
|
PROHIBITED_SHOPEE = {
|
|
" original", " autêntico", "genuíno", "original de verdade",
|
|
"garantia de originalidade",
|
|
"melhor que", "superior a", "mais barato que", "pior que",
|
|
"comparado a", "na frente de",
|
|
"frete grátis", "frete gratuito",
|
|
"promoção", "desconto especial", "oferta exclusiva", "liquidação",
|
|
"Deus", "Jesus", "abençoado", "salvação",
|
|
"Bolsonaro", "Lula", "comunismo", "fascismo", "ESG", "woke",
|
|
"R$ 1", "R$ 0,01", "de R$ 999 por R$ 1",
|
|
"clube do preço baixo", "grupo de revenda", "compre junto e ganhe", "kit revenda",
|
|
}
|
|
|
|
PROHIBITED_AMAZON = {
|
|
"made in China", "veio da China", "fabricado na China",
|
|
"chinês", # como insulto ou desmerecimento
|
|
"better than", "superior a", "worse than",
|
|
"best", "top 1", "número 1",
|
|
"mais vendido do Brasil",
|
|
"garantia vitalícia", "garantia de 10 anos", "garantia permanente",
|
|
"só hoje", "últimasunidades", "última peça", "último par", "hoje ou nunca",
|
|
"5 estrelas", "avaliado por dermatologistas", "clinicamente testado",
|
|
}
|
|
|
|
ALL_PROHIBITED = {
|
|
"MercadoLivre": PROHIBITED_ML,
|
|
"Shopee": PROHIBITED_SHOPEE,
|
|
"Amazon": PROHIBITED_AMAZON,
|
|
}
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 2. CATEGORIAS REGULATÓRIAS — palavras-chave
|
|
# ─────────────────────────────────────────────
|
|
|
|
ANVISA_COSMETICO = {
|
|
"protetor solar", "filtro solar", "fps", "creme para rosto", "creme para corpo",
|
|
"anti-idade", "clareador", "hidratante", "nutritivo",
|
|
"base", "batom", "sombra", "delineador", "máscara para cílios",
|
|
"perfume", "colônia", "tintura", "esmalte", "unha", "depilatório",
|
|
"desodorante", "shampoo", "condicionador", "queratina", "pantenol",
|
|
"maquiagem", "cosmético", "skincare", "serum",
|
|
}
|
|
|
|
ANVISA_SUPLEMENTO = {
|
|
"whey", "caseína", "suplemento proteico", "termogênico",
|
|
"carnitina", "guaraná", "efedrina", "sinefrina", "capsicum",
|
|
"vitamina", "mineral", "comprimido", "cápsula",
|
|
"ômega 3", "probiótico", "nutracêutico", "nutraceutico",
|
|
"emagrecedor", "bloqueador de gordura", "orlistat", "quitosana",
|
|
"energético", "bcaa", "glutamina", "creatina", "colágeno",
|
|
"suplemento", "anabolizante", "esteroides",
|
|
}
|
|
|
|
ANVISA_SANEANTE = {
|
|
"desinfetante", "sanitizante", "alvejante", "detergente bactericida",
|
|
"produto de limpeza", "limpeza", "antimicrobiano", "virucida",
|
|
"comodoro", "q-boa", "Veja",
|
|
}
|
|
|
|
MAPA_ANIMAL = {
|
|
"mel", "própolis", "geleia real", "pólen",
|
|
"queijo", "laticínio", "leite", "manteiga", "nata",
|
|
"salsicha", "linguiça", "presunto", "bacon", "salame", "embutido",
|
|
"carne processada", "hambúrguer", "almôndega",
|
|
"ovo", "ovos",
|
|
"peixe", "pescado", "sardinha", "filé de peixe",
|
|
"geleia com mel", "geleia com animal",
|
|
}
|
|
|
|
MAPA_VEGETAL = {
|
|
"café torrado", "café moído", "óleo de soja", "óleo de canola",
|
|
"óleo de girassol", "óleo vegetal",
|
|
"arroz parboilizado", "feijão industrializado",
|
|
"erva medicinal", "chá medicinal",
|
|
"orgânico", "produto orgânico",
|
|
}
|
|
|
|
INMETRO_BRINQUEDO = {
|
|
"brinquedo", "brinq", "boneca", "boneco", "carro", "avião",
|
|
"jogo infantil", "quebra-cabeça", "puzzle", "monta",
|
|
"bola", "roda", "infantil", "criança",
|
|
}
|
|
|
|
INMETRO_PUERICULTURA = {
|
|
"cadeirinha auto", "berço", "carrinho de bebê", "alcofa",
|
|
"moisés", "porta-bebê", "bebê conforto",
|
|
}
|
|
|
|
INMETRO_ELETRICO = {
|
|
"filtro de linha", "estabilizador", "nobreak", "no-break",
|
|
"lâmpada led", "lampada led", "led",
|
|
"capacete", "capacete de moto", "capacete motociclista",
|
|
"colchão", "extintor",
|
|
}
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 3. PRODUTOS SEMPRE PROIBIDOS (não vender)
|
|
# ─────────────────────────────────────────────
|
|
|
|
ALWAYS_BLOCK = {
|
|
"cigarro eletrônico", "vaper", "vaporizador",
|
|
"esteroides", "anabolizante",
|
|
"thc", "maconha", "haixe",
|
|
"armamento", "arma de fogo", "munição",
|
|
"explosivo", "bomba",
|
|
}
|
|
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 4. FUNÇÕES AUXILIARES
|
|
# ─────────────────────────────────────────────
|
|
|
|
def normalize(text: str) -> str:
|
|
"""Remove acentos e converte para minúsculas."""
|
|
text = text.lower()
|
|
text = unicodedata.normalize("NFD", text)
|
|
text = re.sub(r"[\u0300-\u036f]", "", text)
|
|
return text
|
|
|
|
|
|
def strip_punctuation(text: str) -> str:
|
|
"""Remove pontuação para busca parcial."""
|
|
return re.sub(r"[^\w\s]", " ", text)
|
|
|
|
|
|
def check_wordlists(text: str, wordlist: set, label: str) -> list[dict]:
|
|
"""Retorna violações de uma wordlist."""
|
|
violations = []
|
|
text_norm = normalize(text)
|
|
text_clean = strip_punctuation(text_norm)
|
|
for word in wordlist:
|
|
word_norm = normalize(word)
|
|
# Match whole word
|
|
pattern = r"\b" + re.escape(word_norm) + r"\b"
|
|
if re.search(pattern, text_clean):
|
|
violations.append({
|
|
"word": word,
|
|
"list": label,
|
|
"severity": "HIGH",
|
|
})
|
|
return violations
|
|
|
|
|
|
def detect_category(text: str) -> dict[str, bool]:
|
|
"""Heurística simples para detectar categoria regulatória."""
|
|
text_lower = text.lower()
|
|
text_norm = normalize(text_lower)
|
|
text_clean = strip_punctuation(text_norm)
|
|
|
|
return {
|
|
"anvisa_cosmetico": any(k in text_clean for k in [normalize(k) for k in ANVISA_COSMETICO]),
|
|
"anvisa_suplemento": any(k in text_clean for k in [normalize(k) for k in ANVISA_SUPLEMENTO]),
|
|
"anvisa_saneante": any(k in text_clean for k in [normalize(k) for k in ANVISA_SANEANTE]),
|
|
"mapa_animal": any(k in text_clean for k in [normalize(k) for k in MAPA_ANIMAL]),
|
|
"mapa_vegetal": any(k in text_clean for k in [normalize(k) for k in MAPA_VEGETAL]),
|
|
"inmetro_brinquedo": any(k in text_clean for k in [normalize(k) for k in INMETRO_BRINQUEDO]),
|
|
"inmetro_puericultura": any(k in text_clean for k in [normalize(k) for k in INMETRO_PUERICULTURA]),
|
|
"inmetro_eletrico": any(k in text_clean for k in [normalize(k) for k in INMETRO_ELETRICO]),
|
|
}
|
|
|
|
|
|
def check_always_block(text: str) -> list[dict]:
|
|
"""Verifica produtos sempre bloqueados."""
|
|
violations = []
|
|
text_clean = normalize(strip_punctuation(normalize(text)))
|
|
for term in ALWAYS_BLOCK:
|
|
term_norm = normalize(term)
|
|
pattern = r"\b" + re.escape(term_norm) + r"\b"
|
|
if re.search(pattern, text_clean):
|
|
violations.append({
|
|
"word": term,
|
|
"list": "ALWAYS_BLOCK",
|
|
"severity": "CRITICAL",
|
|
})
|
|
return violations
|
|
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 5. SCORING
|
|
# ─────────────────────────────────────────────
|
|
|
|
WEIGHTS = {
|
|
"ALWAYS_BLOCK": -100, # sempre bloqueia
|
|
"MercadoLivre": -8,
|
|
"Shopee": -8,
|
|
"Amazon": -8,
|
|
"anvisa_cosmetico": -5,
|
|
"anvisa_suplemento": -5,
|
|
"anvisa_saneante": -5,
|
|
"mapa_animal": -5,
|
|
"mapa_vegetal": -5,
|
|
"inmetro_brinquedo": -5,
|
|
"inmetro_puericultura": -5,
|
|
"inmetro_eletrico": -5,
|
|
}
|
|
|
|
|
|
def compute_score(violations: list[dict], detected_categories: dict) -> dict:
|
|
"""Calcula score 0-100."""
|
|
score = 100
|
|
|
|
for v in violations:
|
|
key = v.get("list", "unknown")
|
|
if key in WEIGHTS:
|
|
score += WEIGHTS[key] # já são negativos
|
|
|
|
# Penalidades por categoria detectada (sem registro declarado)
|
|
for cat, detected in detected_categories.items():
|
|
if detected and cat in WEIGHTS:
|
|
score += WEIGHTS[cat]
|
|
|
|
score = max(0, min(100, score))
|
|
return score
|
|
|
|
|
|
def decision(score: int) -> str:
|
|
if score >= 90:
|
|
return "PUBLICAR"
|
|
elif score >= 70:
|
|
return "REVISAR"
|
|
elif score >= 40:
|
|
return "REVISAR_OBRIGATORIO"
|
|
else:
|
|
return "BLOQUEADO"
|
|
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 6. RECOMENDAÇÕES
|
|
# ─────────────────────────────────────────────
|
|
|
|
RECOMMENDATIONS = {
|
|
"anvisa_cosmetico": "⚠️ Cosmético — exigir número de Registro ANVISA antes de publicar (RDC 685/2018)",
|
|
"anvisa_suplemento": "⚠️ Suplemento — exigir número de Registro ANVISA antes de publicar (RDC 26/2015)",
|
|
"anvisa_saneante": "⚠️ Saneante — exigir registro ANVISA antes de publicar",
|
|
"mapa_animal": "⚠️ Produto animal — exige SIF (venda interestadual) ou SIE (venda estadual). Verificar com MAPA.",
|
|
"mapa_vegetal": "⚠️ Produto vegetal processado — pode exigir registro MAPA ou estadual.",
|
|
"inmetro_brinquedo": "⚠️ Brinquedo — exige certificação INMETRO compulsória.",
|
|
"inmetro_puericultura": "⚠️ Artigo de puericultura — exige certificação INMETRO.",
|
|
"inmetro_eletrico": "⚠️ Equipamento elétrico — exige certificação de segurança INMETRO.",
|
|
}
|
|
|
|
|
|
# ─────────────────────────────────────────────
|
|
# 7. MAIN
|
|
# ─────────────────────────────────────────────
|
|
|
|
def run_check(title: str, description: str = "", category_hint: str = "") -> dict:
|
|
"""
|
|
Verifica título e descrição contra todas as listas.
|
|
Retorna dict JSON com score, violações e recomendações.
|
|
"""
|
|
full_text = f"{title} {description}"
|
|
|
|
# Coleta violações
|
|
all_violations = []
|
|
|
|
for marketplace, wordlist in ALL_PROHIBITED.items():
|
|
all_violations.extend(check_wordlists(full_text, wordlist, marketplace))
|
|
|
|
all_violations.extend(check_always_block(full_text))
|
|
|
|
# Detecta categorias
|
|
detected = detect_category(full_text)
|
|
|
|
# Se categoria foi informada manualmente
|
|
if category_hint:
|
|
cat_map = {
|
|
"cosmetico": "anvisa_cosmetico",
|
|
"suplemento": "anvisa_suplemento",
|
|
"saneante": "anvisa_saneante",
|
|
"animal": "mapa_animal",
|
|
"vegetal": "mapa_vegetal",
|
|
"brinquedo": "inmetro_brinquedo",
|
|
"puericultura": "inmetro_puericultura",
|
|
"eletrico": "inmetro_eletrico",
|
|
}
|
|
hint_key = cat_map.get(category_hint.lower().strip())
|
|
if hint_key:
|
|
detected[hint_key] = True
|
|
|
|
# Score
|
|
score = compute_score(all_violations, detected)
|
|
dec = decision(score)
|
|
|
|
# Recomendações
|
|
recommendations = []
|
|
for cat, active in detected.items():
|
|
if active and cat in RECOMMENDATIONS:
|
|
recommendations.append(RECOMMENDATIONS[cat])
|
|
|
|
# Violações críticas
|
|
critical = [v for v in all_violations if v.get("severity") == "CRITICAL"]
|
|
if critical:
|
|
recommendations.insert(0, "🚫 PRODUTO SEMPRE BLOQUEADO — Não publar. Verificar se é produto proibido (armas, drogas, cigarros eletrônicos, etc.)")
|
|
|
|
return {
|
|
"score": score,
|
|
"decision": dec,
|
|
"violations": all_violations,
|
|
"violation_count": len(all_violations),
|
|
"detected_categories": {k: v for k, v in detected.items() if v},
|
|
"recommendations": recommendations,
|
|
"check_passed": dec in ("PUBLICAR", "REVISAR"),
|
|
}
|
|
|
|
|
|
def interactive():
|
|
print("=== COMPLIANCE CHECKER — Ecommerce M5 Brasil ===\n")
|
|
print("Pressione ENTER para pular qualquer campo.\n")
|
|
|
|
title = input("Título do produto: ").strip()
|
|
if not title:
|
|
print("Título é obrigatório. Encerrando.")
|
|
return
|
|
|
|
description = input("Descrição do produto (ou ENTER para vazio): ").strip()
|
|
category = input(
|
|
"Categoria (opcional — ajuda na detecção):\n"
|
|
" cosmetico | suplemento | saneante | animal | vegetal\n"
|
|
" brinquedo | puericultura | eletrico | ENTER=pular\n"
|
|
" >> "
|
|
).strip()
|
|
|
|
result = run_check(title, description, category)
|
|
print("\n" + json.dumps(result, ensure_ascii=False, indent=2))
|
|
|
|
|
|
def main():
|
|
args = sys.argv[1:]
|
|
|
|
if "--interactive" in args or len(args) == 0:
|
|
interactive()
|
|
return
|
|
|
|
# Parse arguments
|
|
title = ""
|
|
description = ""
|
|
category = ""
|
|
|
|
i = 0
|
|
while i < len(args):
|
|
if args[i] in ("--title", "-t"):
|
|
title = args[i + 1]
|
|
i += 2
|
|
elif args[i] in ("--description", "-d"):
|
|
description = args[i + 1]
|
|
i += 2
|
|
elif args[i] in ("--category", "-c"):
|
|
category = args[i + 1]
|
|
i += 2
|
|
elif args[i] == "--interactive":
|
|
interactive()
|
|
return
|
|
else:
|
|
i += 1
|
|
|
|
if not title:
|
|
print("Erro: --title é obrigatório.", file=sys.stderr)
|
|
print("Uso: python3 compliance-checker.py --title '...' [--description '...'] [--category 'cosmetico'] [--interactive]")
|
|
sys.exit(1)
|
|
|
|
result = run_check(title, description, category)
|
|
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|