---
$schema: "http://json-schema.org/draft-07/schema#"
$id: "https://oimlsmart.org/schemas/R137.yaml"
title: "OIML R137 (Dynamic Measuring Systems for Gases — Gas Meters) Certificate Schema"
type: object
required: [certificate, certified_type, characteristics, recommendation]
properties:
  certificate:           { $ref: "_core.yaml#/definitions/Certificate" }
  issuing_authority:     { $ref: "_core.yaml#/definitions/IssuingAuthority" }
  applicants:
    type: array
    items: { $ref: "_core.yaml#/definitions/Party" }
  manufacturers:
    type: array
    items: { $ref: "_core.yaml#/definitions/Party" }
  certified_type:        { $ref: "#/definitions/CertifiedType" }
  characteristics:       { $ref: "#/definitions/Characteristics" }
  recommendation:
    type: object
    required: [id, edition, scheme]
    properties:
      id:          { type: string, const: R137 }
      edition:     { type: integer, enum: [2012] }
      amendment:   { type: [integer, "null"] }
      scheme:      { type: string, enum: [A, B] }
      accuracy_classes:
        type: array
        items: { $ref: "#/definitions/AccuracyClassToken" }
  test_reports:          { $ref: "_core.yaml#/definitions/TestReportList" }
  revision_history:      { $ref: "_core.yaml#/definitions/RevisionHistory" }
  model_family:          { $ref: "_core.yaml#/definitions/ModelFamily" }
  components:
    type: array
    items: { $ref: "_core.yaml#/definitions/Component" }
  footnotes:             { $ref: "_core.yaml#/definitions/FootnoteList" }
  matrix_tables:         { $ref: "#/definitions/MatrixTableList" }
  d011_tests_applicable:
    description: |
      Tests mandated by R137 §3.2 type evaluation (referencing OIML D 11:2013).
      Gas meters are typically electronic-instrument family devices and
      inherit D 11 climatic, mechanical, and electromagnetic test
      applicability per the meter's classification.
    type: array
    items: { $ref: "#/definitions/D011TestReference" }
    default:
    - { test_id: d011_t06_static_temperature }
    - { test_id: d011_t07_static_temperature }
    - { test_id: d011_t08_damp_heat_steady_state }
    - { test_id: d011_t09_damp_heat_cyclic }
    - { test_id: d011_t11_atmospheric_pressure }
    - { test_id: d011_t16_vibration_sinusoidal }
    - { test_id: d011_t18_dc_mains_voltage_variation }
    - { test_id: d011_t20_ac_mains_voltage_variation }
    - { test_id: d011_t22_dc_mains_dips_interruptions }
    - { test_id: d011_t23_ac_mains_dips_interruptions }
    - { test_id: d011_t26_bursts_ac_dc_mains }
    - { test_id: d011_t27_surges_ac_dc_mains }
    - { test_id: d011_t28_bursts_signal_data_control }
    - { test_id: d011_t29_surges_signal_data_control }
    - { test_id: d011_t33_rf_em_fields_general }
    - { test_id: d011_t35_electrostatic_discharges }

