Open Badge 3.0

#12
by Surn - opened
Hugging Face MCP Course org
edited about 7 hours ago

Could you add Open Badge metadata to the certificates please?

https://www.imsglobal.org/spec/ob/v3p0/impl

You can always hire me to do it. I have a S-corp and can just be a contractor. You could also point me at open source code.

I know you have coders, so here is something to give you a headstart...

Sample code

from PIL import Image
from PIL.PngImagePlugin import PngInfo
import json

def add_openbadge_metadata(image_path, metadata_json, output_path):
    """
    Embeds Open Badge 3.0 metadata into a PNG image using PIL.

    Args:
        image_path (str): Path to the input image (preferably PNG).
        metadata_json (str): JSON-LD string containing Open Badge 3.0 metadata.
        output_path (str): Path to save the output PNG image with embedded metadata.

    Raises:
        ValueError: If metadata_json is not a valid JSON string.
        OSError: If the input image cannot be opened or saved.
    """
    # Validate metadata_json
    try:
        json.loads(metadata_json)
    except json.JSONDecodeError:
        raise ValueError("metadata_json is not a valid JSON string")

    # Open the image
    try:
        img = Image.open(image_path)
    except OSError as e:
        raise OSError(f"Failed to open image: {e}")

    # Warn if input is not PNG
    if img.format != 'PNG':
        print("Warning: Input image is not PNG, converting to PNG.")

    # Create PngInfo object
    pnginfo = PngInfo()

    # Add iTXt chunk with Open Badge 3.0 metadata
    pnginfo.add_itxt("openbadgecredential", metadata_json, lang="", tkey="", zip=False)

    # Save the image with metadata
    try:
        img.save(output_path, "PNG", pnginfo=pnginfo)
    except OSError as e:
        raise OSError(f"Failed to save image: {e}")

Example metadata

{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json"
  ],
  "id": "https://example.org/badges/123",
  "type": ["VerifiableCredential", "OpenBadgeCredential"],
  "issuer": {
    "id": "https://example.org/issuer",
    "type": "Profile",
    "name": "Example Issuer"
  },
  "issuanceDate": "2025-06-25T09:07:00Z",
  "credentialSubject": {
    "id": "did:example:recipient123",
    "type": "Profile"
  },
  "credentialSchema": {
    "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json",
    "type": "JsonSchemaValidator2019"
  },
  "proof": {
    "type": "DataIntegrityProof",
    "created": "2025-06-25T09:07:00Z",
    "proofPurpose": "assertionMethod",
    "verificationMethod": "https://example.org/issuer#key1",
    "cryptosuite": "eddsa-2022",
    "proofValue": "z3...signature..."
  }
}

Build the metadata example

import json

# Global constant for credentialSchema
CREDENTIAL_SCHEMA = {
    "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json",
    "type": "JsonSchemaValidator2019"
}

def build_openbadge_metadata(
    credential_id: str,
    subject_id: str,
    issuer: dict,
    valid_from: str,
    achievement: dict,
    alignments: list[dict] = None,
    name: str = None,
    description: str = None,
    image: dict = None,
) -> str:
    """
    Builds Open Badge 3.0 metadata as a JSON-LD string, incorporating required fields and optional skills alignment.

    Args:
        credential_id (str): Unique URI for the credential (e.g., 'urn:uuid:1234').
        subject_id (str): Identifier for the recipient (e.g., DID, email URI).
        issuer (dict): Issuer profile with 'id', 'type', 'name' (e.g., {'id': 'https://example.edu/issuers/565049', 'type': 'Profile', 'name': 'Example University'}).
        valid_from (str): ISO 8601 date when the credential is valid (e.g., '2025-06-25T09:07:00Z').
        achievement (dict): Achievement details, must include 'id', 'type' (['Achievement']), 'name', 'description', 'criteria'.
        alignments (list[dict], optional): List of skill alignments, each with 'targetName', 'targetUrl', etc.
        name (str, optional): Credential name for display.
        description (str, optional): Brief credential description.
        image (dict, optional): Image metadata (e.g., {'id': 'https://example.edu/images/badge.png', 'type': 'Image'}).

    Returns:
        str: JSON-LD string representing the Open Badge 3.0 metadata.

    Example:
        issuer = {
            "id": "https://example.edu/issuers/565049",
            "type": "Profile",
            "name": "Example University"
        }
        achievement = {
            "id": "https://example.edu/achievements/123",
            "type": ["Achievement"],
            "name": "Robotics Badge",
            "description": "Awarded for building a robot",
            "criteria": {"narrative": "Build and demonstrate a robot"}
        }
        alignments = [
            {
                "targetName": "Robotics Skill",
                "targetUrl": "https://skills.example.org/robotics",
                "targetType": "ceasn:Competency"
            }
        ]
        metadata = build_openbadge_metadata(
            credential_id="urn:uuid:1234",
            subject_id="did:example:recipient",
            issuer=issuer,
            valid_from="2025-06-25T09:07:00Z",
            achievement=achievement,
            alignments=alignments,
            name="Robotics Credential"
        )
    """
    # Set default type for the credential
    credential_type = ["VerifiableCredential", "OpenBadgeCredential"]

    # Construct credentialSubject
    credential_subject = {
        "type": ["AchievementSubject"],
        "id": subject_id,
        "achievement": {
            **achievement,
            "alignment": alignments if alignments else []
        }
    }

    # Construct the credential
    credential = {
        "@context": [
            "https://www.w3.org/ns/auth/v2",
            "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.6.json"
        ],
        "id": credential_id,
        "type": credential_type,
        "issuer": issuer,
        "validFrom": valid_from,
        "credentialSubject": credential_subject,
        "credentialSchema": [CREDENTIAL_SCHEMA]
    }

    # Add optional fields if provided
    if name:
        credential["name"] = name
    if description:
        credential["description"] = description
    if image:
        credential["image"] = image

    # Serialize to JSON-LD string
    return json.dumps(credential, indent=2)

Sign up or log in to comment