#!/usr/bin/env python3 """ scripts/generate_synthetic.py Gerador de dataset sintetico para fine-tuning do Agente CBMGO (CERCON) Gera exemplos no formato SFT (Supervised Fine-Tuning) para Llama-3 / Mistral """ import json import random import argparse from pathlib import Path from datetime import datetime OCUPACOES = [ {"grupo": "A-1", "desc": "Residencial unifamiliar", "ex": "casa, sobrado, chale"}, {"grupo": "A-2", "desc": "Residencial multifamiliar", "ex": "apartamento, flat, kitnet"}, {"grupo": "B-1", "desc": "Servico de hospedagem", "ex": "hotel, motel, pousada"}, {"grupo": "B-2", "desc": "Hospedagem longa permanencia", "ex": "apart-hotel, residencial senior"}, {"grupo": "C-1", "desc": "Comercio pequeno porte", "ex": "loja, boutique, sapataria"}, {"grupo": "C-2", "desc": "Comercio medio e grande porte", "ex": "supermercado, shopping"}, {"grupo": "D-1", "desc": "Reuniao publica - esporte e lazer", "ex": "ginasio, estadio, piscina"}, {"grupo": "D-2", "desc": "Reuniao publica - cultura e religiao", "ex": "teatro, cinema, igreja"}, {"grupo": "D-3", "desc": "Reuniao publica - ensino", "ex": "escola, universidade, creche"}, {"grupo": "E-1", "desc": "Saude e assistencia social", "ex": "hospital, clinica, UBS"}, {"grupo": "F-1", "desc": "Servico profissional e negocios", "ex": "escritorio, banco, consultorio"}, {"grupo": "F-2", "desc": "Servico de transporte", "ex": "aeroporto, rodoviaria, correios"}, {"grupo": "G-1", "desc": "Servicos automotivos", "ex": "garagem, estacionamento, posto"}, {"grupo": "H-1", "desc": "Industrial baixo risco", "ex": "industria alimenticia, textil"}, {"grupo": "H-2", "desc": "Industrial medio risco", "ex": "metalurgica, moveleira, grafica"}, {"grupo": "H-3", "desc": "Industrial alto risco", "ex": "quimica, petroquimica, fogos"}, {"grupo": "I-1", "desc": "Deposito baixo risco", "ex": "arquivo, deposito de papel"}, {"grupo": "I-2", "desc": "Deposito medio risco", "ex": "deposito de madeira, borracha"}, {"grupo": "I-3", "desc": "Deposito alto risco", "ex": "deposito de liquidos inflamaveis"}, {"grupo": "J-1", "desc": "Explosivos e inflamaveis especiais", "ex": "refinaria, base de gas"}, ] CIDADES_GOIAS = [ "Goiania", "Aparecida de Goiania", "Anapolis", "Rio Verde", "Luziania", "Aguas Lindas de Goias", "Valparaiso de Goias", "Trindade", "Formosa", "Novo Gama", "Itumbiara", "Senador Canedo", "Catalao", "Jatai", "Planaltina", "Caldas Novas", "Goianesia", "Mineiros", "Inhumas", "Goiatuba", ] RESPONSAVEIS = [ ("Joao Silva", "CREA-GO 12345-D"), ("Maria Santos", "CREA-GO 67890-D"), ("Carlos Oliveira", "CAU A123456-0"), ("Ana Ferreira", "CREA-GO 11111-D"), ("Pedro Alves", "CAU A654321-0"), ("Lucia Martins", "CREA-GO 99999-D"), ] PERGUNTAS_CLASSIFICACAO = [ "Qual e o grupo de ocupacao para {desc}?", "Uma {desc} se enquadra em qual grupo da NT-01/2025?", "Como classificar uma {desc} segundo a NT-01/2025 do CBMGO?", "Qual a classificacao de ocupacao para {desc} no CBMGO?", "Preciso classificar uma {desc}. Qual grupo de ocupacao devo usar?", ] PERGUNTAS_CALCULO = [ "Quantos extintores sao necessarios para uma {desc} com {area}m2?", "Como calcular as medidas de seguranca para uma {desc} de {area}m2?", "Qual o numero minimo de hidrantes para uma {desc} com {area}m2?", "Uma {desc} de {area}m2 e {pavimentos} pavimentos precisa de SPDA?", "Quais medidas de seguranca sao obrigatorias para uma {desc} de {area}m2?", ] PERGUNTAS_MEMORIAL = [ "Gere um memorial descritivo de PPCI para uma {desc} em {cidade}.", "Preciso do memorial de prevencao contra incendio para {desc} com {area}m2.", "Elabore o memorial descritivo conforme NT-01/2025 para {desc}.", "Crie um memorial de PPCI completo para {desc} de {area}m2 em {cidade}.", ] PERGUNTAS_NORMAS = [ "Quais sao as exigencias da NT-01/2025 para {desc}?", "O que a NT-01/2025 determina sobre {desc}?", "Quais normas se aplicam para {desc} segundo o CBMGO?", "Quais sao os requisitos minimos de prevencao de incendio para {desc}?", ] def gerar_resposta_classificacao(ocupacao): grupo = ocupacao["grupo"] desc = ocupacao["desc"] ex = ocupacao["ex"] letra = grupo.split("-")[0] descricoes = { "A": "residencial", "B": "hospedagem", "C": "comercial", "D": "reuniao de publico", "E": "saude", "F": "servicos", "G": "automotivo", "H": "industrial", "I": "deposito", "J": "especial", } return f"""Com base na NT-01/2025 do CBMGO, a classificacao correta e: **Grupo: {grupo} - {desc}** Divisao {letra} ({descricoes.get(letra, "especial")}): edificacoes como {ex}. **Implicacoes para o PPCI:** - Grupo {grupo} determina medidas de seguranca obrigatorias - Consulte Tabela 1 da NT-01/2025 para requisitos especificos - Area total e pavimentos influenciam exigencias adicionais ] PERGUNTAS_CALCULO = [ "Quantos extintores sao necessarios para uma {desc} com {area}m2?", "Como calcular as medidas de seguranca para uma {desc} de {area}m2?", "Qual o numero minimo de hidrantes para uma {desc} com {area}m2?", "Uma {desc} de {area}m2 e {pavimentos} pavimentos precisa de SPDA?", "Quais medidas de seguranca sao obrigatorias para uma {desc} de {area}m2?", ] PERGUNTAS_MEMORIAL = [ "Gere um memorial descritivo de PPCI para uma {desc} em {cidade}.", "Preciso do memorial de prevencao contra incendio para {desc} com {area}m2.", "Elabore o memorial descritivo conforme NT-01/2025 para {desc}.", "Crie um memorial de PPCI completo para {desc} de {area}m2 em {cidade}.", ] PERGUNTAS_NORMAS = [ "Quais sao as exigencias da NT-01/2025 para {desc}?", "O que a NT-01/2025 determina sobre {desc}?", "Quais normas se aplicam para {desc} segundo o CBMGO?", "Quais sao os requisitos minimos de prevencao de incendio para {desc}?", ] def gerar_resposta_classificacao(ocupacao): grupo = ocupacao["grupo"] desc = ocupacao["desc"] ex = ocupacao["ex"] letra = grupo.split("-")[0] descricoes = { "A": "residencial", "B": "hospedagem", "C": "comercial", "D": "reuniao de publico", "E": "saude", "F": "servicos", "G": "automotivo", "H": "industrial", "I": "deposito", "J": "especial", } return f"""Com base na NT-01/2025 do CBMGO, a classificacao correta e: **Grupo: {grupo} - {desc}** Divisao {letra} ({descricoes.get(letra, "especial")}): edificacoes como {ex}. **Implicacoes para o PPCI:** - Grupo {grupo} determina medidas de seguranca obrigatorias - Consulte Tabela 1 da NT-01/2025 para requisitos especificos - Area total e pavimentos influenciam exigencias adicionais Para duvidas, consulte o CBMGO ou profissional habilitado (CREA/CAU).""" def gerar_resposta_calculo(ocupacao, area, pavimentos): grupo = ocupacao["grupo"] risco = "alto" if grupo[0] in ["H","I","J"] else ("medio" if grupo[0] in ["C","D","G"] else "baixo") area_ext = 150 if risco == "alto" else (250 if risco == "medio" else 500) num_ext = max(2, int(area / area_ext) + 1) precisa_hidrante = area > 750 or pavimentos >= 3 altura = pavimentos * 3.0 precisa_spda = altura > 15 or area > 1000 precisa_ilum = area > 200 or pavimentos >= 2 return f"""Para edificacao {grupo} com {area:.0f}m2 e {pavimentos} pavimento(s): **EXTINTORES:** - Risco: {risco.upper()} | Area/extintor: {area_ext}m2 - Quantidade minima: {num_ext} extintores ABC 6kg **HIDRANTES:** - {"OBRIGATORIO" if precisa_hidrante else "Verificar com CBMGO"} **SPDA:** - {"OBRIGATORIO (altura {:.1f}m)".format(altura) if precisa_spda else "Avaliar necessidade"} **ILUMINACAO DE EMERGENCIA:** - {"OBRIGATORIA - autonomia minima 1h (NBR 10898)" if precisa_ilum else "Recomendada"} *Nota: Estes calculos sao estimativas. PPCI deve ser elaborado por profissional habilitado.*""" def gerar_memorial(ocupacao, area, pavimentos, cidade, responsavel): grupo = ocupacao["grupo"] desc = ocupacao["desc"] nome_resp, registro = responsavel data_atual = datetime.now().strftime("%d/%m/%Y") altura = pavimentos * 3.0 risco = "alto" if grupo[0] in ["H","I","J"] else ("medio" if grupo[0] in ["C","D","G"] else "baixo") area_ext = 150 if risco == "alto" else (250 if risco == "medio" else 500) num_ext = max(2, int(area / area_ext) + 1) carga = "500 MJ/m2" if risco == "baixo" else ("1.000 MJ/m2" if risco == "medio" else "2.000 MJ/m2") return f"""MEMORIAL DESCRITIVO DE PREVENCAO E PROTECAO CONTRA INCENDIO E PANICO PLANO DE PREVENCAO CONTRA INCENDIO - PPCI 1. IDENTIFICACAO DO PROJETO Proprietario: [A PREENCHER] Endereco: [Endereco completo], {cidade} - GO Uso/Ocupacao: {desc} (Grupo {grupo} - NT-01/2025) Area Total: {area:.2f} m2 | Pavimentos: {pavimentos} | Altura: {altura:.2f} m Data: {data_atual} Responsavel Tecnico: {nome_resp} | {registro} 2. OBJETIVO Este memorial apresenta medidas de seguranca conforme: - NT-01/2025 do CBMGO | ABNT NBR 9077:2001 | Lei Estadual 15.802/2006 3. CARACTERISTICAS DA EDIFICACAO - Grupo: {grupo} - {desc} - Risco de Incendio: {risco.upper()} - Carga de Incendio Especifica: {carga} - Estrutura: Alvenaria convencional / Concreto armado 4. MEDIDAS DE SEGURANCA 4.1 Extintores: {num_ext} extintores ABC 6kg (1 a cada {area_ext}m2) 4.2 Saidas de Emergencia: largura minima 1,20m, abertura no sentido do escape 4.3 Iluminacao de Emergencia: autonomia 1h, 30 lux nas saidas (NBR 10898) 4.4 Sinalizacao: conforme ABNT NBR 13434 5. RESPONSABILIDADE TECNICA {nome_resp} | {registro} ART/RRT No: [A PREENCHER] {cidade}, {data_atual}""" def gerar_exemplo_classificacao(): ocupacao = random.choice(OCUPACOES) tmpl = random.choice(PERGUNTAS_CLASSIFICACAO) pergunta = tmpl.format(desc=ocupacao["desc"], grupo=ocupacao["grupo"]) return { "instruction": pergunta, "input": "", "output": gerar_resposta_classificacao(ocupacao), "category": "classificacao", "grupo": ocupacao["grupo"], "source": "synthetic_nt01_2025" } def gerar_exemplo_calculo(): ocupacao = random.choice(OCUPACOES) area = random.choice([100,150,200,300,500,750,1000,1500,2000,3000,5000]) pavimentos = random.choice([1,2,3,4,5,8,10,15]) tmpl = random.choice(PERGUNTAS_CALCULO) pergunta = tmpl.format(desc=ocupacao["desc"], area=area, pavimentos=pavimentos, grupo=ocupacao["grupo"]) return { "instruction": pergunta, "input": f"Area: {area}m2, Pavimentos: {pavimentos}, Grupo: {ocupacao['grupo']}", "output": gerar_resposta_calculo(ocupacao, area, pavimentos), "category": "calculo", "grupo": ocupacao["grupo"], "area": area, "pavimentos": pavimentos, "source": "synthetic_nt01_2025" } def gerar_exemplo_memorial(): ocupacao = random.choice(OCUPACOES) area = random.choice([150,200,300,500,750,1000,1500,2000]) pavimentos = random.choice([1,2,3,4,5]) cidade = random.choice(CIDADES_GOIAS) responsavel = random.choice(RESPONSAVEIS) tmpl = random.choice(PERGUNTAS_MEMORIAL) pergunta = tmpl.format(desc=ocupacao["desc"], area=area, cidade=cidade, grupo=ocupacao["grupo"]) return { "instruction": pergunta, "input": f"Edificacao: {ocupacao['desc']}, Area: {area}m2, Pavimentos: {pavimentos}, Cidade: {cidade}", "output": gerar_memorial(ocupacao, area, pavimentos, cidade, responsavel), "category": "memorial", "grupo": ocupacao["grupo"], "area": area, "cidade": cidade, "source": "synthetic_nt01_2025" } def gerar_exemplo_normas(): ocupacao = random.choice(OCUPACOES) tmpl = random.choice(PERGUNTAS_NORMAS) pergunta = tmpl.format(desc=ocupacao["desc"], grupo=ocupacao["grupo"]) grupo = ocupacao["grupo"] letra = grupo.split("-")[0] normas_por_grupo = { "A": ["NBR 9077","NBR 10897"], "B": ["NBR 9077","NBR 13714","NBR 12693"], "C": ["NBR 9077","NBR 12693"], "D": ["NBR 9077","NBR 13434","NR-23"], "E": ["RDC ANVISA","NBR 9077","NBR 12693"], "H": ["NBR 14276","NR-23","NBR 12693"], } normas = normas_por_grupo.get(letra, ["NBR 9077","NBR 12693","NT-01/2025"]) normas_str = chr(10).join(f"- {n}" for n in normas) resposta = f"""Para {ocupacao["desc"]} (grupo {grupo}), NT-01/2025 determina: **Normas Aplicaveis:** {normas_str} - NT-01/2025 CBMGO | Lei Estadual 15.802/2006 **Exigencias Principais:** - Extintores com manutencao anual (NBR 12693) - Sinalizacao de emergencia (NBR 13434) - Rotas de saida desobstruidas (NBR 9077) - PPCI aprovado pelo CBMGO antes da ocupacao **Periodicidade de Vistorias:** - Inicial: Antes da ocupacao - Renovacao: 3 anos (baixo risco) / 1 ano (alto risco)""" return { "instruction": pergunta, "input": "", "output": resposta, "category": "normas", "grupo": grupo, "source": "synthetic_nt01_2025" } def formatar_chatml(exemplo): system_msg = "Voce e o CERCON, assistente do CBMGO especializado em NT-01/2025 e prevencao de incendio em Goias." user_content = exemplo["instruction"] if exemplo.get("input"): user_content += f"\n\nDados fornecidos:\n{exemplo['input']}" return { "messages": [ {"role": "system", "content": system_msg}, {"role": "user", "content": user_content}, {"role": "assistant", "content": exemplo["output"]} ] } def formatar_alpaca(exemplo): return { "instruction": exemplo["instruction"], "input": exemplo.get("input", ""), "output": exemplo["output"] } def formatar_llama3(exemplo): system_msg = "Voce e o CERCON, assistente do CBMGO especializado em NT-01/2025." user_content = exemplo["instruction"] if exemplo.get("input"): user_content += f"\n\n{exemplo['input']}" text = f"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n{system_msg}<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n{user_content}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n{exemplo['output']}<|eot_id|>" return {"text": text} def gerar_dataset(n_total=2000, output_dir="data", formato="chatml", seed=42): random.seed(seed) Path(output_dir).mkdir(parents=True, exist_ok=True) n_class = int(n_total * 0.30) n_calc = int(n_total * 0.25) n_mem = int(n_total * 0.30) n_norm = n_total - n_class - n_calc - n_mem print(f"Gerando dataset sintetico NT-01/2025 CBMGO") print(f"Total: {n_total} | Classificacao: {n_class} | Calculo: {n_calc} | Memorial: {n_mem} | Normas: {n_norm}") exemplos = [] print("Gerando exemplos de classificacao...") for _ in range(n_class): exemplos.append(gerar_exemplo_classificacao()) print("Gerando exemplos de calculo...") for _ in range(n_calc): exemplos.append(gerar_exemplo_calculo()) print("Gerando exemplos de memorial...") for _ in range(n_mem): exemplos.append(gerar_exemplo_memorial()) print("Gerando exemplos de normas...") for _ in range(n_norm): exemplos.append(gerar_exemplo_normas()) random.shuffle(exemplos) formatadores = {"alpaca": formatar_alpaca, "chatml": formatar_chatml, "llama3": formatar_llama3} fmt = formatadores.get(formato, formatar_chatml) formatados = [fmt(e) for e in exemplos] split = int(len(formatados) * 0.9) treino, val = formatados[:split], formatados[split:] out = Path(output_dir) with open(out / f"train_{formato}.jsonl", "w", encoding="utf-8") as f: for ex in treino: f.write(json.dumps(ex, ensure_ascii=False) + "\n") print(f"Treino: {out}/train_{formato}.jsonl ({len(treino)} exemplos)") with open(out / f"validation_{formato}.jsonl", "w", encoding="utf-8") as f: for ex in val: f.write(json.dumps(ex, ensure_ascii=False) + "\n") print(f"Validacao: {out}/validation_{formato}.jsonl ({len(val)} exemplos)") meta = { "total": n_total, "treino": len(treino), "validacao": len(val), "formato": formato, "seed": seed, "distribuicao": {"classificacao": n_class, "calculo": n_calc, "memorial": n_mem, "normas": n_norm}, "base_normativa": "NT-01/2025 CBMGO", "gerado_em": datetime.now().isoformat(), "versao": "1.0.0", } with open(out / "dataset_metadata.json", "w", encoding="utf-8") as f: json.dump(meta, f, ensure_ascii=False, indent=2) print(f"Metadados: {out}/dataset_metadata.json") print(f"\nDataset gerado com sucesso! Total: {n_total} exemplos") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Gerador de dataset sintetico NT-01/2025 CBMGO") parser.add_argument("--n", type=int, default=2000, help="Numero de exemplos (default: 2000)") parser.add_argument("--output", type=str, default="data", help="Diretorio de saida (default: data)") parser.add_argument("--formato", type=str, default="chatml", choices=["alpaca","chatml","llama3"], help="Formato SFT (default: chatml)") parser.add_argument("--seed", type=int, default=42, help="Semente aleatoria (default: 42)") args = parser.parse_args() gerar_dataset(n_total=args.n, output_dir=args.output, formato=args.formato, seed=args.seed)