Add generic production context helpers

This commit is contained in:
2026-09-06 05:16:25 +02:00
parent 9fea9cb6ba
commit 0856629e77
3 changed files with 188 additions and 3 deletions
@@ -0,0 +1,62 @@
"""Deterministic ERP context helpers, independent of database and timeseries access."""
import math
import re
__all__ = ["build_enlyze_production_order", "extract_nominal_width_m"]
# Capture malformed numeric tokens too, so a valid-looking suffix cannot become width.
_NUMBER_TOKEN = r"[+-]?(?:[0-9]+(?:[.,][0-9]+)*|inf(?:inity)?|nan)(?:[eE][+-]?[0-9]+)?"
_DIMENSIONS = re.compile(
rf"(?<![\w.,+\-/])(?P<width>{_NUMBER_TOKEN})\s*x\s*"
rf"(?P<length>{_NUMBER_TOKEN})\s*m(?![\w/²³^])",
re.IGNORECASE,
)
_PLAIN_NUMBER = re.compile(r"[0-9]+(?:[.,][0-9]+)?")
def build_enlyze_production_order(erp_production_order: str, format_template: str) -> str:
"""Format a single ERP order using explicit caller-supplied configuration.
The template must contain exactly one literal {production_order} placeholder
and no other braces. Outer order whitespace is stripped; the order
must otherwise contain ASCII digits only (leading zeros are preserved).
This is not a parser for ENLYZE identifiers, combined or otherwise.
"""
order = erp_production_order.strip()
if not order or not order.isascii() or not order.isdigit():
raise ValueError("ERP production order must be a non-empty ASCII digit string")
placeholder = "{production_order}"
literal = format_template.replace(placeholder, "")
if format_template.count(placeholder) != 1 or "{" in literal or "}" in literal:
raise ValueError("Format template must contain exactly one {production_order} placeholder")
return format_template.replace(placeholder, order)
def extract_nominal_width_m(article_description: str | None) -> float | None:
"""Read one unambiguous positive '<width> x <length> m' pair from ERP text.
Decimal comma/point and variable whitespace are supported. Multiple pairs,
dimension chains, signed/scientific notation and invalid dimensions fail
closed.
"""
if article_description is None:
return None
matches = list(_DIMENSIONS.finditer(article_description))
if len(matches) != 1:
return None
match = matches[0]
# Do not mistake the tail of a three-dimensional expression for a width pair.
if re.search(r"[x×]\s*$", article_description[:match.start()], re.IGNORECASE):
return None
if re.match(r"\s*[x×]", article_description[match.end():], re.IGNORECASE):
return None
values = []
for token in (match["width"], match["length"]):
if _PLAIN_NUMBER.fullmatch(token) is None:
return None
value = float(token.replace(",", "."))
if not math.isfinite(value) or value <= 0:
return None
values.append(value)
return values[0]