| # 📊 FLUXOGRAMA.md — Para.AI Assuntos Jurídicos |
| **Fluxo de dados nas pesquisas disponíveis** |
|
|
| --- |
|
|
| ## Fluxo 1 — GET /busca (full-text) |
|
|
| ``` |
| Cliente |
| │ GET /busca?q=aposentadoria&ramo=DIREITO+PREVIDENCIÁRIO&size=10 |
| ▼ |
| routes.busca_get() |
| │ valida Query params via FastAPI/Pydantic |
| ▼ |
| es_client.buscar(q, ramo, nivel2, nivel3, lei, page, size, com_facets) |
| │ |
| ├─ build_busca_query() |
| │ multi_match: |
| │ query: "aposentadoria" |
| │ fields: [nome_assunto^4, titulo_curto^3, breve_sintese^2, |
| │ glossario, classes_path^2, texto_completo] |
| │ fuzziness: AUTO · prefix_length: 2 |
| │ filter: [term{ramo: "DIREITO PREVIDENCIÁRIO"}] |
| │ highlight: [nome_assunto, titulo_curto, breve_sintese] |
| │ _source.excludes: [texto_completo] |
| │ aggs: {por_ramo, por_nivel2, por_nivel3, por_lei, profundidades} |
| │ from: 0 · size: 10 |
| │ |
| ▼ |
| Elasticsearch 8.12 |
| │ Analyzer juridico_pt: tokenize → lowercase → asciifolding → stem PT |
| │ BM25 scoring com boosts |
| │ Agregações bucket |
| ▼ |
| builders.build_busca_response(raw, took_ms, page, size) |
| │ hits → AssuntoHit(score, Assunto(**src)) |
| │ highlight injetado em breve_sintese |
| │ aggs → Facets(por_ramo, por_nivel2 …) |
| ▼ |
| BuscaResponse {total, pagina, tamanho, took_ms, resultados[], facets} |
| │ ORJSONResponse |
| ▼ |
| Cliente recebe JSON |
| ``` |
|
|
| --- |
|
|
| ## Fluxo 2 — POST /busca-q (estruturada para LLMs) |
|
|
| ``` |
| LLM / Cliente |
| │ POST /busca-q |
| │ {q, campos:[{campo,valor}…], modo, topk, operador, retornar[]} |
| ▼ |
| routes.busca_q_post() |
| │ valida BuscaQRequest (Pydantic) |
| │ valida retornar[] ∈ FICHA_CAMPOS_VALIDOS |
| ▼ |
| es_client.busca_q(q, campos, modo, topk, operador, …, retornar) |
| │ |
| ├─ _ficha_to_es_source(retornar, incluir_texto_completo) |
| │ retornar=[] → {"excludes":["texto_completo"]} |
| │ retornar=[…] → {"includes":[campos_es mapeados]} |
| │ "texto" em retornar OR incluir_texto_completo=True |
| │ → inclui texto_completo |
| │ |
| ├─ build_busca_q_query(q, campos, modo, topk, …) |
| │ must: multi_match(q) com pesos |
| │ should: _clausula_campo(campo, valor, modo) × N |
| │ fuzzy → match com fuzziness AUTO |
| │ exato → term (keyword) |
| │ minimum_should_match: 1 (or) | N (and) |
| │ _source: ← _ficha_to_es_source() |
| │ size: topk |
| │ |
| ▼ |
| Elasticsearch 8.12 |
| │ score combinado: must (q) + should (campos) |
| │ retorna apenas campos _source solicitados |
| ▼ |
| builders.build_busca_q_response(raw, …, retornar) |
| │ hits → FichaHit(score, campos_matched, _src_to_ficha()) |
| │ |
| │ _src_to_ficha(): ← FIX #1 aplicado aqui |
| │ want = set(retornar)|{"id"} se retornar else None |
| │ _want(campo) = True se want is None OR campo in want |
| │ titulo ← hl("titulo", "nome_assunto") |
| │ introducao ← hl("introducao", "breve_sintese") |
| │ definicao ← hl("definicao", "glossario") |
| │ normas ← src["dispositivos_legais"] |
| │ texto ← src["texto_completo"] |
| │ SE (incluir_texto OR |
| │ (want is not None AND "texto" in want)) |
| │ |
| ▼ |
| BuscaQResponse {total, retornados, took_ms, modo, operador, resultados[]} |
| │ payload compacto (~2KB) ← ideal para LLMs |
| ▼ |
| LLM recebe fichas estruturadas |
| ``` |
|
|
| --- |
|
|
| ## Fluxo 3 — GET /autocomplete |
|
|
| ``` |
| Interface (digitação) |
| │ GET /autocomplete?q=aposen&size=8 |
| ▼ |
| routes.autocomplete() |
| ▼ |
| es_client.autocomplete(q, size) |
| │ |
| ├─ build_autocomplete_query("aposen", 8) |
| │ _source: [nome_assunto, titulo_curto, ramo, classes_nivel2] |
| │ bool.should: |
| │ match{nome_assunto.autocomplete: "aposen", boost:2} |
| │ match{titulo_curto.autocomplete: "aposen", boost:1} |
| │ |
| ▼ |
| Elasticsearch |
| │ Tokenizer edge_ngram: min_gram=2, max_gram=20 |
| │ "aposentadoria" → ["ap","apo","apos","aposen","aposent",…] |
| │ Match: docs cujo prefixo == "aposen" |
| │ Rank: boost nome_assunto > titulo_curto |
| ▼ |
| Deduplicação (set) + prioriza nome_assunto |
| ▼ |
| AutocompleteResponse {sugestoes: ["Aposentadoria", "Aposentadoria Especial", …]} |
| ``` |
|
|
| --- |
|
|
| ## Fluxo 4 — GET /hierarquia |
|
|
| ``` |
| Cliente |
| │ GET /hierarquia |
| ▼ |
| routes.hierarquia() |
| ▼ |
| es_client.get_hierarquia() |
| │ |
| ├─ Query ES: |
| │ size: 0 (sem hits, só agregações) |
| │ aggs: |
| │ ramos: terms{classes_nivel1, size:25} |
| │ nivel2: terms{classes_nivel2, size:30} |
| │ nivel3: terms{classes_nivel3, size:30} |
| │ |
| ▼ |
| Elasticsearch — aggregations aninhadas |
| ▼ |
| builders.build_hierarquia_response(raw) |
| │ ramos_buckets → HierarquiaNo(nome, caminho, total, filhos[]) |
| │ Para cada ramo → para cada nivel2 → para cada nivel3 |
| │ Recursão: filhos aninhados |
| ▼ |
| HierarquiaResponse {ramos: [HierarquiaNo{nome, caminho, total, filhos[…]}]} |
| ``` |
|
|
| --- |
|
|
| ## Fluxo 5 — GET /grafo/filhos (drill-down) |
|
|
| ``` |
| Cliente |
| │ GET /grafo/filhos?ancestor=Crimes+contra+o+Patrimônio&size=20 |
| ▼ |
| routes.drill_down(ancestor, size) |
| ▼ |
| es_client.drill_down(ancestor, size) |
| │ |
| ├─ Query ES: |
| │ size: 20 |
| │ _source.excludes: [texto_completo, glossario] |
| │ query: term{classes_ancestors: "Crimes contra o Patrimônio"} |
| │ |
| ▼ |
| Elasticsearch — filtra por campo classes_ancestors (keyword array) |
| ▼ |
| {ancestor, total, filhos:[_source dos hits]} |
| ``` |
|
|
| --- |
|
|
| ## Fluxo 6 — Inicialização do Sistema |
|
|
| ``` |
| docker-compose up |
| ▼ |
| Container app: entrypoint.sh |
| │ 1. Loop: curl http://elasticsearch:9200/_cluster/health |
| │ Aguarda status ≠ red (retry até 60s) |
| │ 2. Download: |
| │ curl -L https://github.com/…/bulk_assuntos.ndjson |
| │ -o /app/data/bulk_assuntos.ndjson |
| │ 3. exec uvicorn app.main:app |
| ▼ |
| FastAPI lifespan (main.py) |
| │ await es_client.setup_es() |
| │ wait_for_es() ← tenacity retry 10x |
| │ Índice existe? |
| │ NÃO → create_index() → bulk_index() |
| │ SIM → count() |
| │ count == 0 → bulk_index() |
| │ count > 0 → skip (já indexado) |
| ▼ |
| bulk_index() |
| │ _iter_bulk_docs(): lê NDJSON, 2 linhas por doc |
| │ async_streaming_bulk(chunk_size=500) |
| │ Log: "5.184 documentos indexados" |
| ▼ |
| API pronta — porta 8000 |
| ``` |
|
|
| --- |
|
|
| *Última atualização: 24/02/2026 · v1.0.0* |
|
|