Authoring patterns.

Purpose

Manufacturers publish their products in wildly different shapes. One ships a separate cutsheet for every orderable SKU; another ships a single cutsheet that covers tens of thousands of SKUs through a configurator. A ULC record has to represent both ends of that range without duplicating data or losing it. This document describes the patterns for mapping real manufacturer documentation into ULC records.

It is not normative. It describes patterns, not requirements, and it informs the shape of ulc.schema.json and the reference validator rather than constraining them. The schema is the normative artifact.

What a ULC record is

A ULC record represents one attested photometric scenario for a fixture, not an orderable SKU and not an IES file. The reasoning behind that unit, and the two decouplings it buys, is in methodology.md; what matters for authoring is the two links that carry the weight. A structured applicability block inside the record connects it to the orderable SKUs it describes. Provenance metadata on each value connects that value to the measurement evidence behind it.

The four authoring patterns

Four distinct patterns emerged across four manufacturers during the schema evaluation phase. Each is grounded in a real cutsheet.

Pattern A: one SKU per cutsheet

Example: Erco Quintessence Downlight 30416.023. One cutsheet, one order code, one IES file, one LDT, one ULD, no configurator.

Each orderable SKU gets its own cutsheet PDF and its own accompanying files. Nothing expands combinatorially inside a part number. Accessories are separately numbered SKUs that the cutsheet cross-references.

ULC mapping: one record per SKU. The applicability block is narrow, covering the single orderable configuration. Accessories are listed in compatible_accessories[] with catalog numbers and references; none gets its own photometric record.

Records per cutsheet: 1.

Pattern B: one record per photometric scenario

Example: Selux AYA Pole. A single cutsheet covers roughly 54,000 theoretical SKUs across optics, mounting, output tier, CCT, finish, voltage, power-cord length, and option combinations. Page 10 declares 21 photometric scenarios: three distributions × three outputs for the white light engine, plus 12 BioRed scenarios. CCT variance is declared through a multiplier table (2200K=0.86, 2700K=0.93, 3000K=1.00, 3500K=1.00, 4000K=1.07, 5000K=1.07) applied to a single tested 3000K baseline per optic × output combination.

ULC mapping: one record per tested photometric scenario. The applicability block is wide: it declares which SKU axes the record covers and how derived values are computed for each. A single Selux record covers all six white CCTs through the multiplier table, all six voltage variants through the regulated-driver rationale, all finish options because finish does not affect photometry, and so on.

Records per cutsheet: 21 (Selux AYA), not 54,000.

Pattern C: one record per IES, with provenance classes

Example: Lumenpulse Lumenfacade Inground Color Changing (LOI). One cutsheet publishes 1,728 IES files covering 4 lengths × 4 color modes × 14 optics × 2 tilts × 2 optical-option states. The IES headers reveal that those 1,728 files derive from a much smaller base-test set (distinct test IDs are visible, at least five in a random sample of five files), combined through optical simulation (Synopsys LightTools) and mathematical scaling. Every file carries the header note This file was scaled by Lumenpulse to reflect a different fixture configuration.

The manufacturer chose to materialize the full extended-photometry matrix as discrete IES files, so specifiers pick by configuration and get an IES that matches their exact order code.

ULC mapping: one record per IES file, preserving the 1:1 mapping specifiers expect. Each record declares its photometric provenance class explicitly:

  • Values attested by a physical LM-79 carry provenance.method: extracted with value_type: measured.
  • Values generated by a simulation tool carry provenance.method: optical_simulation with value_type: rated and a reference to the calibrating attestation.
  • Values produced by scaling a base test carry provenance.method: extended_photometry (or the more general scaled) with value_type: rated and a reference to the base attestation’s test ID.

Consumers who need attestation fidelity (DLC applications, spec audits) filter to measured-only records; consumers who just need a photometric file for design work use whatever matches their configuration.

Records per cutsheet: 1,728, matching the IES count. Roughly 5 to 20 carry measured provenance; the rest carry simulation or extended-photometry provenance.

Pattern D: per-foot linear scaling

