ecom-store/scripts/compliance/compliance-checker.py
Iris Florencio 9c2f7ff7b4 chore: triagem de arquivos untracked (60+ → 0)
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*_*/
2026-07-06 18:50:26 -03:00

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()