# Crie um Chat com Dashboard utilizando **Oracle Autonomous Database SELECT AI** ## 1. Introdução Este tutorial cria uma **interface web** simples (chat + cards com **gráficos** e **tabelas**) transformando **perguntas em linguagem natural** (pt-BR) em **SQL** automaticamente, usando o **SELECT AI** do Autonomous Database. É ideal para cenários como **Pronto-Socorro (ER)**, vendas, logística etc., quando usuários de negócio querem **insights imediatos** sem escrever SQL. ### Como funciona: - O **Flask** serve uma página com um campo de pergunta e um histórico de respostas (cada resposta pode ter **SQL gerado**, **tabela** e **gráfico Chart.js**). - Ao enviar a pergunta, o backend chama `SELECT AI 'sua pergunta' FROM ` — quem gera e executa o SQL é o **SELECT AI** dentro do banco. - O app formata os resultados, tenta inferir o **tipo de gráfico** e permite **exportar tudo** para **PDF** (html2canvas + jsPDF, no navegador) e **Excel** (duas opções: no navegador com XlsxPopulate ou **no servidor** com `openpyxl`). ### Tecnologias principais: - **Oracle Autonomous Database 23ai** com **SELECT AI** e **DBMS_CLOUD_AI** (perfis de LLM) - **Python** + **Flask** + **python-oracledb (Thin)** com **mTLS** (wallet) - **Front-end**: Chart.js, html2canvas + jsPDF e XlsxPopulate (via CDN) --- ## 2. Pré-requisitos **Oracle Cloud** - Um **Autonomous Database (Serverless)** em **23ai** (ou com SELECT AI disponível). - **Saída de rede do ADB** para acessar o serviço OCI Generative AI. - Permissões para usar **DBMS_CLOUD** / **DBMS_CLOUD_AI** e criar **perfis de IA**. - **Modelo/Região** do OCI Generative AI habilitados (ex.: `cohere.command-r-08-2024` em `us-chicago-1`). **Uma VM Linux para hospedagem da página Web (app Flask)** - **Python 3.10+** (recomendado). - Pacotes: `flask`, `oracledb`, `openpyxl`, `pillow`. - **Wallet** do ADB (ZIP baixado do console). --- ## 3. Entendendo o código (trecho a trecho) O código-fonte pode ser baixado aqui: [app_select_ai.py](./files/app_select_ai.py) ### Conexão e sessão com perfil de IA - Lê **config** (wallet, alias TNS, usuário/senha). - Define **`TNS_ADMIN`**. - `session_callback` ativa o **perfil de IA** (garante SELECT AI pronto em cada sessão). ![img_1.png](images/img_1.png) ### run_select_ai - Monta a sentença **`SELECT AI 'pergunta' FROM `**. - Executa e retorna **SQL gerado**, **headers/rows** e usuário/perfil ativos. ![img_2.png](images/img_2.png) ### build_chart e format_table - `build_chart`: heurísticas para inferir gráfico (pizza, barras, linha). - `format_table`: limita registros e devolve `{headers, rows}`. ![img_3.png](images/img_3.png) ### Front-end (PAGE) - **Chart.js** desenha gráficos; toolbar alterna tipos. - **Exportar PDF**: usa html2canvas + jsPDF. - **Exportar Excel**: cliente (XlsxPopulate) ou servidor (openpyxl). ### Histórico (session) - Mantém últimos 10 cards em `timeline`. --- ## 4. SELECT AI no Autonomous Database - Permite **NL→SQL** direto no banco. - Configuração feita via **Perfis de IA** (`DBMS_CLOUD_AI.CREATE_PROFILE`). - Perfis incluem provider, modelo, region, credential e lista de objetos (tables/views). - Comandos auxiliares: `showsql`, `explain`, `narrate` etc. --- ## 5. Instalação e implantação ### 5.1. Criar usuário e permissionamento como ADMIN Execute estes comandos como ADMIN no Autonomous Database: ```sql CREATE USER MEU_USUARIO IDENTIFIED BY "SenhaForte123"; ALTER USER MEU_USUARIO QUOTA UNLIMITED ON DATA; GRANT CREATE SESSION TO MEU_USUARIO; GRANT CREATE TABLE, CREATE VIEW, CREATE SEQUENCE TO MEU_USUARIO; GRANT SELECT ON ADMIN.DATASET_ED_ADMISSION TO MEU_USUARIO; GRANT EXECUTE ON DBMS_CLOUD TO MEU_USUARIO; GRANT EXECUTE ON DBMS_CLOUD_AI TO MEU_USUARIO; ``` ### 5.2 Escolher o método de autenticação do SELECT AI Há duas opções. Escolha uma opção (**A ou B**) conforme necessidade: **Opção A** — Resource Principal (RP) (recomendada no ADB) Sem armazenar chave no banco; usa a identidade do serviço ADB. ```sql BEGIN DBMS_CLOUD_ADMIN.ENABLE_PRINCIPAL_AUTH(provider => 'OCI'); -- habilita provedor OCI no ADB END; / BEGIN DBMS_CLOUD_ADMIN.ENABLE_RESOURCE_PRINCIPAL(); -- ativa RP no banco END; / BEGIN DBMS_CLOUD_ADMIN.ENABLE_PRINCIPAL_AUTH( -- permite o schema usar RP provider => 'OCI', username => 'MEU_USUARIO' ); END; / ``` **Opção B** — Credencial com API Key Armazena uma credencial (OCID do usuário IAM + chave privada). Substitua username e password pelos seus valores (OCID e chave/PEM). ```sql BEGIN DBMS_CLOUD.CREATE_CREDENTIAL( credential_name => 'OCI_GENAI_CRED', username => 'ocid1.user.oc1..aaaa...vx', -- OCID do usuário IAM password => 'SUA_CHAVE_PRIVADA_OU_CONTEUDO' -- chave/PEM ); END; / ``` ### 5.3 Criar o(s) Perfil(is) de IA (SELECT AI) O perfil define provedor, região, credencial/RP, quais objetos o LLM pode usar e (opcionalmente) o modelo. Exemplo 1 — Perfil com Resource Principal ```sql BEGIN DBMS_CLOUD_AI.CREATE_PROFILE( 'OCI_GENAI', '{ "provider": "OCI", "credential_name": "OCI$RESOURCE_PRINCIPAL", "object_list": [ { "owner": "ADMIN" } ], "model": "cohere.command-r-08-2024", "oci_runtimetype": "COHERE", "temperature": "0.4" }' ); END; / ``` Exemplo 2 — Perfil principal da aplicação (com Credencial) Inclui explicitamente os objetos que o SELECT AI pode usar: ```sql BEGIN DBMS_CLOUD_AI.CREATE_PROFILE( profile_name => 'OCI_GENERATIVE_AI_PROFILE', attributes => '{ "provider":"OCI", "region":"us-chicago-1", "credential_name":"OCI$RESOURCE_PRINCIPAL", "object_list":[ {"owner":"ADMIN","name":"DATASET_ED_ADMISSION"}, {"owner":"MEU_USUARIO","name":"NLU_ED_ADMISSION"} ], "model":"cohere.command-r-08-2024" }' ); END; / ``` >**Nota:** 💡 Dica: se preferir, você pode omitir "model" e deixar o ADB usar o modelo padrão da região/provedor. >**Nota:** 🔒 Segurança: liste somente os objetos necessários em object_list. ### 5.4 Ativar o perfil e torná-lo ativo na sessão ```sql BEGIN DBMS_CLOUD_AI.ENABLE_PROFILE('OCI_GENERATIVE_AI_PROFILE'); END; / EXEC DBMS_CLOUD_AI.SET_PROFILE('OCI_GENERATIVE_AI_PROFILE'); SELECT DBMS_CLOUD_AI.GET_PROFILE() AS active_after FROM dual; ``` >**IMPORTANTE:** É necessário executar este comando abaixo antes de cada consulta SELECT AI: ```sql CALL DBMS_CLOUD_AI.SET_PROFILE('OCI_GENERATIVE_AI_PROFILE'); ``` ### 5.5 Enriquecer o vocabulário Enriquecer o vocabulário com COMMENT ON na tabela gerada no tutorial: [Hospital Risk Admission Prediction with Machine Learning](https://github.com/hoshikawa2/hospital_risk_admission) Comentários ajudam o LLM a entender o domínio (descrições de tabelas/colunas). ```sql COMMENT ON TABLE DATASET_ED_ADMISSION IS 'Tabela de pacientes que deram entrada no pronto-socorro / ER patients admission table'; COMMENT ON COLUMN DATASET_ED_ADMISSION.subject_id IS 'Patient ID / ID do paciente (unique identifier)'; COMMENT ON COLUMN DATASET_ED_ADMISSION.hadm_id IS 'Hospital admission ID / ID da internação hospitalar (NULL if not admitted)'; COMMENT ON COLUMN DATASET_ED_ADMISSION.stay_id IS 'ER stay ID / ID da estadia no pronto-socorro, ou o mesmo que o ID de internação'; COMMENT ON COLUMN DATASET_ED_ADMISSION.intime IS 'ER entry timestamp / Data-hora de entrada no pronto-socorro (use EXTRACT(MONTH) for month filter)'; COMMENT ON COLUMN DATASET_ED_ADMISSION.outtime IS 'ER discharge timestamp / Data-hora de saída do pronto-socorro'; COMMENT ON COLUMN DATASET_ED_ADMISSION.gender IS 'Gender (Male or M/Female or F) / Sexo (Masculino ou M/Feminino ou F)'; COMMENT ON COLUMN DATASET_ED_ADMISSION.race IS 'Race/Ethnicity / Raça ou etnia do paciente'; COMMENT ON COLUMN DATASET_ED_ADMISSION.arrival_transport IS 'Arrival transport mode (ambulance, walk) / Forma de chegada (ambulância, caminhada)'; COMMENT ON COLUMN DATASET_ED_ADMISSION.disposition IS 'Disposition after ER (ADMITTED, HOME, etc.) / Destino após atendimento'; COMMENT ON COLUMN DATASET_ED_ADMISSION.admitted_from_ed IS 'Hospitalized from ER (1=yes, 0=no) / Internado a partir do pronto-socorro (1=internação/0=Sem internação)'; COMMENT ON COLUMN DATASET_ED_ADMISSION.temperature IS 'Body temperature (Celsius) / Temperatura corporal'; COMMENT ON COLUMN DATASET_ED_ADMISSION.heartrate IS 'Heart rate (bpm) / Frequência cardíaca'; COMMENT ON COLUMN DATASET_ED_ADMISSION.resprate IS 'Respiratory rate (breaths/min) / Frequência respiratória'; COMMENT ON COLUMN DATASET_ED_ADMISSION.o2sat IS 'Oxygen saturation (SpO2) / Saturação de oxigênio'; COMMENT ON COLUMN DATASET_ED_ADMISSION.sbp IS 'Systolic blood pressure / Pressão arterial sistólica'; COMMENT ON COLUMN DATASET_ED_ADMISSION.dbp IS 'Diastolic blood pressure / Pressão arterial diastólica'; COMMENT ON COLUMN DATASET_ED_ADMISSION.n_diagnosis IS 'Number of diagnoses / Número de diagnósticos registrados'; COMMENT ON COLUMN DATASET_ED_ADMISSION.split IS 'Data split flag (train, val, test) / Particionamento dos dados'; ``` ### 5.6 Criar uma view para NL com nomes claros Esta view, mais amigável, facilitará a LLM do banco de dados a entender mais facilmente os campos. Criar uma view no schema do usuário (recomendado para consumo pelo app): ```sql -- execute no MESMO schema usado pelo SELECT AI (ex.: MEU_USUARIO) CREATE OR REPLACE VIEW MEU_USUARIO.NLU_ED_ADMISSION AS SELECT subject_id AS patient_id, -- ID do paciente hadm_id AS admission_id, -- ID da internação (NULL se não internado) stay_id AS er_stay_id, -- ID da estadia no PS intime AS er_entry_time, -- data/hora de entrada no PS outtime AS er_exit_time, -- data/hora de saída do PS gender, race, arrival_transport, disposition, -- ADMITTED, HOME, etc. admitted_from_ed AS admitted, -- 1=internado, 0=não temperature, heartrate, resprate, o2sat, sbp, dbp, n_diagnosis, split, EXTRACT(MONTH FROM intime) AS month_num, -- 1..12 TO_CHAR(intime, 'fmMonth', 'NLS_DATE_LANGUAGE=PORTUGUESE') as month_name, EXTRACT(YEAR FROM intime) AS year_num -- ano numérico FROM ADMIN.DATASET_ED_ADMISSION; COMMENT ON TABLE MEU_USUARIO.NLU_ED_ADMISSION IS 'Tabela de Internações de pacientes que deram entrada no pronto-socorro com nomes de campos amigaveis para consultas em linguagem natural'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.gender IS 'Genero (Masculino ou M/Feminino ou F)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.race IS 'Raça ou etnia do paciente'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.arrival_transport IS 'Forma de chegada (ambulância, caminhada)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.disposition IS 'Destino após atendimento (ADMITTED=Internados/Internado, HOME=Dispensados/Dispensado/Sem Internação/Sem Internações, TRANSFER=Transferido/Transferidos)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.temperature IS 'Temperatura corporal (celsius)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.heartrate IS 'Frequência cardíaca em BPM'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.resprate IS 'Frequência respiratória (respiração/minuto)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.o2sat IS 'Saturação de oxigênio (SpO2)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.sbp IS 'Pressão arterial sistólica'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.dbp IS 'Pressão arterial diastólica'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.n_diagnosis IS 'Número de diagnósticos registrados'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.split IS 'Particionamento dos dados (train, val, test)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.patient_id IS 'ID do paciente'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.admitted IS 'Admitted indica se Paciente foi Internado (1=Internado/0=Não Internado)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.er_entry_time IS 'Data de Entrada no pronto-socorro'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.month_num IS 'Número do mês da entrada no PS / Mês (1=Janeiro, 2=Fevereiro, 3=Março, 4=Abril, 5=Maio, 6=Junho, 7=Julho, 8=Agosto, 9=Setembro, 10=Outubro, 11=Novembro, 12=Dezembro)'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.month_name IS 'Nome do mês de entrada do paciente no PS (ex.: Janeiro, Fevereiro, Março, ...). Use para exibição; para filtros, prefira month_num e year_num.'; COMMENT ON COLUMN MEU_USUARIO.NLU_ED_ADMISSION.year_num IS 'Ano de Entrada no pronto-socorro'; ``` >**Por que?** Nomes como patient_id, admitted, month_num facilitam a tradução NL→SQL e evitam ambiguidade. Valide o SELECT AI, executando os comandos abaixo numa mesma sessão: ### Testar direto no SQL ```sql BEGIN EXEC DBMS_CLOUD_AI.SET_PROFILE('OCI_GENERATIVE_AI_PROFILE'); SELECT AI 'quantos pacientes no hospital' FROM MEU_USUARIO.NLU_ED_ADMISSION; END; ``` ### 5.7 Preparar o app Flask Configure o arquivo [config](./files/config) com os dados da Wallet do Autonomous Database: ```json { "WALLET_PATH": "/caminho/Wallet_ADB", "DB_ALIAS": "oradb_high", "USERNAME": "MEU_USUARIO", "PASSWORD": "SenhaForte123" } ``` Baixar o arquivo [requirements.txt](./files/requirements.txt). Instalar dependências: ```bash python -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` --- ## 6. Testar a aplicação Rodar o app [app_select_ai.py](./files/app_select_ai.py): ```bash python app_select_ai.py # abre http://localhost:5001 ``` ### Acessando a aplicação Abra `http://localhost:5001` e faça perguntas como: - quantos pacientes chegaram no hospital - mostrar pacientes e suas pressoes arteriais acima de 120 80 - quantos pacientes deram entrada no hospital no mes "Junho" - quantos pacientes do genero F - quantos pacientes do genero M que tenham pressao acima de 120 80 - listar pacientes do genero M com todos os seus dados de mediçao que tenham pressao acima de 120 80 - quantos pacientes internados no hospital por genero ### Exportar resultados - **PDF**: botão “Exportar PDF”. - **Excel**: botão “Exportar Excel” (via servidor). --- ![img.png](images/img.png) ## Reference - [Install OCI-CLI](https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/cliinstall.htm) - [Download Wallet Database Connection File](https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/connect-download-wallet.html#GUID-DED75E69-C303-409D-9128-5E10ADD47A35) - [About SELECT AI](https://docs.oracle.com/en-us/iaas/autonomous-database-serverless/doc/select-ai.html) - [Hospital Risk Admission Prediction with Machine Learning](https://github.com/hoshikawa2/hospital_risk_admission) ## Acknowledgments - **Author** - Cristiano Hoshikawa (Oracle LAD A-Team Solution Engineer)