Example: Vode Nexa Suspended 807. Performance tables on pages 8 and 9 declare 48 photometric scenarios (2 optics × 3 outputs × 2 CRI tiers × 4 CCTs). Measurements are expressed per foot (lumens per foot, watts per foot, efficacy). Fixture lengths from 48 to 96 inches multiply the per-foot values linearly, and a single IES file at a reference length anchors the scaling.

The ordering configurator has 18 positions, most of them non-photometric (mounting type, canopy style, power-location cable length, sensor presence, finish, options). These multiply the SKU count without changing photometric output.

ULC mapping: one record per tested photometric scenario, the same principle as Pattern B. Photometric values are carried both as baseline-measured-at-length and as per-foot normalized. Length becomes an applicability axis with explicit linear-scaling rules. The non-photometric axes (mounting, canopy, finish, sensor, power location) live in applicability.covered_axes with a rationale that each is photometrically transparent.

Two Vode-specific attestation patterns also appear:

  • Option-conditional attestations: Chicago Plenum compliance requires the CPP order-code option and Remote Power, so it is an attestation with applicability.required_order_code_options: ["CPP"] plus a compatibility constraint.
  • Case-by-case attestations: BAA and BABA compliance depend on per-project manufacturer verification, so they are attestations with value_type: nominal and verification: requires_manufacturer_confirmation plus a contact_reference.

Records per cutsheet: 48 (Vode Nexa), with per-foot scaling covering every length variant inside each record.

Authoring exit-sign and emergency-class records

Product classes are an orthogonal dimension to the four manufacturer patterns above. A manufacturer pattern describes how a cutsheet’s SKUs map onto records; a product class describes which dataset the grader reads. The two compose: a product-class record is authored through whichever manufacturer pattern fits its cutsheet, then adds class-specific blocks. The class is selected by product_family.primary_category, and the two classes grade differently: an exit_sign swaps the architectural-photometry profile for the sign dataset (legend, illumination mode, battery, UL 924), while an emergency_luminaire keeps the normal profile minus luminaire efficacy and adds the emergency-battery gates. The worked examples are the three committed Cooper records, cooper-sure-lites-lpx7sd.ulc, cooper-sure-lites-es61src.ulc, and cooper-atlite-auxswhsd.ulc; all three are internally illuminated exit signs that also carry an emergency block. No committed record is a photoluminescent, self-luminous, or externally illuminated sign, nor a standalone emergency_luminaire; those modes and that class are gradable, but no committed example demonstrates them, so the field shapes below are drawn only from internally illuminated signs.

The exit_sign block. illumination_mode is the driver: it selects which sign rows the grader applies, so it is core-required for every sign (all three Cooper records carry internally_illuminated). legend_color is the other core sign field (red on all three). The universal standard-tier sign fields face_count and directional_indicator are populated on all three (single and ["none"]), and legend_height is the standard-tier letter-height figure (6 inches on the ES, 8 inches on the AUX). Mode-specific photometric fields gate by mode. At standard, sign-face luminance applies to photoluminescent and self-luminous signs, and face illuminance plus contrast ratio to externally illuminated signs, while an internally illuminated sign re-gates input_power_w instead (below). At full, the sign tier is anchored on a test-report-backed value: sign-face luminance with test_report provenance for every self-emitting mode (internally illuminated included), and face illuminance with test_report provenance for externally illuminated. illumination_technology is descriptive; the grader keys on illumination_mode.

The electrical re-gate. An internally illuminated sign re-gates electrical.input_power_w at standard, the running wattage a specifier compares. The ES publishes 3.8 W and the AUX 0.9 W; the LPX cutsheet publishes neither.

The emergency block. emergency_role and power_source are required inside the block. power_source decides the battery detail: an integral_battery sign gates the battery trio (battery_duration_min, battery_chemistry, self_test) at standard, and an ac_only sign carries none of it. The LPX and AUX are integral_battery and populate the trio (ni_cd, 90 minutes, self_diagnostic); the ES is ac_only and correctly omits it.

