Skip to main content

EPD Templating

EPD Templates define the verified modeling envelope for a given tool within a project. They validate performance at defined boundary conditions and confirm that the tool enforces approved limits without exceeding the verified scope.

Key principle

Smart EPD provides the framework and logic for defining, enforcing, and versioning template constraints. The specific setpoints, ranges, and rules within a template are determined by tool providers, manufacturers, consultants, and verifiers during the verification process. Smart EPD facilitates; it does not prescribe.

How templates relate to specifications​

A template is not authored directly. It is the resolved output of the specification inheritance chain. Each level can define or override constraints, and the template is what results when the full chain is evaluated:

The specification chain defines the rules. The verifier reviews, adjusts, and approves the constraints. The result is a locked, versioned EPD Template that governs every EPD created within that project.

Within an EPD Template, each field has a defined state:

  • Locked fields -Prefilled values that cannot be changed (e.g., declared unit, methodology references)
  • Unlocked fields -Editable by the project team, but subject to validation constraints
  • Constrained fields -Unlocked, but bounded by range constraints

Templates and Reference EPDs​

A template defines what is allowed — locked values and input bounds — but it is not itself a declaration, and it cannot be verified in the abstract: there is nothing concrete to check until real values exist. Supplying those values is the job of a Reference EPD — the fully populated instance that fills in every field and anchors a product family.

The template and its Reference EPD are therefore verified together, in a single pass. Approving the Reference EPD simultaneously confirms the template's locked fields, its input bounds, and that a complete, conformant EPD comes out the other side. Once approved, the template governs every variant derived from that reference.

In short: the template is the rulebook and the form; the Reference EPD is that form filled with representative numbers to prove the rulebook works; variant EPDs are validated against the same template constraints when they are generated.


Template characteristics​

Each EPD Template represents a bounded modeling framework. The dimensions along which a template defines its scope are flexible and project-specific, but common examples include:

Manufacturing plants or plant archetypes​

The template may scope which manufacturing facilities are covered. A product manufactured at Plant A under different energy mixes than Plant B may require separate templates or plant-level parameterization.

Manufacturing process archetypes​

The template may define which production processes are in scope, such as extrusion, injection molding, or assembly. Different processes may produce different environmental profiles even for the same product family.

Material families​

The template may bound which material families are permitted, such as PE, PVC, or PP for a plastics manufacturer. Materials outside the defined families fall outside the verified scope.

Parameter ranges (input constraints)​

The most nuanced dimension. Parameter ranges define the allowable variability for inputs that drive the LCA model: dimensions, material composition, energy use, waste ratios. These ensure variant EPDs stay within the verified modeling envelope.

tip

The dimensions listed above are illustrative examples. EPD Templating is flexible: a template can define scope along any dimension relevant to the product category and verification context. The platform does not prescribe a fixed set of bounding dimensions.


Input range constraints​

Smart EPD supports four constraint types for validating input values: range, statistical, formula, and allowed_values. These are not mutually exclusive: a single template can combine several to create a comprehensive validation framework. The verifier determines which types to apply and what specific values to set.

Any constraint can also be conditional via the applies_if_json field — the constraint is only evaluated when the specified condition is true.

Range constraints​

Numeric min/max bounds on an input value with configurable inclusivity (inclusive, exclusive, left-open, right-open). Catches data entry errors and enforces verified scope boundaries. Most templates will have range constraints on the majority of their inputs.

Example: Total product mass must be between 0.5 and 25 kg. PE content must fall within 0.30–0.78 kg PE / kg product.

Range constraints handle absolute bounds, ratio-based bounds, and any simple min/max pattern. The parameters_json keys are min, max (either may be omitted for an open-ended bound), and bounds_mode — one of inclusive (default), exclusive, left_open, or right_open.

{ "min": 0.5, "max": 25, "bounds_mode": "inclusive" }

Statistical constraints​

Distribution-based bounds using z-score checks. Values outside a defined statistical threshold from the population mean are flagged as outliers. Supports both normal and lognormal distributions.

Example: GWP value more than 2 standard deviations from the category mean is flagged.

The parameters_json keys are distribution ("normal", the default, or "lognormal") and threshold — the maximum allowed z-score (default 3.0). A normal distribution takes mean and std_dev; a lognormal distribution takes geometric_mean (> 0) and geometric_std_dev (> 1) instead.

{ "distribution": "normal", "mean": 50, "std_dev": 10, "threshold": 2 }

Most valuable for mature product categories with many existing declarations.

