caarleexx commited on
Commit
ae166b4
·
verified ·
1 Parent(s): 27e6ace

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +376 -596
app.py CHANGED
@@ -1,4 +1,6 @@
 
1
 
 
2
  import os
3
  import sys
4
  import json
@@ -9,6 +11,7 @@ import urllib3
9
  from flask import Flask, request, jsonify, render_template_string
10
  from playwright.sync_api import sync_playwright
11
  import traceback
 
12
 
13
  # Suprimir warnings de SSL
14
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
@@ -20,7 +23,7 @@ logger = logging.getLogger(__name__)
20
  app = Flask(__name__)
21
 
22
  # ============================================
23
- # PAYLOAD BASE (extraído do curl, com placeholders)
24
  # ============================================
25
  PAYLOAD_BUSCA_BASE = {
26
  "query": {
@@ -36,44 +39,46 @@ PAYLOAD_BUSCA_BASE = {
36
  {"query_string": {
37
  "default_operator": "AND",
38
  "fields": [
39
- "processo_codigo_completo.plural",
40
- "acordao_ata.plural^3",
41
- "documental_acordao_mesmo_sentido_lista_texto.plural",
42
- "documental_doutrina_texto.plural",
43
- "documental_indexacao_texto.plural",
44
- "documental_jurisprudencia_citada_texto.plural",
45
- "documental_legislacao_citada_texto.plural",
46
- "documental_observacao_texto.plural",
47
- "documental_publicacao_lista_texto.plural",
48
- "documental_tese_tema_texto.plural^3",
49
- "documental_tese_texto.plural^3",
50
- "ementa_texto.plural^3",
51
- "ministro_facet.plural",
52
- "revisor_processo_nome.plural",
53
- "orgao_julgador.plural",
54
- "partes_lista_texto.plural",
55
- "procedencia_geografica_completo.plural",
56
- "processo_classe_processual_unificada_extenso.plural",
57
- "titulo.plural^6",
58
- "colac_numero.plural",
59
- "colac_pagina.plural",
60
- "decisao_texto.plural^2",
61
- "documental_decisao_mesmo_sentido_lista_texto.plural",
62
- "processo_precedente_texto.plural",
63
- "sumula_texto.plural^3",
64
- "ramo_direito.plural^1",
65
- "situacao_sumula.plural^1",
66
- "materia_noticia.plural^1",
67
- "titulo_noticia.plural^3",
68
- "resumo_noticia.plural^3",
69
- "conteudo_noticia.plural^1",
70
- "ramo_noticia.plural^1"
 
71
  ],
72
- "query": "__TERMO__", # será substituído
73
  "type": "cross_fields",
74
  "fuzziness": "AUTO:4,7",
75
- "analyzer": "legal_search_analyzer",
76
- "quote_analyzer": "legal_index_analyzer"
 
77
  }}
78
  ],
79
  "must": [],
@@ -81,30 +86,32 @@ PAYLOAD_BUSCA_BASE = {
81
  {"query_string": {
82
  "default_operator": "AND",
83
  "fields": [
84
- "acordao_ata.plural^3",
85
- "documental_doutrina_texto.plural",
86
- "documental_indexacao_texto.plural",
87
- "documental_jurisprudencia_citada_texto.plural",
88
- "documental_observacao_texto.plural",
89
- "documental_tese_tema_texto.plural^3",
90
- "documental_tese_texto.plural^3",
91
- "ementa_texto.plural^3",
92
- "titulo.plural^6",
93
- "decisao_texto.plural^2",
94
- "sumula_texto.plural^3",
95
- "ramo_direito.plural^1",
96
- "situacao_sumula.plural^1",
97
- "materia_noticia.plural^1",
98
- "titulo_noticia.plural^3",
99
- "resumo_noticia.plural^3",
100
- "conteudo_noticia.plural^1",
101
- "ramo_noticia.plural^1"
 
102
  ],
103
  "query": "__TERMO__",
104
  "tie_breaker": 1,
105
  "fuzziness": "AUTO:4,7",
106
- "analyzer": "legal_search_analyzer",
107
- "quote_analyzer": "legal_index_analyzer"
 
108
  }},
109
  {"query_string": {
110
  "default_operator": "and",
@@ -112,20 +119,22 @@ PAYLOAD_BUSCA_BASE = {
112
  "tie_breaker": 1,
113
  "phrase_slop": 20,
114
  "fields": [
115
- "acordao_ata.plural^3",
116
- "documental_tese_tema_texto.plural^3",
117
- "documental_tese_texto.plural^3",
118
- "ementa_texto.plural^3",
119
- "decisao_texto.plural^2",
120
- "situacao_sumula.plural^1",
121
- "titulo_noticia.plural^3",
122
- "resumo_noticia.plural^3",
123
- "conteudo_noticia.plural^1"
 
124
  ],
125
  "query": "__TERMO__",
126
  "fuzziness": "AUTO:4,7",
127
- "analyzer": "legal_search_analyzer",
128
- "quote_analyzer": "legal_index_analyzer"
 
129
  }},
130
  {"query_string": {
131
  "default_operator": "and",
@@ -133,44 +142,46 @@ PAYLOAD_BUSCA_BASE = {
133
  "tie_breaker": 1,
134
  "phrase_slop": 5,
135
  "fields": [
136
- "documental_acordao_mesmo_sentido_lista_texto.plural",
137
- "documental_doutrina_texto.plural",
138
- "documental_indexacao_texto.plural",
139
- "documental_jurisprudencia_citada_texto.plural",
140
- "documental_legislacao_citada_texto.plural",
141
- "documental_observacao_texto.plural",
142
- "partes_lista_texto.plural",
143
- "processo_precedente_texto.plural",
144
- "documental_decisao_mesmo_sentido_lista_texto.plural"
145
  ],
146
  "query": "__TERMO__",
147
  "fuzziness": "AUTO:4,7",
148
- "analyzer": "legal_search_analyzer",
149
- "quote_analyzer": "legal_index_analyzer"
 
150
  }},
151
  {"query_string": {
152
  "default_operator": "and",
153
  "type": "phrase",
154
  "phrase_slop": 1,
155
  "fields": [
156
- "documental_publicacao_lista_texto.plural",
157
- "ministro_facet.plural",
158
- "revisor_processo_nome.plural",
159
- "orgao_julgador.plural",
160
- "ramo_direito.plural^1",
161
- "ramo_noticia.plural^1",
162
- "procedencia_geografica_completo.plural",
163
- "processo_classe_processual_unificada_extenso.plural",
164
- "titulo.plural^6",
165
- "colac_numero.plural",
166
- "colac_pagina.plural",
167
- "sumula_texto.plural^3"
168
  ],
169
  "query": "__TERMO__",
170
  "fuzziness": "AUTO:4,7",
171
  "boost": 0,
172
- "analyzer": "legal_search_analyzer",
173
- "quote_analyzer": "legal_index_analyzer"
 
174
  }}
175
  ]
176
  }
@@ -338,44 +349,46 @@ PAYLOAD_BUSCA_BASE = {
338
  {"query_string": {
339
  "default_operator": "AND",
340
  "fields": [
341
- "processo_codigo_completo.plural",
342
- "acordao_ata.plural^3",
343
- "documental_acordao_mesmo_sentido_lista_texto.plural",
344
- "documental_doutrina_texto.plural",
345
- "documental_indexacao_texto.plural",
346
- "documental_jurisprudencia_citada_texto.plural",
347
- "documental_legislacao_citada_texto.plural",
348
- "documental_observacao_texto.plural",
349
- "documental_publicacao_lista_texto.plural",
350
- "documental_tese_tema_texto.plural^3",
351
- "documental_tese_texto.plural^3",
352
- "ementa_texto.plural^3",
353
- "ministro_facet.plural",
354
- "revisor_processo_nome.plural",
355
- "orgao_julgador.plural",
356
- "partes_lista_texto.plural",
357
- "procedencia_geografica_completo.plural",
358
- "processo_classe_processual_unificada_extenso.plural",
359
- "titulo.plural^6",
360
- "colac_numero.plural",
361
- "colac_pagina.plural",
362
- "decisao_texto.plural^2",
363
- "documental_decisao_mesmo_sentido_lista_texto.plural",
364
- "processo_precedente_texto.plural",
365
- "sumula_texto.plural^3",
366
- "ramo_direito.plural^1",
367
- "situacao_sumula.plural^1",
368
- "materia_noticia.plural^1",
369
- "titulo_noticia.plural^3",
370
- "resumo_noticia.plural^3",
371
- "conteudo_noticia.plural^1",
372
- "ramo_noticia.plural^1"
 
373
  ],
374
  "query": "__TERMO__",
375
  "type": "cross_fields",
376
  "fuzziness": "AUTO:4,7",
377
- "analyzer": "legal_search_analyzer",
378
- "quote_analyzer": "legal_index_analyzer"
 
379
  }}
380
  ],
381
  "must": [],
@@ -383,30 +396,32 @@ PAYLOAD_BUSCA_BASE = {
383
  {"query_string": {
384
  "default_operator": "AND",
385
  "fields": [
386
- "acordao_ata.plural^3",
387
- "documental_doutrina_texto.plural",
388
- "documental_indexacao_texto.plural",
389
- "documental_jurisprudencia_citada_texto.plural",
390
- "documental_observacao_texto.plural",
391
- "documental_tese_tema_texto.plural^3",
392
- "documental_tese_texto.plural^3",
393
- "ementa_texto.plural^3",
394
- "titulo.plural^6",
395
- "decisao_texto.plural^2",
396
- "sumula_texto.plural^3",
397
- "ramo_direito.plural^1",
398
- "situacao_sumula.plural^1",
399
- "materia_noticia.plural^1",
400
- "titulo_noticia.plural^3",
401
- "resumo_noticia.plural^3",
402
- "conteudo_noticia.plural^1",
403
- "ramo_noticia.plural^1"
 
404
  ],
405
  "query": "__TERMO__",
406
  "tie_breaker": 1,
407
  "fuzziness": "AUTO:4,7",
408
- "analyzer": "legal_search_analyzer",
409
- "quote_analyzer": "legal_index_analyzer"
 
410
  }},
411
  {"query_string": {
412
  "default_operator": "and",
@@ -414,20 +429,22 @@ PAYLOAD_BUSCA_BASE = {
414
  "tie_breaker": 1,
415
  "phrase_slop": 20,
416
  "fields": [
417
- "acordao_ata.plural^3",
418
- "documental_tese_tema_texto.plural^3",
419
- "documental_tese_texto.plural^3",
420
- "ementa_texto.plural^3",
421
- "decisao_texto.plural^2",
422
- "situacao_sumula.plural^1",
423
- "titulo_noticia.plural^3",
424
- "resumo_noticia.plural^3",
425
- "conteudo_noticia.plural^1"
 
426
  ],
427
  "query": "__TERMO__",
428
  "fuzziness": "AUTO:4,7",
429
- "analyzer": "legal_search_analyzer",
430
- "quote_analyzer": "legal_index_analyzer"
 
431
  }},
432
  {"query_string": {
433
  "default_operator": "and",
@@ -435,44 +452,46 @@ PAYLOAD_BUSCA_BASE = {
435
  "tie_breaker": 1,
436
  "phrase_slop": 5,
437
  "fields": [
438
- "documental_acordao_mesmo_sentido_lista_texto.plural",
439
- "documental_doutrina_texto.plural",
440
- "documental_indexacao_texto.plural",
441
- "documental_jurisprudencia_citada_texto.plural",
442
- "documental_legislacao_citada_texto.plural",
443
- "documental_observacao_texto.plural",
444
- "partes_lista_texto.plural",
445
- "processo_precedente_texto.plural",
446
- "documental_decisao_mesmo_sentido_lista_texto.plural"
447
  ],
448
  "query": "__TERMO__",
449
  "fuzziness": "AUTO:4,7",
450
- "analyzer": "legal_search_analyzer",
451
- "quote_analyzer": "legal_index_analyzer"
 
452
  }},
453
  {"query_string": {
454
  "default_operator": "and",
455
  "type": "phrase",
456
  "phrase_slop": 1,
457
  "fields": [
458
- "documental_publicacao_lista_texto.plural",
459
- "ministro_facet.plural",
460
- "revisor_processo_nome.plural",
461
- "orgao_julgador.plural",
462
- "ramo_direito.plural^1",
463
- "ramo_noticia.plural^1",
464
- "procedencia_geografica_completo.plural",
465
- "processo_classe_processual_unificada_extenso.plural",
466
- "titulo.plural^6",
467
- "colac_numero.plural",
468
- "colac_pagina.plural",
469
- "sumula_texto.plural^3"
470
  ],
471
  "query": "__TERMO__",
472
  "fuzziness": "AUTO:4,7",
473
  "boost": 0,
474
- "analyzer": "legal_search_analyzer",
475
- "quote_analyzer": "legal_index_analyzer"
 
476
  }}
477
  ]
478
  }
@@ -483,27 +502,28 @@ PAYLOAD_BUSCA_BASE = {
483
  "pre_tags": ["<em>"],
484
  "post_tags": ["</em>"],
485
  "fields": {
486
- "ementa_texto": {"fragment_size": 24000, "matched_fields": ["ementa_texto.plural"], "type": "fvh"},
487
- "sumula_texto": {"number_of_fragments": 0, "matched_fields": ["sumula_texto.plural"], "type": "fvh"},
488
- "materia_noticia": {"matched_fields": ["materia_noticia.plural"], "type": "fvh"},
489
- "titulo_noticia": {"matched_fields": ["titulo_noticia.plural"], "type": "fvh"},
490
- "resumo_noticia": {"fragment_size": 5000, "matched_fields": ["resumo_noticia.plural"], "type": "fvh"},
491
- "conteudo_noticia": {"fragment_size": 50000, "matched_fields": ["conteudo_noticia.plural"], "type": "fvh"},
492
- "acordao_ata": {"fragment_size": 600, "matched_fields": ["acordao_ata.plural"], "type": "fvh"},
493
- "decisao_texto": {"fragment_size": 1200, "matched_fields": ["decisao_texto.plural"], "type": "fvh"},
494
- "documental_tese_texto": {"fragment_size": 2000, "matched_fields": ["documental_tese_texto.plural"], "type": "fvh"},
495
- "documental_tese_tema_texto": {"fragment_size": 2000, "matched_fields": ["documental_tese_tema_texto.plural"], "type": "fvh"},
496
- "documental_observacao_texto": {"matched_fields": ["documental_observacao_texto.plural"], "type": "fvh"},
497
- "documental_indexacao_texto": {"matched_fields": ["documental_indexacao_texto.plural"], "type": "fvh"},
498
- "documental_legislacao_citada_texto": {"matched_fields": ["documental_legislacao_citada_texto.plural"], "type": "fvh"},
499
- "documental_jurisprudencia_citada_texto": {"matched_fields": ["documental_jurisprudencia_citada_texto.plural"], "type": "fvh"},
500
- "documental_doutrina_texto": {"matched_fields": ["documental_doutrina_texto.plural"], "type": "fvh"},
501
- "partes_lista_texto": {"matched_fields": ["partes_lista_texto.plural"], "type": "fvh"},
502
- "documental_publicacao_lista_texto": {"matched_fields": ["documental_publicacao_lista_texto.plural"], "type": "fvh"},
503
- "documental_acordao_mesmo_sentido_lista_texto": {"matched_fields": ["documental_acordao_mesmo_sentido_lista_texto.plural"], "type": "fvh"},
504
- "documental_decisao_mesmo_sentido_lista_texto": {"matched_fields": ["documental_decisao_mesmo_sentido_lista_texto.plural"], "type": "fvh"},
505
- "processo_precedente_texto": {"matched_fields": ["processo_precedente_texto.plural"], "type": "fvh"},
506
- "procedencia_geografica_completo": {"matched_fields": ["procedencia_geografica_completo.plural"], "type": "fvh"}
 
507
  }
508
  },
509
  "track_total_hits": True
@@ -525,62 +545,119 @@ HEADERS = {
525
  token_cache = {"token": None, "expires_at": 0}
526
 
527
  # ============================================
528
- # HTML TEMPLATE COMPLETO (mesmo da versão anterior)
529
  # ============================================
530
- HTML_TEMPLATE = """
531
- <!DOCTYPE html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
  <html>
533
  <head>
534
- <title>⚖️ STF Jurisprudência - Visualizador Completo</title>
535
  <meta charset="utf-8">
536
  <meta name="viewport" content="width=device-width, initial-scale=1">
537
  <style>
538
  body { font-family: 'Segoe UI', Roboto, system-ui, sans-serif; max-width: 1600px; margin: 0 auto; padding: 20px; background: #f0f2f5; }
539
  .container { background: white; border-radius: 12px; padding: 30px; box-shadow: 0 8px 20px rgba(0,0,0,0.1); }
540
- h1 { color: #1a1a2e; border-bottom: 3px solid #4CAF50; padding-bottom: 15px; display: flex; align-items: center; gap: 10px; }
541
  .info-box { background: #e8f0fe; border-left: 5px solid #2196F3; padding: 15px 20px; margin: 20px 0; border-radius: 8px; }
542
- .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 25px 0; }
543
- .stat-card { background: white; border: 1px solid #e0e0e0; border-radius: 10px; padding: 20px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
544
- .stat-value { font-size: 32px; font-weight: bold; color: #1a73e8; }
545
- .stat-label { color: #5f6368; font-size: 14px; margin-top: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
546
- .button-group { display: flex; gap: 15px; flex-wrap: wrap; margin: 25px 0; }
547
- button { background: #1a73e8; color: white; border: none; padding: 14px 28px; font-size: 16px; font-weight: 500; border-radius: 8px; cursor: pointer; transition: all 0.3s; display: inline-flex; align-items: center; gap: 10px; box-shadow: 0 2px 8px rgba(26,115,232,0.3); }
548
- button:hover { background: #1557b0; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(26,115,232,0.4); }
549
- button:disabled { background: #a0a0a0; cursor: not-allowed; transform: none; box-shadow: none; }
550
- .loading { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(255,255,255,0.3); border-top: 3px solid white; border-radius: 50%; animation: spin 1s linear infinite; margin-right: 10px; vertical-align: middle; }
551
- @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
552
- pre { background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 10px; padding: 20px; overflow: auto; max-height: 800px; font-size: 13px; font-family: 'Consolas', 'Monaco', monospace; box-shadow: inset 0 2px 4px rgba(0,0,0,0.05); }
553
- .success { color: #0f9d58; background: #e6f4ea; border-left: 5px solid #34a853; padding: 15px 20px; margin: 15px 0; border-radius: 8px; font-weight: 500; }
554
- .error { color: #d93025; background: #fce8e6; border-left: 5px solid #ea4335; padding: 15px 20px; margin: 15px 0; border-radius: 8px; font-weight: 500; }
555
- .token-box { background: #1a1a2e; color: #e0e0e0; padding: 15px; border-radius: 8px; font-family: 'Consolas', monospace; word-break: break-all; margin: 15px 0; border: 1px solid #2a2a3e; }
556
- .token-label { color: #f9ab00; font-weight: bold; margin-bottom: 8px; display: block; }
557
- .filters { background: #f8f9fa; border-radius: 8px; padding: 20px; margin: 20px 0; border: 1px solid #e0e0e0; }
558
- .filter-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; align-items: end; }
559
- .filter-item { min-width: 200px; }
560
- .filter-item label { display: block; margin-bottom: 8px; color: #5f6368; font-size: 14px; font-weight: 500; }
561
- .filter-item input, .filter-item select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
562
- .campo-lista { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; margin: 10px 0; }
563
- .campo-nome { font-weight: bold; color: #2c3e50; font-size: 14px; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 5px; }
564
- .campo-valor { color: #1a1a2e; font-size: 14px; word-break: break-word; font-family: 'Consolas', monospace; background: white; padding: 8px; border-radius: 4px; border: 1px solid #e0e0e0; }
565
- .texto-completo { max-height: 400px; overflow-y: auto; background: #f1f8fe; padding: 15px; border-radius: 8px; border: 1px solid #b8daf5; margin: 10px 0; white-space: pre-wrap; font-family: inherit; line-height: 1.5; }
566
- .nav-tabs { display: flex; gap: 5px; margin: 20px 0; flex-wrap: wrap; border-bottom: 2px solid #e0e0e0; padding-bottom: 10px; }
567
- .nav-tab { padding: 10px 20px; cursor: pointer; background: white; border: 1px solid #e0e0e0; border-radius: 8px 8px 0 0; margin-bottom: -2px; font-weight: 500; transition: all 0.2s; }
568
- .nav-tab.active { background: #1a73e8; color: white; border-color: #1a73e8; }
569
- .tab-content { display: none; padding: 20px; background: white; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 8px 8px; }
570
- .tab-content.active { display: block; }
571
- .campo-card { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; margin: 10px 0; }
572
- .url-link { color: #1a73e8; text-decoration: none; word-break: break-all; }
573
- .url-link:hover { text-decoration: underline; }
574
- .highlight { background-color: #f9ab00; color: #1a1a2e; font-weight: bold; padding: 2px 4px; border-radius: 4px; }
575
- .highlight-box { background: #fef7e0; border: 1px solid #f9ab00; padding: 10px; border-radius: 8px; margin: 10px 0; }
576
  </style>
577
  </head>
578
  <body>
579
  <div class="container">
580
- <h1>STF Jurisprudência - Visualizador Completo</h1>
581
  <div class="info-box">
582
- <strong>📌 API de Jurisprudência do STF - Todos os Campos com Highlight</strong><br>
583
- • Total de campos disponíveis: <span id="totalCampos">102</span><br>
584
  • Documentos disponíveis: <span id="totalDocs">carregando...</span>
585
  </div>
586
  <div class="stats-grid">
@@ -609,78 +686,27 @@ HTML_TEMPLATE = """
609
  </div>
610
  <script>
611
  let lastResult = null, lastToken = null, requestsCount = 0, successCount = 0, failCount = 0, docsCount = 0;
612
- function renderCampo(nome, valor, tipo='normal') {
613
- if (!valor && valor!==0) return `<div class="campo-card"><div class="campo-nome">${nome}</div><div class="campo-vazio">(vazio)</div></div>`;
614
- if (tipo==='url') return `<div class="campo-card"><div class="campo-nome">${nome}</div><div class="campo-valor"><a href="${valor}" target="_blank" class="url-link">${valor}</a></div></div>`;
615
- if (tipo==='texto' && valor.length>200) return `<div class="campo-card"><div class="campo-nome">${nome}</div><div class="texto-completo">${valor.replace(/\\n/g,'<br>').replace(/<em>/g,'<span class="highlight">').replace(/<\\/em>/g,'</span>')}</div></div>`;
616
- return `<div class="campo-card"><div class="campo-nome">${nome}</div><div class="campo-valor">${String(valor).replace(/\\n/g,'<br>').replace(/<em>/g,'<span class="highlight">').replace(/<\\/em>/g,'</span>')}</div></div>`;
617
- }
618
  function displayResult(data) {
619
- if (!data?.result?.hits?.hits) return document.getElementById('result').innerHTML = '<div class="warning">⚠️ Nenhum documento encontrado</div>';
620
  const hits = data.result.hits.hits, total = data.result.hits.total?.value || 0;
621
  let html = `<div class="success">✅ Encontrados ${total.toLocaleString()} documentos. Exibindo ${hits.length} resultados.</div>`;
622
  hits.forEach((hit, idx) => {
623
- const src = hit._source || {}, hl = hit.highlight || {}, docId = src.id || hit._id || `doc_${idx}`;
624
- html += `<div class="campo-lista"><h3 style="display:flex;justify-content:space-between;"><span>📄 ${idx+1}: ${src.titulo || src.processo_codigo_completo || docId}</span><span class="badge">ID: ${docId}</span></h3>`;
625
- html += `<div style="margin-bottom:10px;"><button class="secondary" onclick="fetchFullDocument('${docId}')">📄 Obter texto completo</button></div>`;
626
- html += `<div style="display:flex;gap:5px;margin:15px 0;flex-wrap:wrap;">`;
627
- ['geral','ementa','acordao','legislacao','highlight','completo'].forEach(t => html += `<button class="badge" style="cursor:pointer;" onclick="showDocTab('${t}-${idx}')">${t}</button>`);
628
- html += `</div>`;
629
- html += `<div id="doc-tab-geral-${idx}" class="doc-tab" style="display:block;">`;
630
- html += renderCampo('Processo', src.processo_codigo_completo);
631
- html += renderCampo('Classe', src.processo_classe_processual_unificada_extenso);
632
- html += renderCampo('Órgão Julgador', src.orgao_julgador);
633
- html += renderCampo('Relator', src.relator_processo_nome);
634
- html += renderCampo('Relator Acórdão', src.relator_acordao_nome);
635
- html += renderCampo('Ministros', src.ministro_facet);
636
- html += renderCampo('Data Julgamento', src.julgamento_data);
637
- html += renderCampo('Data Publicação', src.publicacao_data);
638
- html += renderCampo('Procedência', src.procedencia_geografica_completo);
639
- html += `</div>`;
640
- html += `<div id="doc-tab-ementa-${idx}" class="doc-tab" style="display:none;">`;
641
- html += renderCampo('Ementa', src.ementa_texto, 'texto');
642
- html += renderCampo('Tese', src.documental_tese_texto, 'texto');
643
- html += renderCampo('Tema', src.documental_tese_tema_texto);
644
- html += `</div>`;
645
- html += `<div id="doc-tab-acordao-${idx}" class="doc-tab" style="display:none;">`;
646
- html += renderCampo('Acórdão/Ata', src.acordao_ata, 'texto');
647
- html += renderCampo('Decisão', src.decisao_texto, 'texto');
648
- html += renderCampo('Súmula', src.sumula_texto, 'texto');
649
- html += renderCampo('Indexação', src.documental_indexacao_texto, 'texto');
650
- html += renderCampo('Observações', src.documental_observacao_texto, 'texto');
651
- html += `</div>`;
652
- html += `<div id="doc-tab-legislacao-${idx}" class="doc-tab" style="display:none;">`;
653
- html += renderCampo('Legislação Citada', src.documental_legislacao_citada_texto, 'texto');
654
- html += renderCampo('Jurisprudência Citada', src.documental_jurisprudencia_citada_texto, 'texto');
655
- html += renderCampo('Doutrina', src.documental_doutrina_texto, 'texto');
656
- html += renderCampo('Precedentes', src.processo_precedente_texto, 'texto');
657
- html += `</div>`;
658
- html += `<div id="doc-tab-highlight-${idx}" class="doc-tab" style="display:none;">`;
659
- if (Object.keys(hl).length) {
660
- for (const [campo, valores] of Object.entries(hl)) {
661
- html += `<div class="campo-card"><div class="campo-nome">${campo}</div>`;
662
- valores.forEach(valor => html += `<div class="highlight-box">${valor.replace(/<em>/g,'<span class="highlight">').replace(/<\\/em>/g,'</span>')}</div>`);
663
- html += `</div>`;
664
- }
665
- } else html += `<div class="campo-vazio">Nenhum termo destacado</div>`;
666
- html += `</div>`;
667
- html += `<div id="doc-tab-completo-${idx}" class="doc-tab" style="display:none;">`;
668
- html += `<pre>${JSON.stringify(src, null, 2).replace(/<em>/g,'<span class="highlight">').replace(/<\\/em>/g,'</span>')}</pre>`;
669
- html += `</div>`;
670
- if (src.inteiro_teor_url) html += `<div style="margin-top:15px;padding:10px;background:#e8f0fe;border-radius:8px;"><strong>🔗 Links:</strong><br><a href="${src.inteiro_teor_url}" target="_blank" class="url-link">📄 Inteiro Teor</a></div>`;
671
  html += `</div>`;
672
  });
673
  document.getElementById('result').innerHTML = html;
674
  }
675
- function showDocTab(tabId) {
676
- const idx = tabId.split('-')[2];
677
- document.querySelectorAll(`[id^="doc-tab-"]`).forEach(el => { if (el.id.includes(`-${idx}`)) el.style.display = 'none'; });
678
- const sel = document.getElementById(`doc-tab-${tabId}`);
679
- if (sel) sel.style.display = 'block';
680
- }
681
  async function runSearch() {
682
  const btn = document.getElementById('testBtn'), loading = document.getElementById('loading'), resultDiv = document.getElementById('result');
683
- btn.disabled = true; loading.style.display = 'inline-block'; resultDiv.innerHTML = '<div class="info-box">⏳ Executando busca...</div>';
684
  try {
685
  const resp = await fetch('/api/search-advanced', {
686
  method:'POST', headers:{'Content-Type':'application/json'},
@@ -758,150 +784,14 @@ HTML_TEMPLATE = """
758
  const resp = await fetch('/api/health');
759
  const data = await resp.json();
760
  if (data.total_docs) document.getElementById('totalDocs').textContent = data.total_docs.toLocaleString();
761
- document.getElementById('totalCampos').textContent = "102";
762
  } catch(e) { document.getElementById('totalDocs').textContent = 'indisponível'; }
763
  };
764
  </script>
765
  </body>
766
- </html>
767
- """
768
 
769
  # ============================================
770
- # Funções auxiliares (mantidas)
771
- # ============================================
772
- def get_fresh_token():
773
- global token_cache
774
- if token_cache["token"] and time.time() < token_cache["expires_at"]:
775
- logger.info("Usando token em cache")
776
- return token_cache["token"]
777
-
778
- logger.info("Obtendo novo token via Playwright")
779
- try:
780
- with sync_playwright() as p:
781
- browser = p.chromium.launch(headless=True, args=['--no-sandbox'])
782
- context = browser.new_context(
783
- viewport={'width': 1920, 'height': 1080},
784
- user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
785
- )
786
- page = context.new_page()
787
- page.goto("https://jurisprudencia.stf.jus.br/pages/search", wait_until='domcontentloaded', timeout=30000)
788
- page.wait_for_timeout(3000)
789
- cookies = context.cookies()
790
- token = None
791
- for cookie in cookies:
792
- if cookie.get('name') == 'aws-waf-token':
793
- token = cookie.get('value')
794
- break
795
- browser.close()
796
- if token:
797
- token_cache["token"] = token
798
- token_cache["expires_at"] = time.time() + 3300
799
- logger.info(f"Token obtido: {token[:30]}...")
800
- return token
801
- else:
802
- logger.warning("Token não encontrado nos cookies")
803
- return None
804
- except Exception as e:
805
- logger.error(f"Erro ao obter token: {str(e)}")
806
- return None
807
-
808
- def search_with_token(token, payload):
809
- if not token:
810
- return {"success": False, "error": "Token não fornecido"}
811
- headers = HEADERS.copy()
812
- headers['Cookie'] = f'aws-waf-token={token}'
813
- try:
814
- response = requests.post(URL_API, headers=headers, json=payload, verify=False, timeout=30)
815
- logger.info(f"Resposta: status {response.status_code}")
816
- if response.status_code == 200:
817
- return {"success": True, "data": response.json()}
818
- elif response.status_code == 403:
819
- token_cache["token"] = None
820
- return {"success": False, "error": "Token expirado", "status": 403}
821
- else:
822
- return {"success": False, "error": f"HTTP {response.status_code}", "status": response.status_code, "text": response.text[:500]}
823
- except Exception as e:
824
- logger.error(f"Erro na requisição: {str(e)}")
825
- return {"success": False, "error": str(e)}
826
-
827
- def get_document_by_id(token, doc_id):
828
- payload = {
829
- "query": {"ids": {"values": [doc_id]}},
830
- "_source": PAYLOAD_BUSCA_BASE["_source"], # reutiliza a lista de campos
831
- "size": 1
832
- }
833
- return search_with_token(token, payload)
834
-
835
- def get_document_raw(token, doc_id):
836
- if not token:
837
- return {"success": False, "error": "Token não fornecido"}
838
- headers = HEADERS.copy()
839
- headers['Cookie'] = f'aws-waf-token={token}'
840
- headers.pop("Content-Type", None)
841
- url = f"{URL_API_GET}/{doc_id}"
842
- try:
843
- response = requests.get(url, headers=headers, verify=False, timeout=30)
844
- logger.info(f"GET documento raw: status {response.status_code}")
845
- if response.status_code == 200:
846
- return {"success": True, "data": response.json()}
847
- elif response.status_code == 403:
848
- token_cache["token"] = None
849
- return {"success": False, "error": "Token expirado", "status": 403}
850
- else:
851
- return {"success": False, "error": f"HTTP {response.status_code}", "status": response.status_code, "text": response.text[:500]}
852
- except Exception as e:
853
- return {"success": False, "error": str(e)}
854
-
855
- def test_with_playwright_full(payload):
856
- logger.info("Tentando acesso com Playwright...")
857
- try:
858
- with sync_playwright() as p:
859
- browser = p.chromium.launch(headless=True, args=['--no-sandbox'])
860
- context = browser.new_context(
861
- viewport={'width': 1920, 'height': 1080},
862
- user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
863
- )
864
- page = context.new_page()
865
- page.goto("https://jurisprudencia.stf.jus.br/pages/search", wait_until='domcontentloaded', timeout=30000)
866
- page.wait_for_timeout(3000)
867
- cookies = context.cookies()
868
- token = None
869
- for cookie in cookies:
870
- if cookie.get('name') == 'aws-waf-token':
871
- token = cookie.get('value')
872
- break
873
- api_result = page.evaluate("""
874
- async (payload) => {
875
- try {
876
- const response = await fetch('https://jurisprudencia.stf.jus.br/api/search/search', {
877
- method: 'POST',
878
- headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
879
- body: JSON.stringify(payload)
880
- });
881
- if (response.ok) {
882
- return { success: true, data: await response.json() };
883
- } else {
884
- return { success: false, status: response.status };
885
- }
886
- } catch (error) {
887
- return { success: false, error: error.toString() };
888
- }
889
- }
890
- """, payload)
891
- browser.close()
892
- if api_result.get('success'):
893
- if token:
894
- token_cache["token"] = token
895
- token_cache["expires_at"] = time.time() + 3300
896
- return {"success": True, "data": api_result.get('data'), "token": token}
897
- else:
898
- return {"success": False, "error": api_result.get('error', 'Falha desconhecida'), "status": api_result.get('status'), "token": token}
899
- except Exception as e:
900
- logger.error(f"Erro no Playwright: {str(e)}")
901
- return {"success": False, "error": str(e)}
902
-
903
- # ============================================
904
- # Rotas (mantidas, incluindo /busca)
905
  # ============================================
906
  @app.route('/')
907
  def index():
@@ -916,42 +806,39 @@ def search_advanced():
916
  page_size = data.get('pageSize', 100)
917
  sort_order = data.get('sortOrder', 'desc')
918
  base = data.get('base')
919
-
920
  token = get_fresh_token()
921
  if not token:
922
- # Fallback Playwright
923
- payload = {
924
- "query": {"bool": {"filter": [{"term": {"base": base or "acordaos"}}]}},
925
- "_source": PAYLOAD_BUSCA_BASE["_source"],
926
- "size": min(page_size, 250),
927
- "sort": [{"julgamento_data": {"order": sort_order}}],
928
- "track_total_hits": True
929
- }
930
- res = test_with_playwright_full(payload)
931
- if res.get('success'):
932
- return jsonify({"success": True, "token": res.get('token'), "data": res['data'], "timestamp": time.time()})
933
- else:
934
- return jsonify({"success": False, "error": res.get('error', 'Falha')}), 500
935
-
936
- # Constrói payload com base e ordenação
937
  payload = PAYLOAD_BUSCA_BASE.copy()
938
  payload["size"] = min(page_size, 250)
939
- payload["sort"] = [{"julgamento_data": {"order": sort_order}}] if sort_order != "score" else [{"_score": "desc"}]
940
  if base:
941
  payload["post_filter"]["bool"]["must"] = [{"term": {"base": base}}]
942
-
943
- res = search_with_token(token, payload)
944
- if res.get("success"):
945
- return jsonify({"success": True, "token": token, "data": res["data"], "timestamp": time.time()})
946
  else:
947
- return jsonify({"success": False, "error": res.get('error')}), 500
 
 
 
 
 
 
948
 
949
  @app.route('/api/document/<doc_id>', methods=['GET'])
950
  def get_document(doc_id):
951
  token = get_fresh_token()
952
  if not token:
953
  return jsonify({"error": "Não foi possível obter token"}), 500
954
- result = get_document_by_id(token, doc_id)
 
 
 
 
 
955
  if result.get("success") and result["data"].get("hits", {}).get("hits"):
956
  doc = result["data"]["hits"]["hits"][0]
957
  return jsonify({"success": True, "document": doc})
@@ -969,30 +856,13 @@ def get_document_raw_endpoint(doc_id):
969
  else:
970
  if result.get("status") == 403:
971
  token_cache["token"] = None
972
- token = get_fresh_token()
973
  if token:
974
  result = get_document_raw(token, doc_id)
975
  if result.get("success"):
976
  return jsonify({"success": True, "document": result["data"]})
977
  return jsonify({"success": False, "error": result.get("error", "Falha")}), 500
978
 
979
- @app.route('/api/test-bypass', methods=['POST'])
980
- def test_bypass():
981
- token = get_fresh_token()
982
- if token:
983
- # Tenta busca simples
984
- payload = PAYLOAD_BUSCA_BASE.copy()
985
- payload["size"] = 5
986
- res = search_with_token(token, payload)
987
- if res.get("success"):
988
- return jsonify({"success": True, "method": "token", "token": token, "data": res["data"]})
989
- # Fallback Playwright
990
- payload = {"query": {"match_all": {}}, "size": 5}
991
- res = test_with_playwright_full(payload)
992
- if res.get('success'):
993
- return jsonify({"success": True, "method": "playwright", "token": res.get('token'), "data": res['data']})
994
- return jsonify({"success": False, "error": res.get('error', 'Falha')}), 500
995
-
996
  @app.route('/api/health', methods=['GET'])
997
  def health():
998
  playwright_status = False
@@ -1036,55 +906,15 @@ def clear_cache():
1036
  return jsonify({"success": True, "message": "Cache limpo"})
1037
 
1038
  # ============================================
1039
- # NOVO ENDPOINT SIMPLIFICADO /busca (com payload exato)
1040
- # ============================================
1041
-
1042
-
1043
- # ============================================
1044
- # Função get_fresh_token modificada (aceita force_refresh)
1045
- # ============================================
1046
- def get_fresh_token(force_refresh=False):
1047
- global token_cache
1048
- if not force_refresh and token_cache["token"] and time.time() < token_cache["expires_at"]:
1049
- logger.info("Usando token em cache")
1050
- return token_cache["token"]
1051
-
1052
- logger.info("Obtendo novo token via Playwright" + (" (forçado)" if force_refresh else ""))
1053
- try:
1054
- with sync_playwright() as p:
1055
- browser = p.chromium.launch(headless=True, args=['--no-sandbox'])
1056
- context = browser.new_context(
1057
- viewport={'width': 1920, 'height': 1080},
1058
- user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
1059
- )
1060
- page = context.new_page()
1061
- page.goto("https://jurisprudencia.stf.jus.br/pages/search", wait_until='domcontentloaded', timeout=30000)
1062
- page.wait_for_timeout(3000)
1063
- cookies = context.cookies()
1064
- token = None
1065
- for cookie in cookies:
1066
- if cookie.get('name') == 'aws-waf-token':
1067
- token = cookie.get('value')
1068
- break
1069
- browser.close()
1070
- if token:
1071
- # Cache por 10 minutos (600 segundos) – ajustável
1072
- token_cache["token"] = token
1073
- token_cache["expires_at"] = time.time() + 600
1074
- logger.info(f"Token obtido: {token[:30]}...")
1075
- return token
1076
- else:
1077
- logger.warning("Token não encontrado nos cookies")
1078
- return None
1079
- except Exception as e:
1080
- logger.error(f"Erro ao obter token: {str(e)}")
1081
- return None
1082
-
1083
- # ============================================
1084
- # Endpoint /busca com tratamento de 202
1085
  # ============================================
1086
  @app.route('/busca')
1087
  def busca_simplificada():
 
 
 
 
 
1088
  query = request.args.get('q', '')
1089
  if not query:
1090
  return jsonify({"erro": "Parâmetro 'q' é obrigatório"}), 400
@@ -1093,20 +923,19 @@ def busca_simplificada():
1093
  if not token:
1094
  return jsonify({"erro": "Não foi possível obter token de acesso"}), 503
1095
 
1096
- payload = PAYLOAD_BUSCA_BASE.copy()
1097
- payload_str = json.dumps(payload).replace("__TERMO__", query)
1098
  payload = json.loads(payload_str)
1099
 
1100
  headers = HEADERS.copy()
1101
  headers['Cookie'] = f'aws-waf-token={token}'
1102
 
1103
- # Tentativa inicial
1104
  try:
1105
  response = requests.post(URL_API, headers=headers, json=payload, verify=False, timeout=30)
1106
  except Exception as e:
1107
  return jsonify({"erro": f"Falha na comunicação com a API: {str(e)}"}), 502
1108
 
1109
- # Se recebeu 202 (token expirado), tenta renovar e faz uma segunda tentativa
1110
  if response.status_code == 202:
1111
  logger.info("Recebido status 202, forçando renovação do token...")
1112
  token = get_fresh_token(force_refresh=True)
@@ -1125,66 +954,6 @@ def busca_simplificada():
1125
  data = response.json()
1126
  hits = data.get('result', {}).get('hits', {}).get('hits', [])
1127
 
1128
- resultados = []
1129
- for hit in hits:
1130
- source = hit.get('_source', {})
1131
- item = {
1132
- "id": source.get('id') or hit.get('_id'),
1133
- "titulo": source.get('titulo'),
1134
- "processo": source.get('processo_codigo_completo'),
1135
- "relator": source.get('relator_processo_nome'),
1136
- "orgao": source.get('orgao_julgador'),
1137
- "data": source.get('julgamento_data'),
1138
- "ementa": source.get('ementa_texto'),
1139
- "url_documento": source.get('inteiro_teor_url'),
1140
- "score": hit.get('_score')
1141
- }
1142
- item = {k: v for k, v in item.items() if v is not None}
1143
- resultados.append(item)
1144
-
1145
- return jsonify({
1146
- "q": query,
1147
- "total": data.get('result', {}).get('hits', {}).get('total', {}).get('value', 0),
1148
- "resultados": resultados
1149
- })
1150
-
1151
- @app.route('/busca1')
1152
- def busca_simplificada1():
1153
- """
1154
- Endpoint público para busca simplificada de jurisprudência.
1155
- Usa o payload completo do curl (com boosts, aggs, highlight) e retorna apenas os campos essenciais.
1156
- Exemplo: /busca?q=dano%20moral
1157
- """
1158
- query = request.args.get('q', '')
1159
- if not query:
1160
- return jsonify({"erro": "Parâmetro 'q' é obrigatório"}), 400
1161
-
1162
- token = get_fresh_token()
1163
- if not token:
1164
- return jsonify({"erro": "Não foi possível obter token de acesso"}), 503
1165
-
1166
- # Cria uma cópia do payload base e substitui o placeholder pelo termo
1167
- payload = PAYLOAD_BUSCA_BASE.copy()
1168
- # Substitui em todas as ocorrências de "__TERMO__" no dicionário (deep replace)
1169
- payload_str = json.dumps(payload)
1170
- payload_str = payload_str.replace("__TERMO__", query)
1171
- payload = json.loads(payload_str)
1172
-
1173
- # Envia a requisição
1174
- headers = HEADERS.copy()
1175
- headers['Cookie'] = f'aws-waf-token={token}'
1176
-
1177
- try:
1178
- response = requests.post(URL_API, headers=headers, json=payload, verify=False, timeout=30)
1179
- except Exception as e:
1180
- return jsonify({"erro": f"Falha na comunicação com a API: {str(e)}"}), 502
1181
-
1182
- if response.status_code != 200:
1183
- return jsonify({"erro": f"API retornou status {response.status_code}"}), response.status_code
1184
-
1185
- data = response.json()
1186
- hits = data.get('result', {}).get('hits', {}).get('hits', [])
1187
-
1188
  resultados = []
1189
  for hit in hits:
1190
  source = hit.get('_source', {})
@@ -1227,3 +996,14 @@ if __name__ == '__main__':
1227
  logger.warning(f"⚠️ Playwright pode não estar configurado: {e}")
1228
  port = int(os.environ.get('PORT', 7860))
1229
  app.run(host='0.0.0.0', port=port, debug=False)
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Aqui está o app.py completo com o payload exato da busca (conforme o curl enviado) e o endpoint /busca simplificado, mantendo a estrutura anterior de token e renovação.
2
 
3
+ ```python
4
  import os
5
  import sys
6
  import json
 
11
  from flask import Flask, request, jsonify, render_template_string
12
  from playwright.sync_api import sync_playwright
13
  import traceback
14
+ import re
15
 
16
  # Suprimir warnings de SSL
17
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
23
  app = Flask(__name__)
24
 
25
  # ============================================
26
+ # PAYLOAD EXATO DA BUSCA (conforme curl enviado)
27
  # ============================================
28
  PAYLOAD_BUSCA_BASE = {
29
  "query": {
 
39
  {"query_string": {
40
  "default_operator": "AND",
41
  "fields": [
42
+ "processo_codigo_completo",
43
+ "acordao_ata^3",
44
+ "documental_acordao_mesmo_sentido_lista_texto",
45
+ "documental_doutrina_texto",
46
+ "documental_indexacao_texto",
47
+ "documental_jurisprudencia_citada_texto",
48
+ "documental_legislacao_citada_texto",
49
+ "documental_observacao_texto",
50
+ "documental_publicacao_lista_texto",
51
+ "documental_tese_tema_texto^3",
52
+ "documental_tese_texto^3",
53
+ "ementa_texto^3",
54
+ "ministro_facet",
55
+ "revisor_processo_nome",
56
+ "orgao_julgador",
57
+ "partes_lista_texto",
58
+ "procedencia_geografica_completo",
59
+ "processo_classe_processual_unificada_extenso",
60
+ "titulo^6",
61
+ "colac_numero",
62
+ "colac_pagina",
63
+ "decisao_texto^2",
64
+ "documental_decisao_mesmo_sentido_lista_texto",
65
+ "processo_precedente_texto",
66
+ "sumula_texto^3",
67
+ "ramo_direito^1",
68
+ "situacao_sumula^1",
69
+ "materia_noticia^1",
70
+ "titulo_noticia^3",
71
+ "resumo_noticia^3",
72
+ "conteudo_noticia^1",
73
+ "ramo_noticia^1",
74
+ "inteiro_teor_texto"
75
  ],
76
+ "query": "__TERMO__",
77
  "type": "cross_fields",
78
  "fuzziness": "AUTO:4,7",
79
+ "analyzer": "radical_search_analyzer",
80
+ "quote_analyzer": "standard_analyzer",
81
+ "quote_field_suffix": ".standard"
82
  }}
83
  ],
84
  "must": [],
 
86
  {"query_string": {
87
  "default_operator": "AND",
88
  "fields": [
89
+ "acordao_ata^3",
90
+ "documental_doutrina_texto",
91
+ "documental_indexacao_texto",
92
+ "documental_jurisprudencia_citada_texto",
93
+ "documental_observacao_texto",
94
+ "documental_tese_tema_texto^3",
95
+ "documental_tese_texto^3",
96
+ "ementa_texto^3",
97
+ "titulo^6",
98
+ "decisao_texto^2",
99
+ "sumula_texto^3",
100
+ "ramo_direito^1",
101
+ "situacao_sumula^1",
102
+ "materia_noticia^1",
103
+ "titulo_noticia^3",
104
+ "resumo_noticia^3",
105
+ "conteudo_noticia^1",
106
+ "ramo_noticia^1",
107
+ "inteiro_teor_texto"
108
  ],
109
  "query": "__TERMO__",
110
  "tie_breaker": 1,
111
  "fuzziness": "AUTO:4,7",
112
+ "analyzer": "radical_search_analyzer",
113
+ "quote_analyzer": "standard_analyzer",
114
+ "quote_field_suffix": ".standard"
115
  }},
116
  {"query_string": {
117
  "default_operator": "and",
 
119
  "tie_breaker": 1,
120
  "phrase_slop": 20,
121
  "fields": [
122
+ "acordao_ata^3",
123
+ "documental_tese_tema_texto^3",
124
+ "documental_tese_texto^3",
125
+ "ementa_texto^3",
126
+ "decisao_texto^2",
127
+ "situacao_sumula^1",
128
+ "titulo_noticia^3",
129
+ "resumo_noticia^3",
130
+ "conteudo_noticia^1",
131
+ "inteiro_teor_texto^0.5"
132
  ],
133
  "query": "__TERMO__",
134
  "fuzziness": "AUTO:4,7",
135
+ "analyzer": "radical_search_analyzer",
136
+ "quote_analyzer": "standard_analyzer",
137
+ "quote_field_suffix": ".standard"
138
  }},
139
  {"query_string": {
140
  "default_operator": "and",
 
142
  "tie_breaker": 1,
143
  "phrase_slop": 5,
144
  "fields": [
145
+ "documental_acordao_mesmo_sentido_lista_texto",
146
+ "documental_doutrina_texto",
147
+ "documental_indexacao_texto",
148
+ "documental_jurisprudencia_citada_texto",
149
+ "documental_legislacao_citada_texto",
150
+ "documental_observacao_texto",
151
+ "partes_lista_texto",
152
+ "processo_precedente_texto",
153
+ "documental_decisao_mesmo_sentido_lista_texto"
154
  ],
155
  "query": "__TERMO__",
156
  "fuzziness": "AUTO:4,7",
157
+ "analyzer": "radical_search_analyzer",
158
+ "quote_analyzer": "standard_analyzer",
159
+ "quote_field_suffix": ".standard"
160
  }},
161
  {"query_string": {
162
  "default_operator": "and",
163
  "type": "phrase",
164
  "phrase_slop": 1,
165
  "fields": [
166
+ "documental_publicacao_lista_texto",
167
+ "ministro_facet",
168
+ "revisor_processo_nome",
169
+ "orgao_julgador",
170
+ "ramo_direito^1",
171
+ "ramo_noticia^1",
172
+ "procedencia_geografica_completo",
173
+ "processo_classe_processual_unificada_extenso",
174
+ "titulo^6",
175
+ "colac_numero",
176
+ "colac_pagina",
177
+ "sumula_texto^3"
178
  ],
179
  "query": "__TERMO__",
180
  "fuzziness": "AUTO:4,7",
181
  "boost": 0,
182
+ "analyzer": "radical_search_analyzer",
183
+ "quote_analyzer": "standard_analyzer",
184
+ "quote_field_suffix": ".standard"
185
  }}
186
  ]
187
  }
 
349
  {"query_string": {
350
  "default_operator": "AND",
351
  "fields": [
352
+ "processo_codigo_completo",
353
+ "acordao_ata^3",
354
+ "documental_acordao_mesmo_sentido_lista_texto",
355
+ "documental_doutrina_texto",
356
+ "documental_indexacao_texto",
357
+ "documental_jurisprudencia_citada_texto",
358
+ "documental_legislacao_citada_texto",
359
+ "documental_observacao_texto",
360
+ "documental_publicacao_lista_texto",
361
+ "documental_tese_tema_texto^3",
362
+ "documental_tese_texto^3",
363
+ "ementa_texto^3",
364
+ "ministro_facet",
365
+ "revisor_processo_nome",
366
+ "orgao_julgador",
367
+ "partes_lista_texto",
368
+ "procedencia_geografica_completo",
369
+ "processo_classe_processual_unificada_extenso",
370
+ "titulo^6",
371
+ "colac_numero",
372
+ "colac_pagina",
373
+ "decisao_texto^2",
374
+ "documental_decisao_mesmo_sentido_lista_texto",
375
+ "processo_precedente_texto",
376
+ "sumula_texto^3",
377
+ "ramo_direito^1",
378
+ "situacao_sumula^1",
379
+ "materia_noticia^1",
380
+ "titulo_noticia^3",
381
+ "resumo_noticia^3",
382
+ "conteudo_noticia^1",
383
+ "ramo_noticia^1",
384
+ "inteiro_teor_texto"
385
  ],
386
  "query": "__TERMO__",
387
  "type": "cross_fields",
388
  "fuzziness": "AUTO:4,7",
389
+ "analyzer": "radical_search_analyzer",
390
+ "quote_analyzer": "standard_analyzer",
391
+ "quote_field_suffix": ".standard"
392
  }}
393
  ],
394
  "must": [],
 
396
  {"query_string": {
397
  "default_operator": "AND",
398
  "fields": [
399
+ "acordao_ata^3",
400
+ "documental_doutrina_texto",
401
+ "documental_indexacao_texto",
402
+ "documental_jurisprudencia_citada_texto",
403
+ "documental_observacao_texto",
404
+ "documental_tese_tema_texto^3",
405
+ "documental_tese_texto^3",
406
+ "ementa_texto^3",
407
+ "titulo^6",
408
+ "decisao_texto^2",
409
+ "sumula_texto^3",
410
+ "ramo_direito^1",
411
+ "situacao_sumula^1",
412
+ "materia_noticia^1",
413
+ "titulo_noticia^3",
414
+ "resumo_noticia^3",
415
+ "conteudo_noticia^1",
416
+ "ramo_noticia^1",
417
+ "inteiro_teor_texto"
418
  ],
419
  "query": "__TERMO__",
420
  "tie_breaker": 1,
421
  "fuzziness": "AUTO:4,7",
422
+ "analyzer": "radical_search_analyzer",
423
+ "quote_analyzer": "standard_analyzer",
424
+ "quote_field_suffix": ".standard"
425
  }},
426
  {"query_string": {
427
  "default_operator": "and",
 
429
  "tie_breaker": 1,
430
  "phrase_slop": 20,
431
  "fields": [
432
+ "acordao_ata^3",
433
+ "documental_tese_tema_texto^3",
434
+ "documental_tese_texto^3",
435
+ "ementa_texto^3",
436
+ "decisao_texto^2",
437
+ "situacao_sumula^1",
438
+ "titulo_noticia^3",
439
+ "resumo_noticia^3",
440
+ "conteudo_noticia^1",
441
+ "inteiro_teor_texto^0.5"
442
  ],
443
  "query": "__TERMO__",
444
  "fuzziness": "AUTO:4,7",
445
+ "analyzer": "radical_search_analyzer",
446
+ "quote_analyzer": "standard_analyzer",
447
+ "quote_field_suffix": ".standard"
448
  }},
449
  {"query_string": {
450
  "default_operator": "and",
 
452
  "tie_breaker": 1,
453
  "phrase_slop": 5,
454
  "fields": [
455
+ "documental_acordao_mesmo_sentido_lista_texto",
456
+ "documental_doutrina_texto",
457
+ "documental_indexacao_texto",
458
+ "documental_jurisprudencia_citada_texto",
459
+ "documental_legislacao_citada_texto",
460
+ "documental_observacao_texto",
461
+ "partes_lista_texto",
462
+ "processo_precedente_texto",
463
+ "documental_decisao_mesmo_sentido_lista_texto"
464
  ],
465
  "query": "__TERMO__",
466
  "fuzziness": "AUTO:4,7",
467
+ "analyzer": "radical_search_analyzer",
468
+ "quote_analyzer": "standard_analyzer",
469
+ "quote_field_suffix": ".standard"
470
  }},
471
  {"query_string": {
472
  "default_operator": "and",
473
  "type": "phrase",
474
  "phrase_slop": 1,
475
  "fields": [
476
+ "documental_publicacao_lista_texto",
477
+ "ministro_facet",
478
+ "revisor_processo_nome",
479
+ "orgao_julgador",
480
+ "ramo_direito^1",
481
+ "ramo_noticia^1",
482
+ "procedencia_geografica_completo",
483
+ "processo_classe_processual_unificada_extenso",
484
+ "titulo^6",
485
+ "colac_numero",
486
+ "colac_pagina",
487
+ "sumula_texto^3"
488
  ],
489
  "query": "__TERMO__",
490
  "fuzziness": "AUTO:4,7",
491
  "boost": 0,
492
+ "analyzer": "radical_search_analyzer",
493
+ "quote_analyzer": "standard_analyzer",
494
+ "quote_field_suffix": ".standard"
495
  }}
496
  ]
497
  }
 
502
  "pre_tags": ["<em>"],
503
  "post_tags": ["</em>"],
504
  "fields": {
505
+ "ementa_texto": {"fragment_size": 24000, "matched_fields": ["ementa_texto", "ementa_texto.standard"], "type": "fvh"},
506
+ "sumula_texto": {"number_of_fragments": 0, "matched_fields": ["sumula_texto", "sumula_texto.standard"], "type": "fvh"},
507
+ "materia_noticia": {"matched_fields": ["materia_noticia", "materia_noticia.standard"], "type": "fvh"},
508
+ "titulo_noticia": {"matched_fields": ["titulo_noticia", "titulo_noticia.standard"], "type": "fvh"},
509
+ "resumo_noticia": {"fragment_size": 5000, "matched_fields": ["resumo_noticia", "resumo_noticia.standard"], "type": "fvh"},
510
+ "conteudo_noticia": {"fragment_size": 50000, "matched_fields": ["conteudo_noticia", "conteudo_noticia.standard"], "type": "fvh"},
511
+ "acordao_ata": {"fragment_size": 600, "matched_fields": ["acordao_ata", "acordao_ata.standard"], "type": "fvh"},
512
+ "decisao_texto": {"fragment_size": 1200, "matched_fields": ["decisao_texto", "decisao_texto.standard"], "type": "fvh"},
513
+ "documental_tese_texto": {"fragment_size": 2000, "matched_fields": ["documental_tese_texto", "documental_tese_texto.standard"], "type": "fvh"},
514
+ "documental_tese_tema_texto": {"fragment_size": 2000, "matched_fields": ["documental_tese_tema_texto", "documental_tese_tema_texto.standard"], "type": "fvh"},
515
+ "documental_observacao_texto": {"matched_fields": ["documental_observacao_texto", "documental_observacao_texto.standard"], "type": "fvh"},
516
+ "documental_indexacao_texto": {"matched_fields": ["documental_indexacao_texto", "documental_indexacao_texto.standard"], "type": "fvh"},
517
+ "documental_legislacao_citada_texto": {"matched_fields": ["documental_legislacao_citada_texto", "documental_legislacao_citada_texto.standard"], "type": "fvh"},
518
+ "documental_jurisprudencia_citada_texto": {"matched_fields": ["documental_jurisprudencia_citada_texto", "documental_jurisprudencia_citada_texto.standard"], "type": "fvh"},
519
+ "documental_doutrina_texto": {"matched_fields": ["documental_doutrina_texto", "documental_doutrina_texto.standard"], "type": "fvh"},
520
+ "partes_lista_texto": {"matched_fields": ["partes_lista_texto", "partes_lista_texto.standard"], "type": "fvh"},
521
+ "documental_publicacao_lista_texto": {"matched_fields": ["documental_publicacao_lista_texto", "documental_publicacao_lista_texto.standard"], "type": "fvh"},
522
+ "documental_acordao_mesmo_sentido_lista_texto": {"matched_fields": ["documental_acordao_mesmo_sentido_lista_texto", "documental_acordao_mesmo_sentido_lista_texto.standard"], "type": "fvh"},
523
+ "documental_decisao_mesmo_sentido_lista_texto": {"matched_fields": ["documental_decisao_mesmo_sentido_lista_texto", "documental_decisao_mesmo_sentido_lista_texto.standard"], "type": "fvh"},
524
+ "processo_precedente_texto": {"matched_fields": ["processo_precedente_texto", "processo_precedente_texto.standard"], "type": "fvh"},
525
+ "procedencia_geografica_completo": {"matched_fields": ["procedencia_geografica_completo", "procedencia_geografica_completo.standard"], "type": "fvh"},
526
+ "inteiro_teor_texto": {"fragment_size": 600, "matched_fields": ["inteiro_teor_texto", "inteiro_teor_texto.standard"], "type": "fvh"}
527
  }
528
  },
529
  "track_total_hits": True
 
545
  token_cache = {"token": None, "expires_at": 0}
546
 
547
  # ============================================
548
+ # Funções de token e busca
549
  # ============================================
550
+ def get_fresh_token(force_refresh=False):
551
+ global token_cache
552
+ if not force_refresh and token_cache["token"] and time.time() < token_cache["expires_at"]:
553
+ logger.info("Usando token em cache")
554
+ return token_cache["token"]
555
+
556
+ logger.info("Obtendo novo token via Playwright" + (" (forçado)" if force_refresh else ""))
557
+ try:
558
+ with sync_playwright() as p:
559
+ browser = p.chromium.launch(headless=True, args=['--no-sandbox'])
560
+ context = browser.new_context(
561
+ viewport={'width': 1920, 'height': 1080},
562
+ user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
563
+ )
564
+ page = context.new_page()
565
+ page.goto("https://jurisprudencia.stf.jus.br/pages/search", wait_until='domcontentloaded', timeout=30000)
566
+ page.wait_for_timeout(3000)
567
+ cookies = context.cookies()
568
+ token = None
569
+ for cookie in cookies:
570
+ if cookie.get('name') == 'aws-waf-token':
571
+ token = cookie.get('value')
572
+ break
573
+ browser.close()
574
+ if token:
575
+ token_cache["token"] = token
576
+ token_cache["expires_at"] = time.time() + 600 # 10 minutos
577
+ logger.info(f"Token obtido: {token[:30]}...")
578
+ return token
579
+ else:
580
+ logger.warning("Token não encontrado nos cookies")
581
+ return None
582
+ except Exception as e:
583
+ logger.error(f"Erro ao obter token: {str(e)}")
584
+ return None
585
+
586
+ def search_with_token(token, payload):
587
+ if not token:
588
+ return {"success": False, "error": "Token não fornecido"}
589
+ headers = HEADERS.copy()
590
+ headers['Cookie'] = f'aws-waf-token={token}'
591
+ try:
592
+ response = requests.post(URL_API, headers=headers, json=payload, verify=False, timeout=30)
593
+ logger.info(f"Resposta: status {response.status_code}")
594
+ if response.status_code == 200:
595
+ return {"success": True, "data": response.json()}
596
+ elif response.status_code in (403, 202):
597
+ token_cache["token"] = None
598
+ return {"success": False, "error": "Token expirado ou desafio", "status": response.status_code}
599
+ else:
600
+ return {"success": False, "error": f"HTTP {response.status_code}", "status": response.status_code, "text": response.text[:500]}
601
+ except Exception as e:
602
+ logger.error(f"Erro na requisição: {str(e)}")
603
+ return {"success": False, "error": str(e)}
604
+
605
+ def get_document_raw(token, doc_id):
606
+ if not token:
607
+ return {"success": False, "error": "Token não fornecido"}
608
+ headers = HEADERS.copy()
609
+ headers['Cookie'] = f'aws-waf-token={token}'
610
+ headers.pop("Content-Type", None)
611
+ url = f"{URL_API_GET}/{doc_id}"
612
+ try:
613
+ response = requests.get(url, headers=headers, verify=False, timeout=30)
614
+ if response.status_code == 200:
615
+ return {"success": True, "data": response.json()}
616
+ elif response.status_code in (403, 202):
617
+ token_cache["token"] = None
618
+ return {"success": False, "error": "Token expirado", "status": response.status_code}
619
+ else:
620
+ return {"success": False, "error": f"HTTP {response.status_code}"}
621
+ except Exception as e:
622
+ return {"success": False, "error": str(e)}
623
+
624
+ # ============================================
625
+ # HTML TEMPLATE (mantido, mas omitido para brevidade)
626
+ # ============================================
627
+ HTML_TEMPLATE = """<!DOCTYPE html>
628
  <html>
629
  <head>
630
+ <title>⚖️ STF Jurisprudência</title>
631
  <meta charset="utf-8">
632
  <meta name="viewport" content="width=device-width, initial-scale=1">
633
  <style>
634
  body { font-family: 'Segoe UI', Roboto, system-ui, sans-serif; max-width: 1600px; margin: 0 auto; padding: 20px; background: #f0f2f5; }
635
  .container { background: white; border-radius: 12px; padding: 30px; box-shadow: 0 8px 20px rgba(0,0,0,0.1); }
636
+ h1 { color: #1a1a2e; border-bottom: 3px solid #4CAF50; padding-bottom: 15px; }
637
  .info-box { background: #e8f0fe; border-left: 5px solid #2196F3; padding: 15px 20px; margin: 20px 0; border-radius: 8px; }
638
+ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px,1fr)); gap:15px; margin:25px 0; }
639
+ .stat-card { background: white; border:1px solid #e0e0e0; border-radius:10px; padding:20px; text-align:center; }
640
+ .stat-value { font-size:32px; font-weight:bold; color:#1a73e8; }
641
+ .button-group { display:flex; gap:15px; flex-wrap:wrap; margin:25px 0; }
642
+ button { background:#1a73e8; color:white; border:none; padding:14px 28px; border-radius:8px; cursor:pointer; }
643
+ button:hover { background:#1557b0; }
644
+ .loading { display:inline-block; width:20px; height:20px; border:3px solid rgba(255,255,255,0.3); border-top:3px solid white; border-radius:50%; animation:spin 1s linear infinite; }
645
+ @keyframes spin { 0% { transform:rotate(0deg); } 100% { transform:rotate(360deg); } }
646
+ pre { background:#f8f9fa; border:1px solid #e0e0e0; border-radius:10px; padding:20px; overflow:auto; max-height:600px; }
647
+ .success { background:#e6f4ea; border-left:5px solid #34a853; padding:15px; margin:15px 0; }
648
+ .error { background:#fce8e6; border-left:5px solid #ea4335; padding:15px; margin:15px 0; }
649
+ .token-box { background:#1a1a2e; color:#e0e0e0; padding:15px; border-radius:8px; font-family:monospace; margin:15px 0; }
650
+ .filters { background:#f8f9fa; border-radius:8px; padding:20px; margin:20px 0; border:1px solid #e0e0e0; }
651
+ .filter-group { display:grid; grid-template-columns:repeat(auto-fit, minmax(200px,1fr)); gap:15px; }
652
+ .filter-item label { display:block; margin-bottom:8px; color:#5f6368; font-size:14px; font-weight:500; }
653
+ .filter-item input, .filter-item select { width:100%; padding:10px; border:1px solid #ddd; border-radius:6px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  </style>
655
  </head>
656
  <body>
657
  <div class="container">
658
+ <h1>STF Jurisprudência - Visualizador</h1>
659
  <div class="info-box">
660
+ <strong>📌 API de Jurisprudência do STF</strong><br>
 
661
  • Documentos disponíveis: <span id="totalDocs">carregando...</span>
662
  </div>
663
  <div class="stats-grid">
 
686
  </div>
687
  <script>
688
  let lastResult = null, lastToken = null, requestsCount = 0, successCount = 0, failCount = 0, docsCount = 0;
 
 
 
 
 
 
689
  function displayResult(data) {
690
+ if (!data?.result?.hits?.hits) return document.getElementById('result').innerHTML = '<div class="error">Nenhum documento encontrado</div>';
691
  const hits = data.result.hits.hits, total = data.result.hits.total?.value || 0;
692
  let html = `<div class="success">✅ Encontrados ${total.toLocaleString()} documentos. Exibindo ${hits.length} resultados.</div>`;
693
  hits.forEach((hit, idx) => {
694
+ const src = hit._source || {}, docId = src.id || hit._id || `doc_${idx}`;
695
+ html += `<div style="border:1px solid #ccc; margin-bottom:10px; padding:10px;"><h3>${src.titulo || src.processo_codigo_completo || docId}</h3>`;
696
+ html += `<p><strong>ID:</strong> ${docId}</p>`;
697
+ html += `<p><strong>Relator:</strong> ${src.relator_processo_nome || ''}</p>`;
698
+ html += `<p><strong>Órgão:</strong> ${src.orgao_julgador || ''}</p>`;
699
+ html += `<p><strong>Data:</strong> ${src.julgamento_data || ''}</p>`;
700
+ html += `<p><strong>Ementa:</strong> ${(src.ementa_texto || '').substring(0,500)}...</p>`;
701
+ if (src.inteiro_teor_url) html += `<p><a href="${src.inteiro_teor_url}" target="_blank">📄 Inteiro Teor</a></p>`;
702
+ html += `<button onclick="fetchFullDocument('${docId}')">📄 Obter completo</button>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  html += `</div>`;
704
  });
705
  document.getElementById('result').innerHTML = html;
706
  }
 
 
 
 
 
 
707
  async function runSearch() {
708
  const btn = document.getElementById('testBtn'), loading = document.getElementById('loading'), resultDiv = document.getElementById('result');
709
+ btn.disabled = true; loading.style.display = 'inline-block'; resultDiv.innerHTML = '<div class="info-box">⏳ Buscando...</div>';
710
  try {
711
  const resp = await fetch('/api/search-advanced', {
712
  method:'POST', headers:{'Content-Type':'application/json'},
 
784
  const resp = await fetch('/api/health');
785
  const data = await resp.json();
786
  if (data.total_docs) document.getElementById('totalDocs').textContent = data.total_docs.toLocaleString();
 
787
  } catch(e) { document.getElementById('totalDocs').textContent = 'indisponível'; }
788
  };
789
  </script>
790
  </body>
791
+ </html>"""
 
792
 
793
  # ============================================
794
+ # Rotas
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
795
  # ============================================
796
  @app.route('/')
797
  def index():
 
806
  page_size = data.get('pageSize', 100)
807
  sort_order = data.get('sortOrder', 'desc')
808
  base = data.get('base')
809
+
810
  token = get_fresh_token()
811
  if not token:
812
+ return jsonify({"success": False, "error": "Token não disponível"}), 500
813
+
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  payload = PAYLOAD_BUSCA_BASE.copy()
815
  payload["size"] = min(page_size, 250)
 
816
  if base:
817
  payload["post_filter"]["bool"]["must"] = [{"term": {"base": base}}]
818
+ # Substituir placeholder se houver (não usado aqui)
819
+ result = search_with_token(token, payload)
820
+ if result.get("success"):
821
+ return jsonify({"success": True, "token": token, "data": result["data"], "timestamp": time.time()})
822
  else:
823
+ if result.get("status") in (403, 202):
824
+ token = get_fresh_token(force_refresh=True)
825
+ if token:
826
+ result = search_with_token(token, payload)
827
+ if result.get("success"):
828
+ return jsonify({"success": True, "token": token, "data": result["data"], "timestamp": time.time()})
829
+ return jsonify({"success": False, "error": result.get("error")}), 500
830
 
831
  @app.route('/api/document/<doc_id>', methods=['GET'])
832
  def get_document(doc_id):
833
  token = get_fresh_token()
834
  if not token:
835
  return jsonify({"error": "Não foi possível obter token"}), 500
836
+ payload = {
837
+ "query": {"ids": {"values": [doc_id]}},
838
+ "_source": ["id", "titulo", "ementa_texto", "processo_codigo_completo", "relator_processo_nome", "orgao_julgador", "julgamento_data", "inteiro_teor_url"],
839
+ "size": 1
840
+ }
841
+ result = search_with_token(token, payload)
842
  if result.get("success") and result["data"].get("hits", {}).get("hits"):
843
  doc = result["data"]["hits"]["hits"][0]
844
  return jsonify({"success": True, "document": doc})
 
856
  else:
857
  if result.get("status") == 403:
858
  token_cache["token"] = None
859
+ token = get_fresh_token(force_refresh=True)
860
  if token:
861
  result = get_document_raw(token, doc_id)
862
  if result.get("success"):
863
  return jsonify({"success": True, "document": result["data"]})
864
  return jsonify({"success": False, "error": result.get("error", "Falha")}), 500
865
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
866
  @app.route('/api/health', methods=['GET'])
867
  def health():
868
  playwright_status = False
 
906
  return jsonify({"success": True, "message": "Cache limpo"})
907
 
908
  # ============================================
909
+ # ENDPOINT SIMPLIFICADO /busca (com payload exato)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
910
  # ============================================
911
  @app.route('/busca')
912
  def busca_simplificada():
913
+ """
914
+ Endpoint público para busca simplificada de jurisprudência.
915
+ Usa o payload completo do STF e retorna apenas campos essenciais.
916
+ Exemplo: /busca?q=dano%20moral
917
+ """
918
  query = request.args.get('q', '')
919
  if not query:
920
  return jsonify({"erro": "Parâmetro 'q' é obrigatório"}), 400
 
923
  if not token:
924
  return jsonify({"erro": "Não foi possível obter token de acesso"}), 503
925
 
926
+ # Substitui placeholder no payload pelo termo
927
+ payload_str = json.dumps(PAYLOAD_BUSCA_BASE).replace("__TERMO__", query)
928
  payload = json.loads(payload_str)
929
 
930
  headers = HEADERS.copy()
931
  headers['Cookie'] = f'aws-waf-token={token}'
932
 
 
933
  try:
934
  response = requests.post(URL_API, headers=headers, json=payload, verify=False, timeout=30)
935
  except Exception as e:
936
  return jsonify({"erro": f"Falha na comunicação com a API: {str(e)}"}), 502
937
 
938
+ # Se recebeu 202, tenta renovar e faz segunda tentativa
939
  if response.status_code == 202:
940
  logger.info("Recebido status 202, forçando renovação do token...")
941
  token = get_fresh_token(force_refresh=True)
 
954
  data = response.json()
955
  hits = data.get('result', {}).get('hits', {}).get('hits', [])
956
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
957
  resultados = []
958
  for hit in hits:
959
  source = hit.get('_source', {})
 
996
  logger.warning(f"⚠️ Playwright pode não estar configurado: {e}")
997
  port = int(os.environ.get('PORT', 7860))
998
  app.run(host='0.0.0.0', port=port, debug=False)
999
+ ```
1000
+
1001
+ Principais alterações:
1002
+
1003
+ 1. Substituição do PAYLOAD_BUSCA_BASE pelo payload exato do curl, incluindo todos os campos _source, aggs, highlight com matched_fields, inteiro_teor_texto, etc.
1004
+ 2. Placeholder __TERMO__ mantido em todos os locais onde a query aparece (no query_string principal, nos should, e no highlight_query).
1005
+ 3. Endpoint /busca continua simplificado, mapeando os campos essenciais para o formato desejado.
1006
+ 4. Tratamento de token mantido (cache de 10 minutos, renovação forçada em 202/403).
1007
+ 5. Interface web mantida com funcionalidades básicas (busca avançada, download, cópia de token).
1008
+
1009
+ Agora o endpoint /busca?q=... fará exatamente a mesma busca que o site oficial do STF, mas retornará apenas os campos que você especificou.