tuliodisanto commited on
Commit
f67aa61
·
verified ·
1 Parent(s): 0e93b1d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +42 -51
app.py CHANGED
@@ -1,12 +1,12 @@
1
- # app.py (Versão Corrigida com Sintaxe Válida)
2
  ###################################################################################################
3
  #
4
- # CORREÇÃO:
5
- # - A variável 'user_prompt' na função 'get_ai_suggestion' foi reescrita com aspas triplas (""")
6
- # para corrigir o erro de sintaxe 'SyntaxError: EOL while scanning string literal'. Isso
7
- # garante que a string de múltiplas linhas seja formatada corretamente.
8
- #
9
- # O restante do arquivo permanece idêntico, com todas as funcionalidades completas.
10
  #
11
  ###################################################################################################
12
 
@@ -45,7 +45,7 @@ else:
45
  print("--- [SUCESSO] Cliente de Inferência da IA configurado.")
46
 
47
  # Configuração do Repositório Hugging Face
48
- HF_TOKEN = os.environ.get("HF_TOKEN")
49
  REPO_ID = "tuliodisanto/Buscador_Rol_vs.2_IA"
50
  if not HF_TOKEN:
51
  print("--- [AVISO CRÍTICO] Secret 'HF_TOKEN' não encontrado. Os arquivos não serão salvos no repositório. ---")
@@ -80,7 +80,7 @@ def load_user_feedback():
80
  query_norm, tuss_code = row.get('query_normalized', ''), row.get('tuss_code_submitted', '')
81
  if query_norm and tuss_code:
82
  USER_BEST_MATCHES_COUNTS[query_norm][tuss_code] += 1
83
- print(f"--- [SUCESSO] Feedback de usuário carregado. {len(USER_BEST_MATCHES_COUNTS)} queries com feedback. ---")
84
  except Exception as e: print(f"--- [ERRO] Falha ao carregar feedback: {e} ---"); traceback.print_exc()
85
 
86
  def commit_file_to_repo(local_file_name, commit_message):
@@ -114,25 +114,32 @@ atexit.register(save_data_on_exit)
114
 
115
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
116
  try:
117
- from enhanced_search_v2 import load_and_prepare_database, load_correction_corpus, search_procedure_with_log
118
  print("--- [SUCESSO] Módulo 'enhanced_search_v2.py' importado. ---")
119
- except Exception as e:
120
  print(f"--- [ERRO CRÍTICO] Não foi possível importar 'enhanced_search_v2.py': {e} ---"); traceback.print_exc(); sys.exit(1)
121
 
122
  app = Flask(__name__)
123
 
124
- # Declaração das variáveis globais que serão preenchidas na inicialização
125
  DF_ORIGINAL, DF_NORMALIZED, FUZZY_CORPUS, BM25_MODEL, DOC_FREQ = (None, None, None, None, {})
126
- CORRECTION_CORPUS = ([], [], set())
 
127
  CROSS_ENCODER_MODEL = None
128
 
129
  try:
130
  db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'rol_procedures_database.csv')
131
  DF_ORIGINAL, DF_NORMALIZED, FUZZY_CORPUS, BM25_MODEL, DOC_FREQ = load_and_prepare_database(db_path)
132
-
133
  dict_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Dic.csv')
134
- CORRECTION_CORPUS = load_correction_corpus(dict_path, column_name='Termo_Correto')
135
-
 
 
 
 
 
 
136
  load_user_feedback()
137
 
138
  print("\n--- [SETUP] Carregando modelo Cross-Encoder... ---")
@@ -146,11 +153,11 @@ except Exception as e:
146
  # --- Bloco 4: Definição dos Endpoints da API ---
147
 
148
  @app.route('/')
149
- def index():
150
  return render_template('index.html')
151
 
152
  @app.route('/favicon.ico')
153
- def favicon():
154
  return '', 204
155
 
156
  @app.route('/search', methods=['POST'])
@@ -159,13 +166,14 @@ def search():
159
  try:
160
  data = request.get_json()
161
  query = data.get('query', '').strip()
162
-
163
  results = search_procedure_with_log(
164
- query=query,
165
- df_original=DF_ORIGINAL,
166
- df_normalized=DF_NORMALIZED,
167
  fuzzy_search_corpus=FUZZY_CORPUS,
168
- correction_corpus=CORRECTION_CORPUS,
 
169
  bm25_model=BM25_MODEL,
170
  doc_freq=DOC_FREQ,
171
  cross_encoder_model=CROSS_ENCODER_MODEL,
@@ -185,7 +193,7 @@ def submit_feedback_route():
185
  data = request.get_json()
186
  query, tuss_code_submitted = data.get('query'), data.get('tuss_code')
187
  if not query or not tuss_code_submitted: return jsonify({"status": "error", "message": "Dados incompletos."}), 400
188
-
189
  file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), USER_FEEDBACK_FILE)
