Adds docs

This commit is contained in:
2026-05-14 15:30:38 -03:00
parent b2469ccd0e
commit a3cde67c96
4 changed files with 921 additions and 0 deletions

294
docs/api.md Normal file
View File

@@ -0,0 +1,294 @@
# Documentação da API
## Base URL
```
http://<host>:8000
```
## Autenticação
Todos os endpoints protegidos requerem o header:
```
X-API-Key: <sua-chave-de-api>
```
Respostas de erro de autenticação:
```json
HTTP 403 Forbidden
{ "detail": "Invalid API key" }
```
---
## Endpoints
### `GET /health`
Verifica se o serviço está ativo.
**Autenticação:** Não requerida
**Resposta:**
```json
HTTP 200 OK
{ "status": "healthy" }
```
---
### `GET /rules`
Lista os códigos de procedimento que possuem regras de autorização configuradas.
**Autenticação:** Não requerida
**Resposta:**
```json
HTTP 200 OK
{
"codes": [
"40808130",
"40808122",
"31303293",
"31005470",
"41501012",
"40901254",
"40901262",
"..."
]
}
```
---
### `POST /process`
Processa uma lista de guias de autorização médica. Para cada guia, extrai texto dos documentos anexados e avalia cada serviço solicitado usando o agente de IA.
**Autenticação:** Requerida (`X-API-Key`)
**Content-Type:** `application/json`
#### Corpo da Requisição
```json
{
"operadora": {
"organizacaoId": "string",
"processId": "string"
},
"guias": [ <lista de objetos guia> ]
}
```
#### Estrutura de uma Guia
```json
{
"atendimento": {
"codigoGuia": "GUIA-2025-0001",
"nomeAtendente": "Maria da Silva",
"codigoContratante": "UNIMED-SP",
"nomeContratante": "Unimed São Paulo",
"codigoPlanoContratado": "PLANO-OURO",
"planoRegulamentado": "true",
"carteirinhaBeneficiario": "1234567890",
"nomeBeneficiario": "João Pereira",
"dataNascimentoBeneficiario": "1980-05-20",
"idadeBeneficiario": 45,
"generoBeneficiario": "M"
},
"guia": {
"codigoGuiaLocal": "GUIA-2025-0001",
"tipoGuia": "SP/SADT",
"nomePrestadorSolicitante": "Dr. Fulano",
"nomePrestadorExecutante": "Hospital X",
"caraterAtendimento": "eletivo",
"tipoAtendimento": "ambulatorial",
"codigoCid": "S72.0",
"indicacaoClinica": "Paciente com fratura de fêmur."
},
"servicos": [
{
"sequenciaServico": 1,
"codigoServico": "40808130",
"descricaoServico": "Densitometria óssea",
"tipoServico": "EXAME",
"quantidadeSolicitada": 1,
"servicoCobertoPlano": "true",
"dataSolicitacao": "2025-11-17T10:15:00"
}
],
"historico": {
"quantidadeHistoricoProcedimento365dias": 1,
"quantidadeHistoricoProcedimentoTotal": 1
},
"anexos": [
{
"codigoGuia": "GUIA-2025-0001",
"tipoAnexo": "LAUDO",
"descricao": "Laudo do exame.",
"nomeArquivo": "laudo.pdf",
"idAnexo": "IMG-000001",
"mimeType": "application/pdf",
"URLAnexo": "s3://meu-bucket/documentos/laudo.pdf"
}
]
}
```
#### Campos Importantes
| Campo | Tipo | Descrição |
|--------------------------------|--------|------------------------------------------------------------|
| `atendimento.nomeBeneficiario` | string | Nome do paciente (verificado nos documentos pelo agente) |
| `servicos[].codigoServico` | string | Código TUSS do procedimento solicitado |
| `anexos[].URLAnexo` | string | URI S3 do documento (`s3://bucket/chave`) |
| `guia.indicacaoClinica` | string | Indicação clínica usada pelo agente na decisão |
#### Resposta de Sucesso
```json
HTTP 200 OK
{
"status": "success",
"operadora": {
"organizacaoId": "asasasabb-1234-5678-aabb-123456abcdef",
"processId": "ihjas1212rjaklpastash"
},
"guias": [
{
"atendimento": { ... },
"guia": { ... },
"servicos": [ ... ],
"historico": { ... },
"anexos": [
{
"nomeArquivo": "laudo.pdf",
"URLAnexo": "s3://meu-bucket/documentos/laudo.pdf",
"textoExtraido": "João Pereira\nDensitometria Óssea\nResultado: ...",
"pageCount": 2,
"tempoExtracaoSegundos": 3.14
}
],
"avaliacaoAgente": [
{
"codigoServico": "40808130",
"resultado": "Aprovado",
"agentOutput": "{\"status\": \"Aprovado\", \"criterio\": \"Mulher acima de 45 anos\", \"documentos\": [{\"nome\": \"laudo.pdf\", \"pertence_ao_paciente\": true}]}",
"input_tokens": 1523,
"output_tokens": 87,
"tempoAgentSegundos": 4.32
}
],
"tempoProcessamento": {
"extracaoSegundos": 3.14,
"agentSegundos": 4.32,
"totalSegundos": 7.86
}
}
]
}
```
#### Campos da Avaliação (`avaliacaoAgente`)
| Campo | Tipo | Valores possíveis | Descrição |
|------------------------|--------|------------------------------------------|---------------------------------------------------|
| `codigoServico` | string | — | Código original conforme enviado na requisição |
| `resultado` | string | `"Aprovado"`, `"Reprovado"`, `"SKIPPED"` | Decisão final do agente |
| `agentOutput` | string | JSON string | Resposta completa do agente (status + critério + documentos) |
| `input_tokens` | int | — | Total de tokens de entrada consumidos |
| `output_tokens` | int | — | Total de tokens de saída gerados |
| `tempoAgentSegundos` | float | — | Tempo de execução do agente em segundos |
#### Resultado `"SKIPPED"`
Retornado quando o código de procedimento não possui regras configuradas no `rules.yaml`:
```json
{
"codigoServico": "99999999",
"resultado": "SKIPPED",
"motivo": "Codigo '99999999' nao encontrado nas regras",
"agentOutput": "",
"tempoAgentSegundos": 0
}
```
#### Erro em Guia Individual
Se uma guia falhar completamente, ela é substituída por um objeto de erro na lista (as demais guias continuam sendo processadas):
```json
{
"error": "mensagem de erro",
"guia": "GUIA-2025-0001"
}
```
---
## Exemplo Completo
### Requisição
```bash
curl -X POST http://localhost:8000/process \
-H "Content-Type: application/json" \
-H "X-API-Key: sua-chave-aqui" \
-d '{
"operadora": {
"organizacaoId": "org-123",
"processId": "proc-456"
},
"guias": [
{
"atendimento": {
"nomeBeneficiario": "Ana Paula Costa",
"idadeBeneficiario": 52,
"generoBeneficiario": "F",
"carteirinhaBeneficiario": "9876543210"
},
"guia": {
"codigoGuiaLocal": "GUIA-2025-0042",
"indicacaoClinica": "Rastreamento de osteoporose pós-menopausa.",
"codigoCid": "M81.0"
},
"servicos": [
{
"codigoServico": "40808130",
"descricaoServico": "Densitometria óssea",
"quantidadeSolicitada": 1
}
],
"historico": {},
"anexos": [
{
"nomeArquivo": "pedido_medico.pdf",
"URLAnexo": "s3://meu-bucket/guias/pedido_medico.pdf"
}
]
}
]
}'
```
## Formato de Saída no S3
Após cada chamada ao `/process`, os resultados são salvos automaticamente:
```
s3://<OUTPUT_BUCKET>/<API_VERSION>/<codigoGuiaLocal>_<YYYYMMDD_HHMMSS>.json
```
Exemplo:
```
s3://upflux-doc-analyzer/v1/GUIA-2025-0042_20251117_143022.json
```
O conteúdo é o mesmo objeto da guia processada (JSON completo, incluindo texto extraído e avaliações).

