Authoring · 08 of 17
Walkthrough: R 60
In this chapter: the methodology of Chapters 1–7 applied once, in full, to one Recommendation. OIML R 60 (load cells) is the worked example of this volume: every file cited below exists in
data/r60/, and every decision is one the author of the next Recommendation will face in the same order.
8.1 From three documents to one running package
R 60 arrives as three source documents, R 60-1 (requirements),
R 60-2 (test procedures), R 60-3 (test report format), 2021 editions ,
and leaves as a package: 107 YAML files under data/r60/, 44
attributes, 60 requirements, 62 conformance tests, 38 symbols,
17 calculations, 13 seeded evaluation flows, zero validation errors.
The mapping from documents to layers is the methodology’s own:
| Source | Lands in | As |
|---|---|---|
| R 60-1 clause 3 (terms, taxonomy) | model/ |
subject type, variants, dimensions, attributes |
| R 60-1 clauses 5–6 (requirements) | specification/requirements/ |
bound OCL limits |
| R 60-2 (procedures) | specification/conformance/ |
tests with derived variables |
| R 60-3 (forms) | execution/forms/ |
bound evidence views |
| R 60-2 §2.4 + Annex D (selection) | evaluation/sample-selection-rules.yaml |
the merit-walk as data |
| R 60-4 / PD-05 (evaluation) | evaluation/ + entities/workflow.yaml |
the judgment config of Chapter 6 |
The rest of this chapter walks the five decisions that make it a model rather than a transcription, in authoring order.
8.2 The subject: LoadCell, four variants, five axes
The subject declaration (data/r60/model/instrument.yaml ,
subject_type) is a subclass of the metamodel’s
MeasuringInstrumentModel with its normative definition and clause
provenance (R 60-1, 3.1.3). The signal-processing taxonomy of 3.1.3 is
four variants, analogue_passive, analogue_active, digital,
digital_with_processing (3.1.3.1–3.1.3.4), each with definition and
source clause. Variants change what the subject is; they are IS-level
structure, not classification.
Classification is five orthogonal dimension axes
(classification_dimensions), each scoped to the chain level where it
is set: technology, humidity_class and construction at family
scope; accuracy_class and load_type at group scope. The
accuracy_class axis shows why dimensions are data, not just enums:
each value carries the n_lc_limits payload of R 60-1 Table 1 ,
- id: C
n_lc_limits: { lower: 500, upper: 10000 }
, so “class C” is not a string but a machine-checkable claim: n_lc must
fall in [500, 10000], and the linker can test every group that declares
the class. Every axis is mirrored by an is_dimension: true attribute
in attributes.yaml, so requirement applicability blocks and
capability filters reference one uniform vocabulary.
8.3 The family matrix: criteria, groups, and the C6 label
R 60-1, 3.4.2 defines the load cell family by six criteria, transcribed
verbatim into family_criteria, same material, same measurement
technique, same strain-gauge attachment principle, same construction
method, same set of specifications, one or more groups of identical
metrological characteristics. The criterion is a boundary rule: a
candidate model violating any criterion is a different family, a
separate application, not another group.
Inside the family, model_groups declares what a group is: Models
having identical metrological characteristics ,
identical_characteristics: [ metrological_class, n_lc, "y", z, temperature_rating ]
identical_attributes: [ accuracy_class, n_lc, "y", z, t_min, t_max ]
, and each group carries a compact group_label: 'C6' is class C
with n_lc 6000. Within a group, Models differ only by E_max (R 60-2,
Annex D), which is what makes the family a matrix: capacities ×
metrological characteristics, one cell per (group × capacity). The label
is not decorative, it is the same string the certificate template’s
dimension_labels pattern composes ({accuracy_class}{n_lc_thousands}),
so the taxonomy and the certificate can never disagree about what was
approved. Sample selection runs per group, and R 60-2 §2.4 + Annex D’s
merit walk (smallest E_max per group, 5–10× steps) is data in
evaluation/sample-selection-rules.yaml, e.g. rule
D.2.2-smallest-per-group with selector
MIN(e_max) GROUP BY group_id.
8.4 The attribute register: three decisions
The 44-entry register (data/r60/model/attributes.yaml) is the INV-2
schema layer: each attribute defined once, snake_case id, print
symbol, definition, clause source, quantity kind, unit, origin,
scope, category, then valued per chain level. Three entries
teach the decision table:
e_max, design-fixed, model scope. The maximum capacity
distinguishes one catalogue model from another; it is set by design and
stated per Model:
- id: e_max
symbol: E_max
source: { doc: urn:oiml:pub:r:60-1:2021, clause: "3.5.5" }
origin: design-fixed
scope: model
d_min, test-dependent, sample scope. The smallest load actually
applied during test is chosen under test, differs per physical unit,
and is never inherited: it lives on the sample’s test_context:
- id: d_min
symbol: D_min
origin: test-dependent
scope: sample
dr, the scope/origin puzzle. Minimum dead load output return is
origin: test-dependent (its value is verified per sample by the DR
test) but scope: group (it is stated as a declared group
characteristic), with a derived fallback
(self.e_max - self.e_min) / (2 * self.z) and a note making the split
explicit: “Declared group characteristic (verified per sample by the DR
test); the measured value is a D2 MeasurementResult.” Origin says where
the value comes from; scope says where it is stated. The two align
often enough to lull you, dr is the register’s reminder to decide
them independently.
8.5 Requirements walk: definitional and lookup
A definitional requirement (/req/metrological/measuring-range-max,
R 60-1, 5.2) shows the full anatomy in six lines of substance:
binds_to:
- sample.test_context.d_max
- model.parameters.e_max
limit:
expression: "ocl{sample.test_context.d_max <= model.parameters.e_max}"
uses:
- sample.test_context.d_max
- model.parameters.e_max
verification:
method: definitional
Statement + clause URN carry the normative text; binds_to anchors the
two aspect paths (a HAS test-context value against an IS design
parameter, the duality made executable); limit is the machine-check;
verification.method: definitional says honestly that the type
definition itself is checked, no lab needed. The legacy
acceptance_criteria block stays for provenance, but binds/limit is
canonical, an unbound requirement judges nothing.
A lookup requirement (/req/metrological/creep, R 60-1, 5.5.1)
shows the table discipline: the limit is computed from data, never
restated ,
ocl{abs(c_c) <= 0.7 * abs(lookupMPE(sample.test_context.d_max,
group.classification.accuracy_class, 0.7))
and sample.test_context.d_max >= 0.9 * model.parameters.e_max
and sample.test_context.d_max <= model.parameters.e_max}
Three facts are modelled, not hidden in prose: the observable c_c
(creep, a characteristic derived from behavior I/O) is judged against
0.7 × MPE; the MPE itself comes from mpe_tiers in tables.yaml via
lookupMPE (Recommendation tables are data, never bake tiers into
OCL); and the creep clause’s own rule that p_LC is always 0.7 here is
the literal 0.7 argument, not the manufacturer’s declared p_LC. The
conjoined 0.9 * e_max <= d_max <= e_max carries the test-geometry
validity condition in the same statement.
8.6 Tests walk: variables, instances, inheritance
The central metrological test,
/conf/metrological-tests/measurement-error-repeatability-mdlo
(R 60-2, 2.10.1), targets three requirements and shows the variable
discipline. Every variable has a source: measured run inputs,
derived OCL over inputs, computed table lookups ,
- name: e_r
source: derived
derivation: "ocl{(max(runs->collect(r | r.indication)) - min(runs->collect(r | r.indication))) / conversion_factor_f}"
- name: mpe
source: computed
derivation: "ocl{lookupMPE(test_load, accuracy_class, p_lc)}"
, all derived from the requirement limits and subject parameters, never restated by hand (INV-3 for tests).
Two author-facing mechanisms deserve attention:
-
instances:, runtime class-driven instantiation. The run count is not a constant: A/B run 5 applications, C/D run 3 (R 60-2, 2.10.1.12). The test declares it once ,instances: by: accuracy_class values: A: { n_runs: 5 } B: { n_runs: 5 } C: { n_runs: 3 } D: { n_runs: 3 }
, and the applicability engine expands it per subject at runtime.
The comment in the file records the bug this replaced: a hardcoded
n_runs: 3 had silently materialized class A/B forms with 3 runs.
- Class inheritance. Per-class aggregations in
conformance/class-specific.yamlinherit the generic procedure and restate only deltas:inherits_from: /conf/metrological-tests/measurement-error-repeatability-mdlounder scopeapplicability: { accuracy_class: [A] }, with class-scoped targets (/req/class-a/mpe), its run count and its 2 °C MDLO increment. Nothing about the procedure is copied.
Run validity is a precondition, not a verdict: the test’s
preconditions: carry the temperature-stability rule of R 60-2,
2.7.3.1, evaluated before the limit, a violated precondition yields an
invalid run (void), never a fail.
8.7 Forms walk: binds and the binding-key drift
Forms are views onto the entity graph. The shared header
r60-3/header-a (execution/forms/shared/headers.yaml) shows all
three bind families in one place:
- name: application_no
bind: application.application_number # read-only identity
- name: emax
bind: model.parameters.e_max # IS design parameter
- name: dmax
bind: sample.test_context.d_max # HAS test-context value
Bound fields prefill from the resolved chain and write through on
submit; unbound fields (force_generating_system, the conditions
block) are the run’s raw evidence. One header, and the subject chain’s
whole delegation is exercised.
The creep/DR form (execution/forms/sec-6-performance-tests/creep-dr.yaml,
r60-3/table-6.8) shows a form wired end-to-end: header: r60-3/header-a; conformance_test: [/conf/metrological-tests/creep, /conf/metrological-tests/dr]; requirements: [/req/metrological/creep, …]; calculation_context: { dimensions: true, tables: [mpe_tiers] };
per-field measurement_method (declared, direct, computed +
calculation/calculation_bindings, derived OCL, evaluated with
evaluation.rule); and a closing pass_fail.pass_if over the three
evaluated results.
It also carries the register’s most instructive defect. The
conversionFactor calculation declares an input named n, the
verification intervals of the tested range: (E_max − E_min)/v_min
when the test spans the classified range (R 60-3, 2.1.2.4), equal to Y
in that case. The form’s sibling field n_lc is a different quantity:
the manufacturer’s declared maximum intervals
(group.parameters.n_lc). The methodology’s pitfall register (§9.4,
item 8) records the drift, the form bound n_lc where the calc
declares n, and the repaired binding is what ships:
calculation: conversionFactor
calculation_bindings:
avgIndicationAt75pct: reference_indication_75pct
indicationAtDmin: indication_at_dmin
"n": n_test_intervals # calc input n ← dedicated derived field
Two lessons in one line: binding keys must equal the calc’s declared
input names, and easy-to-confuse integer quantities (n_lc vs n vs
y) are different semantics, the linker rule
(“calculation_bindings key matches a declared calc input”) exists
because this exact drift shipped green once.
8.8 Evaluation walk: thirteen flows to a certificate
data/r60/sample-data.yaml seeds thirteen complete flows compiled from
fifteen real certificate PDFs (the public OIML-CS register, issuer and
manufacturer names replaced by the fictional set): one manufacturer →
one family (technology: digital,
humidity_symbol: CH, construction: strain-gauge, parameters p_LC
0.7, t_min −10 °C, t_max +40 °C…) → groups by classification label
(grp-acme-lc-d1 with group_label: "D1", n_lc 6000, Y 20000,
Z 6000, DR 0.03 kg) → models → 1–2 samples with test_context →
application → test request → test report with form instances →
evaluation → ACTIVE certificate. The full Chapter 6 synthesis runs over
this evidence: TestReportDeterminations at the admissibility gate,
per-requirement verdict re-execution rendered as the verdict matrix
(VerdictMatrix.vue), ModelEvaluations per model, the overall decision,
and a certificate whose classification labels (C6) are the groups’
own labels.
The seed is the package’s proof of life: it exercises every layer in order, delegation through the chain, applicability over the five axes, calculations over bound evidence, the verdict chain, and it fails the build when any layer drifts.
8.9 The twin angle: this package, switched on (○)
Everything above built the package for the laboratory. Volume I,
chapter 14 (Primmel SMART docs), §14.9 shows the same package
switched on: ACME ships LC-500 units with the endpoint lc500_api
declared on the subject (chapter 2, §2.11 of this volume), a quarry’s
belt scale integrates one, and the IA’s Compliance Engine subscribes to
watch_state and runs an hourly monitor over the served values.
Nothing in §8.2–§8.8 changes to make that possible, that is the
point. The monitor evaluates the §8.5 requirements’ own
lookupMPE(...) against a live indication; a unit reporting fault
yields invalid through the same precondition semantics as §8.6; the
quarterly creep re-derivation is §8.6’s characteristic computed over a
streamed series instead of a lab run; and the freshness rule (stale ⇒
indeterminate, never a silent pass) guards every served value. The
package authored once judges the Tuesday in the lab, and every day
after it. (The endpoint declaration ships in this package ,
model/twin.prl, task 49, and the monitor + gateway runtimes are
live, tasks 33/34; the model they
consume is the one this chapter just walked.)
8.10 Grammar sketch (illustrative v3 syntax)
The whole walkthrough as one closure slice, each secondary element anchoring the primary tier, each judgment referencing one derivation:
subject LoadCell {
is { design_parameters { e_max : mass, origin design-fixed, scope model } }
has {
dimensions { accuracy_class : group ∈ {A,B,C,D}
values { C { n_lc_limits { lower 500, upper 10000 } } } }
attributes { d_min : mass, origin test-dependent, scope sample }
characteristics { c_c : creep = Δindication over 30 min at constant load }
}
does { behavior creep { in force, time -> out indication } }
}
requirement /req/metrological/creep {
binds_to [observable c_c, sample.test_context.d_max,
group.classification.accuracy_class, model.parameters.e_max]
limit ocl{ abs(c_c) <= 0.7 * abs(lookupMPE(sample.test_context.d_max,
group.classification.accuracy_class, 0.7)) }
source "urn:oiml:pub:r:60-1:2021#clause-5.5.1"
}
conformance /conf/metrological-tests/creep {
targets [/req/metrological/creep]
instances by accuracy_class { A:{n_runs 5} B:{n_runs 5} C:{n_runs 3} D:{n_runs 3} }
preconditions { temperature_stability :
ocl{ temperature_variation <= min(2, 0.2 * (t_max - t_min)) } } # invalid, not fail
}
form r60-3/table-6.8 {
header r60-3/header-a
fields { emax bind model.parameters.e_max
dmax bind sample.test_context.d_max
f computed conversionFactor { n <- n_test_intervals } } # key = calc input
pass_if ocl{ creep_30min_result and creep_20_30_result
and dr_half_v_result }
}
8.11 Validation rules
The walkthrough exercises, concretely, the checks the author of the next Recommendation should run first:
- every
binds_to/bind:path resolves to a declared attribute at a scope-appropriate level (model.parameters.e_maxyes;sample.test_context.e_maxno); - every dimension enum value used in
applicability/instances:exists on the axis, and payload constraints hold (a group declaring class C with n_lc 12000 violatesn_lc_limits); - every
lookupMPEtable exists intables.yaml; everycalculation_bindingskey equals a declared calc input; - every test
targetsresolves; every class-specific aggregation’sinherits_fromresolves and its scope narrows the parent’s; - every clause reference resolves against the editions declared in
standard.yaml(the 2017 → 2021 renumbering is recorded per file header: barometric-pressure2.10.3 → 2.10.4, humidity2.10.4 → 2.10.5/6); - the seed compiles: every flow’s chain (family → … → certificate)
has all FKs resolving and
standard_id: oiml-r60throughout.
8.12 Summary
- R 60 is the methodology executed once: three source documents → one package of 107 files, with each clause landing in its tier’s layer.
- The subject is four IS variants over five HAS classification axes;
dimension values carry machine-checkable payloads (
n_lc_limits). - The family matrix is criteria (verbatim) + groups (identical
characteristics) + labels (
C6) that the certificate reuses , taxonomy and certificate cannot drift apart. - The attribute register’s hard cases are the lessons:
e_max(design/model) vsd_min(test/sample) vsdr(origin ≠ scope). - Requirements bind and look up; tests derive variables and instantiate per class; forms bind into the chain and their binding keys are checked names, not suggestions; thirteen seeded flows carry the whole synthesis to a certificate.
- Switched on, nothing is re-modelled: Volume I, chapter 14, §14.9 runs
this package, the
lc500_apiendpoint, the hourly monitor, the samelookupMPE, as its live-twin worked example (○).
Next: Chapter 9, Walkthrough: R 91 and R 144: two Recommendations of different kinds, and what modelling them forced into the frame.