- New tools/oci_bom_gen.py: generates .xlsx BOM from YAML spec with
Oracle Redwood styling, Excel formulas, and Oracle disclaimer.
Supports standard BOM (--output) and AppCA import format (--appca)
- New tools/refresh_sku_catalog.py: refreshes SKU catalog prices from
Oracle's public pricing API (apexapps.oracle.com)
- New kb/pricing/oci-sku-catalog.yaml: ~160 OCI SKUs across 14
categories, validated against Oracle API
- New templates/bom-spec.yaml: input spec template for BOM generation
- Add SA TOOLS section to SKILL.md (options 13-14)
- Register BOM generator in oci_output.py
180 lines
5.2 KiB
Python
180 lines
5.2 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"],
|
|
"bom": ["bom"],
|
|
}
|
|
|
|
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)",
|
|
},
|
|
"bom": {
|
|
"module": "oci_bom_gen",
|
|
"class": "OCIBomGenerator",
|
|
"method": "from_spec",
|
|
"description": "Bill of Materials (.xlsx)",
|
|
"optional": True,
|
|
},
|
|
}
|
|
|
|
|
|
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()
|