{# CLP "digital label" content - Regulation (EU) 2024/2865 amending CLP (EC) 1272/2008. A voluntary, complementary-to-the-physical-label page reachable via a data carrier (QR/2D barcode) on the physical label - generating that data carrier is out of scope here (see pac_id_generator), this is only the landing page it points to, same role pac_issuer_landing_page.jinja.html already plays for the generic case. No skeleton file of its own: pac_issuer_landing_page_skeleton.jinja.html is already fully content-agnostic (just an HTMX loading shell), so an issuer adopting this reuses it unchanged and only overrides pac_issuer_landing_page.jinja.html's content to `{% include "pac_info/digital_label.jinja.html" %}` instead of `pac_info/pac_info.jinja.html` - see developer-docs/design-choices.md. Lives alongside pac_info.jinja.html rather than its own subdirectory, since it's a single file. Built entirely from the existing pac_info macro library - no rendering logic of its own beyond picking which attributes to surface where. #} {% import "pac_info/base-components.jinja.html" as pac_macros with context %} {% import "pac_info/card-components.jinja.html" as card_macros %} {% block head_extension %} {% endblock %} {% if pac_info %}
{# ---- Product identification. Trade name/category segments only - the chemical/regulatory identity (product_identifiers) has its own section below, sibling of Documents, not folded into this card. ---- #}
{{ card_macros.card_title(pac_info) }} {{ card_macros.card_main(pac_info) }} {{ card_macros.qualification_state(pac_info) }}
{# ---- Product identifiers. CLP Art. 18's chemical/regulatory identity (Annex VI index no., CAS/EC numbers, product code) - the trade name itself is already card_title's job above, so these are additional, not a replacement. ---- #} {% set product_identifiers = pac_info.product_identifiers %} {% if product_identifiers %}

Product Identifiers

{% set labels = product_identifiers | map(attribute='label') | list %} {% set values = product_identifiers | map(attribute='values') | list %} {{ pac_macros.key_value_table(zip(labels, values)) }}
{% endif %} {# ---- Hazard information, surfaced first: matches what a user expects immediately after scanning the data carrier, not buried in a generic attribute table indistinguishable from any other attribute group. Hazard/precautionary statement codes get their own table (code beside its pictogram(s) and full text - see base-components.jinja.html's hazard_statement_row/precautionary_statement_row), rather than the generic attribute_row used for signal word and UFI. ---- #} {% set signal_word = pac_info.signal_word %} {% set hazard_statements = pac_info.hazard_statements %} {% set precautionary_statements = pac_info.precautionary_statements %} {% set ufi = pac_info.ufi %} {# Pictograms are a property of the substance as a whole, not of any one statement row - pulled once from every hazard/precautionary code (a set dedupes, e.g. H300/H310/H330 sharing Skull-and-crossbones), sorted by GHS number, and shown as a single strip above the table instead of repeated per row. Supersedes the old plain safety_pictograms() display - this is a strict superset (same explicit attributes, plus codes derived from the statements themselves). #} {% set explicit_image_urls = pac_info.explicit_pictogram_image_urls %} {% set pictogram_codes = pac_info.safety_pictogram_codes %} {% if pictogram_codes or explicit_image_urls or signal_word or hazard_statements or precautionary_statements or ufi %}

Hazard Information

{{ pac_macros.pictogram_strip(pictogram_codes, explicit_image_urls) }} {% if signal_word %}

{{ signal_word }}

{% endif %}
{% if hazard_statements %} {% for statement in hazard_statements %}{{ pac_macros.hazard_statement_row(statement) }}{% endfor %}
{% endif %} {% if precautionary_statements %} {% for statement in precautionary_statements %}{{ pac_macros.precautionary_statement_row(statement) }}{% endfor %}
{% endif %} {% if ufi %} {{ pac_macros.attribute_row(ufi) }}
{% endif %}
{% endif %} {# ---- Supplier & quantity. SUPPLIER's value is expected to be a PAC-ID for the supplier itself (their own name/address/phone live on their own PAC-ID's attributes) - attribute_row() already renders a reference card for any attribute value that resolves as a PAC-ID, no extra handling needed. This issuer's own configured contact_address (SiteMeta) - for the common case where the issuer *is* the supplier - is a separate block, below the safety data sheet. ---- #} {% set supplier = pac_info.supplier %} {% set nominal_quantity = pac_info.nominal_quantity %} {% if supplier or nominal_quantity %}

Supplier & Quantity

{% if supplier %}{{ pac_macros.attribute_row(supplier) }}{% endif %} {% if nominal_quantity %}{{ pac_macros.attribute_row(nominal_quantity) }}{% endif %}
{% endif %} {# ---- Documents. Every DocumentKeys entry (SDS, Certificate of Analysis, Datasheet, User Manual), from an attribute or a user handover Service - see PacInfo.documents/_get_document. Supersedes a Safety-Data-Sheet-only section - SDS is just one member of this same set now. ---- #} {% set documents = pac_info.documents %} {% if documents %}

Documents

{% for document in documents %}{{ pac_macros.document_row(document) }}{% endfor %}
{% endif %} {# ---- Physical & chemical properties (e.g. boiling point, density, flash point, pH, viscosity) - SDS Section 9-style info, distinct from the hazard classification above. ---- #} {% set physico_chemical_properties = pac_info.physico_chemical_properties %} {% if physico_chemical_properties %}

Physical & Chemical Properties

{% set labels = physico_chemical_properties | map(attribute='label') | list %} {% set values = physico_chemical_properties | map(attribute='values') | list %} {{ pac_macros.key_value_table(zip(labels, values)) }}
{% endif %} {# ---- Synonyms / alternate names. ---- #} {% set synonyms = pac_info.synonyms %} {% if synonyms %}

Synonyms

{% set labels = synonyms | map(attribute='label') | list %} {% set values = synonyms | map(attribute='values') | list %} {{ pac_macros.key_value_table(zip(labels, values)) }}
{% endif %} {# ---- Specifications - product/QC parameters typically found on a Certificate of Analysis (assay %, water content, heavy metals, sterility, ...), distinct from physico_chemical_properties' bulk material properties. The CoA link itself (if any) sits right below the table it backs, in addition to also appearing in the general Documents section above - a deliberate exception to that section's dedup, per the user's ask. ---- #} {% set specifications = pac_info.specifications %} {% set certificate_of_analysis = pac_info.certificate_of_analysis %} {% if specifications or certificate_of_analysis %}

Specifications

{% if specifications %} {% set labels = specifications | map(attribute='label') | list %} {% set values = specifications | map(attribute='values') | list %} {{ pac_macros.key_value_table(zip(labels, values)) }} {% endif %} {% if certificate_of_analysis %} {{ pac_macros.document_row(certificate_of_analysis) }}
{% endif %}
{% endif %} {# ---- Supplier contact address, below the SDS per the user's placement call - this issuer's own configured address (SiteMeta), not attribute data. Only the per-issuer pac_issuer_demo apps (IssuerFlaskAppFactory) supply site_meta as a Jinja global - other consumers of this template (e.g. instrument_demo, which has no "issuer site" concept at all) don't, so this is guarded the same way pac_card_url_for/route_pac_id_url_for are elsewhere in these templates rather than assuming every caller has it. ---- #} {% if site_meta is defined and site_meta.contact_address %}

Supplier Contact

{# Plain text, not render_text() - that helper's tag allowlist (sub/sup/i/em/ pre) is for sanitizing untrusted attribute values, not issuer-owned config; it also has no
, so it'd fight a multi-line address rather than help. Auto-escaped by Jinja same as any other value; newlines become visual line breaks via CSS white-space: pre-line instead. #}

{{ site_meta.contact_address }}

{% endif %} {# ---- Services ---- #} {% if pac_info.user_handovers %}

Services

{% for sg in pac_info.user_handovers %} {{ pac_macros.service_group_block(sg) }} {% endfor %}
{% endif %} {# ---- Everything else. other_attribute_groups excludes whatever a dedicated property above already surfaced (see PacInfo._CLAIMED_ATTRIBUTE_KEYS), so nothing shown above is repeated here. ---- #} {% set other_attribute_groups = pac_info.other_attribute_groups %} {% if other_attribute_groups %}

Attributes

{% for ag in other_attribute_groups.values() %} {% if ag.group_key not in hide_attribute_groups %} {{ pac_macros.attribute_group_block(ag) }} {% endif %} {% endfor %}
{% endif %} {# ---- Attached data (T-REX) ---- #} {% if pac_info.attached_data %}

Attached Data (T-REX)

{{ pac_macros.attached_data_block(pac_info.attached_data) }}
{% endif %}
{% endif %}