Skip to content

DCC Export

Export of Digital Calibration Certificates in calServer v2.


Overview

The DCC export generates XML-based calibration certificates according to the PTB standard (Physikalisch-Technische Bundesanstalt). A DCC contains all relevant information about a calibration in a machine-readable format.

PTB DCC Standard

The Digital Calibration Certificate (DCC) is an international standard for the digital exchange of calibration results. It enables automatic processing and validation of calibration data.

API Endpoint

The DCC export is available via the V2 API:

POST /api/v2/calibrations/{ctag}/dcc
Parameter Type Description
ctag string Unique calibration tag (CTAG)
report_id string The DCC entry that was clicked (see Menu configuration). Without it the export takes the first configured entry.
include_pdf bool Forces XML-only regardless of the configuration. Default: true.

Response: JSON with DCC XML data and meta information.

{
    "data": {
        "type": "dcc",
        "attributes": {
            "xml": "<?xml version=\"1.0\" ...?>...",
            "calibration_id": "CTAG-123",
            "generated_at": "2026-03-08T10:00:00Z"
        }
    }
}

Permission

The endpoint requires the calibration_view and using_API permissions.

The DCC used to be a hardcoded button in V2: "DCC Export" sat in every calibration's overflow menu, for everyone allowed to see the calibration, and the server guessed which certificate to embed (first direct-print report, otherwise the first one at all). For a document a laboratory issues, that is the wrong default: who may issue it, and which certificate it is, is the laboratory's decision.

V1 never worked differently — there the "report name (DCC XML)" entry hangs on the report package carrying a schema/dcc-mapping.json (AdminReportSetting::hasDccMapping()) and inherits placement, order and role/group visibility from that report. V2 builds the DCC from the calibration data, so there is no mapping file to detect — the same information now comes from configuration.

Admin → Reports → Edit report (grid calibration only):

Field Column Meaning
Offer DCC export is_dcc The report gains an additional "report name (DCC XML)" entry.
Embed human-readable certificate dcc_include_pdf PDF inside dcc:document. Off = XML only. Default: on.
Certificate to embed dcc_pdf_report_uID Which calibration certificate is embedded. Empty = this report itself.

What follows from that:

  • Where — the DCC entry sits directly beneath its report, in the same context (menu: Detail, Master, Master-Detail) and the same order (sort_order). A report with menu = Master has no DCC entry on the detail page, because it has no entry there itself.
  • By whom — the report's role_name/group_id apply unchanged (ReportAccessService). Someone who cannot see the report gets neither it nor its DCC, not even by calling the endpoint with its report_id (404).
  • Which representation — the embedded certificate is selectable. Several reports may carry is_dcc; each then contributes its own entry with its own certificate.
  • The enable_dcc_plugin feature flag remains the master switch. While it is off, GET /calibrations/{ctag}/reports reports is_dcc: false and no entry appears — exactly as in V1.

The rules live in DccReportSettingResolver, the input validation in ValidatesDccReportConfig (DCC only on the calibration grid; the embedded certificate must be an active calibration report and not the report itself).

Existing installations

The migration sets is_dcc on exactly the report the export would already have embedded — but only where the DCC plugin is switched on. The previous behaviour is then an explicit configuration row instead of a continuing guess. Anyone enabling the plugin later has to set the entry once; without a configured report there is no menu entry.

API clients

POST /calibrations/{ctag}/dcc without report_id keeps working, even on an installation with no entry configured — the old automatic pick of the embedded certificate applies there.

DCC XML Structure

A generated DCC XML contains the following main sections:

<?xml version="1.0" encoding="UTF-8"?>
<dcc:digitalCalibrationCertificate
    xmlns:dcc="https://ptb.de/dcc">

    <!-- Administrative data -->
    <dcc:administrativeData>
        <dcc:calibrationLaboratory>...</dcc:calibrationLaboratory>
        <dcc:coreData>
            <dcc:uniqueIdentifier>CTAG-123</dcc:uniqueIdentifier>
            <dcc:beginPerformanceDate>2026-01-15</dcc:beginPerformanceDate>
            <dcc:endPerformanceDate>2026-01-15</dcc:endPerformanceDate>
        </dcc:coreData>
    </dcc:administrativeData>

    <!-- Measurement results -->
    <dcc:measurementResults>
        <dcc:measurementResult>...</dcc:measurementResult>
    </dcc:measurementResults>
</dcc:digitalCalibrationCertificate>

Content Sections

