The diagram path now follows a documented standard procedure (lookup the closest Oracle Architecture Center reference → confirm components → author absolute_layout → spec validator → render → visually verify) and ships persistent guardrails so layout regressions can't recur. Persistent procedure changes (apply to all users, all sessions): - tools/diagram_spec_validator.py — geometry checks (CONTAINER_TOO_THIN, CONTAINER_PADDING_VIOLATION, LABEL_OVERFLOW_PARENT) run BEFORE either renderer (drawio + PPTX). Catches the subnet-collapse / label-overflow bugs that the post-render drawio validator missed. - tools/oci_diagram_gen.py + tools/oci_pptx_diagram_gen.py — call the spec validator before emitting any output. Adds mysql / mysql_heatwave type aliases. - tools/archcenter_pattern_lookup.py — scores against cached page descriptions (not just the 1-line summary), supports --queries for multi-fragment composition, and applies synonym expansion via kb/architecture-center/synonyms.yaml so "LB HA cross AD" matches "load balancer high availability availability domain". - kb/architecture-center/synonyms.yaml — canonical synonym table (load balancer, autonomous database, data guard, …) used by the lookup scorer. KB enrichment: - tools/archcenter_description_fetcher.py + 121 cached _description.md under kb/diagram/assets/archcenter-refs/<slug>/. Removes the runtime dependency on docs.oracle.com when authoring specs and feeds the pattern-lookup scorer. - 110+ cached .drawio / .svg / .png references for offline reuse, plus the OCI Toolkit v24.2 import (kb/diagram/assets/oci-toolkit-drawio). Documentation: - docs/skill/output-formats.md — new "Standard diagram-generation procedure (MANDATORY)" + geometry rules + the new validator entry. - SKILL.md option 2 — references the mandatory procedure. - README.md — describes the spec validator, archcenter_pattern_lookup and description fetcher, and updates the KB-health table. Tooling that backs the procedure (cumulative across recent sessions): tools/archcenter_case_runner.py, archcenter_batch_driver.py, archcenter_zip_downloader.py, drawio_visual_validator.py, drawio_fidelity_eval.py, harvest_drawio_icon.py, import_oci_library.py, oci_pptx_diagram_gen.py, oci_pptx_render.py, refresh_pptx_icon_index.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
170 lines
7.0 KiB
Python
170 lines
7.0 KiB
Python
import sys
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
TOOLS_DIR = PROJECT_ROOT / "tools"
|
|
|
|
if str(TOOLS_DIR) not in sys.path:
|
|
sys.path.insert(0, str(TOOLS_DIR))
|
|
|
|
from oci_deck_gen import OCIDeckGenerator
|
|
from oci_pptx_diagram_gen import NativePPTXDiagramRenderer
|
|
|
|
|
|
def test_native_pptx_architecture_slide_renders_into_saved_deck(tmp_path):
|
|
spec = {
|
|
"metadata": {
|
|
"customer": "TestCo",
|
|
"project": "Native Diagram",
|
|
"subtitle": "Smoke test",
|
|
},
|
|
"summary": {
|
|
"why": "Test native rendering",
|
|
"current_state": ["Legacy DB"],
|
|
"target_state": "OCI target",
|
|
"timeline": "Q2",
|
|
},
|
|
"architecture": {
|
|
"description": "Two-region native PPTX test",
|
|
"visual": {
|
|
"render_mode": "native_pptx",
|
|
"on_prem": {"name": "On-Premises", "connection": "FastConnect"},
|
|
"regions": [
|
|
{
|
|
"name": "Ashburn",
|
|
"label": "PRIMARY",
|
|
"availability_domains": [
|
|
{
|
|
"name": "Availability Domain 1",
|
|
"vcn": {
|
|
"name": "VCN",
|
|
"cidr": "10.10.0.0/16",
|
|
"subnets": [
|
|
{
|
|
"name": "App Subnet",
|
|
"cidr": "10.10.10.0/24",
|
|
"services": [
|
|
{"id": "lb", "name": "Load Balancer", "type": "load_balancer"},
|
|
{"id": "vm1", "name": "Virtual Machine", "type": "virtual_machine"},
|
|
{"id": "sgw", "name": "Service Gateway", "type": "service_gateway"},
|
|
],
|
|
},
|
|
{
|
|
"name": "Data Subnet",
|
|
"cidr": "10.10.20.0/24",
|
|
"services": [
|
|
{"id": "db", "name": "ADB-D", "type": "adb_d"},
|
|
{"id": "safe", "name": "Data Safe", "type": "data_safe"},
|
|
{"id": "obj", "name": "Object Storage", "type": "object_storage"},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
"name": "Availability Domain 2",
|
|
"vcn": {
|
|
"name": "VCN",
|
|
"cidr": "10.10.0.0/16",
|
|
"subnets": [
|
|
{
|
|
"name": "Standby Subnet",
|
|
"cidr": "10.10.30.0/24",
|
|
"services": [
|
|
{"id": "dg", "name": "Data Guard", "type": "data_guard"},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
"name": "Montreal",
|
|
"label": "DR STANDBY",
|
|
"services": [
|
|
{"id": "drdb", "name": "ADB-D", "type": "adb_d"},
|
|
{"id": "drdg", "name": "Data Guard", "type": "data_guard"},
|
|
],
|
|
"details": "Cross-region DR",
|
|
},
|
|
],
|
|
"connections": [
|
|
{"from": "lb", "to": "vm1", "label": "HTTPS"},
|
|
{"from": "vm1", "to": "db", "label": "SQL"},
|
|
{"from": "db", "to": "dg", "label": "ADG"},
|
|
{"from": "db", "to": "drdb", "label": "DR", "style": "dashed"},
|
|
],
|
|
"security_footer": "OCI-native smoke test footer",
|
|
},
|
|
},
|
|
}
|
|
|
|
output = tmp_path / "native-smoke-test.pptx"
|
|
gen = OCIDeckGenerator.from_spec(spec)
|
|
gen.save(str(output))
|
|
|
|
assert output.is_file()
|
|
with zipfile.ZipFile(output) as archive:
|
|
slide_xml = b"".join(
|
|
archive.read(name)
|
|
for name in archive.namelist()
|
|
if name.startswith("ppt/slides/slide") and name.endswith(".xml")
|
|
).decode("utf-8", errors="ignore")
|
|
rels_xml = b"".join(
|
|
archive.read(name)
|
|
for name in archive.namelist()
|
|
if name.startswith("ppt/slides/_rels/slide") and name.endswith(".rels")
|
|
).decode("utf-8", errors="ignore")
|
|
media_files = [
|
|
name for name in archive.namelist()
|
|
if name.startswith("ppt/media/")
|
|
]
|
|
|
|
assert "Availability Domain 1" in slide_xml
|
|
assert "FastConnect" in slide_xml
|
|
assert "OCI-native smoke test footer" in slide_xml
|
|
assert "Load" in slide_xml
|
|
assert "relationships/image" in rels_xml
|
|
assert media_files
|
|
|
|
|
|
def test_native_pptx_icon_resolution_accepts_common_aliases():
|
|
renderer = NativePPTXDiagramRenderer()
|
|
|
|
compute_ref = renderer._resolve_icon_ref(
|
|
{"name": "Virtual Machine app tier", "type": "compute"}
|
|
)
|
|
lb_ref = renderer._resolve_icon_ref(
|
|
{"name": "Ingress", "type": "public_load_balancer"}
|
|
)
|
|
storage_ref = renderer._resolve_icon_ref(
|
|
{"name": "Object Storage (DR)", "type": "storage"}
|
|
)
|
|
|
|
assert compute_ref is not None
|
|
assert compute_ref["display_number"] in {29, 30, 32, 34}
|
|
assert lb_ref is not None
|
|
assert "load" in lb_ref["text"].lower()
|
|
assert storage_ref is not None
|
|
assert "object" in storage_ref["text"].lower()
|
|
|
|
|
|
def test_native_pptx_icon_resolution_prefers_compact_refs_for_problem_icons():
|
|
renderer = NativePPTXDiagramRenderer()
|
|
|
|
api_ref = renderer._resolve_icon_ref({"name": "OCI API Gateway", "type": "api_gateway"})
|
|
notify_ref = renderer._resolve_icon_ref({"name": "Notifications", "type": "notifications"})
|
|
oke_ref = renderer._resolve_icon_ref({"name": "OKE cluster", "type": "oke"})
|
|
vpn_ref = renderer._resolve_icon_ref({"name": "Site-to-Site VPN", "type": "vpn"})
|
|
|
|
assert api_ref is not None
|
|
assert notify_ref is not None
|
|
assert oke_ref is not None
|
|
assert vpn_ref is not None
|
|
assert int(api_ref["bbox"]["cx"]) < 2_500_000
|
|
assert int(notify_ref["bbox"]["cx"]) < 2_500_000
|
|
assert int(oke_ref["bbox"]["cx"]) < 2_500_000
|