Add generic production context helpers
This commit is contained in:
@@ -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]
|
||||
Reference in New Issue
Block a user