Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
-
# app.py (Versão
|
| 2 |
###################################################################################################
|
| 3 |
#
|
| 4 |
-
#
|
| 5 |
-
#
|
| 6 |
-
#
|
| 7 |
-
#
|
| 8 |
-
#
|
| 9 |
-
#
|
| 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
|
| 125 |
DF_ORIGINAL, DF_NORMALIZED, FUZZY_CORPUS, BM25_MODEL, DOC_FREQ = (None, None, None, None, {})
|
| 126 |
-
CORRECTION_CORPUS = ([], []
|
|
|
|
| 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 |
-
|
| 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.
|
| 250 |
-
2. **Forneça a resposta final:** Após a tag `<thought>`, seu único resultado deve ser um bloco de código JSON
|
| 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:
|