CERCON / modules /calculadora.py
carlosh10's picture
feat: Adiciona calculadora de incendio NT-01/2025 completa
5e5dced verified
"""
Calculadora de Incendio - NT-01/2025 CBMGO
Calculos normativos: extintores, hidrantes, SPDA, iluminacao, lotacao.
"""
import math
from typing import Dict, List, Tuple
class CalculadoraIncendio:
"""
Calculadora normativa para sistemas de prevencao de incendio.
Baseada na NT-01/2025 CBMGO e normas ABNT associadas.
"""
# Tabela de extintores por grupo (NT-01/2025 Anexo B)
TABELA_EXTINTORES = {
"A": {"area": 500, "cap": "2-A:10-B:C", "dist_max": 15},
"B": {"area": 250, "cap": "2-A:20-B:C", "dist_max": 15},
"C": {"area": 300, "cap": "2-A:10-B:C", "dist_max": 15},
"D": {"area": 400, "cap": "2-A:20-B:C", "dist_max": 15},
"E": {"area": 300, "cap": "2-A:10-B:C", "dist_max": 15},
"F": {"area": 200, "cap": "2-A:20-B:C", "dist_max": 15},
"G": {"area": 250, "cap": "2-A:40-B:C", "dist_max": 15},
"H": {"area": 500, "cap": "2-A:10-B:C", "dist_max": 15},
"I": {"area": 300, "cap": "4-A:20-B:C", "dist_max": 15},
}
# Indices de lotacao por ocupacao (pessoa/m2) - NT-01/2025 Tabela 2
INDICES_LOTACAO = {
"residencial": 18.0,
"escritorio": 7.0,
"comercio_varejo": 3.0,
"supermercado": 2.5,
"restaurante": 1.5,
"bar_lanchonete": 1.2,
"hotel": 25.0,
"hospital": 15.0,
"escola": 2.0,
"auditorio": 0.7,
"teatro_cinema": 0.65,
"garagem": 30.0,
"industria_leve": 10.0,
"industria_pesada": 20.0,
"deposito": 50.0,
"ginasio": 0.5,
"academia": 4.0,
}
def calcular_extintores(self, area_m2: float, grupo: str) -> Dict:
"""Calcula extintores conforme NT-01/2025 Anexo B."""
cfg = self.TABELA_EXTINTORES.get(grupo.upper(), self.TABELA_EXTINTORES["D"])
qtd = max(1, math.ceil(area_m2 / cfg["area"]))
# Minimo de 2 extintores por pavimento para areas > 200m2
if area_m2 > 200:
qtd = max(2, qtd)
return {
"quantidade_minima": qtd,
"capacidade_extintora": cfg["cap"],
"distancia_maxima_m": cfg["dist_max"],
"area_por_extintor_m2": cfg["area"],
"tipos_recomendados": self._tipos_extintor(grupo),
"referencia": f"NT-01/2025 Anexo B - Grupo {grupo.upper()} | ABNT NBR 12693"
}
def _tipos_extintor(self, grupo: str) -> List[str]:
"""Recomenda tipos de agente extintor por grupo de ocupacao."""
g = grupo.upper()
if g in ["A", "E", "G"]:
return ["Po ABC", "Agua pressurizada (apenas para classe A)"]
elif g in ["B", "C", "D", "H"]:
return ["Po ABC", "CO2 (proximo a equipamentos eletronicos)"]
elif g in ["F", "I"]:
return ["Po ABC", "CO2", "Espuma (para liquidos inflamaveis se aplicavel)"]
return ["Po ABC"]
def calcular_hidrantes(self, area_m2: float, altura_m: float,
ocupacao: str = "comercial") -> Dict:
"""Calcula sistema de hidrantes conforme NT-01/2025 Art. 18."""
# Determinar sistema necessario
if altura_m < 6 and area_m2 < 750:
sistema = "Mangueira de 1a intervencao (hidrante simples)"
vazao_lpm = 150
pressao_min_mca = 10
reserva_m3 = round(vazao_lpm * 30 / 1000, 1)
obrigatorio = False
elif altura_m < 12:
sistema = "Sistema de Mangueiras / Coluna Seca"
vazao_lpm = 300
pressao_min_mca = 15
reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
obrigatorio = True
elif altura_m < 30:
sistema = "Hidrante com Reservatorio Elevado"
vazao_lpm = 450
pressao_min_mca = 20
reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
obrigatorio = True
else:
sistema = "Chuveiros Automaticos + Hidrante (Alta Pressao)"
vazao_lpm = 600
pressao_min_mca = 30
reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
obrigatorio = True
# Ajuste para uso industrial de alto risco
if "industrial" in ocupacao.lower() or "deposito" in ocupacao.lower():
vazao_lpm = int(vazao_lpm * 1.5)
reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
return {
"sistema": sistema,
"obrigatorio": obrigatorio,
"vazao_minima_lpm": vazao_lpm,
"pressao_minima_mca": pressao_min_mca,
"reserva_tecnica_m3": reserva_m3,
"diametro_tubulacao_min_mm": 65 if vazao_lpm <= 300 else 100,
"referencia": "NT-01/2025 Art. 18 | ABNT NBR 13714"
}
def calcular_lotacao(self, area_m2: float, uso: str) -> Dict:
"""Calcula lotacao maxima conforme NT-01/2025 Tabela 2."""
indice = self.INDICES_LOTACAO.get(uso.lower().replace(" ", "_"),
self.INDICES_LOTACAO["comercio_varejo"])
lotacao = max(1, math.ceil(area_m2 / indice))
# Numero minimo de saidas de emergencia
if lotacao <= 50:
saidas = 1
elif lotacao <= 200:
saidas = 2
elif lotacao <= 1000:
saidas = 3
else:
saidas = 4
# Largura minima das saidas
modulos = math.ceil(lotacao / 60)
largura_m = modulos * 0.55
largura_m = max(0.90, largura_m)
return {
"lotacao_maxima": lotacao,
"indice_m2_por_pessoa": indice,
"saidas_emergencia_minimo": saidas,
"largura_minima_saidas_m": round(largura_m, 2),
"modulos_saida": modulos,
"referencia": "NT-01/2025 Tabela 2 | ABNT NBR 9077"
}
def calcular_spda(self, area_m2: float, altura_m: float,
uso: str = "comercial") -> Dict:
"""Avalia necessidade de SPDA conforme NT-01/2025 Art. 25."""
usos_criticos = ["hospital", "escola", "industria", "deposito_inflamavel",
"reuniao", "hotel"]
obrigatorio = (
area_m2 > 1000 or
altura_m > 20 or
any(u in uso.lower() for u in usos_criticos)
)
nivel_protecao = "IV"
if area_m2 > 5000 or altura_m > 45:
nivel_protecao = "II"
elif area_m2 > 2000 or altura_m > 30:
nivel_protecao = "III"
return {
"obrigatorio": obrigatorio,
"nivel_protecao_nbr": nivel_protecao,
"justificativa": (
"Obrigatorio: area > 1000m2" if area_m2 > 1000
else "Obrigatorio: altura > 20m" if altura_m > 20
else "Obrigatorio: uso critico" if obrigatorio
else "Verificar com projeto especifico"
),
"norma": "ABNT NBR 5419",
"referencia": "NT-01/2025 Art. 25 | ABNT NBR 5419"
}
def calcular_iluminacao_emergencia(self, area_m2: float,
altura_m: float,
lotacao: int = 0) -> Dict:
"""Calcula sistema de iluminacao de emergencia conforme NT-01/2025."""
obrigatorio = (
altura_m > 12 or
lotacao > 50 or
area_m2 > 500
)
if obrigatorio:
autonomia_h = 1.0
nivel_lux_rota = 3
nivel_lux_acesso = 10
pontos_min = max(2, math.ceil(area_m2 / 100))
else:
autonomia_h = 0
nivel_lux_rota = 0
nivel_lux_acesso = 0
pontos_min = 0
return {
"obrigatorio": obrigatorio,
"autonomia_horas": autonomia_h,
"nivel_iluminamento_rota_lux": nivel_lux_rota,
"nivel_iluminamento_acesso_lux": nivel_lux_acesso,
"pontos_minimos_estimados": pontos_min,
"referencia": "NT-01/2025 Art. 20 | ABNT NBR 10898"
}
def calcular_chuveiros_automaticos(self, area_m2: float,
altura_m: float,
ocupacao: str = "comercial") -> Dict:
"""Avalia necessidade de chuveiros automaticos conforme NT-01/2025."""
usos_obrigatorios = ["hotel", "hospital", "shopping", "industrial_alto",
"deposito_alto", "reuniao_grande"]
obrigatorio = (
altura_m > 30 or
(area_m2 > 2000 and "industrial" in ocupacao.lower()) or
any(u in ocupacao.lower() for u in usos_obrigatorios)
)
if obrigatorio:
densidade_mm_min = 6 if "industrial" in ocupacao.lower() else 5
area_atuacao_m2 = 72 if altura_m > 30 else 144
else:
densidade_mm_min = 0
area_atuacao_m2 = 0
return {
"obrigatorio": obrigatorio,
"densidade_minima_mm_min": densidade_mm_min,
"area_atuacao_m2": area_atuacao_m2,
"tipo_chuveiro": "Resposta rapida" if obrigatorio else "N/A",
"referencia": "NT-01/2025 | ABNT NBR 10897"
}
def calcular_tudo(self, dados: Dict) -> Dict:
"""Executa todos os calculos para um projeto."""
area = dados.get("area_m2", 0)
altura = dados.get("altura_m", 0)
grupo = dados.get("grupo", "D")
ocupacao = dados.get("ocupacao", "comercial")
uso = dados.get("uso", ocupacao)
# Calcular lotacao se nao fornecida
lotacao_calc = self.calcular_lotacao(area, uso)
lotacao = dados.get("lotacao", lotacao_calc["lotacao_maxima"])
return {
"extintores": self.calcular_extintores(area, grupo),
"hidrantes": self.calcular_hidrantes(area, altura, ocupacao),
"lotacao": lotacao_calc,
"spda": self.calcular_spda(area, altura, ocupacao),
"iluminacao_emergencia": self.calcular_iluminacao_emergencia(
area, altura, lotacao),
"chuveiros_automaticos": self.calcular_chuveiros_automaticos(
area, altura, ocupacao),
}
def formatar_relatorio(self, resultados: Dict, dados_projeto: Dict) -> str:
"""Gera relatorio formatado dos calculos."""
area = dados_projeto.get("area_m2", 0)
altura = dados_projeto.get("altura_m", 0)
grupo = dados_projeto.get("grupo", "D")
nome = dados_projeto.get("nome", "Edificacao")
ext = resultados["extintores"]
hid = resultados["hidrantes"]
lot = resultados["lotacao"]
spd = resultados["spda"]
ilu = resultados["iluminacao_emergencia"]
chu = resultados["chuveiros_automaticos"]
linhas = [
"=" * 60,
"RELATORIO DE CALCULOS - PSCIP",
f"Edificacao: {nome}",
f"Area: {area:.1f} m2 | Altura: {altura:.1f} m | Grupo NT-01: {grupo}",
f"Ref: NT-01/2025 CBMGO",
"=" * 60,
"",
"1. EXTINTORES PORTATEIS",
f" Quantidade minima: {ext['quantidade_minima']} unidades",
f" Capacidade extintora: {ext['capacidade_extintora']}",
f" Dist. maxima ao extintor: {ext['distancia_maxima_m']} m",
f" Tipos: {', '.join(ext['tipos_recomendados'])}",
f" Base: {ext['referencia']}",
"",
"2. SISTEMA DE HIDRANTES",
f" Sistema: {hid['sistema']}",
f" Obrigatorio: {'SIM' if hid['obrigatorio'] else 'NAO'}",
f" Vazao minima: {hid['vazao_minima_lpm']} L/min",
f" Pressao minima: {hid['pressao_minima_mca']} mca",
f" Reserva tecnica: {hid['reserva_tecnica_m3']} m3",
f" Base: {hid['referencia']}",
"",
"3. LOTACAO E SAIDAS DE EMERGENCIA",
f" Lotacao maxima estimada: {lot['lotacao_maxima']} pessoas",
f" Indice: {lot['indice_m2_por_pessoa']} m2/pessoa",
f" Saidas de emergencia (minimo): {lot['saidas_emergencia_minimo']}",
f" Largura minima das saidas: {lot['largura_minima_saidas_m']} m",
f" Base: {lot['referencia']}",
"",
"4. SPDA",
f" Obrigatorio: {'SIM' if spd['obrigatorio'] else 'NAO'}",
f" Nivel de protecao: {spd['nivel_protecao_nbr']} (NBR 5419)",
f" Justificativa: {spd['justificativa']}",
"",
"5. ILUMINACAO DE EMERGENCIA",
f" Obrigatoria: {'SIM' if ilu['obrigatorio'] else 'NAO'}",
f" Autonomia: {ilu['autonomia_horas']} hora(s)",
f" Iluminamento rota de fuga: >= {ilu['nivel_iluminamento_rota_lux']} lux",
f" Pontos estimados: {ilu['pontos_minimos_estimados']}",
f" Base: {ilu['referencia']}",
"",
"6. CHUVEIROS AUTOMATICOS",
f" Obrigatorio: {'SIM' if chu['obrigatorio'] else 'NAO'}",
]
if chu["obrigatorio"]:
linhas.append(f" Densidade minima: {chu['densidade_minima_mm_min']} mm/min")
linhas.append(f" Area de atuacao: {chu['area_atuacao_m2']} m2")
linhas.append(f" Base: {chu['referencia']}")
linhas.append("")
linhas.append("=" * 60)
return "\n".join(linhas)
# Instancia global
_calc_instance = None
def get_calculadora() -> CalculadoraIncendio:
global _calc_instance
if _calc_instance is None:
_calc_instance = CalculadoraIncendio()
return _calc_instance
if __name__ == "__main__":
calc = CalculadoraIncendio()
dados = {
"nome": "Edificio Comercial Exemplo",
"area_m2": 1200,
"altura_m": 15,
"grupo": "D",
"ocupacao": "comercial",
"uso": "comercio_varejo"
}
resultados = calc.calcular_tudo(dados)
print(calc.formatar_relatorio(resultados, dados))