Files
AI-upflux-docprocessor/docs/modulos.md
2026-05-14 15:30:38 -03:00

343 lines
13 KiB
Markdown

# Documentação dos Módulos
## 1. `app.py` — Aplicação Principal
Ponto de entrada da API REST. Define os endpoints FastAPI, autentica as requisições e orquestra o processamento das guias.
### Autenticação
Toda requisição ao endpoint `/process` deve incluir o header:
```
X-API-Key: <chave configurada no Secrets Manager>
```
Se a chave estiver ausente ou incorreta, a API retorna `HTTP 403 Forbidden`.
### Função `process_guia(guia: dict) → dict`
Processa uma única guia de autorização do início ao fim.
**Etapas:**
1. Itera sobre todos os anexos da guia (`guia.anexos`).
2. Para cada anexo, extrai o texto via OCR (em paralelo, usando `asyncio.gather`).
3. Concatena todos os textos extraídos em uma única string (`file_content`).
4. Avalia cada serviço médico (`guia.servicos`) em paralelo, chamando o agente de IA.
5. Adiciona os tempos de processamento ao objeto da guia.
**Campos adicionados à guia:**
| Campo | Tipo | Descrição |
|------------------------------------------|---------|---------------------------------------------------|
| `anexo.textoExtraido` | string | Texto extraído via OCR do arquivo |
| `anexo.pageCount` | int | Número de páginas extraídas |
| `anexo.tempoExtracaoSegundos` | float | Tempo de extração do anexo em segundos |
| `anexo.error` | string | Mensagem de erro (apenas se falhou) |
| `guia.avaliacaoAgente` | list | Lista de avaliações dos serviços |
| `guia.tempoProcessamento.extracaoSegundos` | float | Tempo total de extração de todos os anexos |
| `guia.tempoProcessamento.agentSegundos` | float | Tempo total do agente para todos os serviços |
| `guia.tempoProcessamento.totalSegundos` | float | Tempo total de processamento da guia |
### Função `_extract_anexo(anexo_idx, anexo)`
Função interna de `process_guia`. Processa um único anexo:
- Lê o campo `urlAnexo` ou `URLAnexo` para obter a URI S3.
- Se a URI for inválida ou não começar com `s3://`, define `textoExtraido = ""` e retorna `None`.
- Chama `extract_text_from_s3_document` para baixar e executar OCR.
- Em caso de erro, armazena a mensagem em `anexo.error` e retorna texto vazio.
- Retorna a string formatada `"--- <nomeArquivo> ---\n<texto>"`.
### Modelo de Requisição `ProcessRequest`
```python
class ProcessRequest(BaseModel):
operadora: dict # Metadados da operadora (passado diretamente na resposta)
guias: list[dict] # Lista de guias a serem avaliadas
```
### Endpoints
| Método | Rota | Autenticação | Descrição |
|--------|------------|--------------|---------------------------------------------------|
| POST | `/process` | X-API-Key | Processa todas as guias da requisição |
| GET | `/health` | Não | Verifica se o serviço está ativo |
| GET | `/rules` | Não | Lista os códigos de procedimento com regras ativas|
---
## 2. `services/document_extractor.py` — Extração de Documentos
Responsável por baixar arquivos do S3 e extrair texto via AWS Textract.
### Clientes AWS
Dois clientes boto3 são criados no início do módulo:
- `_s3_input`: cliente S3 autenticado com as credenciais do Secrets Manager (para acesso ao bucket de entrada de documentos).
- `_textract`: cliente Textract usando as credenciais de ambiente padrão.
### Função `parse_s3_uri(s3_uri: str) → tuple[str, str]`
Converte uma URI S3 no formato `s3://bucket/chave` em uma tupla `(bucket, chave)`.
**Erros:**
- Lança `ValueError` se o scheme não for `s3://`.
- Lança `ValueError` se o bucket ou a chave estiverem ausentes.
**Exemplos:**
```python
parse_s3_uri("s3://meu-bucket/pasta/arquivo.pdf")
# → ("meu-bucket", "pasta/arquivo.pdf")
```
### Função `extract_text_from_s3_document(bucket: str, key: str) → tuple[str, int]`
Função assíncrona principal do módulo. Baixa o arquivo e extrai o texto conforme o tipo:
| Extensão | Comportamento |
|-----------------------|-------------------------------------------------------------------|
| `.png`, `.jpg`, `.jpeg` | Envia os bytes diretamente ao Textract. Retorna `(texto, 1)`. |
| `.pdf` | Separa o PDF em páginas individuais. Cada página é enviada ao Textract separadamente (em paralelo). Retorna `(texto_completo, num_paginas)`. |
| Outros | Retorna `("", 0)` sem processar. |
> **Nota:** Operações bloqueantes do boto3 são executadas em threads separadas via `asyncio.to_thread` para não bloquear o event loop.
### Funções Auxiliares
#### `_split_pdf_pages(pdf_bytes: bytes) → list[bytes]`
Usa PyPDF2 para ler o PDF e separar cada página em um arquivo PDF individual (em bytes). Necessário porque o Textract aceita no máximo uma página por chamada via bytes.
#### `_textract_detect_bytes(file_bytes: bytes) → str`
Chama `textract.detect_document_text` com os bytes do arquivo e retorna o texto extraído.
#### `_extract_text_from_textract_response(response: dict) → str`
Filtra os blocos da resposta do Textract, mantendo apenas blocos do tipo `LINE`, e os concatena em uma string com quebras de linha.
---
## 3. `services/authorization.py` — Avaliação de Serviços
Avalia se um serviço médico deve ser aprovado ou reprovado, consultando o agente de IA.
### Função `evaluate_servico(servico, guia, file_content) → dict`
**Parâmetros:**
| Parâmetro | Tipo | Descrição |
|----------------|--------|--------------------------------------------------------|
| `servico` | dict | Objeto do serviço médico com `codigoServico` e outros campos |
| `guia` | dict | Guia completa (atendimento, guia, histórico) |
| `file_content` | str | Texto OCR concatenado de todos os anexos |
**Fluxo:**
1. Extrai o `codigoServico` e mantém apenas os dígitos numéricos.
2. Verifica se o código está presente no dicionário `RULES` (carregado do `rules.yaml`).
- Se não estiver: retorna `resultado = "SKIPPED"` imediatamente.
3. Monta um payload JSON com `atendimento`, `guia`, `servico` e `historico`.
4. Chama `run_agent(query, code, file_content)` de forma assíncrona.
5. Tenta parsear a resposta JSON do agente para extrair o campo `status`.
6. Suporta respostas encapsuladas em blocos de código markdown (` ```json ... ``` `).
**Resposta:**
```json
{
"codigoServico": "40808130",
"resultado": "Aprovado",
"agentOutput": "{ \"status\": \"Aprovado\", ... }",
"input_tokens": 1523,
"output_tokens": 87,
"tempoAgentSegundos": 4.32
}
```
**Resultado possível:**
| Valor | Condição |
|-------------|-------------------------------------------------------|
| `"Aprovado"` | Agente retornou `status: Aprovado` |
| `"Reprovado"` | Agente retornou `status: Reprovado` ou erro no parse |
| `"SKIPPED"` | Código de serviço não possui regras configuradas |
---
## 4. `services/result_store.py` — Persistência de Resultados
Salva os resultados do processamento no S3 de saída.
### Função `save_results(results: list[dict]) → None`
Persiste cada guia processada como um arquivo JSON separado no S3.
**Localização no S3:**
```
s3://<OUTPUT_BUCKET>/<API_VERSION>/<codigoGuiaLocal>_<YYYYMMDD_HHMMSS>.json
```
Exemplo:
```
s3://upflux-doc-analyzer/v1/GUIA-2025-0001_20251117_103000.json
```
**Comportamento:**
- Todas as guias são salvas em paralelo com `asyncio.gather`.
- Erros são silenciados (`except Exception: pass`) para não impedir a resposta da API.
- Usa o cliente S3 padrão de ambiente (sem credenciais explícitas).
---
## 5. `utils/langgraph_agent.py` — Agente de IA (LangGraph)
Núcleo de decisão do sistema. Define o fluxo do agente ReAct usando LangGraph e executa a avaliação via AWS Bedrock (Claude).
### Carregamento das Regras
Ao importar o módulo, o arquivo `rules.yaml` é lido e dois dicionários são populados:
- `RULES`: mapeamento de código de procedimento → critérios de auto-aprovação (texto livre).
- `MIN_DOC`: mapeamento de código de procedimento → documentação mínima exigida (texto livre).
### `AgentState` (TypedDict)
Estado compartilhado entre os nós do grafo LangGraph:
```python
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
```
O campo `messages` acumula todas as mensagens trocadas (System, Human, AI, Tool) ao longo da execução.
### Função `create_agent(file_content: str) → CompiledGraph`
Cria e compila o grafo LangGraph para uma execução específica.
**Ferramenta disponível:**
- `check(expression: str) → str`: Retorna o conteúdo OCR dos documentos. O agente pode chamar essa ferramenta quando o JSON da guia não for suficiente para a decisão.
**Nós do grafo:**
| Nó | Função |
|-----------|------------------------------------------------------------------|
| `agent` | Chama o LLM (Bedrock Claude) com o estado atual das mensagens |
| `tools` | Executa as ferramentas solicitadas pelo LLM |
**Fluxo condicional:**
```
[entrada] → agent → (tem tool_calls?) → Sim → tools → agent → ...
→ Não → END
```
### Função `run_agent(query, code, file_content) → dict`
Executa o agente para um serviço específico.
**System Prompt:** Instruções em inglês que descrevem:
- O papel do agente (aprovador/reprovador de procedimentos médicos).
- Os critérios de auto-aprovação do código (extraídos de `RULES[code]`).
- A documentação mínima exigida (extraída de `MIN_DOC[code]`).
- A lógica de decisão (auto-aprovação tem prioridade sobre documentação mínima).
- O formato obrigatório de resposta (JSON puro, sem markdown):
```json
{
"status": "Aprovado" ou "Reprovado",
"criterio": "descrição breve do critério atendido ou motivo da negação",
"documentos": [
{ "nome": "nome do documento", "pertence_ao_paciente": true }
]
}
```
**Human Message:** JSON da guia + conteúdo OCR dos documentos dentro da tag `<documentos_anexados>`.
**Observabilidade:** Cada execução é rastreada no Langfuse via `CallbackHandler`.
**Retorno:**
```python
{
"response": "<JSON string da resposta do agente>",
"input_tokens": 1523,
"output_tokens": 87
}
```
Os tokens são somados de todas as mensagens AI geradas durante a execução (incluindo chamadas intermediárias de ferramentas).
---
## 6. `utils/config.py` — Configurações
Carrega variáveis de ambiente com valores padrão:
```python
AWS_REGION = os.environ.get("AWS_REGION", "us-east-2")
BEDROCK_MODEL_ARN = os.environ["BEDROCK_MODEL_ARN"] # obrigatória
OUTPUT_BUCKET = os.environ.get("OUTPUT_BUCKET", "upflux-doc-analyzer")
API_VERSION = os.environ.get("API_VERSION", "v1")
LANGFUSE_HOST = os.environ.get("LANGFUSE_HOST", "https://cloud.langfuse.com")
```
`BEDROCK_MODEL_ARN` é obrigatória; se ausente, o serviço falha na inicialização com `KeyError`.
---
## 7. `utils/secrets_manager.py` — Gerenciamento de Segredos
Carrega os segredos do AWS Secrets Manager uma única vez na inicialização:
```python
_client = boto3.client("secretsmanager", region_name="us-east-2")
_response = _client.get_secret_value(SecretId="doc-analyzer")
SECRETS: dict = json.loads(_response["SecretString"])
```
O dicionário `SECRETS` é importado pelos módulos que precisam de credenciais (`app.py`, `document_extractor.py`, `langgraph_agent.py`).
**Chaves esperadas em `SECRETS`:**
| Chave | Usado em |
|------------------------|-----------------------------------|
| `API-KEY` | `app.py` (autenticação da API) |
| `AWS_ACCESS_KEY` | `document_extractor.py` (S3) |
| `AWS_SECRET_KEY` | `document_extractor.py` (S3) |
| `LANGFUSE-SECRET-KEY` | `langgraph_agent.py` (Langfuse) |
| `LANGFUSE-PUBLIC-KEY` | `langgraph_agent.py` (Langfuse) |
---
## 8. `utils/rules.yaml` — Regras de Autorização
Arquivo YAML com dois blocos principais:
### `rules` — Critérios de Auto-Aprovação
Cada entrada é um código de procedimento TUSS mapeado para um texto descrevendo as condições que, se atendidas, permitem aprovação imediata sem auditoria médica.
**Exemplos de códigos:**
| Código | Procedimento |
|--------------|-------------------------------------------|
| `40808130` | Densitometria óssea (osteoporose) |
| `31303293` | DIU Hormonal (Mirena/Kyleena) |
| `31005470` | Colecistectomia (cálculo biliar/colelitíase) |
| `40901254` | Ultrassom obstétrico 1º trimestre |
| `40901262` | Ultrassom obstétrico morfológico |
| `41501012` | Cirurgia de catarata |
| `20103190` | Reabilitação do assoalho pélvico |
| `40304906` | Eco-doppler venoso |
### `min_doc` — Documentação Mínima
Para cada código, descreve quais documentos são necessários quando os critérios de auto-aprovação não são atendidos. O agente verifica a presença dessas informações nos anexos OCR.