190
  with open(file_path, 'a', newline='', encoding='utf-8') as f:
191
  writer = csv.writer(f)
@@ -194,12 +202,12 @@ def submit_feedback_route():
194
  tuss_desc_assoc = " | ".join(matching_rows['Descricao_TUSS'].unique()) if not matching_rows.empty else 'Não encontrado'
195
  rol_names_assoc = " | ".join(matching_rows['Procedimento_Rol'].unique()) if not matching_rows.empty else 'Não encontrado'
196
  writer.writerow([datetime.datetime.now().isoformat(), query, query_normalized, tuss_code_submitted, '', tuss_desc_assoc, rol_names_assoc, 'confirm_result'])
197
-
198
  DATA_HAS_CHANGED = True
199
  print(f"--- [DADOS] Feedback recebido para a query '{query}'. Commit agendado. ---")
200
  load_user_feedback()
201
  return jsonify({"status": "success", "message": "Feedback recebido!"}), 200
202
- except Exception as e:
203
  print("--- [ERRO NO SUBMIT_FEEDBACK] ---"); traceback.print_exc();
204
  return jsonify({"status": "error", "message": "Erro interno."}), 500
205
 
@@ -231,34 +239,18 @@ def get_ai_suggestion():
231
  unique_id = f"{r.get('Codigo_TUSS')}_{sha1(str(r.get('Procedimento_Rol', '')).encode('utf-8')).hexdigest()[:8]}"
232
  pruned_result = {'unique_id': unique_id, **{key: r.get(key) for key in RELEVANT_KEYS_FOR_AI if r.get(key) and pd.notna(r.get(key))}}
233
  if 'Codigo_TUSS' in pruned_result: simplified_results.append(pruned_result)
234
-
235
  formatted_results_str = json.dumps(simplified_results, indent=2, ensure_ascii=False)
236
- system_prompt = (
237
- "Você é um especialista em terminologia de procedimentos médicos do Brasil (Tabela TUSS e Rol da ANS). "
238
- "Sua tarefa é analisar uma lista de procedimentos e escolher os 3 que melhor correspondem à consulta do usuário, em ordem de relevância."
239
- )
240
-
241
- # --- CORREÇÃO APLICADA AQUI ---
242
- # A string de múltiplas linhas agora usa aspas triplas (""") para evitar o SyntaxError.
243
  user_prompt = f"""Consulta do usuário: "{query}"
244
 
245
  ### Resultados da Busca para Análise (JSON):
246
  {formatted_results_str}
247
 
248
  ### Sua Tarefa:
249
- 1. **Pense em voz alta:** Dentro de uma tag `<thought>`, explique seu processo de raciocínio passo a passo. Analise a consulta e compare os resultados, justificando por que um é mais relevante que o outro com base em seu `Procedimento_Rol` e outros campos.
250
- 2. **Forneça a resposta final:** Após a tag `<thought>`, seu único resultado deve ser um bloco de código JSON. Este JSON **DEVE** conter uma chave `suggested_ids` com uma lista de **EXATAMENTE 3 strings** do campo `unique_id` que você selecionou, ordenadas da mais para a menos relevante.
251
-
252
- **EXEMPLO DE RESPOSTA OBRIGATÓRIA:**
253
- <thought>
254
- O Raciocínio da IA fica aqui...
255
- </thought>
256
- ```json
257
- {{
258
- "suggested_ids": ["30602122_abc12345", "30602360_def67890", "30602033_ghi11223"]
259
- }}
260
- ```"""
261
-
262
  completion = client_ia.chat.completions.create( model="baidu/ERNIE-4.5-21B-A3B-PT", messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}], max_tokens=1500, temperature=0.1 )
263
  raw_response = completion.choices[0].message.content.strip()
264
 
@@ -274,13 +266,12 @@ O Raciocínio da IA fica aqui...
274
  start = raw_response.find("```json") + len("```json")
275
  end = raw_response.rfind("```")
276
  json_str = raw_response[start:end].strip()
277
- try:
278
- json_part = json.loads(json_str)
279
  except json.JSONDecodeError: pass
280
-
281
  if not json_part or "suggested_ids" not in json_part or not isinstance(json_part.get("suggested_ids"), list):
