# DHuO LIb

![Badge em Desenvolvimento](http://img.shields.io/static/v1?label=STATUS&message=EM%20DESENVOLVIMENTO&color=GREEN&style=for-the-badge)

`version`: 0.4.6
`lib`: ![dhuodata-lib](https://pypi.org/project/dhuodata-lib/)


# Índice 

* [Índice](#índice)
* [Features](#features)
* [Class: `GenericRepository`](#class-genericrepository)
* [Class: `DhuolibExperimentClient`](#class-dhuolibexperimentclient)
* [Class: `DhuolibPlatformClient`](#class-dhuolibplatformclient)
* [Demonstração da Aplicação](#demonstração-da-aplicação)
* [Referências](#referências)

![](assets/imgs/dhuo.png)


# Features

### Login **Não implementado**

- [x] login
    - Auth.login
    - Auth.is_logged


--- 
## Análise Exploratória / Persistencia / Aquisição de Dados


## Class: `GenericRepository`

#### Overview

A classe `GenericRepository` Simplifica o uso e o acesso ao datalake. Possui diversos métodos, incluindo inserção e busca de dados. Seu objetivo é facilitar o trabalho dos desenvolvedores no processo de predição de modelos de machine learning, fornecendo uma interface simplificada para a interação com os dados.

### `__init__(self, db_connection)`

Initializes the repository with a database connection.

- **Parameters:**
  - `db_connection`: Uma instancia do `DatabaseConnection` que prove a connecção e controle de acesso com o datalake

### `create_table_by_dataframe(self, table_name: str, df: pd.DataFrame)`
Cria uma tabela baseada em um dataframe existente

- **Parameters:**
  - `table_name`: Nome da tabela onde o dado sera inserido.
  - `dataframe`: Um dataframe representando a tabela e os dados a serem inseridos

- **Returns:**
  - Retorna o numero de itens inseridos na tabela

 **Example:**
  ```python
  df = pd.DataFrame({"name": ["example"], "value": [42]})
  number_lines = repo.create_table_by_dataframe("my_table", data)
  ```


### `insert(self, table_name: str, data: dict)`

Insere um novo dado na base de dados. Uma boa opção para inserir dados no database sem utilizar dataframe pandas

- **Parameters:**
  - `table_name`: Nome da tabela onde o dado sera inserido.
  - `data`: Um dicionario representando o dado a ser inserido.

- **Returns:**
  - Retorna o dado inserido.

- **Example:**
  ```python
  data = {"name": "example", "value": 42}
  inserted_record = repo.insert("my_table", data)
  ```
### `update_table_by_dataframe(self, table_name: str, df_predict: pd.DataFrame, if_exists: str = "append")`

Atualiza uma tabela adicionando ou substituindo registros usando um DataFrame do pandas.

- **Parâmetros:**
- `table_name`: O nome da tabela a ser atualizada.
- `df_predict`: Um DataFrame do pandas contendo os registros a serem inseridos.
- `if_exists`: Especifica o comportamento se a tabela já existir. O padrão é "append".

- **Example:**
  ```python
  df = pd.DataFrame({"name": ["example"], "value": [42]})
  repo.update_table_by_dataframe("my_table", df)


### `get_items_with_pagination(self, table_name: str, page: int = 1, page_size: int = 10000)`

Recupera registros da tabela especificada utilizando paginação.

- **Parâmetros:**
  - `table_name`: O nome da tabela a ser consultada.
  - `page`: O número da página a ser recuperada. O padrão é 1.
  - `page_size`: O número de registros por página. O padrão é 10000.

- **Retorna:**
  - Um dicionário contendo os registros, número da página, tamanho da página e total de itens.

- **Exemplo:**
  ```python
  result = repo.get_items_with_pagination("my_table", page=1, page_size=10)
  print(result["items"])
  ```


### `to_dataframe(self, table_name: str = None, filter_clause: str = None, list_columns: list = None)`

Converte os resultados da consulta em um DataFrame do pandas.

- **Parâmetros:**
  - `table_name`: O nome da tabela a ser consultada.
  - `filter_clause`: Uma cláusula de filtro opcional para aplicar à consulta.
  - `list_columns`: Uma lista opcional de colunas a serem incluídas na consulta.

- **Retorna:**
  - Um DataFrame do pandas contendo os resultados da consulta.

- **Exemplo:**
  ```python
  df = repo.to_dataframe("my_table", filter_clause="value > 10", list_columns=["name", "value"])
  print(df)
  ```

--- 
### Criação de Experimentos, Ciclo de vida do modelo e Predição


## Class: `DhuolibExperimentClient`

DhuolibExperimentClient` interage com o serviço Dhuolib para gerenciar experimentos, executar modelos, criar modelos e fazer previsões. Inclui métodos para criar e executar experimentos, criar modelos e fazer previsões online e em lote.

### `__init__(self, service_endpoint=None)`

Inicializa o cliente com um endpoint de serviço.

- **Parâmetros:**
  - `service_endpoint`: O endpoint do serviço Dhuolib.

- **Lança:**
  - `ValueError`: Se `service_endpoint` não for fornecido.

### `create_experiment(self, experiment_name: str, experiment_tags: dict = None) -> dict`

Cria um novo experimento com o nome e tags especificados.

- **Parâmetros:**
  - `experiment_name`: O nome do experimento.
  - `experiment_tags`: Dicionário opcional de tags para o experimento.

- **Retorna:**
  - Um dicionário contendo os detalhes do experimento criado ou uma mensagem de erro.

- **Exemplo:**
  ```python
      experiment_response = dholib_client.create_experiment(
        experiment_name="iris-classification", experiment_tags={"tag": "iris"}
    )
  ```

### `run_experiment(self, type_model: str, experiment_id: str, modelpkl_path: str, requirements_path) -> dict`

Executa um experimento com o modelo e requisitos especificados.

- **Parâmetros:**
  - `type_model`: O tipo do modelo.
  - `experiment_id`: O ID do experimento.
  - `modelpkl_path`: O caminho para o arquivo pickle do modelo.
  - `requirements_path`: O caminho para o arquivo de requisitos.

- **Retorna:**
  - Um dicionário contendo os detalhes da resposta ou uma mensagem de erro.

- **Exemplo:**
  ```python
    experiment_run = dholib_client.run_experiment(
        type_model="lightgbm",
        experiment_id=experiment_response["experiment_id"],
        modelpkl_path="{path}/iris.pkl",
        requirements_path="{path}/requirements.txt",
    )
  ```

### `create_model(self, model_params) -> dict`

Cria um novo modelo com os parâmetros especificados.

- **Parâmetros:**
  - `model_params`: Um dicionário contendo os parâmetros do modelo.

- **Retorna:**
  - Um dicionário contendo os detalhes do modelo criado ou uma mensagem de erro.

- **Exemplo:**
  ```python
  run_params = {
        "modelname": "iris-classification",
        "modeltag": "lightgbm",
        "stage": "Production",
        "run_id": experiment_run["run_id"],
        "model_uri": experiment_run["model_uri"],
    }
  result = dholib_client.create_model(run_params)
  ```

### `prediction_batch_with_dataframe(self, batch_params: dict, df: pd.DataFrame) -> dict`

Faz uma previsão em lote usando um DataFrame do pandas.

- **Parâmetros:**
  - `batch_params`: Um dicionário contendo os parâmetros da previsão em lote.
  - `df`: Um DataFrame do pandas contendo os dados para a previsão.

- **Retorna:**
  - Um dicionário contendo os resultados da previsão em lote ou uma mensagem de erro.

- **Exemplo:**
  ```python
  batch_params = {
          "modelname": "iris-classification-lightgbm",
          "stage": "Production",
          "experiment_name": "iris-classification",
          "type_model": "lightgbm",
          "run_id": "121",
          "batch_model_dir": "iris.pkl",
  }
  response = client.prediction_batch_with_dataframe(batch_params, dataframe)
  ```

## Exemplo de Uso

```python
dholib_client = DhuolibExperimentClient(
        service_endpoint="http://localhost:8000")
repository = get_repository(
        config_file_name="/home/diego/Documentos/eng/test-dhuodata/config/database.json"
)
df_iris_train = repository.to_dataframe(table_name="IRIS_TRAIN")

X = df_iris_train[["sepal_length", "sepal_width",
                       "petal_length", "petal_width"]]
y = df_iris_train["class"]
clf = lgb.LGBMClassifier()
clf.fit(X, y)
with open("iris.pkl", "wb") as f:
    pickle.dump(clf, f)

experiment_response = dholib_client.create_experiment(
    experiment_name="iris-classification", experiment_tags={"tag": "iris"}
)

experiment_run = dholib_client.run_experiment(
    type_model="lightgbm",
    experiment_id=experiment_response["experiment_id"],
    modelpkl_path="{path}/iris.pkl",
     requirements_path="{path}/requirements.txt",
  )

 run_params = {
        "modelname": "iris-classification",
        "modeltag": "lightgbm",
        "stage": "Production",
        "run_id": experiment_run["run_id"],
        "model_uri": experiment_run["model_uri"],
  }
 
 df_iris = repository.to_dataframe(
        table_name="IRIS_FEATURE",
        list_columns=["SEPAL_LENGTH", "SEPAL_WIDTH",
                      "PETAL_LENGTH", "PETAL_WIDTH"],
)

 result = dholib_client.create_model(run_params)
 predicts = dholib_client.prediction_batch_with_dataframe(
        batch_params=batch_params, df=df_iris
    )
 df_iris["predict"] = predicts
```

## Class: `DhuolibPlatformClient`


`DhuolibPlatformClient` interage com o serviço Dhuolib para gerenciar projetos em lote, implantar scripts, verificar o status do pipeline, criar clusters e executar pipelines em lote.
s
### `__init__(self, service_endpoint=None, project_name=None)`

Inicializa o cliente com um endpoint de serviço e um nome de projeto opcional.

- **Parâmetros:**
  - `service_endpoint`: O endpoint do serviço Dhuolib.
  - `project_name`: Nome opcional do projeto.

- **Lança:**
  - `ValueError`: Se `service_endpoint` não for fornecido.

### `create_batch_project(self, project_name: str)`

Cria um novo projeto em lote com o nome especificado.

- **Parâmetros:**
  - `project_name`: O nome do projeto.

- **Retorna:**
  - Um dicionário contendo os detalhes do projeto criado ou uma mensagem de erro.

- **Lança:**
  - `ValueError`: Se o projeto já existir.
  - `ConnectionError`: Se houver um erro de conexão.

- **Exemplo:**
  ```python
  response = client.create_batch_project("MeuProjeto")
  ```

### `deploy_batch_project(self, script_filename: str, requirements_filename: str)`

Implanta um projeto em lote com o script e requisitos especificados.

- **Parâmetros:**
  - `script_filename`: O nome do arquivo do script.
  - `requirements_filename`: O nome do arquivo de requisitos.

- **Retorna:**
  - A resposta do serviço Dhuolib ou uma mensagem de erro.

- **Lança:**
  - `ValueError`: Se `project_name`, `script_filename` ou `requirements_filename` não forem fornecidos.
  - `FileNotFoundError`: Se os arquivos especificados não forem encontrados.

- **Exemplo:**
  ```python
  response = client.deploy_batch_project("script.py", "requirements.txt")
  ```

### `pipeline_status_report(self)`

Gera um relatório de status do pipeline para o projeto em lote.

- **Retorna:**
  - Uma lista de dicionários contendo a data, etapa e status de cada log do pipeline.

- **Lança:**
  - `ValueError`: Se `project_name` não for fornecido.

- **Exemplo:**
  ```python
  status_report = client.pipeline_status_report()
  ```

### `create_cluster(self, cluster_size: int)`

Cria um cluster com o tamanho especificado para o projeto em lote.

- **Parâmetros:**
  - `cluster_size`: O tamanho do cluster (1, 2 ou 3).

- **Retorna:**
  - A resposta do serviço Dhuolib.

- **Lança:**
  - `ValueError`: Se `project_name` ou `cluster_size` não forem fornecidos ou se `cluster_size` não for 1, 2 ou 3.

- **Exemplo:**
  ```python
  response = client.create_cluster(2)
  ```

### `batch_run(self)`

Executa o pipeline em lote para o projeto.

- **Retorna:**
  - A resposta do serviço Dhuolib.

- **Lança:**
  - `ValueError`: Se `project_name` não for fornecido.

- **Exemplo:**
  ```python
  response = client.batch_run()
  ```

## Exemplo de Uso

```python
from dhuolib_platform_client import DhuolibPlatformClient

# Inicializa o cliente
client = DhuolibPlatformClient(service_endpoint="http://example.com", project_name="MeuProjeto")

# Cria um projeto em lote
response = client.create_batch_project("MeuProjeto")
print(response)

# Implanta um projeto em lote
response = client.deploy_batch_project("script.py", "requirements.txt")
print(response)

# Gera um relatório de status do pipeline
status_report = client.pipeline_status_report()
print(status_report)

# Cria um cluster
response = client.create_cluster(2)
print(response)

# Executa o pipeline em lote
response = client.batch_run()
print(response)
```

### Feedback dos steps do Pipeline para o usuário



# Demonstração da Aplicação

## Projeto Iris-t com lightgbm

#### Na primeira etapa é importante criar o código de treino para gerar o pickle
#### Apos o pickle ser gerado é necessário cadastrar o pickle
- `dholib_client.create_experiment` - crie o experimento 
- `dholib_client.run_experiment` - execute o experimento logando ele no nosso sistema
- `dholib_client.create_model` - crie o modelo. O modelo uma vez criado é apto para ser posto em produção. O modelo pode ter 4 estados None, Production, Stagging ou Archive


  - `None`: Este é o estado padrão de um modelo quando ele é registrado pela primeira vez. Ele indica que o modelo ainda não foi atribuído a nenhum dos outros estados.

  - `Staging`: Este estado é usado para modelos que estão em um ambiente de teste. Eles foram testados em um ambiente isolado e estão prontos para serem movidos para produção.

  - `Production`: Este estado é usado para modelos que estão atualmente em uso em um ambiente de produção.

  - `Archived`: Este estado é usado para modelos que não estão mais em uso. Eles são mantidos para fins de registro e não devem ser usados para inferência.

 
### Train 

```python
import pickle
import lightgbm as lgb

from dhuolib.clients.experiment import DhuolibExperimentClient
from dhuolib.repository import DatabaseConnection, GenericRepository


def get_repository(config_file_name):
    if not config_file_name:
        raise ValueError("config_file_name is required")

    db = DatabaseConnection(config_file_name=config_file_name)
    repository = GenericRepository(db_connection=db)

    return repository


def train():
    dholib_client = DhuolibExperimentClient(
        service_endpoint="http://{uri_endpoint}")
    repository = get_repository(
        config_file_name="{path}/database.json"
    )
    df_iris_train = repository.to_dataframe(table_name="IRIS_TRAIN")

    X = df_iris_train[["sepal_length", "sepal_width",
                       "petal_length", "petal_width"]]
    y = df_iris_train["class"]
    clf = lgb.LGBMClassifier()
    clf.fit(X, y)
    with open("{path}/iris.pkl", "wb") as f:
        pickle.dump(clf, f)

    experiment_response = dholib_client.create_experiment(
        experiment_name="iris-classification", experiment_tags={"tag": "iris"}
    )

    experiment_run = dholib_client.run_experiment(
        type_model="lightgbm",
        experiment_id=experiment_response["experiment_id"],
        model_pkl_path="{path}/iris.pkl",
        requirements_path="{path}/requirements.txt",
    )
    print(experiment_run)
    run_params = {
        "modelname": "iris-classification",
        "modeltag": "lightgbm",
        "stage": "Production",
        "run_id": experiment_run["run_id"],
        "model_uri": experiment_run["model_uri"],
    }
    result = dholib_client.create_model(run_params)
    print(result)


if __name__ == "__main__":
    train()





```
### Inferência


####  Esse é o script de predição. Para que o fluxo em lote seja executado é necessario fazer um deploy desse arquivo junto com o requirements na dhuolib. Ao fazer isso o script estará disponivel para o processamento em batch. 

Obs: O nome deve ser sempre script.py

```python
from dhuolib.clients.experiment import DhuolibExperimentClient
from dhuolib.repository import DatabaseConnection, GenericRepository


def get_repository(config_file_name):
    if not config_file_name:
        raise ValueError("config_file_name is required")

    db = DatabaseConnection(config_file_name=config_file_name)
    repository = GenericRepository(db_connection=db)

    return repository

def predict():
    dholib_client = DhuolibExperimentClient(
        service_endpoint="http://{path}")
    repository = get_repository(
        config_file_name="{path}/database.json"
    )
    df_iris = repository.to_dataframe(
        table_name="IRIS_FEATURE",
        list_columns=["SEPAL_LENGTH", "SEPAL_WIDTH",
                      "PETAL_LENGTH", "PETAL_WIDTH"],
    )

    print(df_iris.head(10))

    batch_params = {
        "modelname": "iris-classification-lightgbm",
        "stage": "Production",
        "experiment_name": "iris-classification",
        "type_model": "lightgbm",
        "run_id": "",
        "batch_model_dir": "iris.pkl",
    }
    predicts = dholib_client.prediction_batch_with_dataframe(
        batch_params=batch_params, df=df_iris
    )
    df_iris["predict"] = predicts
    repository.update_table_by_dataframe(
        table_name="IRIS_OUTPUT", df_predict=df_iris)


if __name__ == "__main__":
    predict()
```

### Fazendo o deploy e executando a solução

- `dholib_platform.create_batch_project` - Crie o projeto
- `dholib_platform.deploy_batch_project` - Faça o deploy do projeto. Lembre de passar o caminho do script e o caminho do requirements
- `dholib_platform.pipeline_status_report` - Verifique os status da execução
```python

from dhuolib.clients.platform import DhuolibPlatformClient


def deploy():
    dholib_platform = DhuolibPlatformClient(
        service_endpoint="http://{endpoint}")
    dholib_platform.create_batch_project('iris-classification-lightgbm-1')
    response = dholib_platform.deploy_batch_project(
        script_filename="{path}/script.py",
        requirements_filename="{path}/requirements.txt"
    )
    print(response)
    print(dholib_platform.pipeline_status_report())


if __name__ == "__main__":
    deploy()

```
### Executando o projeto remotamente

- `dholib_platform.create_cluster` - Crie o cluster. O cluster possui 3 niveis de capacidade. SMALL(1), MEDIUM(2) ou LARGE(3). 
- `dholib_platform.batch_run` - Execute o script do projeto

```python

from dhuolib.clients.platform import DhuolibPlatformClient


def run():
    dholib_platform = DhuolibPlatformClient(
        service_endpoint="http://{endpoint}",
        project_name="iris-classification-lightgbm-1")

    response_create_cluster = dholib_platform.create_cluster(1)
    print(response_create_cluster)
    response_batch_run = dholib_platform.batch_run()
    print(response_batch_run)


if __name__ == "__main__":
    run()

```
## Referências
- https://docs.oracle.com/pt-br/iaas/data-flow/using/third-party-provide-archive.htm
- https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml
- https://plantuml.com/
