1. Pretensioner and Load Limiter Model Automation 100 Cases

Why a Baseline Framework Comes First

Pretensioner and load limiter models are rarely built once. In a real restraint-system program, the same model topology is reused across dozens of variants: different stroke profiles, different force-displacement curves, different firing timings, and different occupant packages. If each variant is built by hand-editing a CAE deck, the result is a set of models that look similar but are not comparable, and every downstream comparison inherits that inconsistency.

This first case establishes the baseline automation framework: a single parameter schema, a single model template, and a single execution entry point. Every later case in this series extends one of these three artifacts rather than inventing its own. The goal here is not to produce a validated restraint model — it is to produce a contract that later cases can rely on.

The Three Artifacts and How They Relate

The framework separates what varies from what stays fixed from how it runs:

  • Parameter schema — a declarative description of every quantity that a pretensioner or load limiter variant may change. It is the only place where a parameter name, unit, and valid range are defined.
  • Model template — a model file containing placeholder tokens instead of hard-coded values. It defines the topology (pretensioner pull-in, force limiter stage, belt routing) and nothing about a specific variant.
  • Execution entry point — a single command that reads a parameter set, validates it against the schema, renders the template, and writes a runnable model plus a manifest.

The critical design decision is that the template never reads the schema directly and the schema never knows the template's syntax. The entry point is the only component that touches both, which means a template change cannot silently break parameter validation.

Parameter Schema schema.json pretensioner.stroke_mm pretensioner.pull_in_ms limiter.plateau_kN limiter.slope_kN_per_mm Model Template template.k *PARAMETER stroke = {{stroke_mm}} pullin = {{pull_in_ms}} plateau = {{plateau_kN}} Execution Entry Point build_model.py 1. load + validate params 2. render template tokens 3. write model + manifest validate render variant_model.k rendered deck manifest.json params + hashes validation error → abort, no output

Defining the Parameter Schema

The schema is a JSON document with one entry per parameter. Each entry carries a name, unit, type, default, and a validity range. The range is not decoration: it is the first line of defense against a test-data parser (case 2) or a curve fitter (case 3) injecting a physically impossible value.

{
  "schema_version": "1.0",
  "parameters": {
    "pretensioner.stroke_mm": {
      "unit": "mm", "type": "float", "default": 120.0,
      "min": 40.0, "max": 250.0,
      "description": "Total belt pull-in stroke"
    },
    "pretensioner.pull_in_ms": {
      "unit": "ms", "type": "float", "default": 12.0,
      "min": 3.0, "max": 40.0,
      "description": "Time to reach full stroke"
    },
    "limiter.plateau_kN": {
      "unit": "kN", "type": "float", "default": 3.5,
      "min": 1.0, "max": 8.0,
      "description": "Force limiter plateau level"
    },
    "limiter.slope_kN_per_mm": {
      "unit": "kN/mm", "type": "float", "default": 0.08,
      "min": 0.01, "max": 0.5,
      "description": "Post-plateau stiffness"
    }
  }
}

Two conventions matter for every later case. First, parameter keys are dotted and namespaced (pretensioner.*, limiter.*) so that a new subsystem can be added without renaming existing keys. Second, units are stored in the schema, not in the template — the template receives bare numbers, and unit conversion, if any, happens in the entry point.

The Model Template

The template is a plain text model deck in which every variant-dependent value is replaced by a {{key}} token. The token name is the schema key with the namespace prefix stripped, which keeps the template readable while preserving the link back to the schema.

*PRETENSIONER
stroke_mm   = {{stroke_mm}}
pull_in_ms  = {{pull_in_ms}}

*LOAD_LIMITER
plateau_kN  = {{plateau_kN}}
slope       = {{slope_kN_per_mm}}

Keep the template free of conditionals and loops. If a variant needs a different topology, that is a new template, not a new branch inside this one. This constraint is what makes the rendered decks comparable across variants.

The Execution Entry Point

The entry point is a single script that performs three steps in a fixed order: validate, render, write. Validation must complete before any file is written, so a bad parameter set never leaves a half-rendered deck on disk.

import json, re, hashlib, pathlib, sys

ROOT = pathlib.Path(__file__).parent
SCHEMA = json.loads((ROOT / "schema.json").read_text())
TEMPLATE = (ROOT / "template.k").read_text()
TOKEN = re.compile(r"\{\{(\w+)\}\}")

def validate(params):
    errors = []
    for key, spec in SCHEMA["parameters"].items():
        if key not in params:
            errors.append(f"missing: {key}")
            continue
        v = params[key]
        if not isinstance(v, (int, float)):
            errors.append(f"{key}: not numeric ({v!r})")
        elif not (spec["min"] <= v <= spec["max"]):
            errors.append(f"{key}: {v} outside [{spec['min']}, {spec['max']}]")
    return errors

def render(params):
    def sub(m):
        key = m.group(1)
        full = next(k for k in SCHEMA["parameters"] if k.endswith("." + key))
        return repr(params[full])
    return TOKEN.sub(sub, TEMPLATE)

def build(param_file, out_dir):
    params = json.loads(pathlib.Path(param_file).read_text())
    errors = validate(params)
    if errors:
        print("VALIDATION FAILED"); [print(" -", e) for e in errors]
        sys.exit(1)
    out_dir = pathlib.Path(out_dir); out_dir.mkdir(parents=True, exist_ok=True)
    deck = render(params)
    (out_dir / "variant_model.k").write_text(deck)
    manifest = {
        "schema_version": SCHEMA["schema_version"],
        "params": params,
        "deck_sha256": hashlib.sha256(deck.encode()).hexdigest(),
    }
    (out_dir / "manifest.json").write_text(json.dumps(manifest, indent=2))
    print(f"OK -> {out_dir}")

if __name__ == "__main__":
    build(sys.argv[1], sys.argv[2])

Run it against a parameter file:

python build_model.py params/base.json out/base

Expected console output on success: OK -> out/base, with out/base/variant_model.k and out/base/manifest.json present. On a bad value, the script prints VALIDATION FAILED followed by one line per offending parameter and exits with status 1 — no output directory is created.

The manifest's deck_sha256 is the reproducibility anchor for the whole series. Two runs from the same parameter file must produce the same hash; if they do not, something in the template or renderer is non-deterministic and must be fixed before any downstream comparison is trusted.

Verification Checklist

CheckMethodPass condition
Schema completenessRun with a params file missing one keyExit 1, message names the missing key
Range enforcementSet limiter.plateau_kN to 12.0Exit 1, value reported outside [1.0, 8.0]
Token coverageGrep rendered deck for {{No matches remain
DeterminismBuild twice, compare manifestsIdentical deck_sha256
No partial outputTrigger a validation failureOutput directory absent or empty
A common early failure is a template token whose name does not match any schema key suffix. The renderer above raises StopIteration in that case rather than emitting a literal {{token}} into the deck. Treat that exception as a template bug, not a parameter bug — the schema is the source of truth for names.

What Later Cases Extend

With these three artifacts in place, the extension points are explicit. Case 2 will add a parser that emits a parameter file conforming to this schema from raw stroke-versus-time data. Case 3 will add a curve fitter that populates the limiter.* keys from bench force-displacement data. Neither case needs to modify the template or the entry point — they only produce inputs that the baseline already validates and renders. That separation is the entire value of this first case.