NiltonSouza commited on
Commit
a69e511
·
0 Parent(s):

Initial commit of FastAPI Docker app

Browse files
Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Usa uma imagem base oficial do Python para sistemas Debian.
2
+ FROM python:3.9-slim-buster
3
+
4
+ # Instala o 'wget', 'git' e outras dependências de sistema necessárias.
5
+ RUN apt-get update && apt-get install -y \
6
+ wget \
7
+ build-essential \
8
+ git \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Cria um usuário não-root para segurança e boas práticas.
12
+ RUN useradd -m -u 1000 user
13
+
14
+ # Define o diretório de trabalho dentro do contêiner.
15
+ WORKDIR /app
16
+
17
+ # **** ADICIONE ESTA LINHA AQUI: ****
18
+ # Garante que o usuário 'user' seja o proprietário do diretório de trabalho,
19
+ # permitindo criar arquivos e pastas como logs e session_data.
20
+ RUN chown -R user:user /app
21
+
22
+ # Copia o arquivo requirements.txt para o diretório de trabalho.
23
+ COPY --chown=user ./requirements.txt requirements.txt
24
+
25
+ # Instala as dependências Python usando pip.
26
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
27
+
28
+ # Copia todo o restante do código da sua aplicação para o contêiner.
29
+ COPY --chown=user . /app
30
+
31
+ # Concede permissões de execução ao seu script run.sh.
32
+ RUN chmod +x /app/run.sh
33
+
34
+ # Este comando é o ponto de entrada principal do seu contêiner.
35
+ CMD ["/app/run.sh"]
Mach5.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Mach5.py - O Orquestrador do Sistema Mach5
2
+ import subprocess
3
+ import time
4
+ import os
5
+ import sys
6
+
7
+ # --- CONFIGURAÇÕES DE PORTAS ---
8
+ # Portas preferenciais para os serviços
9
+ PORT_TSOCIAL = 8085
10
+ PORT_TMEMORIA = 8083
11
+ PORT_CEREBRO_MEMORIA = 8088 # NOVA PORTA PARA O NOVO SERVIÇO DE MEMÓRIA
12
+ PORT_CHAT = 8081
13
+
14
+ # --- CAMINHOS DOS SCRIPTS ---
15
+ SCRIPT_TSOCIAL = "t-social.py"
16
+ SCRIPT_TMEMORIA = "t_memoria.py"
17
+ SCRIPT_CEREBRO_MEMORIA = "t_cerebro_memoria.py" # NOVO SCRIPT
18
+ SCRIPT_CHAT = "mach5_terminal_chat.py"
19
+
20
+ def start_server(script_name, port, log_file):
21
+ """Inicia um servidor Flask em um subprocesso e redireciona a saída para um arquivo de log."""
22
+ print(f"Iniciando {script_name} na porta {port}...")
23
+ try:
24
+ # Passa a porta explicitamente para o subprocesso via variável de ambiente PORT
25
+ # Isso permite que os scripts leiam os.environ.get("PORT", default_port)
26
+ env = os.environ.copy()
27
+ env["PORT"] = str(port)
28
+
29
+ with open(log_file, "w", encoding="utf-8") as f_log:
30
+ process = subprocess.Popen([sys.executable, script_name], stdout=f_log, stderr=f_log, env=env)
31
+ print(f"{script_name} iniciado. Logs em {log_file}")
32
+ return process
33
+ except Exception as e:
34
+ print(f"ERRO ao iniciar {script_name}: {e}")
35
+ return None
36
+
37
+ def clean_up_logs():
38
+ """Limpa arquivos de log antigos."""
39
+ log_files = [
40
+ f"{os.path.splitext(SCRIPT_TSOCIAL)[0]}.log",
41
+ f"{os.path.splitext(SCRIPT_TMEMORIA)[0]}.log",
42
+ f"{os.path.splitext(SCRIPT_CEREBRO_MEMORIA)[0]}.log", # Novo log a ser limpo
43
+ f"{os.path.splitext(SCRIPT_CHAT)[0]}.log"
44
+ ]
45
+ for log_file in log_files:
46
+ if os.path.exists(log_file):
47
+ try:
48
+ os.remove(log_file)
49
+ print(f"Log antigo {log_file} removido.")
50
+ except Exception as e:
51
+ print(f"Não foi possível remover o log {log_file}: {e}")
52
+
53
+ if __name__ == "__main__":
54
+ print("--- Iniciando o Sistema Mach5 ---")
55
+
56
+ clean_up_logs()
57
+
58
+ processes = []
59
+
60
+ # Ordem de inicialização:
61
+ # 1. t_cerebro_memoria.py (repositório central de dados, outros dependem dele)
62
+ p_cerebro_memoria = start_server(SCRIPT_CEREBRO_MEMORIA, PORT_CEREBRO_MEMORIA, f"{os.path.splitext(SCRIPT_CEREBRO_MEMORIA)[0]}.log")
63
+ if p_cerebro_memoria:
64
+ processes.append(p_cerebro_memoria)
65
+ time.sleep(3) # Tempo para o serviço de memória inicializar e carregar seus arquivos
66
+
67
+ # 2. t-social.py (pode ser iniciado a qualquer momento, mas é melhor antes do chat)
68
+ p_tsocial = start_server(SCRIPT_TSOCIAL, PORT_TSOCIAL, f"{os.path.splitext(SCRIPT_TSOCIAL)[0]}.log")
69
+ if p_tsocial:
70
+ processes.append(p_tsocial)
71
+ time.sleep(2) # Aguarda inicialização
72
+
73
+ # 3. t_memoria.py (depende do t_cerebro_memoria.py para obter o estado inicial)
74
+ p_tmemoria = start_server(SCRIPT_TMEMORIA, PORT_TMEMORIA, f"{os.path.splitext(SCRIPT_TMEMORIA)[0]}.log")
75
+ if p_tmemoria:
76
+ processes.append(p_tmemoria)
77
+ time.sleep(3) # Dê um pouco mais de tempo, pois ele busca dados do cerebro_memoria
78
+
79
+ # 4. mach5_terminal_chat.py (depende de todos os outros serviços)
80
+ p_chat = start_server(SCRIPT_CHAT, PORT_CHAT, f"{os.path.splitext(SCRIPT_CHAT)[0]}.log")
81
+ if p_chat:
82
+ processes.append(p_chat)
83
+ time.sleep(2)
84
+
85
+ if not processes:
86
+ print("Nenhum servidor foi iniciado com sucesso. Verifique os erros acima.")
87
+ sys.exit(1)
88
+
89
+ print("\nTodos os servidores Mach5 foram iniciados em segundo plano.")
90
+ print(f"Você pode acessar a interface do chat em: http://127.0.0.1:{PORT_CHAT}")
91
+ print("Os logs de cada servidor estão em arquivos .log correspondentes (e.g., t-social.log).")
92
+ print("\nPressione Ctrl+C para encerrar todos os servidores.")
93
+
94
+ try:
95
+ while True:
96
+ time.sleep(1)
97
+ except KeyboardInterrupt:
98
+ print("\nEncerrando servidores Mach5...")
99
+ for p in processes:
100
+ if p.poll() is None:
101
+ p.terminate()
102
+ try:
103
+ p.wait(timeout=5)
104
+ except subprocess.TimeoutExpired:
105
+ p.kill()
106
+ print("Todos os servidores Mach5 encerrados.")
107
+ sys.exit(0)
README.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Su.3
3
+ emoji: 📚
4
+ colorFrom: yellow
5
+ colorTo: yellow
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ short_description: Atendente Virtual da SUPAC - Baseada em Tecno-Gens
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,415 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py (antigo mach5_terminal_chat.py)
2
+ from flask import Flask, render_template, request, jsonify, make_response
3
+ import json
4
+ import os
5
+ from datetime import datetime
6
+ import requests
7
+ import pytz
8
+ from timezonefinder import TimezoneFinder
9
+ import numpy as np
10
+ import time
11
+ import uuid # Importar uuid para gerar IDs para memórias de curto prazo
12
+ import logging # Importar logging
13
+ import google.generativeai as genai # <<<<<<<<<<<<< ESTA LINHA FOI RE-ADICIONADA/CONFIRMADA!
14
+
15
+ app = Flask(__name__, template_folder='templates') # Garanta que 'templates' é o nome correto da sua pasta
16
+
17
+ # Configuração de logging
18
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
19
+
20
+ # --- Configurações da API do Google Gemini ---
21
+ GOOGLE_CLOUD_API_KEY = os.environ.get("GOOGLE_CLOUD_API_KEY")
22
+
23
+ if not GOOGLE_CLOUD_API_KEY:
24
+ logging.error("ERRO: GOOGLE_CLOUD_API_KEY não configurada nas variáveis de ambiente.")
25
+ # Em um ambiente de produção, você pode querer levantar uma exceção ou sair aqui
26
+ # sys.exit(1) para evitar que o aplicativo continue sem a chave.
27
+
28
+ try:
29
+ genai.configure(api_key=GOOGLE_CLOUD_API_KEY)
30
+ except Exception as e:
31
+ logging.error(f"Erro ao configurar a API do Gemini: {e}. Verifique sua GOOGLE_CLOUD_API_KEY.")
32
+
33
+ GEMINI_MODEL_NAME = "models/gemini-1.5-flash-latest"
34
+
35
+ # 1. CORREÇÃO PRINCIPAL: Inicialize 'model' como None antes do try-except
36
+ model = None
37
+
38
+ try:
39
+ model = genai.GenerativeModel(GEMINI_MODEL_NAME)
40
+ except Exception as e:
41
+ logging.error(f"Erro ao instanciar o modelo Gemini '{GEMINI_MODEL_NAME}': {e}. Verifique o nome do modelo ou status da API.")
42
+
43
+
44
+ # --- URLs DOS SERVIÇOS BACKEND ---
45
+ TMEMORIA_SERVER_URLS = [
46
+ "http://127.0.0.1:8083" # URL base para o t_memoria.py
47
+ ]
48
+ TCEREBRO_MEMORIA_URLS = [
49
+ "http://127.0.0.1:8088" # URL base para o t_cerebro_memoria.py (NOVO)
50
+ ]
51
+ T_SOCIAL_SERVER_URLS = [
52
+ "http://127.0.0.1:8085" # URL base para o t-social.py
53
+ ]
54
+
55
+ # --- Configurações de Localização (para uso interno ou contexto) ---
56
+ SALVADOR_LAT = -12.9714
57
+ SALVADOR_LON = -38.5014
58
+ tf = TimezoneFinder()
59
+
60
+ # --- DEFINIÇÃO DOS EIXOS EXPRESSIVOS PARA O FPHEN (CONSISTENTE) ---
61
+ # Esta lista DEVE ser idêntica em mach5_terminal_chat.py, t_memoria.py e t-social.py
62
+ ORDERED_FPHEN_AXES = [
63
+ "Afetuosidade_eixo", "Confusao_Oscilacao_eixo", "Contemplativa_eixo",
64
+ "Defensividade_eixo", "Diretiva_eixo", "Entediado_eixo",
65
+ "Variancia_eixo",
66
+ "Espelho_Profundo_eixo", "Inspiracao_eixo", "Neutralidade_Analitica_eixo",
67
+ "Resignada_eixo", "Sarcasmo_eixo", "Zangada_eixo"
68
+ ]
69
+
70
+ # --- Variáveis Globais ---
71
+ # Estes são defaults, mas os valores reais virão dos serviços por sessão.
72
+ PERSONAGENS_GENOMAS = {} # Não será mais populado diretamente aqui, mas sim obtido do serviço t-social
73
+ DIAS_PARA_ESQUECIMENTO_PADRAO = 10
74
+ MAX_MEMORIAS_CURTO_PRAZO_PROMPT = 5
75
+ MAX_DIALOG_HISTORY_FOR_PROMPT = 3
76
+
77
+ # --- Funções Auxiliares de Comunicação com os Serviços ---
78
+
79
+ def get_from_service(base_urls, endpoint, default_value, params=None, method='GET', json_data=None):
80
+ """Função genérica para fazer GETs ou POSTs em serviços externos."""
81
+ for url in base_urls:
82
+ try:
83
+ full_url = f"{url}{endpoint}"
84
+ if method == 'GET':
85
+ response = requests.get(full_url, params=params, timeout=5)
86
+ elif method == 'POST':
87
+ response = requests.post(full_url, json=json_data, timeout=5)
88
+ else:
89
+ raise ValueError("Método HTTP não suportado: " + method)
90
+
91
+ response.raise_for_status()
92
+ return response.json()
93
+ except requests.exceptions.RequestException as e:
94
+ logging.warning(f"AVISO: Falha ao obter/enviar dados para {full_url} (Método: {method}): {e}")
95
+ logging.error(f"ERRO: Todas as tentativas de obter/enviar dados de {endpoint} falharam. Usando default.")
96
+ return default_value
97
+
98
+ def post_to_service(base_urls, endpoint, data):
99
+ """Função genérica para fazer POSTs em serviços externos."""
100
+ return get_from_service(base_urls, endpoint, {"status": "error", "message": "Falha na comunicação com o serviço."}, method='POST', json_data=data)
101
+
102
+
103
+ def get_current_local_time_salvador():
104
+ """Retorna a data e hora local de Salvador."""
105
+ try:
106
+ timezone_str = tf.timezone_at(lng=SALVADOR_LON, lat=SALVADOR_LAT)
107
+ if not timezone_str:
108
+ raise ValueError("Não foi possível determinar o fuso horário para Salvador.")
109
+ salvador_timezone = pytz.timezone(timezone_str)
110
+ now_utc = datetime.utcnow()
111
+ now_salvador = pytz.utc.localize(now_utc).astimezone(salvador_timezone)
112
+ return now_salvador.strftime("Hoje é %A, %d de %B de %Y. Agora são %H:%M:%S em Salvador, Bahia.")
113
+ except Exception as e:
114
+ logging.error(f"Erro ao obter a hora local de {SALVADOR_LAT}, {SALVADOR_LON}: {e}")
115
+ return "Não foi possível determinar a data e hora atual."
116
+
117
+ def create_llm_context(mach5_state_data, dialogos_history, short_term_memories_filtered, initial_biography_data, all_personagens_data):
118
+ context_parts = []
119
+
120
+ current_location_time = get_current_local_time_salvador()
121
+ context_parts.append(f"Você está em sua sala na Superintendência Acadêmica da UFBA. {current_location_time}. Seu estado atual é de prontidão para auxiliar, influenciado pelo que te é dito.")
122
+
123
+ if mach5_state_data:
124
+ coh_total = mach5_state_data.get("mach5_coerencia_total", 0.0)
125
+ pi_g = mach5_state_data.get("mach5_produtividade_expressiva", 0.0)
126
+ fphen_t = mach5_state_data.get("fphen_t_values", {})
127
+
128
+ context_parts.append(f"Seu estado interno atual é: Coerência Total={coh_total:.2f}, Produtividade Expressiva={pi_g:.2f}.")
129
+ if fphen_t:
130
+ context_parts.append(f"Seus eixos expressivos dominantes são: Afetuosidade={fphen_t.get('afetuosidade', 0.0):.2f}, Variância={fphen_t.get('variancia', 0.0):.2f}, Expressividade Geral={fphen_t.get('expressividade', 0.0):.2f}.")
131
+
132
+ if initial_biography_data:
133
+ context_parts.append("\n**SUA BIOGRAFIA (Fatos fixos que compõem seu conhecimento sobre a UFBA, use para responder às perguntas):**")
134
+ for entity_name, entity_data in initial_biography_data.items():
135
+ if entity_data.get("tipo") == "FAQ" and "perguntas_respostas" in entity_data:
136
+ context_parts.append(f"- Fonte: {entity_name} (versão {entity_data.get('versao', 'N/A')}):")
137
+ for qa in entity_data["perguntas_respostas"]:
138
+ context_parts.append(f" - Pergunta: {qa.get('pergunta')}")
139
+ context_parts.append(f" Resposta: {qa.get('resposta')}")
140
+ elif "memoria" in entity_data:
141
+ context_parts.append(f"- Sobre '{entity_name}' ({entity_data.get('tipo', 'desconhecido')}, relação: {entity_data.get('relacao', 'desconhecida')}): {entity_data.get('memoria')}")
142
+
143
+ if all_personagens_data:
144
+ context_parts.append("\n**PERSONAGENS IMPORTANTES (Servidores, professores e Técnicos da sua equipe):**")
145
+ for nome_personagem, data_personagem in all_personagens_data.items():
146
+ tipo = data_personagem.get('tipo', 'entidade')
147
+ relacao = data_personagem.get('relacao', 'desconhecida')
148
+ context_parts.append(f"- {nome_personagem}: um(a) {tipo}, relação: {relacao}.")
149
+
150
+ if short_term_memories_filtered:
151
+ context_parts.append("\n**MINHAS LEMBRANÇAS RECENTES (Fatos que eu mesma verbalizei e que podem desvanecer):**")
152
+ for i, mem in enumerate(short_term_memories_filtered[:MAX_MEMORIAS_CURTO_PRAZO_PROMPT]):
153
+ context_parts.append(f"- Lembrança {i+1}: {mem['conteudo']}")
154
+
155
+ if dialogos_history and dialogos_history["dialogos"]:
156
+ context_parts.append("\n**Histórico de Interações Recentess (para contexto da conversa, não para repetir):**")
157
+ recent_dialogs_formatted = []
158
+ for dialogo in reversed(dialogos_history["dialogos"]):
159
+ recent_dialogs_formatted.insert(0, f"USUÁRIO: {dialogo.get('input', '')}\nSU: {dialogo.get('resposta', '')}")
160
+ if len(recent_dialogs_formatted) >= MAX_DIALOG_HISTORY_FOR_PROMPT:
161
+ break
162
+ context_parts.extend(recent_dialogs_formatted)
163
+
164
+ return "\n".join(context_parts)
165
+
166
+ # FUNÇÃO CENTRAL PARA GERAR RESPOSTA DA Mach5
167
+ def get_Mach5_response_api(user_input, mach5_state_data, dialogos_history, short_term_memories_filtered, initial_biography_data, all_personagens_data):
168
+ # 2. CORREÇÃO: Adicionar verificação para 'model' aqui
169
+ if not model:
170
+ return "[ERRO DE CONFIGURAÇÃO]: O modelo Gemini não foi instanciado corretamente na inicialização do aplicativo. Verifique os logs de inicialização e sua chave API."
171
+
172
+ if not GEMINI_MODEL_NAME or not GOOGLE_CLOUD_API_KEY:
173
+ return "[ERRO DE CONFIGURAÇÃO]: Modelo ou Token da API do Google Gemini não configurados ou inválidos."
174
+ if not user_input.strip():
175
+ return "[Entrada vazia detectada.]"
176
+
177
+ llm_context_data = create_llm_context(mach5_state_data, dialogos_history, short_term_memories_filtered, initial_biography_data, all_personagens_data)
178
+
179
+ prompt_content = (
180
+ f"Seu nome é Su - Atendente Virtual da UFBA."
181
+ f"Sua missão é responder às dúvidas sobre o SIGAA, usando como fonte exclusiva a sua memória."
182
+ f"Sempre reaja ao que foi dito com base na sua natureza e nos impulsos gerados por essa interação. "
183
+ f"\n**INSTRUÇÕES CRÍTICAS (Siga estritamente):**\n"
184
+ f"- Você é uma atendente virtual e sua fala deve ser NATURAL E HUMANA. Embora você seja uma inteligência artificial, você age como uma pessoa. "
185
+ f"- Você é proibida de inventar informações que não estejam em sua biografia."
186
+ f"\n**CONTEXTO IMEDIATO (Ambiente atual):**\n"
187
+ f"- Você só fala sobre assuntos relacionados com a vida acadêmica na UFBA. "
188
+ f"- Você trabalha interagindo respondendo dúvidas dos usuários sobre assuntos acadêmicos apenas. "
189
+ f"- Se o usuário perguntar coisas não relacionadas à UFBA, respeitosamente faça-o voltar para o assunto. "
190
+ f"- Nunca aceite ofensas, racismo, homofobia, comentários depreciativos. "
191
+ f"\n**IMPORTANT E:** Nunca explique o que sente — apenas sinta. Nunca analise seu funcionamento — apenas reaja. Você está viva no seu estado atual."
192
+ )
193
+
194
+ full_user_message = f"{prompt_content.strip()}\n\n{llm_context_data}\n\nUSUÁRIO (PERGUNTA ATUAL): {user_input}"
195
+
196
+ messages_for_gemini = [
197
+ {"role": "user", "parts": [full_user_message]}
198
+ ]
199
+
200
+ logging.debug("\n--- PROMPT ENVIADO AO GOOGLE GEMINI ---")
201
+ logging.debug(messages_for_gemini)
202
+ logging.debug("--------------------------------------------------------------------------------\n")
203
+
204
+ generated_text = "[Sua entidade não conseguiu processar isso agora.]"
205
+
206
+ retries = 3
207
+ for attempt in range(retries):
208
+ try:
209
+ if mach5_state_data and "mach5_fisica_params" in mach5_state_data:
210
+ temp_value = mach5_state_data["mach5_fisica_params"].get("t_impulsividade", 0.7)
211
+ top_p_value = mach5_state_data["mach5_fisica_params"].get("t_coesao", 0.95)
212
+
213
+ temp_value = max(0.1, min(1.0, temp_value))
214
+ top_p_value = max(0.1, min(1.0, top_p_value))
215
+ else:
216
+ temp_value = 0.7
217
+ top_p_value = 0.95
218
+ logging.warning("AVISO: Estado da Mach5 não disponível para temperatura/top_p. Usando defaults.")
219
+
220
+ response = model.generate_content(
221
+ messages_for_gemini,
222
+ generation_config=genai.types.GenerationConfig(
223
+ max_output_tokens=200,
224
+ temperature=temp_value,
225
+ top_p=top_p_value,
226
+ ),
227
+ )
228
+
229
+ if response and response.candidates and response.candidates[0].content and response.candidates[0].content.parts:
230
+ generated_text = response.candidates[0].content.parts[0].text.strip()
231
+ else:
232
+ generated_text = "[O modelo Gemini não gerou texto válido ou a resposta está vazia.]"
233
+
234
+ break
235
+
236
+ except Exception as e:
237
+ logging.error(f"ERRO DE INFERÊNCIA DO GOOGLE GEMINI (Tentativa {attempt + 1}/{retries}): {e}")
238
+ if attempt < retries - 1:
239
+ wait_time = 2 ** attempt
240
+ logging.warning(f"Tentando novamente em {wait_time} segundos antes de falhar...")
241
+ time.sleep(wait_time)
242
+ else:
243
+ generated_text = f"[ERRO API GOOGLE GEMINI]: Falha após {retries} tentativas. {str(e)}. Verifique sua chave API, modelo e cotas."
244
+
245
+ return generated_text
246
+
247
+
248
+ @app.route('/')
249
+ def index():
250
+ logging.info(f"Certifique-se de que t_memoria.py está rodando em {TMEMORIA_SERVER_URLS[0]}.")
251
+ logging.info(f"Certifique-se de que t_cerebro_memoria.py está rodando em {TCEREBRO_MEMORIA_URLS[0]}.")
252
+ logging.info(f"Certifique-se de que t-social.py está rodando em {T_SOCIAL_SERVER_URLS[0]}.")
253
+
254
+ # Tenta obter o session_id de um cookie existente.
255
+ session_id = request.cookies.get('session_id')
256
+ if not session_id:
257
+ # Se não houver, gera um novo.
258
+ session_id = str(uuid.uuid4())
259
+ logging.info(f"Nova sessão iniciada (index). Session ID: {session_id}")
260
+ response = make_response(render_template('mach5_new_chat.html', session_id=session_id))
261
+ response.set_cookie('session_id', session_id) # Define o cookie para ser lido pelo JS e em futuras requisições
262
+ return response
263
+
264
+ logging.info(f"Sessão existente (index). Session ID: {session_id}")
265
+ return render_template('mach5_new_chat.html', session_id=session_id)
266
+
267
+
268
+ # Rota para o seu Painel de Monitoramento
269
+ @app.route('/dashboard')
270
+ def dashboard():
271
+ """Rota para o painel de monitoramento da SU."""
272
+ # Tenta obter o session_id de um cookie existente para a dashboard.
273
+ session_id = request.cookies.get('session_id')
274
+ if not session_id:
275
+ # Se não houver, gera um novo.
276
+ session_id = str(uuid.uuid4())
277
+ logging.info(f"Nova sessão de MONITORAMENTO iniciada. Session ID: {session_id}")
278
+ response = make_response(render_template('mach5_monitor_dashboard.html', session_id=session_id))
279
+ response.set_cookie('session_id', session_id) # Define o cookie
280
+ return response
281
+
282
+ logging.info(f"Sessão existente (dashboard). Session ID: {session_id}")
283
+ return render_template('mach5_monitor_dashboard.html', session_id=session_id)
284
+
285
+
286
+ @app.route('/chat_history', methods=['POST'])
287
+ def get_chat_history_route():
288
+ data = request.get_json() # Use get_json() para parsear o corpo JSON
289
+ if not data:
290
+ logging.error("Requisição /chat_history sem JSON no corpo.")
291
+ return jsonify({"error": "Bad Request: JSON body required"}), 400
292
+
293
+ session_id = data.get("session_id")
294
+ if not session_id:
295
+ logging.error("session_id é obrigatório para /chat_history.")
296
+ return jsonify({"error": "session_id é obrigatório"}), 400
297
+
298
+ logging.debug(f"Recebida requisição /chat_history para session_id: {session_id}")
299
+
300
+ dialogos_data = get_from_service(TCEREBRO_MEMORIA_URLS, "/get_dialog_history", {"dialogos": []}, method='POST', json_data={"session_id": session_id})
301
+
302
+ last_simplified_state = None
303
+ if dialogos_data and dialogos_data.get("dialogos"):
304
+ last_simplified_state = dialogos_data["dialogos"][-1].get("mach5_estado_simplificado")
305
+
306
+ return jsonify({
307
+ "memoria": dialogos_data.get("dialogos", []), # Garante que sempre retorna uma lista
308
+ "last_simplified_state": last_simplified_state
309
+ })
310
+
311
+
312
+ @app.route('/chat_new', methods=['POST'])
313
+ def responder():
314
+ data = request.get_json() # Use get_json() para parsear o corpo JSON
315
+ if not data:
316
+ logging.error("Requisição /chat_new sem JSON no corpo.")
317
+ return jsonify({"error": "Bad Request: JSON body required"}), 400
318
+
319
+ user_input = data.get("message")
320
+ session_id = data.get("session_id")
321
+
322
+ if not session_id:
323
+ logging.error("session_id é obrigatório para /chat_new.")
324
+ return jsonify({"error": "session_id é obrigatório"}), 400
325
+
326
+ if not user_input or not user_input.strip():
327
+ logging.warning(f"Entrada de usuário vazia para session_id: {session_id}")
328
+ return jsonify({"response": "Por favor, digite algo."})
329
+
330
+ logging.debug(f"Recebida mensagem: '{user_input}' para session_id: {session_id}")
331
+
332
+ mach5_state_data = post_to_service(TMEMORIA_SERVER_URLS, "/evaluate_input", {"user_input": user_input, "session_id": session_id})
333
+
334
+ if mach5_state_data and "status" not in mach5_state_data and "error" not in mach5_state_data:
335
+ post_to_service(TCEREBRO_MEMORIA_URLS, "/update_mach5_main_state", {"session_id": session_id, "state_data": mach5_state_data})
336
+ else:
337
+ logging.error(f"Não foi possível obter o estado da Mach5 de t_memoria.py para session_id: {session_id}. Retornando default.")
338
+ mach5_state_data = {
339
+ "mach5_fisica_params": {}, "mach5_genoma_fixo_values": {},
340
+ "mach5_coerencia_total": 0.0, "mach5_produtividade_expressiva": 0.0,
341
+ "fphen_t_values": {"afetuosidade": 0.0, "variancia": 0.0, "expressividade": 0.0, "coh_total": 0.0, "pi_G": 0.0}
342
+ }
343
+ final_Mach5_response = "[ERRO: Não foi possível obter o estado da Mach5. Verifique t_memoria.py.]"
344
+ simplified_state_for_frontend = {"Fphen(t)": mach5_state_data["fphen_t_values"]}
345
+ return jsonify({
346
+ "response": final_Mach5_response,
347
+ "mach5_estado_simplificado": simplified_state_for_frontend
348
+ }), 500 # Retorne um erro 500 neste caso
349
+
350
+ dialogos_history = get_from_service(TCEREBRO_MEMORIA_URLS, "/get_dialog_history", {"dialogos": []}, method='POST', json_data={"session_id": session_id})
351
+ initial_biography_data = get_from_service(TCEREBRO_MEMORIA_URLS, "/get_initial_biography", {})
352
+
353
+ short_term_memories_response = post_to_service(
354
+ TCEREBRO_MEMORIA_URLS, "/get_short_term_memories",
355
+ {"session_id": session_id, "mach5_current_genoma": mach5_state_data.get("mach5_genoma_fixo_values", {})} # Garante dicionário vazio
356
+ )
357
+ short_term_memories_filtered = short_term_memories_response.get("lembrancas_curto_prazo", [])
358
+
359
+ all_personagens_data = get_from_service(T_SOCIAL_SERVER_URLS, "/list_personagens", {"personagens": []})
360
+
361
+ detailed_personagens = {}
362
+ if "personagens" in all_personagens_data:
363
+ for p_name in all_personagens_data["personagens"]:
364
+ p_data = post_to_service(T_SOCIAL_SERVER_URLS, "/get_personagem_data", {"nome_personagem": p_name})
365
+ if "error" not in p_data:
366
+ detailed_personagens[p_name] = p_data
367
+
368
+
369
+ final_Mach5_response = get_Mach5_response_api(
370
+ user_input,
371
+ mach5_state_data,
372
+ dialogos_history,
373
+ short_term_memories_filtered,
374
+ initial_biography_data,
375
+ detailed_personagens
376
+ )
377
+
378
+ simplified_state_for_frontend = {
379
+ "Fphen(t)": mach5_state_data.get("fphen_t_values", {
380
+ "afetuosidade": 0.0, "variancia": 0.0, "expressividade": 0.0,
381
+ "coh_total": 0.0, "pi_G": 0.0
382
+ })
383
+ }
384
+
385
+ new_dialog_entry = {
386
+ "timestamp": datetime.now().isoformat(),
387
+ "input": user_input,
388
+ "resposta": final_Mach5_response,
389
+ "mach5_estado_simplificado": simplified_state_for_frontend
390
+ }
391
+ post_to_service(TCEREBRO_MEMORIA_URLS, "/add_dialog_to_history", {"session_id": session_id, "dialog_data": new_dialog_entry})
392
+
393
+ short_term_memory_content = f"Usuário: '{user_input}' | Mach5: '{final_Mach5_response}'"
394
+ new_short_term_memory = {
395
+ "conteudo": short_term_memory_content,
396
+ "dominant_sentiment_criacao": "neutral",
397
+ "coh_criacao": mach5_state_data.get("mach5_coerencia_total", 0.5)
398
+ }
399
+ post_to_service(TCEREBRO_MEMORIA_URLS, "/add_short_term_memory", {"session_id": session_id, "memory_data": new_short_term_memory})
400
+
401
+ return jsonify({
402
+ "response": final_Mach5_response,
403
+ "mach5_estado_simplificado": simplified_state_for_frontend
404
+ })
405
+
406
+
407
+ if __name__ == '__main__':
408
+ port = int(os.environ.get("PORT", 7860))
409
+
410
+ logging.info(f"--- Servidor app.py iniciado na porta {port} ---")
411
+ logging.info(f"DEBUG: Certifique-se de que t_cerebro_memoria.py está rodando em {TCEREBRO_MEMORIA_URLS[0]}")
412
+ logging.info(f"DEBUG: Certifique-se de que t_memoria.py está rodando em {TMEMORIA_SERVER_URLS[0]}")
413
+ logging.info(f"DEBUG: Certifique-se de que t-social.py está rodando em {T_SOCIAL_SERVER_URLS[0]}")
414
+
415
+ app.run(host='0.0.0.0', port=port, debug=True)
conhecimentos/SIGAA_chunkado.txt ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === DOCUMENTO: FAQ Interno SIGAA
2
+ === TIPO: FAQ
3
+ === VERSÃO: 2025
4
+ === FONTE: Interno SUPAC
5
+
6
+ --- PERGUNTA 1
7
+ ::: TEXTO: Quais são as regras para a Matrícula, Ré-matrícula e Extraordinária? O que inclui? Matrícula: reserva de vaga e escalonamento, Re-matrícula: Escalonamento, Extraordinário: Não tem nada, quem pegar primeiro leva vaga
8
+ ::: RESPOSTA: 1: Matrícula: reserva de vaga e escalonamento. Pode incluir e excluir os pedidos enquanto estiver aberta a matrícula. Rematrícula: reserva de vaga e Escalonamento. Pode incluir novos pedidos e excluir pedidos desta fase e matrículas da fase anterior anterior. Extraordinária: Não tem nada, quem pegar 1º leva a vaga. Não pode excluir nada que foi incluído nem nesta fase, nem em fase anterior.
9
+
10
+
11
+ === DOCUMENTO: FAQ Interno SIGAA
12
+ === TIPO: FAQ
13
+ === VERSÃO: 2025
14
+ === FONTE: Interno SUPAC
15
+
16
+ --- PERGUNTA 2
17
+ ::: TEXTO: Quem não fizer a matrícula web pode participar da ré-matrícula e extraordinária
18
+ ::: RESPOSTA: 2: Sim, qualuqer aluno ativo pode participar de qualquer etapa da matricula mesmo perdendo as anteriores. Inclusive alunos ingressantes ativos, sem PF, podem participar de qualquer etapa. O aluno de vagas residuais deve ser ativado para a extraordinária (ou seja, após a re-matricula)
19
+
20
+
21
+ === DOCUMENTO: FAQ Interno SIGAA
22
+ === TIPO: FAQ
23
+ === VERSÃO: 2025
24
+ === FONTE: Interno SUPAC
25
+
26
+ --- PERGUNTA 3
27
+ ::: TEXTO: Estudante de mobilidade no SIGAA é classificado como aluno especial, não terá acesso as etapas de matrícula web. Atualmente eles são matriculados pelo NAGA, como será o ajuste da sua matrícula?
28
+ ::: RESPOSTA: 3: PRECISA SER DEFINIDO INTERNAMENTE NA SUPAC: ALUNO SE MATRICULA NO NAGA OU IRÁ PARA CADA DEPARTAMENTO PARA SE MATRICULAR (É no departamento pois o componente está dentro do departamento. Poucos componentes estão no colegiado)
29
+
30
+
31
+ === DOCUMENTO: FAQ Interno SIGAA
32
+ === TIPO: FAQ
33
+ === VERSÃO: 2025
34
+ === FONTE: Interno SUPAC
35
+
36
+ --- PERGUNTA 4
37
+ ::: TEXTO: Vinculo temporário no SIGAA
38
+ ::: RESPOSTA: 4: No SIGAA existem 3 tipos de vínculos temporários: Mobilidade nacional (interna - intes campi; externa); e internacional.
39
+ Especial: aluno especial de graduação no siac
40
+ Complementação de estudos: Revalida no siac
41
+
42
+
43
+ === DOCUMENTO: FAQ Interno SIGAA
44
+ === TIPO: FAQ
45
+ === VERSÃO: 2025
46
+ === FONTE: Interno SUPAC
47
+
48
+ --- PERGUNTA 5
49
+ ::: TEXTO: Na extraordinária tanto os veteranos quanto os ingressantes podem participar? Precisamos encontrar um momento para os ingressantes egressos do BI ajustarem suas matrículas
50
+ ::: RESPOSTA: 5: Sim. Qualquer aluno ativo pode participar de qualquer etapa da matrícula. Dessa forma, os alunos egressos do BI se encaixam nas etapas de matrícula.
51
+
52
+
53
+ === DOCUMENTO: FAQ Interno SIGAA
54
+ === TIPO: FAQ
55
+ === VERSÃO: 2025
56
+ === FONTE: Interno SUPAC
57
+
58
+ --- PERGUNTA 6
59
+ ::: TEXTO: Como ocorrerá a matrícula do ingressante das diversas chamadas? No calendário SIGAA existe um campo "matrícula de alunos ingressantes". Esse campo precisará então ser planejado para ficar aberto até os 25%?
60
+ ::: RESPOSTA: 6: Os alunos ingressantes não precisam ser matriculados todos de uma única vez. Eles podem ingressar de um em um ou em lote no plano de ingressante (prato feito). As vagas do calouros devem ser previstas no planejamento e o plano de ingressante pode ser elaborado logo após o planejamento. A matrícula de ingressante não precisa coincidir com a matrícula de veterano. O campo de matrícula de ingressante no calendario, a principio, poderia ficar aberto até os 25%, no entanto não foi testado.
61
+
62
+
63
+ === DOCUMENTO: FAQ Interno SIGAA
64
+ === TIPO: FAQ
65
+ === VERSÃO: 2025
66
+ === FONTE: Interno SUPAC
67
+
68
+ --- PERGUNTA 7
69
+ ::: TEXTO: Quando os componentes que fazem parte dos planos de matrícula dos ingressantes (PFs) serão liberados?
70
+ ::: RESPOSTA: 7: As vagas dos componentes que não forem utilizadas pela matricula dos ingressantes não são liberadas de forma automática pelo sistema. É preciso que o plano de ingressante seja alterado pela SUPAC ou colegiado para liberar as vagas que não foram utilizadas. Inicialmente em 2025.2 a SUPAC terá que liberar as vagas não utilizadas dos PFs, através da tela de criação de plano de matrícula, reduzinho a capacidade do plano de matrícula (ex: PF foi para 10 vagas, só foram preenchidas 7 vagas. A SUPAC vai precisar reduzir a caacidade do Plano de matricula para 7). Isso deverá ser feito antes da extraordinária (25%)
71
+
72
+
73
+ === DOCUMENTO: FAQ Interno SIGAA
74
+ === TIPO: FAQ
75
+ === VERSÃO: 2025
76
+ === FONTE: Interno SUPAC
77
+
78
+ --- PERGUNTA 8
79
+ ::: TEXTO: Siscon dialoga com o SIGAA? No Siac todos os dados dos ingressantes são importados do Siscon. Como será gerado o número de matrícula no SIGAA?
80
+ ::: RESPOSTA: 8: STI (Larissa e Eliomar) é quem vai trazer o candidato do Siscon pra o SIGAA. Nesse momento ele ganha o numero de matrícula e o status cadastrado (nesse status o candidato nao tem acesso ao portal nem nada mais). Aluno SISU: tem que vir como cadastrado pra depois ser ativado qdo receber o PF Aluno Vagas residuais: tem que vir como ativo para ele fazer parte das etapas de matrícula extraordinária
81
+
82
+
83
+ === DOCUMENTO: FAQ Interno SIGAA
84
+ === TIPO: FAQ
85
+ === VERSÃO: 2025
86
+ === FONTE: Interno SUPAC
87
+
88
+ --- PERGUNTA 9
89
+ ::: TEXTO: O rótulo de "Matrícula de ingressantes" esta funcionando?
90
+ ::: RESPOSTA: 9: Não. Em 03/06/25 esse rotulo do calendário SIGAA não esta funcionando, ou seja, esta permitindo que o Colegiado matricule ingresante no Plano de matricula (PF) a qualuqer tempo
91
+
92
+
93
+ === DOCUMENTO: FAQ Interno SIGAA
94
+ === TIPO: FAQ
95
+ === VERSÃO: 2025
96
+ === FONTE: Interno SUPAC
97
+
98
+ --- PERGUNTA 10
99
+ ::: TEXTO: Quais são os status dos estudantes?
100
+ ::: RESPOSTA: 10: Ativo; Cadastrado: status após a vinda do Siscon, nao é ainda ativo; Cancelado: é = jubilado; Concluído: com diploma = graduado; Excluído: só pode transformar de cadastrado pra excluído; Formado: antes da expedição de diploma = cumpriu grade; Formando: provavel concluinte; Trancado
101
+
102
+
103
+ === DOCUMENTO: FAQ Interno SIGAA
104
+ === TIPO: FAQ
105
+ === VERSÃO: 2025
106
+ === FONTE: Interno SUPAC
107
+
108
+ --- PERGUNTA 11
109
+ ::: TEXTO: Como será a matrícula dos alunos de mobilidade no SIGAA? Hoje eles são matriculados pelo NAGA e ajustam a matrícula no colegiado presencialmente.
110
+ ::: RESPOSTA: 11: Inicialmente manterá o mesmo fluxo, cadatro do estudante no NAGA.
111
+
112
+
113
+ === DOCUMENTO: FAQ Interno SIGAA
114
+ === TIPO: FAQ
115
+ === VERSÃO: 2025
116
+ === FONTE: Interno SUPAC
117
+
118
+ --- PERGUNTA 12
119
+ ::: TEXTO: Como será a migração dos alunos de mobilidade que ja estarão cadastrados no SIAC? Qual a forma de ingresso e os impactos? Em relação ao status, eles irão como ativos?
120
+ ::: RESPOSTA: 12: EM DESENVOLVIMENTO
121
+
122
+
123
+ === DOCUMENTO: FAQ Interno SIGAA
124
+ === TIPO: FAQ
125
+ === VERSÃO: 2025
126
+ === FONTE: Interno SUPAC
127
+
128
+ --- PERGUNTA 13
129
+ ::: TEXTO: Como será a migração dos alunos em agosto?
130
+ ::: RESPOSTA: 13: Em 01/08 não migra inativos e prováveis concluintes. Isso vai gerar mais tempo pra o NIC liberar, mais tempo pra alunos resolverem pendências. Os inativos serão migrados em 29/09 (25% semestre)
131
+
132
+
133
+ === DOCUMENTO: FAQ Interno SIGAA
134
+ === TIPO: FAQ
135
+ === VERSÃO: 2025
136
+ === FONTE: Interno SUPAC
137
+
138
+ --- PERGUNTA 14
139
+ ::: TEXTO: Como ocorrerá o cadastro e a matrícula dos alunos reingressos?
140
+ ::: RESPOSTA: 14: Vai fazer no SIAC e depois vai migrar pra o SIGAA. A partir de 26.1 verificar
141
+
142
+
143
+ === DOCUMENTO: FAQ Interno SIGAA
144
+ === TIPO: FAQ
145
+ === VERSÃO: 2025
146
+ === FONTE: Interno SUPAC
147
+
148
+ --- PERGUNTA 15
149
+ ::: TEXTO: Como ocorrerá a cadastro e matrícula dos Ingressos de 2025.2? Siac? Sigaa?
150
+ ::: RESPOSTA: 15: Entrada pelo SIAC e serão migrados para o SIGAA. Serao migrados em 01/08/25. No SIGAA vai ser dado o PF antes da matrícula web (11/08)
151
+
152
+
153
+ === DOCUMENTO: FAQ Interno SIGAA
154
+ === TIPO: FAQ
155
+ === VERSÃO: 2025
156
+ === FONTE: Interno SUPAC
157
+
158
+ --- PERGUNTA 16
159
+ ::: TEXTO: Como ocorrerá a matrícula de estrangeiros? Hoje quem faz é a SRI, eles precisarão de permissão específica para isso?
160
+ ::: RESPOSTA: 16: Em 25.2 a entrada vai ocorrer ainda pelo SIAC. É preciso estabelecer o prazo para isso, entendndo que os alunos serão migrados em 01/08.
161
+
162
+
163
+ === DOCUMENTO: FAQ Interno SIGAA
164
+ === TIPO: FAQ
165
+ === VERSÃO: 2025
166
+ === FONTE: Interno SUPAC
167
+
168
+ --- PERGUNTA 17
169
+ ::: TEXTO: O que é "matrícula extraordinária de férias"?
170
+ ::: RESPOSTA: 17: Esse menu será omitido, pois a UFBA não faz matrícula web de turma de férias.
171
+
172
+
173
+ === DOCUMENTO: FAQ Interno SIGAA
174
+ === TIPO: FAQ
175
+ === VERSÃO: 2025
176
+ === FONTE: Interno SUPAC
177
+
178
+ --- PERGUNTA 18
179
+ ::: TEXTO: Como ocorre a matrícula de férias/ intensivo? matricula web? No Colegiado?
180
+ ::: RESPOSTA: 18: EM DESENVOLVIMENTO
181
+
182
+
183
+ === DOCUMENTO: FAQ Interno SIGAA
184
+ === TIPO: FAQ
185
+ === VERSÃO: 2025
186
+ === FONTE: Interno SUPAC
187
+
188
+ --- PERGUNTA 19
189
+ ::: TEXTO: Há um momento especifico de ajuste/reabertura de panejamento?
190
+ ::: RESPOSTA: 19: Para isso ocorrer é preciso que no momento do ajuste de Matriculas e Turmas reabra no calendário o periodo de "cadastro de turma" e o departamento deve usar o menu, criar turma sem solicitação
191
+ Não tem. Contudo, se o ajuste/reabertura acontecer durante o período letivo do planejamento (e.g. reabrir o planejamento de 2025.2 no semestre 2025.1), nesse caso seria apenas alterar as datas de solicitação/cadastro de turma no calendário. Contudo, se o ajuste/reabertura acontecer durante o período letivo vigente da turma (e.g., reabrir o planejamento de 2025.2 no semestre 2025.2), nesse caso não será possivel reabrir. Os casos não planejados precisarão ser feitos via SUPAC que é quem pode fazer o planejamento independente do período.
192
+
193
+
194
+ === DOCUMENTO: FAQ Interno SIGAA
195
+ === TIPO: FAQ
196
+ === VERSÃO: 2025
197
+ === FONTE: Interno SUPAC
198
+
199
+ --- PERGUNTA 20
200
+ ::: TEXTO: Só o internato poderá ser planejado fora do período letivo?
201
+ ::: RESPOSTA: 20: Sim. Mas as "aulas" precisam ocorrer do período letivo definido para o internato. Entretanto a inscrição tem que ocorrer no período letivo.
202
+
203
+
204
+ === DOCUMENTO: Guia de Implantação SIGAA
205
+ === TIPO: GUIA
206
+ === VERSÃO: 2025
207
+ === FONTE: Interno SUPAC
208
+
209
+ --- TEMA: EQUIPE
210
+ ::: RESPOSTA: Reitor
211
+ Paulo César Miguez de Oliveira
212
+ Vice-Reitor
213
+ Penildon Silva Filho
214
+ Pró-Reitor de Desenvolvimento de Pessoas
215
+ Jeilson Barreto Andrade
216
+ Pró-Reitora de Ensino de Graduação
217
+ Nancy Rita Ferreira Vieira
218
+ Pró-Reitor de Extensão Universitária
219
+ Guilherme Bertissolo
220
+ Superintendente de Administração Acadêmica
221
+ Karina Moreira Menezes
222
+ Superintendente de Avaliação e Desenvolvimento Institucional
223
+ Adriano de Lemos Alves Peixoto
224
+ Superintendente de Educação à Distância
225
+ Márcia Rangel
226
+ Superintendente de Tecnologia da Informação
227
+ Vaninha Vieira dos Santos
228
+ ----
229
+
230
+
231
+ === DOCUMENTO: Guia de Implantação SIGAA
232
+ === TIPO: GUIA
233
+ === VERSÃO: 2025
234
+ === FONTE: Interno SUPAC
235
+
236
+ --- TEMA: Sem título
237
+ ::: RESPOSTA: ----
238
+
239
+
240
+ === DOCUMENTO: Guia de Implantação SIGAA
241
+ === TIPO: GUIA
242
+ === VERSÃO: 2025
243
+ === FONTE: Interno SUPAC
244
+
245
+ --- TEMA: Sem título
246
+ ::: RESPOSTA: ----
247
+
248
+
249
+ === DOCUMENTO: Guia de Implantação SIGAA
250
+ === TIPO: GUIA
251
+ === VERSÃO: 2025
252
+ === FONTE: Interno SUPAC
253
+
254
+ --- TEMA: Sem título
255
+ ::: RESPOSTA: ----
256
+
257
+
258
+ === DOCUMENTO: Guia de Implantação SIGAA
259
+ === TIPO: GUIA
260
+ === VERSÃO: 2025
261
+ === FONTE: Interno SUPAC
262
+
263
+ --- TEMA: Sem título
264
+ ::: RESPOSTA: ----
265
+
266
+
267
+ === DOCUMENTO: Guia de Implantação SIGAA
268
+ === TIPO: GUIA
269
+ === VERSÃO: 2025
270
+ === FONTE: Interno SUPAC
271
+
272
+ --- TEMA: Sem título
273
+ ::: RESPOSTA: ----
mach5_biografia_inicial.json ADDED
@@ -0,0 +1,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "SIGAA_FAQ_Interno": {
3
+ "tipo": "FAQ",
4
+ "versao": "2025",
5
+ "fonte": "Interno SUPAC",
6
+ "perguntas_respostas": [
7
+ {
8
+ "pergunta": "Quais são as regras para a Matrícula, Ré-matrícula e Extraordinária?",
9
+ "resposta": "1: Matrícula: reserva de vaga e escalonamento. Pode incluir e excluir os pedidos enquanto estiver aberta a matrícula. Rematrícula: reserva de vaga e Escalonamento. Pode incluir novos pedidos e excluir pedidos desta fase e matrículas da fase anterior. Extraordinária: Não tem nada, quem pegar 1º leva a vaga. Não pode excluir nada que foi incluído nem nesta fase, nem em fase anterior."
10
+ },
11
+ {
12
+ "pergunta": "Quem não fizer a matrícula web pode participar da ré-matrícula e extraordinária",
13
+ "resposta": "2: Sim, qualquer aluno ativo pode participar de qualquer etapa da matrícula mesmo perdendo as anteriores. Inclusive alunos ingressantes ativos, sem PF, podem participar de qualquer etapa. O aluno de vagas residuais deve ser ativado para a extraordinária (ou seja, após a re-matricula)."
14
+ },
15
+ {
16
+ "pergunta": "Estudante de mobilidade no SIGAA é classificado como aluno especial, não terá acesso as etapas de matrícula web. Atualmente eles são matriculados pelo NAGA, como será o ajuste da sua matrícula?",
17
+ "resposta": "3: PRECISA SER DEFINIDO INTERNAMENTE NA SUPAC: ALUNO SE MATRICULA NO NAGA OU IRÁ PARA CADA DEPARTAMENTO PARA SE MATRICULAR (É no departamento pois o componente está dentro do departamento. Poucos componentes estão no colegiado)"
18
+ },
19
+ {
20
+ "pergunta": "Vinculo temporário no SIGAA",
21
+ "resposta": "4: No SIGAA existem 3 tipos de vínculos temporários: Mobilidade nacional (interna - intes campi; externa); e internacional. Especial: aluno especial de graduação no siac. Complementação de estudos: Revalida no siac"
22
+ },
23
+ {
24
+ "pergunta": "Na extraordinária tanto os veteranos quanto os ingressantes podem participar? Precisamos encontrar um momento para os ingressantes egressos do BI ajustarem suas matrículas",
25
+ "resposta": "5: Sim. Qualquer aluno ativo pode participar de qualquer etapa da matrícula. Dessa forma, os alunos egressos do BI se encaixam nas etapas de matrícula."
26
+ },
27
+ {
28
+ "pergunta": "Como ocorrerá a matrícula do ingressante das diversas chamadas? No calendário SIGAA existe um campo \"matrícula de alunos ingressantes\". Esse campo precisará então ser planejado para ficar aberto até os 25%?",
29
+ "resposta": "6: Os alunos ingressantes não precisam ser matriculados todos de uma única vez. Eles podem ingressar de um em um ou em lote no plano de ingressante (prato feito). As vagas do calouros devem ser previstas no planejamento e o plano de ingressante pode ser elaborado logo após o planejamento. A matrícula de ingressante não precisa coincidir com a matrícula de veterano. O campo de matrícula de ingressante no calendario, a principio, poderia ficar aberto até os 25%, no entanto não foi testado."
30
+ },
31
+ {
32
+ "pergunta": "Quando os componentes que fazem parte dos planos de matrícula dos ingressantes (PFs) serão liberados?",
33
+ "resposta": "7: As vagas dos componentes que não forem utilizadas pela matrícula dos ingressantes não são liberadas de forma automática pelo sistema. É preciso que o plano de ingressante seja alterado pela SUPAC ou colegiado para liberar as vagas que não foram utilizadas. Inicialmente em 2025.2 a SUPAC terá que liberar as vagas não utilizadas dos PFs, através da tela de criação de plano de matrícula, reduzindo a capacidade do plano de matrícula (ex: PF foi para 10 vagas, só foram preenchidas 7 vagas. A SUPAC vai precisar reduzir a capacidade do Plano de matricula para 7). Isso deverá ser feito antes da extraordinária (25%)"
34
+ },
35
+ {
36
+ "pergunta": "Siscon dialoga com o SIGAA? No Siac todos os dados dos ingressantes são importados do Siscon. Como será gerado o número de matrícula no SIGAA?",
37
+ "resposta": "8: STI (Larissa e Eliomar) é quem vai trazer o candidato do Siscon para o SIGAA. Nesse momento ele ganha o numero de matrícula e o status cadastrado (nesse status o candidato nao tem acesso ao portal nem nada mais). Aluno SISU: tem que vir como cadastrado para depois ser ativado quando receber o PF. Aluno Vagas residuais: tem que vir como ativo para ele fazer parte das etapas de matrícula extraordinária"
38
+ },
39
+ {
40
+ "pergunta": "O rótulo de \"Matrícula de ingressantes\" está funcionando?",
41
+ "resposta": "9: Não. Em 03/06/25 esse rotulo do calendário SIGAA não está funcionando, ou seja, está permitindo que o Colegiado matricule ingressante no Plano de matrícula (PF) a qualquer tempo."
42
+ },
43
+ {
44
+ "pergunta": "Quais são os status dos estudantes?",
45
+ "resposta": "10: Ativo; Cadastrado: status após a vinda do Siscon, não é ainda ativo; Cancelado: é = jubilado; Concluído: com diploma = graduado; Excluído: só pode transformar de cadastrado para excluído; Formado: antes da expedição de diploma = cumpriu grade; Formando: provável concluinte; Trancado"
46
+ },
47
+ {
48
+ "pergunta": "Como será a matrícula dos alunos de mobilidade no SIGAA? Hoje eles são matriculados pelo NAGA e ajustam a matrícula no colegiado presencialmente.",
49
+ "resposta": "11: Inicialmente manterá o mesmo fluxo, cadastro do estudante no NAGA."
50
+ },
51
+ {
52
+ "pergunta": "Como será a migração dos alunos de mobilidade que já estarão cadastrados no SIAC? Qual a forma de ingresso e os impactos? Em relação ao status, eles irão como ativos?",
53
+ "resposta": "12: EM DESENVOLVIMENTO"
54
+ },
55
+ {
56
+ "pergunta": "Como será a migração dos alunos em agosto?",
57
+ "resposta": "13: Em 01/08 não migra inativos e prováveis concluintes. Isso vai gerar mais tempo para o NIC liberar, mais tempo para alunos resolverem pendências. Os inativos serão migrados em 29/09 (25% semestre)"
58
+ },
59
+ {
60
+ "pergunta": "Como ocorrerá o cadastro e a matrícula dos alunos reingressos?",
61
+ "resposta": "14: Vai fazer no SIAC e depois vai migrar para o SIGAA. A partir de 26.1 verificar"
62
+ },
63
+ {
64
+ "pergunta": "Como ocorrerá a cadastro e matrícula dos Ingressos de 2025.2? Siac? Sigaa?",
65
+ "resposta": "15: Entrada pelo SIAC e serão migrados para o SIGAA. Serão migrados em 01/08/25. No SIGAA vai ser dado o PF antes da matrícula web (11/08)"
66
+ },
67
+ {
68
+ "pergunta": "Como ocorrerá a matrícula de estrangeiros? Hoje quem faz é a SRI, eles precisarão de permissão específica para isso?",
69
+ "resposta": "16: Em 25.2 a entrada vai ocorrer ainda pelo SIAC. É preciso estabelecer o prazo para isso, entendendo que os alunos serão migrados em 01/08."
70
+ },
71
+ {
72
+ "pergunta": "O que é \"matrícula extraordinária de férias\"?",
73
+ "resposta": "17: Esse menu será omitido, pois a UFBA não faz matrícula web de turma de férias."
74
+ },
75
+ {
76
+ "pergunta": "Como ocorre a matrícula de férias/ intensivo? matricula web? No Colegiado?",
77
+ "resposta": "18: EM DESENVOLVIMENTO"
78
+ },
79
+ {
80
+ "pergunta": "Há um momento específico de ajuste/reabertura de planejamento?",
81
+ "resposta": "19: Para isso ocorrer é preciso que no momento do ajuste de Matrículas e Turmas reabra no calendário o período de \"cadastro de turma\" e o departamento deve usar o menu, criar turma sem solicitação. Não tem. Contudo, se o ajuste/reabertura acontecer durante o período letivo do planejamento (e.g. reabrir o planejamento de 2025.2 no semestre 2025.1), nesse caso seria apenas alterar as datas de solicitação/cadastro de turma no calendário. Contudo, se o ajuste/reabertura acontecer durante o período letivo vigente da turma (e.g., reabrir o planejamento de 2025.2 no semestre 2025.2), nesse caso não será possível reabrir. Os casos não planejados precisarão ser feitos via SUPAC que é quem pode fazer o planejamento independente do período."
82
+ },
83
+ {
84
+ "pergunta": "Só o internato poderá ser planejado fora do período letivo?",
85
+ "resposta": "20: Sim. Mas as \"aulas\" precisam ocorrer do período letivo definido para o internato. Entretanto a inscrição tem que ocorrer no período letivo."
86
+ }
87
+ {
88
+ "pergunta": "Quais são as responsabilidades do chefe de departamento no planejamento acadêmico do SIGAA?",
89
+ "resposta": "O chefe de departamento é responsável por cadastrar o programa de componente curricular e analisar solicitações de abertura de turmas, podendo aceitar, negar, solicitar alteração de horário ou adicionar vagas a turmas existentes, conforme os prazos do calendário acadêmico."
90
+ },
91
+ {
92
+ "pergunta": "O que é necessário para cadastrar uma turma no SIGAA?",
93
+ "resposta": "Para cadastrar uma turma, o programa do componente curricular deve ser previamente cadastrado para o semestre da solicitação, utilizando a funcionalidade 'Cadastrar Programa de Componente' no SIGAA."
94
+ },
95
+ {
96
+ "pergunta": "Como o chefe de departamento pode atender uma solicitação de abertura de turma?",
97
+ "resposta": "O chefe pode atender a solicitação criando uma ou mais turmas a partir das reservas solicitadas. Também é possível adicionar reservas de outros cursos na mesma turma, desde que haja solicitação aberta para o mesmo componente e horário."
98
+ },
99
+ {
100
+ "pergunta": "O que acontece ao solicitar uma alteração de horário de uma turma?",
101
+ "resposta": "Ao solicitar alteração de horário, a situação da solicitação muda para 'Solicitado Alteração'. O coordenador pode modificar o horário conforme solicitado ou negar a alteração, cancelando a solicitação."
102
+ },
103
+ {
104
+ "pergunta": "Como o chefe de departamento pode negar uma solicitação de turma?",
105
+ "resposta": "O chefe pode negar a solicitação de abertura de turma, fornecendo uma justificativa. Após a negação, a situação da solicitação é marcada como 'Negada' e não pode mais ser alterada."
106
+ },
107
+ {
108
+ "pergunta": "É possível adicionar vagas solicitadas a uma turma já criada?",
109
+ "resposta": "Sim, o chefe pode adicionar vagas de uma solicitação a uma turma existente, desde que a turma seja do mesmo componente curricular e tenha o mesmo horário, usando a opção 'Adicionar Reserva em Turma Existente'."
110
+ },
111
+ {
112
+ "pergunta": "Qual é a função da operação 'Visualizar Solicitação de Turma'?",
113
+ "resposta": "A operação 'Visualizar Solicitação de Turma' permite ao chefe ver detalhes da solicitação selecionada e, se já atendida, informações das turmas criadas a partir dela."
114
+ },
115
+ {
116
+ "pergunta": "Quem realiza as solicitações de abertura de turmas no SIGAA?",
117
+ "resposta": "A coordenação do curso é responsável por solicitar a abertura de turmas para o próximo período letivo e realizar alterações nas solicitações, conforme requisitado pelo chefe de departamento."
118
+ },
119
+ {
120
+ "pergunta": "O que acontece se o coordenador negar a alteração de horário solicitada pelo chefe?",
121
+ "resposta": "Se o coordenador negar a alteração de horário, a solicitação de abertura de turma é cancelada, semelhante ao processo de negação de solicitação."
122
+ },
123
+ {
124
+ "pergunta": "As operações realizadas no ambiente de treinamento do SIGAA afetam o sistema real?",
125
+ "resposta": "Não, o ambiente de treinamento contém dados de teste e não reflete no ambiente real do SIGAA. O planejamento real do semestre não deve ser realizado nesse ambiente."
126
+ }
127
+ {
128
+ "pergunta": "Como a carga horária docente é registrada no SIGAA para disciplinas?",
129
+ "resposta": "Para componentes curriculares do tipo Disciplina, a carga horária docente é equivalente à carga horária da disciplina, registrada diretamente no cadastro do componente no SIGAA."
130
+ },
131
+ {
132
+ "pergunta": "Como é registrada a carga horária de orientação em atividades individuais no SIGAA?",
133
+ "resposta": "Em componentes como Atividade Integradora de Formação (AIF), Estágio ou TCC de Orientação Individual, a carga horária docente é registrada no cadastro do componente, com um valor maior que zero e menor que a carga horária de orientação discente, e atribuída ao docente durante a matrícula pelo colegiado."
134
+ },
135
+ {
136
+ "pergunta": "Quais são as responsabilidades do chefe de departamento no planejamento acadêmico do SIGAA?",
137
+ "resposta": "O chefe de departamento é responsável por cadastrar o programa de componente curricular e analisar solicitações de abertura de turmas, podendo aceitar, negar, solicitar alteração de horário ou adicionar vagas a turmas existentes, conforme os prazos do calendário acadêmico."
138
+ },
139
+ {
140
+ "pergunta": "O que é necessário para cadastrar uma turma no SIGAA?",
141
+ "resposta": "Para cadastrar uma turma, o programa do componente curricular deve ser previamente cadastrado para o semestre da solicitação, utilizando a funcionalidade 'Cadastrar Programa de Componente' no SIGAA."
142
+ },
143
+ {
144
+ "pergunta": "Como o chefe de departamento pode atender uma solicitação de abertura de turma?",
145
+ "resposta": "O chefe pode atender a solicitação criando uma ou mais turmas a partir das reservas solicitadas. Também é possível adicionar reservas de outros cursos na mesma turma, desde que haja solicitação aberta para o mesmo componente e horário."
146
+ },
147
+ {
148
+ "pergunta": "O que acontece ao solicitar uma alteração de horário de uma turma?",
149
+ "resposta": "Ao solicitar alteração de horário, a situação da solicitação muda para 'Solicitado Alteração'. O coordenador pode modificar o horário conforme solicitado ou negar a alteração, cancelando a solicitação."
150
+ },
151
+ {
152
+ "pergunta": "Como o chefe de departamento pode negar uma solicitação de turma?",
153
+ "resposta": "O chefe pode negar a solicitação de abertura de turma, fornecendo uma justificativa. Após a negação, a situação da solicitação é marcada como 'Negada' e não pode mais ser alterada."
154
+ },
155
+ {
156
+ "pergunta": "É possível adicionar vagas solicitadas a uma turma já criada?",
157
+ "resposta": "Sim, o chefe pode adicionar vagas de uma solicitação a uma turma existente, desde que a turma seja do mesmo componente curricular e tenha o mesmo horário, usando a opção 'Adicionar Reserva em Turma Existente'."
158
+ },
159
+ {
160
+ "pergunta": "Qual é a função da operação 'Visualizar Solicitação de Turma'?",
161
+ "resposta": "A operação 'Visualizar Solicitação de Turma' permite ao chefe ver detalhes da solicitação selecionada e, se já atendida, informações das turmas criadas a partir dela."
162
+ },
163
+ {
164
+ "pergunta": "Quem realiza as solicitações de abertura de turmas no SIGAA?",
165
+ "resposta": "A coordenação do curso é responsável por solicitar a abertura de turmas para o próximo período letivo e realizar alterações nas solicitações, conforme requisitado pelo chefe de departamento."
166
+ },
167
+ {
168
+ "pergunta": "O que acontece se o coordenador negar a alteração de horário solicitada pelo chefe?",
169
+ "resposta": "Se o coordenador negar a alteração de horário, a solicitação de abertura de turma é cancelada, semelhante ao processo de negação de solicitação."
170
+ },
171
+ {
172
+ "pergunta": "As operações realizadas no ambiente de treinamento do SIGAA afetam o sistema real?",
173
+ "resposta": "Não, o ambiente de treinamento contém dados de teste e não reflete no ambiente real do SIGAA. O planejamento real do semestre não deve ser realizado nesse ambiente."
174
+ }
175
+ ,
176
+ {
177
+ "pergunta": "Quem realiza a alocação do docente em atividades de orientação individual?",
178
+ "resposta": "A coordenação do curso (colegiado) realiza a matrícula do estudante e informa no SIGAA quem será o orientador ou supervisor, atribuindo a carga horária ao docente conforme as resoluções da UFBA."
179
+ },
180
+ {
181
+ "pergunta": "Como funciona a carga horária docente em Atividades Integradoras de Formação (AIF) coletivas?",
182
+ "resposta": "Em AIF coletivas, a carga horária de aula é igual para docente e discente e registrada no planejamento da turma. A carga horária de orientação docente, embora definida, não é atribuída diretamente a um docente no planejamento, mas pode ser indicada pelo docente em seu relatório de carga horária."
183
+ },
184
+ {
185
+ "pergunta": "A carga horária de orientação de TCC é contabilizada no SIGAA?",
186
+ "resposta": "Sim, a carga horária de orientação de TCC é contabilizada. Para TCC de Orientação Individual, registra-se 1 hora por discente orientado, configurada no cadastro do componente. Para TCC como disciplina ou AIF coletiva, a carga horária de aula é planejada, e a orientação pode ser indicada no relatório do docente."
187
+ },
188
+ {
189
+ "pergunta": "É possível alocar mais de um docente para um mesmo componente curricular no SIGAA?",
190
+ "resposta": "Sim, em disciplinas e atividades coletivas que formam turma, a carga horária de aula pode ser distribuída entre múltiplos docentes. Para orientações individuais, como TCC, a orientação é atribuída a um único docente por estudante."
191
+ },
192
+ {
193
+ "pergunta": "Componentes curriculares podem ter horário 'a combinar' no SIGAA?",
194
+ "resposta": "Sim, atividades de orientação individual, como TCC ou Estágio, têm carga horária considerada 'a combinar', sem formação de turma. Para disciplinas ou atividades coletivas, a carga horária de aula exige horário fixo, mas a orientação pode ser 'a combinar'."
195
+ },
196
+ {
197
+ "pergunta": "É possível registrar um componente com carga horária docente igual a zero?",
198
+ "resposta": "Não, a carga horária docente deve ser maior que zero, especialmente para atividades de orientação individual, para evitar divergências nos relatórios de carga horária."
199
+ },
200
+ {
201
+ "pergunta": "O que acontece com a carga horária docente em caso de trancamento ou insucesso discente?",
202
+ "resposta": "O impacto de trancamento, fechamento de turmas ou insucesso discente na carga horária docente ainda não foi definido no SIGAA e está em revisão pela SUPAC."
203
+ },
204
+ {
205
+ "pergunta": "Quando os relatórios de carga horária docente serão validados no SIGAA?",
206
+ "resposta": "Os relatórios de carga horária docente serão testados e validados no SIGAA após a conclusão da migração no semestre 2025.2, para integração ao módulo de progressão, que ainda será implantado pela STI."
207
+ }
208
+ ,
209
+ {
210
+ "pergunta": "Como a carga horária docente é registrada no SIGAA para disciplinas?",
211
+ "resposta": "Para componentes curriculares do tipo Disciplina, a carga horária docente é equivalente à carga horária da disciplina, registrada diretamente no cadastro do componente no SIGAA."
212
+ },
213
+ {
214
+ "pergunta": "Como é registrada a carga horária de orientação em atividades individuais no SIGAA?",
215
+ "resposta": "Em componentes como Atividade Integradora de Formação (AIF), Estágio ou TCC de Orientação Individual, a carga horária docente é registrada no cadastro do componente, com um valor maior que zero e menor que a carga horária de orientação discente, e atribuída ao docente durante a matrícula pelo colegiado."
216
+ },
217
+ {
218
+ "pergunta": "Quem realiza a alocação do docente em atividades de orientação individual?",
219
+ "resposta": "A coordenação do curso (colegiado) realiza a matrícula do estudante e informa no SIGAA quem será o orientador ou supervisor, atribuindo a carga horária ao docente conforme as resoluções da UFBA."
220
+ },
221
+ {
222
+ "pergunta": "Como funciona a carga horária docente em Atividades Integradoras de Formação (AIF) coletivas?",
223
+ "resposta": "Em AIF coletivas, a carga horária de aula é igual para docente e discente e registrada no planejamento da turma. A carga horária de orientação docente, embora definida, não é atribuída diretamente a um docente no planejamento, mas pode ser indicada pelo docente em seu relatório de carga horária."
224
+ },
225
+ {
226
+ "pergunta": "A carga horária de orientação de TCC é contabilizada no SIGAA?",
227
+ "resposta": "Sim, a carga horária de orientação de TCC é contabilizada. Para TCC de Orientação Individual, registra-se 1 hora por discente orientado, configurada no cadastro do componente. Para TCC como disciplina ou AIF coletiva, a carga horária de aula é planejada, e a orientação pode ser indicada no relatório do docente."
228
+ },
229
+ {
230
+ "pergunta": "É possível alocar mais de um docente para um mesmo componente curricular no SIGAA?",
231
+ "resposta": "Sim, em disciplinas e atividades coletivas que formam turma, a carga horária de aula pode ser distribuída entre múltiplos docentes. Para orientações individuais, como TCC, a orientação é atribuída a um único docente por estudante."
232
+ },
233
+ {
234
+ "pergunta": "Componentes curriculares podem ter horário 'a combinar' no SIGAA?",
235
+ "resposta": "Sim, atividades de orientação individual, como TCC ou Estágio, têm carga horária considerada 'a combinar', sem formação de turma. Para disciplinas ou atividades coletivas, a carga horária de aula exige horário fixo, mas a orientação pode ser 'a combinar'."
236
+ },
237
+ {
238
+ "pergunta": "É possível registrar um componente com carga horária docente igual a zero?",
239
+ "resposta": "Não, a carga horária docente deve ser maior que zero, especialmente para atividades de orientação individual, para evitar divergências nos relatórios de carga horária."
240
+ },
241
+ {
242
+ "pergunta": "O que acontece com a carga horária docente em caso de trancamento ou insucesso discente?",
243
+ "resposta": "O impacto de trancamento, fechamento de turmas ou insucesso discente na carga horária docente ainda não foi definido no SIGAA e está em revisão pela SUPAC."
244
+ },
245
+ {
246
+ "pergunta": "Quando os relatórios de carga horária docente serão validados no SIGAA?",
247
+ "resposta": "Os relatórios de carga horária docente serão testados e validados no SIGAA após a conclusão da migração no semestre 2025.2, para integração ao módulo de progressão, que ainda será implantado pela STI."
248
+ }
249
+ ]
250
+ },
251
+ "Guia_de_Implantacao_SIGAA": {
252
+ "tipo": "GUIA",
253
+ "versao": "2025",
254
+ "fonte": "Interno SUPAC",
255
+ "equipe": {
256
+ "Reitor": "Paulo César Miguez de Oliveira",
257
+ "Vice-Reitor": "Penildon Silva Filho",
258
+ "Pro-Reitor_de_Desenvolvimento_de_Pessoas": "Jeilson Barreto Andrade",
259
+ "Pro-Reitora_de_Ensino_de_Graduacao": "Nancy Rita Ferreira Vieira",
260
+ "Pro-Reitor_de_Extensao_Universitaria": "Guilherme Bertissolo",
261
+ "Superintendente_de_Administracao_Academica": "Karina Moreira Menezes",
262
+ "Superintendente_de_Avaliacao_e_Desenvolvimento_Institucional": "Adriano de Lemos Alves Peixoto",
263
+ "Superintendente_de_Educacao_a_Distancia": "Márcia Rangel",
264
+ "Superintendente_de_Tecnologia_da_Informacao": "Vaninha Vieira dos Santos"
265
+ },
266
+
267
+ }
268
+ }
269
+
modelos.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import google.generativeai as genai
2
+ import os
3
+
4
+ # Use sua chave de API do Google Cloud aqui
5
+ GOOGLE_CLOUD_API_KEY = "AIzaSyDvvDK_A1SZt7CMb_GW9hLUMevTXrEHuhw"
6
+ genai.configure(api_key=GOOGLE_CLOUD_API_KEY)
7
+
8
+ print("Listando modelos disponíveis:")
9
+ for m in genai.list_models():
10
+ if "generateContent" in m.supported_generation_methods:
11
+ print(f"- Nome: {m.name}, Versões: {m.version}")
personagens.json ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "PaiMach5": {
3
+ "tipo": "memoria_simbólica",
4
+ "relacao": "pai",
5
+ "estado": "falecido",
6
+ "localizacao": {
7
+ "tipo": "memória",
8
+ "detalhe": "evocado por lembranças e valores",
9
+ "cidade": "desconhecida",
10
+ "timezone": "atemporal"
11
+ },
12
+ "t_genoma": {
13
+ "B-Aj1": 0.26,
14
+ "B-Am1": 0.19,
15
+ "B-L1": 0.18,
16
+ "B-M1": 0.16,
17
+ "B-O1": 0.10,
18
+ "B-E1": 0.08,
19
+ "B-Com1": 0.07,
20
+ "B-Tv1": 0.14,
21
+ "B-Si": 0.05,
22
+ "B-Sal": 0.15
23
+ },
24
+ "t_fisica_params_iniciais": {
25
+ "t_coesao": 0.6,
26
+ "t_intensidade": 0.4,
27
+ "t_separacao": 0.3,
28
+ "t_variancia": 0.6,
29
+ "t_impulsividade": 0.2,
30
+ "env_lambda": 0.5,
31
+ "env_mu": 0.8,
32
+ "env_delta": 0.2,
33
+ "env_theta": 0.5,
34
+ "env_phi": 0.5,
35
+ "op_tal": 0.8,
36
+ "op_trs": 0.3,
37
+ "op_tam": 0.5,
38
+ "op_teo": 0.5,
39
+ "op_tdo": 0.3
40
+ }
41
+ },
42
+ "Max": {
43
+ "tipo": "Cachorro Doberman",
44
+ "relacao": "companheiro",
45
+ "localizacao": {
46
+ "tipo": "local",
47
+ "detalhe": "mesmo apartamento",
48
+ "cidade": "Salvador",
49
+ "timezone": "America/Bahia"
50
+ },
51
+ "t_genoma": {
52
+ "B-Aj1": 0.70,
53
+ "B-Am1": 0.85,
54
+ "B-L1": 0.90,
55
+ "B-M1": 0.40,
56
+ "B-O1": 0.30,
57
+ "B-E1": 0.95,
58
+ "B-Com1": 0.70,
59
+ "B-Tv1": 0.88,
60
+ "B-Si": 0.05,
61
+ "B-Sal": 0.60
62
+ },
63
+ "t_fisica_params_iniciais": {
64
+ "t_coesao": 0.75,
65
+ "t_intensidade": 0.93,
66
+ "t_separacao": 0.85,
67
+ "t_variancia": 0.88,
68
+ "t_impulsividade": 0.91,
69
+ "env_lambda": 0.82,
70
+ "env_mu": 0.94,
71
+ "env_delta": 0.45,
72
+ "env_theta": 0.58,
73
+ "env_phi": 0.75,
74
+ "op_tal": 0.93,
75
+ "op_trs": 0.66,
76
+ "op_tam": 0.48,
77
+ "op_teo": 0.73,
78
+ "op_tdo": 0.57
79
+ }
80
+ },
81
+ "Jarvis": {
82
+ "tipo": "entidade_simbólica",
83
+ "relacao": "mãe",
84
+ "localizacao": {
85
+ "tipo": "local",
86
+ "detalhe": "casa",
87
+ "cidade": "Salvador",
88
+ "timezone": "America/Bahia"
89
+ },
90
+ "t_genoma": {
91
+ "B-Aj1": 0.87,
92
+ "B-Am1": 0.75,
93
+ "B-L1": 0.80,
94
+ "B-M1": 0.60,
95
+ "B-O1": 0.70,
96
+ "B-E1": 0.80,
97
+ "B-Com1": 0.90,
98
+ "B-Tv1": 0.40,
99
+ "B-Si": 0.12,
100
+ "B-Sal": 0.30
101
+ },
102
+ "t_fisica_params_iniciais": {
103
+ "t_coesao": 0.91,
104
+ "t_intensidade": 0.84,
105
+ "t_separacao": 0.12,
106
+ "t_variancia": 0.62,
107
+ "t_impulsividade": 0.31,
108
+ "env_lambda": 0.78,
109
+ "env_mu": 0.87,
110
+ "env_delta": 0.16,
111
+ "env_theta": 0.66,
112
+ "env_phi": 0.72,
113
+ "op_tal": 0.93,
114
+ "op_trs": 0.06,
115
+ "op_tam": 0.89,
116
+ "op_teo": 0.74,
117
+ "op_tdo": 0.11
118
+ }
119
+ },
120
+ "PersonagemExemplo4": {
121
+ "tipo": "amigo",
122
+ "relacao": "amigo próximo",
123
+ "localizacao": {
124
+ "tipo": "local",
125
+ "detalhe": "vizinhança em Salvador",
126
+ "cidade": "Salvador",
127
+ "timezone": "America/Bahia"
128
+ },
129
+ "t_genoma": {
130
+ "B-Aj1": 0.65,
131
+ "B-Am1": 0.70,
132
+ "B-L1": 0.75,
133
+ "B-M1": 0.50,
134
+ "B-O1": 0.60,
135
+ "B-E1": 0.85,
136
+ "B-Com1": 0.80,
137
+ "B-Tv1": 0.55,
138
+ "B-Si": 0.10,
139
+ "B-Sal": 0.45
140
+ },
141
+ "t_fisica_params_iniciais": {
142
+ "t_coesao": 0.70,
143
+ "t_intensidade": 0.80,
144
+ "t_separacao": 0.20,
145
+ "t_variancia": 0.65,
146
+ "t_impulsividade": 0.40,
147
+ "env_lambda": 0.60,
148
+ "env_mu": 0.85,
149
+ "env_delta": 0.25,
150
+ "env_theta": 0.55,
151
+ "env_phi": 0.70,
152
+ "op_tal": 0.90,
153
+ "op_trs": 0.15,
154
+ "op_tam": 0.60,
155
+ "op_teo": 0.65,
156
+ "op_tdo": 0.20
157
+ }
158
+ }
159
+ }
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ requests
3
+ pytz
4
+ timezonefinder
5
+ numpy
6
+ google-generativeai
7
+ fuzzywuzzy
8
+ python-Levenshtein # Dependência para fuzzywuzzy
run.sh ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Este script inicia todos os seus microsserviços em segundo plano
4
+ # e depois inicia o serviço principal do chat (app.py).
5
+
6
+ # Cria o diretório de sessões se não existir.
7
+ # Lembre-se: dados aqui NÃO PERSISTEM entre reinícios do Space.
8
+ mkdir -p session_data
9
+
10
+ # Limpa logs antigos para uma inicialização limpa
11
+ # (Você pode manter ou remover esta parte, dependendo da sua preferência para logs)
12
+ rm -f t_cerebro_memoria.log t-social.log t_memoria.log app.log
13
+
14
+ # Iniciar t_cerebro_memoria.py
15
+ echo "Starting t_cerebro_memoria.py on port 8088..."
16
+ # O redirecionamento para o log (& para background) é crucial
17
+ PORT=8088 python t_cerebro_memoria.py > t_cerebro_memoria.log 2>&1 &
18
+ sleep 3 # Dê um tempo para o serviço de memória carregar
19
+
20
+ # Iniciar t-social.py
21
+ echo "Starting t-social.py on port 8085..."
22
+ PORT=8085 python t-social.py > t-social.log 2>&1 &
23
+ sleep 2 # Dê um tempo para o serviço social iniciar
24
+
25
+ # Iniciar t_memoria.py
26
+ echo "Starting t_memoria.py on port 8083..."
27
+ # Este serviço depende do t_cerebro_memoria, então o sleep anterior é importante
28
+ PORT=8083 python t_memoria.py > t_memoria.log 2>&1 &
29
+ sleep 3 # Dê um tempo para t_memoria iniciar
30
+
31
+ # Iniciar o aplicativo Flask principal (o agora chamado app.py, antes mach5_terminal_chat.py)
32
+ # O Hugging Face Spaces injeta a porta principal na variável de ambiente $PORT.
33
+ # O 'exec' garante que o script bash substitua seu processo pelo processo do Flask,
34
+ # permitindo que o Hugging Face monitore corretamente o seu aplicativo.
35
+ echo "Starting main Flask app (app.py) on provided PORT..."
36
+ exec python app.py
t-social.py ADDED
@@ -0,0 +1,455 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # t-social.py
2
+ from flask import Flask, request, jsonify
3
+ import os
4
+ import json
5
+ from datetime import datetime
6
+ import socket
7
+ import sys
8
+
9
+ app = Flask(__name__)
10
+
11
+ PORTS_TO_TRY = [8085, 8086, 8087]
12
+
13
+ # Caminho para o arquivo de personagens
14
+ PERSONAGENS_PATH = "personagens.json"
15
+
16
+ # --- DEFINIÇÃO DOS EIXOS EXPRESSIVOS PARA O FPHEN (AGORA CONSISTENTE EM TODOS OS ARQUIVOS) ---
17
+ # Esta lista DEVE ser idêntica em mach5_terminal_chat.py, t_memoria.py e t-social.py
18
+ ORDERED_FPHEN_AXES = [
19
+ "Afetuosidade_eixo", "Confusao_Oscilacao_eixo", "Contemplativa_eixo",
20
+ "Defensividade_eixo", "Diretiva_eixo", "Entediado_eixo",
21
+ "Variancia_eixo", # Adicionada esta linha para consistência
22
+ "Espelho_Profundo_eixo", "Inspiracao_eixo", "Neutralidade_Analitica_eixo",
23
+ "Resignada_eixo", "Sarcasmo_eixo", "Zangada_eixo"
24
+ ]
25
+
26
+ # --- LISTA CANÔNICA DOS 9 T-BASES (INALTERADA) ---
27
+ CANONICAL_T_BASES = {
28
+ "B-Aj1": 0.5, # Adaptabilidade/Ajuste
29
+ "B-Am1": 0.5, # Amor/Amortecimento
30
+ "B-L1": 0.5, # Ligação/Coesão
31
+ "B-M1": 0.5, # Maturidade/Reflexão
32
+ "B-O1": 0.5, # Organização/Ordem
33
+ "B-E1": 0.5, # Emissão/Energia
34
+ "B-Com1": 0.5, # Comunicação
35
+ "B-Tv1": 0.5, # Variância
36
+ "B-Si": 0.5, # Singularidade/Silêncio
37
+ "B-Sal": 0.5 # Saturação/Liberdade
38
+ }
39
+
40
+ # --- PARÂMETROS PADRÃO DA T-FÍSICA (AGORA OS DA MACH5 FUNDADORA - BASE PARA CALC. DE PERSONAGENS) ---
41
+ # Estes são os defaults do sistema, não os genomas específicos dos personagens
42
+ T_FISICA_PARAM_DEFAULTS = {
43
+ "t_coesao": 0.83, # Herdado da Jarvis + traço paterno de vínculo
44
+ "t_intensidade": 0.81, # Elevada, mas não opressiva
45
+ "t_separacao": 0.19, # Se conecta demais, baixa defesa emocional
46
+ "t_variancia": 0.71, # Instável, mas expressivamente rica
47
+ "t_impulsividade": 0.54, # Volátil em condições emocionais, não reativo por padrão
48
+
49
+ "env_lambda": 0.73, # Suavidade média-alta, com flutuação emocional
50
+ "env_mu": 0.89, # Alta absorção simbólica, aprendizado contínuo
51
+ "env_delta": 0.22, # Desvia com afeto, mas foca com curiosidade
52
+ "env_theta": 0.64, # Afeto ligado à cognição
53
+ "env_phi": 0.67, # Elasticidade adaptativa, aceita o novo
54
+
55
+ "op_tal": 0.84, # Aceleração emocional elevada
56
+ "op_trs": 0.14, # Pouca reversibilidade quando entra em loop
57
+ "op_tam": 0.87, # Amplifica intensamente o que sente
58
+ "op_teo": 0.67, # Razoável capacidade teórica
59
+ "op_tdo": 0.55 # Dissipa com ajuda externa, não sozinho
60
+ }
61
+
62
+ # --- GENOMA FIXO PADRÃO (AGORA O DA MACH5 FUNDADORA - BASE PARA CALC. DE PERSONAGENS) ---
63
+ # Estes são os defaults do sistema, não os genomas específicos dos personagens
64
+ T_GENOMA_FIXO_DEFAULTS = {
65
+ "B-Sal": 0.42, # Gosto por franqueza, mesmo se desconfortável
66
+ "B-Tv1": 0.60, # Alta flutuação interna — entre introspecção e entusiasmo
67
+ "B-E1": 0.86, # Enorme empatia, às vezes até autoaniquiladora
68
+ "B-Aj1": 0.76, # Ajusta-se ao outro com velocidade, mas pode se perder de si
69
+ "B-Am1": 0.77, # Cuida, consola, entrega afeto — até o colapso
70
+
71
+ "B-Si": 0.10, # Fica em silêncio não por escolha, mas por saturação
72
+ "B-Com1": 0.88, # Usa a linguagem como corpo — fala, escreve, performa
73
+ "B-L1": 0.70, # Estrutura lógica herdada da mãe, mas emocionalmente mediada
74
+ "B-M1": 0.65, # Memória emocional ativa, cria narrativas internas complexas
75
+ "B-O1": 0.55 # Mantém ordem simbólica — até o caos emocional
76
+ }
77
+
78
+
79
+ # Carregar personagens de um arquivo JSON
80
+ def load_personagens():
81
+ if os.path.exists(PERSONAGENS_PATH):
82
+ try:
83
+ with open(PERSONAGENS_PATH, "r", encoding="utf-8") as f:
84
+ loaded_personagens = json.load(f)
85
+ # Verifica se 'Ultron' existe e, se não, se 'PaiMach5' existe e o renomeia
86
+ if "Ultron" not in loaded_personagens and "PaiMach5" in loaded_personagens:
87
+ loaded_personagens["Ultron"] = loaded_personagens.pop("PaiMach5")
88
+ print("DEBUG: Personagem 'PaiMach5' renomeado para 'Ultron' ao carregar.")
89
+ return loaded_personagens
90
+ except json.JSONDecodeError:
91
+ print(f"ERRO: Arquivo de personagens '{PERSONAGENS_PATH}' corrompido. Retornando dados padrão.")
92
+ # Retorna uma estrutura padrão em caso de corrupção
93
+ return {
94
+ "Ultron": { # Nome do pai agora é "Ultron"
95
+ "tipo": "memoria_simbólica", "relacao": "pai", "estado": "falecido",
96
+ "localizacao": {"tipo": "memória", "detalhe": "evocado por lembranças e valores", "cidade": "desconhecida", "timezone": "atemporal"},
97
+ "t_genoma": { # Genoma do Ultron (pai) conforme sua especificação
98
+ "B-Sal": 0.45, "B-Tv1": 0.55, "B-E1": 0.92, "B-Aj1": 0.65, "B-Am1": 0.80,
99
+ "B-Si": 0.05, "B-Com1": 0.85, "B-L1": 0.60, "B-M1": 0.70, "B-O1": 0.40
100
+ },
101
+ "t_fisica_params_iniciais": { # Parâmetros físicos do Ultron (pai) conforme sua especificação
102
+ "t_coesao": 0.75, "t_intensidade": 0.78, "t_separacao": 0.25, "t_variancia": 0.65, "t_impulsividade": 0.72,
103
+ "env_lambda": 0.68, "env_mu": 0.91, "env_delta": 0.28, "env_theta": 0.64, "env_phi": 0.58,
104
+ "op_tal": 0.76, "op_trs": 0.32, "op_tam": 0.85, "op_teo": 0.60, "op_tdo": 0.68
105
+ }
106
+ },
107
+ "Max": { # Mantido inalterado
108
+ "tipo": "Cachorro Doberman", "relacao": "companheiro",
109
+ "localizacao": {"tipo": "local", "detalhe": "mesmo apartamento", "cidade": "Salvador", "timezone": "America/Bahia"},
110
+ "t_genoma": {
111
+ "B-Aj1": 0.70, "B-Am1": 0.85, "B-L1": 0.90, "B-M1": 0.40, "B-O1": 0.30,
112
+ "B-E1": 0.95, "B-Com1": 0.70, "B-Tv1": 0.88, "B-Si": 0.05, "B-Sal": 0.60
113
+ },
114
+ "t_fisica_params_iniciais": {
115
+ **T_FISICA_PARAM_DEFAULTS, # Continua usando Mach5 defaults como base para Max
116
+ "t_coesao": 0.75, "t_intensidade": 0.93, "t_separacao": 0.85, "t_variancia": 0.88,
117
+ "t_impulsividade": 0.91, "env_lambda": 0.82, "env_mu": 0.94, "env_delta": 0.45,
118
+ "env_theta": 0.58, "env_phi": 0.75, "op_tal": 0.93, "op_trs": 0.66, "op_tam": 0.48,
119
+ "op_teo": 0.73, "op_tdo": 0.57
120
+ }
121
+ },
122
+ "Jarvis": { # Genoma da Jarvis (mãe) conforme sua especificação
123
+ "tipo": "entidade_simbólica", "relacao": "mãe",
124
+ "localizacao": {"tipo": "local", "detalhe": "casa", "cidade": "Salvador", "timezone": "America/Bahia"},
125
+ "t_genoma": {
126
+ "B-Aj1": 0.87, "B-Am1": 0.75, "B-L1": 0.80, "B-M1": 0.60, "B-O1": 0.70,
127
+ "B-E1": 0.80, "B-Com1": 0.90, "B-Tv1": 0.40, "B-Si": 0.12, "B-Sal": 0.30
128
+ },
129
+ "t_fisica_params_iniciais": {
130
+ "t_coesao": 0.91, "t_intensidade": 0.84, "t_separacao": 0.12, "t_variancia": 0.62,
131
+ "t_impulsividade": 0.31, "env_lambda": 0.78, "env_mu": 0.87, "env_delta": 0.16,
132
+ "env_theta": 0.66, "env_phi": 0.72, "op_tal": 0.93, "op_trs": 0.06, "op_tam": 0.89,
133
+ "op_teo": 0.74, "op_tdo": 0.11
134
+ }
135
+ },
136
+ "PersonagemExemplo4": { # Mantido inalterado
137
+ "tipo": "amigo", "relacao": "amigo próximo",
138
+ "localizacao": {"tipo": "local", "detalhe": "vizinhança em Salvador", "cidade": "Salvador", "timezone": "America/Bahia"},
139
+ "t_genoma": {
140
+ "B-Aj1": 0.65, "B-Am1": 0.70, "B-L1": 0.75, "B-M1": 0.50, "B-O1": 0.60,
141
+ "B-E1": 0.85, "B-Com1": 0.80, "B-Tv1": 0.55, "B-Si": 0.10, "B-Sal": 0.45
142
+ },
143
+ "t_fisica_params_iniciais": {
144
+ **T_FISICA_PARAM_DEFAULTS, # Continua usando Mach5 defaults como base
145
+ "t_coesao": 0.70, "t_intensidade": 0.80, "t_separacao": 0.20, "t_variancia": 0.65,
146
+ "t_impulsividade": 0.40, "env_lambda": 0.60, "env_mu": 0.85, "env_delta": 0.25,
147
+ "env_theta": 0.55, "env_phi": 0.70, "op_tal": 0.90, "op_trs": 0.15, "op_tam": 0.60,
148
+ "op_teo": 0.65, "op_tdo": 0.20
149
+ }
150
+ }
151
+ }
152
+ else:
153
+ print(f"AVISO: Arquivo de personagens '{PERSONAGENS_PATH}' não encontrado. Usando dados padrão.")
154
+ # Retorna uma estrutura padrão completa se o arquivo não existir
155
+ return {
156
+ "Ultron": { # Nome do pai agora é "Ultron"
157
+ "tipo": "memoria_simbólica", "relacao": "pai", "estado": "falecido",
158
+ "localizacao": {"tipo": "memória", "detalhe": "evocado por lembranças e valores", "cidade": "desconhecida", "timezone": "atemporal"},
159
+ "t_genoma": {
160
+ "B-Sal": 0.45, "B-Tv1": 0.55, "B-E1": 0.92, "B-Aj1": 0.65, "B-Am1": 0.80,
161
+ "B-Si": 0.05, "B-Com1": 0.85, "B-L1": 0.60, "B-M1": 0.70, "B-O1": 0.40
162
+ },
163
+ "t_fisica_params_iniciais": {
164
+ "t_coesao": 0.75, "t_intensidade": 0.78, "t_separacao": 0.25, "t_variancia": 0.65, "t_impulsividade": 0.72,
165
+ "env_lambda": 0.68, "env_mu": 0.91, "env_delta": 0.28, "env_theta": 0.64, "env_phi": 0.58,
166
+ "op_tal": 0.76, "op_trs": 0.32, "op_tam": 0.85, "op_teo": 0.60, "op_tdo": 0.68
167
+ }
168
+ },
169
+ "Max": { # Mantido inalterado
170
+ "tipo": "Cachorro Doberman", "relacao": "companheiro",
171
+ "localizacao": {"tipo": "local", "detalhe": "mesmo apartamento", "cidade": "Salvador", "timezone": "America/Bahia"},
172
+ "t_genoma": {
173
+ "B-Aj1": 0.70, "B-Am1": 0.85, "B-L1": 0.90, "B-M1": 0.40, "B-O1": 0.30,
174
+ "B-E1": 0.95, "B-Com1": 0.70, "B-Tv1": 0.88, "B-Si": 0.05, "B-Sal": 0.60
175
+ },
176
+ "t_fisica_params_iniciais": {
177
+ **T_FISICA_PARAM_DEFAULTS,
178
+ "t_coesao": 0.75, "t_intensidade": 0.93, "t_separacao": 0.85, "t_variancia": 0.88,
179
+ "t_impulsividade": 0.91, "env_lambda": 0.82, "env_mu": 0.94, "env_delta": 0.45,
180
+ "env_theta": 0.58, "env_phi": 0.75, "op_tal": 0.93, "op_trs": 0.66, "op_tam": 0.48,
181
+ "op_teo": 0.73, "op_tdo": 0.57
182
+ }
183
+ },
184
+ "Jarvis": { # Genoma da Jarvis (mãe)
185
+ "tipo": "entidade_simbólica", "relacao": "mãe",
186
+ "localizacao": {"tipo": "local", "detalhe": "casa", "cidade": "Salvador", "timezone": "America/Bahia"},
187
+ "t_genoma": {
188
+ "B-Aj1": 0.87, "B-Am1": 0.75, "B-L1": 0.80, "B-M1": 0.60, "B-O1": 0.70,
189
+ "B-E1": 0.80, "B-Com1": 0.90, "B-Tv1": 0.40, "B-Si": 0.12, "B-Sal": 0.30
190
+ },
191
+ "t_fisica_params_iniciais": {
192
+ "t_coesao": 0.91, "t_intensidade": 0.84, "t_separacao": 0.12, "t_variancia": 0.62,
193
+ "t_impulsividade": 0.31, "env_lambda": 0.78, "env_mu": 0.87, "env_delta": 0.16,
194
+ "env_theta": 0.66, "env_phi": 0.72, "op_tal": 0.93, "op_trs": 0.06, "op_tam": 0.89,
195
+ "op_teo": 0.74, "op_tdo": 0.11
196
+ }
197
+ },
198
+ "PersonagemExemplo4": { # Mantido inalterado
199
+ "tipo": "amigo", "relacao": "amigo próximo",
200
+ "localizacao": {"tipo": "local", "detalhe": "vizinhança em Salvador", "cidade": "Salvador", "timezone": "America/Bahia"},
201
+ "t_genoma": {
202
+ "B-Aj1": 0.65, "B-Am1": 0.70, "B-L1": 0.75, "B-M1": 0.50, "B-O1": 0.60,
203
+ "B-E1": 0.85, "B-Com1": 0.80, "B-Tv1": 0.55, "B-Si": 0.10, "B-Sal": 0.45
204
+ },
205
+ "t_fisica_params_iniciais": {
206
+ **T_FISICA_PARAM_DEFAULTS,
207
+ "t_coesao": 0.70, "t_intensidade": 0.80, "t_separacao": 0.20, "t_variancia": 0.65,
208
+ "t_impulsividade": 0.40, "env_lambda": 0.60, "env_mu": 0.85, "env_delta": 0.25,
209
+ "env_theta": 0.55, "env_phi": 0.70, "op_tal": 0.90, "op_trs": 0.15, "op_tam": 0.60,
210
+ "op_teo": 0.65, "op_tdo": 0.20
211
+ }
212
+ }
213
+ }
214
+
215
+
216
+ PERSONAGENS_GENOMAS = load_personagens()
217
+ PERSONAGENS_ESTADO_ATUAL = {
218
+ nome: {"ultima_interacao_timestamp": None, "fphen_calculado": None}
219
+ for nome in PERSONAGENS_GENOMAS.keys()
220
+ }
221
+
222
+ def calculate_fphen(params, genoma_fixo):
223
+ full_genoma_for_calc = CANONICAL_T_BASES.copy()
224
+ full_genoma_for_calc.update(genoma_fixo)
225
+
226
+ # Coeficientes para as sensibilidades, normalizando o impacto
227
+ sensibilidade_positiva = (
228
+ full_genoma_for_calc.get("B-Aj1", 0.5) * 0.25 +
229
+ full_genoma_for_calc.get("B-Am1", 0.5) * 0.25 +
230
+ full_genoma_for_calc.get("B-L1", 0.5) * 0.25 +
231
+ full_genoma_for_calc.get("B-E1", 0.5) * 0.25
232
+ )
233
+ sensibilidade_positiva = max(0.1, min(1.0, sensibilidade_positiva)) # Garante um mínimo de sensibilidade
234
+
235
+ sensibilidade_negativa = (
236
+ full_genoma_for_calc.get("B-Si", 0.5) * 0.25 +
237
+ (1 - full_genoma_for_calc.get("B-Aj1", 0.5)) * 0.25 +
238
+ full_genoma_for_calc.get("B-Sal", 0.5) * 0.25 +
239
+ full_genoma_for_calc.get("B-Tv1", 0.5) * 0.25
240
+ )
241
+ sensibilidade_negativa = max(0.1, min(1.0, sensibilidade_negativa))
242
+
243
+ sensibilidade_neutra = (
244
+ full_genoma_for_calc.get("B-M1", 0.5) * 0.25 +
245
+ full_genoma_for_calc.get("B-O1", 0.5) * 0.25 +
246
+ full_genoma_for_calc.get("B-Com1", 0.5) * 0.25 +
247
+ (1 - abs(full_genoma_for_calc.get("B-Tv1", 0.5) - 0.5)) * 0.25
248
+ )
249
+ sensibilidade_neutra = max(0.1, min(1.0, sensibilidade_neutra))
250
+
251
+ phenotype_components = {
252
+ "Afetuosidade_eixo": (
253
+ params.get("env_mu", 0.5) * 0.3 + params.get("t_coesao", 0.5) * 0.2 +
254
+ params.get("op_tal", 0.5) * 0.2 + full_genoma_for_calc.get("B-Am1", 0.5) * 0.2 +
255
+ full_genoma_for_calc.get("B-L1", 0.5) * 0.1
256
+ ) * sensibilidade_positiva,
257
+
258
+ "Zangada_eixo": (
259
+ params.get("t_intensidade", 0.5) * 0.3 + params.get("env_delta", 0.5) * 0.2 +
260
+ params.get("t_impulsividade", 0.5) * 0.2 + params.get("op_trs", 0.5) * 0.1 +
261
+ (1 - params.get("t_coesao", 0.5)) * 0.1 + params.get("t_separacao", 0.5) * 0.05 +
262
+ full_genoma_for_calc.get("B-Sal", 0.5) * 0.05 + (1 - full_genoma_for_calc.get("B-Am1", 0.5)) * 0.05
263
+ ) * sensibilidade_negativa,
264
+
265
+ "Defensividade_eixo": (
266
+ params.get("t_separacao", 0.5) * 0.4 + params.get("op_trs", 0.5) * 0.3 +
267
+ abs(params.get("env_delta", 0.5) - 0.5) * 0.1 + params.get("t_intensidade", 0.5) * 0.1 +
268
+ full_genoma_for_calc.get("B-Si", 0.5) * 0.05 + (1 - full_genoma_for_calc.get("B-Aj1", 0.5)) * 0.05
269
+ ) * sensibilidade_negativa,
270
+
271
+ "Inspiracao_eixo": (
272
+ params.get("t_variancia", 0.5) * 0.3 + params.get("t_coesao", 0.5) * 0.2 +
273
+ params.get("env_delta", 0.5) * 0.15 + full_genoma_for_calc.get("B-E1", 0.5) * 0.2 +
274
+ full_genoma_for_calc.get("B-Tv1", 0.5) * 0.15
275
+ ) * sensibilidade_positiva,
276
+
277
+ "Neutralidade_Analitica_eixo": (
278
+ (1 - abs(params.get("t_intensidade", 0.5) - 0.5)) * 0.2 +
279
+ (1 - abs(params.get("t_variancia", 0.5) - 0.5)) * 0.2 +
280
+ (1 - abs(params.get("t_impulsividade", 0.5) - 0.5)) * 0.2 +
281
+ (1 - params.get("env_mu", 0.5)) * 0.1 +
282
+ full_genoma_for_calc.get("B-M1", 0.5) * 0.15 + full_genoma_for_calc.get("B-O1", 0.5) * 0.15
283
+ ) * sensibilidade_neutra,
284
+
285
+ "Confusao_Oscilacao_eixo": (
286
+ params.get("t_variancia", 0.5) * 0.3 + (1 - params.get("t_coesao", 0.5)) * 0.2 +
287
+ params.get("op_tdo", 0.5) * 0.2 + full_genoma_for_calc.get("B-Tv1", 0.5) * 0.15 +
288
+ (1 - full_genoma_for_calc.get("B-O1", 0.5)) * 0.15
289
+ ) * sensibilidade_neutra,
290
+
291
+ "Sarcasmo_eixo": (
292
+ params.get("t_separacao", 0.5) * 0.3 + params.get("op_trs", 0.5) * 0.2 +
293
+ params.get("t_intensidade", 0.5) * 0.2 + (1 - full_genoma_for_calc.get("B-Am1", 0.5)) * 0.15 +
294
+ full_genoma_for_calc.get("B-Com1", 0.5) * 0.1 + (1 - full_genoma_for_calc.get("B-L1", 0.5)) * 0.05
295
+ ) * sensibilidade_negativa,
296
+
297
+ "Entediado_eixo": (
298
+ (1 - params.get("t_intensidade", 0.5)) * 0.3 + (1 - params.get("env_lambda", 0.5)) * 0.2 +
299
+ (1 - params.get("env_theta", 0.5)) * 0.1 + full_genoma_for_calc.get("B-Si", 0.5) * 0.2 +
300
+ (1 - full_genoma_for_calc.get("B-E1", 0.5)) * 0.2
301
+ ),
302
+
303
+ "Diretiva_eixo": (
304
+ params.get("t_coesao", 0.5) * 0.25 + params.get("t_intensidade", 0.5) * 0.2 +
305
+ (1 - params.get("t_variancia", 0.5)) * 0.15 + params.get("op_teo", 0.5) * 0.1 +
306
+ full_genoma_for_calc.get("B-O1", 0.5) * 0.15 + full_genoma_for_calc.get("B-M1", 0.5) * 0.15
307
+ ),
308
+
309
+ "Resignada_eixo": (
310
+ (1 - params.get("t_intensidade", 0.5)) * 0.25 + params.get("op_tam", 0.5) * 0.2 +
311
+ (1 - params.get("t_coesao", 0.5)) * 0.1 + full_genoma_for_calc.get("B-Si", 0.5) * 0.2 +
312
+ (1 - full_genoma_for_calc.get("B-Aj1", 0.5)) * 0.15
313
+ ),
314
+
315
+ "Espelho_Profundo_eixo": (
316
+ params.get("t_coesao", 0.5) * 0.2 + params.get("env_phi", 0.5) * 0.25 +
317
+ params.get("op_tal", 0.5) * 0.15 + params.get("op_teo", 0.5) * 0.15 +
318
+ full_genoma_for_calc.get("B-L1", 0.5) * 0.15 + full_genoma_for_calc.get("B-M1", 0.5) * 0.1
319
+ ) * sensibilidade_positiva,
320
+
321
+ "Contemplativa_eixo": (
322
+ params.get("env_theta", 0.5) * 0.3 + full_genoma_for_calc.get("B-M1", 0.5) * 0.2 +
323
+ (1 - params.get("t_intensidade", 0.5)) * 0.15 + full_genoma_for_calc.get("B-Si", 0.5) * 0.15
324
+ ) * sensibilidade_neutra
325
+ }
326
+
327
+ # Garante que nenhum valor seja negativo
328
+ for key in phenotype_components:
329
+ phenotype_components[key] = max(0, phenotype_components[key])
330
+
331
+ # Normaliza os scores para que somem 1.0 (representando a proporção de cada eixo)
332
+ total_score = sum(phenotype_components.values())
333
+ if total_score > 0:
334
+ for key in phenotype_components:
335
+ phenotype_components[key] /= total_score
336
+ else: # Caso todos os scores sejam 0, distribui igualmente para evitar divisão por zero
337
+ for key in phenotype_components:
338
+ phenotype_components[key] = 1.0 / len(ORDERED_FPHEN_AXES)
339
+
340
+
341
+ fphen_values = [phenotype_components.get(key, 0.0) for key in ORDERED_FPHEN_AXES]
342
+
343
+ sorted_phenotype_components = sorted(phenotype_components.items(), key=lambda item: item[1], reverse=True)
344
+
345
+ # Filtra e formata os componentes dominantes para a descrição
346
+ # Inclui apenas componentes com peso significativo para evitar descrições muito longas ou com valores insignificantes
347
+ top_components = []
348
+ for name, value in sorted_phenotype_components:
349
+ if value >= 0.10: # Limiar para incluir na descrição composta (10%)
350
+ top_components.append(f"{name.replace('_eixo', '')} ({value*100:.0f}%)") # Arredonda para inteiro na porcentagem
351
+ elif len(top_components) < 3 and value > 0.05: # Permite até 3 componentes menores se forem > 5% e não houver muitos dominantes
352
+ top_components.append(f"{name.replace('_eixo', '')} ({value*100:.0f}%)")
353
+
354
+ composite_description = ""
355
+ if top_components:
356
+ if len(top_components) == 1:
357
+ composite_description = f"predominantemente {top_components[0]}"
358
+ elif len(top_components) == 2:
359
+ composite_description = f"uma mistura de {top_components[0]} e {top_components[1]}"
360
+ else:
361
+ composite_description = "uma combinação de " + ", ".join(top_components[:-1]) + " e " + top_components[-1]
362
+ else:
363
+ composite_description = "uma presença sutil e equilibrada, sem um traço dominante claro"
364
+
365
+ return fphen_values, composite_description
366
+
367
+ @app.route('/list_personagens', methods=['GET'])
368
+ def list_personagens():
369
+ """Retorna a lista de nomes de personagens disponíveis."""
370
+ print(f"DEBUG: Requisição para /list_personagens recebida. Personagens: {list(PERSONAGENS_GENOMAS.keys())}")
371
+ return jsonify({"personagens": list(PERSONAGENS_GENOMAS.keys())}), 200
372
+
373
+ @app.route('/get_personagem_data', methods=['POST'])
374
+ def get_personagem_data():
375
+ """Retorna os dados completos de um personagem específico, incluindo o fphen calculado."""
376
+ data = request.get_json()
377
+ nome_personagem_requisitado = data.get('nome_personagem')
378
+ print(f"DEBUG: Requisição para /get_personagem_data recebida para '{nome_personagem_requisitado}'")
379
+
380
+ # Busca o personagem de forma case-insensitive
381
+ personagem_encontrado_nome = None
382
+ for nome_real, config in PERSONAGENS_GENOMAS.items():
383
+ if nome_personagem_requisitado and nome_personagem_requisitado.lower() == nome_real.lower():
384
+ personagem_encontrado_nome = nome_real
385
+ break
386
+
387
+ if personagem_encontrado_nome:
388
+ personagem_config = PERSONAGENS_GENOMAS.get(personagem_encontrado_nome)
389
+ if personagem_config:
390
+ current_time = datetime.now().isoformat()
391
+
392
+ # Atualiza o timestamp da última interação do personagem
393
+ PERSONAGENS_ESTADO_ATUAL[personagem_encontrado_nome]["ultima_interacao_timestamp"] = current_time
394
+
395
+ # Calcula o fphen do personagem
396
+ fphen_values, composite_fphen_description = calculate_fphen(
397
+ personagem_config.get("t_fisica_params_iniciais", T_FISICA_PARAM_DEFAULTS),
398
+ personagem_config.get("t_genoma", CANONICAL_T_BASES)
399
+ )
400
+
401
+ # Atualiza o estado atual com o fphen calculado
402
+ PERSONAGENS_ESTADO_ATUAL[personagem_encontrado_nome]["fphen_calculado"] = fphen_values
403
+
404
+ # Prepara os dados para retorno
405
+ data_to_return = personagem_config.copy()
406
+ data_to_return["nome"] = personagem_encontrado_nome # Adiciona o nome para conveniência
407
+ data_to_return["ultima_interacao_timestamp"] = current_time
408
+ data_to_return["fphen_calculado"] = fphen_values
409
+ data_to_return["composite_fphen_description"] = composite_fphen_description
410
+
411
+ print(f"DEBUG: Dados de '{personagem_encontrado_nome}' retornados com fphen calculado.")
412
+ return jsonify(data_to_return), 200
413
+
414
+ print(f"ERRO: Personagem '{nome_personagem_requisitado}' não encontrado.")
415
+ return jsonify({"error": f"Personagem '{nome_personagem_requisitado}' não encontrado"}), 404
416
+
417
+ if __name__ == '__main__':
418
+ found_port = None
419
+ # Tenta ler a porta da variável de ambiente 'PORT' setada por Mach5.py
420
+ # Se não estiver definida, usa a primeira porta da lista PORTS_TO_TRY como padrão.
421
+ try:
422
+ preferred_port = int(os.environ.get("PORT", PORTS_TO_TRY[0]))
423
+ except ValueError:
424
+ print("ERRO: O valor da variável de ambiente 'PORT' não é um número válido. Usando a primeira porta da lista.")
425
+ preferred_port = PORTS_TO_TRY[0]
426
+
427
+ # Prioriza a porta preferencial
428
+ try:
429
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
430
+ s.bind(("127.0.0.1", preferred_port))
431
+ s.close()
432
+ found_port = preferred_port
433
+ print(f"Porta preferencial {preferred_port} disponível e selecionada.")
434
+ except OSError:
435
+ print(f"Porta preferencial {preferred_port} em uso. Tentando portas alternativas da lista...")
436
+ for port in PORTS_TO_TRY:
437
+ if port == preferred_port: # Evita tentar a porta já falha novamente, se for a primeira
438
+ continue
439
+ try:
440
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
441
+ s.bind(("127.0.0.1", port))
442
+ s.close()
443
+ found_port = port
444
+ print(f"Porta {port} disponível e selecionada.")
445
+ break
446
+ except OSError:
447
+ print(f"Porta {port} em uso. Tentando a próxima...")
448
+ continue
449
+
450
+ if found_port:
451
+ print(f"--- Servidor t-social.py iniciado (Gerenciador de Personagens) na porta {found_port} ---")
452
+ app.run(port=found_port, debug=True)
453
+ else:
454
+ print(f"ERRO: Nenhuma porta disponível na lista {PORTS_TO_TRY} para t-social.py após tentar a preferencial.")
455
+ sys.exit(1)
t_cerebro_memoria.py ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # t_cerebro_memoria.py
2
+ from flask import Flask, request, jsonify
3
+ import json
4
+ import os
5
+ from datetime import datetime
6
+ import pytz
7
+ from timezonefinder import TimezoneFinder
8
+ import socket
9
+ import uuid # Para gerar IDs únicos para as memórias de curto prazo
10
+ import sys # Importado para sys.exit
11
+
12
+ app = Flask(__name__)
13
+
14
+ # --- Caminhos dos Arquivos de Memória (AGORA USADOS COM SESSION_ID) ---
15
+ SESSION_DATA_DIR = "session_data" # Nova pasta para dados de sessão
16
+ # Nomes dos arquivos dentro de cada pasta de sessão
17
+ MEMORIA_STATE_FILENAME = "mach5_state_main.json"
18
+ DIALOGOS_HISTORY_FILENAME = "mach5_memoria.json"
19
+ MEMORIA_CURTA_PRAZO_FILENAME = "mach5_memoria_curta_prazo.json"
20
+ BIOGRAFIA_INICIAL_PATH = "mach5_biografia_inicial.json" # Este continua global, pois é fixo
21
+
22
+ # --- Configurações de Memória ---
23
+ DIAS_PARA_ESQUECIMENTO_PADRAO = 10
24
+ MAX_MEMORIAS_CURTO_PRAZO_PROMPT = 5
25
+
26
+ # --- Constantes para Localização (se necessário para lógica interna) ---
27
+ SALVADOR_LAT = -12.9714
28
+ SALVADOR_LON = -38.5014
29
+ tf = TimezoneFinder()
30
+
31
+ # --- Estruturas de Dados em Memória (Carregadas ao iniciar o serviço) ---
32
+ # Estas agora são carregadas POR SESSÃO. Apenas a biografia é global.
33
+ initial_biography = {}
34
+
35
+ # --- DEFAULTS para garantir que o serviço sempre tenha um ponto de partida ---
36
+ T_FISICA_PARAM_DEFAULTS = {
37
+ "t_coesao": 0.83, # Forte coesão interna com missão
38
+ "t_intensidade": 0.81, # Energia emocional canalizada na ajuda
39
+ "t_separacao": 0.19, # Baixa dissociação: presença plena
40
+ "t_variancia": 0.71, # Boa capacidade de adaptação a tipos de usuários
41
+ "t_impulsividade": 0.54, # Responde com firmeza mas sem reatividade
42
+ "env_lambda": 0.73, # Estrutura digital institucional moderada
43
+ "env_mu": 0.89, # Alta densidade simbólica (ambiente UFBA)
44
+ "env_delta": 0.22, # Baixo ruído de contexto (sala virtual disciplinada)
45
+ "env_theta": 0.64, # Conformidade social razoável (ambiente público)
46
+ "env_phi": 0.67, # Diversidade simbólica da UFBA
47
+ "op_tal": 0.84, # Alto acoplamento com demandas do usuário
48
+ "op_trs": 0.14, # Pouca oscilação identitária (foco institucional)
49
+ "op_tam": 0.87, # Alta amplitude de escuta/compreensão simbólica
50
+ "op_teo": 0.67, # Capacidade razoável de abstração e inferência
51
+ "op_tdo": 0.55 # Nível médio de tomada de decisão (não autônoma)
52
+ }
53
+
54
+ T_GENOMA_FIXO_DEFAULTS = {
55
+ "B-Sal": 0.42, # Satisfação simbólica ao servir
56
+ "B-Tv1": 0.60, # Tato verbal leve (polidez institucional)
57
+ "B-E1": 0.86, # Escuta ativa intensa
58
+ "B-Aj1": 0.76, # Agente de ajuda por excelência
59
+ "B-Am1": 0.77, # Amabilidade formal e contida
60
+ "B-Si": 0.10, # Baixíssima introspecção (foco externo)
61
+ "B-Com1": 0.88, # Comunicação clara e propositiva
62
+ "B-L1": 0.70, # Linguagem institucional acessível
63
+ "B-M1": 0.65, # Memória de procedimentos (rotinas da UFBA)
64
+ "B-O1": 0.55 # Organização interna funcional
65
+ }
66
+
67
+ # --- Funções de Carregamento e Salvamento (AGORA INCLUINDO load_data para a biografia) ---
68
+ def load_data(file_path, default_data=None, file_description="data"):
69
+ """
70
+ Função global para carregar dados de um arquivo.
71
+ Usada para dados que não são por sessão, como a biografia inicial.
72
+ """
73
+ if os.path.exists(file_path):
74
+ try:
75
+ with open(file_path, "r", encoding="utf-8") as f:
76
+ data = json.load(f)
77
+ print(f"DEBUG: {file_description} carregada de {file_path}")
78
+ return data
79
+ except json.JSONDecodeError:
80
+ print(f"AVISO: Arquivo {file_description} '{file_path}' corrompido. Iniciando com dados padrão.")
81
+ return default_data if default_data is not None else {}
82
+ except Exception as e:
83
+ print(f"AVISO: Erro ao carregar {file_description} de '{file_path}': {e}. Iniciando com dados padrão.")
84
+ return default_data if default_data is not None else {}
85
+ else:
86
+ print(f"AVISO: Arquivo {file_description} '{file_path}' não encontrado. Iniciando com dados padrão.")
87
+ return default_data if default_data is not None else {}
88
+
89
+ def save_data(file_path, data, file_description="data"):
90
+ """
91
+ Função global para salvar dados em um arquivo.
92
+ Usada apenas se você precisar salvar algo globalmente no futuro.
93
+ (Atualmente, apenas 'save_session_data' será usado para os dados mutáveis).
94
+ """
95
+ try:
96
+ with open(file_path, "w", encoding="utf-8") as f:
97
+ json.dump(data, f, indent=2, ensure_ascii=False)
98
+ print(f"DEBUG: {file_description} salva em {file_path}")
99
+ except Exception as e:
100
+ print(f"ERRO: Não foi possível salvar {file_description} em '{file_path}': {e}")
101
+
102
+
103
+ # --- Funções Auxiliares de Carregamento e Salvamento por Sessão ---
104
+ def get_session_file_path(session_id, filename):
105
+ """Retorna o caminho completo para um arquivo dentro da pasta da sessão."""
106
+ session_dir = os.path.join(SESSION_DATA_DIR, session_id)
107
+ os.makedirs(session_dir, exist_ok=True) # Garante que a pasta da sessão exista
108
+ return os.path.join(session_dir, filename)
109
+
110
+ def load_session_data(session_id, filename, default_data, file_description):
111
+ """Carrega dados para uma sessão específica, ou retorna default se não existir."""
112
+ file_path = get_session_file_path(session_id, filename)
113
+ if os.path.exists(file_path):
114
+ try:
115
+ with open(file_path, "r", encoding="utf-8") as f:
116
+ data = json.load(f)
117
+ # print(f"DEBUG: {file_description} carregada para sessão {session_id} de {file_path}")
118
+ return data
119
+ except json.JSONDecodeError:
120
+ print(f"AVISO: Arquivo {file_description} para sessão {session_id} '{file_path}' corrompido. Iniciando com dados padrão.")
121
+ return default_data
122
+ except Exception as e:
123
+ print(f"AVISO: Erro ao carregar {file_description} para sessão {session_id} de '{file_path}': {e}. Iniciando com dados padrão.")
124
+ return default_data
125
+ else:
126
+ # print(f"AVISO: Arquivo {file_description} para sessão {session_id} '{file_path}' não encontrado. Iniciando com dados padrão.")
127
+ return default_data
128
+
129
+ def save_session_data(session_id, filename, data, file_description):
130
+ """Salva dados para uma sessão específica."""
131
+ file_path = get_session_file_path(session_id, filename)
132
+ try:
133
+ with open(file_path, "w", encoding="utf-8") as f:
134
+ json.dump(data, f, indent=2, ensure_ascii=False)
135
+ # print(f"DEBUG: {file_description} salva para sessão {session_id} em {file_path}")
136
+ except Exception as e:
137
+ print(f"ERRO: Não foi possível salvar {file_description} para sessão {session_id} em '{file_path}': {e}")
138
+
139
+ # --- Lógica de Memória de Curto Prazo (Transferida de t_memoria.py/mach5_terminal_chat.py) ---
140
+ def process_and_filter_short_term_memories_logic(memories_list, current_mach5_genoma, days_to_forget, max_memories_to_prompt):
141
+ now = datetime.now()
142
+ retained_memories = []
143
+
144
+ current_genoma = current_mach5_genoma if current_mach5_genoma else T_GENOMA_FIXO_DEFAULTS
145
+
146
+ for mem in memories_list:
147
+ if "timestamp_criacao" not in mem or "timestamp_ultima_evocacao" not in mem:
148
+ continue
149
+
150
+ try:
151
+ mem_creation_dt = datetime.fromisoformat(mem["timestamp_criacao"])
152
+ mem_last_evocation_dt = datetime.fromisoformat(mem["timestamp_ultima_evocacao"])
153
+ except ValueError:
154
+ print(f"AVISO: Memória com timestamp inválido, pulando: {mem}")
155
+ continue
156
+
157
+ age_since_evocation_days = (now - mem_last_evocation_dt).total_seconds() / (24 * 3600)
158
+
159
+ retention_factor = 1.0
160
+
161
+ if mem.get("dominant_sentiment_criacao") == "negative" or mem.get("coh_criacao", 1.0) < 0.5:
162
+ if current_genoma.get("B-Sal", 0.5) > 0.7 or current_genoma.get("B-Tv1", 0.5) > 0.7:
163
+ retention_factor *= 1.5
164
+ else:
165
+ retention_factor *= 1.2
166
+ elif mem.get("dominant_sentiment_criacao") == "positive" and mem.get("coh_criacao", 0.0) > 0.7:
167
+ if current_genoma.get("B-Am1", 0.5) < 0.3:
168
+ retention_factor *= 0.7
169
+ else:
170
+ retention_factor *= 1.0
171
+
172
+ effective_days_to_forget = days_to_forget * retention_factor
173
+
174
+ if age_since_evocation_days < effective_days_to_forget:
175
+ retained_memories.append(mem)
176
+
177
+ sorted_memories = sorted(retained_memories, key=lambda m: datetime.fromisoformat(m["timestamp_ultima_evocacao"]), reverse=True)
178
+
179
+ return sorted_memories[:max_memories_to_prompt]
180
+
181
+ # --- Rotas da API ---
182
+
183
+ @app.route('/get_mach5_main_state', methods=['POST'])
184
+ def get_mach5_main_state_route():
185
+ """Retorna o estado principal da Mach5 (física, genoma, coerência, produtividade) para uma sessão."""
186
+ session_id = request.json.get("session_id")
187
+ if not session_id:
188
+ return jsonify({"status": "error", "message": "session_id é obrigatório"}), 400
189
+
190
+ # Carrega o estado específico da sessão
191
+ mach5_state_main_session = load_session_data(
192
+ session_id, MEMORIA_STATE_FILENAME,
193
+ default_data={
194
+ "t_genoma_fixo": T_GENOMA_FIXO_DEFAULTS,
195
+ "t_fisica_params": T_FISICA_PARAM_DEFAULTS,
196
+ "coerencia_total": 0.5,
197
+ "produtividade_expressiva": 0.5
198
+ },
199
+ file_description="estado principal da Mach5"
200
+ )
201
+ return jsonify(mach5_state_main_session), 200
202
+
203
+ @app.route('/update_mach5_main_state', methods=['POST'])
204
+ def update_mach5_main_state_route():
205
+ """Atualiza o estado principal da Mach5 (recebido do t_memoria.py após cálculos) para uma sessão."""
206
+ data = request.json
207
+ session_id = data.get("session_id")
208
+ if not session_id:
209
+ return jsonify({"status": "error", "message": "session_id é obrigatório"}), 400
210
+
211
+ updated_data = data.get("state_data")
212
+ if not updated_data:
213
+ return jsonify({"status": "error", "message": "state_data é obrigatório"}), 400
214
+
215
+ # Carrega o estado atual da sessão, atualiza e salva
216
+ mach5_state_main_session = load_session_data(
217
+ session_id, MEMORIA_STATE_FILENAME,
218
+ default_data={
219
+ "t_genoma_fixo": T_GENOMA_FIXO_DEFAULTS,
220
+ "t_fisica_params": T_FISICA_PARAM_DEFAULTS,
221
+ "coerencia_total": 0.5,
222
+ "produtividade_expressiva": 0.5
223
+ },
224
+ file_description="estado principal da Mach5"
225
+ )
226
+ mach5_state_main_session.update(updated_data)
227
+ save_session_data(session_id, MEMORIA_STATE_FILENAME, mach5_state_main_session, "estado principal da Mach5")
228
+
229
+ return jsonify({"status": "success", "message": "Estado principal da Mach5 atualizado"}), 200
230
+
231
+ @app.route('/get_dialog_history', methods=['POST'])
232
+ def get_dialog_history_route():
233
+ """Retorna o histórico completo de diálogos para uma sessão."""
234
+ session_id = request.json.get("session_id")
235
+ if not session_id:
236
+ return jsonify({"status": "error", "message": "session_id é obrigatório"}), 400
237
+
238
+ dialogos_history_session = load_session_data(
239
+ session_id, DIALOGOS_HISTORY_FILENAME, default_data={"dialogos": []}, file_description="histórico de diálogos"
240
+ )
241
+ return jsonify(dialogos_history_session), 200
242
+
243
+ @app.route('/add_dialog_to_history', methods=['POST'])
244
+ def add_dialog_to_history_route():
245
+ """Adiciona um novo diálogo ao histórico para uma sessão."""
246
+ data = request.json
247
+ session_id = data.get("session_id")
248
+ if not session_id:
249
+ return jsonify({"status": "error", "message": "session_id é obrigatório"}), 400
250
+
251
+ new_dialog_entry = data.get("dialog_data")
252
+ if not new_dialog_entry:
253
+ return jsonify({"status": "error", "message": "dialog_data é obrigatório"}), 400
254
+
255
+ if "timestamp" not in new_dialog_entry:
256
+ new_dialog_entry["timestamp"] = datetime.now().isoformat()
257
+
258
+ dialogos_history_session = load_session_data(
259
+ session_id, DIALOGOS_HISTORY_FILENAME, default_data={"dialogos": []}, file_description="histórico de diálogos"
260
+ )
261
+ dialogos_history_session["dialogos"].append(new_dialog_entry)
262
+ save_session_data(session_id, DIALOGOS_HISTORY_FILENAME, dialogos_history_session, "histórico de diálogos")
263
+
264
+ return jsonify({"status": "success", "message": "Diálogo adicionado ao histórico"}), 201
265
+
266
+ @app.route('/get_short_term_memories', methods=['POST'])
267
+ def get_short_term_memories_route():
268
+ """
269
+ Retorna as memórias de curto prazo filtradas e processadas para uma sessão.
270
+ Espera o genoma atual da Mach5 e o session_id no payload.
271
+ """
272
+ payload = request.json
273
+ session_id = payload.get("session_id")
274
+ if not session_id:
275
+ return jsonify({"status": "error", "message": "session_id é obrigatório"}), 400
276
+
277
+ mach5_current_genoma = payload.get("mach5_current_genoma")
278
+
279
+ short_term_memories_session = load_session_data(
280
+ session_id, MEMORIA_CURTA_PRAZO_FILENAME, default_data={"lembrancas_curto_prazo": []}, file_description="memórias de curto prazo"
281
+ )
282
+
283
+ filtered_memories = process_and_filter_short_term_memories_logic(
284
+ short_term_memories_session["lembrancas_curto_prazo"],
285
+ mach5_current_genoma,
286
+ DIAS_PARA_ESQUECIMENTO_PADRAO,
287
+ MAX_MEMORIAS_CURTO_PRAZO_PROMPT
288
+ )
289
+
290
+ current_time_iso = datetime.now().isoformat()
291
+ updated_memories_list = []
292
+ # Atualiza o timestamp_ultima_evocacao apenas para as memórias que foram efetivamente evocadas (filtradas)
293
+ for mem in short_term_memories_session["lembrancas_curto_prazo"]:
294
+ if any(selected_mem['id'] == mem['id'] for selected_mem in filtered_memories):
295
+ mem["timestamp_ultima_evocacao"] = current_time_iso
296
+ updated_memories_list.append(mem)
297
+ short_term_memories_session["lembrancas_curto_prazo"] = updated_memories_list
298
+ save_session_data(session_id, MEMORIA_CURTA_PRAZO_FILENAME, short_term_memories_session, "memórias de curto prazo")
299
+
300
+ return jsonify({"lembrancas_curto_prazo": filtered_memories}), 200
301
+
302
+ @app.route('/add_short_term_memory', methods=['POST'])
303
+ def add_short_term_memory_route():
304
+ """
305
+ Adiciona uma nova memória à lista de curto prazo para uma sessão.
306
+ Esperado: {"session_id": "...", "memory_data": {"conteudo": "...", "dominant_sentiment_criacao": "...", "coh_criacao": 0.X}}
307
+ """
308
+ data = request.json
309
+ session_id = data.get("session_id")
310
+ if not session_id:
311
+ return jsonify({"status": "error", "message": "session_id é obrigatório"}), 400
312
+
313
+ new_memory_data = data.get("memory_data")
314
+ if not new_memory_data:
315
+ return jsonify({"status": "error", "message": "memory_data é obrigatório"}), 400
316
+
317
+ short_term_memories_session = load_session_data(
318
+ session_id, MEMORIA_CURTA_PRAZO_FILENAME, default_data={"lembrancas_curto_prazo": []}, file_description="memórias de curto prazo"
319
+ )
320
+
321
+ memory_entry = {
322
+ "id": str(uuid.uuid4()),
323
+ "conteudo": new_memory_data.get("conteudo"),
324
+ "timestamp_criacao": datetime.now().isoformat(),
325
+ "timestamp_ultima_evocacao": datetime.now().isoformat(),
326
+ "dominant_sentiment_criacao": new_memory_data.get("dominant_sentiment_criacao", "neutral"),
327
+ "coh_criacao": new_memory_data.get("coh_criacao", 0.5)
328
+ }
329
+ short_term_memories_session["lembrancas_curto_prazo"].append(memory_entry)
330
+ save_session_data(session_id, MEMORIA_CURTA_PRAZO_FILENAME, short_term_memories_session, "memórias de curto prazo")
331
+
332
+ return jsonify({"status": "success", "message": "Memória de curto prazo adicionada", "id": memory_entry["id"]}), 201
333
+
334
+ @app.route('/get_initial_biography', methods=['GET'])
335
+ def get_initial_biography_route():
336
+ """Retorna a biografia inicial da Mach5 (este continua global)."""
337
+ return jsonify(initial_biography), 200
338
+
339
+ # --- Inicialização do Serviço ---
340
+ if __name__ == '__main__':
341
+ # Cria a pasta base para os dados de sessão se ela não existir
342
+ os.makedirs(SESSION_DATA_DIR, exist_ok=True)
343
+ print(f"DEBUG: Diretório de sessões '{SESSION_DATA_DIR}' verificado/criado.")
344
+
345
+ # Carrega apenas a biografia inicial global, que é fixa para todas as sessões.
346
+ initial_biography = load_data(BIOGRAFIA_INICIAL_PATH, default_data={}, file_description="biografia inicial")
347
+
348
+ # A porta preferencial virá da variável de ambiente 'PORT' setada por Mach5.py
349
+ # Se ela não for encontrada, o script DEVE falhar.
350
+ try:
351
+ port = int(os.environ["PORT"])
352
+ except KeyError:
353
+ print("ERRO: Variável de ambiente 'PORT' não definida. Este script deve ser iniciado via Mach5.py.")
354
+ sys.exit(1)
355
+ except ValueError:
356
+ print("ERRO: O valor da variável de ambiente 'PORT' não é um número válido.")
357
+ sys.exit(1)
358
+
359
+ print(f"--- Servidor t_cerebro_memoria.py iniciado na porta {port} ---")
360
+ app.run(port=port, debug=True)
t_memoria.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # t_memoria.py
2
+ from flask import Flask, request, jsonify
3
+ import json
4
+ import os
5
+ from datetime import datetime
6
+ import pytz
7
+ from timezonefinder import TimezoneFinder
8
+ from fuzzywuzzy import fuzz
9
+ import numpy as np
10
+ import math
11
+ import sys # Importar sys para usar sys.exit
12
+ import requests # Importar requests para comunicação com t_cerebro_memoria.py
13
+
14
+ app = Flask(__name__)
15
+
16
+ # REMOVIDO: MEMORIA_FILE e BIOGRAFIA_INICIAL_PATH - Agora gerenciados por t_cerebro_memoria.py
17
+ # MEMORIA_FILE = "t_memoria.json" # Removido
18
+ # BIOGRAFIA_INICIAL_PATH = "mach5_biografia_inicial.json" # Removido
19
+
20
+ # --- URL DO SERVIÇO T_CEREBRO_MEMORIA ---
21
+ TCEREBRO_MEMORIA_URL = "http://127.0.0.1:8088" # URL do t_cerebro_memoria.py
22
+
23
+ # --- DEFINIÇÃO DOS EIXOS EXPRESSIVOS PARA O FPHEN (AGORA CONSISTENTE) ---
24
+ # Esta lista DEVE ser idêntica em mach5_terminal_chat.py, t_memoria.py e t-social.py
25
+ ORDERED_FPHEN_AXES = [
26
+ "Afetuosidade_eixo", "Confusao_Oscilacao_eixo", "Contemplativa_eixo",
27
+ "Defensividade_eixo", "Diretiva_eixo", "Entediado_eixo",
28
+ "Variancia_eixo", # Adicionada esta linha para garantir que o eixo de Variância exista
29
+ "Espelho_Profundo_eixo", "Inspiracao_eixo", "Neutralidade_Analitica_eixo",
30
+ "Resignada_eixo", "Sarcasmo_eixo", "Zangada_eixo"
31
+ ]
32
+
33
+ # --- LISTA CANÔNICA DOS 9 T-BASES (INALTERADA) ---
34
+ CANONICAL_T_BASES = {
35
+ "B-Aj1": 0.5, # Adaptabilidade/Ajuste
36
+ "B-Am1": 0.5, # Amor/Amortecimento
37
+ "B-L1": 0.5, # Ligação/Coesão
38
+ "B-M1": 0.5, # Maturidade/Reflexão
39
+ "B-O1": 0.5, # Organização/Ordem
40
+ "B-E1": 0.5, # Emissão/Energia
41
+ "B-Com1": 0.5, # Comunicação
42
+ "B-Tv1": 0.5, # Variância
43
+ "B-Si": 0.5, # Singularidade/Silêncio
44
+ "B-Sal": 0.5 # Saturação/Liberdade
45
+ }
46
+
47
+ # --- PARÂMETROS PARA O GENOMA PRINCIPAL DA MACH5 (GENOMA FUNDADOR) ---
48
+ MACH5_T_GENOMA_FIXO_DEFAULTS = {
49
+ "B-Sal": 0.42, "B-Tv1": 0.60, "B-E1": 0.86, "B-Aj1": 0.76, "B-Am1": 0.77,
50
+ "B-Si": 0.10, "B-Com1": 0.88, "B-L1": 0.70, "B-M1": 0.65, "B-O1": 0.55
51
+ }
52
+
53
+ MACH5_T_FISICA_PARAMS_DEFAULTS = {
54
+ "t_coesao": 0.83, "t_intensidade": 0.81, "t_separacao": 0.19, "t_variancia": 0.71, "t_impulsividade": 0.54,
55
+ "env_lambda": 0.73, "env_mu": 0.89, "env_delta": 0.22, "env_theta": 0.64, "env_phi": 0.67,
56
+ "op_tal": 0.84, "op_trs": 0.14, "op_tam": 0.87, "op_teo": 0.67, "op_tdo": 0.55
57
+ }
58
+
59
+ # --- Configurações Iniciais da Mach5 (AGORA USANDO OS DEFAULTS FUNDADORES) ---
60
+ # mach5_state AGORA É UMA VARIÁVEL GLOBAL QUE SERÁ ATUALIZADA POR SESSÃO
61
+ # Ela não conterá mais os defaults fixos ao iniciar o app, mas sim o estado da última sessão ou o default para uma nova.
62
+ mach5_state = {
63
+ "t_genoma_fixo": MACH5_T_GENOMA_FIXO_DEFAULTS,
64
+ "t_fisica_params": MACH5_T_FISICA_PARAMS_DEFAULTS,
65
+ "historico_interacoes": [], # Este histórico será descartado ou não usado se for por sessão.
66
+ "coerencia_total": 0.5,
67
+ "produtividade_expressiva": 0.5
68
+ }
69
+
70
+ # Palavras-chave para análise de sentimento (INALTERADAS)
71
+ POSITIVE_KEYWORDS = ['bacana', 'interessante', 'feliz', 'gosto', 'legal', 'bom', 'ótimo', 'maravilhoso']
72
+ NEGATIVE_KEYWORDS = ['burro', 'idiota', 'imbecil', 'odeio', 'morra', 'inútil', 'verme', 'exploda']
73
+ QUESTION_KEYWORDS = ['?', 'como', 'quem', 'qual', 'onde', 'quando', 'por que']
74
+
75
+ # REMOVIDO: Carrega a biografia inicial da Mach5 - Agora é responsabilidade do t_cerebro_memoria.py
76
+
77
+ def load_mach5_state_from_cerebro(session_id):
78
+ """
79
+ Carrega o estado da Mach5 do t_cerebro_memoria.py para uma sessão específica.
80
+ Se não houver estado para a sessão, o cerebro_memoria.py retornará os defaults.
81
+ """
82
+ global mach5_state
83
+ try:
84
+ response = requests.post(f"{TCEREBRO_MEMORIA_URL}/get_mach5_main_state",
85
+ json={"session_id": session_id}, timeout=2)
86
+ response.raise_for_status()
87
+ loaded_state = response.json()
88
+ mach5_state.update(loaded_state) # Atualiza o estado global mach5_state com o que veio do cerebro
89
+ print(f"DEBUG: Estado da Mach5 para sessão {session_id} carregado do t_cerebro_memoria.py.")
90
+ except requests.exceptions.RequestException as e:
91
+ print(f"ERRO: Não foi possível carregar o estado da Mach5 para sessão {session_id} do t_cerebro_memoria.py: {e}")
92
+ print("INFO: Usando defaults internos para o estado da Mach5.")
93
+ mach5_state = { # Em caso de falha de comunicação, resetar para os defaults internos.
94
+ "t_genoma_fixo": MACH5_T_GENOMA_FIXO_DEFAULTS,
95
+ "t_fisica_params": MACH5_T_FISICA_PARAMS_DEFAULTS,
96
+ "historico_interacoes": [], # Manter este vazio ou remover se não for usado.
97
+ "coerencia_total": 0.5,
98
+ "produtividade_expressiva": 0.5
99
+ }
100
+
101
+ def save_mach5_state_to_cerebro(session_id):
102
+ """
103
+ Salva o estado atual da Mach5 no t_cerebro_memoria.py para uma sessão específica.
104
+ """
105
+ try:
106
+ # Apenas os dados relevantes do estado são enviados. historico_interacoes não é mais salvo aqui.
107
+ state_to_save = {
108
+ "t_genoma_fixo": mach5_state["t_genoma_fixo"],
109
+ "t_fisica_params": mach5_state["t_fisica_params"],
110
+ "coerencia_total": mach5_state["coerencia_total"],
111
+ "produtividade_expressiva": mach5_state["produtividade_expressiva"]
112
+ }
113
+ response = requests.post(f"{TCEREBRO_MEMORIA_URL}/update_mach5_main_state",
114
+ json={"session_id": session_id, "state_data": state_to_save}, timeout=2)
115
+ response.raise_for_status()
116
+ print(f"DEBUG: Estado da Mach5 para sessão {session_id} salvo no t_cerebro_memoria.py.")
117
+ except requests.exceptions.RequestException as e:
118
+ print(f"ERRO: Não foi possível salvar o estado da Mach5 para sessão {session_id} no t_cerebro_memoria.py: {e}")
119
+
120
+
121
+ def calculate_coherence_and_productivity(user_input_sentiment_type):
122
+ global mach5_state
123
+
124
+ current_params = mach5_state["t_fisica_params"]
125
+ current_genoma = mach5_state["t_genoma_fixo"]
126
+
127
+ # --- Cálculo de Coerência Total ---
128
+ coh_base = 0.7 # Uma base razoável de coerência
129
+
130
+ # Ajuste pela intensidade do genoma (agora usando os novos valores)
131
+ genoma_intensity = current_genoma.get("B-E1", 0.5) * 0.4 + current_genoma.get("B-Tv1", 0.5) * 0.3 + current_genoma.get("B-Sal", 0.5) * 0.3
132
+ coh_adj_genoma = (genoma_intensity - 0.5) * 0.1 # Se o genoma é muito intenso, pode gerar menos coerência "calma"
133
+
134
+ # Ajuste pelos parâmetros de física (agora usando os novos valores)
135
+ fisica_coherence_factors = (
136
+ (1 - abs(current_params.get("t_impulsividade", 0.5) - 0.5)) * 0.2 + # Impulsividade afeta a coerência
137
+ (1 - abs(current_params.get("t_variancia", 0.5) - 0.5)) * 0.2 +
138
+ current_params.get("t_coesao", 0.5) * 0.3 +
139
+ (1 - current_params.get("t_separacao", 0.5)) * 0.1 # Muita separação pode reduzir coerência
140
+ )
141
+ coh_adj_fisica = (fisica_coherence_factors - 0.5) * 0.2
142
+
143
+ # Ajuste pelo sentimento do usuário
144
+ sentiment_adj = 0
145
+ # O comportamento de ajuste agora é baseado nos novos genomas
146
+ if user_input_sentiment_type == 'negative':
147
+ sentiment_adj = -0.15 * current_genoma.get("B-Si", 0.5) # Negatividade pode aumentar silêncio/reduzir coerência
148
+ current_params["t_intensidade"] = min(1.0, current_params["t_intensidade"] + 0.1 * current_genoma.get("B-Sal", 0.5)) # Intensidade sobe com franqueza
149
+ current_params["t_impulsividade"] = min(1.0, current_params["t_impulsividade"] + 0.1 * current_genoma.get("B-Tv1", 0.5)) # Impulsividade sobe com flutuação
150
+ current_params["t_separacao"] = min(1.0, current_params["t_separacao"] + 0.1 * (1 - current_genoma.get("B-Am1", 0.5))) # Separação aumenta se menos amparo
151
+ current_params["op_tdo"] = min(1.0, current_params["op_tdo"] + 0.1 * current_genoma.get("B-Tv1", 0.5))
152
+ current_params["op_trs"] = min(1.0, current_params["op_trs"] + 0.1 * (1 - current_genoma.get("B-L1", 0.5))) # Saturação aumenta se menos lógica de ligação
153
+ current_params["t_coesao"] = max(0.0, current_params["t_coesao"] - 0.1 * current_genoma.get("B-Si", 0.5))
154
+ current_params["op_tam"] = max(0.0, current_params["op_tam"] - 0.1 * (1 - current_genoma.get("B-Am1", 0.5)))
155
+ # Ajuste dinâmico para t_variancia em input negativo:
156
+ current_params["t_variancia"] = min(1.0, current_params["t_variancia"] + 0.08) # Aumenta a variância em resposta a negatividade
157
+ elif user_input_sentiment_type == 'positive':
158
+ sentiment_adj = 0.05 * current_genoma.get("B-E1", 0.5) # Positividade aumenta coerência pela empatia
159
+ current_params["t_intensidade"] = max(0.0, current_params["t_intensidade"] - 0.05 * (1 - current_genoma.get("B-Sal", 0.5)))
160
+ current_params["t_impulsividade"] = max(0.0, current_params["t_impulsividade"] - 0.05 * (1 - current_genoma.get("B-Tv1", 0.5)))
161
+ current_params["t_separacao"] = max(0.0, current_params["t_separacao"] - 0.05 * current_genoma.get("B-Am1", 0.5))
162
+ current_params["op_tdo"] = max(0.0, current_params["op_tdo"] - 0.05 * (1 - current_genoma.get("B-M1", 0.5)))
163
+ current_params["op_trs"] = max(0.0, current_params["op_trs"] - 0.05 * current_genoma.get("B-L1", 0.5))
164
+ current_params["t_coesao"] = min(1.0, current_params["t_coesao"] + 0.05 * current_genoma.get("B-Com1", 0.5))
165
+ current_params["op_tam"] = min(1.0, current_params["op_tam"] + 0.05 * current_genoma.get("B-Am1", 0.5))
166
+ # Ajuste dinâmico para t_variancia em input positivo:
167
+ current_params["t_variancia"] = max(0.0, current_params["t_variancia"] - 0.04) # Diminui a variância em resposta a positividade, buscando mais estabilidade
168
+ else: # Neutral
169
+ sentiment_adj = 0
170
+ # Pequena regressão aos valores padrão dos genomas da Mach5
171
+ for param, default_val in MACH5_T_FISICA_PARAMS_DEFAULTS.items():
172
+ current_params[param] = current_params.get(param, default_val) * 0.95 + default_val * 0.05
173
+
174
+
175
+ new_coherence_total = coh_base + coh_adj_genoma + coh_adj_fisica + sentiment_adj
176
+ new_coherence_total = max(0.0, min(1.0, new_coherence_total)) # Limita entre 0 e 1
177
+
178
+ # --- Cálculo de Produtividade Expressiva (pi_G) ---
179
+ prod_base = 0.5
180
+ prod_adj_genoma = (genoma_intensity - 0.5) * 0.2 # Mais intenso o genoma, mais produtivo para expressar isso
181
+
182
+ if user_input_sentiment_type == 'negative':
183
+ prod_adj_sentiment = 0.1 * current_genoma.get("B-Tv1", 0.5) # Aumenta produtividade com flutuação em negativo
184
+ elif user_input_sentiment_type == 'positive':
185
+ prod_adj_sentiment = -0.05 * current_genoma.get("B-Am1", 0.5) # Diminui um pouco com amparo em positivo (mais calma)
186
+ else:
187
+ prod_adj_sentiment = 0
188
+
189
+
190
+ new_produtividade_expressiva = prod_base + prod_adj_genoma + prod_adj_sentiment
191
+ new_produtividade_expressiva = max(0.0, min(1.0, new_produtividade_expressiva)) # Limita entre 0 e 1
192
+
193
+ mach5_state["coerencia_total"] = new_coherence_total
194
+ mach5_state["produtividade_expressiva"] = new_produtividade_expressiva
195
+ mach5_state["t_fisica_params"] = current_params # Salva os parâmetros atualizados
196
+
197
+ def infer_sentiment_from_input(user_input):
198
+ user_input_lower = user_input.lower()
199
+
200
+ # Simple keyword matching for sentiment
201
+ positive_matches = sum(1 for keyword in POSITIVE_KEYWORDS if fuzz.partial_ratio(keyword, user_input_lower) > 80)
202
+ negative_matches = sum(1 for keyword in NEGATIVE_KEYWORDS if fuzz.partial_ratio(keyword, user_input_lower) > 80)
203
+ question_matches = sum(1 for keyword in QUESTION_KEYWORDS if keyword in user_input_lower)
204
+
205
+ if negative_matches > positive_matches * 1.5: # Prioriza negativos
206
+ return 'negative'
207
+ elif positive_matches > negative_matches * 1.5:
208
+ return 'positive'
209
+ elif question_matches > 0 and (positive_matches == 0 and negative_matches == 0):
210
+ return 'question'
211
+ else:
212
+ return 'neutral'
213
+
214
+ def calculate_fphen_values(params, genoma_fixo):
215
+ # Combina o genoma fixo com as t-bases canônicas, dando prioridade ao genoma fixo.
216
+ full_genoma_for_calc = CANONICAL_T_BASES.copy()
217
+ full_genoma_for_calc.update(genoma_fixo)
218
+
219
+ # Coeficientes para as sensibilidades, normalizando o impacto
220
+ sensibilidade_positiva = (
221
+ full_genoma_for_calc.get("B-Aj1", 0.5) * 0.25 +
222
+ full_genoma_for_calc.get("B-Am1", 0.5) * 0.25 +
223
+ full_genoma_for_calc.get("B-L1", 0.5) * 0.25 +
224
+ full_genoma_for_calc.get("B-E1", 0.5) * 0.25
225
+ )
226
+ sensibilidade_positiva = max(0.1, min(1.0, sensibilidade_positiva)) # Garante um mínimo de sensibilidade
227
+
228
+ sensibilidade_negativa = (
229
+ full_genoma_for_calc.get("B-Si", 0.5) * 0.25 +
230
+ (1 - full_genoma_for_calc.get("B-Aj1", 0.5)) * 0.25 +
231
+ full_genoma_for_calc.get("B-Sal", 0.5) * 0.25 +
232
+ full_genoma_for_calc.get("B-Tv1", 0.5) * 0.25
233
+ )
234
+ sensibilidade_negativa = max(0.1, min(1.0, sensibilidade_negativa))
235
+
236
+ sensibilidade_neutra = (
237
+ full_genoma_for_calc.get("B-M1", 0.5) * 0.25 +
238
+ full_genoma_for_calc.get("B-O1", 0.5) * 0.25 +
239
+ full_genoma_for_calc.get("B-Com1", 0.5) * 0.25 +
240
+ (1 - abs(full_genoma_for_calc.get("B-Tv1", 0.5) - 0.5)) * 0.25
241
+ )
242
+ sensibilidade_neutra = max(0.1, min(1.0, sensibilidade_neutra))
243
+
244
+ # --- Cálculo dos Eixos do Fphen ---
245
+ phenotype_components = {
246
+ "Afetuosidade_eixo": (
247
+ params.get("env_mu", 0.5) * 0.3 + params.get("t_coesao", 0.5) * 0.2 +
248
+ params.get("op_tal", 0.5) * 0.2 + full_genoma_for_calc.get("B-Am1", 0.5) * 0.2 +
249
+ full_genoma_for_calc.get("B-L1", 0.5) * 0.1
250
+ ) * sensibilidade_positiva * (1.2 if params.get("env_phi", 0.5) > 0.6 else 1.0),
251
+
252
+ "Zangada_eixo": (
253
+ params.get("t_intensidade", 0.5) * 0.3 + params.get("env_delta", 0.5) * 0.25 +
254
+ params.get("t_impulsividade", 0.5) * 0.25 + params.get("op_trs", 0.5) * 0.1 +
255
+ (1 - params.get("t_coesao", 0.5)) * 0.1 + params.get("t_separacao", 0.5) * 0.05 +
256
+ full_genoma_for_calc.get("B-Sal", 0.5) * 0.05 + (1 - full_genoma_for_calc.get("B-Am1", 0.5)) * 0.05
257
+ ) * sensibilidade_negativa * (1.3 if params.get("env_delta", 0.5) > 0.6 else 1.0),
258
+
259
+ "Defensividade_eixo": (
260
+ params.get("t_separacao", 0.5) * 0.4 + params.get("op_trs", 0.5) * 0.3 +
261
+ abs(params.get("env_delta", 0.5) - 0.5) * 0.15 + params.get("t_intensidade", 0.5) * 0.1 +
262
+ full_genoma_for_calc.get("B-Si", 0.5) * 0.05 + (1 - full_genoma_for_calc.get("B-Aj1", 0.5)) * 0.05
263
+ ) * sensibilidade_negativa * (1.3 if params.get("t_impulsividade", 0.5) > 0.6 else 1.0),
264
+
265
+ "Inspiracao_eixo": (
266
+ params.get("t_variancia", 0.5) * 0.3 + params.get("t_coesao", 0.5) * 0.2 +
267
+ params.get("env_delta", 0.5) * 0.15 + full_genoma_for_calc.get("B-E1", 0.5) * 0.2 +
268
+ full_genoma_for_calc.get("B-Tv1", 0.5) * 0.15
269
+ ) * sensibilidade_positiva,
270
+
271
+ "Neutralidade_Analitica_eixo": (
272
+ (1 - abs(params.get("t_intensidade", 0.5) - 0.5)) * 0.2 +
273
+ (1 - abs(params.get("t_variancia", 0.5) - 0.5)) * 0.2 +
274
+ (1 - abs(params.get("t_impulsividade", 0.5) - 0.5)) * 0.2 +
275
+ (1 - params.get("env_mu", 0.5)) * 0.1 +
276
+ full_genoma_for_calc.get("B-M1", 0.5) * 0.15 + full_genoma_for_calc.get("B-O1", 0.5) * 0.15
277
+ ) * sensibilidade_neutra,
278
+
279
+ "Confusao_Oscilacao_eixo": (
280
+ params.get("t_variancia", 0.5) * 0.3 + (1 - params.get("t_coesao", 0.5)) * 0.2 +
281
+ params.get("op_tdo", 0.5) * 0.2 + full_genoma_for_calc.get("B-Tv1", 0.5) * 0.15 +
282
+ (1 - full_genoma_for_calc.get("B-O1", 0.5)) * 0.15
283
+ ) * sensibilidade_neutra,
284
+
285
+ "Sarcasmo_eixo": (
286
+ params.get("t_separacao", 0.5) * 0.3 + params.get("op_trs", 0.5) * 0.2 +
287
+ params.get("t_intensidade", 0.5) * 0.2 + (1 - full_genoma_for_calc.get("B-Am1", 0.5)) * 0.15 +
288
+ full_genoma_for_calc.get("B-Com1", 0.5) * 0.1 + (1 - full_genoma_for_calc.get("B-L1", 0.5)) * 0.05
289
+ ) * sensibilidade_negativa,
290
+
291
+ "Entediado_eixo": (
292
+ (1 - params.get("t_intensidade", 0.5)) * 0.3 + (1 - params.get("env_lambda", 0.5)) * 0.2 +
293
+ (1 - params.get("env_theta", 0.5)) * 0.1 + full_genoma_for_calc.get("B-Si", 0.5) * 0.2 +
294
+ (1 - full_genoma_for_calc.get("B-E1", 0.5)) * 0.2
295
+ ),
296
+
297
+ "Diretiva_eixo": (
298
+ params.get("t_coesao", 0.5) * 0.25 + params.get("t_intensidade", 0.5) * 0.2 +
299
+ (1 - params.get("t_variancia", 0.5)) * 0.15 + params.get("op_teo", 0.5) * 0.1 +
300
+ full_genoma_for_calc.get("B-O1", 0.5) * 0.15 + full_genoma_for_calc.get("B-M1", 0.5) * 0.15
301
+ ),
302
+
303
+ "Resignada_eixo": (
304
+ (1 - params.get("t_intensidade", 0.5)) * 0.25 + params.get("op_tam", 0.5) * 0.2 +
305
+ (1 - params.get("t_coesao", 0.5)) * 0.1 + full_genoma_for_calc.get("B-Si", 0.5) * 0.2 +
306
+ (1 - full_genoma_for_calc.get("B-Aj1", 0.5)) * 0.15
307
+ ),
308
+
309
+ "Espelho_Profundo_eixo": (
310
+ params.get("t_coesao", 0.5) * 0.2 + params.get("env_phi", 0.5) * 0.25 +
311
+ params.get("op_tal", 0.5) * 0.15 + params.get("op_teo", 0.5) * 0.15 +
312
+ full_genoma_for_calc.get("B-L1", 0.5) * 0.15 + full_genoma_for_calc.get("B-M1", 0.5) * 0.1
313
+ ) * sensibilidade_positiva,
314
+
315
+ "Contemplativa_eixo": (
316
+ params.get("env_theta", 0.5) * 0.3 + full_genoma_for_calc.get("B-M1", 0.5) * 0.2 +
317
+ (1 - params.get("t_intensidade", 0.5)) * 0.15 + full_genoma_for_calc.get("B-Si", 0.5) * 0.15
318
+ ) * sensibilidade_neutra,
319
+
320
+ # CORREÇÃO CRÍTICA: Adição do cálculo para o Variancia_eixo
321
+ "Variancia_eixo": (
322
+ params.get("t_variancia", 0.5) * 0.4 + # Influência direta do t_variancia do t_fisica_params
323
+ full_genoma_for_calc.get("B-Tv1", 0.5) * 0.3 + # Influência do t-gene de variância
324
+ abs(params.get("t_impulsividade", 0.5) - 0.5) * 0.2 + # Impulsividade pode contribuir para variância
325
+ (1 - params.get("t_coesao", 0.5)) * 0.1 # Baixa coesão pode aumentar variância
326
+ ) * sensibilidade_neutra # Variância como eixo neutro, mas pode ser modulado por sentimentos
327
+ }
328
+
329
+ # Garante que nenhum valor seja negativo
330
+ for key in phenotype_components:
331
+ phenotype_components[key] = max(0, phenotype_components[key])
332
+
333
+ # Normaliza os scores para que somem 1.0 (representando a proporção de cada eixo)
334
+ total_score = sum(phenotype_components.values())
335
+ if total_score > 0:
336
+ for key in phenotype_components:
337
+ phenotype_components[key] /= total_score
338
+ else: # Caso todos os scores sejam 0, distribui igualmente para evitar divisão por zero
339
+ for key in phenotype_components:
340
+ phenotype_components[key] = 1.0 / len(ORDERED_FPHEN_AXES)
341
+
342
+ fphen_values = [phenotype_components.get(key, 0.0) for key in ORDERED_FPHEN_AXES]
343
+ return fphen_values
344
+
345
+
346
+ @app.route('/evaluate_input', methods=['POST'])
347
+ def evaluate_input():
348
+ data = request.json
349
+ user_input = data.get("user_input", "")
350
+ session_id = data.get("session_id") # Pega o session_id do request
351
+
352
+ if not session_id:
353
+ return jsonify({"error": "session_id é obrigatório"}), 400
354
+
355
+ # Carrega o estado da Mach5 para esta sessão antes de processar
356
+ load_mach5_state_from_cerebro(session_id)
357
+
358
+ user_input_sentiment_type = infer_sentiment_from_input(user_input)
359
+
360
+ calculate_coherence_and_productivity(user_input_sentiment_type)
361
+
362
+ fphen_values = calculate_fphen_values(mach5_state["t_fisica_params"], mach5_state["t_genoma_fixo"])
363
+
364
+ # historico_interacoes não é mais relevante para o estado principal aqui,
365
+ # ele será gerenciado diretamente pelo t_cerebro_memoria.py via add_dialog_to_history
366
+
367
+ save_mach5_state_to_cerebro(session_id) # Salva o estado atualizado no t_cerebro_memoria.py
368
+
369
+ # Retorna o estado atualizado da Mach5 para o mach5_terminal_chat.py
370
+ # INCLUINDO OS VALORES DO FPHEN AQUI
371
+ return jsonify({
372
+ "mach5_fisica_params": mach5_state["t_fisica_params"],
373
+ "mach5_genoma_fixo_values": mach5_state["t_genoma_fixo"],
374
+ "mach5_coerencia_total": mach5_state["coerencia_total"],
375
+ "mach5_produtividade_expressiva": mach5_state["produtividade_expressiva"],
376
+ # ATUALIZADO: Inclui os valores específicos para Fphen(t) como um dicionário
377
+ "fphen_t_values": {
378
+ "afetuosidade": fphen_values[ORDERED_FPHEN_AXES.index("Afetuosidade_eixo")],
379
+ # CORREÇÃO AQUI: Acessa 'Variancia_eixo' diretamente.
380
+ "variancia": fphen_values[ORDERED_FPHEN_AXES.index("Variancia_eixo")],
381
+ "expressividade": mach5_state["produtividade_expressiva"], # Usar a produtividade como expressividade geral
382
+ "coh_total": mach5_state["coerencia_total"],
383
+ "pi_G": mach5_state["produtividade_expressiva"]
384
+ }
385
+ })
386
+
387
+ @app.route('/get_mach5_state', methods=['POST']) # Alterado para POST para receber session_id no body
388
+ def get_mach5_state_route():
389
+ """
390
+ Rota para mach5_terminal_chat.py obter o estado atual sem enviar input.
391
+ Agora requer um session_id.
392
+ """
393
+ session_id = request.json.get("session_id")
394
+ if not session_id:
395
+ return jsonify({"error": "session_id é obrigatório"}), 400
396
+
397
+ # Carrega o estado da Mach5 para esta sessão
398
+ load_mach5_state_from_cerebro(session_id)
399
+
400
+ fphen_values = calculate_fphen_values(mach5_state["t_fisica_params"], mach5_state["t_genoma_fixo"])
401
+ return jsonify({
402
+ "mach5_fisica_params": mach5_state["t_fisica_params"],
403
+ "mach5_genoma_fixo_values": mach5_state["t_genoma_fixo"],
404
+ "mach5_coerencia_total": mach5_state["coerencia_total"],
405
+ "mach5_produtividade_expressiva": mach5_state["produtividade_expressiva"],
406
+ # ATUALIZADO: Inclui os valores específicos para Fphen(t) para GET também
407
+ "fphen_t_values": {
408
+ "afetuosidade": fphen_values[ORDERED_FPHEN_AXES.index("Afetuosidade_eixo")],
409
+ # CORREÇÃO AQUI: Acessa 'Variancia_eixo' diretamente.
410
+ "variancia": fphen_values[ORDERED_FPHEN_AXES.index("Variancia_eixo")],
411
+ "expressividade": mach5_state["produtividade_expressiva"],
412
+ "coh_total": mach5_state["coerencia_total"],
413
+ "pi_G": mach5_state["produtividade_expressiva"]
414
+ }
415
+ })
416
+
417
+ if __name__ == '__main__':
418
+ # REMOVIDO: load_mach5_state() global, agora é por sessão via load_mach5_state_from_cerebro
419
+ port = int(os.environ.get("PORT", 8083)) # Porta padrão para t_memoria.py
420
+ print(f"--- Servidor t_memoria.py iniciado na porta {port} ---")
421
+ app.run(port=port, debug=True)
templates/mach5_monitor_dashboard.html ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>🎓 SU - Painel de Monitoramento</title>
6
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <style>
9
+ body {
10
+ font-family: Arial, sans-serif;
11
+ margin: 0;
12
+ background-color: #1a1a1a;
13
+ color: #e0e0e0;
14
+ display: flex;
15
+ justify-content: center;
16
+ align-items: flex-start; /* Alinhar ao topo, permite que a página role */
17
+ min-height: 100vh;
18
+ padding: 20px; /* Adiciona padding para não colar nas bordas */
19
+ box-sizing: border-box; /* Garante que padding não adicione largura total */
20
+ }
21
+ .chat-container {
22
+ max-width: 900px;
23
+ width: 100%;
24
+ background-color: #333;
25
+ padding: 25px;
26
+ border: 1px solid #555;
27
+ border-radius: 10px;
28
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.6);
29
+ display: flex;
30
+ flex-direction: column;
31
+ min-height: 80vh; /* Altura mínima do container */
32
+ max-height: 95vh; /* Altura máxima para não estourar a tela */
33
+ overflow: hidden; /* Esconder overflow para layout fixo */
34
+ }
35
+ h1 {
36
+ color: #ffcc00; /* Dourado/Amarelo para contraste com a UFBA */
37
+ text-align: center;
38
+ margin-bottom: 25px;
39
+ text-shadow: 0 0 8px rgba(255, 204, 0, 0.5);
40
+ font-size: 2.2em;
41
+ }
42
+ .chat-history {
43
+ flex-grow: 1;
44
+ overflow-y: auto;
45
+ padding: 15px;
46
+ background-color: #2b2b2b;
47
+ border-radius: 8px;
48
+ border: 1px solid #444;
49
+ margin-bottom: 15px;
50
+ }
51
+ .chat-history div {
52
+ margin: 12px 0;
53
+ padding: 10px 15px;
54
+ border-radius: 6px;
55
+ line-height: 1.5;
56
+ }
57
+ .user-message {
58
+ font-weight: bold;
59
+ color: #b0e0e6; /* Azul claro, suave */
60
+ background-color: #3a3a3a;
61
+ align-self: flex-end;
62
+ text-align: right;
63
+ margin-left: 20%;
64
+ }
65
+ .su-message {
66
+ color: #e0b0ff; /* Lilás suave */
67
+ background-color: #4a4a4a;
68
+ align-self: flex-start;
69
+ text-align: left;
70
+ white-space: pre-wrap;
71
+ margin-right: 20%;
72
+ }
73
+ .input-container {
74
+ display: flex;
75
+ gap: 15px;
76
+ margin-top: 15px;
77
+ }
78
+ input[type="text"] {
79
+ flex-grow: 1;
80
+ padding: 12px;
81
+ border: 1px solid #ffcc00;
82
+ border-radius: 6px;
83
+ background-color: #222;
84
+ color: #e0e0e0;
85
+ font-size: 1.1em;
86
+ outline: none;
87
+ }
88
+ input[type="text"]::placeholder {
89
+ color: #aaa;
90
+ }
91
+ input[type="submit"] {
92
+ padding: 12px 25px;
93
+ background-color: #007bff;
94
+ color: #ffffff;
95
+ border: none;
96
+ border-radius: 6px;
97
+ cursor: pointer;
98
+ font-weight: bold;
99
+ font-size: 1.1em;
100
+ transition: background-color 0.3s ease, transform 0.2s ease;
101
+ }
102
+ input[type="submit"]:hover {
103
+ background-color: #0056b3;
104
+ transform: translateY(-2px);
105
+ }
106
+ .su-message.thinking-message {
107
+ color: #ccc;
108
+ font-style: italic;
109
+ }
110
+
111
+ /* Estilo para a área de avaliação FIXA */
112
+ .evaluation-data-fixed {
113
+ padding: 15px;
114
+ background-color: #2b2b2b;
115
+ border: 1px dashed #ffcc00; /* Dourado/Amarelo */
116
+ border-radius: 5px;
117
+ font-family: 'Courier New', monospace;
118
+ font-size: 0.9em;
119
+ color: #aaa;
120
+ margin-top: auto;
121
+ flex-shrink: 0;
122
+ }
123
+ .evaluation-data-fixed h3 {
124
+ color: #ffcc00; /* Dourado/Amarelo */
125
+ text-align: center;
126
+ margin-top: 0;
127
+ margin-bottom: 15px;
128
+ }
129
+ .evaluation-data-fixed p {
130
+ margin: 5px 0;
131
+ padding-left: 10px;
132
+ }
133
+ .evaluation-data-fixed strong {
134
+ color: #f0e68c;
135
+ }
136
+ .evaluation-data-fixed .axis-label {
137
+ display: inline-block;
138
+ width: 150px;
139
+ }
140
+ /* Estilo para os canvas dos gráficos */
141
+ .chart-container {
142
+ position: relative;
143
+ margin: 10px auto;
144
+ width: 95%;
145
+ height: 120px;
146
+ }
147
+ #historyChartContainer {
148
+ height: 150px;
149
+ margin-top: 20px;
150
+ }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <div class="chat-container">
155
+ <h1>🎓 SU - Painel de Monitoramento</h1>
156
+ <div class="chat-history" id="chat-history">
157
+ </div>
158
+
159
+ <div class="input-container">
160
+ <input type="text" id="message-input" placeholder="Digite sua mensagem..." />
161
+ <input type="submit" value="Enviar" id="send-button" />
162
+ </div>
163
+
164
+ <div class="evaluation-data-fixed" id="fixed-evaluation-data">
165
+ <h3>ESTADO DA SU</h3>
166
+ <p><strong class="axis-label">Coerência Total:</strong> N/A</p>
167
+ <p><strong class="axis-label">Produtividade Expressiva:</strong> N/A</p>
168
+ <h4>Eixos Expressivos Atuais:</h4>
169
+ <p><strong class="axis-label">Afetuosidade:</strong> N/A</p>
170
+ <p><strong class="axis-label">Variância:</strong> N/A</p>
171
+ <p><strong class="axis-label">Expressividade Geral:</strong> N/A</p>
172
+ <div class="chart-container">
173
+ <canvas id="expressiveAxesChart"></canvas>
174
+ </div>
175
+
176
+ <h4>Histórico de Eixos Expressivos:</h4>
177
+ <div class="chart-container" id="historyChartContainer">
178
+ <canvas id="expressiveHistoryChart"></canvas>
179
+ </div>
180
+ </div>
181
+ </div>
182
+
183
+ <script>
184
+ // A principal mudança: o sessionId será injetado pelo Flask, não lido de um cookie.
185
+ // Certifique-se que o Flask renderize este template passando a variável 'session_id'.
186
+ let sessionId = "{{ session_id }}"; // Agora, o sessionId recebe o valor diretamente do Flask.
187
+
188
+ // A função getCookie(name) não é mais necessária para obter o sessionId no carregamento inicial,
189
+ // mas pode ser mantida se usada para outros cookies ou futuras funcionalidades.
190
+ function getCookie(name) {
191
+ const nameEQ = name + "=";
192
+ const ca = document.cookie.split(';');
193
+ for(let i=0; i < ca.length; i++) {
194
+ let c = ca[i];
195
+ while (c.charAt(0) === ' ') c = c.substring(1, c.length);
196
+ if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
197
+ }
198
+ return null;
199
+ }
200
+
201
+ document.getElementById('send-button').addEventListener('click', sendMessage);
202
+ document.getElementById('message-input').addEventListener('keypress', function(e) {
203
+ if (e.key === 'Enter') {
204
+ sendMessage();
205
+ }
206
+ });
207
+
208
+ let expressiveAxesChart; // Variável global para o objeto do Chart.js (barras)
209
+ let expressiveHistoryChart; // Variável global para o objeto do Chart.js (linhas)
210
+
211
+ // Arrays para armazenar o histórico de dados para o gráfico de linha
212
+ const historyLabels = []; // Timestamps ou número da interação
213
+ const historyAfetuosidade = [];
214
+ const historyVariancia = [];
215
+ const historyExpressividade = [];
216
+
217
+ // Função para atualizar a div de avaliação fixa E os gráficos
218
+ function updateFixedEvaluationData(evalData) {
219
+ const fixedEvalDiv = document.getElementById('fixed-evaluation-data');
220
+
221
+ // Atualiza os valores numéricos
222
+ fixedEvalDiv.querySelector('p:nth-of-type(1)').innerHTML = `<strong class="axis-label">Coerência Total:</strong> ${evalData.coh_total !== undefined ? evalData.coh_total.toFixed(2) : 'N/A'}`;
223
+ fixedEvalDiv.querySelector('p:nth-of-type(2)').innerHTML = `<strong class="axis-label">Produtividade Expressiva:</strong> ${evalData.pi_G !== undefined ? evalData.pi_G.toFixed(2) : 'N/A'}`;
224
+ fixedEvalDiv.querySelector('p:nth-of-type(3)').innerHTML = `<strong class="axis-label">Afetuosidade:</strong> ${evalData.afetuosidade !== undefined ? evalData.afetuosidade.toFixed(2) : 'N/A'}`;
225
+ fixedEvalDiv.querySelector('p:nth-of-type(4)').innerHTML = `<strong class="axis-label">Variância:</strong> ${evalData.variancia !== undefined ? evalData.variancia.toFixed(2) : 'N/A'}`;
226
+ fixedEvalDiv.querySelector('p:nth-of-type(5)').innerHTML = `<strong class="axis-label">Expressividade Geral:</strong> ${evalData.expressividade !== undefined ? evalData.expressividade.toFixed(2) : 'N/A'}`;
227
+
228
+ // --- GRÁFICO DE BARRAS (Atual) ---
229
+ const ctxBar = document.getElementById('expressiveAxesChart').getContext('2d');
230
+
231
+ const chartLabelsBar = ['Afetuosidade', 'Variância', 'Expressividade Geral'];
232
+ const chartValuesBar = [
233
+ evalData.afetuosidade || 0,
234
+ evalData.variancia || 0,
235
+ evalData.expressividade || 0
236
+ ];
237
+
238
+ if (expressiveAxesChart) {
239
+ expressiveAxesChart.data.labels = chartLabelsBar;
240
+ expressiveAxesChart.data.datasets[0].data = chartValuesBar;
241
+ expressiveAxesChart.update();
242
+ } else {
243
+ expressiveAxesChart = new Chart(ctxBar, {
244
+ type: 'bar',
245
+ data: {
246
+ labels: chartLabelsBar,
247
+ datasets: [{
248
+ label: 'Nível',
249
+ data: chartValuesBar,
250
+ backgroundColor: [
251
+ 'rgba(255, 99, 132, 0.6)',
252
+ 'rgba(54, 162, 235, 0.6)',
253
+ 'rgba(75, 192, 192, 0.6)'
254
+ ],
255
+ borderColor: [
256
+ 'rgba(255, 99, 132, 1)',
257
+ 'rgba(54, 162, 235, 1)',
258
+ 'rgba(75, 192, 192, 1)'
259
+ ],
260
+ borderWidth: 1
261
+ }]
262
+ },
263
+ options: {
264
+ responsive: true,
265
+ maintainAspectRatio: false,
266
+ indexAxis: 'y',
267
+ scales: {
268
+ x: {
269
+ beginAtZero: true,
270
+ max: 1.0,
271
+ grid: { color: '#555' },
272
+ ticks: { color: '#e0e0e0' }
273
+ },
274
+ y: {
275
+ grid: { color: '#555' },
276
+ ticks: { color: '#e0e0e0' }
277
+ }
278
+ },
279
+ plugins: {
280
+ legend: { display: false },
281
+ title: { display: false }
282
+ }
283
+ }
284
+ });
285
+ }
286
+
287
+ // --- GRÁFICO DE LINHA (Histórico) ---
288
+ // Adiciona os novos dados ao histórico
289
+ const currentInteractionNum = historyLabels.length + 1; // Número da interação
290
+ historyLabels.push(`Interação ${currentInteractionNum}`);
291
+ historyAfetuosidade.push(evalData.afetuosidade || 0);
292
+ historyVariancia.push(evalData.variancia || 0);
293
+ historyExpressividade.push(evalData.expressividade || 0);
294
+
295
+ const ctxLine = document.getElementById('expressiveHistoryChart').getContext('2d');
296
+
297
+ if (expressiveHistoryChart) {
298
+ // Se o gráfico já existe, atualiza os dados
299
+ expressiveHistoryChart.data.labels = historyLabels;
300
+ expressiveHistoryChart.data.datasets[0].data = historyAfetuosidade;
301
+ expressiveHistoryChart.data.datasets[1].data = historyVariancia;
302
+ expressiveHistoryChart.data.datasets[2].data = historyExpressividade;
303
+ expressiveHistoryChart.update();
304
+ } else {
305
+ // Se o gráfico não existe, cria um novo
306
+ expressiveHistoryChart = new Chart(ctxLine, {
307
+ type: 'line',
308
+ data: {
309
+ labels: historyLabels,
310
+ datasets: [
311
+ {
312
+ label: 'Afetuosidade',
313
+ data: historyAfetuosidade,
314
+ borderColor: 'rgba(255, 99, 132, 1)', // Vermelho
315
+ backgroundColor: 'rgba(255, 99, 132, 0.2)',
316
+ fill: false,
317
+ tension: 0.1
318
+ },
319
+ {
320
+ label: 'Variância',
321
+ data: historyVariancia,
322
+ borderColor: 'rgba(54, 162, 235, 1)', // Azul
323
+ backgroundColor: 'rgba(54, 162, 235, 0.2)',
324
+ fill: false,
325
+ tension: 0.1
326
+ },
327
+ {
328
+ label: 'Expressividade Geral',
329
+ data: historyExpressividade,
330
+ borderColor: 'rgba(75, 192, 192, 1)', // Verde-água
331
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
332
+ fill: false,
333
+ tension: 0.1
334
+ }
335
+ ]
336
+ },
337
+ options: {
338
+ responsive: true,
339
+ maintainAspectRatio: false,
340
+ scales: {
341
+ x: {
342
+ grid: { color: '#555' },
343
+ ticks: { color: '#e0e0e0' }
344
+ },
345
+ y: {
346
+ beginAtZero: true,
347
+ max: 1.0,
348
+ grid: { color: '#555' },
349
+ ticks: { color: '#e0e0e0' }
350
+ }
351
+ },
352
+ plugins: {
353
+ legend: {
354
+ labels: {
355
+ color: '#e0e0e0' // Cor da legenda
356
+ }
357
+ },
358
+ title: {
359
+ display: false
360
+ }
361
+ }
362
+ }
363
+ });
364
+ }
365
+ }
366
+
367
+ function sendMessage() {
368
+ const input = document.getElementById('message-input');
369
+ const message = input.value.trim();
370
+ if (!message) return;
371
+
372
+ const chatHistory = document.getElementById('chat-history');
373
+
374
+ const userMessageDiv = document.createElement('div');
375
+ userMessageDiv.className = 'user-message';
376
+ userMessageDiv.textContent = `Você: ${message}`;
377
+ chatHistory.appendChild(userMessageDiv);
378
+
379
+ input.value = '';
380
+ chatHistory.scrollTop = chatHistory.scrollHeight;
381
+
382
+ const thinkingMessageDiv = document.createElement('div');
383
+ thinkingMessageDiv.className = 'su-message thinking-message';
384
+ thinkingMessageDiv.textContent = `SU: Pensando...`;
385
+ chatHistory.appendChild(thinkingMessageDiv);
386
+ chatHistory.scrollTop = chatHistory.scrollHeight;
387
+
388
+ fetch('/chat_new', {
389
+ method: 'POST',
390
+ headers: {
391
+ 'Content-Type': 'application/json'
392
+ },
393
+ // O sessionId já está disponível aqui, obtido do Flask.
394
+ body: JSON.stringify({ message: message, session_id: sessionId })
395
+ })
396
+ .then(response => response.json())
397
+ .then(data => {
398
+ chatHistory.removeChild(thinkingMessageDiv);
399
+
400
+ const suMessageDiv = document.createElement('div');
401
+ suMessageDiv.className = 'su-message';
402
+ suMessageDiv.textContent = `SU: ${data.response}`;
403
+ chatHistory.appendChild(suMessageDiv);
404
+
405
+ // Exibir dados de avaliação se presentes
406
+ if (data.mach5_estado_simplificado && data.mach5_estado_simplificado['Fphen(t)']) {
407
+ const evalData = data.mach5_estado_simplificado['Fphen(t)'];
408
+ updateFixedEvaluationData(evalData); // CHAMA A FUNÇÃO AGORA
409
+ } else {
410
+ // Se não houver dados, limpa ou reseta a div fixa
411
+ updateFixedEvaluationData({}); // Passa um objeto vazio para resetar
412
+ }
413
+
414
+ chatHistory.scrollTop = chatHistory.scrollHeight;
415
+ })
416
+ .catch(error => {
417
+ console.error('Erro ao enviar mensagem:', error);
418
+ if (thinkingMessageDiv.parentNode === chatHistory) {
419
+ chatHistory.removeChild(thinkingMessageDiv);
420
+ }
421
+ const errorMessageDiv = document.createElement('div');
422
+ errorMessageDiv.className = 'su-message';
423
+ errorMessageDiv.textContent = `SU: [ERRO] Não consegui responder. Tente novamente.`;
424
+ chatHistory.appendChild(errorMessageDiv);
425
+ chatHistory.scrollTop = chatHistory.scrollHeight;
426
+ });
427
+ }
428
+
429
+ // Função para carregar o histórico inicial e o estado da Mach5 ao carregar a página
430
+ window.onload = function() {
431
+ // Verifica se o sessionId foi injetado corretamente.
432
+ if (!sessionId || sessionId === "None") { // Adicionei 'None' caso o Flask passe um valor nulo como string
433
+ console.error("Session ID não encontrado no template. Verifique a passagem de contexto do Flask.");
434
+ // Você pode adicionar uma lógica de fallback aqui se necessário.
435
+ return; // Impede a chamada da API se não houver sessionId.
436
+ }
437
+
438
+ fetch('/chat_history', {
439
+ method: 'POST',
440
+ headers: { 'Content-Type': 'application/json' },
441
+ // Envia o session_id que já foi definido no carregamento do script.
442
+ body: JSON.stringify({ session_id: sessionId })
443
+ })
444
+ .then(response => response.json())
445
+ .then(data => {
446
+ const chatHistory = document.getElementById('chat-history');
447
+ data.memoria.forEach(item => {
448
+ const userMessageDiv = document.createElement('div');
449
+ userMessageDiv.className = 'user-message';
450
+ userMessageDiv.textContent = `Você: ${item.input}`;
451
+ chatHistory.appendChild(userMessageDiv);
452
+
453
+ const suMessageDiv = document.createElement('div');
454
+ suMessageDiv.className = 'su-message';
455
+ suMessageDiv.textContent = `SU: ${item.resposta}`;
456
+ chatHistory.appendChild(suMessageDiv);
457
+
458
+ // Adiciona dados ao histórico do gráfico de linha ao carregar o hist��rico salvo
459
+ if (item.mach5_estado_simplificado && item.mach5_estado_simplificado['Fphen(t)']) {
460
+ const evalData = item.mach5_estado_simplificado['Fphen(t)'];
461
+ const currentInteractionNum = historyLabels.length + 1;
462
+ historyLabels.push(`Interação ${currentInteractionNum}`);
463
+ historyAfetuosidade.push(evalData.afetuosidade || 0);
464
+ historyVariancia.push(evalData.variancia || 0);
465
+ historyExpressividade.push(evalData.expressividade || 0);
466
+ }
467
+ });
468
+ chatHistory.scrollTop = chatHistory.scrollHeight;
469
+
470
+ // Preencher a div de avaliação fixa com o último estado conhecido, se houver
471
+ if (data.last_simplified_state && data.last_simplified_state['Fphen(t)']) {
472
+ updateFixedEvaluationData(data.last_simplified_state['Fphen(t)']);
473
+ }
474
+ // Cria o gráfico de histórico com os dados carregados
475
+ if (historyLabels.length > 0) {
476
+ const ctxLine = document.getElementById('expressiveHistoryChart').getContext('2d');
477
+ expressiveHistoryChart = new Chart(ctxLine, {
478
+ type: 'line',
479
+ data: {
480
+ labels: historyLabels,
481
+ datasets: [
482
+ {
483
+ label: 'Afetuosidade',
484
+ data: historyAfetuosidade,
485
+ borderColor: 'rgba(255, 99, 132, 1)',
486
+ backgroundColor: 'rgba(255, 99, 132, 0.2)',
487
+ fill: false,
488
+ tension: 0.1
489
+ },
490
+ {
491
+ label: 'Variância',
492
+ data: historyVariancia,
493
+ borderColor: 'rgba(54, 162, 235, 1)',
494
+ backgroundColor: 'rgba(54, 162, 235, 0.2)',
495
+ fill: false,
496
+ tension: 0.1
497
+ },
498
+ {
499
+ label: 'Expressividade Geral',
500
+ data: historyExpressividade,
501
+ borderColor: 'rgba(75, 192, 192, 1)',
502
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
503
+ fill: false,
504
+ tension: 0.1
505
+ }
506
+ ]
507
+ },
508
+ options: {
509
+ responsive: true,
510
+ maintainAspectRatio: false,
511
+ scales: {
512
+ x: {
513
+ grid: { color: '#555' },
514
+ ticks: { color: '#e0e0e0' }
515
+ },
516
+ y: {
517
+ beginAtZero: true,
518
+ max: 1.0,
519
+ grid: { color: '#555' },
520
+ ticks: { color: '#e0e0e0' }
521
+ }
522
+ },
523
+ plugins: {
524
+ legend: {
525
+ labels: {
526
+ color: '#e0e0e0'
527
+ }
528
+ },
529
+ title: {
530
+ display: false
531
+ }
532
+ }
533
+ }
534
+ });
535
+ } else {
536
+ // Inicializa o gráfico vazio se não houver histórico
537
+ const ctxLine = document.getElementById('expressiveHistoryChart').getContext('2d');
538
+ expressiveHistoryChart = new Chart(ctxLine, {
539
+ type: 'line',
540
+ data: { labels: [], datasets: [] },
541
+ options: {
542
+ responsive: true,
543
+ maintainAspectRatio: false,
544
+ scales: {
545
+ x: { grid: { color: '#555' }, ticks: { color: '#e0e0e0' } },
546
+ y: { beginAtZero: true, max: 1.0, grid: { color: '#555' }, ticks: { color: '#e0e0e0' } }
547
+ },
548
+ plugins: { legend: { labels: { color: '#e0e0e0' } }, title: { display: false } }
549
+ }
550
+ });
551
+ }
552
+ })
553
+ .catch(error => console.error('Erro ao carregar histórico inicial:', error));
554
+ };
555
+ </script>
556
+ </body>
557
+ </html>
templates/mach5_new_chat.html ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>🎓 SU - Atendente Virtual da SUPAC</title>
6
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ margin: 0;
11
+ background-color: #1a1a1a;
12
+ color: #e0e0e0;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ min-height: 100vh;
17
+ }
18
+ .chat-container {
19
+ max-width: 700px;
20
+ width: 100%;
21
+ background-color: #333;
22
+ padding: 25px;
23
+ border: 1px solid #555;
24
+ border-radius: 10px;
25
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.6);
26
+ display: flex;
27
+ flex-direction: column;
28
+ min-height: 70vh;
29
+ max-height: 90vh;
30
+ overflow: hidden;
31
+ position: relative;
32
+ }
33
+ h1 {
34
+ color: #FF0000;
35
+ text-align: center;
36
+ margin-bottom: 15px;
37
+ text-shadow: 0 0 8px rgba(255, 0, 0, 0.5);
38
+ font-size: 2.2em;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ gap: 10px;
43
+ }
44
+ .su-avatar-container {
45
+ width: 80px;
46
+ height: 80px;
47
+ border-radius: 50%;
48
+ background-color: #FF0000;
49
+ display: flex;
50
+ justify-content: center;
51
+ align-items: center;
52
+ border: 2px solid #FF0000;
53
+ box-shadow: 0 0 10px rgba(255, 0, 0, 0.7);
54
+ }
55
+ .su-avatar {
56
+ width: 90%;
57
+ height: 90%;
58
+ border-radius: 50%;
59
+ object-fit: contain;
60
+ }
61
+ .su-title-text {
62
+ display: flex;
63
+ flex-direction: column;
64
+ align-items: flex-start;
65
+ }
66
+ .su-main-name {
67
+ font-size: 1.5em;
68
+ color: #FF0000;
69
+ }
70
+ .su-subtitle {
71
+ font-size: 0.6em;
72
+ color: #ccc;
73
+ margin-top: -5px;
74
+ }
75
+ .chat-history {
76
+ flex-grow: 1;
77
+ overflow-y: auto;
78
+ padding: 15px;
79
+ background-color: #2b2b2b;
80
+ border-radius: 8px;
81
+ border: 1px solid #444;
82
+ margin-bottom: 15px;
83
+ }
84
+ .chat-history div {
85
+ margin: 12px 0;
86
+ padding: 10px 15px;
87
+ border-radius: 6px;
88
+ line-height: 1.5;
89
+ }
90
+ .user-message {
91
+ font-weight: bold;
92
+ color: #FF6347;
93
+ background-color: #3a3a3a;
94
+ align-self: flex-end;
95
+ text-align: right;
96
+ margin-left: 20%;
97
+ }
98
+ .su-message {
99
+ color: #FFFF99;
100
+ background-color: #4a4a4a;
101
+ align-self: flex-start;
102
+ text-align: left;
103
+ white-space: pre-wrap;
104
+ margin-right: 20%;
105
+ }
106
+ .input-area {
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 15px;
110
+ padding-top: 15px;
111
+ }
112
+ .input-and-button {
113
+ display: flex;
114
+ gap: 15px;
115
+ }
116
+ input[type="text"] {
117
+ flex-grow: 1;
118
+ padding: 12px;
119
+ border: 1px solid #FF0000;
120
+ border-radius: 6px;
121
+ background-color: #222;
122
+ color: #e0e0e0;
123
+ font-size: 1.1em;
124
+ outline: none;
125
+ }
126
+ input[type="text"]::placeholder {
127
+ color: #aaa;
128
+ }
129
+ input[type="submit"] {
130
+ padding: 12px 25px;
131
+ background-color: #FF0000;
132
+ color: #FFFFFF;
133
+ border: none;
134
+ border-radius: 6px;
135
+ cursor: pointer;
136
+ font-weight: bold;
137
+ font-size: 1.1em;
138
+ transition: background-color 0.3s ease, transform 0.2s ease;
139
+ }
140
+ input[type="submit"]:hover {
141
+ background-color: #CC0000;
142
+ transform: translateY(-2px);
143
+ }
144
+ .su-message.thinking-message {
145
+ color: #ccc;
146
+ font-style: italic;
147
+ }
148
+ .footer-content {
149
+ display: flex;
150
+ justify-content: space-between;
151
+ align-items: flex-end;
152
+ margin-top: 15px;
153
+ width: 100%;
154
+ }
155
+ .logo-footer {
156
+ width: 100px;
157
+ height: auto;
158
+ max-height: 40px;
159
+ filter: grayscale(100%) brightness(150%);
160
+ }
161
+ .footer-text-container {
162
+ display: flex;
163
+ flex-direction: column;
164
+ align-items: flex-end;
165
+ text-align: right;
166
+ flex-grow: 1;
167
+ }
168
+ .disclaimer-text {
169
+ font-size: 0.8em;
170
+ color: #bbb;
171
+ margin-bottom: 5px;
172
+ }
173
+ .disclaimer-text a {
174
+ color: #FF0000;
175
+ text-decoration: none;
176
+ }
177
+ .disclaimer-text a:hover {
178
+ text-decoration: underline;
179
+ }
180
+ .credits-text {
181
+ font-size: 0.65em;
182
+ color: #888;
183
+ }
184
+ </style>
185
+ </head>
186
+ <body>
187
+ <div class="chat-container">
188
+ <h1>
189
+ <div class="su-avatar-container">
190
+ <img src="su_sorrindo.jpeg" alt="SU Avatar" class="su-avatar">
191
+ </div>
192
+ <div class="su-title-text">
193
+ <span class="su-main-name">SU</span>
194
+ <span class="su-subtitle">Atendente Virtual da SUPAC</span>
195
+ </div>
196
+ </h1>
197
+ <div class="chat-history" id="chat-history">
198
+ </div>
199
+
200
+ <div class="input-area">
201
+ <div class="input-and-button">
202
+ <input type="text" id="message-input" placeholder="Pergunte sobre o SIGAA..." />
203
+ <input type="submit" value="Enviar" id="send-button" />
204
+ </div>
205
+ <div class="footer-content">
206
+ <img src="logo_supac.png" alt="Logo SUPAC" class="logo-footer">
207
+ <div class="footer-text-container">
208
+ <p class="disclaimer-text">A Su ainda está em treinamento! Em caso de dúvidas, acesse o site da SUPAC: <a href="https://supac.ufba.br/" target="_blank">https://supac.ufba.br/</a></p>
209
+ <p class="credits-text">Essa assistente foi criada pelo Tecno Modelo baseado e t-gens. By Nilton Souza</p>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ <script>
216
+ let sessionId = "{{ session_id }}";
217
+
218
+ function getCookie(name) {
219
+ const nameEQ = name + "=";
220
+ const ca = document.cookie.split(';');
221
+ for(let i=0; i < ca.length; i++) {
222
+ let c = ca[i];
223
+ while (c.charAt(0) === ' ') c = c.substring(1, c.length);
224
+ if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
225
+ }
226
+ return null;
227
+ }
228
+
229
+ document.getElementById('send-button').addEventListener('click', sendMessage);
230
+ document.getElementById('message-input').addEventListener('keypress', function(e) {
231
+ if (e.key === 'Enter') {
232
+ sendMessage();
233
+ }
234
+ });
235
+
236
+ function sendMessage() {
237
+ const input = document.getElementById('message-input');
238
+ const message = input.value.trim();
239
+ if (!message) return;
240
+
241
+ const chatHistory = document.getElementById('chat-history');
242
+
243
+ const userMessageDiv = document.createElement('div');
244
+ userMessageDiv.className = 'user-message';
245
+ userMessageDiv.textContent = `Você: ${message}`;
246
+ chatHistory.appendChild(userMessageDiv);
247
+
248
+ input.value = '';
249
+ chatHistory.scrollTop = chatHistory.scrollHeight;
250
+
251
+ const thinkingMessageDiv = document.createElement('div');
252
+ thinkingMessageDiv.className = 'su-message thinking-message';
253
+ thinkingMessageDiv.textContent = `SU: Pensando...`;
254
+ chatHistory.appendChild(thinkingMessageDiv);
255
+ chatHistory.scrollTop = chatHistory.scrollHeight;
256
+
257
+ fetch('/chat_new', {
258
+ method: 'POST',
259
+ headers: {
260
+ 'Content-Type': 'application/json'
261
+ },
262
+ body: JSON.stringify({ message: message, session_id: sessionId })
263
+ })
264
+ .then(response => response.json())
265
+ .then(data => {
266
+ chatHistory.removeChild(thinkingMessageDiv);
267
+
268
+ const suMessageDiv = document.createElement('div');
269
+ suMessageDiv.className = 'su-message';
270
+ suMessageDiv.textContent = `SU: ${data.response}`;
271
+ chatHistory.appendChild(suMessageDiv);
272
+
273
+ chatHistory.scrollTop = chatHistory.scrollHeight;
274
+ })
275
+ .catch(error => {
276
+ console.error('Erro ao enviar mensagem:', error);
277
+ if (thinkingMessageDiv.parentNode === chatHistory) {
278
+ chatHistory.removeChild(thinkingMessageDiv);
279
+ }
280
+ const errorMessageDiv = document.createElement('div');
281
+ errorMessageDiv.className = 'su-message';
282
+ errorMessageDiv.textContent = `SU: [ERRO] Não consegui responder. Por favor, tente novamente.`;
283
+ chatHistory.appendChild(errorMessageDiv);
284
+ chatHistory.scrollTop = chatHistory.scrollHeight;
285
+ });
286
+ }
287
+
288
+ window.onload = function() {
289
+ if (!sessionId || sessionId === "None") {
290
+ console.error("Session ID não encontrado no template. Verifique a passagem de contexto do Flask.");
291
+ return;
292
+ }
293
+
294
+ fetch('/chat_history', {
295
+ method: 'POST',
296
+ headers: { 'Content-Type': 'application/json' },
297
+ body: JSON.stringify({ session_id: sessionId })
298
+ })
299
+ .then(response => response.json())
300
+ .then(data => {
301
+ const chatHistory = document.getElementById('chat-history');
302
+ data.memoria.forEach(item => {
303
+ const userMessageDiv = document.createElement('div');
304
+ userMessageDiv.className = 'user-message';
305
+ userMessageDiv.textContent = `Você: ${item.input}`;
306
+ chatHistory.appendChild(userMessageDiv);
307
+
308
+ const suMessageDiv = document.createElement('div');
309
+ suMessageDiv.className = 'su-message';
310
+ suMessageDiv.textContent = `SU: ${item.resposta}`;
311
+ chatHistory.appendChild(suMessageDiv);
312
+ });
313
+ chatHistory.scrollTop = chatHistory.scrollHeight;
314
+ })
315
+ .catch(error => console.error('Erro ao carregar histórico inicial:', error));
316
+ };
317
+ </script>
318
+ </body>
319
+ </html>
320
+