caarleexx commited on
Commit
6ec9cf3
·
verified ·
1 Parent(s): 0cd2dcb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -437
app.py CHANGED
@@ -1,438 +1,3 @@
1
-
2
- import os
3
- import json
4
- import time
5
- import copy
6
- import logging
7
- import requests
8
- import urllib3
9
- from flask import Flask, request, jsonify
10
- from playwright.sync_api import sync_playwright
11
-
12
- urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
13
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
14
- logger = logging.getLogger(__name__)
15
- app = Flask(__name__)
16
-
17
- # ============================================
18
- # PAYLOADS STF (igual app-7.py)
19
- # ============================================
20
- PAYLOAD_COMPLETO = {
21
- "query": {
22
- "function_score": {
23
- "functions": [
24
- {"exp": {"julgamento_data": {"origin": "now", "scale": "47450d", "offset": "1095d", "decay": 0.1}}}
25
- ],
26
- "query": {
27
- "bool": {
28
- "filter": [
29
- {"query_string": {
30
- "default_operator": "AND",
31
- "fields": [
32
- "processo_codigo_completo.plural",
33
- "acordao_ata.plural^3",
34
- "ementa_texto.plural^3",
35
- "decisao_texto.plural^2",
36
- "documental_tese_texto.plural^3",
37
- "documental_tese_tema_texto.plural^3",
38
- "titulo.plural^6",
39
- "sumula_texto.plural^3",
40
- "ramo_direito.plural^1"
41
- ],
42
- "query": "*",
43
- "type": "cross_fields",
44
- "fuzziness": "AUTO:4,7"
45
- }}
46
- ]
47
- }
48
- }
49
- }
50
- },
51
- "_source": [
52
- "id", "titulo", "ementa_texto", "acordao_ata", "decisao_texto",
53
- "documental_tese_texto", "orgao_julgador", "relator_processo_nome",
54
- "julgamento_data", "processo_codigo_completo",
55
- "inteiro_teor_url", "acompanhamento_processual_url", "dje_url",
56
- "processo_classe_processual_unificada_extenso", "ministro_facet"
57
- ],
58
- "highlight": {
59
- "fields": {
60
- "ementa_texto": {"matched_fields": ["ementa_texto.plural"], "type": "fvh", "fragment_size": 24000},
61
- "acordao_ata": {"matched_fields": ["acordao_ata.plural"], "type": "fvh", "fragment_size": 600},
62
- "decisao_texto": {"matched_fields": ["decisao_texto.plural"], "type": "fvh", "fragment_size": 1200}
63
- },
64
- "pre_tags": ["<em>"],
65
- "post_tags": ["</em>"],
66
- "fragment_size": 300,
67
- "number_of_fragments": 64,
68
- "order": "score"
69
- },
70
- "size": 100,
71
- "from": 0,
72
- "sort": [{"julgamento_data": {"order": "desc"}}],
73
- "track_total_hits": True
74
- }
75
-
76
- PAYLOAD_SEM_HIGHLIGHT = {
77
- "query": PAYLOAD_COMPLETO["query"],
78
- "_source": PAYLOAD_COMPLETO["_source"],
79
- "size": 100,
80
- "from": 0,
81
- "sort": [{"julgamento_data": {"order": "desc"}}],
82
- "track_total_hits": True
83
- }
84
-
85
- PAYLOAD_MINIMO = {
86
- "query": {
87
- "bool": {
88
- "filter": [{"term": {"base": "acordaos"}}]
89
- }
90
- },
91
- "_source": ["id", "titulo", "ementa_texto", "processo_numero", "julgamento_data", "relator_processo_nome", "inteiro_teor_url"],
92
- "size": 100,
93
- "sort": [{"julgamento_data": {"order": "desc"}}],
94
- "track_total_hits": True
95
- }
96
-
97
- # Constantes
98
- URL_API = "https://jurisprudencia.stf.jus.br/api/search/search"
99
- HEADERS = {
100
- "Accept": "application/json, text/plain, */*",
101
- "Content-Type": "application/json",
102
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
103
- "Referer": "https://jurisprudencia.stf.jus.br/pages/search",
104
- "Origin": "https://jurisprudencia.stf.jus.br"
105
- }
106
- token_cache = {"token": None, "expires_at": 0}
107
-
108
- # ============================================
109
- # FUNÇÕES CORE (igual app-7.py)
110
- # ============================================
111
- def get_fresh_token():
112
- global token_cache
113
- if token_cache["token"] and time.time() < token_cache["expires_at"]:
114
- logger.info("Usando token em cache")
115
- return token_cache["token"]
116
-
117
- logger.info("Obtendo novo token via Playwright")
118
- try:
119
- with sync_playwright() as p:
120
- browser = p.chromium.launch(headless=True, args=["--no-sandbox"])
121
- context = browser.new_context(
122
- viewport={"width": 1920, "height": 1080},
123
- user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
124
- )
125
- page = context.new_page()
126
- page.goto("https://jurisprudencia.stf.jus.br/pages/search",
127
- wait_until="domcontentloaded", timeout=30000)
128
- page.wait_for_timeout(3000)
129
- cookies = context.cookies()
130
- token = None
131
- for cookie in cookies:
132
- if cookie.get("name") == "aws-waf-token":
133
- token = cookie.get("value")
134
- break
135
- browser.close()
136
-
137
- if token:
138
- token_cache["token"] = token
139
- token_cache["expires_at"] = time.time() + 3300
140
- logger.info(f"Token obtido: {token[:30]}...")
141
- return token
142
- else:
143
- logger.warning("Token não encontrado nos cookies")
144
- return None
145
- except Exception as e:
146
- logger.error(f"Erro ao obter token: {str(e)}")
147
- return None
148
-
149
-
150
- def search_with_token(token, payload):
151
- """
152
- Busca com token. Retorna {"success": True/False, "data": {...}, "status": code}
153
- HTTP 200 = sucesso
154
- HTTP 403 = token expirado (limpa cache)
155
- HTTP 202 = token inválido/não aceito (não é sucesso!)
156
- Outros = erro
157
- """
158
- if not token:
159
- return {"success": False, "error": "Token não fornecido"}
160
-
161
- headers = HEADERS.copy()
162
- headers["Cookie"] = f"aws-waf-token={token}"
163
-
164
- try:
165
- logger.debug(f"Enviando payload: {json.dumps(payload)[:200]}...")
166
- response = requests.post(URL_API, headers=headers, json=payload, verify=False, timeout=30)
167
- logger.info(f"Resposta: status {response.status_code}")
168
-
169
- if response.status_code == 200:
170
- return {"success": True, "data": response.json()}
171
- elif response.status_code == 403:
172
- token_cache["token"] = None # Token expirado
173
- return {"success": False, "error": "Token expirado", "status": 403}
174
- elif response.status_code == 202:
175
- # HTTP 202 = Accepted mas token não funcionou (WAF bloqueio)
176
- return {"success": False, "error": "Token não aceito (HTTP 202)", "status": 202}
177
- else:
178
- return {"success": False, "error": f"HTTP {response.status_code}",
179
- "status": response.status_code, "text": response.text[:500]}
180
- except Exception as e:
181
- logger.error(f"Erro na requisição: {str(e)}")
182
- return {"success": False, "error": str(e)}
183
-
184
-
185
- def try_search_with_payloads(token, base_payload, sort_order="desc", base_filter=None):
186
- """
187
- Tenta vários payloads em ordem decrescente de complexidade até obter sucesso.
188
- Retorna (success, data, payload_usado)
189
- """
190
- # Payload 1: completo com aggs e highlight
191
- payload1 = base_payload.copy()
192
- payload1["sort"] = [{"julgamento_data": {"order": sort_order}}]
193
- if base_filter:
194
- payload1["post_filter"] = {"bool": {"must": [{"term": {"base": base_filter}}]}}
195
-
196
- logger.info("Tentando payload completo...")
197
- res = search_with_token(token, payload1)
198
- if res.get("success"):
199
- return True, res["data"], "completo"
200
-
201
- # Se falhou com 400, tenta sem highlight e sem aggs
202
- if res.get("status") == 400:
203
- logger.info("Payload completo falhou 400. Tentando payload sem highlight...")
204
- payload2 = PAYLOAD_SEM_HIGHLIGHT.copy()
205
- payload2["sort"] = [{"julgamento_data": {"order": sort_order}}]
206
- if base_filter:
207
- payload2["post_filter"] = {"bool": {"must": [{"term": {"base": base_filter}}]}}
208
-
209
- res2 = search_with_token(token, payload2)
210
- if res2.get("success"):
211
- return True, res2["data"], "sem_highlight"
212
-
213
- if res2.get("status") == 400:
214
- logger.info("Payload sem highlight falhou. Tentando payload mínimo...")
215
- payload3 = PAYLOAD_MINIMO.copy()
216
- payload3["sort"] = [{"julgamento_data": {"order": sort_order}}]
217
- if base_filter:
218
- payload3["post_filter"] = {"bool": {"must": [{"term": {"base": base_filter}}]}}
219
-
220
- res3 = search_with_token(token, payload3)
221
- if res3.get("success"):
222
- return True, res3["data"], "minimo"
223
-
224
- return False, None, None
225
-
226
-
227
- def test_with_playwright_full(payload):
228
- """Último recurso: Playwright com busca direta na API via browser"""
229
- logger.info("Tentando acesso com Playwright...")
230
- try:
231
- with sync_playwright() as p:
232
- browser = p.chromium.launch(headless=True, args=["--no-sandbox"])
233
- context = browser.new_context(
234
- viewport={"width": 1920, "height": 1080},
235
- user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
236
- )
237
- page = context.new_page()
238
- page.goto("https://jurisprudencia.stf.jus.br/pages/search",
239
- wait_until="domcontentloaded", timeout=30000)
240
- page.wait_for_timeout(3000)
241
-
242
- cookies = context.cookies()
243
- token = None
244
- for cookie in cookies:
245
- if cookie.get("name") == "aws-waf-token":
246
- token = cookie.get("value")
247
- break
248
-
249
- api_result = page.evaluate("""
250
- async (payload) => {
251
- try {
252
- const response = await fetch('https://jurisprudencia.stf.jus.br/api/search/search', {
253
- method: 'POST',
254
- headers: {
255
- 'Content-Type': 'application/json',
256
- 'Accept': 'application/json'
257
- },
258
- body: JSON.stringify(payload)
259
- });
260
- if (response.ok) {
261
- return {success: true, data: await response.json()};
262
- } else {
263
- return {success: false, status: response.status};
264
- }
265
- } catch (error) {
266
- return {success: false, error: error.toString()};
267
- }
268
- }
269
- """, payload)
270
-
271
- browser.close()
272
-
273
- if api_result.get("success"):
274
- if token:
275
- token_cache["token"] = token
276
- token_cache["expires_at"] = time.time() + 3300
277
- return {"success": True, "data": api_result.get("data"), "token": token}
278
- else:
279
- return {"success": False, "error": api_result.get("error", "Falha desconhecida"),
280
- "status": api_result.get("status"), "token": token}
281
- except Exception as e:
282
- logger.error(f"Erro no Playwright: {str(e)}")
283
- return {"success": False, "error": str(e)}
284
-
285
-
286
- # ============================================
287
- # ENDPOINT /busca (simplificado para Para.AI)
288
- # ============================================
289
- @app.route("/busca")
290
- def busca():
291
- """
292
- GET /busca?q=termo&page=1&size=20
293
- Endpoint simplificado para integração Para.AI
294
- """
295
- query = request.args.get("q", "").strip()
296
- page = max(1, int(request.args.get("page", 1)))
297
- size = min(int(request.args.get("size", 20)), 100)
298
-
299
- if not query:
300
- return jsonify({"error": "Parâmetro 'q' é obrigatório"}), 400
301
-
302
- # Monta payload base
303
- payload = copy.deepcopy(PAYLOAD_COMPLETO)
304
- payload["query"]["function_score"]["query"]["bool"]["filter"][0]["query_string"]["query"] = query
305
- payload["size"] = size
306
- payload["from"] = (page - 1) * size
307
-
308
- # Obtém token
309
- token = get_fresh_token()
310
- if not token:
311
- return jsonify({"success": False, "error": "Falha ao obter token WAF"}), 500
312
-
313
- # Tenta payloads em cascata
314
- success, data, payload_type = try_search_with_payloads(token, payload, "desc", None)
315
-
316
- if success:
317
- logger.info(f"Busca bem-sucedida com payload {payload_type}")
318
- # Extrai hits (API retorna result.hits.hits)
319
- hits_root = data.get("result", data)
320
- hits = hits_root.get("hits", {})
321
- total = hits.get("total", {}).get("value", 0)
322
- resultados = hits.get("hits", [])
323
-
324
- # Formata resposta
325
- results = []
326
- for hit in resultados:
327
- source = hit.get("_source", {})
328
- hl = hit.get("highlight", {})
329
-
330
- ementa = (
331
- hl.get("ementa_texto", [None])[0] or
332
- source.get("ementa_texto") or ""
333
- )
334
-
335
- results.append({
336
- "id": source.get("id"),
337
- "titulo": source.get("titulo"),
338
- "processo": source.get("processo_codigo_completo"),
339
- "orgao": source.get("orgao_julgador"),
340
- "relator": source.get("relator_processo_nome"),
341
- "data": source.get("julgamento_data"),
342
- "url_documento": (
343
- source.get("inteiro_teor_url") or
344
- source.get("acompanhamento_processual_url") or
345
- source.get("dje_url")
346
- ),
347
- "ementa": ementa.strip(),
348
- "score": hit.get("_score", 0)
349
- })
350
-
351
- return jsonify({
352
- "success": True,
353
- "query": query,
354
- "total": total,
355
- "page": page,
356
- "size": size,
357
- "results": results
358
- })
359
- else:
360
- # Fallback Playwright
361
- res = test_with_playwright_full(payload)
362
- if res.get("success"):
363
- return jsonify({"success": True, "method": "playwright",
364
- "token": res.get("token"), "data": res["data"]})
365
-
366
- return jsonify({"success": False, "error": "Todas as tentativas falharam"}), 500
367
-
368
-
369
- @app.route("/health")
370
- def health():
371
- """Status do serviço"""
372
- playwright_status = False
373
- try:
374
- with sync_playwright() as p:
375
- p.chromium.launch(headless=True).close()
376
- playwright_status = True
377
- except:
378
- pass
379
-
380
- total_docs = None
381
- token = get_fresh_token()
382
- if token:
383
- try:
384
- res = search_with_token(token, {"size": 0, "track_total_hits": True})
385
- if res.get("success") and res["data"].get("hits", {}).get("total"):
386
- total_docs = res["data"]["hits"]["total"]["value"]
387
- except:
388
- pass
389
-
390
- return jsonify({
391
- "status": "healthy",
392
- "timestamp": time.time(),
393
- "playwright_ready": playwright_status,
394
- "token_cached": bool(token_cache["token"]),
395
- "token_expires_in": max(0, token_cache["expires_at"] - time.time()) if token_cache["expires_at"] else 0,
396
- "total_docs": total_docs,
397
- "python_version": sys.version
398
- })
399
-
400
-
401
- if __name__ == "__main__":
402
- try:
403
- import certifi
404
- os.environ["SSL_CERT_FILE"] = certifi.where()
405
- os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
406
- except:
407
- pass
408
-
409
- logger.info("=" * 50)
410
- logger.info("🚀 Iniciando aplicação STF Jurisprudência")
411
- logger.info(f"📋 Campos _source: {len(PAYLOAD_COMPLETO['_source'])}")
412
- logger.info("=" * 50)
413
-
414
- try:
415
- with sync_playwright() as p:
416
- p.chromium.launch(headless=True).close()
417
- logger.info("✅ Playwright pronto para uso")
418
- except Exception as e:
419
- logger.warning(f"⚠️ Playwright pode não estar configurado: {e}")
420
-
421
- port = int(os.environ.get("PORT", 7860))
422
- app.run(host="0.0.0.0", port=port, debug=False)
423
-
424
- Identifiquei os seguintes problemas no código:
425
-
426
- 1. **`import sys` ausente** — usado em `/health` mas nunca importado
427
- 2. **Shallow copy em `PAYLOAD_SEM_HIGHLIGHT`** — referencia `PAYLOAD_COMPLETO["query"]` diretamente, mutações vazam entre payloads
428
- 3. **`try_search_with_payloads` usa `.copy()` raso** — não isola o payload base
429
- 4. **Payload desatualizado** — faltam as 3 funções de score, `post_filter`, e `aggs` do payload real capturado
430
- 5. **`/busca` sem `post_filter`** — a API retorna todos os tipos (`acordaos`, `sumulas`, `decisoes`) sem o filtro explícito
431
- 6. **`sort` padrão errado** — o endpoint usa `julgamento_data` por padrão, mas o portal usa `_score`
432
-
433
- Aqui está o código corrigido e atualizado:
434
-
435
- ```python
436
  import os
437
  import sys
438
  import json
@@ -1158,5 +723,4 @@ if __name__ == "__main__":
1158
  logger.warning(f"⚠️ Playwright: {e}")
1159
 
1160
  port = int(os.environ.get("PORT", 7860))
1161
- app.run(host="0.0.0.0", port=port, debug=False)
1162
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import sys
3
  import json
 
723
  logger.warning(f"⚠️ Playwright: {e}")
724
 
725
  port = int(os.environ.get("PORT", 7860))
726
+ app.run(host="0.0.0.0", port=port, debug=False)