Add Bento 1 fresh bentonite consumption

This commit is contained in:
2026-09-06 13:29:06 +02:00
parent ed0e20a04b
commit be8d76507a
11 changed files with 430 additions and 14 deletions
@@ -1,6 +1,6 @@
"""Strict configuration for executable material-consumption instances."""
from dataclasses import dataclass, fields
from dataclasses import MISSING, dataclass, fields
from math import isfinite
from pathlib import Path
@@ -54,6 +54,10 @@ class MaterialCalculationConfig:
output_metric: str
output_unit: str
group_by: str
source_mode: str = "direct_mass_rate"
application_signal_refs: tuple[str, ...] = ()
erp_workplace: str = ""
production_order_format: str = ""
def load_material_calculation(
@@ -71,7 +75,9 @@ def load_material_calculation(
entries = document["calculations"]
if not isinstance(entries, list) or not entries:
raise CalculationConfigError("calculations must be a non-empty list")
required = {field.name for field in fields(MaterialCalculationConfig)}
allowed = {field.name for field in fields(MaterialCalculationConfig)}
required = {field.name for field in fields(MaterialCalculationConfig)
if field.default is MISSING}
instances = {}
for index, entry in enumerate(entries):
prefix = f"calculations[{index}]"
@@ -79,10 +85,13 @@ def load_material_calculation(
raise CalculationConfigError(f"{prefix} must be a mapping")
if "type" in entry and entry["type"] != "material_consumption":
raise CalculationConfigError(f"{prefix}.type: only 'material_consumption' is supported")
area = entry.get("source_mode", "direct_mass_rate") == "area_application"
if area:
entry.setdefault("rate_signal_ref", "unused")
missing = required - entry.keys()
if missing:
raise CalculationConfigError(f"{prefix}: missing fields: {', '.join(sorted(missing))}")
if entry.keys() - required:
if entry.keys() - allowed:
raise CalculationConfigError(f"{prefix}: unknown fields (check spelling)")
for name in sorted(required - {"gate_threshold", "max_sample_gap_seconds"}):
if not isinstance(entry[name], str) or not entry[name].strip():
@@ -98,6 +107,32 @@ def load_material_calculation(
entry["max_sample_gap_seconds"] = finite_number(
entry["max_sample_gap_seconds"], f"{prefix}.max_sample_gap_seconds", positive=True,
)
mode = entry.get("source_mode", "direct_mass_rate")
if not isinstance(mode, str) or mode not in {"direct_mass_rate", "area_application"}:
raise CalculationConfigError("Unsupported material source_mode")
if area:
refs = entry.get("application_signal_refs")
if (not isinstance(refs, list) or not refs
or any(not isinstance(ref, str) or not ref.strip() for ref in refs)
or len(set(refs)) != len(refs)):
raise CalculationConfigError(
"application_signal_refs must be unique signal strings"
)
if entry["rate_signal_ref"] != "unused":
raise CalculationConfigError("area_application cannot specify rate_signal_ref")
for name in ("erp_workplace", "production_order_format"):
if not isinstance(entry.get(name), str) or not entry[name].strip():
raise CalculationConfigError(f"area_application requires {name}")
from production_analytics.context import build_enlyze_production_order
try:
build_enlyze_production_order("0", entry["production_order_format"])
except ValueError as exc:
raise CalculationConfigError("Invalid production_order_format") from exc
entry["application_signal_refs"] = tuple(refs)
elif any(name in entry for name in (
"application_signal_refs", "erp_workplace", "production_order_format",
)):
raise CalculationConfigError("Area context fields require area_application")
if entry["id"] in instances:
raise CalculationConfigError("Calculation ids must be unique")
instances[entry["id"]] = MaterialCalculationConfig(**entry)