
Infographic Generator
- 38 installs
- 5 repo stars
- Updated June 18, 2026
- drshailesh88/integrated_content_os
Generates publication-grade medical infographics from JSON data via a Python CLI, defaulting to 1080x1350 Instagram size with mesh gradients and branded footers.
About
Renders infographic images from structured JSON across six templates using a Python CLI script. A developer uses it to produce branded medical infographics for social posting after preparing the copy.
- CLI takes a template plus JSON data and writes a PNG
- Templates include hero, dense, comparison, myth, process, checklist
Infographic Generator by the numbers
- 38 all-time installs (skills.sh)
- Ranked #935 of 1,337 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/drshailesh88/integrated_content_os --skill infographic-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 38 |
|---|---|
| repo stars | ★ 5 |
| Last updated | June 18, 2026 |
| Repository | drshailesh88/integrated_content_os ↗ |
What it does
Generates publication-grade medical infographics from JSON data via a Python CLI, defaulting to 1080x1350 Instagram size with mesh gradients and branded footers.
Files
Infographic Generator
Generate publication-grade infographics that match your carousel visual quality. Uses mesh gradients, bold typography, icons, and branded footers.
World-Class Templates
| Template | Use Case | Visual Style |
|---|---|---|
infographic-hero | Single key stat | Giant gradient stat badge, icon, branded footer |
infographic-dense | Multi-section content | Grid of styled cards with icons |
infographic-comparison | Drug vs drug, treatment options | Split layout with contrast colors |
infographic-myth | Debunking misconceptions | Red/Green split with icons |
infographic-process | Workflows, algorithms | Numbered steps with connectors |
infographic-checklist | Patient prep, guides | Styled checkbox items |
Quick Start
Single Infographic
# Hero stat infographic
python skills/cardiology/infographic-generator/scripts/infographic_cli.py \
--template infographic-hero \
--data '{"stat":"26%","label":"Mortality Reduction","context":"HR 0.74 (95% CI 0.65-0.85)","source":"PARADIGM-HF","icon":"chart-down","tag":"CLINICAL TRIAL"}' \
--output outputs/hero-paradigm.png
# Dense multi-section
python skills/cardiology/infographic-generator/scripts/infographic_cli.py \
--template infographic-dense \
--data '{"tag":"PATIENT GUIDE","title":"GLP-1 Roll-Off","icon":"pill","sections":[{"title":"Who this is for","bullets":["Stable HF patients","No recent decompensation"],"icon":"people"},{"title":"Red flags","bullets":["Weight gain >2kg/week","New edema"],"icon":"warning","accent":"danger"}],"callout":{"label":"Key","text":"Monitor weekly during taper"}}' \
--output outputs/dense-glp1.png
# Comparison
python skills/cardiology/infographic-generator/scripts/infographic_cli.py \
--template infographic-comparison \
--data '{"tag":"TREATMENT CHOICE","title":"ACE-I vs ARB","left":{"label":"ACE Inhibitors","stat":"22%","statLabel":"Mortality Reduction","icon":"pill","bullets":["First-line","More cough"],"theme":"primary"},"right":{"label":"ARBs","stat":"18%","statLabel":"Mortality Reduction","icon":"shield","bullets":["ACE-I intolerant","Better tolerated"],"theme":"accent"}}' \
--output outputs/comparison-acei-arb.png
# Myth buster
python skills/cardiology/infographic-generator/scripts/infographic_cli.py \
--template infographic-myth \
--data '{"tag":"MYTH BUSTED","title":"Statins cause muscle damage","myth":{"text":"Taking statins will definitely give you muscle pain"},"truth":{"text":"Only 5-10% experience symptoms, most can continue therapy"},"evidence":"Meta-analysis of 19 RCTs","source":"Lancet 2022"}' \
--output outputs/myth-statins.png
# Process flow
python skills/cardiology/infographic-generator/scripts/infographic_cli.py \
--template infographic-process \
--data '{"tag":"ALGORITHM","title":"Starting SGLT2 Inhibitors","steps":[{"title":"Screen","description":"Confirm HFrEF, check eGFR","icon":"magnify"},{"title":"Initiate","description":"Start at recommended dose","icon":"pill"},{"title":"Monitor","description":"Check creatinine at 1-2 weeks","icon":"chart-up"}],"note":"eGFR ≥20 for most agents"}' \
--output outputs/process-sglt2.png
# Checklist
python skills/cardiology/infographic-generator/scripts/infographic_cli.py \
--template infographic-checklist \
--data '{"tag":"PATIENT CHECKLIST","title":"Before Your Stress Test","icon":"heart","categories":[{"title":"24 Hours Before","items":[{"text":"Avoid caffeine"},{"text":"Continue medications"}]},{"title":"Day of Test","items":[{"text":"Wear comfortable shoes"},{"text":"Bring medication list"}]}],"callout":{"icon":"warning","text":"Tell staff about chest pain"}}' \
--output outputs/checklist-stress.pngBatch Generation
Generate multiple infographics from a config file (perfect for content campaigns):
# From JSON config
python skills/cardiology/infographic-generator/scripts/batch_generate.py \
--config examples/batch_demo.json
# Parallel generation (faster)
python skills/cardiology/infographic-generator/scripts/batch_generate.py \
--config examples/batch_demo.json \
--parallel 4
# Validate config without generating
python skills/cardiology/infographic-generator/scripts/batch_generate.py \
--config my_config.json \
--dry-runExample batch config (examples/batch_demo.json):
[
{
"template": "infographic-hero",
"data": {
"stat": "26%",
"label": "Mortality Reduction",
"source": "PARADIGM-HF",
"icon": "chart-down",
"tag": "CLINICAL TRIAL"
},
"output": "outputs/hero-paradigm.png"
},
{
"template": "infographic-myth",
"data": {
"tag": "MYTH BUSTED",
"title": "Statins cause muscle damage",
"myth": {"text": "Taking statins will give you pain"},
"truth": {"text": "Only 5-10% experience symptoms"}
},
"output": "outputs/myth-statins.png"
}
]Batch Features:
- ✅ Validate all configs before generating
- ✅ Parallel generation (1-8 workers)
- ✅ Stop on first error (optional)
- ✅ JSON or YAML config formats
- ✅ Progress tracking
Template Data Schemas
infographic-hero
{
"stat": "26%",
"label": "Mortality Reduction",
"context": "HR 0.74, 95% CI 0.65-0.85",
"source": "PARADIGM-HF Trial",
"icon": "chart-down",
"tag": "CLINICAL TRIAL",
"theme": "primary|success|accent|dark",
"showFooter": true,
"footerName": "Dr. Shailesh Singh",
"footerHandle": "@heartdocshailesh"
}infographic-dense
{
"tag": "PATIENT GUIDE",
"title": "GLP-1 Roll-Off in Heart Patients",
"subtitle": "A practical tapering guide",
"icon": "pill",
"sections": [
{
"title": "Who this is for",
"bullets": ["Stable HF patients", "No recent decompensation"],
"icon": "people",
"accent": "teal|danger|success|accent"
}
],
"callout": { "label": "Bottom line", "text": "..." },
"footer": "Educational infographic. Not medical advice.",
"showBrandFooter": true
}infographic-comparison
{
"tag": "TREATMENT COMPARISON",
"title": "ACE-I vs ARB in HFrEF",
"left": {
"label": "ACE Inhibitors",
"stat": "22%",
"statLabel": "Mortality Reduction",
"icon": "pill",
"bullets": ["First-line therapy", "More cough"],
"theme": "primary|success|accent|danger"
},
"right": {
"label": "ARBs",
"stat": "18%",
"statLabel": "Mortality Reduction",
"icon": "shield",
"bullets": ["ACE-I intolerant", "Better tolerated"],
"theme": "accent"
},
"source": "Meta-analysis, Circulation 2022"
}infographic-myth
{
"tag": "MYTH BUSTED",
"title": "Statins cause muscle damage in everyone",
"myth": {
"text": "Taking statins will definitely give you muscle pain",
"icon": "cross"
},
"truth": {
"text": "Only 5-10% experience symptoms, most can continue",
"icon": "check"
},
"evidence": "Meta-analysis of 19 RCTs (n=71,000)",
"source": "Lancet 2022"
}infographic-process
{
"tag": "TREATMENT ALGORITHM",
"title": "Starting SGLT2 Inhibitors",
"subtitle": "Step-by-step for clinicians",
"steps": [
{ "title": "Screen", "description": "Confirm HFrEF", "icon": "magnify" },
{ "title": "Initiate", "description": "Start at dose", "icon": "pill" },
{ "title": "Monitor", "description": "Check creatinine", "icon": "chart-up" }
],
"note": "eGFR ≥20 for most agents"
}infographic-checklist
{
"tag": "PATIENT CHECKLIST",
"title": "Before Your Stress Test",
"subtitle": "Complete preparation guide",
"icon": "heart",
"categories": [
{
"title": "24 Hours Before",
"items": [
{ "text": "Avoid caffeine", "checked": false },
{ "text": "Continue medications", "checked": false }
]
}
],
"callout": { "icon": "warning", "text": "Tell staff about chest pain" }
}Available Icons
Medical: pill, heart, heart-pulse, stethoscope, syringe, blood-drop, dna, microscope, brain, lungs, bone, hospital, ambulance, doctor
Charts: chart-up, chart-down, graph
Status: check, cross, warning, stop, star, fire, lightning, target, bulb, trophy, shield, clock, magnify, books, people
Arrows: arrow-up, arrow-down, arrow-right
Visual Design System
All templates use:
- Mesh gradients (layered radials, not flat colors)
- Font weights: 900 for headlines, 300 for subtitles
- 3x+ size jumps for hierarchy
- Icon containers with styled backgrounds
- Gradient stat badges with shadows
- Branded footer with handle
Defaults
- Size: 1080x1350 (Instagram portrait, 4:5)
- Font: Helvetica/Arial
- Brand colors: Teal (#16697A), Coral (#EF5350), Success (#27AE60)
Output Location
Default: skills/cardiology/visual-design-system/outputs/infographics/
Python API
from skills.cardiology.visual_design_system.scripts.generate_infographic import generate
result = generate(
"infographic-hero",
{
"stat": "26%",
"label": "Mortality Reduction",
"source": "PARADIGM-HF",
"icon": "chart-down",
"tag": "LANDMARK TRIAL"
},
"output.png",
width=1080,
height=1350
)
if result["success"]:
print(f"Generated: {result['output']}")[
{
"template": "infographic-hero",
"data": {
"stat": "26%",
"label": "Mortality Reduction",
"context": "HR 0.74 (95% CI 0.65-0.85)",
"source": "PARADIGM-HF Trial",
"icon": "chart-down",
"tag": "LANDMARK TRIAL",
"theme": "primary"
},
"output": "outputs/infographics/batch-hero-paradigm.png"
},
{
"template": "infographic-comparison",
"data": {
"tag": "TREATMENT COMPARISON",
"title": "ACE-I vs ARB in HFrEF",
"left": {
"label": "ACE Inhibitors",
"stat": "22%",
"statLabel": "Mortality Reduction",
"icon": "pill",
"bullets": [
"First-line therapy",
"More cough (10-15%)"
],
"theme": "primary"
},
"right": {
"label": "ARBs",
"stat": "18%",
"statLabel": "Mortality Reduction",
"icon": "shield",
"bullets": [
"ACE-I intolerant",
"Better tolerated"
],
"theme": "accent"
},
"source": "Meta-analysis, Circulation 2022"
},
"output": "outputs/infographics/batch-comparison-acei.png"
},
{
"template": "infographic-myth",
"data": {
"tag": "MYTH BUSTED",
"title": "Statins cause muscle damage in everyone",
"myth": {
"text": "Taking statins will definitely give you muscle pain and weakness",
"icon": "cross"
},
"truth": {
"text": "Only 5-10% experience symptoms, most can continue therapy with monitoring",
"icon": "check"
},
"evidence": "Meta-analysis of 19 RCTs (n=71,000)",
"source": "Lancet 2022"
},
"output": "outputs/infographics/batch-myth-statins.png"
},
{
"template": "infographic-process",
"data": {
"tag": "TREATMENT ALGORITHM",
"title": "Starting SGLT2 Inhibitors in HFrEF",
"subtitle": "Step-by-step initiation guide",
"steps": [
{
"title": "Screen",
"description": "Confirm HFrEF diagnosis, check baseline eGFR",
"icon": "magnify"
},
{
"title": "Initiate",
"description": "Start at recommended dose (no titration needed)",
"icon": "pill"
},
{
"title": "Monitor",
"description": "Check creatinine and volume status at 1-2 weeks",
"icon": "chart-up"
},
{
"title": "Continue",
"description": "Maintain therapy unless contraindications develop",
"icon": "target"
}
],
"note": "eGFR ≥20 mL/min/1.73m² for most agents"
},
"output": "outputs/infographics/batch-process-sglt2.png"
},
{
"template": "infographic-dense",
"data": {
"tag": "PATIENT GUIDE",
"title": "Heart Failure Warning Signs",
"subtitle": "When to call your doctor",
"icon": "warning",
"sections": [
{
"title": "Immediate Attention",
"bullets": [
"Sudden severe shortness of breath",
"Chest pain or pressure",
"Confusion or altered mental state"
],
"icon": "ambulance",
"accent": "danger"
},
{
"title": "Call Within 24 Hours",
"bullets": [
"Weight gain >2 kg in 1 week",
"Increasing leg swelling",
"Can't sleep flat anymore"
],
"icon": "warning",
"accent": "accent"
},
{
"title": "Routine Follow-up",
"bullets": [
"Mild exercise intolerance",
"Medication side effects",
"Diet questions"
],
"icon": "clock",
"accent": "teal"
}
],
"callout": {
"label": "Remember",
"text": "Early intervention prevents hospitalization"
}
},
"output": "outputs/infographics/batch-dense-warning.png"
},
{
"template": "infographic-checklist",
"data": {
"tag": "PATIENT CHECKLIST",
"title": "Before Your Stress Test",
"subtitle": "Complete preparation guide",
"icon": "heart-pulse",
"categories": [
{
"title": "24 Hours Before",
"items": [
{"text": "Avoid caffeine (coffee, tea, chocolate)"},
{"text": "Continue all medications unless told otherwise"},
{"text": "Get good sleep (7-8 hours)"}
]
},
{
"title": "Day of Test",
"items": [
{"text": "Wear comfortable shoes and loose clothing"},
{"text": "Bring list of current medications"},
{"text": "Light meal 2-3 hours before (avoid heavy foods)"},
{"text": "Arrive 15 minutes early"}
]
},
{
"title": "What to Bring",
"items": [
{"text": "Photo ID and insurance card"},
{"text": "Medication list"},
{"text": "Previous test results if available"}
]
}
],
"callout": {
"icon": "warning",
"text": "Tell staff immediately if you experience chest pain, dizziness, or severe shortness of breath"
}
},
"output": "outputs/infographics/batch-checklist-stress.png"
}
]
Infographic Style Guide (Brand-Aligned)
These are the minimum style constraints for this system.
Layout
- Portrait 1080x1350 by default.
- Use card-based sections with consistent padding.
- Limit to 4-7 sections to avoid crowding.
- Keep primary headline short (max 2 lines).
Typography
- Use the visual-design-system font tokens (Helvetica/Arial).
- Headline: large and bold. Body: short, scannable bullets.
- Avoid dense paragraphs.
Color and Contrast
- Use brand tokens from
visual-design-system/tokens/. - Maintain high contrast between text and background.
- Use accents sparingly for hierarchy (one primary, one accent).
Visual Rhythm
- Use icons to anchor bullet lists.
- Prefer 2-column grids for paired comparisons.
- Use subtle gradients or soft background shapes only if they support readability.
Data Integrity
- Include numeric evidence (HR, CI, NNT, p-values) when relevant.
- Always add source labels when citing trials.
Infographic Template Map
Use these templates with infographic_cli.py. Keep copy short and data-specific.
Satori Templates (React -> SVG -> PNG)
stat-card
Use: Single metric highlight Fields: value, label, sublabel (optional), source (optional)
{"value":"26%","label":"Mortality reduction","sublabel":"HR 0.74 (95% CI 0.65-0.85)","source":"PARADIGM-HF"}comparison
Use: Two-column comparison Fields: title, left{value,label}, right{value,label}, metric (optional), source (optional)
{"title":"Treatment vs Control","left":{"value":"11.4%","label":"Dapagliflozin"},"right":{"value":"15.6%","label":"Placebo"},"metric":"CV death or HF hospitalization","source":"DAPA-HF"}process-flow
Use: Step-by-step pathway Fields: title, steps[{title,description,number(optional)}]
{"title":"HFrEF Treatment Algorithm","steps":[{"title":"Diagnose","description":"EF <= 40%"},{"title":"Initiate","description":"Start foundational therapy"},{"title":"Optimize","description":"Titrate to target"}]}trial-summary
Use: Trial result card Fields: trialName, population, intervention, primaryEndpoint, result{hr,ci,pValue}, nnt(optional)
{"trialName":"DAPA-HF","population":"HFrEF patients","intervention":"Dapagliflozin 10mg","primaryEndpoint":"CV death or HF hospitalization","result":{"hr":0.74,"ci":"0.65-0.85","pValue":"<0.001"},"nnt":21}key-finding
Use: Single key takeaway Fields: finding, icon(optional), context(optional), evidence(optional)
{"finding":"SGLT2 inhibitors reduce HF hospitalization by 30%","icon":"arrow-down","context":"Meta-analysis of 5 major trials","evidence":"Class I, Level A"}infographic-dense
Use: Information-dense, multi-section infographic Fields: tag, title, subtitle, sections[{title, bullets[], accent(optional)}], callout{label,text}, footer
{
"tag": "PATIENT GUIDE",
"title": "GLP-1 Roll-Off in Heart Patients",
"subtitle": "A practical tapering infographic",
"sections": [
{"title": "Who this is for", "bullets": ["Stable HF patients on GLP-1", "No active ischemia or decompensation"]},
{"title": "Why consider roll-off", "bullets": ["GI intolerance or weight plateau", "Cost/coverage issues", "Need to reassess baseline symptoms"]},
{"title": "Taper plan (6-8 weeks)", "bullets": ["Reduce dose stepwise every 2 weeks", "Pause if HF symptoms worsen", "Maintain nutrition/activity support"]},
{"title": "Monitor weekly", "bullets": ["Weight, edema, BP", "Dyspnea/exertional capacity", "Glucose trends if diabetic"]},
{"title": "Red flags", "bullets": ["Rapid weight gain", "Orthopnea or PND", "Resting HR >110 or new arrhythmia"], "accent": "danger"},
{"title": "Follow-up", "bullets": ["Clinic or tele-visit at 2-4 weeks", "Adjust diuretics/RAASi if needed"]}
],
"callout": {"label": "Bottom line", "text": "Taper slowly, monitor symptoms, and re-titrate HF therapy as needed."},
"footer": "Educational infographic. Not medical advice."
}SVG Templates (Structured SVGs)
Use when you need multi-section layouts or complex visuals. Render via skills/cardiology/visual-design-system/svglue_templates/template_renderer.py.
Templates available:
trial_results.svgdrug_mechanism.svgpatient_stats.svgbefore_after.svgrisk_factors.svg
Tip: Start with Satori templates for speed; move to SVG templates when you need a denser layout or diagram.
#!/usr/bin/env python3
"""
Batch Infographic Generator
Generate multiple infographics from a batch configuration file.
Useful for creating complete content sets for articles, social media campaigns,
or systematic documentation.
Usage:
# From JSON file
python batch_generate.py --config batch_config.json
# From YAML file
python batch_generate.py --config batch_config.yaml --parallel 4
# From inline JSON
python batch_generate.py --json '[{"template": "infographic-hero", "data": {...}, "output": "hero.png"}]'
Example Config (JSON):
[
{
"template": "infographic-hero",
"data": {
"stat": "26%",
"label": "Mortality Reduction",
"source": "PARADIGM-HF",
"icon": "chart-down",
"tag": "CLINICAL TRIAL"
},
"output": "outputs/hero-paradigm.png"
},
{
"template": "infographic-comparison",
"data": {
"tag": "TREATMENT CHOICE",
"title": "ACE-I vs ARB",
"left": {...},
"right": {...}
},
"output": "outputs/comparison-acei.png"
}
]
Example Config (YAML):
- template: infographic-hero
data:
stat: "26%"
label: "Mortality Reduction"
source: "PARADIGM-HF"
output: "outputs/hero.png"
- template: infographic-myth
data:
tag: "MYTH BUSTED"
title: "Statins cause muscle damage"
myth:
text: "Taking statins will give you muscle pain"
truth:
text: "Only 5-10% experience symptoms"
output: "outputs/myth-statins.png"
"""
import argparse
import json
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from typing import Any, Dict, List
import importlib.util
# Import the generator
ROOT = Path(__file__).resolve().parents[1]
GENERATOR_PATH = ROOT.parent / "visual-design-system/scripts/generate_infographic.py"
def load_generator():
"""Load the infographic generator module."""
if not GENERATOR_PATH.exists():
raise FileNotFoundError(f"Missing generator: {GENERATOR_PATH}")
spec = importlib.util.spec_from_file_location("generate_infographic", GENERATOR_PATH)
module = importlib.util.module_from_spec(spec)
if spec.loader is None:
raise RuntimeError("Failed to load generator module")
spec.loader.exec_module(module)
return module
def load_config(config_path: Path) -> List[Dict[str, Any]]:
"""
Load batch configuration from JSON or YAML file.
Args:
config_path: Path to config file (.json or .yaml/.yml)
Returns:
List of infographic configs
"""
if not config_path.exists():
raise FileNotFoundError(f"Config file not found: {config_path}")
content = config_path.read_text()
if config_path.suffix == '.json':
return json.loads(content)
elif config_path.suffix in ['.yaml', '.yml']:
try:
import yaml
return yaml.safe_load(content)
except ImportError:
print("Error: PyYAML not installed. Install with: pip install pyyaml")
sys.exit(1)
else:
raise ValueError(f"Unsupported config format: {config_path.suffix}")
def validate_item(item: Dict[str, Any], index: int) -> Dict[str, str]:
"""
Validate a single batch item.
Args:
item: Batch item config
index: Item index (for error messages)
Returns:
Dict with validation errors (empty if valid)
"""
errors = {}
if 'template' not in item:
errors['template'] = f"Item {index}: Missing 'template' field"
if 'data' not in item:
errors['data'] = f"Item {index}: Missing 'data' field"
if 'output' not in item:
errors['output'] = f"Item {index}: Missing 'output' field"
return errors
def generate_one(generator, item: Dict[str, Any], index: int) -> Dict[str, Any]:
"""
Generate a single infographic.
Args:
generator: Generator module
item: Batch item config
index: Item index
Returns:
Result dict with success status
"""
template = item['template']
data = item['data']
output = item['output']
width = item.get('width', 1080)
height = item.get('height', 1350)
print(f"[{index + 1}] Generating {template} → {output}")
result = generator.generate(
template,
data,
output,
width=width,
height=height
)
result['index'] = index
result['template'] = template
result['output'] = output
return result
def generate_batch(
items: List[Dict[str, Any]],
parallel: int = 1,
stop_on_error: bool = False
) -> Dict[str, Any]:
"""
Generate multiple infographics.
Args:
items: List of batch item configs
parallel: Number of parallel workers (1 = sequential)
stop_on_error: Stop on first error if True
Returns:
Summary dict with success/failure counts
"""
generator = load_generator()
# Validate all items first
print(f"Validating {len(items)} items...")
all_errors = []
for i, item in enumerate(items):
errors = validate_item(item, i)
if errors:
all_errors.extend(errors.values())
if all_errors:
print("\nValidation errors:")
for error in all_errors:
print(f" ❌ {error}")
return {
'success': False,
'total': len(items),
'completed': 0,
'failed': len(items),
'errors': all_errors
}
# Generate infographics
print(f"\nGenerating {len(items)} infographics (parallel={parallel})...")
results = {
'success': True,
'total': len(items),
'completed': 0,
'failed': 0,
'items': []
}
if parallel == 1:
# Sequential generation
for i, item in enumerate(items):
result = generate_one(generator, item, i)
results['items'].append(result)
if result.get('success'):
results['completed'] += 1
print(f" ✅ {result['output']}")
else:
results['failed'] += 1
print(f" ❌ {result['output']}: {result.get('error')}")
if stop_on_error:
results['success'] = False
break
else:
# Parallel generation
with ThreadPoolExecutor(max_workers=parallel) as executor:
futures = {
executor.submit(generate_one, generator, item, i): i
for i, item in enumerate(items)
}
for future in as_completed(futures):
result = future.result()
results['items'].append(result)
if result.get('success'):
results['completed'] += 1
print(f" ✅ {result['output']}")
else:
results['failed'] += 1
print(f" ❌ {result['output']}: {result.get('error')}")
if stop_on_error:
results['success'] = False
executor.shutdown(wait=False, cancel_futures=True)
break
# Sort results by index
results['items'].sort(key=lambda x: x.get('index', 0))
return results
def main() -> int:
"""CLI entry point."""
parser = argparse.ArgumentParser(
description="Generate multiple infographics from a batch config",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--config',
type=Path,
help='Path to batch config file (.json or .yaml)'
)
parser.add_argument(
'--json',
help='Inline JSON config string'
)
parser.add_argument(
'--parallel',
type=int,
default=1,
help='Number of parallel workers (default: 1 = sequential)'
)
parser.add_argument(
'--stop-on-error',
action='store_true',
help='Stop on first error'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Validate config without generating'
)
args = parser.parse_args()
# Load config
if args.config and args.json:
print("Error: Specify only one of --config or --json")
return 1
if args.config:
items = load_config(args.config)
elif args.json:
items = json.loads(args.json)
else:
print("Error: Must specify --config or --json")
return 1
if not items:
print("Error: Config is empty")
return 1
# Dry run - validate only
if args.dry_run:
print(f"Validating {len(items)} items...")
all_errors = []
for i, item in enumerate(items):
errors = validate_item(item, i)
if errors:
all_errors.extend(errors.values())
if all_errors:
print("\nValidation errors:")
for error in all_errors:
print(f" ❌ {error}")
return 1
else:
print(f"✅ All {len(items)} items valid")
return 0
# Generate
results = generate_batch(
items,
parallel=args.parallel,
stop_on_error=args.stop_on_error
)
# Print summary
print("\n" + "=" * 60)
print(f"Batch Generation Summary")
print("=" * 60)
print(f"Total: {results['total']}")
print(f"Completed: {results['completed']} ✅")
print(f"Failed: {results['failed']} ❌")
print("=" * 60)
if results['failed'] > 0:
print("\nFailed items:")
for item in results['items']:
if not item.get('success'):
print(f" ❌ {item['template']} → {item['output']}")
print(f" {item.get('error')}")
return 0 if results['success'] and results['failed'] == 0 else 1
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env python3
"""
Infographic CLI wrapper.
Defaults to 1080x1350 and routes generation through the visual-design-system
Satori pipeline.
"""
import argparse
import json
import sys
import importlib.util
from pathlib import Path
from typing import Optional
ROOT = Path(__file__).resolve().parents[4]
GENERATOR_PATH = ROOT / "skills/cardiology/visual-design-system/scripts/generate_infographic.py"
DEFAULT_OUT_DIR = ROOT / "skills/cardiology/visual-design-system/outputs/infographics"
def load_generator():
if not GENERATOR_PATH.exists():
raise FileNotFoundError(f"Missing generator script: {GENERATOR_PATH}")
spec = importlib.util.spec_from_file_location("generate_infographic", GENERATOR_PATH)
module = importlib.util.module_from_spec(spec)
if spec.loader is None:
raise RuntimeError("Failed to load infographic generator module")
spec.loader.exec_module(module)
return module
def read_data(args):
if args.data and args.data_file:
raise ValueError("Use only one of --data or --data-file")
if args.data_file:
data_path = Path(args.data_file)
return json.loads(data_path.read_text())
if args.data:
return json.loads(args.data)
raise ValueError("Missing infographic data. Provide --data or --data-file")
def resolve_output(path_value: Optional[str], template: str) -> Path:
if path_value:
return Path(path_value)
DEFAULT_OUT_DIR.mkdir(parents=True, exist_ok=True)
return DEFAULT_OUT_DIR / f"{template}.png"
def main() -> int:
parser = argparse.ArgumentParser(
description="Generate infographic outputs using the visual-design-system."
)
parser.add_argument("--template", help="Template name (stat-card, comparison, process-flow, trial-summary, key-finding)")
parser.add_argument("--data", help="JSON data string")
parser.add_argument("--data-file", help="Path to JSON file")
parser.add_argument("--output", help="Output PNG path")
parser.add_argument("--width", type=int, default=1080)
parser.add_argument("--height", type=int, default=1350)
parser.add_argument("--list", action="store_true", help="List available templates")
args = parser.parse_args()
module = load_generator()
if args.list:
templates = module.list_templates()
print("Available templates:")
for name in templates:
print(f"- {name}")
return 0
if not args.template:
print("Error: --template is required (or use --list)", file=sys.stderr)
return 1
try:
data = read_data(args)
except ValueError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
output_path = resolve_output(args.output, args.template)
result = module.generate(
args.template,
data,
str(output_path),
width=args.width,
height=args.height,
)
if result.get("success"):
print(f"Generated: {result.get('output')}")
return 0
print(f"Error: {result.get('error')}", file=sys.stderr)
if result.get("stdout"):
print(result.get("stdout"), file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())