Formula constraints​

Constraints defined by an arbitrary mathematical expression evaluated against EPD data. Where range constraints handle simple bounds, formula constraints handle relationships across multiple fields — non-linear interactions, cross-field comparisons, and domain-specific expressions.

The expression computes a number; the separate operator and bound parameters then compare that number against a threshold. The parameters_json keys are:

KeyRequiredMeaning
expressionyesMath expression evaluated against the input data. Must produce a number.
operatorno (default lte)One of eq, lt, lte, gt, gte, between. An unrecognized operator falls back to lte.
thresholdwith the single-sided operators (default 0)The bound the computed value is compared against.
min, maxwith betweenInclusive bounds: the constraint passes when min <= computed <= max. Either may be omitted for an open end.
{ "expression": "gwp_a1_a3 / total_mass", "operator": "lte", "threshold": 12.5 }
{ "expression": "pe_content + pp_content", "operator": "between", "min": 0.30, "max": 0.78 }

Cross-field comparisons are written by moving everything to one side of the expression and comparing against 0:

{ "expression": "gwp_a1_a3 - base_gwp * (1 + 0.15 * recycled_fraction)", "operator": "lte", "threshold": 0 }
Common mistakes
  • The bound key is threshold, not value. Unknown keys in parameters_json are silently ignored and threshold defaults to 0 — so {"operator": "lte", "value": 1000} actually enforces <= 0.
  • Do not write the comparison inside the expression ("a + b <= 1000" with operator: "eq"). The engine evaluates the comparison to a boolean, coerces it to 1.0/0.0, and compares that against the threshold — inverting your intent. Rearrange onto one side instead, as above.
  • eq compares floats with a small tolerance (1e-9), not exact equality.

Expression grammar​

Expressions are evaluated with a sandboxed engine (simpleeval) — no builtins, imports, or attribute access.

  • Variables: every numeric input field is available by its field_id (numeric strings are coerced). Non-numeric fields are excluded — referencing one makes the constraint not evaluable rather than failing it. The special name _value holds the current value of the field the constraint is attached to. External inputs are referenced with their ext__ prefix (declared per template — see Reading the template).
  • Identifiers: a field can only be referenced if its ID is a valid identifier — letters, digits, and underscores, not starting with a digit (gwp_total works; gwp-total cannot be referenced).
  • Functions: abs, min, and max — min/max accept any number of arguments (min(a, b, c)). Nothing else works: no round, no pow (use **), and no sum — the sandbox rejects the list syntax sum needs, so use a + b + c instead.
  • Arithmetic: +, -, *, /, //, %, ** (exponent magnitude is capped for safety), unary +/-, and parentheses.
  • Result: must be a number. Comparison and conditional syntax technically parse, but their boolean results coerce to 1.0/0.0 — keep comparisons in the operator field.

A division by zero or a reference to an absent field marks the constraint not evaluable (still reported as a violation); an unparseable expression marks it misconfigured. Neither silently passes.

Naming note

The term "parametric" is avoided here intentionally. In the EPD and LCA context it carries a specific meaning related to parameterized and variant EPDs. Formula constraint is used to prevent ambiguity in documentation and audit trails.

Allowed-values constraints​

Checks a text (or multiselect) value against a fixed list of permitted strings — built for fields like LCI background datasets where the PCR publishes the exact list manufacturers may choose from.

The parameters_json keys are values (required, the list of allowed strings) and case_sensitive (optional, default false — comparison trims whitespace and ignores case). List inputs pass only when every item is allowed. Blank input passes: this constraint only polices provided values; required-ness is enforced separately by the field's is_required.

{ "values": ["Ecoinvent 3.9", "Ecoinvent 3.10", "GaBi 2023"], "case_sensitive": false }

Constraint model structure​

Every constraint in the system follows the same data model:

