{# 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 %}
{% 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. #}
{% 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 %}