Adds api functionality

This commit is contained in:
2026-02-04 13:29:15 -03:00
parent fd6756c507
commit 5717cdd254
6 changed files with 825 additions and 132 deletions

View File

@@ -1,156 +1,204 @@
from fastapi import FastAPI,Header from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn import uvicorn
""" import boto3
Simple LangGraph Agent using @tool decorator import json
import time
import io
from pathlib import Path
from urllib.parse import urlparse
from PyPDF2 import PdfReader
Clean implementation with decorator-based tool definitions. from utils.langgraph_agent import RULES, run_agent
"""
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.tools import tool
from langchain_aws import ChatBedrock
app = FastAPI() app = FastAPI()
# Define tools using @tool decorator AWS_REGION = "us-east-2"
rules="""- Mulheres acima de 45 anos ou menopausada
- Homens com mais de 70 anos;
- Osteogênese imperfeita (para esta patologia, poderá haver a liberação de (02) dois
exames ao ano - cada 180 dias);
- RX com osteopenia ou fratura patológica;
- Antecedente pessoal de fratura após os 40 anos: punho, ombros, vértebras, quadril;
- Parente de primeiro grau com osteoporose.
- Mulheres com massa corporal <20kg/m2 ou peso < 57,8kg;
- Menopausa antes dos 45 anos ou hipogonasismo crônico (falência ovariana
precoce);
- Uso de glicocorticóides (>=7,5 prednizona/ dia equivalente por mais três meses, ou
presença de síndrome de cushing;
- Hiperparatireoidismo primário;
- Uso prolongado de anticonvulsivantes (< 10 anos);
- Síndrome de má absorção crônica ou desnutrição doenças inflamatória intestinal
(independente da causa: bariatrica, celiacos, intolerancia a lactose).
- Quimioterapia, se sobrevida esperada for longa (< 5 anos);
- Diminuição documentada de altura;
- Presença de cifose após menopausa.
- Imobilização prolongada"""
SYSTEM_PROMPT="""
You are a assisant, your job is to aprove or not a procedure based on the following rules:"""
+rules+"""
Your input will be a json, evaluate it based on the rules and return:
Aproved: If one of the criteira is met
Reproved: If not a single one of the criterias are met."""
@tool
def add(a: int, b: int) -> int:
"""Add two numbers together.
Args:
a: First number
b: Second number
"""
return a + b
@tool # --- S3 / Textract helpers ---
def multiply(a: int, b: int) -> int:
"""Multiply two numbers. def parse_s3_uri(s3_uri: str) -> tuple[str, str]:
parsed = urlparse(s3_uri)
Args: if parsed.scheme != "s3":
a: First number raise ValueError(f"Not an S3 URI: {s3_uri}")
b: Second number bucket = parsed.netloc
""" key = parsed.path.lstrip("/")
return a * b if not bucket or not key:
raise ValueError(f"Invalid S3 URI: {s3_uri}")
return bucket, key
@tool def get_s3_client():
def get_word_length(word: str) -> int: return boto3.client("s3", region_name=AWS_REGION)
"""Get the length of a word.
Args:
word: The word to measure
"""
return len(word)
@tool def get_textract_client():
def search_info(topic: str) -> str: return boto3.client("textract", region_name=AWS_REGION)
"""Search for information about a topic (mock implementation).
Args:
topic: The topic to search for
"""
# Mock response - replace with actual search/API
return f"Information about {topic}: This is a mock response. In production, this would return real data."
# Define agent state def get_pdf_page_count(pdf_bytes: bytes) -> int:
class AgentState(TypedDict): try:
messages: Annotated[list, add_messages] return len(PdfReader(io.BytesIO(pdf_bytes)).pages)
except Exception:
return 1
# Define tools list def extract_text_from_textract_response(response: dict) -> str:
tools = [add, multiply, get_word_length, search_info] if not response:
return ""
return "\n".join(
# Agent node block["Text"] for block in response.get("Blocks", [])
def call_model(state: AgentState): if block["BlockType"] == "LINE"
"""Call the LLM with current state and tools."""
model = ChatBedrock(
model_id="arn:aws:bedrock:us-east-2:232048051668:application-inference-profile/uy4xskop19zn",
region_name="us-east-2",
provider="anthropic"
) )
model_with_tools = model.bind_tools(tools)
messages = [
SystemMessage(content=SYSTEM_PROMPT)
] + state["messages"]
response = model_with_tools.invoke(messages)
return {"messages": [response]}
# Build the graph def extract_text_from_s3_document(bucket: str, key: str) -> str:
def create_agent(): s3 = get_s3_client()
"""Create and compile the agent graph.""" textract = get_textract_client()
file_ext = Path(key).suffix.lower()
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node("agent", call_model)
workflow.add_node("tools", ToolNode(tools))
# Add edges
workflow.add_edge(START, "agent")
workflow.add_conditional_edges("agent", tools_condition)
workflow.add_edge("tools", "agent")
return workflow.compile()
if file_ext in [".png", ".jpg", ".jpeg"]:
# Main execution response = textract.detect_document_text(
Document={"S3Object": {"Bucket": bucket, "Name": key}}
@app.post("/")
async def root(json:str= Header(...)):
agent = create_agent()
query=json
result = agent.invoke(
{"messages": [HumanMessage(content=query)]},
config={"recursion_limit": 10}
) )
return extract_text_from_textract_response(response)
final_message = result["messages"][-1]
return {"status": "success", "message": final_message.content} if file_ext == ".pdf":
obj = s3.get_object(Bucket=bucket, Key=key)
pdf_bytes = obj["Body"].read()
page_count = get_pdf_page_count(pdf_bytes)
if page_count > 1:
response = textract.start_document_text_detection(
DocumentLocation={"S3Object": {"Bucket": bucket, "Name": key}}
)
job_id = response["JobId"]
while True:
result = textract.get_document_text_detection(JobId=job_id)
status = result["JobStatus"]
if status == "SUCCEEDED":
return extract_text_from_textract_response(result)
elif status == "FAILED":
return ""
time.sleep(2)
else:
response = textract.detect_document_text(
Document={"S3Object": {"Bucket": bucket, "Name": key}}
)
return extract_text_from_textract_response(response)
return ""
# --- Guia processing ---
def process_guia(guia: dict) -> dict:
guia_code = guia.get("guia", {}).get("codigoGuiaLocal", "unknown")
# Step 1: Extract text from all anexos
anexos = guia.get("anexos", [])
all_extracted_texts = []
for anexo_idx, anexo in enumerate(anexos):
s3_uri = anexo.get("urlAnexo") or anexo.get("URLAnexo", "")
nome_arquivo = anexo.get("nomeArquivo", f"attachment_{anexo_idx}")
if not s3_uri or not s3_uri.startswith("s3://"):
anexo["textoExtraido"] = ""
continue
try:
bucket, key = parse_s3_uri(s3_uri)
extracted_text = extract_text_from_s3_document(bucket, key)
except Exception as e:
print(f" Error extracting text from {nome_arquivo}: {e}")
extracted_text = ""
anexo["textoExtraido"] = extracted_text
all_extracted_texts.append(f"--- {nome_arquivo} ---\n{extracted_text}")
file_content = "\n\n".join(all_extracted_texts)
# Step 2: For each servico, run the agent
servicos = guia.get("servicos", [])
avaliacao_resultados = []
for servico in servicos:
codigo_servico_raw = str(servico.get("codigoServico", ""))
code = "".join(c for c in codigo_servico_raw if c.isdigit())
if code not in RULES:
avaliacao_resultados.append({
"codigoServico": codigo_servico_raw,
"resultado": "SKIPPED",
"motivo": f"Codigo '{code}' nao encontrado nas regras",
"agentOutput": ""
})
continue
query_data = {
"atendimento": guia.get("atendimento", {}),
"guia": guia.get("guia", {}),
"servico": servico,
"historico": guia.get("historico", {})
}
query = json.dumps(query_data, indent=2, ensure_ascii=False)
try:
agent_output = run_agent(query, code, file_content)
except Exception as e:
print(f" Agent error for servico {codigo_servico_raw}: {e}")
agent_output = f"ERROR: {str(e)}"
avaliacao_resultados.append({
"codigoServico": codigo_servico_raw,
"resultado": "Aprovado" if "aprov" in "".join(c for c in agent_output.lower() if c.isalnum() or c == ' ') else "Reprovado",
"agentOutput": agent_output
})
guia["avaliacaoAgente"] = avaliacao_resultados
return guia
# --- API models ---
class ProcessRequest(BaseModel):
operadora: dict
guias: list[dict]
# --- Endpoints ---
@app.post("/process")
async def process(request: ProcessRequest):
results = []
for idx, guia in enumerate(request.guias):
try:
enriched = process_guia(guia)
results.append(enriched)
except Exception as e:
results.append({
"error": str(e),
"guia": guia.get("guia", {}).get("codigoGuiaLocal", f"index_{idx}")
})
return {
"status": "success",
"operadora": request.operadora,
"guias": results
}
@app.get("/health") @app.get("/health")
async def health(): async def health():
return {"status": "healthy"} return {"status": "healthy"}
@app.get("/rules")
async def get_rules():
return {"codes": list(RULES.keys())}
if __name__ == "__main__": if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000) uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -9,6 +9,7 @@ RUN pip install --no-cache-dir -r requirements.txt
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY app.py . COPY app.py .
COPY utils/ ./utils/
EXPOSE 8000 EXPOSE 8000

