SIENTIAPDE-1241: Remove model equation implementation documentation and add test coverage for _generate_artifacts in ModelRepository.

This commit is contained in:
Bruno Domingues
2025-10-30 14:56:10 -03:00
parent a5c0cb6e47
commit f5c1ac3615
2 changed files with 45 additions and 78 deletions

View File

@@ -1,78 +0,0 @@
# Implementação da Equação do Modelo como Artefato JSON
## Visão Geral
Esta implementação adiciona a capacidade de extrair e salvar a equação do modelo de regressão linear como um artefato JSON, seguindo a arquitetura existente do projeto.
## Mudanças Implementadas
### 1. TrainModelResult
- **Arquivo**: `model_manager/utils/models/train_model_result.py`
- **Mudanças**:
- Adicionado campo `equation: dict | None = None` para armazenar os metadados da equação
- Adicionado campo `equation_path: str | None = None` para armazenar o caminho do arquivo JSON
### 2. TrainingRepository
- **Arquivo**: `model_manager/utils/repository/training_repository.py`
- **Mudanças**:
- Adicionado método `_extract_model_equation()` para extrair coeficientes e intercept do modelo
- Integrado a extração da equação no método `after_train_calculation()`
### 3. ModelRepository
- **Arquivo**: `model_manager/utils/repository/model_repository.py`
- **Mudanças**:
- Adicionado import do módulo `json`
- Modificado `_generate_report()` para salvar a equação como arquivo JSON
- Modificado `_save_run()` para fazer log do artefato da equação no MLflow
## Estrutura do JSON da Equação
O arquivo `model_equation.json` terá a seguinte estrutura:
```json
{
"target_variable": "target_column_name",
"coefficients": {
"feature1": 0.123456,
"feature2": -0.789012,
"feature3": 0.345678
},
"intercept": 1.234567,
"equation_string": "target_column_name = 1.234567 + 0.123456 * feature1 + -0.789012 * feature2 + 0.345678 * feature3",
"latex_equation": "target_column_name = 1.234567 + 0.123456 \\cdot feature1 + -0.789012 \\cdot feature2 + 0.345678 \\cdot feature3",
"model_type": "Linear Regression"
}
```
## Fluxo de Execução
1. **Treinamento**: O modelo é treinado no `TrainingRepository.train()`
2. **Pós-treinamento**: O método `after_train_calculation()` é chamado, que:
- Calcula as métricas (MSE, MAE, R²)
- Extrai a equação usando `_extract_model_equation()`
3. **Salvamento**: O `ModelRepository.save_model()` é chamado, que:
- Gera os artefatos (relatórios, dados CSV)
- Salva a equação como `model_equation.json`
- Faz log de todos os artefatos no MLflow
## Benefícios
- **Rastreabilidade**: A equação fica disponível como artefato versionado no MLflow
- **Transparência**: Fácil acesso aos coeficientes e estrutura do modelo
- **Compatibilidade**: Formato JSON facilita integração com outras ferramentas
- **Flexibilidade**: Inclui tanto formato legível quanto LaTeX para diferentes usos
## Compatibilidade
Esta implementação é totalmente compatível com:
- A arquitetura existente do projeto
- O fluxo de treinamento atual
- O sistema de logging do MLflow
- Os testes existentes (não quebra funcionalidades)
## Exemplo de Uso
Após o treinamento, a equação estará disponível em:
- **Memória**: `train_result.equation` (dicionário Python)
- **Arquivo**: `train_result.equation_path` (caminho para o JSON)
- **MLflow**: Como artefato `model_equation.json` no run do experimento

View File

@@ -802,3 +802,48 @@ def test_generate_artifacts_header_not_found(
with pytest.raises(FileNotFoundError, match='Header file does not exist'): with pytest.raises(FileNotFoundError, match='Header file does not exist'):
repo._generate_artifacts(mock_train_result) repo._generate_artifacts(mock_train_result)
@patch('model_manager.utils.repository.model_repository.ModelServing')
@patch('model_manager.utils.repository.model_repository.path.exists')
@patch('model_manager.utils.repository.model_repository.path.join')
def test_generate_artifacts_success(
mock_join, mock_exists, mock_model_serving_class, mock_logger, mock_train_result
):
"""Test _generate_artifacts success case covering lines 147-148."""
from model_manager.utils.repository.model_repository import ModelRepository
repo = ModelRepository(
url='http://mlflow.test', username='user', password='pass', logger=mock_logger
)
# Mock path.join to return predictable paths
def join_side_effect(*args):
return '/'.join(args)
mock_join.side_effect = join_side_effect
mock_exists.return_value = True # Both reports dir and header.html exist
# Mock the internal methods to avoid actual file operations
with (
patch.object(repo, '_setup_run_directory') as mock_setup,
patch.object(repo, '_generate_report') as mock_generate_report,
patch.object(repo, '_init_artifacts_data') as mock_init_data,
patch.object(repo, '_get_reports_directory') as mock_get_reports_dir,
patch.object(repo, '_create_run_directory') as mock_create_run_dir,
):
# Setup mocks
mock_init_data.return_value = (pd.DataFrame(), pd.DataFrame())
mock_get_reports_dir.return_value = '/reports'
mock_create_run_dir.return_value = '/reports/run_1'
mock_generate_report.return_value = mock_train_result
# Call the method
result = repo._generate_artifacts(mock_train_result)
# Verify the methods on lines 147-148 were called
mock_setup.assert_called_once_with('/reports/run_1', '/reports/header.html')
mock_generate_report.assert_called_once()
# Verify result
assert result == mock_train_result