carlosh10 commited on
Commit
5e5dced
·
verified ·
1 Parent(s): ffd03d5

feat: Adiciona calculadora de incendio NT-01/2025 completa

Browse files
Files changed (1) hide show
  1. modules/calculadora.py +357 -0
modules/calculadora.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Calculadora de Incendio - NT-01/2025 CBMGO
3
+ Calculos normativos: extintores, hidrantes, SPDA, iluminacao, lotacao.
4
+ """
5
+ import math
6
+ from typing import Dict, List, Tuple
7
+
8
+
9
+ class CalculadoraIncendio:
10
+ """
11
+ Calculadora normativa para sistemas de prevencao de incendio.
12
+ Baseada na NT-01/2025 CBMGO e normas ABNT associadas.
13
+ """
14
+
15
+ # Tabela de extintores por grupo (NT-01/2025 Anexo B)
16
+ TABELA_EXTINTORES = {
17
+ "A": {"area": 500, "cap": "2-A:10-B:C", "dist_max": 15},
18
+ "B": {"area": 250, "cap": "2-A:20-B:C", "dist_max": 15},
19
+ "C": {"area": 300, "cap": "2-A:10-B:C", "dist_max": 15},
20
+ "D": {"area": 400, "cap": "2-A:20-B:C", "dist_max": 15},
21
+ "E": {"area": 300, "cap": "2-A:10-B:C", "dist_max": 15},
22
+ "F": {"area": 200, "cap": "2-A:20-B:C", "dist_max": 15},
23
+ "G": {"area": 250, "cap": "2-A:40-B:C", "dist_max": 15},
24
+ "H": {"area": 500, "cap": "2-A:10-B:C", "dist_max": 15},
25
+ "I": {"area": 300, "cap": "4-A:20-B:C", "dist_max": 15},
26
+ }
27
+
28
+ # Indices de lotacao por ocupacao (pessoa/m2) - NT-01/2025 Tabela 2
29
+ INDICES_LOTACAO = {
30
+ "residencial": 18.0,
31
+ "escritorio": 7.0,
32
+ "comercio_varejo": 3.0,
33
+ "supermercado": 2.5,
34
+ "restaurante": 1.5,
35
+ "bar_lanchonete": 1.2,
36
+ "hotel": 25.0,
37
+ "hospital": 15.0,
38
+ "escola": 2.0,
39
+ "auditorio": 0.7,
40
+ "teatro_cinema": 0.65,
41
+ "garagem": 30.0,
42
+ "industria_leve": 10.0,
43
+ "industria_pesada": 20.0,
44
+ "deposito": 50.0,
45
+ "ginasio": 0.5,
46
+ "academia": 4.0,
47
+ }
48
+
49
+ def calcular_extintores(self, area_m2: float, grupo: str) -> Dict:
50
+ """Calcula extintores conforme NT-01/2025 Anexo B."""
51
+ cfg = self.TABELA_EXTINTORES.get(grupo.upper(), self.TABELA_EXTINTORES["D"])
52
+ qtd = max(1, math.ceil(area_m2 / cfg["area"]))
53
+
54
+ # Minimo de 2 extintores por pavimento para areas > 200m2
55
+ if area_m2 > 200:
56
+ qtd = max(2, qtd)
57
+
58
+ return {
59
+ "quantidade_minima": qtd,
60
+ "capacidade_extintora": cfg["cap"],
61
+ "distancia_maxima_m": cfg["dist_max"],
62
+ "area_por_extintor_m2": cfg["area"],
63
+ "tipos_recomendados": self._tipos_extintor(grupo),
64
+ "referencia": f"NT-01/2025 Anexo B - Grupo {grupo.upper()} | ABNT NBR 12693"
65
+ }
66
+
67
+ def _tipos_extintor(self, grupo: str) -> List[str]:
68
+ """Recomenda tipos de agente extintor por grupo de ocupacao."""
69
+ g = grupo.upper()
70
+ if g in ["A", "E", "G"]:
71
+ return ["Po ABC", "Agua pressurizada (apenas para classe A)"]
72
+ elif g in ["B", "C", "D", "H"]:
73
+ return ["Po ABC", "CO2 (proximo a equipamentos eletronicos)"]
74
+ elif g in ["F", "I"]:
75
+ return ["Po ABC", "CO2", "Espuma (para liquidos inflamaveis se aplicavel)"]
76
+ return ["Po ABC"]
77
+
78
+ def calcular_hidrantes(self, area_m2: float, altura_m: float,
79
+ ocupacao: str = "comercial") -> Dict:
80
+ """Calcula sistema de hidrantes conforme NT-01/2025 Art. 18."""
81
+
82
+ # Determinar sistema necessario
83
+ if altura_m < 6 and area_m2 < 750:
84
+ sistema = "Mangueira de 1a intervencao (hidrante simples)"
85
+ vazao_lpm = 150
86
+ pressao_min_mca = 10
87
+ reserva_m3 = round(vazao_lpm * 30 / 1000, 1)
88
+ obrigatorio = False
89
+ elif altura_m < 12:
90
+ sistema = "Sistema de Mangueiras / Coluna Seca"
91
+ vazao_lpm = 300
92
+ pressao_min_mca = 15
93
+ reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
94
+ obrigatorio = True
95
+ elif altura_m < 30:
96
+ sistema = "Hidrante com Reservatorio Elevado"
97
+ vazao_lpm = 450
98
+ pressao_min_mca = 20
99
+ reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
100
+ obrigatorio = True
101
+ else:
102
+ sistema = "Chuveiros Automaticos + Hidrante (Alta Pressao)"
103
+ vazao_lpm = 600
104
+ pressao_min_mca = 30
105
+ reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
106
+ obrigatorio = True
107
+
108
+ # Ajuste para uso industrial de alto risco
109
+ if "industrial" in ocupacao.lower() or "deposito" in ocupacao.lower():
110
+ vazao_lpm = int(vazao_lpm * 1.5)
111
+ reserva_m3 = round(vazao_lpm * 60 / 1000, 1)
112
+
113
+ return {
114
+ "sistema": sistema,
115
+ "obrigatorio": obrigatorio,
116
+ "vazao_minima_lpm": vazao_lpm,
117
+ "pressao_minima_mca": pressao_min_mca,
118
+ "reserva_tecnica_m3": reserva_m3,
119
+ "diametro_tubulacao_min_mm": 65 if vazao_lpm <= 300 else 100,
120
+ "referencia": "NT-01/2025 Art. 18 | ABNT NBR 13714"
121
+ }
122
+
123
+ def calcular_lotacao(self, area_m2: float, uso: str) -> Dict:
124
+ """Calcula lotacao maxima conforme NT-01/2025 Tabela 2."""
125
+ indice = self.INDICES_LOTACAO.get(uso.lower().replace(" ", "_"),
126
+ self.INDICES_LOTACAO["comercio_varejo"])
127
+ lotacao = max(1, math.ceil(area_m2 / indice))
128
+
129
+ # Numero minimo de saidas de emergencia
130
+ if lotacao <= 50:
131
+ saidas = 1
132
+ elif lotacao <= 200:
133
+ saidas = 2
134
+ elif lotacao <= 1000:
135
+ saidas = 3
136
+ else:
137
+ saidas = 4
138
+
139
+ # Largura minima das saidas
140
+ modulos = math.ceil(lotacao / 60)
141
+ largura_m = modulos * 0.55
142
+ largura_m = max(0.90, largura_m)
143
+
144
+ return {
145
+ "lotacao_maxima": lotacao,
146
+ "indice_m2_por_pessoa": indice,
147
+ "saidas_emergencia_minimo": saidas,
148
+ "largura_minima_saidas_m": round(largura_m, 2),
149
+ "modulos_saida": modulos,
150
+ "referencia": "NT-01/2025 Tabela 2 | ABNT NBR 9077"
151
+ }
152
+
153
+ def calcular_spda(self, area_m2: float, altura_m: float,
154
+ uso: str = "comercial") -> Dict:
155
+ """Avalia necessidade de SPDA conforme NT-01/2025 Art. 25."""
156
+ usos_criticos = ["hospital", "escola", "industria", "deposito_inflamavel",
157
+ "reuniao", "hotel"]
158
+
159
+ obrigatorio = (
160
+ area_m2 > 1000 or
161
+ altura_m > 20 or
162
+ any(u in uso.lower() for u in usos_criticos)
163
+ )
164
+
165
+ nivel_protecao = "IV"
166
+ if area_m2 > 5000 or altura_m > 45:
167
+ nivel_protecao = "II"
168
+ elif area_m2 > 2000 or altura_m > 30:
169
+ nivel_protecao = "III"
170
+
171
+ return {
172
+ "obrigatorio": obrigatorio,
173
+ "nivel_protecao_nbr": nivel_protecao,
174
+ "justificativa": (
175
+ "Obrigatorio: area > 1000m2" if area_m2 > 1000
176
+ else "Obrigatorio: altura > 20m" if altura_m > 20
177
+ else "Obrigatorio: uso critico" if obrigatorio
178
+ else "Verificar com projeto especifico"
179
+ ),
180
+ "norma": "ABNT NBR 5419",
181
+ "referencia": "NT-01/2025 Art. 25 | ABNT NBR 5419"
182
+ }
183
+
184
+ def calcular_iluminacao_emergencia(self, area_m2: float,
185
+ altura_m: float,
186
+ lotacao: int = 0) -> Dict:
187
+ """Calcula sistema de iluminacao de emergencia conforme NT-01/2025."""
188
+ obrigatorio = (
189
+ altura_m > 12 or
190
+ lotacao > 50 or
191
+ area_m2 > 500
192
+ )
193
+
194
+ if obrigatorio:
195
+ autonomia_h = 1.0
196
+ nivel_lux_rota = 3
197
+ nivel_lux_acesso = 10
198
+ pontos_min = max(2, math.ceil(area_m2 / 100))
199
+ else:
200
+ autonomia_h = 0
201
+ nivel_lux_rota = 0
202
+ nivel_lux_acesso = 0
203
+ pontos_min = 0
204
+
205
+ return {
206
+ "obrigatorio": obrigatorio,
207
+ "autonomia_horas": autonomia_h,
208
+ "nivel_iluminamento_rota_lux": nivel_lux_rota,
209
+ "nivel_iluminamento_acesso_lux": nivel_lux_acesso,
210
+ "pontos_minimos_estimados": pontos_min,
211
+ "referencia": "NT-01/2025 Art. 20 | ABNT NBR 10898"
212
+ }
213
+
214
+ def calcular_chuveiros_automaticos(self, area_m2: float,
215
+ altura_m: float,
216
+ ocupacao: str = "comercial") -> Dict:
217
+ """Avalia necessidade de chuveiros automaticos conforme NT-01/2025."""
218
+ usos_obrigatorios = ["hotel", "hospital", "shopping", "industrial_alto",
219
+ "deposito_alto", "reuniao_grande"]
220
+
221
+ obrigatorio = (
222
+ altura_m > 30 or
223
+ (area_m2 > 2000 and "industrial" in ocupacao.lower()) or
224
+ any(u in ocupacao.lower() for u in usos_obrigatorios)
225
+ )
226
+
227
+ if obrigatorio:
228
+ densidade_mm_min = 6 if "industrial" in ocupacao.lower() else 5
229
+ area_atuacao_m2 = 72 if altura_m > 30 else 144
230
+ else:
231
+ densidade_mm_min = 0
232
+ area_atuacao_m2 = 0
233
+
234
+ return {
235
+ "obrigatorio": obrigatorio,
236
+ "densidade_minima_mm_min": densidade_mm_min,
237
+ "area_atuacao_m2": area_atuacao_m2,
238
+ "tipo_chuveiro": "Resposta rapida" if obrigatorio else "N/A",
239
+ "referencia": "NT-01/2025 | ABNT NBR 10897"
240
+ }
241
+
242
+ def calcular_tudo(self, dados: Dict) -> Dict:
243
+ """Executa todos os calculos para um projeto."""
244
+ area = dados.get("area_m2", 0)
245
+ altura = dados.get("altura_m", 0)
246
+ grupo = dados.get("grupo", "D")
247
+ ocupacao = dados.get("ocupacao", "comercial")
248
+ uso = dados.get("uso", ocupacao)
249
+
250
+ # Calcular lotacao se nao fornecida
251
+ lotacao_calc = self.calcular_lotacao(area, uso)
252
+ lotacao = dados.get("lotacao", lotacao_calc["lotacao_maxima"])
253
+
254
+ return {
255
+ "extintores": self.calcular_extintores(area, grupo),
256
+ "hidrantes": self.calcular_hidrantes(area, altura, ocupacao),
257
+ "lotacao": lotacao_calc,
258
+ "spda": self.calcular_spda(area, altura, ocupacao),
259
+ "iluminacao_emergencia": self.calcular_iluminacao_emergencia(
260
+ area, altura, lotacao),
261
+ "chuveiros_automaticos": self.calcular_chuveiros_automaticos(
262
+ area, altura, ocupacao),
263
+ }
264
+
265
+ def formatar_relatorio(self, resultados: Dict, dados_projeto: Dict) -> str:
266
+ """Gera relatorio formatado dos calculos."""
267
+ area = dados_projeto.get("area_m2", 0)
268
+ altura = dados_projeto.get("altura_m", 0)
269
+ grupo = dados_projeto.get("grupo", "D")
270
+ nome = dados_projeto.get("nome", "Edificacao")
271
+
272
+ ext = resultados["extintores"]
273
+ hid = resultados["hidrantes"]
274
+ lot = resultados["lotacao"]
275
+ spd = resultados["spda"]
276
+ ilu = resultados["iluminacao_emergencia"]
277
+ chu = resultados["chuveiros_automaticos"]
278
+
279
+ linhas = [
280
+ "=" * 60,
281
+ "RELATORIO DE CALCULOS - PSCIP",
282
+ f"Edificacao: {nome}",
283
+ f"Area: {area:.1f} m2 | Altura: {altura:.1f} m | Grupo NT-01: {grupo}",
284
+ f"Ref: NT-01/2025 CBMGO",
285
+ "=" * 60,
286
+ "",
287
+ "1. EXTINTORES PORTATEIS",
288
+ f" Quantidade minima: {ext['quantidade_minima']} unidades",
289
+ f" Capacidade extintora: {ext['capacidade_extintora']}",
290
+ f" Dist. maxima ao extintor: {ext['distancia_maxima_m']} m",
291
+ f" Tipos: {', '.join(ext['tipos_recomendados'])}",
292
+ f" Base: {ext['referencia']}",
293
+ "",
294
+ "2. SISTEMA DE HIDRANTES",
295
+ f" Sistema: {hid['sistema']}",
296
+ f" Obrigatorio: {'SIM' if hid['obrigatorio'] else 'NAO'}",
297
+ f" Vazao minima: {hid['vazao_minima_lpm']} L/min",
298
+ f" Pressao minima: {hid['pressao_minima_mca']} mca",
299
+ f" Reserva tecnica: {hid['reserva_tecnica_m3']} m3",
300
+ f" Base: {hid['referencia']}",
301
+ "",
302
+ "3. LOTACAO E SAIDAS DE EMERGENCIA",
303
+ f" Lotacao maxima estimada: {lot['lotacao_maxima']} pessoas",
304
+ f" Indice: {lot['indice_m2_por_pessoa']} m2/pessoa",
305
+ f" Saidas de emergencia (minimo): {lot['saidas_emergencia_minimo']}",
306
+ f" Largura minima das saidas: {lot['largura_minima_saidas_m']} m",
307
+ f" Base: {lot['referencia']}",
308
+ "",
309
+ "4. SPDA",
310
+ f" Obrigatorio: {'SIM' if spd['obrigatorio'] else 'NAO'}",
311
+ f" Nivel de protecao: {spd['nivel_protecao_nbr']} (NBR 5419)",
312
+ f" Justificativa: {spd['justificativa']}",
313
+ "",
314
+ "5. ILUMINACAO DE EMERGENCIA",
315
+ f" Obrigatoria: {'SIM' if ilu['obrigatorio'] else 'NAO'}",
316
+ f" Autonomia: {ilu['autonomia_horas']} hora(s)",
317
+ f" Iluminamento rota de fuga: >= {ilu['nivel_iluminamento_rota_lux']} lux",
318
+ f" Pontos estimados: {ilu['pontos_minimos_estimados']}",
319
+ f" Base: {ilu['referencia']}",
320
+ "",
321
+ "6. CHUVEIROS AUTOMATICOS",
322
+ f" Obrigatorio: {'SIM' if chu['obrigatorio'] else 'NAO'}",
323
+ ]
324
+
325
+ if chu["obrigatorio"]:
326
+ linhas.append(f" Densidade minima: {chu['densidade_minima_mm_min']} mm/min")
327
+ linhas.append(f" Area de atuacao: {chu['area_atuacao_m2']} m2")
328
+ linhas.append(f" Base: {chu['referencia']}")
329
+
330
+ linhas.append("")
331
+ linhas.append("=" * 60)
332
+
333
+ return "\n".join(linhas)
334
+
335
+
336
+ # Instancia global
337
+ _calc_instance = None
338
+
339
+ def get_calculadora() -> CalculadoraIncendio:
340
+ global _calc_instance
341
+ if _calc_instance is None:
342
+ _calc_instance = CalculadoraIncendio()
343
+ return _calc_instance
344
+
345
+
346
+ if __name__ == "__main__":
347
+ calc = CalculadoraIncendio()
348
+ dados = {
349
+ "nome": "Edificio Comercial Exemplo",
350
+ "area_m2": 1200,
351
+ "altura_m": 15,
352
+ "grupo": "D",
353
+ "ocupacao": "comercial",
354
+ "uso": "comercio_varejo"
355
+ }
356
+ resultados = calc.calcular_tudo(dados)
357
+ print(calc.formatar_relatorio(resultados, dados))