The UL 924 attestation. The emergency-lighting safety listing lives in product_family.shared_attestations as a ul_924 program token, never a boolean field, so listings have one home. Record its listing_number when the cutsheet prints one (the AUX carries E69466). Attaching the UL Certificate of Compliance as a compliance_documents source file and pointing the attestation’s source_document_ref at it is what raises the emergency achievement theme from claimed to documented: the AUX does this and is the corpus’s one documented case, while the LPX and ES list ul_924 without an attached certificate. UL 924 satisfies the core safety listing in every region, so a sign whose only listing is UL 924 still clears the core gate.

Which fields lift a sign from core to standard. legend_height (a sign field) and electrical.input_power_w (the internally-illuminated re-gate) are both standard-tier. The LPX illustrates the gap: it grades core, held below standard by exactly those two figures its cutsheet does not publish, one sign field and one electrical field. Frame the core-versus-standard gap as those two, not as letter geometry alone. For the full per-record grade catalogue, see examples/README.md; this section teaches the block shapes rather than what each record demonstrates.

Authoring achievement attestations

The Product Achievements axis is computed from the attestations a record already carries; authoring for it means recording the right attestation fields, not filling a separate block (the builder stamps index.achievements). Which program tokens feed which of the six themes is the published, versioned map in the achievement-themes appendix of compliance-attestation.md; author against that map rather than restating it here.

Evidence separates claimed from documented. A theme reaches documented only when a qualifying attestation carries a source_document_ref, a file reference the schema guarantees to have a filename and a SHA-256 hash. Attach the evidence document (certificate, EPD, or declaration) as a compliance_documents source file and point the attestation’s source_document_ref at it; without an attached document the theme caps at claimed. The corpus shows both ends: the AUX exit sign’s ul_924 attestation carries an attached UL Certificate of Compliance and its emergency theme is documented, while the Selux pole and Vode pendant hold material_health (and, on Selux, dark_sky) at claimed, their Declare and DarkSky qualifications present but backed by no attached evidence document.

Expiry is record-relative. When an attestation carries a valid_until date and the record carries record_status_as_of, an attestation whose validity ended before the record’s as-of date cannot reach documented, though it still supports claimed. The comparison is always against the record’s own record_status_as_of, never the build clock, so a rebuilt record produces identical achievements; when either date is absent, no expiry check runs.

Status disqualifiers. An attestation whose status is expired, withdrawn, or not_applicable contributes to no theme, as if absent. Every other status contributes normally.

Restricted-substances flag. RoHS, REACH, Prop 65, and similar restricted-substances programs populate the index.restricted_substances_declared sibling flag rather than a theme, because restricted-substances compliance is a legal floor, not a prestige achievement. The two Lumenpulse facade records and the Selux pole declare rohs there.

Quantitative sustainability payloads. An attestation can hang a sustainability_metric object off itself to carry the numbers behind a sustainability claim, so the figures inherit the attestation’s status, evidence, and expiry. Its fields: ceam_score (the TM66 CEAM design score, 0 to 4); embodied_carbon_kgco2e (a declared embodied-carbon figure in kg CO2e, recorded SI-native), which when present requires the companions embodied_carbon_scope and embodied_carbon_functional_unit so the number is unambiguous about its life-cycle boundary and unit basis; c2c_overall_level (a Cradle to Cradle overall rating); and method_variant (a regional or method sub-variant, such as a TM65 national annex). The optional issuing_authority attestation field names the specific operator when a program runs under more than one, as EPDs do. No committed example populates sustainability_metric, so the shape described here comes from the schema rather than a worked record.

The primitives that support all four

product_family

A top-level block carrying the data that is true for every SKU in the cutsheet. The PIM populates it once and replicates it into each record emitted from the family.

Typical contents: family_id, family_display_name, manufacturer, catalog_line, catalog_model, cutsheet (filename, URL, sha256, revision), primary_category, shared_mechanical, shared_warranty, and shared_attestations (claims that apply to every SKU without qualification, such as UL Listing and Declare Red List status).

Consumers group records by family_id to reconstruct cutsheet-level views without forcing cutsheet-level JSON bundles.

configuration

A top-level block identifying the specific photometric scenario the record represents. Its fields describe the tested configuration, not the applicability range.

