Update E2E test report and enhance drift analysis handling - Updated the E2E test report metrics to reflect the latest test results, showing 47 collected tests with all passing. - Removed outdated sections related to failed tests and their causes, streamlining the report. - Implemented a regression fix in the drift analysis to handle empty merged frames, ensuring workflows skip export when no drift metrics are available. - Enhanced the `insert_sample_data` and `insert_sample_prediction` functions to allow customizable timestamps for better test accuracy. - Refactored E2E tests to improve clarity and maintainability, particularly in handling repeat scenarios with distinct timestamps.
436 lines
15 KiB
Markdown
436 lines
15 KiB
Markdown
# E2E Scenario Documentation - Predictions Batch
|
||
|
||
This document describes the end-to-end scenarios for `predictions_batch` and its child workflows:
|
||
`prediction_process` and `format_and_export_prediction`.
|
||
|
||
It is a functional reference of scenario behavior, inputs, and expected outcomes.
|
||
|
||
## Execution Context
|
||
|
||
- Tests run under `e2e/` and are marked with `@pytest.mark.integration`.
|
||
- PostgreSQL and MinIO are provisioned with testcontainers.
|
||
- `test_minio_offload.py` uses real MinIO I/O; other scenario suites may use stubs/mocks for optional outputs.
|
||
|
||
---
|
||
|
||
## 1. Main Workflow Scenarios
|
||
Source: `e2e/test_predictions_batch_main_workflow.py`
|
||
|
||
### 1.1.1 Happy Path - Complete Success
|
||
**Summary**: Full workflow succeeds with valid query and default gate behavior.
|
||
|
||
**Description**:
|
||
- Query returns rows for a model.
|
||
- `prediction_process` runs transform and predict paths.
|
||
- Final prediction and transformed data are persisted.
|
||
|
||
**Expected Outcome**:
|
||
- Exactly one prediction row is created.
|
||
- Transform rows are created.
|
||
- Confidence/status/comments are success values.
|
||
|
||
### 1.2.1 SQL Query Execution Error
|
||
**Summary**: Invalid SQL leads to no persisted prediction.
|
||
|
||
**Description**:
|
||
- Input query is invalid.
|
||
- Load step fails and workflow follows error/short-circuit path.
|
||
|
||
**Expected Outcome**:
|
||
- No prediction rows for the model.
|
||
- Workflow does not require retry-loop assumptions in assertions.
|
||
|
||
### 1.2.2 Missing Required Parameters
|
||
**Summary**: Missing required fields prevent workflow completion path.
|
||
|
||
**Description**:
|
||
- Required input key (e.g. `query`) is omitted.
|
||
- Workflow fails to produce actionable input for child flow.
|
||
|
||
**Expected Outcome**:
|
||
- No prediction rows are persisted.
|
||
- Workflow handle may require explicit terminate in E2E harness.
|
||
|
||
### 1.2.3 Invalid Datetime Column Specification (de-prioritized)
|
||
**Summary**: Legacy invalid datetime-column case is retained only as low-priority legacy coverage.
|
||
|
||
**Description**:
|
||
- `datetime_columns` references non-existing columns.
|
||
- Behavior may vary by query shape and parser fallback.
|
||
|
||
**Expected Outcome**:
|
||
- No predictions persisted in the covered legacy assertion path.
|
||
- Scenario is not considered primary behavior coverage.
|
||
|
||
---
|
||
|
||
## 2. Prediction Process Scenarios
|
||
Source: `e2e/test_predictions_batch_prediction_process.py`
|
||
|
||
### 2.1 Input Gate Path Decisions
|
||
|
||
#### 2.1.1 CONTINUE
|
||
**Summary**: Input filter flags quality issue but allows continuation via default path.
|
||
|
||
**Description**:
|
||
- Input gate returns `CONTINUE`.
|
||
- MLFlow transform/predict are skipped.
|
||
- Export path persists default-style prediction with warning context.
|
||
|
||
#### 2.1.2 STOP
|
||
**Summary**: Input filter blocks processing.
|
||
|
||
**Description**:
|
||
- Input gate returns `STOP`.
|
||
- Workflow exits without export.
|
||
|
||
#### 2.1.3 REPEAT with history
|
||
**Summary**: Prior prediction is reused; outcome depends on batch vs history timestamp.
|
||
|
||
**Description**:
|
||
- Input gate returns `REPEAT`.
|
||
- `repeat_last_prediction` inserts a row using ``last_timestamp`` from the batch payload (max timestamp in `laborious_data` for the query), not the copied row’s timestamp.
|
||
|
||
**Tests**:
|
||
- **Collision**: batch `last_timestamp` equals the historical prediction row’s `timestamp` → Postgres `unique_model_id_timestamp` violation; workflow fails; still one row.
|
||
- **Distinct batch time**: laborious rows are stamped later than the historical prediction → second row inserted; same prediction fields as the first (see `assert_repeat`).
|
||
|
||
#### 2.1.4 REPEAT without history
|
||
**Summary**: Repeat requested but no previous prediction exists.
|
||
|
||
**Description**:
|
||
- Input gate returns `REPEAT`.
|
||
- No prior row is available to duplicate.
|
||
|
||
**Expected Outcome**:
|
||
- No new prediction rows are created for the model.
|
||
|
||
### 2.2 Transform Gate Decisions
|
||
|
||
#### 2.2.1 CONTINUE on transform response error
|
||
**Summary**: Transform response is degraded, but workflow continues.
|
||
|
||
#### 2.2.2 STOP on transform response error
|
||
**Summary**: Transform response error blocks downstream processing.
|
||
|
||
#### 2.2.3 REPEAT on transform response error
|
||
**Summary**: Transform response error triggers repeat-last-prediction path.
|
||
|
||
**Tests**: Same timestamp collision vs distinct batch timestamp as §2.1.3 (`*_fails` / `*_inserts_second_row`).
|
||
|
||
#### 2.2.4 STOP on transform content NaN
|
||
**Summary**: Content gate (`NAN_VALUES`) blocks on all-NaN transform payload.
|
||
|
||
### 2.3 Predict Gate Decisions
|
||
|
||
#### 2.3.1 CONTINUE on predict response error
|
||
**Summary**: Predict response degraded; workflow exports with degraded metadata.
|
||
|
||
#### 2.3.2 STOP on predict response error
|
||
**Summary**: Predict response error blocks export.
|
||
|
||
#### 2.3.3 REPEAT on predict response error
|
||
**Summary**: Predict response error routes to repeat-last-prediction.
|
||
|
||
**Tests**: Same timestamp collision vs distinct batch timestamp as §2.1.3 (`*_fails` / `*_inserts_second_row`).
|
||
|
||
### 2.4.1 Priority Conflict Resolution
|
||
**Summary**: Deterministic selection when multiple filters produce different flags.
|
||
|
||
**Description**:
|
||
- Multiple filters may produce `STOP`, `CONTINUE`, and/or `REPEAT`.
|
||
- `path_priority` defines precedence.
|
||
|
||
**Expected Outcome**:
|
||
- Highest-priority flag is applied consistently.
|
||
- Executed branch matches configured priority ordering.
|
||
|
||
---
|
||
|
||
## 3. Format and Export Scenarios
|
||
Source: `e2e/test_predictions_batch_format_export.py`
|
||
|
||
### 3.1 Output Combination Scenarios
|
||
|
||
#### 3.1.1 Default prediction export
|
||
**Summary**: Non-`None` path flag uses `format_default_prediction`.
|
||
|
||
**Description**:
|
||
- Default prediction is generated.
|
||
- Transform export is skipped.
|
||
- Optional outputs (PI/OPC) still execute when configured.
|
||
|
||
#### 3.1.2 OPC only
|
||
**Summary**: Postgres + OPC writes, PI Web API disabled.
|
||
|
||
#### 3.1.3 PI Web API only
|
||
**Summary**: Postgres + PI writes, OPC disabled.
|
||
|
||
#### 3.1.4 Postgres only
|
||
**Summary**: Both optional outputs disabled; only Postgres persistence and metrics.
|
||
|
||
#### 3.1.5 No transformed data export
|
||
**Summary**: Prediction is persisted; transformed table is not written.
|
||
|
||
### 3.2 Degraded-but-successful Completion
|
||
|
||
#### 3.2.1 PI Web API write error
|
||
**Summary**: PI write failure does not fail workflow.
|
||
|
||
**Expected Outcome**:
|
||
- Workflow completes.
|
||
- Prediction persisted with degraded confidence/comments (PI error semantics).
|
||
|
||
#### 3.2.2 OPC write error
|
||
**Summary**: OPC write failure does not fail workflow.
|
||
|
||
**Expected Outcome**:
|
||
- Workflow completes.
|
||
- Prediction persisted with OPC degraded confidence/comments.
|
||
|
||
#### 3.2.3 PI Web API partial write error
|
||
**Summary**: Partial PI acknowledgement is treated as degraded success.
|
||
|
||
**Expected Outcome**:
|
||
- Workflow completes.
|
||
- Prediction persisted with PI error confidence and descriptive comment.
|
||
|
||
### 3.3.1 Combined Optional Outputs (PI + OPC)
|
||
**Summary**: Both external output channels are enabled together.
|
||
|
||
**Description**:
|
||
- PI Web API and OPC configs are both present.
|
||
- Output mutation order matters for final persisted payload.
|
||
|
||
**Expected Outcome**:
|
||
- PI write executes before OPC write in workflow sequence.
|
||
- Final Postgres payload reflects any confidence/comment updates.
|
||
- OPC metrics are emitted when tag writes return response times.
|
||
|
||
---
|
||
|
||
## 4. MinIO Offload Scenarios
|
||
Source: `e2e/test_minio_offload.py`
|
||
|
||
### 4.1.1 Forced offload to MinIO
|
||
**Summary**: Very low threshold forces parquet upload.
|
||
|
||
**Description**:
|
||
- Payload is offloaded (`object_key` present, inline data absent/empty).
|
||
- Object is present in MinIO under `prediction_datasets/...`.
|
||
- Retrieval reconstructs the dataframe.
|
||
|
||
### 4.1.2 Full workflow with offloaded load payload
|
||
**Summary**: Offload path works during full `predictions_batch` execution.
|
||
|
||
**Expected Outcome**:
|
||
- Workflow completes.
|
||
- Prediction row is persisted.
|
||
|
||
### 4.2.1 Inline payload below threshold
|
||
**Summary**: Data remains inline when threshold is not exceeded.
|
||
|
||
**Expected Outcome**:
|
||
- Payload stores inline `data`.
|
||
- `object_key` is `None`.
|
||
- Downstream persistence behavior matches offload scenario semantics.
|
||
|
||
---
|
||
|
||
## 5. Drift Workflow Scenarios
|
||
Source: `e2e/test_drift.py`
|
||
|
||
The drift suite drives the **real** `sientia_model.analytics.drift_analysis.DriftAnalysis`
|
||
analyzer (no stubs / mocks). Each scenario exercises the full pipeline:
|
||
|
||
```
|
||
laborious_data (Postgres) -> load_custom_query
|
||
-> calculate_drift (DriftAnalysis univariate + multivariate)
|
||
-> export_data_to_postgres (sientia_data.drift_metrics)
|
||
```
|
||
|
||
The `mlflow_repository_stub` provides the reference-data CSV via
|
||
`download_artifacts`, and tests assert postgres rows in
|
||
`sientia_data.drift_metrics` against this canonical schema:
|
||
|
||
`id, model_id, feature, method, value, alert, chunk_index, chunk_start_date, chunk_end_date, accurate, timestamp, created_at`.
|
||
|
||
Tests assert behavioral / structural properties (column presence, NOT NULL
|
||
constraints, business-key invariants like uniform `timestamp` and stamped
|
||
`model_id`) rather than exact numeric drift scores, since those depend on
|
||
the real analyzer implementation and the synthetic data fed in.
|
||
|
||
### 5.1 Happy paths
|
||
|
||
#### D.1.1 Full pipeline persists all columns with reference data
|
||
**Summary**: 10 minutes of target data are inserted; a 10-row reference CSV
|
||
is configured via the MLflow stub. The `DriftAnalysis` runs end-to-end.
|
||
|
||
**Expected Outcome**:
|
||
- One row per `(chunk_index, feature, method)` plus a `multivariate` block
|
||
per chunk is persisted.
|
||
- Every column in the DDL is populated; `feature` is the only nullable column
|
||
per the new schema.
|
||
- `accurate=True` for every row (reference path).
|
||
- All three default univariate methods reach the analyzer.
|
||
- `model_id` is stamped as `text` and uniform across rows.
|
||
- `timestamp` equals `max(target_data.timestamp)` and is uniform across rows.
|
||
- `chunk_start_date` / `chunk_end_date` are persisted as ISO text and ordered.
|
||
- `p_value` is dropped before persistence.
|
||
|
||
#### D.1.2 30% fallback when reference data is unavailable
|
||
**Summary**: MLflow alias resolution is forced to fail so
|
||
`get_reference_data` returns `None`; `calculate_drift` falls back to the
|
||
first 30% of target rows as reference.
|
||
|
||
**Expected Outcome**:
|
||
- All persisted rows carry `accurate=False`.
|
||
- A `MODEL_METRICS_REFERENCE_DATA_WARNING` notification is emitted to MongoDB.
|
||
|
||
### 5.2 Failure paths
|
||
|
||
#### D.3.1 Empty target data short-circuits the workflow
|
||
**Summary**: `load_custom_query` returns no rows.
|
||
|
||
**Expected Outcome**:
|
||
- The workflow returns early and writes nothing to `sientia_data.drift_metrics`.
|
||
|
||
### 5.3 Configuration paths
|
||
|
||
#### D.4.2 Invalid `chunk_period` raises ValueError
|
||
**Summary**: Anything other than `min` / `s` is rejected by `calculate_drift`.
|
||
|
||
**Expected Outcome**:
|
||
- The workflow surfaces the `ValueError` ("Invalid chunk period: ...").
|
||
- No rows are persisted.
|
||
|
||
#### D.4.3a Insufficient drift metrics while target has rows
|
||
**Summary**: Laborious raises when the merged drift table is empty but the
|
||
target window is non-empty (`Insufficient drift data:` + notification
|
||
`MODEL_METRICS_DRIFT_INSUFFICIENT_DATA`).
|
||
|
||
**Description**:
|
||
- The e2e patches `ModelMetrics.get_drift_metrics` to return an empty
|
||
DataFrame, simulating a ``sientia_model`` path that emits no rows.
|
||
|
||
**Expected Outcome**:
|
||
- Workflow fails; no rows in `sientia_data.drift_metrics`.
|
||
|
||
#### D.4.3b `chunk_period='s'` preserves seconds in `chunk_start_date`
|
||
**Summary**: Target data includes sub-minute spacing across several minutes;
|
||
the activity uses `chunk_period='s'`.
|
||
|
||
**Expected Outcome**:
|
||
- At least one persisted `chunk_start_date` carries `seconds=30`, proving
|
||
that the analyzer chunked at sub-minute granularity and the ISO-text
|
||
serialization preserved the boundary.
|
||
|
||
**Note**: The e2e patches `DriftAnalysis._chunk_dataframe` to **skip empty**
|
||
`pd.Grouper(freq='s')` buckets. The stock implementation iterates every
|
||
second between min/max timestamps, producing empty chunks and NaT rows that
|
||
`calculate_drift` filters away entirely. The durable fix belongs in
|
||
`sientia_model`.
|
||
|
||
---
|
||
|
||
## 6. Simple Metrics Workflow Scenarios
|
||
Source: `e2e/test_simple_metrics.py`
|
||
|
||
Validates `sientia_data.simple_metrics` columns:
|
||
`id, model_id, metric, value, timestamp, data_size, interval_minutes, created_at`.
|
||
Note: ``timestamp`` is now nullable per the new DDL and ``model_id`` is ``text``.
|
||
|
||
### 6.1 Happy paths
|
||
|
||
#### S.1.1 rmse/mse/mae/r2 happy path
|
||
**Summary**: Prediction/target pairs are inserted; the activity computes all
|
||
four metrics with closed-form expected values.
|
||
|
||
**Expected Outcome**:
|
||
- One row per metric is persisted; all columns populated.
|
||
- `data_size` matches the joined row count and `interval_minutes=60`.
|
||
|
||
#### S.1.2 Subset metrics
|
||
**Summary**: Requesting `metrics=['rmse']` writes only the rmse row.
|
||
|
||
### 6.2 Edge cases
|
||
|
||
#### S.2.1 Zero-variance target returns r2=0
|
||
**Summary**: When all targets are equal, `ss_tot=0`; the activity must guard
|
||
against division by zero and return `r2=0`.
|
||
|
||
### 6.3 Failure paths
|
||
|
||
#### S.3.1 No overlapping data short-circuits persistence
|
||
**Summary**: With no `laborious_data` rows for the configured target variable
|
||
the workflow exits before `calculate_simple_metrics` and writes nothing.
|
||
|
||
---
|
||
|
||
## 7. Minimal Retrain Workflow Scenarios
|
||
Source: `e2e/test_minimal_retrain.py`
|
||
|
||
The MLflow registry is fully mocked (no real artifacts in test container).
|
||
Validates `sientia_data.log_retrain` columns:
|
||
`mlflow_experiment_id, mlflow_run_id, model_id, model_name, status, timestamp, version`.
|
||
Note: the new DDL drops the legacy ``id`` and ``created_at`` columns,
|
||
``mlflow_experiment_id`` is now ``int8`` and ``model_id`` is ``text``.
|
||
|
||
### 7.1 Happy path
|
||
|
||
#### MR.1.1 Successful retrain + promotion
|
||
**Summary**: Training data loads via MinIO offload, `wrapper.retrain` succeeds,
|
||
the new version is promoted to the `production` alias.
|
||
|
||
**Expected Outcome**:
|
||
- Report row has success status, `version='7'`, `mlflow_run_id='retrain-run-id'`,
|
||
`mlflow_experiment_id=4242` (`int8`).
|
||
- `mlflow.log_artifact` is called with the input CSV.
|
||
- `promote_to_alias` is called once with the resolved version and alias.
|
||
|
||
### 7.2 Failure paths
|
||
|
||
#### MR.2.1 Wrapper retrain raises
|
||
**Summary**: `wrapper.retrain` raises `RuntimeError`. The activity returns
|
||
`success=False`, `update_production_model` is NOT invoked.
|
||
|
||
**Expected Outcome**:
|
||
- Report row carries the error message and `version`/`mlflow_*` columns are NULL.
|
||
|
||
#### MR.2.2 Missing `model_config.target`
|
||
**Summary**: Empty model config short-circuits before any MLflow call.
|
||
|
||
**Expected Outcome**:
|
||
- Report row carries the explicit guard message.
|
||
- `get_cached_model` is never invoked.
|
||
|
||
#### MR.3.1 No training data
|
||
**Summary**: The training query returns no rows; the workflow does not
|
||
persist any report row. The current code raises plain `ValueError` from the
|
||
workflow function, which Temporal treats as a workflow-task failure (see
|
||
`CODE_ISSUES.md` issue MR-1).
|
||
|
||
---
|
||
|
||
## Input Contract Reference
|
||
|
||
Common scenario input fields:
|
||
- `schedule_name`
|
||
- `model_name`
|
||
- `model_id`
|
||
- `query`
|
||
- `schema`
|
||
- `table_name`
|
||
- `transform_table_name`
|
||
- `input_filters`
|
||
- `mlflow_transform_filters`
|
||
- `mlflow_predict_filters`
|
||
- `path_priority` (default order: `STOP`, `CONTINUE`, `REPEAT`)
|
||
- `save_transform`
|
||
- `prediction_store_policy`
|
||
- `model_config.target`
|
||
- `datetime_columns` (when query returns temporal fields)
|
||
|
||
Optional outputs:
|
||
- `opc_output_config`
|
||
- `pi_web_api_output_config` |