282
  return jsonify({ "error": "A IA não retornou a lista de 'suggested_ids' no formato esperado.", "details": raw_response }), 422
283
-
284
  return jsonify({ "suggested_ids": json_part["suggested_ids"][:3], "thought_process": thought_process })
285
 
286
  except Exception as e:
 
1
+ # app.py (Versão Final Absolutamente Completa)
2
  ###################################################################################################
3
  #
4
+ # Este arquivo serve como a interface da aplicação (API/backend). Ele é responsável por:
5
+ # 1. Carregar os modelos de IA e a base de dados na inicialização.
6
+ # 2. Carregar e unificar todos os dicionários para a correção ortográfica.
7
+ # 3. Expor todos os endpoints da API: /search, /submit_feedback, /get_tuss_info, /get_ai_suggestion.
8
+ # 4. Chamar o motor de busca (enhanced_search_v2.py) com os parâmetros corretos.
9
+ # 5. Gerenciar o feedback do usuário e a persistência dos dados no Hugging Face Hub.
10
  #
11
  ###################################################################################################
12
 
 
45
  print("--- [SUCESSO] Cliente de Inferência da IA configurado.")
46
 
47
  # Configuração do Repositório Hugging Face
48
+ HF_TOKEN = os.environ.get("HF_TOKEN")
49
  REPO_ID = "tuliodisanto/Buscador_Rol_vs.2_IA"
50
  if not HF_TOKEN:
51
  print("--- [AVISO CRÍTICO] Secret 'HF_TOKEN' não encontrado. Os arquivos não serão salvos no repositório. ---")
 
80
  query_norm, tuss_code = row.get('query_normalized', ''), row.get('tuss_code_submitted', '')
81
  if query_norm and tuss_code:
82
  USER_BEST_MATCHES_COUNTS[query_norm][tuss_code] += 1
83
+ print(f"--- [SUCESSO] Feedback de usuário carregado. {len(USER_BEST_MATCHES_COUNTS)} queries com feedback.")
84
  except Exception as e: print(f"--- [ERRO] Falha ao carregar feedback: {e} ---"); traceback.print_exc()
85
 
86
  def commit_file_to_repo(local_file_name, commit_message):
 
114
 
115
  sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
116
  try:
117
+ from enhanced_search_v2 import load_and_prepare_database, load_correction_corpus, load_general_dictionary, search_procedure_with_log
118
  print("--- [SUCESSO] Módulo 'enhanced_search_v2.py' importado. ---")
119
+ except Exception as e:
120
  print(f"--- [ERRO CRÍTICO] Não foi possível importar 'enhanced_search_v2.py': {e} ---"); traceback.print_exc(); sys.exit(1)
121
 
122
  app = Flask(__name__)
123
 
124
+ # Declaração das variáveis globais
125
  DF_ORIGINAL, DF_NORMALIZED, FUZZY_CORPUS, BM25_MODEL, DOC_FREQ = (None, None, None, None, {})
126
+ CORRECTION_CORPUS = ([], [])
127
+ VALID_WORDS_SET = set()
128
  CROSS_ENCODER_MODEL = None
129
 
130
  try:
131
  db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'rol_procedures_database.csv')
132
  DF_ORIGINAL, DF_NORMALIZED, FUZZY_CORPUS, BM25_MODEL, DOC_FREQ = load_and_prepare_database(db_path)
133
+
134
  dict_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Dic.csv')
135
+ original_terms, normalized_terms, db_word_set = load_correction_corpus(dict_path, column_name='Termo_Correto')
136
+ CORRECTION_CORPUS = (original_terms, normalized_terms)
137
+
138
+ general_dict_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dicionario_ptbr.txt')
139
+ portuguese_word_set = load_general_dictionary(general_dict_path)
140
+ VALID_WORDS_SET = db_word_set.union(portuguese_word_set)
141
+ print(f"--- [SUCESSO] Dicionário unificado criado com {len(VALID_WORDS_SET)} palavras válidas. ---")
142
+
143
  load_user_feedback()
144
 
145
  print("\n--- [SETUP] Carregando modelo Cross-Encoder... ---")
 
153
  # --- Bloco 4: Definição dos Endpoints da API ---
154
 
155
  @app.route('/')
156
+ def index():
157
  return render_template('index.html')
158
 
159
  @app.route('/favicon.ico')
160
+ def favicon():
161
  return '', 204
162
 
163
  @app.route('/search', methods=['POST'])
 
166
  try:
167
  data = request.get_json()
