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))