File size: 8,086 Bytes
16e2ec0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
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))
|