File size: 10,743 Bytes
cf52a55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
"""
CAMADA L1 — Tábua de Conceitos (Aristóteles: Categorias)
=========================================================
Mapeia cada termo do prompt a relações semânticas fixas:
  - Sinonímia   : mesma denotação
  - Antonímia   : oposição semântica direta
  - Hiponímia   : relação específico → geral
  - Homonímia   : mesma forma, sentidos distintos
  - Paronímia   : semelhança formal, sentidos distintos

As relações são BINÁRIAS nesta camada — elimina a necessidade de
defuzzificação posterior na camada L3.
"""

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, List, Optional
import re
import json
import os


@dataclass
class ConceptNode:
    """Um conceito na tábua, com todas as suas relações."""
    term: str
    definition: str = ""
    synonyms:   List[str] = field(default_factory=list)
    antonyms:   List[str] = field(default_factory=list)
    hyponyms:   List[str] = field(default_factory=list)   # mais específicos
    hypernyms:  List[str] = field(default_factory=list)   # mais gerais
    homonyms:   Dict[str, str] = field(default_factory=dict)  # sentido → definição
    paronyms:   List[str] = field(default_factory=list)
    domain:     str = "geral"


class ConceptTable:
    """
    Tábua de conceitos fixos.  Em produção seria alimentada por um
    dicionário / ontologia formal (WordNet-PT, OpenWordNet-PT, etc.).
    Aqui usamos um conjunto seminal suficiente para demonstrar todas
    as camadas do modelo.
    """

    def __init__(self) -> None:
        self._table: Dict[str, ConceptNode] = {}
        # Tábua seminal em português
        self._build_seed_table()
        # Banco de conceitos em inglês aprendido de dicionário externo (se existir)
        self._load_external_concepts()

    # ------------------------------------------------------------------ #
    # API pública                                                          #
    # ------------------------------------------------------------------ #

    def get(self, term: str) -> Optional[ConceptNode]:
        return self._table.get(self._normalize(term))

    def extract_concepts(self, text: str) -> List[ConceptNode]:
        """Extrai e retorna os nós de todos os termos encontrados no texto."""
        tokens = re.findall(r"[a-záàãâéêíóôõúüçA-ZÁÀÃÂÉÊÍÓÔÕÚÜÇ]+", text)
        seen, result = set(), []
        for tok in tokens:
            key = self._normalize(tok)
            if key not in seen:
                node = self._table.get(key)
                if node:
                    seen.add(key)
                    result.append(node)
        return result

    def add(self, node: ConceptNode) -> None:
        self._table[self._normalize(node.term)] = node

    def relation_type(self, term_a: str, term_b: str) -> str:
        """Retorna o tipo de relação semântica entre dois termos."""
        a = self._normalize(term_a)
        b = self._normalize(term_b)
        node_a = self._table.get(a)
        if not node_a:
            return "desconhecida"
        if b in [self._normalize(s) for s in node_a.synonyms]:
            return "sinonímia"
        if b in [self._normalize(s) for s in node_a.antonyms]:
            return "antonímia"
        if b in [self._normalize(s) for s in node_a.hyponyms]:
            return "hiponímia"
        if b in [self._normalize(s) for s in node_a.hypernyms]:
            return "hiperonímia"
        if b in [self._normalize(s) for s in node_a.paronyms]:
            return "paronímia"
        if b in [self._normalize(k) for k in node_a.homonyms]:
            return "homonímia"
        return "sem_relação_direta"

    # ------------------------------------------------------------------ #
    # Construção da tábua seminal                                          #
    # ------------------------------------------------------------------ #

    def _build_seed_table(self) -> None:
        entries = [
            ConceptNode(
                term="quente",
                definition="Que possui temperatura alta.",
                synonyms=["aquecido", "cálido", "morno", "tépido"],
                antonyms=["frio", "gelado", "fresco"],
                hypernyms=["temperatura"],
                hyponyms=["escaldante", "ardente"],
                domain="físico",
            ),
            ConceptNode(
                term="frio",
                definition="Que possui temperatura baixa.",
                synonyms=["gelado", "fresco", "frígido"],
                antonyms=["quente", "aquecido", "cálido"],
                hypernyms=["temperatura"],
                hyponyms=["congelado", "glacial"],
                domain="físico",
            ),
            ConceptNode(
                term="morno",
                definition="Entre quente e frio; tépido.",
                synonyms=["tépido", "ameno"],
                antonyms=["escaldante", "glacial"],
                hypernyms=["temperatura", "quente", "frio"],
                hyponyms=[],
                domain="físico",
            ),
            ConceptNode(
                term="temperatura",
                definition="Grandeza física que mede o grau de calor de um corpo.",
                synonyms=["calor", "grau"],
                antonyms=[],
                hypernyms=["grandeza_física"],
                hyponyms=["quente", "frio", "morno"],
                domain="físico",
            ),
            ConceptNode(
                term="água",
                definition="Substância H2O, geralmente em estado líquido.",
                synonyms=["H2O", "líquido"],
                antonyms=[],
                hypernyms=["substância", "fluido"],
                hyponyms=["vapor", "gelo"],
                domain="físico",
            ),
            ConceptNode(
                term="verdadeiro",
                definition="Que está de acordo com os fatos ou a realidade.",
                synonyms=["correto", "real", "factual"],
                antonyms=["falso", "incorreto", "fictício"],
                hypernyms=["valor_lógico"],
                domain="lógica",
            ),
            ConceptNode(
                term="falso",
                definition="Que não corresponde aos fatos ou à realidade.",
                synonyms=["incorreto", "errado", "fictício"],
                antonyms=["verdadeiro", "correto", "real"],
                hypernyms=["valor_lógico"],
                domain="lógica",
            ),
            ConceptNode(
                term="banco",
                definition="Móvel para sentar; instituição financeira; repositório de dados.",
                synonyms=[],
                antonyms=[],
                hypernyms=[],
                homonyms={
                    "assento": "móvel para sentar",
                    "financeiro": "instituição financeira",
                    "dados": "repositório de dados",
                },
                domain="geral",
            ),
            ConceptNode(
                term="eminente",
                definition="Pessoa ilustre ou notável.",
                synonyms=["ilustre", "notável"],
                antonyms=[],
                paronyms=["iminente"],
                domain="geral",
            ),
            ConceptNode(
                term="iminente",
                definition="Que está prestes a acontecer.",
                synonyms=["próximo", "imediato"],
                antonyms=[],
                paronyms=["eminente"],
                domain="geral",
            ),
            ConceptNode(
                term="inteligência",
                definition="Capacidade de compreender, raciocinar e resolver problemas.",
                synonyms=["cognição", "raciocínio", "entendimento"],
                antonyms=["ignorância", "estupidez"],
                hypernyms=["capacidade_mental"],
                domain="cognitivo",
            ),
            ConceptNode(
                term="conhecimento",
                definition="Ato ou efeito de conhecer; saber, ciência, erudição.",
                synonyms=["saber", "ciência", "erudição"],
                antonyms=["ignorância", "desconhecimento"],
                hypernyms=["epistemologia"],
                domain="filosófico",
            ),
            ConceptNode(
                term="verdade",
                definition="Conformidade entre o que se diz e o que é.",
                synonyms=["veracidade", "factualidade", "realidade"],
                antonyms=["mentira", "falsidade", "ilusão"],
                hypernyms=["epistemologia"],
                domain="filosófico",
            ),
        ]
        for node in entries:
            self.add(node)

    @staticmethod
    def _normalize(term: str) -> str:
        return term.strip().lower()

    # ------------------------------------------------------------------ #
    # Carregamento de conceitos externos (ex.: dicionário em inglês)      #
    # ------------------------------------------------------------------ #

    def _load_external_concepts(self) -> None:
        """
        Carrega conceitos adicionais de um banco gerado a partir do
        dicionário em inglês (arquivo JSON se existir).

        Formato esperado (lista de objetos):
          {
            "term": "abacus",
            "definition": "Frame with beads for calculating...",
            "synonyms": [],
            "antonyms": [],
            "hyponyms": [],
            "hypernyms": [],
            "domain": "geral"
          }
        """
        base_dir = os.path.dirname(__file__) or "."
        json_path = os.path.join(base_dir, "data", "concepts_en.json")
        if not os.path.exists(json_path):
            return
        try:
            with open(json_path, "r", encoding="utf-8") as f:
                items = json.load(f)
        except Exception:
            return

        for item in items:
            term = item.get("term")
            if not term:
                continue
            node = ConceptNode(
                term=term,
                definition=item.get("definition", ""),
                synonyms=item.get("synonyms", []),
                antonyms=item.get("antonyms", []),
                hyponyms=item.get("hyponyms", []),
                hypernyms=item.get("hypernyms", []),
                homonyms=item.get("homonyms", {}),
                paronyms=item.get("paronyms", []),
                domain=item.get("domain", "geral"),
            )
            # Não sobrescreve conceitos portugueses existentes
            key = self._normalize(term)
            if key not in self._table:
                self._table[key] = node