definitions:
  AccuracyClass:
    description: |
      OIML R137-1 §3.3 gas meter accuracy class. Decimal values per
      R137-1 table 1 — maximum permissible error (MPE) expressed as
      a percentage. Lower number = tighter accuracy. The `value` may
      be null when the cert declares accuracy only at
      `recommendation.accuracy_classes` (data extraction noise per
      the R137 architecture analysis, G1).
    allOf:
    - $ref: "_core.yaml#/definitions/StructuredValue"
    - properties:
        value:
          type: [number, string, "null"]
          oneOf:
          - const: 0.5
            description: Class 0.5 — highest accuracy (MPE ±0.5%)
          - const: "0.5"
            description: Class 0.5 — highest accuracy (MPE ±0.5%)
          - const: 1.0
            description: Class 1.0 — high accuracy (MPE ±1%)
          - const: "1.0"
            description: Class 1.0 — high accuracy (MPE ±1%)
          - const: "1"
            description: Class 1 — high accuracy (MPE ±1%)
          - const: 1.5
            description: Class 1.5 — medium accuracy (MPE ±1.5%)
          - const: "1.5"
            description: Class 1.5 — medium accuracy (MPE ±1.5%)
          - const: 2.0
            description: Class 2.0 — lower accuracy (MPE ±2%)
          - const: "2.0"
            description: Class 2.0 — lower accuracy (MPE ±2%)
          - const: "2"
            description: Class 2 — lower accuracy (MPE ±2%)
          - const: 2.5
            description: Class 2.5 — lowest accuracy (MPE ±2.5%)
          - const: "2.5"
            description: Class 2.5 — lowest accuracy (MPE ±2.5%)
          - const: "N/A"
            description: Not applicable
          - type: "null"

  AccuracyClassToken:
    description: |
      Flat-string form of OIML R137-1 §3.3 accuracy class as it appears
      at `recommendation.accuracy_classes`. Accepts both numeric and
      string spellings (e.g. `1.0` and `"1.0"`) since the extractor
      emits both.
    type: [number, string]
    oneOf:
    - const: 0.5
      description: Class 0.5 — highest accuracy (MPE ±0.5%)
    - const: "0.5"
      description: Class 0.5 — highest accuracy (MPE ±0.5%)
    - const: 1.0
      description: Class 1.0 — high accuracy (MPE ±1%)
    - const: "1.0"
      description: Class 1.0 — high accuracy (MPE ±1%)
    - const: "1"
      description: Class 1 — high accuracy (MPE ±1%)
    - const: 1.5
      description: Class 1.5 — medium accuracy (MPE ±1.5%)
    - const: "1.5"
      description: Class 1.5 — medium accuracy (MPE ±1.5%)
    - const: 2.0
      description: Class 2.0 — lower accuracy (MPE ±2%)
    - const: "2.0"
      description: Class 2.0 — lower accuracy (MPE ±2%)
    - const: "2"
      description: Class 2 — lower accuracy (MPE ±2%)
    - const: 2.5
      description: Class 2.5 — lowest accuracy (MPE ±2.5%)
    - const: "2.5"
      description: Class 2.5 — lowest accuracy (MPE ±2.5%)

  MeterTechnology:
    description: |
      R137-1 §3.1 meter technology family. Canonical tokens derived from
      the metering principle. Free-text `certified_type.category` values
      (e.g. "Diaphragm gas meter", "Ultrasonic Gas Meter") should be
      normalized to these tokens.
    type: string
    oneOf:
    - const: diaphragm
      description: Diaphragm (positive displacement) gas meter
    - const: rotary
      description: Rotary displacement gas meter
    - const: turbine
      description: Turbine gas meter
    - const: ultrasonic
      description: Ultrasonic gas meter
    - const: thermal_mass
      description: Thermal-mass flow gas meter
    - const: coriolis
      description: Coriolis (mass-flow) gas meter

  PressureReference:
    description: |
      R137-1 §4.1.2 pressure reference qualifier. Gas meter pressure
      specifications may be expressed relative to atmospheric pressure
      (gauge) or against vacuum (absolute), or as a difference between
      two measurement points (differential). This is an R137-specific
      aspect — distinct from the D 11 environmental classes and not
      part of the D 11 module.
    type: string
    oneOf:
    - const: gauge
      description: Gauge pressure — measured relative to atmospheric pressure (e.g. bar(g))
    - const: absolute
      description: Absolute pressure — measured against vacuum (e.g. bar(a))
    - const: differential
      description: Differential pressure — measured between two points (e.g. ΔP across the meter)

  PressureValue:
    description: |
      StructuredValue extended with a pressure reference qualifier
      (gauge/absolute/differential). This is the R137-specific way to
      represent pressure measurements where the reference frame is
      metrologically significant.
    allOf:
    - $ref: "_core.yaml#/definitions/StructuredValue"
    - properties:
        reference:
          $ref: "#/definitions/PressureReference"

  CertifiedType:
    type: object
    required: [category, type_designations]
    properties:
      category:          { type: [string, "null"] }
      type_designations:
        type: array
        items: { type: string }
      meter_technology:  { $ref: "#/definitions/MeterTechnology" }
      module_designation:
        description: |
          Free-text description of the meter type (e.g. "An ultrasonic
          gas meter Type: UIM-4F", "Not applicable"). In cert data this
          appears as either a string OR as an object whose `description`
          key carries the text — both forms are accepted.
        oneOf:
        - type: [string, "null"]
        - type: object
          properties:
            description: { type: [string, "null"] }
          additionalProperties: true
      description:        { type: [string, "null"] }

  Characteristics:
    type: object
    properties:
      type_level: { $ref: "#/definitions/TypeLevel" }
      model_level:
        type: array
        items: { $ref: "#/definitions/ModelLevelEntry" }
      config_level:
        type: array
        items: { $ref: "#/definitions/ConfigLevelEntry" }

  TypeLevel:
    type: object
    properties:
      accuracy_class:                    { $ref: "#/definitions/AccuracyClass" }
      meter_technology:                  { $ref: "#/definitions/MeterTechnology" }
      temperature_range:                 { $ref: "_modules/d011_environmental.yaml#/definitions/TemperatureRange" }
      gas_temperature_range:             { $ref: "_modules/d011_environmental.yaml#/definitions/TemperatureRange" }
      ambient_temperature_range:         { $ref: "_modules/d011_environmental.yaml#/definitions/TemperatureRange" }
      working_temperature_range_tw:      { $ref: "_modules/d011_environmental.yaml#/definitions/TemperatureRange" }
      storage_temperature:               { $ref: "_modules/d011_environmental.yaml#/definitions/TemperatureRange" }
      environmental_classes:             { $ref: "#/definitions/EnvironmentalClasses" }
      climatic_environment_class:        { $ref: "#/definitions/NullableClimaticClass" }
      electromagnetic_environment_class: { $ref: "#/definitions/NullableEMClass" }
      mechanical_environment_class:      { $ref: "#/definitions/NullableMechanicalClass" }
      maximum_pressure:                  { $ref: "#/definitions/PressureValue" }
      minimum_pressure:                  { $ref: "#/definitions/PressureValue" }
      maximum_working_pressure_pmax:     { $ref: "#/definitions/PressureValue" }
      working_pressure:                  { $ref: "#/definitions/PressureValue" }
      working_pressure_range:            { $ref: "#/definitions/PressureValue" }
      operating_pressure_range_standard_mounts:   { $ref: "#/definitions/PressureValue" }
      operating_pressure_range_isolated_mounts:   { $ref: "#/definitions/PressureValue" }
      base_pressure:                     { $ref: "#/definitions/PressureValue" }
      maximum_flow_rate:                 { $ref: "_core.yaml#/definitions/StructuredValue" }
      minimum_flow_rate:                 { $ref: "_core.yaml#/definitions/StructuredValue" }
      transitional_flow_rate:            { $ref: "_core.yaml#/definitions/StructuredValue" }
      overload_flow_rate_qr:             { $ref: "_core.yaml#/definitions/StructuredValue" }
      cyclic_volume:                     { $ref: "_core.yaml#/definitions/StructuredValue" }
      indicating_range:                  { $ref: "_core.yaml#/definitions/StructuredValue" }
      scale_interval:                    { $ref: "_core.yaml#/definitions/StructuredValue" }
      counting_scale:                    { $ref: "_core.yaml#/definitions/StructuredValue" }
      resolution:                        { $ref: "_core.yaml#/definitions/StructuredValue" }
      connection_threads_size:           { $ref: "_core.yaml#/definitions/StructuredValue" }
      distance_between_connections:      { $ref: "_core.yaml#/definitions/StructuredValue" }
      orientation:                       { $ref: "_core.yaml#/definitions/StructuredValue" }
      flow_direction:                    { $ref: "_core.yaml#/definitions/StructuredValue" }
      pulse_weight:                      { $ref: "_core.yaml#/definitions/StructuredValue" }
      density_range:                     { $ref: "_core.yaml#/definitions/StructuredValue" }
      path_angle:                        { $ref: "_core.yaml#/definitions/StructuredValue" }
      path_configuration:                { $ref: "_core.yaml#/definitions/StructuredValue" }
      transducers:                       { $ref: "_core.yaml#/definitions/StructuredValue" }
      power_supply_voltage:              { $ref: "_core.yaml#/definitions/StructuredValue" }
      power_supply_frequency:            { $ref: "_core.yaml#/definitions/StructuredValue" }
      power_supply_type:                 { $ref: "_core.yaml#/definitions/StructuredValue" }
      power_supply_battery:              { $ref: "_core.yaml#/definitions/StructuredValue" }
      electrical_power:                  { $ref: "_core.yaml#/definitions/StructuredValue" }
      software_identification:           { $ref: "_core.yaml#/definitions/SoftwareIdentification" }
      designed_for:                      { $ref: "_core.yaml#/definitions/StructuredValue" }
      designed_for_humidity_conditions:  { $ref: "_core.yaml#/definitions/StructuredValue" }
      destined_for:                      { $ref: "_core.yaml#/definitions/StructuredValue" }
      destined_for_measurement_of:       { $ref: "_core.yaml#/definitions/StructuredValue" }
      ancillary_devices:                 { $ref: "_core.yaml#/definitions/StructuredValue" }
      lubrication:                       { $ref: "_core.yaml#/definitions/StructuredValue" }
      indicating_device:                 { $ref: "_core.yaml#/definitions/StructuredValue" }

  EnvironmentalClasses:
    description: |
      Composite D 11 environmental classification as seen in cert data.
      Decomposed into mechanical (M1-M3) and electromagnetic (E1-E3)
      sub-classes per OIML D 11:2013 §7. When a StructuredValue wraps
      the classification, the inner `value` carries the decomposition;
      the unit fields (`unit_symbol`, `unit_id`) remain null.

      In practice, the inner `mechanical` / `electromagnetic` keys carry
      bare enum tokens (e.g. "M1", "E2") rather than full StructuredValue
      objects, so both forms are accepted here.
    type: object
    properties:
      value:
        oneOf:
        - type: object
          properties:
            mechanical:
              type: [string, "null"]
              # Accept canonical D 11 tokens plus free-text forms the
              # GLM extractor emits ("M1/M2" multi-class, "Not
              # applicable" for mechanical-only meters). Downstream
              # normalization canonicalizes these.
              enum:
              - M1
              - M2
              - M3
              - "M1/M2"
              - "M2/M3"
              - "Not applicable"
              - null
            electromagnetic:
              type: [string, "null"]
              enum:
              - E1
              - E2
              - E3
              - "E1/E2"
              - "E2/E3"
              - "Not applicable"
              - null
          additionalProperties: true
        - type: [string, "null", number, boolean]
      unit_symbol:
        type: [string, "null"]
      unit_id:
        type: [string, "null"]
      footnote_markers:
        type: array
        items: { type: string }
    additionalProperties: true

  NullableClimaticClass:
    description: |
      D 11 §7.2 climatic class wrapped to allow null values, which appear
      in cert data when the characteristic is declared but the extractor
      could not assign a class. Defined standalone (not via allOf with
      the d011 ClimaticClass) because JSON Schema allOf does not allow
      overriding a sibling subschema's `value` constraints.
    type: object
    properties:
      value:
        type: [string, "null"]
        oneOf:
        - const: H1
        - const: H2
        - const: H3
        - const: "N/A"
        - const: "Not applicable"
        - type: "null"
      unit_symbol:   { type: [string, "null"] }
      unit_id:       { type: [string, "null"] }
      footnote_markers:
        type: array
        items: { type: string }
    additionalProperties: true

  NullableEMClass:
    description: |
      D 11 §7.4 electromagnetic class wrapped to allow null values, which
      appear in cert data when the meter has no electronics ("Not
      applicable") but the characteristic cell is still present.
    type: object
    properties:
      value:
        type: [string, "null"]
        oneOf:
        - const: E1
        - const: E2
        - const: E3
        - const: "N/A"
        - const: "Not applicable"
        - type: "null"
      unit_symbol:   { type: [string, "null"] }
      unit_id:       { type: [string, "null"] }
      footnote_markers:
        type: array
        items: { type: string }
    additionalProperties: true

  NullableMechanicalClass:
    description: |
      D 11 §7.3 mechanical class wrapped to allow null values for the
      same reason as NullableEMClass.
    type: object
    properties:
      value:
        type: [string, "null"]
        oneOf:
        - const: M1
        - const: M2
        - const: M3
        - const: "N/A"
        - const: "Not applicable"
        - type: "null"
      unit_symbol:   { type: [string, "null"] }
      unit_id:       { type: [string, "null"] }
      footnote_markers:
        type: array
        items: { type: string }
    additionalProperties: true

  ModelLevelEntry:
    type: object
    required: [attribute, values]
    properties:
      attribute: { type: string }
      unit_symbol: { type: [string, "null"] }
      values:
        type: array
        items:
          type: object
          required: [model, value]
          properties:
            model:            { type: string }
            value:            { $ref: "_core.yaml#/definitions/ValueOrScalar" }
            footnote_markers:
              type: array
              items: { type: string }

  ConfigLevelEntry:
    type: object
    required: [attribute, values]
    properties:
      attribute: { type: string }
      axis:      { type: [string, "null"] }
      unit_symbol: { type: [string, "null"] }
      values:
        type: array
        items:
          type: object
          anyOf:
          - required: [condition, value]
            properties:
              condition: { type: string }
              value:     { $ref: "_core.yaml#/definitions/ValueOrScalar" }
          - required: [model, value]
            properties:
              model:     { type: string }
              value:     { $ref: "_core.yaml#/definitions/ValueOrScalar" }

  MatrixTable:
    description: |
      Tabular characteristic data (e.g. size x flow-rate tables common in
      R137 gas meter family certificates). R137-1 §4.3 declares the
      canonical flow characteristic table columns.
    type: object
    required: [name, columns, rows]
    properties:
      name:        { type: string }
      description: { type: [string, "null"] }
      columns:
        type: array
        items: { type: string }
      column_units:
        type: [array, "null"]
        items: { type: string }
      rows:
        type: array
        items: { type: object }
      footnotes:   { $ref: "_core.yaml#/definitions/FootnoteList" }

  MatrixTableList:
    type: array
    items: { $ref: "#/definitions/MatrixTable" }

  D011TestReference:
    description: Reference to an OIML D 11:2013 performance test.
    type: object
    required: [test_id]
    properties:
      test_id:          { type: string, pattern: '^d011_t\d{2}_' }
      test_level_index: { type: integer, minimum: 1, maximum: 5 }
      notes:            { type: string }

allOf:
- description: |
    Conditional applicability of D 11 damp heat tests (R137 references
    D 11 §9.2 for humidity performance):
    t08 (steady state) applies when the meter is rated for non-condensing
    (stable humidity) conditions;
    t09 (cyclic) applies when the meter is rated for condensing humidity.
    The environmental class H1/H2/H3 determines which applies.
  if:
    properties:
      characteristics:
        properties:
          type_level:
            properties:
              climatic_environment_class:
                properties:
                  value: { const: H1 }
  then:
    properties:
      d011_tests_applicable:
        contains:
          properties:
            test_id: { const: d011_t08_damp_heat_steady_state }
- if:
    properties:
      characteristics:
        properties:
          type_level:
            properties:
              climatic_environment_class:
                properties:
                  value: { const: H2 }
  then:
    properties:
      d011_tests_applicable:
        contains:
          properties:
            test_id: { const: d011_t09_damp_heat_cyclic }
- description: |
    Conducted/radiated EM and mains tests apply only to meters with
    electronic components (powered meters). Purely mechanical meters
    (mechanical diaphragm meters without electronics) are exempt from
    D 11 §11-§12 tests per R137-1 §3.2.
  if:
    properties:
      characteristics:
        properties:
          type_level:
            properties:
              power_supply_type:
                properties:
                  value: { const: "mechanical" }
  then:
    properties:
      d011_tests_applicable:
        not:
          contains:
            properties:
              test_id:
                enum:
                - d011_t18_dc_mains_voltage_variation
                - d011_t20_ac_mains_voltage_variation
                - d011_t22_dc_mains_dips_interruptions
                - d011_t23_ac_mains_dips_interruptions
                - d011_t26_bursts_ac_dc_mains
                - d011_t27_surges_ac_dc_mains
                - d011_t28_bursts_signal_data_control
                - d011_t29_surges_signal_data_control
                - d011_t33_rf_em_fields_general
                - d011_t35_electrostatic_discharges
