# 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: ``` 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 `"--- ---\n"`. ### 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:////_.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 ``. **Observabilidade:** Cada execução é rastreada no Langfuse via `CallbackHandler`. **Retorno:** ```python { "response": "", "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.