170
docs/langfuse.md Normal file
View File

@@ -0,0 +1,170 @@
# Observabilidade com Langfuse
## O que é o Langfuse
O [Langfuse](https://langfuse.com) é uma plataforma de observabilidade para aplicações LLM. No doc-processor ele é usado para rastrear cada chamada ao agente de IA, permitindo:
- Visualizar o histórico completo de mensagens de cada execução (System, Human, AI, Tool calls).
- Monitorar consumo de tokens (entrada e saída) por execução e por procedimento.
- Medir latência das chamadas ao modelo.
- Inspecionar as respostas brutas do agente para depuração.
- Auditar decisões do sistema ao longo do tempo.
---
## Como está integrado no código
### Inicialização (`utils/langgraph_agent.py`)
O cliente Langfuse é criado **uma única vez** na importação do módulo, usando as credenciais do Secrets Manager:
```python
from langfuse import Langfuse
from langfuse.langchain import CallbackHandler
langfuse = Langfuse(
secret_key=SECRETS["LANGFUSE-SECRET-KEY"],
public_key=SECRETS["LANGFUSE-PUBLIC-KEY"],
host=LANGFUSE_HOST, # padrão: https://cloud.langfuse.com
)
```
### Rastreamento de execuções (`run_agent`)
Para cada chamada ao agente, um `CallbackHandler` do Langfuse é instanciado e passado como callback ao LangGraph. Isso faz com que todas as mensagens trocadas durante a execução sejam enviadas automaticamente ao Langfuse:
```python
langfuse_handler = CallbackHandler()
config = {"callbacks": [langfuse_handler]}
final_state = await agent.ainvoke(initial_state, config=config)
```
O `CallbackHandler` captura automaticamente:
- O conteúdo do System Prompt e da mensagem Human enviados ao modelo.
- Cada resposta do LLM (incluindo tool calls intermediárias).
- As respostas das ferramentas (`ToolMessage`).
- Tokens usados em cada step.
### Flush assíncrono
Após cada execução do agente, o buffer do Langfuse é enviado ao servidor de forma assíncrona para garantir que nenhum trace seja perdido mesmo que o processo seja encerrado logo após:
```python
await asyncio.to_thread(langfuse.flush)
```
> **Por que `to_thread`?** O método `flush()` do Langfuse é bloqueante. Executá-lo em uma thread separada evita que ele bloqueie o event loop do FastAPI enquanto aguarda a resposta da rede.
---
## Contagem de Tokens
Além do rastreamento automático pelo Langfuse, o código também acumula os tokens manualmente a partir dos metadados das mensagens AI para retorná-los na resposta da API:
```python
input_tokens = 0
output_tokens = 0
for msg in final_state["messages"]:
usage = getattr(msg, "usage_metadata", None)
if usage:
input_tokens += usage.get("input_tokens", 0)
output_tokens += usage.get("output_tokens", 0)
```
Os tokens são somados de **todas** as mensagens AI geradas durante a execução, incluindo chamadas intermediárias quando o agente usa a ferramenta `check`. Esses valores são retornados em cada `avaliacaoAgente`:
```json
{
"codigoServico": "40808130",
"resultado": "Aprovado",
"input_tokens": 1523,
"output_tokens": 87
}
```
---
## Criando uma Organização e um Projeto no Langfuse
### 1. Criar conta e organização
1. Acesse o ip do Ec2 via http na porta 3000, após liberar no security group para o ip desejado, e clique em **Sign Up**.
2. Após o login, o Langfuse pede para criar ou entrar em uma **organização**. A organização agrupa todos os projetos e membros da equipe.
- Clique em **Create new organization**, preencha o nome e confirme.
### 2. Criar um projeto
Dentro da organização:
1. Crie um novo projeto.
2. Nos passos iniciais tem um botão para criar as Api keys, copie os valores de Secret e Public.
### 3. Apontar o sistema para o novo projeto
Atualize os segredos no AWS Secrets Manager (`doc-analyzer`) com os novos valores:
Basta ir no Secrets manager, escolher o segredo doc-analyzer, ir em retrieve secret value e edit. Atualize com os novos valores.
Após atualizar, talvez seja nescessário reiniciar o container/serviço para que ele carregue as novas credenciais:
```bash
# ECS — forçar novo deploy
aws ecs update-service \
--cluster <nome-do-cluster> \
--service <nome-do-servico> \
--force-new-deployment
```
Nas próximas execuções, os traces aparecerão no novo projeto dentro da nova organização.
---
## Configuração
### Variável de Ambiente
| Variável | Padrão | Descrição |
|------------------|------------------------------|------------------------------------|
| `LANGFUSE_HOST` | `http://13.59.214.47:3000/` | Endereço do servidor Langfuse. Pode ser uma instância self-hosted. |
### Segredos (AWS Secrets Manager — `doc-analyzer`)
| Chave | Descrição |
|-----------------------|------------------------------------|
| `LANGFUSE-SECRET-KEY` | Chave secreta do projeto Langfuse |
| `LANGFUSE-PUBLIC-KEY` | Chave pública do projeto Langfuse |
As chaves são obtidas no painel do Langfuse em **Settings → API Keys**.
### Self-hosted
Para usar uma instância própria do Langfuse (ex: rodando internamente), basta configurar (variável de ambiente do ecs):
```bash
LANGFUSE_HOST=http://13.59.214.47:3000/
```
E atualizar as chaves correspondentes no Secrets Manager.
---
## O que visualizar no painel
Cada chamada ao `POST /process` gera uma ou mais **traces** no Langfuse (uma por serviço avaliado). Em cada trace é possível ver:
1. **Input**: JSON da guia + conteúdo OCR dos documentos enviados ao agente.
2. **System Prompt**: Critérios de auto-aprovação e documentação mínima do procedimento avaliado.
3. **Steps intermediários**: Se o agente chamou a ferramenta `check`, aparece como um step separado com o conteúdo retornado.
4. **Output**: JSON final com `status`, `criterio` e lista de `documentos`.
5. **Tokens e latência**: Custo estimado e tempo de resposta do modelo.
---
## Depuração
Se as traces não aparecerem no painel:
1. Verifique se `LANGFUSE-SECRET-KEY` e `LANGFUSE-PUBLIC-KEY` estão corretos no Secrets Manager.
2. Confirme que `LANGFUSE_HOST` aponta para o servidor correto.
3. Verifique os logs do container — erros de conexão com o Langfuse são silenciosos por padrão (o `flush` falha sem derrubar o serviço).
4. Confirme que o container tem acesso de rede ao host do Langfuse.

342
docs/modulos.md Normal file
View File

@@ -0,0 +1,342 @@
# 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.

115
docs/visao-geral.md Normal file
View File

@@ -0,0 +1,115 @@
# Doc-Processor — Visão Geral do Sistema
## O que é
O **Doc-Processor** é um serviço de análise automatizada de guias de autorização médica. Ele recebe solicitações de procedimentos médicos (guias), extrai o texto dos documentos anexados via OCR, e utiliza um agente de inteligência artificial (LLM) para decidir se cada serviço solicitado deve ser **Aprovado** ou **Reprovado**, com base em regras de negócio configuradas para cada código de procedimento.
O sistema foi desenvolvido para operar no contexto de operadoras de saúde (ex: Unimed), reduzindo o trabalho manual dos auditores médicos.
---
## Fluxo de Processamento
```
POST /process
├─► Para cada guia (em paralelo):
│ │
│ ├─► Para cada anexo (em paralelo):
│ │ ├─► Baixa o arquivo do S3
│ │ ├─► OCR via AWS Textract
│ │ └─► Armazena texto extraído na guia
│ │
│ └─► Para cada serviço (em paralelo):
│ ├─► Verifica se o código existe nas regras
│ ├─► Monta o payload JSON (atendimento + guia + serviço + histórico)
│ ├─► Chama o agente LangGraph com o texto dos documentos
│ └─► Retorna Aprovado / Reprovado / SKIPPED
├─► Salva todos os resultados no S3 (bucket de saída)
└─► Retorna resposta HTTP com as avaliações
```
---
## Decisão do Agente
O agente segue a seguinte lógica de decisão para cada serviço:
1. **Critérios de auto-aprovação** (`rules.yaml rules`): Se qualquer um desses critérios estiver presente na guia ou nos documentos, o procedimento é aprovado imediatamente.
2. **Documentação mínima** (`rules.yaml min_doc`): Se nenhum critério de auto-aprovação for atendido, o agente verifica se a documentação mínima exigida está presente. Se estiver (mesmo que em tipo diferente de documento), o procedimento é aprovado.
3. **Reprovação**: Se nem os critérios de auto-aprovação nem a documentação mínima estiverem presentes, o procedimento é reprovado.
O agente também verifica, em cada documento, se o nome do beneficiário da guia está presente.
---
## Tecnologias Utilizadas
| Tecnologia | Função |
|-------------------|-----------------------------------------------------|
| FastAPI | Framework web / API REST |
| LangGraph | Orquestração do agente de IA (fluxo ReAct) |
| LangChain AWS | Integração com AWS Bedrock (LLM Claude/Anthropic) |
| AWS Bedrock | Modelo de linguagem (LLM) para decisão |
| AWS Textract | OCR de PDFs e imagens |
| AWS S3 | Armazenamento de documentos de entrada e resultados |
| AWS Secrets Manager | Gerenciamento seguro de credenciais |
| Langfuse | Observabilidade e rastreamento de chamadas ao LLM |
| PyPDF2 | Separação de páginas de PDFs para OCR por página |
| Pydantic | Validação do corpo da requisição |
| Docker | Containerização do serviço |
---
## Estrutura de Diretórios
```
code/
├── app.py # Ponto de entrada da API FastAPI
├── dockerfile # Imagem Docker da aplicação
├── Makefile # Automação de build e deploy
├── requirements.txt # Dependências Python
├── services/
│ ├── authorization.py # Avalia cada serviço via agente de IA
│ ├── document_extractor.py # Extrai texto de documentos no S3 via Textract
│ └── result_store.py # Persiste resultados no S3
├── utils/
│ ├── config.py # Variáveis de ambiente / configurações
│ ├── langgraph_agent.py # Definição e execução do agente LangGraph
│ ├── rules.yaml # Regras de autorização por código de procedimento
│ └── secrets_manager.py # Carrega segredos do AWS Secrets Manager
└── document/ # Documentação do sistema (esta pasta)
├── visao-geral.md
├── modulos.md
└── api.md
```
---
## Configuração
### Variáveis de Ambiente
| Variável | Obrigatória | Padrão | Descrição |
|----------------------|-------------|-------------------------------|----------------------------------------------|
| `AWS_REGION` | Não | `us-east-2` | Região AWS usada para todos os serviços |
| `BEDROCK_MODEL_ARN` | **Sim** | — | ARN do modelo Claude no AWS Bedrock |
| `OUTPUT_BUCKET` | Não | `upflux-doc-analyzer` | Bucket S3 onde os resultados são salvos |
| `API_VERSION` | Não | `v1` | Prefixo de versão usado na chave do S3 |
| `LANGFUSE_HOST` | Não | `https://cloud.langfuse.com` | Host do servidor Langfuse (observabilidade) |
### Segredos (AWS Secrets Manager — Secret ID: `doc-analyzer`)
| Chave | Descrição |
|------------------------|----------------------------------------------------|
| `API-KEY` | Chave de autenticação da API (`X-API-Key`) |
| `AWS_ACCESS_KEY` | Access Key para acesso ao S3 de entrada |
| `AWS_SECRET_KEY` | Secret Key para acesso ao S3 de entrada |
| `LANGFUSE-SECRET-KEY` | Chave secreta do Langfuse |
| `LANGFUSE-PUBLIC-KEY` | Chave pública do Langfuse |
---