Use rotational discharge for Bento consumption
This commit is contained in:
@@ -80,6 +80,31 @@ def width_provider():
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('configured,returned', [
|
||||
('Bento 1', 'BENTO 1'),
|
||||
('Bento 1', ' \tBENTO 1\n'),
|
||||
(' \tBento 1\n', 'BENTO 1'),
|
||||
('K7', ' k7 '),
|
||||
])
|
||||
def test_width_context_normalizes_workplace(configured, returned):
|
||||
provider = width_provider()
|
||||
provider.workplace = configured
|
||||
provider.gateway.get_current_workplace_status.return_value.workplace = returned
|
||||
assert provider('Bento 1-12026000814') == 5.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('production_order', [
|
||||
'Bento 1-12026000815',
|
||||
'BENTO 1-12026000814',
|
||||
' Bento 1-12026000814 ',
|
||||
])
|
||||
def test_width_context_keeps_production_order_strict(production_order):
|
||||
provider = width_provider()
|
||||
provider.gateway.get_current_workplace_status.return_value.workplace = 'BENTO 1'
|
||||
with pytest.raises(ValueError, match='ERP context does not match the production order'):
|
||||
provider(production_order)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field,value', [
|
||||
('production_order', '12026000815'), ('workplace', 'K7'),
|
||||
('article_description', None), ('article_description', '180305'),
|
||||
@@ -91,10 +116,11 @@ def test_width_context_fails_closed(field, value):
|
||||
provider('Bento 1-12026000814')
|
||||
|
||||
|
||||
def test_bento_reference_aggregate_bootstrap_and_persistent_resume(tmp_path):
|
||||
@pytest.mark.parametrize('inter_run_gap_seconds', [5, 3761])
|
||||
def test_bento_rpm_bootstrap_and_persistent_resume(tmp_path, inter_run_gap_seconds):
|
||||
"""Synthetic aggregate fixture, not recorded ENLYZE samples.
|
||||
|
||||
Uses reported active time/area/application within the actual run boundaries.
|
||||
Uses realistic rpm values and synthetic active time within actual run boundaries.
|
||||
"""
|
||||
config = load_material_calculation('config/bento1-material-consumption.yaml')
|
||||
order = 'Bento 1-12026000814'
|
||||
@@ -106,7 +132,11 @@ def test_bento_reference_aggregate_bootstrap_and_persistent_resume(tmp_path):
|
||||
('25d67424-8346-42d4-b844-75b4097382bd',
|
||||
'2026-08-26T05:43:39Z', '2026-08-26T05:54:05Z'),
|
||||
]]
|
||||
speed = 31294.6 / (5 * 1256)
|
||||
runs[1] = replace(
|
||||
runs[1], start=runs[0].end + timedelta(seconds=inter_run_gap_seconds),
|
||||
end=runs[0].end + timedelta(seconds=inter_run_gap_seconds + 626),
|
||||
)
|
||||
speed = 2.0
|
||||
client = Mock()
|
||||
|
||||
def response(path, body):
|
||||
@@ -122,8 +152,8 @@ def test_bento_reference_aggregate_bootstrap_and_persistent_resume(tmp_path):
|
||||
if start <= stop <= end:
|
||||
times.add(stop)
|
||||
return SimpleNamespace(body={'data': {
|
||||
'columns': ['time', *config.application_signal_refs, config.gate_signal_ref],
|
||||
'records': [[t.isoformat(), 2300, 2398, speed if t < stop else 0]
|
||||
'columns': ['time', *config.rotational_speed_signal_refs, config.gate_signal_ref],
|
||||
'records': [[t.isoformat(), 3.739, 3.956, speed if t < stop else 0]
|
||||
for t in sorted(times)],
|
||||
}})
|
||||
|
||||
@@ -139,35 +169,44 @@ def test_bento_reference_aggregate_bootstrap_and_persistent_resume(tmp_path):
|
||||
rate_variable_id=config.rate_signal_ref, gate_variable_id=config.gate_signal_ref,
|
||||
gate_threshold=config.gate_threshold,
|
||||
max_sample_gap_seconds=config.max_sample_gap_seconds,
|
||||
application_variable_ids=config.application_signal_refs,
|
||||
rotational_speed_variable_ids=config.rotational_speed_signal_refs,
|
||||
specific_discharge_kg_per_rev_m=config.specific_discharge_kg_per_rev_m,
|
||||
nominal_width_provider=width_provider(),
|
||||
)
|
||||
|
||||
partial = service().poll_once(now=runs[1].start + timedelta(seconds=300))
|
||||
result = service().poll_once(now=runs[1].end)
|
||||
assert result.state.cumulative_consumption_kg - partial.state.cumulative_consumption_kg == (
|
||||
pytest.approx((3.739 + 3.956) * 5 * 3.12 * 326 / 60)
|
||||
)
|
||||
assert result.state.integrated_running_seconds / 60 == pytest.approx(1256)
|
||||
assert result.state.cumulative_consumption_kg == pytest.approx(147021.9, abs=0.2)
|
||||
assert result.state.integrated_running_seconds / 60 * speed * 5 == pytest.approx(31294.6)
|
||||
assert result.state.cumulative_consumption_kg == pytest.approx(
|
||||
(3.739 + 3.956) * 5 * 3.12 * 1256,
|
||||
)
|
||||
assert service().poll_once(now=runs[1].end).state == result.state
|
||||
assert gateway.get_production_runs.call_count == 1
|
||||
assert [(timestamp(call.args[1]['start']), timestamp(call.args[1]['end']))
|
||||
for call in client.post_json.call_args_list[:2]] == [
|
||||
(run.start, run.end) for run in runs]
|
||||
(runs[0].start, runs[0].end),
|
||||
(runs[1].start, runs[1].start + timedelta(seconds=300)),
|
||||
]
|
||||
|
||||
|
||||
def test_k7_config_defaults_remain_direct():
|
||||
config = load_material_calculation('config/k7-material-consumption.yaml')
|
||||
assert config.source_mode == 'direct_mass_rate'
|
||||
assert config.rotational_speed_signal_refs == ()
|
||||
assert config.application_signal_refs == ()
|
||||
assert config.gate_threshold == 0.5
|
||||
assert config.rate_signal_ref == 'c9d06af5-f6d6-4ede-b6c4-5a98bac77129'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field,value', [
|
||||
('source_mode', 'unknown'), ('application_signal_refs', []),
|
||||
('application_signal_refs', ['same', 'same']), ('erp_workplace', ''),
|
||||
('source_mode', 'unknown'), ('rotational_speed_signal_refs', []),
|
||||
('rotational_speed_signal_refs', ['same', 'same']), ('erp_workplace', ''),
|
||||
('production_order_format', '{wrong}'), ('rate_signal_ref', 'direct'),
|
||||
])
|
||||
def test_invalid_area_configuration(tmp_path, field, value):
|
||||
def test_invalid_rotational_configuration(tmp_path, field, value):
|
||||
with open('config/bento1-material-consumption.yaml') as stream:
|
||||
document = yaml.safe_load(stream)
|
||||
document['calculations'][0][field] = value
|
||||
@@ -177,7 +216,7 @@ def test_invalid_area_configuration(tmp_path, field, value):
|
||||
load_material_calculation(path)
|
||||
|
||||
|
||||
def test_area_context_failure_does_not_touch_persistent_state(tmp_path):
|
||||
def test_rotational_context_failure_does_not_touch_persistent_state(tmp_path):
|
||||
config = load_material_calculation('config/bento1-material-consumption.yaml')
|
||||
gateway = Mock()
|
||||
now = timestamp('2026-08-25T05:27:18Z')
|
||||
@@ -191,7 +230,9 @@ def test_area_context_failure_does_not_touch_persistent_state(tmp_path):
|
||||
gateway=gateway, state_store=store, machine_id=config.machine_ref,
|
||||
rate_variable_id=config.rate_signal_ref, gate_variable_id=config.gate_signal_ref,
|
||||
gate_threshold=config.gate_threshold, max_sample_gap_seconds=20,
|
||||
application_variable_ids=config.application_signal_refs, nominal_width_provider=provider,
|
||||
rotational_speed_variable_ids=config.rotational_speed_signal_refs,
|
||||
specific_discharge_kg_per_rev_m=config.specific_discharge_kg_per_rev_m,
|
||||
nominal_width_provider=provider,
|
||||
)
|
||||
with pytest.raises(ValueError, match='context'):
|
||||
service.poll_once(now=now)
|
||||
|
||||
Reference in New Issue
Block a user