Typical contents: photometric_scenario_id, catalog_number (when the scenario is one specific SKU), scenario_label, tested_axes (distribution code, light-engine variant, output tier, CRI tier), tested_conditions (tested CCT, tested voltage, tested mounting, ambient temperature), and source_ies_ref (a string reference to the matching entry in the top-level source_files[] array).

applicability

A top-level block declaring the range of orderable SKUs the record’s measurements apply to. This is what keeps one record from being rewritten for every SKU variant.

  • applicable_catalog_pattern: the order-code skeleton, with fixed and variable axes
  • fixed_axes: order-code segments that must match (for example Optics=SR, Output=HO)
  • covered_axes: segments where several values share this record’s photometry, each with a rationale and an optional derivation rule (CCT multiplier table, per-foot linear scaling, regulated-driver voltage independence, and so on)
  • excluded_combinations: combinations that are orderable for other records but not this one (for example “Max Output not available with 2200K, 2700K, 3500K, 5000K”)
  • applicable_sku_count_estimate: informational

Provenance classes

Every photometric value declares a provenance method from a closed set. The values that matter for cutsheet authoring:

  • extracted: pulled directly from a source file
  • validated: checked against a second source
  • optical_simulation: generated by an optical design tool calibrated against a base LM-79. Carries provenance.simulation_tool and provenance.base_attestation_ref, and pairs with value_type: rated.
  • extended_photometry: derived from a base LM-79 attestation by manufacturer-applied scaling rules. Carries provenance.base_attestation_ref and provenance.extension_method, and pairs with value_type: rated.
  • scaled: general closed-form mathematical scaling (CCT multiplier, per-foot scaling, wattage-tier scaling). Pairs with value_type: rated.

Consumers filter records by these classes to prioritize direct attestations when they need to.

Conditional attestations

Two patterns from the Vode cutsheet do not fit the absolute-attestation shape.

An option-conditional attestation is true when a specific order-code option is selected and false otherwise:

{
  "program": "chicago_plenum",
  "value_type": "rated",
  "applicability": {
    "required_order_code_options": ["CPP"],
    "required_constraints": { "Power Location": "remote_power" }
  }
}

A case-by-case attestation is one the manufacturer supports but requires per-project verification before a consumer may represent the SKU as compliant:

{
  "program": "baa",
  "value_type": "nominal",
  "verification": {
    "type": "requires_manufacturer_confirmation",
    "contact_reference": "[email protected]"
  }
}

A validator must not propagate a case-by-case attestation downstream as if it were a measured or rated claim. The verification.type field is the signal.

Sustainability declaration

A Declare label (International Living Future Institute) carries structured data well beyond a “has a Declare label” boolean. ULC models it as a dedicated sustainability_declaration block that preserves the ingredient list, expiration date, LBC Red List tier, and document identifier.

Typical contents: declaration_type (for example ilfi_declare), document_id, expiration_date, original_issue_date, final_assembly_location, life_expectancy_years, end_of_life_options, recyclable_percent, ingredient_list[] (each with material_name and lbc_red_list_status), lbc_criteria_compliance (a boolean for overall Living Building Challenge compliance), voc_content, interior_performance, and responsible_sourcing. When the declaration is specifically a Red List statement rather than a full Declare label, the tier claim lives on the top-level declaration_type field (one of red_list_free, red_list_approved, red_list_declared).

Generated index

Every record carries a top-level index block: a flat, denormalized summary of the most commonly queried values (manufacturer, catalog, primary category, nominal CCT, total lumens, input power, BUG, attestation programs, the computed Product Achievements axis (achievements) and its restricted-substances flag (restricted_substances_declared), and search keywords). It exists to make AI scanning, search indexing, and filter UIs cheap, so a consumer can read the index without walking the deep blocks beneath it.

The index is generated, never hand-authored. The spec forbids manufacturers from typing index values. The canonical builder is the ulc build-index subcommand of the Go reference CLI at tools/validator/. It reads the deep blocks (product_family, configuration, electrical, photometry, colorimetry, outdoor_classification, attestations, sustainability_declaration) and writes the index deterministically. A manufacturer’s PIM or authoring tool runs the builder as the final step before emitting a record.

