Files
oci-deal-accelerator/tools/oci_output.py
2026-03-18 18:03:44 -03:00

172 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""
OCI Deal Accelerator — Output Orchestrator
Routes output generation based on the architect's format selection.
Supports: deck (default), deck+drawio, deck+doc, deck+xlsx, deck+pdf,
full, pdf, doc only.
Usage:
python oci_output.py --spec proposal.yaml --format deck --output-dir ./output
python oci_output.py --spec proposal.yaml --format full --output-dir ./output
"""
import yaml
import argparse
import os
import sys
from pathlib import Path
# Output format definitions
FORMATS = {
"deck": ["pptx"],
"deck+drawio": ["pptx", "drawio"],
"deck+doc": ["pptx", "docx"],
"deck+xlsx": ["pptx", "xlsx"],
"deck+pdf": ["pptx", "pdf"],
"full": ["pptx", "drawio", "docx", "xlsx", "pdf"],
"doc": ["docx"],
"pdf": ["pdf"],
}
GENERATORS = {
"pptx": {
"module": "oci_deck_gen",
"class": "OCIDeckGenerator",
"method": "from_spec",
"description": "Slide deck (.pptx)",
},
"drawio": {
"module": "oci_diagram_gen",
"class": "OCIDiagramGenerator",
"method": "from_spec",
"description": "Architecture diagram (.drawio)",
},
"docx": {
"module": "oci_doc_gen",
"class": "OCIDocGenerator",
"method": "from_spec",
"description": "Technical document (.docx)",
"optional": True,
},
"xlsx": {
"module": "oci_cost_gen",
"class": "OCICostGenerator",
"method": "from_spec",
"description": "Cost spreadsheet (.xlsx)",
"optional": True,
},
"pdf": {
"module": "oci_pdf_gen",
"class": "OCIPDFGenerator",
"method": "from_spec",
"description": "Customer-facing PDF (.pdf)",
},
}
def resolve_format(format_str: str) -> list:
"""Resolve a format string to a list of output types."""
normalized = format_str.lower().strip().replace(" ", "")
# Handle common variations
for key in FORMATS:
if normalized == key.replace("+", "").replace(" ", ""):
return FORMATS[key]
if normalized in FORMATS:
return FORMATS[normalized]
# Default to deck
print(f"Warning: Unknown format '{format_str}', defaulting to 'deck'")
return FORMATS["deck"]
def generate_output(spec: dict, output_type: str, output_dir: str,
base_name: str) -> str | None:
"""Generate a single output type. Returns the output path or None."""
gen_config = GENERATORS.get(output_type)
if not gen_config:
print(f" Skip: No generator for .{output_type}")
return None
output_path = os.path.join(output_dir, f"{base_name}.{output_type}")
try:
# Dynamic import
module = __import__(gen_config["module"])
gen_class = getattr(module, gen_config["class"])
factory = getattr(gen_class, gen_config["method"])
gen = factory(spec)
gen.save(output_path)
print(f" Generated: {output_path}")
return output_path
except ImportError:
if gen_config.get("optional"):
print(f" Skip: {gen_config['description']} "
f"(generator not available)")
return None
raise
except Exception as e:
print(f" Error generating {gen_config['description']}: {e}")
return None
def orchestrate(spec_path: str, format_str: str = "deck",
output_dir: str = ".", base_name: str = None) -> list:
"""Main orchestration: load spec, generate all requested outputs."""
with open(spec_path, 'r') as f:
spec = yaml.safe_load(f)
if not base_name:
base_name = Path(spec_path).stem
os.makedirs(output_dir, exist_ok=True)
output_types = resolve_format(format_str)
print(f"Output format: {format_str}")
print(f"Generating: {', '.join(f'.{t}' for t in output_types)}")
print(f"Output directory: {output_dir}")
print()
results = []
for output_type in output_types:
result = generate_output(spec, output_type, output_dir, base_name)
if result:
results.append(result)
print(f"\nDone. Generated {len(results)} file(s).")
return results
def main():
parser = argparse.ArgumentParser(
description="OCI Deal Accelerator — Output Orchestrator"
)
parser.add_argument(
"--spec", required=True,
help="Path to YAML spec file",
)
parser.add_argument(
"--format", default="deck",
choices=list(FORMATS.keys()),
help="Output format selection (default: deck)",
)
parser.add_argument(
"--output-dir", default=".",
help="Output directory (default: current directory)",
)
parser.add_argument(
"--name", default=None,
help="Base name for output files (default: spec filename)",
)
args = parser.parse_args()
# Add tools/ to path so generators can be imported
tools_dir = os.path.dirname(os.path.abspath(__file__))
if tools_dir not in sys.path:
sys.path.insert(0, tools_dir)
orchestrate(args.spec, args.format, args.output_dir, args.name)
if __name__ == "__main__":
main()