Section Source Description
Calibration laboratory System configuration Name, address, accreditation
Core data tbl_calibration CTAG, date, examiner
Instrument tbl_inventory MTAG, serial number, description
Customer tbl_customer Client
Measurement results tbl_test_step Individual test steps with target/actual values
Reference standards tbl_standard_article Reference standards used

Data Flow

Calibration (CTAG)
    │
    ├── Inventory (MTAG) ──► Instrument data
    │
    ├── Customer (KTAG) ──► Client data
    │
    ├── TestSteps ──► Measurement results
    │       │
    │       └── Target/actual values, tolerances, units
    │
    └── StandardArticles ──► Reference standards
            │
            └── Reference standards with traceability

Configuration

DCC-specific settings are managed via the property table:

Property Description
dcc_laboratory_name Name of the calibration laboratory
dcc_accreditation_number Accreditation number (DAkkS or similar)
dcc_schema_version DCC schema version (default: 3.2.1)

Validation

The generated XML is validated against the official PTB DCC schema before being returned to the client. Missing required fields result in a 422 Unprocessable Entity response with details about the missing data.

No invented values

A calibration certificate claiming the device read 0 V when nothing was recorded is worse than no certificate at all: the document is schema-valid, the statement is false, and no validator on earth can tell. So throughout the exporter:

A missing value is never substituted. Depending on what the schema allows:

Case Behaviour
A row's measured or reference value is missing export refused (422), the affected rows are named in the message
A measurement row has no unit same
Row without any numeric value (visual check, remark) not a measurement row; blocks nothing, stays out of the value list
Measurement error or uncertainty missing in one row of the group the whole quantity is dropped; an si:...XMLList has no gaps
Country cannot be resolved dcc:countryCode is omitted (the schema models address parts as a choice)
Contact without any address an explicit note in dcc:further instead of a guessed country
Laboratory name not configured export refused (422), pointing at Admin → Reports → Report variables (lab_name)

The check lives in DccXmlGeneratorService::missingPrerequisites() and runs in the endpoint before any XML is built.

The rejection as a work list

The sentence in error.detail says what is missing, not where it sits. "Row 3 (Resistance 100 Ohm): displayed measured value missing" is correct as prose and useless as an instruction — you still have to go looking. The 422 therefore carries the same information machine-readable alongside it:

{
  "error": {
    "status": 422,
    "title": "DCC Export Incomplete",
    "detail": "Für ein gültiges DCC (PTB 3.3.0) fehlt noch: …",
    "missing": [
      {
        "code": "lab_name",
        "label": "der Name des Kalibrierlabors",
        "fix": "report_variable",
        "hint": "Admin → Berichtsverwaltung → Berichtseinstellungen → Laborangaben",
        "rows": []
      },
      {
        "code": "results",
        "label": "ein vollständiges Wertepaar aus Referenz- und Messwert in 1 Messzeile(n)",
        "fix": "results",
        "hint": "Zeile 3 (Resistance 100 Ohm): angezeigter Messwert fehlt",
        "rows": [
          {
            "row_number": 3,
            "label": "Resistance 100 Ohm",
            "reason": "angezeigter Messwert fehlt"
          }
        ]
      }
    ]
  }
}
Field Meaning
code Stable identifier: inventory, customer, calibration_date, lab_name, results
label Plain text, the same wording used in the sentence
fix Jump target: calibration_record (edit the record), report_variable (report administration), results (measurement grid)
hint Addendum — path to the setting, or a short list of rows (max. 5, then …)
rows Affected measurement rows, complete and sorted by row_number

row_number is the number shown in the measurement grid (results.row_num), not the position in the list — only that finds the row again when numbering does not start at 1.

Sentence and list come from the same source: missingPrerequisites() renders its sentences from exactly these entries (describeMissing()), so they cannot drift apart. Clients that only read detail stay valid; the frontend renders the rejection as a standing block (DccIssuePanel) with one link per item instead of a toast that vanishes after five seconds while you are still searching. Clicking a measurement row opens the results tab and highlights that row.

Behaviour change

Installations without a configured lab_name now get a 422 instead of a certificate issued by the placeholder "Kalibrierlabor". One-time setup.

refTypes

refType is the machine-readable anchor in a DCC — how a receiver locates the measured value, the reference, the error. The XSD does not check it (refTypesType is a list of arbitrary strings); the DKD refType thesaurus (digilab.ptb.de/dkd/refType/vocab/) is authoritative. An invented identifier is therefore not a schema error, just dead weight.

The exporter emits basic_ identifiers (cross-quantity). DccRefTypeTest::DKD_REF_TYPES carries the full list with provenance and placement rule, and fails as soon as a new one appears.