Two markers make the index self-describing:

  • x-ulc-generated: true: the builder produced the block
  • builder_version: the semver of the builder that produced it

A consumer that reads x-ulc-generated: true can treat the index as trustworthy. A missing marker or a stale builder version is the signal to rebuild.

Drift is prevented by construction, because the index is a pure function of the deep blocks. The builder holds the selection policies in one place: which CCT counts as nominal, how SI-authoritative scalars are extracted from dual-unit fields, which variant of a multi-CCT record supplies the baseline value. SI is always authoritative, and the dual-unit policy is fixed, not per-record. CI runs ulc build-index --check against every committed record as a second line of defense, and an optional local pre-commit hook with the same check ships at tools/hooks/pre-commit (see CONTRIBUTING.md for installation).

This mirrors precedent across the industry: DLC QPL, ETIM MC catalogs, and GLDF-authoring tools typically generate their scan surfaces from tooling rather than having manufacturers hand-author them.

Measured baseline with declared-by-axis scaling

Measurements that a manufacturer scales by a declared rule (Selux CCT multipliers, Vode per-foot rates) are modeled by pairing the measured baseline with a sibling array of declared values across the covered axis.

  • The baseline field (for example photometry.total_luminous_flux_lm) is a single ProvenancedNumber holding the tested value, with value_type: measured and provenance.attestation_ref pointing at the LM-79 that produced it.
  • A sibling array (declared_by_cct[], or declared_by_length[] for the length axis) carries one rated entry per covered value, each with its derivation method and value_type: rated.

A Selux record, for example:

"photometry": {
  "total_luminous_flux_lm": {
    "value": 5074,
    "unit": "lm",
    "value_type": "measured",
    "provenance": {
      "source": "ies",
      "method": "extracted",
      "attestation_ref": "lm79_selux_aya_sr_ho"
    }
  },
  "declared_by_cct": [
    { "cct": 2200, "lumens": { "value": 4364, "unit": "lm", "value_type": "rated" } },
    { "cct": 3000, "lumens": { "value": 5074, "unit": "lm", "value_type": "measured" } },
    { "cct": 4000, "lumens": { "value": 5429, "unit": "lm", "value_type": "rated" } }
  ]
}

Choosing a pattern

Manufacturers do not choose a pattern in the abstract. They choose an authoring surface, and the pattern follows from their own data model.

  • Tests each SKU individually and publishes per-SKU cutsheets: Pattern A.
  • Publishes one cutsheet covering a configurator, with declared scaling tables inside the cutsheet: Pattern B.
  • Publishes a dense pre-computed IES bundle, each file mapping to a configurator output: Pattern C.
  • Normalizes per-unit-length measurements for a linear product family: Pattern D.

A single manufacturer may use different patterns for different product families. A single product family commits to one pattern, to avoid ambiguity.

How records reference each other

  • Family grouping: every record in a cutsheet shares a product_family.family_id. Consumers group on this key to reconstruct cutsheet-level views.
  • Attestation inheritance: derived records (Pattern C simulations and extended-photometry records) carry provenance.base_attestation_ref pointing at the measured base record’s test ID, so a consumer can trace any rated value back to the measurement it rests on.
  • Accessory references: mechanical accessories listed in compatible_accessories[] are identified by their own catalog numbers. An accessory needs its own photometric record only when it changes fixture photometry.

Validation implications

The reference validator uses the patterns and primitives above as follows:

  • Conformance level and achievements are computed, not declared. ulc validate runs JSON Schema validation, index-builder parity, and source-file hash verification, then reports the completeness grade the builder computed (stamped as index.conformance_level) with its two roadmaps, the tier roadmap to the next grade and the non-gating enrichment roadmap, alongside the Product Achievements axis (index.achievements). A hand-edited index value fails the build-parity check like any other field. The per-grade requirement tables and the two-axis model are in the conformance rubric; a conformance level is a data-completeness grade, never a safety certification, and it is never a pass/fail gate.