168
  query = data.get('query', '').strip()
169
+
170
  results = search_procedure_with_log(
171
+ query=query,
172
+ df_original=DF_ORIGINAL,
173
+ df_normalized=DF_NORMALIZED,
174
  fuzzy_search_corpus=FUZZY_CORPUS,
175
+ correction_corpus=CORRECTION_CORPUS,
176
+ valid_words_set=VALID_WORDS_SET,
177
  bm25_model=BM25_MODEL,
178
  doc_freq=DOC_FREQ,
179
  cross_encoder_model=CROSS_ENCODER_MODEL,
 
193
  data = request.get_json()
194
  query, tuss_code_submitted = data.get('query'), data.get('tuss_code')
195
  if not query or not tuss_code_submitted: return jsonify({"status": "error", "message": "Dados incompletos."}), 400
196
+
197
  file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), USER_FEEDBACK_FILE)
198
  with open(file_path, 'a', newline='', encoding='utf-8') as f:
199
  writer = csv.writer(f)
 
202
  tuss_desc_assoc = " | ".join(matching_rows['Descricao_TUSS'].unique()) if not matching_rows.empty else 'Não encontrado'
203
  rol_names_assoc = " | ".join(matching_rows['Procedimento_Rol'].unique()) if not matching_rows.empty else 'Não encontrado'
204
  writer.writerow([datetime.datetime.now().isoformat(), query, query_normalized, tuss_code_submitted, '', tuss_desc_assoc, rol_names_assoc, 'confirm_result'])
205
+
206
  DATA_HAS_CHANGED = True
207
  print(f"--- [DADOS] Feedback recebido para a query '{query}'. Commit agendado. ---")
208
  load_user_feedback()
209
  return jsonify({"status": "success", "message": "Feedback recebido!"}), 200
210
+ except Exception as e:
211
  print("--- [ERRO NO SUBMIT_FEEDBACK] ---"); traceback.print_exc();
212
  return jsonify({"status": "error", "message": "Erro interno."}), 500
213
 
 
239
  unique_id = f"{r.get('Codigo_TUSS')}_{sha1(str(r.get('Procedimento_Rol', '')).encode('utf-8')).hexdigest()[:8]}"
240
  pruned_result = {'unique_id': unique_id, **{key: r.get(key) for key in RELEVANT_KEYS_FOR_AI if r.get(key) and pd.notna(r.get(key))}}
241
  if 'Codigo_TUSS' in pruned_result: simplified_results.append(pruned_result)
242
+
243
  formatted_results_str = json.dumps(simplified_results, indent=2, ensure_ascii=False)
244
+ system_prompt = ( "Você é um especialista em terminologia de procedimentos médicos do Brasil (Tabela TUSS e Rol da ANS). " "Sua tarefa é analisar uma lista de procedimentos e escolher os 3 que melhor correspondem à consulta do usuário, em ordem de relevância." )
 
 
 
 
 
 
245
  user_prompt = f"""Consulta do usuário: "{query}"
246
 
247
  ### Resultados da Busca para Análise (JSON):
248
  {formatted_results_str}
249
 
250
  ### Sua Tarefa:
251
+ 1. **Pense em voz alta:** Dentro de uma tag `<thought>`, explique seu processo de raciocínio passo a passo.
252
+ 2. **Forneça a resposta final:** Após a tag `<thought>`, seu único resultado deve ser um bloco de código JSON contendo uma chave `suggested_ids` com uma lista de **EXATAMENTE 3 strings** do campo `unique_id` que você selecionou, ordenadas da mais para a menos relevante."""
253
+
 
 
 
 
 
 
 
 
 
 
254
  completion = client_ia.chat.completions.create( model="baidu/ERNIE-4.5-21B-A3B-PT", messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}], max_tokens=1500, temperature=0.1 )
255
  raw_response = completion.choices[0].message.content.strip()
256
 
 
266
  start = raw_response.find("```json") + len("```json")
267
  end = raw_response.rfind("```")
268
  json_str = raw_response[start:end].strip()
269
+ try: json_part = json.loads(json_str)
 
270
  except json.JSONDecodeError: pass
271
+
272
  if not json_part or "suggested_ids" not in json_part or not isinstance(json_part.get("suggested_ids"), list):
273
  return jsonify({ "error": "A IA não retornou a lista de 'suggested_ids' no formato esperado.", "details": raw_response }), 422
274
+
275
  return jsonify({ "suggested_ids": json_part["suggested_ids"][:3], "thought_process": thought_process })
276
 
277
  except Exception as e: