> For the complete documentation index, see [llms.txt](https://wedocs.gitbook.io/wedocs-5.1/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wedocs.gitbook.io/wedocs-5.1/wedocs-5.0/release-notes/release-notes-5.0.md).

# Release Notes 5.0

## <mark style="color:blue;">Destaques</mark>

### <img src="/files/w76xBAY5NhszJ4p5J3co" alt="" data-size="line"> DuckDB como armazenamento offline de dados&#x20;

Agora é possível utilizar o **DuckDB** como alternativa ao **MonetDB** para armazenar dados offline de metadados. [#duckdb-armazenamento-de-dados-offline](#duckdb-armazenamento-de-dados-offline "mention")

### <img src="/files/lpVA358VBo0IoWLdJhaC" alt="" data-size="line">  Metadados via script Python

A partir da versão 5.0 será possível criar metadados utilizando scripts em Python. Para isso, acesse a tela de criação ou edição de metadados em Tabelas e selecione a opção 'Script Python'. [#criacao-de-metadado-via-script-python](#criacao-de-metadado-via-script-python "mention")

### &#x20;<img src="/files/DW2mlXjDv0N6O2Uy7g94" alt="" data-size="line"> Componente Python

Além da criação de metadados via Python, agora também é possível criar componentes com Python. Na aba Dashboard, foi adicionada a opção de componente Python. [#novo-componente-python](#novo-componente-python "mention")

## <mark style="color:blue;">Detalhes</mark>

### DuckDB - Armazenamento de dados offline

* Agora é possível utilizar o **DuckDB** como alternativa ao **MonetDB** para armazenar dados offline de metadados.

**Principais mudanças:**

* **Nova opção de banco offline no cadastro de metadados:**\
  Ao criar ou editar um metadado, você poderá escolher entre **DuckDB** (novo padrão) e **MonetDB**.
* **Migração facilitada entre bancos:**\
  Ao alterar o banco de dados de um metadado existente, o sistema executará automaticamente a migração dos dados, com opção de excluir os dados antigos.
* **Suporte total à carga incremental e tabelas virtuais:**\
  Caso haja outras tabelas virtuais vinculadas, será possível aplicar a alteração a todos os metadados relacionados.
* **Entrada em vigor automática:**\
  A mudança do banco só será aplicada na próxima carga de dados (agendada ou manual). Até lá, os dados continuarão sendo lidos normalmente do banco anterior.
* **Explorador de dados com DuckDB:**\
  Quando o explorador estiver usando metadados online com armazenamento offline forçado, o banco utilizado será sempre o **DuckDB**.

***

Essa atualização traz mais leveza, simplicidade e desempenho, já que o DuckDB é um banco embutido, ideal para cenários locais e de leitura intensiva.

<div align="left"><figure><img src="/files/BJQiirZvjrFgN7QER8PN" alt=""><figcaption></figcaption></figure></div>

Com isso, também foi adicionado um serviço no Gerenciador de Tarefas responsável pela limpeza do banco DuckDB.

<div align="left"><figure><img src="/files/hGsJPlE0LXgKQptt9rtU" alt=""><figcaption></figcaption></figure></div>

### Criação de metadado via Script Python

* A partir da versão 5.0 será possível criar metadados utilizando scripts em Python. Para isso, acesse a tela de criação ou edição de metadados em Tabelas e selecione a opção 'Script Python'.

<div align="left"><figure><img src="/files/5cGUsxloo7GnzH52WiVY" alt=""><figcaption></figcaption></figure></div>

No metadado por script Python é possível escrever um script Python qualquer **mas existe uma regra obrigatória**:\
![](data:image/ico;base64,AAABAAEAEBAgAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAACAAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAkAAAAJAAAACQAAAAgAAAAFAAAAFAAAACYAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAArAAAAKwAAACsAAAAmAAAAFBg5a7AoW6j/KFuo/yhbqP8oW6j/KFuo/yhbqP8tX6r/LV+q/yhbqP8oW6j/KFuo/yhbqP8oW6j/KFuo/xg6aa4lVp3rhLbd/5rN8P+Uy+//p9Ty/7nd9f/O5/j/2efx/9Hk8P+23PT/lMvv/4TD7f93vuv/gcHs/32y2/8lVp3rGTtnnlSGvv+n0/D/bLnq/2Cz6P9gs+j/o9Py/0Q+Pf9EPj3/o9Py/2Cz6P9gs+j/aLfp/4fC6v9Cerj/GjplnAAAAA4nWJbbfajR/5vQ8f9xvOv/eMDs/6DS8v99hY3/fYWN/6DS8v95wOz/cr3r/4XF7v9glsj/JliV2QAAAA0AAAAACRIgNy9rsfnA2u3/pNby/5HM8P+VzfH/k8fo/5PH6P+VzfH/kczw/5vR8f+VwuP/L2ux+QkSITYAAAAAAAAAAAAAAAEWNVZ8VI3C/9zv+v+f1PH/r9v0/4OTnv+Dk5//r9v0/53T8f+z3fX/UozC/xc0UnkAAAABAAAAAAAAAAAAAAAAAAAABSdcjMJyptH/z+v5/8Dj9/9xb3D/cW9w/8Dj9/+v3fX/baPP/yZZir8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAABw8hM3ey76PM5//a7/r/ZVxa/2VcWv/P6/n/mcbk/zR3se0ACBAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARMsQlxamsv/3vP7/3VubP91bm3/2fD7/1eZy/8SKjxVAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACJVd6o4W42//e8/z/2vL7/4S32/8jVHSdAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABA1fa7fos7o/5/M5/80eqnZAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDB4rOz6Vyvs/ksj6CRslNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYOEtfFjVGWwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) O retorno/resultado da execução do script deve ser atribuído numa variável chamada result e esta deve ser um objeto da classe DataFrame do framework/biblioteca Pandas.

Exemplo 1:

```
import pandas as pd

# Criar um DataFrame vazio
result = pd.DataFrame()

try:
if fieldnames is None:
fieldnames = []
except:
fieldnames = []

# Adicionar as colunas com os tipos
if (len(fieldnames) == 0) or ('id' in fieldnames):
result['id'] = pd.Series(dtype=pd.Int64Dtype())

if (len(fieldnames) == 0) or ('nome' in fieldnames):
result['nome'] = pd.Series(dtype=pd.StringDtype())

myfilter = {{myfilter}}

# Inserir linhas manualmente
for i in range(5):
id = i + 1
nome = f'linha {i}'

# Retornar apenas os registros que atendem aos filtros
if myfilter is not None:
if id != myfilter:
continue

# Retornar apenas a quantidade necessária de registros
if i > recsmax - 1:
break

row = []

if (len(fieldnames) == 0) or ('id' in fieldnames):
row = row + [id]

if (len(fieldnames) == 0) or ('nome' in fieldnames):
row = row + [nome]

result.loc[i] = row

if (len(fieldnames) == 0) or ('id' in fieldnames):
result['id'] = result['id'].astype(pd.Int64Dtype())

if (len(fieldnames) == 0) or ('nome' in fieldnames):
result['nome'] = result['nome'].astype(pd.StringDtype())

```

Exemplo 2 :

```
import sqlite3
import pandas as pd

conn = sqlite3.connect('c:/temp/sampleData1000.sdb')

cursor = conn.cursor()

# Retornar apenas as colunas necessárias
try:
if fieldnames is None:
fieldnames = []
except:
fieldnames = []

if len(fieldnames) == 0:
query = 'select id, name from product_category'
else:
query = 'select ' + ', '.join(fieldnames) + ' from product_category'

# Retornar apenas os registros que atendem aos filtros
myfilter = {{myfilter}}

if myfilter is not None:
query = query + ' where id = ' + str(myfilter)

# Retornar apenas a quantidade necessária de registros
if recsmax > 0:
query = query + ' limit ' + str(recsmax)

cursor.execute(query)

result = pd.DataFrame(cursor.fetchall(), columns=[desc[0] for desc in cursor.description])

# Fazer a conversão após preencher os dados para garantir que os tipos de dados estejam corretos
if 'id' in fieldnames:
result['id'] = result['id'].astype(pd.Int64Dtype())

if 'name' in fieldnames:
result['name'] = result['name'].astype(pd.StringDtype())

conn.close()
```

Exemplo 3: Lembrando que todos os dados apresentados são fictícios.

```
import pandas as pd
import random

# Listas de exemplo
hospitais = [f"Hospital {nome}" for nome in [
    "São José", "Santa Marta", "Vida Nova", "Esperança", "Central", "São Lucas", "UniVida", "Vital", "Bem Estar", "Coração"
]]
cidades = ["São Paulo", "Rio de Janeiro", "Belo Horizonte", "Curitiba", "Fortaleza", "Salvador", "Brasília", "Manaus", "Recife", "Porto Alegre"]
tipos = ["Público", "Privado"]

# Gerar dados fictícios
dados = []
for i in range(100):
    nome_hospital = random.choice(hospitais) + f" - Unidade {random.randint(1, 5)}"
    cidade = random.choice(cidades)
    tipo = random.choice(tipos)
    leitos = random.randint(50, 500)
    taxa_ocupacao = round(random.uniform(50, 100), 1)
    pacientes = random.randint(500, 10000)
    nota_avaliacao = round(random.uniform(2.5, 5.0), 1)

    dados.append({
        "hospital": nome_hospital,
        "cidade": cidade,
        "tipo": tipo,
        "leitos": leitos,
        "ocupacao_%": taxa_ocupacao,
        "pacientes_atendidos": pacientes,
        "avaliacao": nota_avaliacao
    })

# Criar DataFrame
df = pd.DataFrame(dados)

# Definir tipos de dados
df = df.astype({
    "hospital": "string",
    "cidade": "string",
    "tipo": "string",
    "leitos": "int",
    "ocupacao_%": "float",
    "pacientes_atendidos": "int",
    "avaliacao": "float"
})

# Aplicar filtros opcionais se existirem
if 'fieldnames' in globals() and fieldnames is not None:
    df = df[list(fieldnames)]

if 'recsmax' in globals() and recsmax > 0:
    df = df.head(recsmax)

# Resultado obrigatório
result = df
```

#### Informações e dicas gerais

![](data:image/ico;base64,AAABAAEAEBAgAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqqqqA+Xl5Qrl5eUKqqqqAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/AuLi4hKKdmyXhnNpmOTk5BOqqqoDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/Au/v7xCLd2uapmo5/6FkNP+Eb2Wc5OTkE6qqqgMAAAAAAAAAAAAAAADi4uIJ7OzsDu/v7xDh4eER4uLiEt/f3xiHdWmYrXFB/9KdX//SnV7/omY4/4RvZJ7f398g39/fGO/v7xC/v78EmHZewKdnOv+mZjr/pmY5/6ZlOf+lZTf/p24//9egZf/VoGD/06Bg/9ShZP+obj3/o2E0/6NhNP+QbFLI7+/vEKpsP//huYf/3K50/9ysdP/brnX/3Kxz/9mobP/XpGf/16Rn/9ejZf/XpGf/2Kds/9urcv/aqnH/pWY4/93d3ReucUT/5MCT/9mobP/ZqGz/2Kps/9mobP/ZqGz/2ahr/9mobP/YqGv/2ahr/9moa//ZqGz/3bF6/6hqPf/f398YsXZJ/+jHnf/crnL/3K1y/9ytcf/crXL/3K1y/9yucv/crnL/3K5y/9yucf/crnH/3K5y/+G4hf+rb0L/3d3dF7V8T//qz6f/37N5/96zef/gtHz/4bd9/+G3ff/guIH/4Ld9/+C3ff/ftXv/3rN4/96zeP/lwI//r3RI/+fn5xa5g1T/7dWw/+K7hf/jwIr/5L+J/+S/iv/kv4n/5L+K/+S/if/kwIn/5MCK/+TAif/huoP/6MaY/7N6Tv/m5uYVvIla//Lfu//nyJP/6ceT/+nHk//px5L/6ceT/+nHk//px5P/58eT/+nIk//px5P/58eT/+3Tpv+2g1T/5eXlFMSVaP/15cX/686c/+vOnP/qzpz/686c/+vNnP/rzpz/6s6c/+rOnP/rzZz/6s6c/+vNnP/w3LP/wJBj/+Tk5BPLn3P/9uvK/+zUo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/8+G6/8aabv/h4eERz6V7//juzv/47c7/+O3N//jtzP/47Mz/+OzL//jsyv/37Mr/9+zK//fryv/368n/9+vI//bqx//Lonb/7u7uD7Sch8DSrIT/06uE/9OrhP/Rq4T/0quE/9GqhP/Rq4P/0aqD/9Gpg//QqoL/0KmC/8+pgf/PqIH/r5eCxuXl5Qp/f38Cv7+/BMzMzAXMzMwF1NTUBtra2gfa2toH39/fCOLi4gnl5eUK5eXlCufn5wvp6ekM6enpDN/f3wh/f38C/h8AAPwPAAD4BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) **Retornar apenas a quantidade de registros necessários**

* Para otimizar a performance e o consumo de memória, é recomendado que o script esteja preparado para retornar apenas a quantidade de registros necessária.\
  Esta informação é repassada ao script através da variável recsmax.\
  Se o valor dessa variável for 0, significa que não há limite. Um valor positivo significa que apenas aquela quantidade máxima é necessária.

![](data:image/ico;base64,AAABAAEAEBAgAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqqqqA+Xl5Qrl5eUKqqqqAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/AuLi4hKKdmyXhnNpmOTk5BOqqqoDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/Au/v7xCLd2uapmo5/6FkNP+Eb2Wc5OTkE6qqqgMAAAAAAAAAAAAAAADi4uIJ7OzsDu/v7xDh4eER4uLiEt/f3xiHdWmYrXFB/9KdX//SnV7/omY4/4RvZJ7f398g39/fGO/v7xC/v78EmHZewKdnOv+mZjr/pmY5/6ZlOf+lZTf/p24//9egZf/VoGD/06Bg/9ShZP+obj3/o2E0/6NhNP+QbFLI7+/vEKpsP//huYf/3K50/9ysdP/brnX/3Kxz/9mobP/XpGf/16Rn/9ejZf/XpGf/2Kds/9urcv/aqnH/pWY4/93d3ReucUT/5MCT/9mobP/ZqGz/2Kps/9mobP/ZqGz/2ahr/9mobP/YqGv/2ahr/9moa//ZqGz/3bF6/6hqPf/f398YsXZJ/+jHnf/crnL/3K1y/9ytcf/crXL/3K1y/9yucv/crnL/3K5y/9yucf/crnH/3K5y/+G4hf+rb0L/3d3dF7V8T//qz6f/37N5/96zef/gtHz/4bd9/+G3ff/guIH/4Ld9/+C3ff/ftXv/3rN4/96zeP/lwI//r3RI/+fn5xa5g1T/7dWw/+K7hf/jwIr/5L+J/+S/iv/kv4n/5L+K/+S/if/kwIn/5MCK/+TAif/huoP/6MaY/7N6Tv/m5uYVvIla//Lfu//nyJP/6ceT/+nHk//px5L/6ceT/+nHk//px5P/58eT/+nIk//px5P/58eT/+3Tpv+2g1T/5eXlFMSVaP/15cX/686c/+vOnP/qzpz/686c/+vNnP/rzpz/6s6c/+rOnP/rzZz/6s6c/+vNnP/w3LP/wJBj/+Tk5BPLn3P/9uvK/+zUo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/8+G6/8aabv/h4eERz6V7//juzv/47c7/+O3N//jtzP/47Mz/+OzL//jsyv/37Mr/9+zK//fryv/368n/9+vI//bqx//Lonb/7u7uD7Sch8DSrIT/06uE/9OrhP/Rq4T/0quE/9GqhP/Rq4P/0aqD/9Gpg//QqoL/0KmC/8+pgf/PqIH/r5eCxuXl5Qp/f38Cv7+/BMzMzAXMzMwF1NTUBtra2gfa2toH39/fCOLi4gnl5eUK5eXlCufn5wvp6ekM6enpDN/f3wh/f38C/h8AAPwPAAD4BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) **Retornar apenas as colunas necessárias**

* Para otimizar a performance e o consumo de memória, é recomendado que o script esteja preparado para retornar apenas as colunas necessárias.\
  Esta informação é repassada ao script através da variável fieldnames (do tipo Tupla).

![](data:image/ico;base64,AAABAAEAEBAgAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqqqqA+Xl5Qrl5eUKqqqqAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/AuLi4hKKdmyXhnNpmOTk5BOqqqoDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/Au/v7xCLd2uapmo5/6FkNP+Eb2Wc5OTkE6qqqgMAAAAAAAAAAAAAAADi4uIJ7OzsDu/v7xDh4eER4uLiEt/f3xiHdWmYrXFB/9KdX//SnV7/omY4/4RvZJ7f398g39/fGO/v7xC/v78EmHZewKdnOv+mZjr/pmY5/6ZlOf+lZTf/p24//9egZf/VoGD/06Bg/9ShZP+obj3/o2E0/6NhNP+QbFLI7+/vEKpsP//huYf/3K50/9ysdP/brnX/3Kxz/9mobP/XpGf/16Rn/9ejZf/XpGf/2Kds/9urcv/aqnH/pWY4/93d3ReucUT/5MCT/9mobP/ZqGz/2Kps/9mobP/ZqGz/2ahr/9mobP/YqGv/2ahr/9moa//ZqGz/3bF6/6hqPf/f398YsXZJ/+jHnf/crnL/3K1y/9ytcf/crXL/3K1y/9yucv/crnL/3K5y/9yucf/crnH/3K5y/+G4hf+rb0L/3d3dF7V8T//qz6f/37N5/96zef/gtHz/4bd9/+G3ff/guIH/4Ld9/+C3ff/ftXv/3rN4/96zeP/lwI//r3RI/+fn5xa5g1T/7dWw/+K7hf/jwIr/5L+J/+S/iv/kv4n/5L+K/+S/if/kwIn/5MCK/+TAif/huoP/6MaY/7N6Tv/m5uYVvIla//Lfu//nyJP/6ceT/+nHk//px5L/6ceT/+nHk//px5P/58eT/+nIk//px5P/58eT/+3Tpv+2g1T/5eXlFMSVaP/15cX/686c/+vOnP/qzpz/686c/+vNnP/rzpz/6s6c/+rOnP/rzZz/6s6c/+vNnP/w3LP/wJBj/+Tk5BPLn3P/9uvK/+zUo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/8+G6/8aabv/h4eERz6V7//juzv/47c7/+O3N//jtzP/47Mz/+OzL//jsyv/37Mr/9+zK//fryv/368n/9+vI//bqx//Lonb/7u7uD7Sch8DSrIT/06uE/9OrhP/Rq4T/0quE/9GqhP/Rq4P/0aqD/9Gpg//QqoL/0KmC/8+pgf/PqIH/r5eCxuXl5Qp/f38Cv7+/BMzMzAXMzMwF1NTUBtra2gfa2toH39/fCOLi4gnl5eUK5eXlCufn5wvp6ekM6enpDN/f3wh/f38C/h8AAPwPAAD4BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) **Retornar os tipos de dados das colunas**

* Os tipos dos dados das colunas serão usados, portanto, é recomendado definir eles no script.\
  Os tipos permitidos são os dtypes do Pandas.\
  O tipo pode ser definido manualmente quando uma coluna é criada manualmente no DataFrame ou automaticamente quando o DataFrame é o retorno de uma query/sql/select, por exemplo.\
  Contudo, mesmo que sejam definidos automaticamente, existem situações onde é necessário fazer uma conversão após preencher os dados para garantir que o tipo seja definido corretamente.\
  Essa conversão pode ser feita usando o método astype da classe Series do Pandas.

![](data:image/ico;base64,AAABAAEAEBAgAAAAAABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqqqqA+Xl5Qrl5eUKqqqqAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/AuLi4hKKdmyXhnNpmOTk5BOqqqoDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf39/Au/v7xCLd2uapmo5/6FkNP+Eb2Wc5OTkE6qqqgMAAAAAAAAAAAAAAADi4uIJ7OzsDu/v7xDh4eER4uLiEt/f3xiHdWmYrXFB/9KdX//SnV7/omY4/4RvZJ7f398g39/fGO/v7xC/v78EmHZewKdnOv+mZjr/pmY5/6ZlOf+lZTf/p24//9egZf/VoGD/06Bg/9ShZP+obj3/o2E0/6NhNP+QbFLI7+/vEKpsP//huYf/3K50/9ysdP/brnX/3Kxz/9mobP/XpGf/16Rn/9ejZf/XpGf/2Kds/9urcv/aqnH/pWY4/93d3ReucUT/5MCT/9mobP/ZqGz/2Kps/9mobP/ZqGz/2ahr/9mobP/YqGv/2ahr/9moa//ZqGz/3bF6/6hqPf/f398YsXZJ/+jHnf/crnL/3K1y/9ytcf/crXL/3K1y/9yucv/crnL/3K5y/9yucf/crnH/3K5y/+G4hf+rb0L/3d3dF7V8T//qz6f/37N5/96zef/gtHz/4bd9/+G3ff/guIH/4Ld9/+C3ff/ftXv/3rN4/96zeP/lwI//r3RI/+fn5xa5g1T/7dWw/+K7hf/jwIr/5L+J/+S/iv/kv4n/5L+K/+S/if/kwIn/5MCK/+TAif/huoP/6MaY/7N6Tv/m5uYVvIla//Lfu//nyJP/6ceT/+nHk//px5L/6ceT/+nHk//px5P/58eT/+nIk//px5P/58eT/+3Tpv+2g1T/5eXlFMSVaP/15cX/686c/+vOnP/qzpz/686c/+vNnP/rzpz/6s6c/+rOnP/rzZz/6s6c/+vNnP/w3LP/wJBj/+Tk5BPLn3P/9uvK/+zUo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/7dSj/+3Uo//t1KP/8+G6/8aabv/h4eERz6V7//juzv/47c7/+O3N//jtzP/47Mz/+OzL//jsyv/37Mr/9+zK//fryv/368n/9+vI//bqx//Lonb/7u7uD7Sch8DSrIT/06uE/9OrhP/Rq4T/0quE/9GqhP/Rq4P/0aqD/9Gpg//QqoL/0KmC/8+pgf/PqIH/r5eCxuXl5Qp/f38Cv7+/BMzMzAXMzMwF1NTUBtra2gfa2toH39/fCOLi4gnl5eUK5eXlCufn5wvp6ekM6enpDN/f3wh/f38C/h8AAPwPAAD4BwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) **Retornar apenas os registros válidos**

* Para otimizar a performance e o consumo de memória, é recomendado que o script esteja preparado para filtrar apenas as linhas necessárias.\
  É possível utilizar os filtros/parâmetros no script Python através da sintaxe: {{NomeParametro}}.\
  Exemplo: minhaVariavelPython = {{MeuParametro1}}\
  Este exemplo apenas atribui o valor de um filtro/parâmetro chamado MeuParametro1 em uma variável chamada minhaVariavelPython. O script deve retornar apenas os registros que atendem à este filtro.

Além da criação, também é possível instalar novas bibliotecas Python. Para isso, clique no botão Instalar biblioteca Python.

<div align="left"><figure><img src="/files/QRgIffiTH2q90Xnei3nJ" alt=""><figcaption></figcaption></figure></div>

Após clicar em 'Instalar biblioteca Python', será aberto um novo menu solicitando o nome da biblioteca desejada. Basta informar o nome e clicar em 'Instalar' para que a instalação seja realizada.

<div align="left"><figure><img src="/files/zInJkI66BOoNDEHTxXOm" alt=""><figcaption></figcaption></figure></div>

{% hint style="danger" %} <mark style="color:red;">**IMPORTANTE**</mark>**: Lembrando que este recurso está em fase experimental.**
{% endhint %}

### Novo componente - Python

* Além da criação de metadados via Python, agora também é possível criar componentes com Python. Na aba Dashboard, foi adicionada a opção de componente Python.

<figure><img src="/files/YhXjjWzSUnobe7ggX76d" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Caso o componente não esteja habilitado, acesse *Administração > Acesso ao Menu Principal*, localize o componente Python e conceda permissão aos usuários desejados.
{% endhint %}

#### Como criar um componente visual com Python

Você pode criar um componente visual escrevendo um script Python que **gera uma imagem em formato SVG** para ser exibida na tela.

#### 1️⃣ Regras e convenções obrigatórias

1. **Resultado final**
   * Seu script **deve terminar** atribuindo o conteúdo SVG a uma variável chamada `result`.
   * Exemplo:

     ```python
     result = svg.getvalue()
     ```
2. **Formato da imagem**
   * O valor de `result` **deve ser** uma imagem **SVG** (Scalable Vector Graphics).
3. **Dados disponíveis no script**
   * `df_internalvalues`: dados originais retornados pelo metadado.
   * `df_displayvalues`: dados formatados para exibição (se não houver formatação, serão iguais ao `df_internalvalues`).
   * Ambas são **DataFrames** do Pandas.
4. **Tamanho da área de exibição**
   * `height` e `width`: altura e largura em **pixels** do espaço disponível para o gráfico.
   * Algumas bibliotecas (como o Matplotlib) usam **polegadas**, então você precisa converter pixels para polegadas:

     ```python
     fig_height = height / rcParams['figure.dpi']
     fig_width = width / rcParams['figure.dpi']
     ```

***

#### 2️⃣ Exemplo prático – Gráfico de linhas com Matplotlib

Neste exemplo, o metadado retorna duas colunas:

* `product_category_name`: nomes das categorias de produtos.
* `product_name_cnt`: quantidade de produtos em cada categoria.

O objetivo é exibir um gráfico de linha mostrando essa relação.

```python
# 📚 Importa bibliotecas necessárias
from matplotlib import rcParams
from matplotlib.figure import Figure
from matplotlib.backends.backend_svg import FigureCanvasSVG
from io import StringIO

# 🎯 Passo 1 – Converter pixels para polegadas
fig_height = height / rcParams['figure.dpi']
fig_width = width / rcParams['figure.dpi']

# 🎯 Passo 2 – Criar figura e "canvas" para SVG
fig = Figure(figsize=(fig_width, fig_height))
canvas = FigureCanvasSVG(fig)

# 🎯 Passo 3 – Adicionar e plotar os dados
ax = fig.add_subplot()
ax.plot(
    df_displayvalues['product_category_name'],  # Eixo X (formatado)
    df_internalvalues['product_name_cnt']       # Eixo Y (valor original)
)

# 🎯 Passo 4 – Exportar para SVG
svg = StringIO()
canvas.print_svg(svg)

# 🎯 Passo 5 – Retornar o SVG para o sistema
result = svg.getvalue()
```

***

#### 3️⃣ Resumo do fluxo

1. **Receba os dados** do metadado (`df_internalvalues` e `df_displayvalues`).
2. **Ajuste o tamanho** da imagem usando `height` e `width`.
3. **Crie e personalize** o gráfico usando a biblioteca de sua escolha (ex.: Matplotlib, Plotly, Seaborn).
4. **Exporte** o gráfico para SVG (em memória).
5. **Atribua** o SVG à variável `result`.

Na criação ou edição do componente, também é possível instalar novas bibliotecas.

<figure><img src="/files/a1FZ9n8xOU5yeMITvkh3" alt=""><figcaption></figcaption></figure>

Abaixo consta um exemplo de script Python criado utilizando o metadado gerado no passo anterior (Exemplo 3).

```python
from matplotlib import rcParams
from matplotlib.figure import Figure
from matplotlib.backends.backend_svg import FigureCanvasSVG
from io import StringIO

# --- Busca o índice do hospital com maior taxa de ocupação ---
idx = df_internalvalues['ocupacao_%'].idxmax()

# --- Recupera dados formatados (display) ---
hospital = df_displayvalues.loc[idx, 'hospital']
cidade = df_displayvalues.loc[idx, 'cidade']
tipo = df_displayvalues.loc[idx, 'tipo']

# --- Recupera dados originais (internal) ---
ocupacao = df_internalvalues.loc[idx, 'ocupacao_%']
leitos = df_internalvalues.loc[idx, 'leitos']
pacientes = df_internalvalues.loc[idx, 'pacientes_atendidos']
avaliacao = df_internalvalues.loc[idx, 'avaliacao']

# --- Define tamanho da figura em polegadas ---
fig_height = height / rcParams['figure.dpi']
fig_width = width / rcParams['figure.dpi']

# --- Cria figura e canvas ---
fig = Figure(figsize=(fig_width, fig_height))
canvas = FigureCanvasSVG(fig)
ax = fig.add_subplot()
ax.axis('off')  # Remove eixos
fig.patch.set_facecolor('white')

# --- Fundo colorido para destaque ---
ax.fill_between([0, 1], 0, 1, color='#f0f4f8')

# --- Título e informações ---
ax.text(0.05, 0.85, "Hospital com Maior Ocupação", fontsize=16, weight='bold', color='#2c3e50')
ax.text(0.05, 0.75, hospital, fontsize=14, weight='bold', color='#34495e')
ax.text(0.05, 0.70, f"Cidade: {cidade} | Tipo: {tipo}", fontsize=11, color='#7f8c8d')

# --- Indicadores principais ---
ax.text(0.05, 0.60, f"Leitos: {leitos}", fontsize=13, color='#2980b9')
ax.text(0.05, 0.53, f"Pacientes Atendidos: {pacientes:,}", fontsize=13, color='#27ae60')
ax.text(0.05, 0.46, f"Avaliação: {avaliacao:.2f} / 5", fontsize=13, color='#f39c12')
ax.text(0.05, 0.39, f"Ocupação: {ocupacao:.1f} %", fontsize=13, color='#c0392b')

# --- Fonte dos dados ---
ax.text(0.05, 0.10, "Fonte: Dados simulados do metadado", fontsize=8, color='gray')

# --- Exporta para SVG ---
svg = StringIO()
canvas.print_svg(svg)
result = svg.getvalue()

```

***

#### Resumo do que o script faz:

* Procura o hospital com a maior ocupação (%).
* Busca informações desse hospital para mostrar: nome, cidade, tipo, leitos, pacientes atendidos, avaliação e taxa de ocupação.
* Cria uma imagem gráfica limpa, sem eixos, com fundo claro.
* Adiciona textos organizados para mostrar as informações de forma visualmente agradável.
* Gera essa imagem no formato SVG e a disponibiliza para exibição no sistema.

<div align="left"><figure><img src="/files/JOXUrTEbdCtsFIhWfqe1" alt=""><figcaption></figcaption></figure></div>

[Veja com mais detalhes no manual](/wedocs-5.1/wedocs-5.0/cliente-desktop/criacao-e-edicao-de-dashboards/componente-python.md)

{% hint style="danger" %} <mark style="color:red;">**IMPORTANTE**</mark>**:** Os campos utilizados no Script Python devem ser previamente configurados na seção 'Campos' para que o script possa ser executado corretamente.
{% endhint %}

### Validações para contêiner de filtros

* Implementamos melhorias importantes no sistema de contêiner de filtros para garantir maior controle sobre os intervalos de datas e opções selecionadas pelos usuários. Agora é possível:
  * Restringir o intervalo de datas que podem ser informadas nos filtros, limitando a data mínima e máxima permitida (por exemplo, até 1 ano atrás ou 6 meses).
  * Definir fórmulas de validação para restringir a seleção de múltiplas opções, limitando o número mínimo ou máximo de escolhas feitas pelo usuário.
  * Exibir mensagens de erro personalizadas quando os valores informados estiverem fora dos limites definidos, impedindo que o usuário aplique filtros inválidos.

#### O que é possível fazer agora?

**1. Limitar intervalo máximo para filtros de data**

Você pode configurar fórmulas para restringir o intervalo de datas que o usuário pode selecionar, por exemplo, impedindo que ele escolha um período maior que 1 ano.

**Exemplo de fórmula para filtro entre datas:**

```sql
if(abs(:value2 - :value1) > 365, "O intervalo de pesquisa não pode ser maior que 365 dias")
```

Se o usuário tentar selecionar um intervalo maior que 365 dias, verá a mensagem acima e não poderá prosseguir.

<div align="left"><figure><img src="/files/YyqJ9jIqBpVaCE9sRxDj" alt=""><figcaption></figcaption></figure></div>

**2. Limitar seleção em filtros de múltipla escolha**

Também é possível definir limites para quantas opções o usuário pode escolher.

**Exemplo de fórmula para seleção múltipla:**

```sql
if(length(:value) > 3, "Selecione no máximo 3 opções")
```

Se o usuário tentar aplicar um filtro com menos de 3 opções selecionadas, uma mensagem será exibida impedindo a continuidade da ação.

<div align="left"><figure><img src="/files/J1N8LxZnaw3mKWag7dFW" alt=""><figcaption></figcaption></figure></div>

Para essa mudança, adicionamos uma nova aba chamada **"Defina, opcionalmente, uma fórmula de verificação"**, onde é possível configurar toda a validação de forma simples e flexível.

<div align="left"><figure><img src="/files/jxWMmtUf3T3JJUx4Pd2K" alt=""><figcaption></figcaption></figure></div>

### Envio de alerta com componente planilha no formato .xlsx

* Agora é possível **enviar componentes em formato de planilha (.XLSX ou .CSV)** em tarefas,.

<div align="left"><figure><img src="/files/MVPnXoeeZnM6j8v6GtST" alt=""><figcaption></figcaption></figure></div>

### Campos calculados por funções de janela podem ser usados como filtros em componentes

* Foi introduzida a possibilidade de utilizar campos derivados de funções de janela como filtros nos componentes dos dashboards. Com isso, funções como `rank()` e `row_number()` podem agora ser tratadas como qualquer outro campo disponível para filtragem, permitindo que o próprio mecanismo de visualização aplique os filtros após o cálculo dessas funções na consulta.&#x20;

<div align="left"><figure><img src="/files/eUR8icn7L4PQi2SRAQoj" alt=""><figcaption></figcaption></figure></div>

No canto do "Filtros (pós resultado/janela)" encontra-se um ícone de interrogação "(?)", o qual exibe a fórmula utilizada para a aplicação do filtro de janela. Essa mesma fórmula também é apresentada logo abaixo.

```sql
select
    *
from
(
    select
        [colunas]
    from
        [script_do_metadado]
    where
        [filtros_por_registro]
    group by
        [colunas_de_agrupamento]
    having
        [filtros_agrupados]
)
where
    [filtros_pós_funções_janela]
```

**Resumo do que cada parte do script faz:**

* Seleciona todas as colunas do resultado da consulta interna.
* Busca os dados na tabela ou consulta base chamada `[script_do_metadado]`
* Filtra os registros para trazer só os que atendem a critérios definidos.
* Agrupa os registros com base nas colunas escolhidas.
* Filtra os grupos para manter apenas os que cumprem certas condições após o agrupamento.
* Por fim, aplica um filtro final usando funções de janela para analisar os dados em grupos e refinar o resultado.

Agora é possível, por exemplo, exibir apenas os dez primeiros itens de um ranking ou filtrar registros cuja soma acumulada ultrapasse um determinado valor, tudo isso diretamente nos componentes e de forma intuitiva.

<div align="left"><figure><img src="/files/QiP7dJcSr2ZonTQ66tak" alt=""><figcaption></figcaption></figure></div>

### **Uso de múltiplos campos do metadado em fórmulas para séries e cores personalizadas**

* A nova funcionalidade permite que fórmulas utilizadas em séries personalizadas e regras de cores condicionais (cor por fórmula) façam referência a valores de outros campos disponíveis no metadado.&#x20;

Por exemplo, é possível definir que a cor de uma barra em um gráfico de desempenho varie de acordo com o valor de uma meta estabelecida: se o valor real for maior ou igual à meta, a barra será exibida em verde; caso contrário, será exibida em vermelho. Essa comparação pode ser feita diretamente por meio de uma fórmula que acessa o campo da meta.

<div align="left"><figure><img src="/files/sh5B2aNNtboE8EgT0Lx5" alt="" width="563"><figcaption></figcaption></figure></div>

### Configuração personalizada do ambiente Python

Agora o WK permite ao usuário defina manualmente o diretório da instalação do Python que será utilizada pelos módulos do sistema. Essa funcionalidade oferece maior flexibilidade e controle sobre o ambiente de execução dos scripts Python.

📍 **Como configurar:**

1. Acesse o **Servidor** ou o **Gerenciador de Tarefas** do WK.
2. Clique em **Configurações**.
3. Localize a opção **Python** na lista de funcionalidades.
4. No campo **Diretório instalação**, informe o caminho completo da instalação do Python desejada (ex: `C:\...\Python313\`).
5. Clique em **OK** para salvar.

<figure><img src="/files/K1iMPzcHqzQjwHyywhmt" alt=""><figcaption></figcaption></figure>

> **Se o diretório informado contiver uma instalação válida do Python**\
> O WK utilizará essa instalação como ambiente principal para execução de scripts Python, garantindo maior controle e isolamento de dependências.
>
> **Se nenhum diretório for especificado**\
> O WK tentará utilizar uma instalação padrão do Python já registrada no sistema operacional Windows. Essa instalação pode ter sido feita previamente por outro software ou pelo próprio usuário.

&#x20;

**Principais mudanças**\
• **Configuração personalizada do Python**\
Agora é possível apontar diretamente para uma instalação específica do Python via interface do WK.\
• **Detecção automática de instalação existente**\
O WK continua detectando automaticamente instalações do Python registradas no Windows.\
• **Instalação dedicada via WK**\
O iniciador do servidor permite instalar uma versão exclusiva do Python, isolada de outras aplicações.\
• **Compatibilidade com múltiplas instalações**\
Suporte a ambientes com mais de uma versão do Python instalada, sem conflitos de dependências.

### Conta web configurável automaticamente ao cadastrar nova conta

* Agora ao adicionar uma nova conta pelo servidor, já será possível vinculá-la automaticamente à versão web. Ou seja, não será mais necessário alterar o arquivo `localConfigs` para permitir o acesso pela web.

Assim que você salvar, o sistema exibirá a seguinte pergunta:

<div align="left"><figure><img src="/files/wl8TBqBlrijYxx4inpPY" alt=""><figcaption></figcaption></figure></div>

Ao clicar em 'Sim', será exibido um novo menu solicitando algumas configurações, como nome, porta, ID e servidor. Por padrão, essas informações serão baseadas em uma conta já cadastrada no `localConfigs`. Caso clique em 'Não', a conta não será adicionada ao arquivo `localConfigs`, e, para acessá-la, será necessário adicioná-la manualmente.

<div align="left"><figure><img src="/files/rRBvGpwApYHzG3s2l3P5" alt=""><figcaption></figcaption></figure></div>

Após concluir a configuração e clicar em 'OK', a conta cadastrada já estará disponível na versão web.

<div align="left"><figure><img src="/files/gLzrQ3JX48exDSj832eE" alt=""><figcaption></figcaption></figure></div>

Caso deseje excluir uma conta que foi configurada para ser aplicada, será exibida uma confirmação perguntando se você realmente deseja removê-la.

<div align="left"><figure><img src="/files/dRA0UzXRJELEZE7QtbUt" alt=""><figcaption></figcaption></figure></div>

### Porta Apache configurável no instalador

* Foi adicionado no instalador a possibilidade de configurar as portas HTTP e HTTPS do Apache, por meio de dois novos campos posicionados logo abaixo do diretório de instalação.&#x20;

Esses campos são disponibilizados somente se a instalação do Apache estiver selecionada, e, por padrão, as portas HTTP e HTTPS vêm com os números 80 e 443, respectivamente. Caso queira modificar, o sistema aceitará apenas números inteiros entre 1 e 65535, com limite de cinco dígitos, além de validação que impede a duplicidade entre as portas. Também foi incluída a funcionalidade de teste de disponibilidade da porta, acessível por um link ao lado de cada campo.

<div align="left"><figure><img src="/files/MDXkaxI82QQmYMEBF6nK" alt=""><figcaption></figcaption></figure></div>

[Veja com mais detalhes no manual](broken://pages/RwK1lBFidffTUfT9lUWT)

***

### Novo acesso às Opções de Ajuda

O novo acesso ao suporte foi centralizado nas **Opções de Ajuda**, disponível no ícone no canto superior direito.

Ao clicar em **Suporte**, o sistema abre o **WeHelp** para atendimento.

<div align="left"><figure><img src="/files/k4rcW3sWHJTGrc3pnex5" alt="" width="563"><figcaption></figcaption></figure></div>

A opção **Acessar WeDocs** direciona para a documentação do sistema.

<div align="left"><figure><img src="/files/9ObrNhaDgGjVHjrMblOH" alt="" width="563"><figcaption></figcaption></figure></div>

***

### Migração para o Angular2+

* Na versão 5.0 implementamos uma atualização significativa para o Angular 2+ com o objetivo primordial de aprimorar o desempenho do sistema.

{% hint style="info" %} <mark style="color:blue;">**Para mais informações acesse nosso SAC**</mark>&#x20;

<https://atendimento.weknowhealthtech.com.br/kb> ou envie-nos um e-mail para <atendimento@weknowhealthtech.com.br>&#x20;
{% endhint %}