Every identifier has been checked against the thesaurus (state 2026-09-03). Replaced along the way: basic_recalibration → basic_recalibrationDate, basic_uncertainty → basic_methodMeasurementUncertainty and gp_method → basic_calibrationMethod; the thesaurus has no gp_ namespace at all.

Own identifiers

Two statements have no term in the thesaurus: the measurement conditions as free text and the applied test procedure. Both use an own namespace and are declared by the certificate itself:

refType Element Meaning
calServer_measurementConditions dcc:usedMethod measurement conditions as free text (report variable)
calServer_measuringProcedure dcc:usedMethod applied test procedure including version

dcc:administrativeData/dcc:refTypeDefinitions declares each one with namespace and link. The node only appears when the certificate actually carries such an identifier — refTypeDefinitionListType requires at least one entry.

An invented basic_… would look official without being it; a declared own identifier is one a receiver can resolve.

Deliberately unchanged

basic_pressure sits on dcc:influenceCondition although the thesaurus only lists ruleQuantity for it (basic_temperature and basic_humidityRelative also carry ruleInfluenceCondition). Which element may carry which identifier is a dimension no checker evaluates yet; writing one of the three conditions differently would only add inconsistency.

dcc:result deliberately carries no identifier: the gp_measuredQuantity that used to sit there belonged to no namespace, and the meaning lives on the dcc:quantity elements inside it anyway.

Units: D-SI notation

A DCC does not write units as mV or °C but in PTB's D-SI notation: \milli\volt, \degreecelsius. DccXmlGeneratorService::toSiUnit() resolves them in this order:

  1. Spelling variants — Ohm, kOhm, uV, degC are normalised to the canonical symbol (Ω, kΩ, µV, °C). Legacy data is full of them: the master-data catalogue carries Ω, people typed Ohm.
  2. Compound and fixed symbols — products (Nm → \newton\metre), quotients (m/s → \metre\per\second), exponents (m³ → \metre\tothe{3}) and the fixed spellings (°C → \degreecelsius, dB → \decibel).
  3. Master-data catalogue — physical_quantity.dsi_unit, maintained under Admin → Requirements → Physical quantities (see below).
  4. Base unit without a prefix — V → \volt, h → \hour.
  5. Prefix + base unit — every combination of an si_prefix row and a unit symbol: mV, kΩ, hPa, daN, µS. Prefixes also apply to catalogue entries whose notation is a single token.
  6. Otherwise the | escape — HRC becomes |HRC.

Step 6 is the change: a unit outside the list used to go into the XML verbatim, where it was not a valid D-SI string. The escape is what D-SI prescribes for units outside its vocabulary — the value stays readable and the document stays valid. Such a unit still is not machine-interpretable for the receiving side; the procedure editor says so while the unit is entered.

Three D-SI rules are visible in the code:

  • R10 — minute, hour, day, hectare, mmHg and the ratio quantities (%, ppm) take no prefix. There is no kmin; it becomes |kmin.
  • R11 — the kilogram is the base unit and is spelled \kilogram, never \kilo\gram.
  • R12 — the decibel is \decibel, never \deci\bel.

Units without a D-SI equivalent are mapped to a | unit on purpose: pH (which would otherwise decompose into pico + henry), dBm (a level referenced to 1 mW, not the product of decibel and milliwatt) and the hardness scales HRC, HV, HBW.

The XSD does not check the unit

si:unitType is an unrestricted xs:string in D-SI 2.2.1 — schema validation happily accepts HRC. The grammar lives in the D-SI rules, so DccUnitNotationTest validates every string the exporter can emit against the regex of PTB's reference implementation (tests/Fixtures/dcc-schema/d-si-unit.regex).

Fixing a unit without a release

The symbol → D-SI mapping used to live exclusively as a constant in the generator. Anything those tables did not know went into the certificate as |unit — valid, but worthless to a machine, and not fixable without a release, even though the unit comes from a catalogue the lab maintains itself.

Hence the dsi_unit column on physical_quantity, editable under Admin → Requirements → Physical quantities:

  • Empty: the exporter derives the notation from the symbol as before.
  • Set: the catalogue value wins, including over the built-in table.
  • Validation covers the form (\token, lowercase, no spaces, exponents as \tothe{2}, | for units outside D-SI), not the vocabulary: an exotic but valid unit must not fail at the input mask.

The factory defaults ship the notation for all ~50 catalogue entries (App\Support\SiUnitCatalog); the migration backfills existing rows without touching values a lab set itself. The column is a plain V2 change — V2 no longer shares any table with V1 and keeps running once V1 is switched off.