View File

@@ -3,4 +3,6 @@ uvicorn[standard]==0.24.0
langgraph langgraph
langchain-aws langchain-aws
langchain langchain
PyPDF2 PyPDF2
pydantic
boto3

0
code/utils/__init__.py Normal file
View File

View File

@@ -0,0 +1,576 @@
#!/usr/bin/env python3
"""
Simple LangGraph agent using AWS Bedrock.
This agent demonstrates a basic ReAct-style agent with tool calling capabilities.
"""
import boto3
import csv
import json
from pathlib import Path
from typing import Annotated, TypedDict, Literal
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langchain_aws import ChatBedrock
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langchain_core.tools import tool
CODE=""
# Base paths
SCRIPTS_DIR = Path(__file__).parent
JSON_OUTPUT_DIR = SCRIPTS_DIR / "json_output"
TEXTRACT_OUTPUT_DIR = SCRIPTS_DIR / "textract_output"
RULES={
"40808130":"""- Mulheres acima de 45 anos ou menopausada
- Homens com mais de 70 anos;
- Osteogênese imperfeita (para esta patologia, poderá haver a liberação de (02) dois
exames ao ano - cada 180 dias);
- RX com osteopenia ou fratura patológica;
- Antecedente pessoal de fratura após os 40 anos: punho, ombros, vértebras, quadril;
- Parente de primeiro grau com osteoporose.
- Mulheres com massa corporal <20kg/m2 ou peso < 57,8kg;
- Menopausa antes dos 45 anos ou hipogonasismo crônico (falência ovariana
precoce);
- Uso de glicocorticóides (>=7,5 prednizona/ dia equivalente por mais três meses, ou
presença de síndrome de cushing;
- Hiperparatireoidismo primário;
- Uso prolongado de anticonvulsivantes (< 10 anos);
- Síndrome de má absorção crônica ou desnutrição doenças inflamatória intestinal
(independente da causa: bariatrica, celiacos, intolerancia a lactose).
- Quimioterapia, se sobrevida esperada for longa (< 5 anos);
- Diminuição documentada de altura;
- Presença de cifose após menopausa.
- Imobilização prolongada""",
"40808122":"""- Mulheres acima de 45 anos ou menopausada
- Homens com mais de 70 anos;
- Osteogênese imperfeita (para esta patologia, poderá haver a liberação de (02) dois
exames ao ano - cada 180 dias);
- RX com osteopenia ou fratura patológica;
- Antecedente pessoal de fratura após os 40 anos: punho, ombros, vértebras, quadril;
- Parente de primeiro grau com osteoporose.
- Mulheres com massa corporal <20kg/m2 ou peso < 57,8kg;
- Menopausa antes dos 45 anos ou hipogonasismo crônico (falência ovariana
precoce);
- Uso de glicocorticóides (>=7,5 prednizona/ dia equivalente por mais três meses, ou
presença de síndrome de cushing;
- Hiperparatireoidismo primário;
- Uso prolongado de anticonvulsivantes (< 10 anos);
- Síndrome de má absorção crônica ou desnutrição doenças inflamatória intestinal
(independente da causa: bariatrica, celiacos, intolerancia a lactose).
- Quimioterapia, se sobrevida esperada for longa (< 5 anos);
- Diminuição documentada de altura;
- Presença de cifose após menopausa.
- Imobilização prolongada""",
"31303293":"""Mirena e Kyleena possuem critérios diferentes para autorização.
Autorizar sem o parecer da Auditoria Médica, somente quando for beneficiária com
idade entre 18 e 45 anos e se o formulário estiver preenchido conforme as quatro
validações abaixo:
· Identificação completa do paciente e médico assistente;
· Campo 1.1: selecionado: Mirena ou Kyleena;
· Campo 1.2: selecionado:
· Mirena: Menorragia idiopática (sangramento) ou Anticoncepção; ou Kyleena:
Anticoncepção;
· Se os itens 2 e 3 estiverem descritos como “NÃO”, “NÃO SE APLICA”, “- “ou EM
BRANCO.
Encaminhar para a auditoria médica (Mirena ou Kyleena) somente quando:
- Solicitações que incluam outros eventos além do implante de DIU;
- Constar pedido de liberação de anestesia;
- No caso de prazo intervalar - Incluir na perícia, a negação gerada e qual a finalidade
da análise do auditor, informar também a data da última liberação do DIU e a
necessidade de avaliação por conta do prazo de repetição.
- Campo 1.1 selecionado: “Outros”;
- Campo 1.2 selecionado:
Mirena: “Outros” ou nenhuma opção selecionada;Kyleena: Menorragia idiopática (Sangramento) ou “Outros”;
- Se qualquer campo dos itens 2 e 3 descreverem alguma contraindicação.
- Beneficiária menor de 18 anos ou acima de 45 anos.""",
"31005470":"""Autorizar sem o parecer da Auditoria Médica se constar no pedido médico ou
laudos, uma das justificativas abaixo:
·Cálculo biliar;
·Colelitíase;
·Litíase;
·Pólipos acima de 01 cm.
Caso a indicação seja outra ou pólipo menor que 01 cm, encaminhar para
auditoria.""",
"41501012":"""Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Autorizar sem o parecer da Auditoria Médica solicitações de beneficiários acima de
60 anos.Autorizar independentemente da idade solicitações em que a justificativa indique
pelo menos uma das patologias abaixo:
·
4)Catarata senil (CIDS H25-1, H25-2, H25-8, H25-9, H26-1, H26-2, H26-3, H26-
·Outros transtornos do cristalino.
BANDEJA
Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
- Bandeja 143 - AML - OFTALMOLOGIA - obrigatoriamente com a documentação
mínima.
- Bandeja 51 - AML - SEM INFORMAÇÃO MÍNIMA PARA ANÁLISE: Quando for
solicitação eletiva e não houver indicação clínica.
Botão Prioridade: Em caso de beneficiário Intercâmbio, PAC, GLP ou SAC Empresa, é
obrigatório selecionar a bandeja de retorno no botão prioridade, para correta
devolução ao responsável.
ATENDIMENTO INTERCÃMBIO
IMPORTADO (Beneficiários de Outras Unimed's sendo atendidos em Curitiba e
região).
1°Passo: Necessário documentação conforme planilha de racionalização.
2° Passo: Gerar a solicitação em sistema com documentação em anexo.
- Se tiver a documentação, anexar no sistema (ATF não necessita anexar).
- Se não tiver documentação, solicitar que beneficiário providencie para dar entrada.3° Passo: Verificar no monitor de intercâmbio, se o processo está pendente de
perícia.
- Se tiver, abrir sala CHAT e anexar as documentações (ATF não necessita anexar).
- Se não tiver, abrir GPU (ATF não necessita abertura).
4° Passo: Deixar processo pendente na bandeja para monitoramento.
-Se trafegou, bandeja 921.
-Se não trafegou, bandeja 11.
5° Passo: Registrar em anotações administrativas as informações pertinentes ao
processo.""",
"40901254":"""Autorizar no ato do atendimento a 1ª solicitação entre o período (DE 11- 14 SEMANAS
DE GESTAÇÃO).
ou
Se o evento foi gerado decorrente da consulta obstétrica 1.01.01987 - CONSULTA
OBSTÉTRICA - 2ª AVALIAÇÃO (ATÉ 10 SEMANAS DE GESTAÇÃO).""",
"40901262":"""Autorizar no ato do atendimento a 1ª solicitação entre o período de 18 a 24 semanas
de gestação ou se o evento foi gerado decorrente da consulta obstétrica 1.01.01989
- CONSULTA OBSTÉTRICA - 4ª AVALIAÇÃO (ATÉ 18 SEMANAS DE GESTAÇÃO).A repetição poderá ser autorizada desde que se enquadre em um dos critérios
abaixo:
·Anomalia fetal diagnosticada em exame de rotina.
·Antecedentes de doenças hereditárias.
·Consanguinidade.
·Exposição a drogas.
·Idade materna avançada.
·Infecções pré-natais.""",
"4091262":"""Autorizar no ato do atendimento a 1ª solicitação entre o período de 18 a 24 semanas
de gestação ou se o evento foi gerado decorrente da consulta obstétrica 1.01.01989
- CONSULTA OBSTÉTRICA - 4ª AVALIAÇÃO (ATÉ 18 SEMANAS DE GESTAÇÃO).A repetição poderá ser autorizada desde que se enquadre em um dos critérios
abaixo:
·Anomalia fetal diagnosticada em exame de rotina.
·Antecedentes de doenças hereditárias.
·Consanguinidade.
·Exposição a drogas.
·Idade materna avançada.
·Infecções pré-natais.""",
"40304906":"""Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Poderá ser liberada de imediato as solicitações eletivas que contenha a indicação
abaixo, conforme DUT:
Avaliação de pacientes adultos com sinais e sintomas de trombose
venosa profunda dos membros inferiores;
URGÊNCIA/EMERGÊNCIA (INTERNAMENTO e AMBULATORIAL)
Poderá ser liberada de imediato as solicitações de Urgência e Emergência em regime
Internamento ou em situação de Observação em Unidades de Urgência que
contenha a indicação:
Pacientes adultos com sinais e sintomas de embolia pulmonar.
Pneumonia ou síndrome respiratória aguda grave, com quadro suspeito ou
confirmado de infecção pelo SARS-CoV-2 (COVID 19).
PLANOS NÃO REGULAMENTADOS E PLANOS ADAPTADOS
Os planos não regulamentados (UNIPLAN, UNIPLAN 2000 E NOVO UNIPLAN) e
adaptados, asseguram cobertura ilimitada para exames laboratoriais, pois não
seguem o Rol e as diretrizes de utilização. Desta forma podem ser autorizados
segundo regras contratuais.""",
"20203020":"""utorizar sem o parecer da Auditoria Médica desde
que a justificativa indique pelo menos uma das patologias abaixo ou CIDs
relacionados:·Incontinência urinária (CID R32);
·Disfunção miccional (CID N39);
·Incontinência de tensão (“stress”) (CID N39.3);
·Síndrome da bexiga hiperativa;
·Distúrbios do assoalho pélvico;
·Incontinência fecal (CID R15);
·Outros transtornos funcionais do intestino (CID K59);
·Fortalecimento do Assoalho Pélvico pré e/ou pós-parto.""",
"41001230":"""PROTOCOLO DE LIBERAÇÃO
Para beneficiário de outras Unimeds (importado)
Ver item ATENDIMENTO INTERCÂMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Todas as solicitações devem ser direcionadas para análise da Auditoria Médica,
com a documentação mínima.""",
"4034906":"""Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Poderá ser liberada de imediato as solicitações eletivas que contenha a indicação
abaixo, conforme DUT:
Avaliação de pacientes adultos com sinais e sintomas de trombose
venosa profunda dos membros inferiores;
URGÊNCIA/EMERGÊNCIA (INTERNAMENTO e AMBULATORIAL)
Poderá ser liberada de imediato as solicitações de Urgência e Emergência em regime
Internamento ou em situação de Observação em Unidades de Urgência que
contenha a indicação:
Pacientes adultos com sinais e sintomas de embolia pulmonar.
Pneumonia ou síndrome respiratória aguda grave, com quadro suspeito ou
confirmado de infecção pelo SARS-CoV-2 (COVID 19).
PLANOS NÃO REGULAMENTADOS E PLANOS ADAPTADOS
Os planos não regulamentados (UNIPLAN, UNIPLAN 2000 E NOVO UNIPLAN) e
adaptados, asseguram cobertura ilimitada para exames laboratoriais, pois não
seguem o Rol e as diretrizes de utilização. Desta forma podem ser autorizados
segundo regras contratuais.""",
"4101230":"""PROTOCOLO DE LIBERAÇÃO
Para beneficiário de outras Unimeds (importado)
Ver item ATENDIMENTO INTERCÂMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Todas as solicitações devem ser direcionadas para análise da Auditoria Médica,
com a documentação mínima.""",
"20103190":"""Autorizar sem o parecer da Auditoria Médica desde que a justificativa indique pelo
menos uma das patologias abaixo ou CIDs relacionados:
·Incontinência urinária (CID R32);
·Disfunção miccional (CID N39);
·Incontinência de tensão (“stress”) (CID N39.3);·Síndrome da bexiga hiperativa;
·Distúrbios do assoalho pélvico;
·Incontinência fecal (CID R15);
·Outros transtornos funcionais do intestino (CID K59);
·Fortalecimento do Assoalho Pélvico pré e/ou pós-parto.
BENEFICIARIOS ADULTOS (ACIMA DE 18 ANOS):
Se a solicitação estiver enquadrada nas indicações acima poderá ser autorizado sem
encaminhar para o AML até 10 quantidades ao mês;
Ponto de atenção - pertinente 10 sessões total ao mês: considerar códigos
associados ou individuais (eletroestimulação do assoalho pélvico, disfunção vesico
uretral, biofeedback, reabilitação perineal)
BENEFICIARIOS CRIANÇAS (ATÉ 17 ANOS E 11 MESES):
Se a solicitaçãoEste evento está parametrizado para autorizar automaticamente para beneficiários
carteirinha 0032.
· Para beneficiários PAC, início do cartão 09759032, as solicitações destes exames
devem ser liberadas de imediato, revertendo a guia no motivo: 9 - AUTORIZADO
CONFORME PROTOCOLO DE LIBERAÇÃO· Para beneficiários 0032 no intercâmbio exportado, as solicitações destes exames
devem ser liberadas de imediato, revertendo a guia no motivo: 9 - AUTORIZADO
CONFORME PROTOCOLO DE LIBERAÇÃO
NÃO NEGAR ATENDIMENTO.
ATENÇÃO: Exame contratualizado para realização na Unimed Laboratório,
exclusivamente na Megaunidade.
Telefone de contato para mais informações: (41) 3021-5252.
ATENDIMENTO INTERCÂMBIO
Encaminhar para deliberação da Unimed origem. estiver enquadrada nas indicações acima poderá ser autorizado sem
encaminhar para o AML até 04 quantidades ao mês;
Ponto de atenção - pertinente 04 sessões ao mês: para cada modalidade
(eletroestimulação do assoalho pélvico, disfunção vesico uretral, biofeedback,
reabilitação perineal)
Acima desta quantidade encaminhar para a análise da Auditoria Médica com
justificativa médica.""",
"40202542":"""Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Esse código não requer análise da Auditoria Médica, analisar baseado na cobertura e
limite contratual do beneficiário.
Caso seja necessário análise da auditoria médica devido alguma mensagem de
negação, consultar o item "Documentação Mínima".
Para beneficiário Unimed 0032 (atendimento em outra singular/Exportado):
A liberação do procedimento não está condicionada a solicitação dos materiais, pois
o mesmo poderá ser realizado sem a utilização da "Alça de Polipectomia" e da
“Agulha de Esclerose ou Injetor”.
Quando houver solicitação dos materiais posteriormente à autorização do evento,
não há a necessidade de análise da AML para os materiais. Deverá ser cadastrado
conforme informações acima e, liberar. (Circular n.º 056/2008 de 01/08/2008).""",
"40202550":"""Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Esse código não requer análise da Auditoria Médica, analisar baseado na cobertura e
limite contratual do beneficiário.
Caso seja necessário análise da auditoria médica devido alguma mensagem de
negação, consultar o item "Documentação Mínima".
Para beneficiário Unimed 0032 (atendimento em outra singular/Exportado):
A liberação do procedimento não está condicionada a solicitação dos materiais, pois
o mesmo poderá ser realizado sem a utilização da "Alça de Polipectomia" e da
“Agulha de Esclerose ou Injetor”.
Quando houver solicitação dos materiais posteriormente à autorização do evento,
não há a necessidade de análise da AML para os materiais. Deverá ser cadastrado
conforme informações acima e, liberar. (Circular n.º 056/2008 de 01/08/2008).""",
"40323676":"""Este evento está parametrizado para autorizar automaticamente para beneficiários
carteirinha 0032.
· Para beneficiários PAC, início do cartão 09759032, as solicitações destes exames
devem ser liberadas de imediato, revertendo a guia no motivo: 9 - AUTORIZADO
CONFORME PROTOCOLO DE LIBERAÇÃO· Para beneficiários 0032 no intercâmbio exportado, as solicitações destes exames
devem ser liberadas de imediato, revertendo a guia no motivo: 9 - AUTORIZADO
CONFORME PROTOCOLO DE LIBERAÇÃO
NÃO NEGAR ATENDIMENTO.
ATENÇÃO: Exame contratualizado para realização na Unimed Laboratório,
exclusivamente na Megaunidade.
Telefone de contato para mais informações: (41) 3021-5252.
ATENDIMENTO INTERCÂMBIO
Encaminhar para deliberação da Unimed origem.""",
}
MIN_DOC={
"20103190":"""DOCUMENTAÇÃO MÍNIMA
Justificativa Médica e/ou indicação clínica.""",
"20203020":"""DOCUMENTAÇÃO MÍNIMA:
·
Justificativa Médica e/ou indicação clínica.""",
"31303293":"""DOCUMENTAÇÃO MÍNIMA
Beneficiário 0032 (atendidos em Curitiba e em Outras cidades):
·Justificativa médica e/ou indicação clínica;
·Formulário de Solicitação - DIU Hormonal (ver anexo do script).
Beneficiário Intercâmbio/Outras Unimeds:
·
Relatório Médico Detalhado (conforme racionalização).""",
"40202542":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Justificativa Médica e/ou indicação clínica.""",
"40202550":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Justificativa Médica e/ou indicação clínica.""",
"40304906":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Justificativa e/ou indicação clínica.""",
"4034906":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Justificativa e/ou indicação clínica.""",
"40314626":"""DOCUMENTAÇÃO MÍNIMA
Não há;""",
"40314618":"""DOCUMENTAÇÃO MÍNIMA
Não há;""",
"40323676":"""DOCUMENTAÇÃO MÍNIMA
Não há;""",
"40901254":"""Justificativa Médica e/ou indicação clínica informando a idade gestacional e Laudo
do 1º exame sonográfico gestacional realizado.""",
"40901262":"""DOCUMENTAÇÃO MÍNIMA
Relatório médico informando a idade gestacional + laudo do 1o exame sonográfico
gestacional realizado.""",
"4091262":"""DOCUMENTAÇÃO MÍNIMA
Relatório médico informando a idade gestacional + laudo do 1o exame sonográfico
gestacional realizado.""",
"4101230":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado)
Ver item ATENDIMENTO INTERCÂMBIO.
Para beneficiário Unimed 0032 (atendimento local):
·
Relatório Médico detalhado com histórico indicando a necessidade de
realização do exame, além de sinais e sintomas, se possui limitação para teste de
esforço físico;
·
Índice de pré-teste segundo os critérios de Diamons e Forrester revisados
e/ ou TIMI risk;
·
Laudos de exames cardiológicos recentes.""",
"4101230":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado)
Ver item ATENDIMENTO INTERCÂMBIO.
Para beneficiário Unimed 0032 (atendimento local):
·
Relatório Médico detalhado com histórico indicando a necessidade de
realização do exame, além de sinais e sintomas, se possui limitação para teste de
esforço físico;
·
Índice de pré-teste segundo os critérios de Diamons e Forrester revisados
e/ ou TIMI risk;
·
Laudos de exames cardiológicos recentes.""",
"41501144":"""DOCUMENTAÇÃO MÍNIMA
PARA BENEFICIÁRIOS 0032 SENDO ATENDIDO EM CURITIBA, FORA (EXPORTADO),
E PAC:
·
Relatório Médico descrevendo a necessidade de realização do exame.·
Se o pedido for para avaliação de glaucoma, é necessário incluir laudo e
imagem da retinografia.
Atenção: Para as demais situações, NÃO é necessário enviar o laudo da retinografia.
PARA BENEFICIÁRIOS INTERCÂMBIO ESTADUAL, CONFORME DETERMINAÇÃO
DO CERS
·Relatório médico descrevendo a necessidade da realização do exame;
·Laudo e imagem de retinografia.
Atenção: NÃO É OBRIGATÓRIO o envio do laudo e imagem de retinografia para:
> Pacientes com diagnóstico da doença já confirmado e que estejam em tratamento
com aplicação de antiangiogênicos, para as patologias abaixo:
·Doença Macular Relacionada à Idade (DMRI);
·Oclusões Vasculares Retinianas;
·Edema Macular secundário a Diabetes Mellitus.
Caso a informação referente ao tratamento não conste no relatório médico, consultar
eventos recentes para identificar se o beneficiário está em tratamento.
Esclarecemos que o envio de retinografia, poderá eventualmente ser solicitado pela
auditoria para fins de elucidação diagnóstica.
PARA BENEFICIÁRIOS INTERCÂMBIO NACIONAL:
SEGUIR DOCUMENTAÇÃO CONFORME RACIONALIZAÇÃO.""",
"31005101":"""DOCUMENTAÇÃO MÍNIMA:
·Relatório médico detalhado;
·Laudo RX e/ou tomografia e/ou ressonância e/ou ultrassonografia.""",
"31005470":"""DOCUMENTAÇÃO MÍNIMA:
·Relatório médico detalhado;
·Laudo RX e/ou tomografia e/ou ressonância e/ou ultrassonografia.""",
"40808122":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÂMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Justificativa e/ou indicação clínica.""",
"40808130":"""DOCUMENTAÇÃO MÍNIMA
Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÂMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Justificativa e/ou indicação clínica.""",
"41501012":"""Para beneficiário de outras Unimeds (importado):
Ver item ATENDIMENTO INTERCÃMBIO.
Para beneficiário Unimed 0032 (atendimento local):
Justificativa médica e/ou indicação clínica."""
}
# Define the agent state
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
def get_bedrock_client():
"""Initialize and return AWS Bedrock runtime client."""
return boto3.client("bedrock-runtime", region_name="us-east-2")
def create_llm():
"""Create and return the Bedrock LLM."""
return ChatBedrock(
model_id="arn:aws:bedrock:us-east-2:232048051668:application-inference-profile/uy4xskop19zn",
region_name="us-east-2",
provider="anthropic"
)
def create_agent(file_content: str = ""):
"""Create and return the LangGraph agent."""
# Define check tool as closure to capture file_content
@tool
def check(expression: str) -> str:
"""Retrieves the values of the files associated with the input for additional information, if the json is not enough"""
return file_content
# Initialize the LLM with tools
llm = create_llm()
tools = [check]
llm_with_tools = llm.bind_tools(tools)
# Create tool lookup
tool_map = {tool.name: tool for tool in tools}
# Define the agent node
def call_model(state: AgentState) -> dict:
"""Call the LLM with the current state."""
messages = state["messages"]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
# Define the tool execution node
def call_tools(state: AgentState) -> dict:
"""Execute tools based on the last message."""
last_message = state["messages"][-1]
tool_messages = []
for tool_call in last_message.tool_calls:
tool_name = tool_call["name"]
tool_args = tool_call["args"]
if tool_name in tool_map:
result = tool_map[tool_name].invoke(tool_args)
tool_messages.append(
ToolMessage(content=str(result), tool_call_id=tool_call["id"])
)
else:
tool_messages.append(
ToolMessage(
content=f"Tool {tool_name} not found",
tool_call_id=tool_call["id"],
)
)
return {"messages": tool_messages}
# Define the routing function
def should_continue(state: AgentState) -> Literal["tools", "end"]:
"""Determine whether to continue with tools or end."""
last_message = state["messages"][-1]
if hasattr(last_message, "tool_calls") and last_message.tool_calls:
return "tools"
return "end"
# Build the graph
workflow = StateGraph(AgentState)
# Add nodes
workflow.add_node("agent", call_model)
workflow.add_node("tools", call_tools)
# Set entry point
workflow.set_entry_point("agent")
# Add conditional edges
workflow.add_conditional_edges(
"agent",
should_continue,
{
"tools": "tools",
"end": END,
},
)
# Add edge from tools back to agent
workflow.add_edge("tools", "agent")
# Compile the graph
return workflow.compile()
def run_agent(query: str, code: str, file_content: str = "") -> str:
"""
Run the agent with a given query.
Args:
query: The user's question or request
code: The service code to look up rules
file_content: OCR text content for the check tool
Returns:
The agent's final response
"""
agent = create_agent(file_content)
SYSTEM_PROMPT = """You are a AI assistant responsible to check if a person is Allowed or Denied acces to medical procedure based on the following rules:
<rules>
"""+RULES[code]+""""
<\rules>
Also this is the required documentation or infomation for aproval, equivalent information to the required one in another is allowed, do try to indetify every document as one of these if it fits, or contains the required information, or not identified:
"""+MIN_DOC[code]+""""
Your capabilities:
- You can check the OCR of anexed documents if the json input is not enough to determinate if it should be aproved, using the check tool.
For every document, check if the name of the person in json is present, and at output list every document and if it belongs to the person in the request.
Start your answer with either:
Aprovado: if any of the rules are met, firts look only at the input, then check the file
Reprovado: If there aren't any rules met.
And list the document classification and the met criteira, in case of aprovation. Be really precise and succint.
Start the response with either Aprovado or Reprovado, do not add any characters before either of them, even "*" """
user_message = query
if file_content:
user_message += "\n\n<documentos_anexados>\n" + file_content + "\n</documentos_anexados>"
initial_state = {
"messages": [
SystemMessage(content=SYSTEM_PROMPT),
HumanMessage(content=user_message)
]
}
print(f"\nUser: {query}")
print("-" * 50)
# Run the agent
final_state = agent.invoke(initial_state)
# Get the final response
final_message = final_state["messages"][-1]
response = final_message.content if hasattr(final_message, "content") else str(final_message)
print(f"Agent: {response}")
return response

View File

@@ -178,7 +178,57 @@ resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
role = aws_iam_role.ecs_task_execution_role.name role = aws_iam_role.ecs_task_execution_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
} }
resource "aws_iam_role_policy" "bedrock_policy" {
name = "${var.app_name}-bedrock-policy"
role = aws_iam_role.ecs_task_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:GetInferenceProfile"
]
Resource = "*"
}]
})
}
resource "aws_iam_role_policy" "s3_policy" {
name = "${var.app_name}-s3-policy"
role = aws_iam_role.ecs_task_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"s3:GetObject"
]
Resource = "arn:aws:s3:::upflux-doc-analyzer/*"
}]
})
}
resource "aws_iam_role_policy" "textract_policy" {
name = "${var.app_name}-textract-policy"
role = aws_iam_role.ecs_task_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"textract:DetectDocumentText",
"textract:StartDocumentTextDetection",
"textract:GetDocumentTextDetection"
]
Resource = "*"
}]
})
}
# ECS Task Definition # ECS Task Definition
resource "aws_ecs_task_definition" "app" { resource "aws_ecs_task_definition" "app" {
family = var.app_name family = var.app_name
@@ -187,7 +237,7 @@ resource "aws_ecs_task_definition" "app" {
cpu = var.fargate_cpu cpu = var.fargate_cpu
memory = var.fargate_memory memory = var.fargate_memory
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
task_role_arn = aws_iam_role.ecs_task_role.arn
container_definitions = jsonencode([{ container_definitions = jsonencode([{
name = var.app_name name = var.app_name
image = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/${var.ecr_repository_name}:${var.image_tag}" image = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${var.aws_region}.amazonaws.com/${var.ecr_repository_name}:${var.image_tag}"
@@ -246,4 +296,20 @@ resource "aws_ecs_service" "app" {
tags = { tags = {
Name = "${var.app_name}-service" Name = "${var.app_name}-service"
} }
} }
#ECS Task Role (for application to call AWS services)
resource "aws_iam_role" "ecs_task_role" {
name = "${var.app_name}-ecs-task-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}]
})
}