SIENTIAPDE-1646
Harden retrain merge and OPC write against silent failures - pandas merge now uses validate='one_to_one' so duplicated timestamps from wrapper.retrain fail loudly instead of silently multiplying rows - OPC write_data raises OpcClientNotInitializedError instead of a bare assert when client is torn down between validation and write, so callers get a reported error instead of an AssertionError - add regression test covering the torn-down client path Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -529,21 +529,26 @@ class MLFlow(SientiaMonitoring):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Keep heavy model fitting outside MLflow run timing.
|
# Keep heavy model fitting outside MLflow run timing.
|
||||||
prediction_data = wrapper.retrain(data)
|
prediction_data = wrapper.retrain(data).rename(columns={target: 'prediction'})
|
||||||
prediction_data.rename(columns={target: 'prediction'}, inplace=True)
|
|
||||||
|
|
||||||
# Merge prediction data with retrain data
|
# Merge prediction data with retrain data. Both sides are indexed by the pivoted
|
||||||
|
# timestamp, so one row per timestamp on each side; `validate` fails loudly instead
|
||||||
|
# of silently multiplying rows if the wrapper ever returns duplicated timestamps.
|
||||||
evaluation_data = pd.merge(
|
evaluation_data = pd.merge(
|
||||||
data, prediction_data, left_index=True, right_index=True, how='left'
|
data,
|
||||||
|
prediction_data,
|
||||||
|
left_index=True,
|
||||||
|
right_index=True,
|
||||||
|
how='left',
|
||||||
|
validate='one_to_one',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Rename target column to "target"
|
# Rename target column to "target", then move the index into a "timestamp" column
|
||||||
evaluation_data.rename(columns={target: 'target'}, inplace=True)
|
evaluation_data = evaluation_data.rename(columns={target: 'target'})
|
||||||
|
|
||||||
# Reset index and put as column "timestamp"
|
|
||||||
evaluation_data['timestamp'] = evaluation_data.index
|
evaluation_data['timestamp'] = evaluation_data.index
|
||||||
evaluation_data.reset_index(drop=True, inplace=True)
|
evaluation_data = evaluation_data.reset_index(drop=True).sort_values(
|
||||||
evaluation_data.sort_values(by='timestamp', inplace=True, ascending=True)
|
by='timestamp', ascending=True
|
||||||
|
)
|
||||||
|
|
||||||
run_name = f'{model_name}-retrain-{datetime.now().strftime("%Y%m%d%H%M%S")}'
|
run_name = f'{model_name}-retrain-{datetime.now().strftime("%Y%m%d%H%M%S")}'
|
||||||
|
|
||||||
|
|||||||
@@ -843,7 +843,10 @@ class OpcRepository(SientiaMonitoring):
|
|||||||
session_id = _opc_authentication_token_str(self.client)
|
session_id = _opc_authentication_token_str(self.client)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
assert self.client is not None
|
if self.client is None:
|
||||||
|
raise OpcClientNotInitializedError(
|
||||||
|
f'OPC client is not initialized for server {self.id}; cannot write {node}'
|
||||||
|
)
|
||||||
node_obj = self.client.get_node(node)
|
node_obj = self.client.get_node(node)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if is_reconnectable_opcua_bad(e):
|
if is_reconnectable_opcua_bad(e):
|
||||||
|
|||||||
@@ -444,6 +444,22 @@ def test_write_data_get_node_failed(opc_repository):
|
|||||||
assert error_data['attachment_content'] is not None
|
assert error_data['attachment_content'] is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_data_without_client_fails_as_get_node_error(opc_repository):
|
||||||
|
"""A client torn down between validation and the write is reported, not asserted away."""
|
||||||
|
opc_repository.validate_connection = MagicMock(return_value=(True, {}))
|
||||||
|
opc_repository.client = None
|
||||||
|
|
||||||
|
is_success, error_data = opc_repository.write_data(
|
||||||
|
'ns=2;s=TestNode', 42.0, 'float', metadata['metadata']
|
||||||
|
)
|
||||||
|
|
||||||
|
assert is_success is False
|
||||||
|
assert error_data['notification_id'] == f'OPC_WRITE_GET_NODE_ERROR_{opc_repository.id}'
|
||||||
|
assert 'OPC client is not initialized' in error_data['message']
|
||||||
|
last_call = opc_repository.emit_metric_sync.call_args_list[-1]
|
||||||
|
assert last_call.args[1]['result'] == 'GetNodeError:OpcClientNotInitializedError'
|
||||||
|
|
||||||
|
|
||||||
def test_write_data_invalid_data_type(opc_repository, mock_client):
|
def test_write_data_invalid_data_type(opc_repository, mock_client):
|
||||||
opc_repository.validate_connection = MagicMock(return_value=(True, {}))
|
opc_repository.validate_connection = MagicMock(return_value=(True, {}))
|
||||||
opc_repository.client = mock_client
|
opc_repository.client = mock_client
|
||||||
|
|||||||
Reference in New Issue
Block a user