Add stage completion certificates and badges system (Priority 3 #2)
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
Some checks failed
Cross-Node Transaction Testing / transaction-test (push) Has been cancelled
Deploy to Testnet / deploy-testnet (push) Has been cancelled
Multi-Node Stress Testing / stress-test (push) Has been cancelled
Node Failover Simulation / failover-test (push) Has been cancelled
- Created generate_certificates.py: - Generates shields.io markdown badges for each completed stage - Creates HTML certificates with styled formatting - Generates summary certificate for completing all stages - Produces Markdown summary with all badges - Updated master_training_launcher.sh: - Added BADGE_DIR and HTML_CERT_DIR variables - Enhanced view_certificates() to display badges and HTML cert paths - Added option to start HTTP server for viewing HTML certificates - Shows badges summary in certificate viewer - Generated badges and HTML certificates for stages 1-10
This commit is contained in:
331
scripts/training/generate_certificates.py
Normal file
331
scripts/training/generate_certificates.py
Normal file
@@ -0,0 +1,331 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Certificate and Badge Generator for AITBC Training
|
||||
|
||||
Generates:
|
||||
1. Markdown badges (shields.io style) for completed stages
|
||||
2. HTML certificates with proper formatting
|
||||
3. Summary certificate for completing all stages
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
CERT_DIR = Path(__file__).parent / ".training_state" / "certificates"
|
||||
BADGE_DIR = Path(__file__).parent / ".training_state" / "badges"
|
||||
HTML_DIR = Path(__file__).parent / ".training_state" / "html_certificates"
|
||||
|
||||
|
||||
def ensure_dirs():
|
||||
"""Ensure output directories exist."""
|
||||
BADGE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
HTML_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def load_certificate(stage_num: int) -> dict:
|
||||
"""Load certificate JSON for a stage."""
|
||||
cert_file = CERT_DIR / f"stage{stage_num}_certificate.json"
|
||||
if not cert_file.exists():
|
||||
return None
|
||||
with open(cert_file) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def generate_markdown_badge(stage_num: int, cert_data: dict) -> str:
|
||||
"""Generate shields.io markdown badge for a stage."""
|
||||
badge_label = f"Stage {stage_num}"
|
||||
badge_message = "Completed"
|
||||
badge_color = "brightgreen"
|
||||
|
||||
# URL encode spaces
|
||||
badge_url = f"https://img.shields.io/badge/{badge_label.replace(' ', '%20')}-{badge_message.replace(' ', '%20')}-{badge_color}?style=flat-square"
|
||||
markdown = f"[]({CERT_DIR}/stage{stage_num}_certificate.json)"
|
||||
return markdown
|
||||
|
||||
|
||||
def generate_html_certificate(stage_num: int, cert_data: dict) -> str:
|
||||
"""Generate HTML certificate for a stage."""
|
||||
stage_name = cert_data.get("stage_name", f"Stage {stage_num}")
|
||||
timestamp = cert_data.get("completion_timestamp", datetime.now(timezone.utc).isoformat())
|
||||
wallet = cert_data.get("wallet_name", "Unknown")
|
||||
cert_id = cert_data.get("certificate_id", "Unknown")
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Certificate - Stage {stage_num}</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}}
|
||||
.certificate {{
|
||||
background: white;
|
||||
padding: 60px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
}}
|
||||
.header {{
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.subtitle {{
|
||||
font-size: 24px;
|
||||
color: #666;
|
||||
margin-bottom: 40px;
|
||||
}}
|
||||
.stage-name {{
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 10px;
|
||||
}}
|
||||
.details {{
|
||||
text-align: left;
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 10px;
|
||||
}}
|
||||
.detail-row {{
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
}}
|
||||
.detail-label {{
|
||||
font-weight: bold;
|
||||
color: #667eea;
|
||||
}}
|
||||
.footer {{
|
||||
margin-top: 40px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}}
|
||||
.badge {{
|
||||
display: inline-block;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
margin: 5px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="certificate">
|
||||
<div class="header">🏆 Certificate of Completion</div>
|
||||
<div class="subtitle">AITBC Agent Training Program</div>
|
||||
|
||||
<div class="stage-name">Stage {stage_num}: {stage_name}</div>
|
||||
|
||||
<div class="details">
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Certificate ID:</span> {cert_id}
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Completed:</span> {timestamp}
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Wallet:</span> {wallet}
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Training Program:</span> hermes AITBC Mastery Training
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="badge">✓ Stage Completed</span>
|
||||
<span class="badge">✓ Hands-On Training</span>
|
||||
<span class="badge">✓ Blockchain Verified</span>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
Generated on {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
return html
|
||||
|
||||
|
||||
def generate_summary_certificate(completed_stages: list) -> str:
|
||||
"""Generate summary HTML certificate for completing all stages."""
|
||||
stages_html = ""
|
||||
for stage in sorted(completed_stages):
|
||||
cert = load_certificate(stage)
|
||||
stage_name = cert.get("stage_name", f"Stage {stage}") if cert else f"Stage {stage}"
|
||||
stages_html += f' <div class="stage-badge">Stage {stage}: {stage_name}</div>\n'
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mastery Certificate - AITBC Training</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}}
|
||||
.certificate {{
|
||||
background: white;
|
||||
padding: 60px;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
max-width: 900px;
|
||||
text-align: center;
|
||||
}}
|
||||
.header {{
|
||||
font-size: 56px;
|
||||
font-weight: bold;
|
||||
color: #667eea;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.subtitle {{
|
||||
font-size: 28px;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.achievement {{
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
|
||||
border-radius: 10px;
|
||||
border: 3px solid #667eea;
|
||||
}}
|
||||
.stages {{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin: 30px 0;
|
||||
}}
|
||||
.stage-badge {{
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.footer {{
|
||||
margin-top: 40px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="certificate">
|
||||
<div class="header">🎓 Mastery Certificate</div>
|
||||
<div class="subtitle">AITBC Agent Training Program</div>
|
||||
|
||||
<div class="achievement">
|
||||
Congratulations! You have completed all {len(completed_stages)} training stages
|
||||
</div>
|
||||
|
||||
<div class="stages">
|
||||
{stages_html}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
Generated on {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}<br>
|
||||
Total Stages Completed: {len(completed_stages)} / 11
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
return html
|
||||
|
||||
|
||||
def generate_markdown_summary(completed_stages: list) -> str:
|
||||
"""Generate Markdown summary with all badges."""
|
||||
markdown = "# AITBC Training Certificates\n\n"
|
||||
markdown += f"Completed {len(completed_stages)} / 11 stages\n\n"
|
||||
|
||||
markdown += "## Badges\n\n"
|
||||
for stage in sorted(completed_stages):
|
||||
cert = load_certificate(stage)
|
||||
if cert:
|
||||
badge = generate_markdown_badge(stage, cert)
|
||||
markdown += f"{badge} "
|
||||
|
||||
markdown += "\n\n## Stages Completed\n\n"
|
||||
for stage in sorted(completed_stages):
|
||||
cert = load_certificate(stage)
|
||||
if cert:
|
||||
stage_name = cert.get("stage_name", f"Stage {stage}")
|
||||
timestamp = cert.get("completion_timestamp", "Unknown")
|
||||
markdown += f"- **Stage {stage}: {stage_name}** - Completed {timestamp}\n"
|
||||
|
||||
return markdown
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
ensure_dirs()
|
||||
|
||||
# Find all completed stages
|
||||
completed_stages = []
|
||||
for i in range(11): # Stages 0-10
|
||||
cert = load_certificate(i)
|
||||
if cert:
|
||||
completed_stages.append(i)
|
||||
|
||||
# Generate markdown badge
|
||||
badge_md = generate_markdown_badge(i, cert)
|
||||
badge_file = BADGE_DIR / f"stage{i}_badge.md"
|
||||
with open(badge_file, 'w') as f:
|
||||
f.write(badge_md)
|
||||
|
||||
# Generate HTML certificate
|
||||
html = generate_html_certificate(i, cert)
|
||||
html_file = HTML_DIR / f"stage{i}_certificate.html"
|
||||
with open(html_file, 'w') as f:
|
||||
f.write(html)
|
||||
|
||||
print(f"✓ Generated badge and HTML certificate for Stage {i}")
|
||||
|
||||
# Generate summary certificate if all stages completed
|
||||
if len(completed_stages) == 11:
|
||||
summary_html = generate_summary_certificate(completed_stages)
|
||||
summary_file = HTML_DIR / "mastery_certificate.html"
|
||||
with open(summary_file, 'w') as f:
|
||||
f.write(summary_html)
|
||||
print(f"✓ Generated Mastery Certificate: {summary_file}")
|
||||
|
||||
# Generate Markdown summary
|
||||
if completed_stages:
|
||||
md_summary = generate_markdown_summary(completed_stages)
|
||||
summary_md_file = BADGE_DIR / "training_summary.md"
|
||||
with open(summary_md_file, 'w') as f:
|
||||
f.write(md_summary)
|
||||
print(f"✓ Generated Markdown summary: {summary_md_file}")
|
||||
|
||||
print(f"\nTotal completed stages: {len(completed_stages)} / 11")
|
||||
print(f"Badges directory: {BADGE_DIR}")
|
||||
print(f"HTML certificates: {HTML_DIR}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -34,6 +34,8 @@ START_TIME=$(date +%s)
|
||||
PROGRESS_FILE="$SCRIPT_DIR/.training_progress"
|
||||
STATE_DIR="$SCRIPT_DIR/.training_state"
|
||||
CERT_DIR="$STATE_DIR/certificates"
|
||||
BADGE_DIR="$STATE_DIR/badges"
|
||||
HTML_CERT_DIR="$STATE_DIR/html_certificates"
|
||||
|
||||
# Skill update flag (default: disabled)
|
||||
ENABLE_SKILL_UPDATE="${ENABLE_SKILL_UPDATE:-false}"
|
||||
@@ -397,16 +399,16 @@ capture_learnings() {
|
||||
# View certificates
|
||||
view_certificates() {
|
||||
print_header "Stage Completion Certificates"
|
||||
|
||||
# Ensure CERT_DIR exists
|
||||
|
||||
# Ensure directories exist
|
||||
if [ ! -d "$CERT_DIR" ]; then
|
||||
mkdir -p "$CERT_DIR"
|
||||
fi
|
||||
|
||||
|
||||
# Collect certificate files into array
|
||||
local cert_files=()
|
||||
local cert_count=0
|
||||
|
||||
|
||||
if [ -d "$CERT_DIR" ]; then
|
||||
for cert_file in "$CERT_DIR"/stage*_certificate.json; do
|
||||
if [ -f "$cert_file" ]; then
|
||||
@@ -415,42 +417,81 @@ view_certificates() {
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
if [ $cert_count -eq 0 ]; then
|
||||
print_warning "No certificates found yet"
|
||||
echo "Complete stages to earn certificates"
|
||||
return 0
|
||||
fi
|
||||
|
||||
|
||||
echo -e "${BOLD}📜 Certificates Earned:${NC}"
|
||||
echo
|
||||
|
||||
|
||||
# Display certificates with index
|
||||
for i in "${!cert_files[@]}"; do
|
||||
local cert_file="${cert_files[$i]}"
|
||||
local stage_num=$(echo "$cert_file" | grep -o 'stage[0-10]' | grep -o '[0-10]')
|
||||
local stage_num=$(echo "$cert_file" | grep -o 'stage[0-9]*' | grep -o '[0-9]*')
|
||||
local stage_name=$(get_stage_name $stage_num)
|
||||
local timestamp=$(python3 -c "import json; print(json.load(open('$cert_file'))['completion_timestamp'])" 2>/dev/null || echo "Unknown")
|
||||
|
||||
|
||||
echo -e " ${GREEN}$(($i+1))${NC}. Stage $stage_num: $stage_name"
|
||||
echo " Completed: $timestamp"
|
||||
echo " File: $cert_file"
|
||||
|
||||
# Show badge path if exists
|
||||
local badge_file="$BADGE_DIR/stage${stage_num}_badge.md"
|
||||
if [ -f "$badge_file" ]; then
|
||||
echo " Badge: $badge_file"
|
||||
fi
|
||||
|
||||
# Show HTML cert path if exists
|
||||
local html_file="$HTML_CERT_DIR/stage${stage_num}_certificate.html"
|
||||
if [ -f "$html_file" ]; then
|
||||
echo " HTML: $html_file"
|
||||
fi
|
||||
|
||||
echo
|
||||
done
|
||||
|
||||
|
||||
echo -e "${BOLD}Total certificates: $cert_count${NC}"
|
||||
|
||||
|
||||
# Show badges summary if available
|
||||
if [ -d "$BADGE_DIR" ] && [ -f "$BADGE_DIR/training_summary.md" ]; then
|
||||
echo
|
||||
echo -e "${BOLD}🏅 Badges Summary:${NC}"
|
||||
cat "$BADGE_DIR/training_summary.md"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -n "View certificate details? Enter number [1-$cert_count] or N: "
|
||||
read -r view_choice
|
||||
|
||||
if [[ "$view_choice" =~ ^[0-10]+$ ]] && [ "$view_choice" -ge 1 ] && [ "$view_choice" -le "$cert_count" ]; then
|
||||
|
||||
if [[ "$view_choice" =~ ^[0-9]+$ ]] && [ "$view_choice" -ge 1 ] && [ "$view_choice" -le "$cert_count" ]; then
|
||||
local idx=$(($view_choice - 1))
|
||||
local cert_file="${cert_files[$idx]}"
|
||||
if [ -f "$cert_file" ]; then
|
||||
echo
|
||||
echo -e "${BOLD}Certificate Details:${NC}"
|
||||
cat "$cert_file" | python3 -m json.tool 2>/dev/null || cat "$cert_file"
|
||||
|
||||
# Offer to open HTML certificate
|
||||
local stage_num=$(echo "$cert_file" | grep -o 'stage[0-9]*' | grep -o '[0-9]*')
|
||||
local html_file="$HTML_CERT_DIR/stage${stage_num}_certificate.html"
|
||||
if [ -f "$html_file" ]; then
|
||||
echo
|
||||
echo -n "Open HTML certificate in browser? [y/N]: "
|
||||
read -r open_choice
|
||||
if [[ "$open_choice" =~ ^[Yy]$ ]]; then
|
||||
if command -v xdg-open &> /dev/null; then
|
||||
xdg-open "$html_file" &> /dev/null &
|
||||
elif command -v python3 &> /dev/null; then
|
||||
echo "Starting HTTP server for certificates..."
|
||||
cd "$HTML_CERT_DIR" && python3 -m http.server 8888 &
|
||||
echo "View certificates at: http://localhost:8888"
|
||||
echo "Press Ctrl+C to stop the server when done"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user