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:
| 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.
Menu configuration¶
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 withmenu = Masterhas no DCC entry on the detail page, because it has no entry there itself. - By whom — the report's
role_name/group_idapply unchanged (ReportAccessService). Someone who cannot see the report gets neither it nor its DCC, not even by calling the endpoint with itsreport_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_pluginfeature flag remains the master switch. While it is off,GET /calibrations/{ctag}/reportsreportsis_dcc: falseand 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:
- Spelling variants —
Ohm,kOhm,uV,degCare normalised to the canonical symbol (Ω,kΩ,µV,°C). Legacy data is full of them: the master-data catalogue carriesΩ, people typedOhm. - 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). - Master-data catalogue —
physical_quantity.dsi_unit, maintained under Admin → Requirements → Physical quantities (see below). - Base unit without a prefix —
V→\volt,h→\hour. - Prefix + base unit — every combination of an
si_prefixrow and a unit symbol:mV,kΩ,hPa,daN,µS. Prefixes also apply to catalogue entries whose notation is a single token. - Otherwise the
|escape —HRCbecomes|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 nokmin; 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.
Related Documentation¶
- Data Model — Table structure for calibrations
- Field Constants — C23xx metrology codes