FieldTypeDescription
constraint_typestringOne of: range, statistical, formula, allowed_values
parameters_jsonobjectConstraint-specific parameters — see each type above for its exact keys (min/max/bounds_mode for range; distribution/threshold/mean/std_dev for statistical; expression/operator/threshold or min/max for formula; values/case_sensitive for allowed_values). Unknown keys are silently ignored.
severitystringerror (blocks variant approve/auto-approve, and a reference EPD's submission when the template enforces its constraints unless the author acknowledges it), warning (surfaced to verifier), or info (informational). Default error.
applies_if_jsonobject or nullOptional conditional logic — when present, the constraint is only evaluated if the condition is true
reference_field_idstringTemplate-level constraints only: the anchor field — see below
labelstringTemplate-level constraints only: display name shown in the editor and review surfaces

The anchor field (reference_field_id)​

Spec-level constraints are defined on an input field, so they anchor to it implicitly. Template-level constraints (entries of template_constraints) declare their anchor explicitly via reference_field_id. The anchor determines three things:

  1. Attribution — violations report the anchor as their field_id, including for formula constraints that reference many fields in the expression.
  2. The required value — when the constraint applies, the anchor field must have a value; a missing (or, for the numeric types, blank) anchor value is itself reported as a violation before the constraint logic runs. An unmet applies_if_json gate suppresses this too — a gated-off constraint reports nothing. (allowed_values is the exception for blanks: a provided but blank anchor passes, per its blank semantics above.)
  3. _value — in formula expressions, _value is the anchor field's (numeric) value.
reference_field_id is required

Saving a template-level constraint with an empty reference_field_id is rejected with a 400 validation_error. (Constraints stored before this validation existed are silently skipped during evaluation — they still appear in verifier checklists but never produce violations — so if you have a legacy unanchored constraint, re-save it with a real field ID.)

Conditional application​

Any constraint type can be made conditional via applies_if_json. This handles product configurations where certain validation rules are relevant only for specific variants:

Example: If the product contains recycled content, the recycled content fraction range constraint only applies.

Shape​

applies_if_json is a JSON object using a small grammar. A leaf condition names an input field, an operator, and an expected value:

{
"field": "recycled_content",
"operator": "gt",
"value": 0
}

Supported operators:

OperatorMeaningNotes
eq (default)EqualString comparison
neqNot equalString comparison
inValue is in a listvalue must be an array
gt, gte, lt, lteNumeric comparisonsBoth sides coerced to float
presentField has a non-null valueNo value needed
absentField is missing or nullNo value needed

Leaf conditions can be combined with all (logical AND) or any (logical OR), and the combinators nest recursively:

{
"all": [
{ "field": "category", "operator": "eq", "value": "concrete" },
{ "field": "strength_mpa", "operator": "gte", "value": 20 },
{
"any": [
{ "field": "admixture", "operator": "present" },
{ "field": "recycled_content", "operator": "gt", "value": 0 }
]
}
]
}

Semantics​

  • null or an empty object → the constraint always applies.
  • Field references resolve against the same input data that drives constraint evaluation, so applies_if_json can gate a constraint on the value of any sibling field in the template.
  • A numeric comparison (gt/gte/lt/lte) whose sides can't coerce to float falls through to "apply the constraint" — those operators fail open.
  • The other operators evaluate against the missing value when the referenced field is absent: eq, in, and present then read as unmet (the constraint is skipped), while neq and absent read as met. Gate on fields you know will be present, or combine with a present condition.
  • Send only the documented shape. Combinator values (all/any) must be arrays of condition objects — malformed shapes are outside the contract and can fail the validation call rather than being ignored.
Not the only applies_if_json in the system

Constraint-level applies_if_json is distinct from the requirement-level applies_if_json used for document search, and from show_if_json used for form-field visibility. Each has its own evaluator and its own shape — don't copy snippets between them.

Constraint type summary​

Constraint TypeComplexityWhen to Use
RangeLowFirst layer: catches data entry errors, enforces ratio bounds
StatisticalMediumOutlier detection against population data (z-score)
FormulaMedium–HighMulti-variable or non-linear relationships not expressible as a simple range
Allowed valuesLowText fields limited to a published list (e.g. permitted LCI datasets)

Sensitivity analysis​

Before defining input range constraints, it is strongly recommended that the tool provider and verifier collaborate on a sensitivity analysis to identify which inputs have the greatest impact on EPD results.

A common approach is to identify inputs responsible for the top 80% of GWP variance. High-impact inputs should receive the tightest constraints, while lower-impact inputs can receive wider bounds.

This is a recommended best practice, not enforced by the platform. Sensitivity-driven constraint design leads to templates that are rigorous where it matters and flexible where it doesn't.


Out-of-bounds handling​

When a manufacturer attempts to generate a variant EPD with input values that fall outside the template's verified bounds, the platform enforces a structured response:

When an out-of-bounds input is detected:

  • All violations are identified at once (not just the first one found)
  • Each violation shows which input is out of range, what the valid range is, and by how much
  • The "Generate EPD" action is disabled until all inputs are within bounds
  • A "Request Template Update" action is available to initiate a template revision