CERCON / modules /auditor.py
carlosh10's picture
feat: Adiciona auditor de projetos PSCIP NT-01/2025
16e2ec0 verified
"""
Auditor de Projetos PSCIP - NT-01/2025 CBMGO
Verifica conformidade de memoriais descritivos e projetos.
"""
import json
import re
import os
from datetime import datetime
from typing import Dict, List, Tuple
class AuditorProjetos:
TERMOS_BUSCA = {
"extintores_portateis": ["extintor","extintores","po abc","co2","capacidade extintora"],
"hidrantes": ["hidrante","hidrantes","mangotinho","coluna seca","coluna de incendio","nbr 13714"],
"sinalizacao_emergencia": ["sinalizacao","sinaliza","emergencia","saida","rota de fuga","nbr 13434"],
"iluminacao_emergencia": ["iluminacao de emergencia","autonomia","bloco autonomo","nbr 10898"],
"spda": ["spda","para-raios","descargas atmosfericas","nbr 5419"],
"alarme_incendio": ["alarme","acionador manual","central de alarme","nbr 17240"],
"deteccao_automatica": ["detector","detectores","detector de fumaca","deteccao automatica"],
"chuveiros_automaticos": ["chuveiro","chuveiros","sprinkler","nbr 10897"],
"saidas_emergencia": ["saida de emergencia","rota de fuga","porta corta-fogo","nbr 9077"],
"reservatorio_incendio": ["reservatorio","reserva tecnica","cisterna"],
"plano_emergencia": ["plano de emergencia","brigada","simulacro","nbr 15219"],
"ventilacao_mecanica": ["ventilacao","exaustao","renovacao de ar"],
}
SEVERIDADE = {
"extintores_portateis": "CRITICO","hidrantes": "CRITICO",
"sinalizacao_emergencia": "CRITICO","iluminacao_emergencia": "IMPORTANTE",
"spda": "IMPORTANTE","alarme_incendio": "IMPORTANTE",
"deteccao_automatica": "IMPORTANTE","chuveiros_automaticos": "CRITICO",
"saidas_emergencia": "CRITICO","reservatorio_incendio": "IMPORTANTE",
"plano_emergencia": "ALERTA","ventilacao_mecanica": "ALERTA",
}
def __init__(self, regras_path="data/regras_declarativas.json"):
self.regras = []
if os.path.exists(regras_path):
with open(regras_path, "r", encoding="utf-8") as f:
self.regras = json.load(f)
def _aplicar_regras(self, dados):
exigencias = {"extintores_portateis", "sinalizacao_emergencia"}
area = dados.get("area_m2", 0)
altura = dados.get("altura_m", 0)
ocupacao = dados.get("ocupacao", "").lower()
lotacao = dados.get("lotacao", 0)
if area > 750 or ocupacao in ["comercial","servicos","industrial"]:
exigencias.add("hidrantes")
if altura > 12:
exigencias.update(["iluminacao_emergencia","saidas_emergencia"])
if area > 1000:
exigencias.add("spda")
if lotacao > 100 or area > 750:
exigencias.add("alarme_incendio")
if lotacao > 200:
exigencias.add("deteccao_automatica")
if (ocupacao == "industrial" and area > 2000) or altura > 30:
exigencias.add("chuveiros_automaticos")
if any(u in ocupacao for u in ["hospital","saude"]):
exigencias.update(["chuveiros_automaticos","deteccao_automatica",
"alarme_incendio","plano_emergencia"])
if "garagem" in ocupacao or "estacionamento" in ocupacao:
exigencias.add("ventilacao_mecanica")
for regra in self.regras:
match = True
for k, v in regra.get("se", {}).items():
if k == "ocupacao":
if dados.get("ocupacao","").lower() != str(v).lower():
match = False; break
elif isinstance(v, str) and v.startswith(">"):
if dados.get(k, 0) <= float(v[1:]):
match = False; break
elif isinstance(v, str) and v.startswith("<"):
if dados.get(k, 0) >= float(v[1:]):
match = False; break
elif str(dados.get(k,"")) != str(v):
match = False; break
if match:
for e in regra.get("entao", []):
exigencias.add(e)
return list(exigencias)
def auditar_memorial(self, memorial_txt, dados_projeto):
if not memorial_txt or len(memorial_txt) < 50:
return {"status":"INVALIDO","apto":False,"erros":[],"alertas":[],"conformes":[],"score":0}
ml = memorial_txt.lower()
exigencias = self._aplicar_regras(dados_projeto)
erros, alertas, conformes = [], [], []
for exig in exigencias:
termos = self.TERMOS_BUSCA.get(exig, [exig.replace("_"," ")])
enc = [t for t in termos if t in ml]
sev = self.SEVERIDADE.get(exig, "ALERTA")
item = {"exigencia": exig, "descricao": exig.replace("_"," ").title(),
"severidade": sev, "termos_encontrados": enc}
if enc:
conformes.append(item)
else:
item["mensagem"] = f"{item['descricao']} - nao encontrado"
(erros if sev == "CRITICO" else alertas).append(item)
# Alertas de dados tecnicos
if not re.search(r'\d+\s*m[2²]', memorial_txt, re.IGNORECASE):
alertas.append({"tipo":"DADO","mensagem":"Area em m2 nao encontrada","severidade":"ALERTA"})
if not re.search(r'(responsavel tecnico|crea|cau)', ml):
alertas.append({"tipo":"RT","mensagem":"Responsavel Tecnico nao identificado","severidade":"IMPORTANTE"})
if "nt-01" not in ml and "nt 01" not in ml:
alertas.append({"tipo":"REF","mensagem":"Referencia NT-01/2025 nao encontrada","severidade":"ALERTA"})
total = len(exigencias)
score = round(len(conformes) / total * 100 if total > 0 else 0, 1)
apto = len(erros) == 0
return {
"status": "APTO PARA PROTOCOLO" if apto else "PENDENCIAS ENCONTRADAS",
"score_conformidade": score, "apto": apto,
"total_exigencias": total, "conformes": conformes,
"erros": erros, "alertas": alertas,
"data_auditoria": datetime.now().isoformat(),
}
def formatar_relatorio(self, resultado):
status = resultado.get("status","?")
score = resultado.get("score_conformidade", 0)
erros = resultado.get("erros", [])
alertas = resultado.get("alertas", [])
conformes = resultado.get("conformes", [])
linhas = [
"=" * 60,
"RELATORIO DE AUDITORIA - PSCIP",
f"Data: {datetime.now().strftime('%d/%m/%Y %H:%M')}",
f"STATUS: {status}",
f"Score de conformidade: {score}%",
"=" * 60,
]
if conformes:
linhas.append(f"\nITENS CONFORMES ({len(conformes)}):")
for c in conformes:
linhas.append(f" [OK] {c['descricao']}")
if erros:
linhas.append(f"\nERROS CRITICOS ({len(erros)}) - IMPEDEM PROTOCOLO:")
for e in erros:
linhas.append(f" [!!] {e['descricao']} - INCLUIR NO MEMORIAL")
if alertas:
linhas.append(f"\nALERTAS ({len(alertas)}):")
for a in alertas:
linhas.append(f" [{a.get('severidade','?')}] {a.get('mensagem',a.get('descricao',''))}")
linhas.extend(["", "Ref: NT-01/2025 CBMGO", "=" * 60])
return "\n".join(linhas)
_auditor_instance = None
def get_auditor():
global _auditor_instance
if _auditor_instance is None:
_auditor_instance = AuditorProjetos()
return _auditor_instance
if __name__ == "__main__":
auditor = AuditorProjetos()
memorial = """MEMORIAL DESCRITIVO - PSCIP
Edificacao comercial 1500m2, 2 pavimentos.
Extintores portateis: 8 un, 2-A:20-B:C, NT-01/2025.
Hidrantes: coluna seca, 300 L/min, NBR 13714.
Sinalizacao de emergencia: NBR 13434.
SPDA: obrigatorio area > 1000m2, NBR 5419.
Responsavel Tecnico: Eng. Silva CREA 123456
"""
dados = {"area_m2": 1500, "altura_m": 7, "ocupacao": "comercial", "lotacao": 100}
res = auditor.auditar_memorial(memorial, dados)
print(auditor.formatar_relatorio(res))