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.
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.
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
| Check | Method | Pass condition |
|---|---|---|
| Schema completeness | Run with a params file missing one key | Exit 1, message names the missing key |
| Range enforcement | Set limiter.plateau_kN to 12.0 | Exit 1, value reported outside [1.0, 8.0] |
| Token coverage | Grep rendered deck for {{ | No matches remain |
| Determinism | Build twice, compare manifests | Identical deck_sha256 |
| No partial output | Trigger a validation failure | Output directory absent or empty |
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.