Authoring · 05 of 17
Forms and Reports
In this chapter: the form, an evidence view that fills the models , and the report skeleton that composes forms into the lab’s deliverable: bind paths, per-field measurement methods, calculation-binding discipline, form wiring, the pass/fail block, required/conditional applicability, the PD-05 checklist, and partial coverage with omissions.
5.1 Forms are evidence views
The third secondary model kind is the form: a fill-in template that
fills the models. A form is a view projecting the subject graph into
a record, bound fields prefill from the resolved chain and write through
on submit, unbound fields are the raw evidence slots. The
template/instance split is INV-2 discipline applied to evidence: the form
as authored is an evidence schema; the FormInstance a lab produces is
the evidence. Forms live in data/<rec>/execution/forms/ (shared
headers first, then per report section), and the form-context engine
(browser/src/data/form-context.ts) is the pure interpreter, all
binding content is in the YAML.
Two consequences follow from “view, not document”. A form owns no facts: its bound fields display subject values resolved by delegation (Family ← Group ← Model ← Sample), and submitting writes through to the proper chain level rather than into a form-local copy. And a form owns no judgments: the form-level result is the lab’s recorded determination, while the IA’s per-requirement verdict is computed independently at evaluation, the fact/judgment firewall (INV-4).
5.2 The bind vocabulary
Bound fields use the same canonical paths as requirement binds_to
(§3.3), plus read-only identity paths for the record’s heading (●
data/r60/execution/forms/shared/headers.yaml, r60-3/header-a):
- name: application_no
bind: application.application_number # identity — read-only
type: string
required: true
- name: load_cell_model
bind: model.model_designation # identity — read-only
type: string
required: true
- name: serial_no
bind: sample.serial_number # identity — read-only
type: string
required: true
- name: emax
bind: model.parameters.e_max # parameter — prefill + write-through
type: number
unit: g, kg, or t
required: true
- name: dmax
bind: sample.test_context.d_max # sample test context
type: number
unit: g, kg, or t
required: true
The full vocabulary: model|family|group.parameters.<attr>,
*.classification.<dim>, sample.test_context.<attr>, and the identity
paths application.application_number, model.model_designation,
sample.serial_number. Scope discipline is identical to chapter 3, a
bind: path resolves to an attribute declared at that scope, and
sample.test_context.e_max is a linker error. Fields without bind:
are the run’s evidence, force_generating_system,
indicating_instrument, the conditions block (dates, temperatures,
humidity, pressure at start and end), stored on the FormInstance, never
written back into the subject chain.
5.3 measurement_method: where each field’s value comes from
Every field declares a measurement_method, the source typing of the
field, parallel to the test-variable sources of §4.3 (●
data/r60/execution/forms/sec-6-performance-tests/creep-dr.yaml):
| method | meaning | creep-dr.yaml example |
|---|---|---|
declared |
a subject/chain value, bound or stated | n_lc, dmin, dmax, plc_for_creep (default 0.7) |
direct |
measured at the bench | reference_indication_75pct, indication_at_dmin, actual_time_s |
computed |
a declared calculation with bindings | conversion_factor_f via calculation: conversionFactor |
derived |
an inline OCL derivation over sibling fields |
max_creep_change_v = ocl{creep_readings->collect(r | r.change_v)->max()} |
lookup |
a table/profile lookup via a calculation | mpe_at_dmax via calculation: mpe |
evaluated |
a Boolean from an OCL evaluation.rule |
creep_30min_result (Pass/Fail labels) |
The evaluated method deserves its example in full, it is how a form
displays a limit check without becoming a verdict:
- name: creep_30min_result
type: boolean
measurement_method: evaluated
evaluation:
rule: ocl{abs(max_creep_change_v) <= 0.7 * abs(mpe_at_dmax)}
condition: "|C_C(30 min)| <= 0.7 x |MPE|"
specification_reference: R 60-1, 5.5.1
true_label: Pass
false_label: Fail
The condition and specification_reference keep the human rendering
and the clause anchor next to the machine rule. Array fields model
series: creep_readings is type: array with a series block naming
its numeric axis (elapsed_min, unit min, role time) and cell
(change_v, unit v), verdict expressions address readings by the
axis (reading_at(window(…), 'elapsed_min', 30, …) per R 60-3 §2.1.5.2),
never by string matching or extreme value.
5.4 calculation_bindings: the key discipline
Computed and lookup fields name a declared calculation and bind its inputs to sibling fields:
- name: conversion_factor_f
measurement_method: computed
calculation: conversionFactor
calculation_bindings:
avgIndicationAt75pct: reference_indication_75pct
indicationAtDmin: indication_at_dmin
"n": n_test_intervals
The keys of calculation_bindings are the calculation’s declared
input names, not field names, not attribute ids, not renamed
conveniences. conversionFactor declares inputs
avgIndicationAt75pct, indicationAtDmin, n (●
data/r60/specification/calculations.yaml), so those exact strings are
the keys; the values are this form’s field names ("n": n_test_intervals maps the calc’s input n to a dedicated derived
field, ocl{(emax - emin) / vmin}, the intervals of the tested range
per R 60-3 §2.1.2.4, not to the sibling y field it coincides with
when the test spans the classified range). The R 60 build’s
binding-key drift pitfall was exactly this: a form binding n_lc
where the calc declared n, the engine silently gets an unbound input.
The linker now checks every binding key against the calc’s declared
inputs; author to the calc, never to habit.
5.5 Wiring: tests, requirements, calculation context
A form declares its place in the closure (● creep-dr.yaml header block):
identifier: r60-3/table-6.8
header: r60-3/header-a
conformance_test:
- /conf/metrological-tests/creep
- /conf/metrological-tests/dr
requirements:
- /req/metrological/creep
- /req/metrological/creep-20-30
- /req/metrological/dr
calculation_context:
dimensions: true
tables:
- mpe_tiers
references:
- { urn: urn:oiml:pub:r:60-1:2021#clause-5.5.1, role: requirement }
- { urn: urn:oiml:pub:r:60-2:2021#clause-2.10.2, role: test-procedure }
- { urn: urn:oiml:pub:r:60-3:2021#clause-2.1.5, role: calculation }
headercomposes a shared header schema, author headers once (§5.2’sr60-3/header-afor load tests,header-bfor humidity,header-cfor EMC), never per form.conformance_testlinks back to §4’s tests (theirresult_formspoint here);requirementslists the requirements whose evidence this form captures. The linker walks both directions: test → form → requirement.calculation_contextdeclares what the form’s calculations may read:dimensions: trueexposes the classification dimensions;tableswhitelists lookup data (mpe_tiersfor the creep MPE).referencescarries typed clause provenance, requirement clauses, test-procedure clauses, calculation clauses, each with its role.
One direction of that wiring is load-bearing, and since task 55 it has
exactly one home (● smart ce10a43): the test’s result_forms IS the
test’s evidence contract. Report-completeness derivations, the form
programme, services/program.service.ts formProgramFor, read the
contract from the test node (evidenceContractFor is the queryable
accessor). The form’s own conformance_test: field survives as the
form-side answer to one question only, “which test does this form
instance serve?” (test-run pinning), and is never a second
completeness source. Adding a form to a test’s programme means authoring
it into the test’s result_forms (in the PRL package), never patching
the form side; the rule already paid for itself when it exposed the R 60
load-test omission in the MDLO test’s programme.
5.6 The pass_fail block
A form closes with its acceptance display:
pass_fail:
criteria: |
30-min creep C_C(t) <= 0.7 * |MPE|;
20-30 min difference C_C(30-20) <= 0.15 * |MPE|;
C_DR <= 0.5 v (if time intervals met)
pass_if: ocl{creep_30min_result and creep_20_30_result and dr_half_v_result}
criteria is the human-readable rendering for the printed report;
pass_if is OCL over the form’s evaluated fields. What this result
is matters: it is the lab’s recorded determination on this form ,
a fact about what the lab concluded, stored with the FormInstance. It is
not a verdict. The IA’s per-requirement verdicts are re-executed at
evaluation from the requirement limits against the same evidence (INV-5);
the two agree when the model is coherent, and the evaluation path is the
authoritative one when they diverge. Authoring rule: pass_if composes
the form’s evaluated fields; it never introduces a new limit, limits
live in requirements (chapter 3) or the VerdictQuantity registry (§3.7),
and forms reference them.
5.7 The test-report skeleton
execution/test-report.yaml structures the lab’s deliverable as sections
of forms, each marked required: always | conditional with
applicability carrying the condition (●
data/r60/execution/test-report.yaml):
- clause 4 “Evaluation Report”, administrative and descriptive:
issuing authority, synopsis, manufacturer/applicant/laboratories, type
information, sample selection, equipment, all
required: always. - clause 5 “Examinations”, marking, suitability, documentation
(
always); software examinationconditionalontechnology: [analogue-active, digital]. - clause 6 “Performance Tests”, summary plus subsections: Base
Metrological Tests (load-cell-errors, repeatability, temperature-mdlo,
creep-dr
always; load-testconditionalper class; barometric-pressureconditionalwith a note, required unless the manufacturer demonstrates pressure-insensitivity, R 60-3 §2.1.6 footnote); Humidity Tests (conditionalonhumidity_class: [CH]/[SH]); Electronic Load Cell Tests, the whole subsection carriesapplicability: { technology: [analogue-active, digital] }, with span-stability further conditioned onaccuracy_class: [B, C, D].
Applicability may sit at form level or subsection level; the applicability engine evaluates both. R 60 deliberately uses no third “optional” tier: a form that never applies to a classification is not required for it; a deliberately skipped in-scope form is an omission (§5.8).
The skeleton is also where the OIML-CS becomes machine-checkable: PD-05
§4.4.3’s 18 required report elements (a)–(r) are declared per element
with obligation and source/validation paths (●
data/r60/execution/test-report-checklist.yaml), title, laboratory,
unique report id, applicant, Recommendation reference, category, type
designation, samples, dates, place, personnel, environmental conditions,
facility, setup, adjustments, results-with-uncertainty (may), per-test
conclusion, authorizing signature. Report compilation validates against
the list; an element with no source path is a gap the author closes in
the skeleton, not in the lab.
5.8 Partial coverage and omissions
Real evaluations split work across laboratories. The granularity of the
split is form × sample × lab, recorded as TestAssignment entities (●
data/r60/entities/workflow.yaml, TestAssignment): the IA creates
the tuples at dispatch, different labs may receive different subsets of
(form, sample) pairs even for one application, and each assignment
produces zero or one FormInstance. Each lab’s TestReport therefore
carries exactly the forms its specialization covers, lab matching itself
is data (lab-selection-criteria.yaml: humidity_symbol=CH →
“humidity-testing”).
A form that is in scope but deliberately not filled is not silence, it is a declared FormOmission with justification (● same file):
- name: form_id
description: "R 60-3 form identifier being omitted (e.g. 'humidity-sh')"
- name: reason
description: "Justification, e.g. 'Not applicable — CH humidity class, SH test not required'"
- name: decided_by
enum_values: [ia, test_lab]
This is the difference between absence by applicability (the skeleton computed the form away), absence by assignment (another lab’s report carries it), and absence by decision (someone with authority said why). Only the third needs a reason, and always needs one, because the completeness side of evaluation (required vs covered forms) counts all three.
5.9 Grammar sketch (illustrative v3 syntax)
form r60-3/table-6.8 {
is {
name "Creep DR" source "urn:oiml:pub:r:60-3:2021#clause-6.8"
header r60-3/header-a
}
has {
conformance_test [ /conf/metrological-tests/creep,
/conf/metrological-tests/dr ]
requirements [ /req/metrological/creep, /req/metrological/creep-20-30,
/req/metrological/dr ]
calculation_context { dimensions true tables [ mpe_tiers ] }
fields {
emax : number bind model.parameters.e_max required
dmax : number bind sample.test_context.d_max required method declared
reference_indication_75pct : number[counts] method direct
conversion_factor_f : number[counts/v] method computed
calc conversionFactor { avgIndicationAt75pct: reference_indication_75pct
indicationAtDmin: indication_at_dmin
n: n_test_intervals } # keys = calc's inputs
creep_readings : array series { axis elapsed_min[min] cell change_v[v] }
max_creep_change_v : number[v] method derived
derive ocl{ creep_readings->collect(r | r.change_v)->max() }
mpe_at_dmax : number[v] method lookup
calc mpe { load: dmax, accuracy_class: accuracy_class, p_lc: plc_for_creep }
creep_30min_result : boolean method evaluated
rule ocl{ abs(max_creep_change_v) <= 0.7 * abs(mpe_at_dmax) }
labels { true: Pass false: Fail }
}
}
does {
pass_fail {
pass_if ocl{ creep_30min_result and creep_20_30_result and dr_half_v_result }
}
}
}
test_report r60-3/test-report {
section "6" "Performance Tests" {
subsection "Humidity Tests" {
form humidity-ch required conditional applicability { humidity_class: [CH] }
form humidity-sh required conditional applicability { humidity_class: [SH] }
}
subsection "Electronic Load Cell Tests"
applicability { technology: [analogue-active, digital] } { … }
}
checklist pd05-4.4.3 # 18 elements, each with obligation + source path
}
5.10 Validation rules
- every
bind:path resolves to a declared attribute at a scope-appropriate level, or to a declared read-only identity path; unbound fields are evidence by construction; - every field declares a
measurement_method;computed/lookupfields name a declared calculation whose every input appears as acalculation_bindingskey (no drift, no orphan keys);derivedandevaluatedfields carry OCL reading sibling fields and bound paths only; - every
conformance_test,requirements,header, andcalculation_context.tablesreference resolves; testresult_formsand formidentifieragree in both directions; pass_ifreads only the form’s evaluated fields; no limit is introduced at form level (registry or requirement references only);- every skeleton
applicabilityvalue is a declared enum value of its dimension; every checklist element carries an obligation and a resolvable source or validation path; - every TestAssignment’s
form_idresolves to a skeleton form; every FormOmission carriesreasonanddecided_by.
5.11 Summary
- A form is an evidence view: bound fields prefill from the delegated chain and write through on submit; unbound fields are the run’s evidence. The form is the schema; the FormInstance is the evidence.
- Bind vocabulary = the requirement bind vocabulary plus read-only identity paths; scope discipline is the same, enforced by the linker.
- Every field’s value has a declared source:
declared | direct | computed | derived | lookup | evaluated.calculation_bindingskeys are the calc’s declared inputs, author to the calc. - Forms wire to tests, requirements, and a calculation context;
pass_failis the lab’s determination, firewalled from the IA’s re-executed verdicts. - The test-report skeleton composes forms with
required: always | conditional+ applicability (form or subsection level); the PD-05 18-element checklist is machine-checkable data. - Work splits at form × sample × lab (TestAssignment); deliberate non-coverage is a justified FormOmission, applicability computes absence, assignment distributes it, decisions explain it.
Next: Chapter 6, Evaluation: determinations, verdicts, model evaluation, the decision, and the certificate template.