3 Commits

Author SHA1 Message Date
root
ff77563133 settings.local.json: extend allowlist for diagram + MCP test workflow
All checks were successful
Deploy Skill to OCI / deploy (push) Successful in 32s
Adds permission entries accumulated during recent work — the local
draw.io.exe path probes used by the fidelity validator, curl probes
against the MCP server (mcp.tech-lad.com) and the Gitea remote
(git.tech-lad.com.br) for status checks, the .githooks/pre-commit
hook so the SKILL.md sync gate runs without a prompt, and a few
read-only awk/grep/curl one-liners hit repeatedly in the SKU /
ref-arch maintenance loop.

Per-machine allowlist only — no behaviour change for other
contributors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:29:50 -03:00
root
1b4828f293 MELI Fraud: case-specific scaffolding scripts (3 chassis variants)
Three one-shot Python scripts that build the MELI Fraud As-Is /
To-Be .drawio pair by cloning an Oracle Architecture Center chassis
and overlaying MELI-specific labels, region renames, and workload
pills. Useful as worked examples of the chassis-then-overlay
transform pattern (drawio_template_transform.DrawioTemplate) that
the diagram procedure recommends:

  • build_meli_fraud_from_adbsplusadbd.py — clones the locally
    accepted ATP-S/ATP-D side-by-side reference and partitions cells
    into AS-IS / TO-BE panels by x-coordinate.
  • build_meli_fraud_from_crossregion.py — uses Oracle's
    exadata-dedicated-cross-region-dataguard ref-arch (two regions
    with DRG + RPC + Data Guard already drawn) as chassis and only
    overlays MELI-specific pills.
  • build_meli_fraud_from_templates.py — combines the in-region and
    cross-region Exadata ref-archs and clones the in-region
    topology to the right for the Montreal DR.

All three honour the standing rule: do not edit text inside Oracle
SVG stencil shapes — overlay new mxCells in whitespace instead.
Outputs land under examples/output-meli-fraud-adbd-migration/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:29:37 -03:00
root
5566a171b0 PPTX: ensure [Content_Types].xml has defaults for every media extension
Symptom: PowerPoint prompted to repair some generated decks. Cause:
when python-pptx (or our native renderer) repacks the .pptx after we
embed Oracle template assets — some of which are SVG-backed —
[Content_Types].xml can lose the Default entry for "svg" (and other
extensions present under ppt/media), which makes Office refuse to
open the file cleanly.

Fix: after writing the deck (and after any native renderer pass),
walk ppt/media, collect the actual extensions in use, and ensure
[Content_Types].xml declares a Default for each one (svg, png, jpg,
jpeg, gif, bmp, tiff, emf, wmf, ico, webp). Repack only when at
least one new Default was added — no-op fast path otherwise.

Lines up with the existing field rule: every embedded media
extension must be declared in [Content_Types].xml Defaults, or
PowerPoint flags the file as corrupt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 09:29:18 -03:00
5 changed files with 1120 additions and 1 deletions

View File

@@ -107,7 +107,95 @@
"WebFetch(domain:developers.openai.com)", "WebFetch(domain:developers.openai.com)",
"Bash(echo \"---exit:$?---\")", "Bash(echo \"---exit:$?---\")",
"Bash(date +%s)", "Bash(date +%s)",
"Read(//tmp/**)" "Read(//tmp/**)",
"Bash(.venv/bin/pip install *)",
"Bash(curl -sS -o /dev/null -w \"HTTP:%{http_code} dns:%{remote_ip} time:%{time_total}s\\\\n\" --max-time 10 https://mcp.tech-lad.com/deal-accelerator/mcp/)",
"Bash(curl -sS -o /dev/null -w \"oauth HTTP:%{http_code}\\\\n\" --max-time 10 https://mcp.tech-lad.com/oauth/authorize)",
"mcp__oci-deal-accelerator__generate_deck",
"mcp__oci-deal-accelerator__generate_business_case",
"mcp__oci-deal-accelerator__generate_bom",
"mcp__oci-deal-accelerator__generate_bom_appca",
"Bash(mkdir -p /tmp/mcp-test-outputs)",
"Bash(curl -sS -o deck-nova.pptx \"https://mcp.tech-lad.com/deal-accelerator/files/176ac90e-test-nova-retail-deck.pptx\")",
"Bash(curl -sS -o biz-nova.pptx \"https://mcp.tech-lad.com/deal-accelerator/files/42dd4da2-test-nova-retail-bizcase.pptx\")",
"Bash(curl -sS -o bom-vector.xlsx \"https://mcp.tech-lad.com/deal-accelerator/files/3868d74a-test-vector-retail-bom.xlsx\")",
"Bash(curl -sS -o bom-vector-appca.xlsx \"https://mcp.tech-lad.com/deal-accelerator/files/b34fa397-test-vector-retail-bom-appca.xlsx\")",
"Bash(curl -sS -o /tmp/mcp-test-outputs/biz-nova.pptx \"https://mcp.tech-lad.com/deal-accelerator/files/42dd4da2-test-nova-retail-bizcase.pptx\")",
"Bash(curl -sS -o /tmp/mcp-test-outputs/bom-vector.xlsx \"https://mcp.tech-lad.com/deal-accelerator/files/3868d74a-test-vector-retail-bom.xlsx\")",
"Bash(curl -sS -o /tmp/mcp-test-outputs/bom-vector-appca.xlsx \"https://mcp.tech-lad.com/deal-accelerator/files/b34fa397-test-vector-retail-bom-appca.xlsx\")",
"Bash(curl -sS -o /tmp/mcp-test-outputs/deck-vector.pptx \"https://mcp.tech-lad.com/deal-accelerator/files/19c5330e-test-vector-bank-deck.pptx\")",
"Bash(curl -sS -o /tmp/mcp-test-outputs/biz-digital.pptx \"https://mcp.tech-lad.com/deal-accelerator/files/16b90f10-test-vector-digital-bizcase.pptx\")",
"Bash(curl -sS -o /dev/null -w \"git remote HTTP: %{http_code} time: %{time_total}s\\\\n\" --max-time 15 https://git.tech-lad.com.br/)",
"Bash(awk '/- sku:/{sku=$NF} /product:/{print sku\" - \"$0}' kb/pricing/oci-sku-catalog.yaml)",
"Bash(awk '{print $3}')",
"Bash(awk '/- sku: B110627|- sku: B110628|- sku: B110629|- sku: B90453/{print;f=1;next} f&&/product:/{print;f=0}' kb/pricing/oci-sku-catalog.yaml)",
"Bash(awk '/- sku:/{sku=$NF} /product:.*Exadata|product:.*ECPU|product:.*Autonomous/{print sku\" - \"$0}' kb/pricing/oci-sku-catalog.yaml)",
"Bash(awk '/- sku: B90453/,/product:/' kb/pricing/oci-sku-catalog.yaml)",
"Bash(awk '/- sku:/{sku=$NF} /product:.*ADB|product:.*NoSQL|product:.*HeatWave|product:.*MySQL/{print sku\" - \"$0}' kb/pricing/oci-sku-catalog.yaml)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" --help)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 10 -o \"tmp/compare-iter/iter1.png\" \"tmp/compare-iter/iter1.drawio\")",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop -s 2 -o tmp/compare-iter/iter4_crop.png tmp/compare-iter/iter4.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop -s 2 -o tmp/compare-iter/REFERENCE.png examples/output-meli-fraud-adbd-migration/adbsplusadbd.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 30 --crop -s 3 -o tmp/compare-iter/iter6_hires.png tmp/compare-iter/iter6.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop -s 3 -o tmp/compare-iter/to_be_v1_hires.png tmp/compare-iter/to_be_v1.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 40 -s 4 -o tmp/compare-iter/to_be_v1_4x.png tmp/compare-iter/to_be_v1.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png --crop -b 20 --width 3000 -o tmp/compare-iter/to_be_v1_w3000.png tmp/compare-iter/to_be_v1.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop --width 4500 -o tmp/compare-iter/to_be_v1_w4500.png tmp/compare-iter/to_be_v1.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 30 --crop --width 5000 -o tmp/compare-iter/as_is_final.png examples/output-meli-fraud-adbd-migration/fraud-as-is-adbs.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 30 --crop --width 5000 -o tmp/compare-iter/to_be_final.png examples/output-meli-fraud-adbd-migration/fraud-to-be-adbd.drawio)",
"Bash(curl -sL \"https://docs.oracle.com/en/solutions/deploy-autonomous-database-db-at-azure/index.html\" -o tmp/oracle-ref-archs/adb-azure.html)",
"Bash(curl -sL -w 'HTTP:%{http_code} size:%{size_download}\\\\n' \"https://docs.oracle.com/en/solutions/deploy-autonomous-database-db-at-azure/index.html\" -o /tmp/x.html)",
"Bash(curl -sL -w 'HTTP:%{http_code} size:%{size_download}\\\\n' \"https://docs.oracle.com/en/solutions/deploy-autonomous-database-db-at-azure/img/autonomous-database-db-azure-diagram-oracle.zip\" -o adb-azure.zip)",
"Bash(unzip -l adb-azure.zip)",
"Bash(unzip -o -q adb-azure.zip)",
"Bash(mv oracle-adb-jde-exadata-arch-oracle *)",
"Bash(curl -sL \"https://docs.oracle.com/en/solutions/dataguard-exadata-dedicated-infrastructure/index.html\" -o dg-exa-dedicated.html)",
"Bash(curl -sL \"https://docs.oracle.com/en/solutions/dataguard-exadata-dedicated-infrastructure/img/exadata-dedicated-region-dataguard.zip\" -o dg-exa-region.zip)",
"Bash(curl -sL \"https://docs.oracle.com/en/solutions/dataguard-exadata-dedicated-infrastructure/img/exadata-dedicated-cross-region-dataguard.zip\" -o dg-exa-cross-region.zip)",
"Bash(unzip -o -q dg-exa-region.zip)",
"Bash(unzip -o -q dg-exa-cross-region.zip)",
"Bash('/mnt/c/Program Files/draw.io/draw.io.exe' -x -f png -b 20 --crop -o /mnt/c/Users/Diego/Documents/oci-deal-accelerator-oracle/oci-deal-accelerator/tmp/oracle-ref-archs/oracle-adb-jde-exadata-arch-oracle/oracle-adb-jde-exadata-arch.png /mnt/c/Users/Diego/Documents/oci-deal-accelerator-oracle/oci-deal-accelerator/tmp/oracle-ref-archs/oracle-adb-jde-exadata-arch-oracle/oracle-adb-jde-exadata-arch.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop -s 2 -o /mnt/c/Users/Diego/Documents/oci-deal-accelerator-oracle/oci-deal-accelerator/tmp/oracle-ref-archs/oracle-adb-jde-exadata-arch-oracle/oracle-adb-jde-exadata-arch.png /mnt/c/Users/Diego/Documents/oci-deal-accelerator-oracle/oci-deal-accelerator/tmp/oracle-ref-archs/oracle-adb-jde-exadata-arch-oracle/oracle-adb-jde-exadata-arch.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop -s 2 -o tmp/oracle-ref-archs/oracle-adb-jde-exadata-arch-oracle/rendered.png tmp/oracle-ref-archs/oracle-adb-jde-exadata-arch-oracle/oracle-adb-jde-exadata-arch.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop -s 2 -o tmp/compare-iter/fraud-as-is-template.png examples/output-meli-fraud-adbd-migration/fraud-as-is-template.drawio)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -b 20 --crop -s 2 -o tmp/compare-iter/fraud-to-be-template.png examples/output-meli-fraud-adbd-migration/fraud-to-be-template.drawio)",
"WebFetch(domain:www.bugraparlayan.com.tr)",
"WebFetch(domain:www.infolob.com)",
"Bash(curl -sS \"https://git.tech-lad.com.br/api/v1/repos/diegoecab/oci-deal-accelerator/actions/runs?limit=10\")",
"Bash(curl -sS \"https://git.tech-lad.com.br/api/v1/repos/diegoecab/oci-deal-accelerator/actions/secrets\")",
"Bash(curl -sS -o /dev/null -w \"%{http_code}\\\\n\" \"https://git.tech-lad.com.br/api/v1/repos/diegoecab/oci-deal-accelerator\")",
"Bash(curl -sS \"https://git.tech-lad.com.br/diegoecab/oci-deal-accelerator/raw/branch/main/.gitea/workflows/sku-catalog-refresh.yaml\")",
"Bash(curl -sS -o /dev/null -w \"%{http_code}\\\\n\" \"https://git.tech-lad.com.br/diegoecab/oci-deal-accelerator/raw/branch/main/.gitea/workflows/sku-catalog-refresh.yaml\")",
"Bash(curl -sS \"https://git.tech-lad.com.br/api/v1/repos/diegoecab/oci-deal-accelerator/issues?type=issues&state=open&limit=5&sort=newest\")",
"Bash(curl -sS \"https://git.tech-lad.com.br/api/v1/repos/diegoecab/oci-deal-accelerator/branches?limit=50\")",
"Bash(curl -sS \"https://git.tech-lad.com.br/api/v1/repos/diegoecab/oci-deal-accelerator\")",
"Bash(xargs -n1 grep -l '\"fidelity\"')",
"Bash(xargs '-I{}' .venv/bin/python -c ' *)",
"Bash(apt-cache show *)",
"Bash(apt list *)",
"Read(//mnt/c/Program Files/draw.io/**)",
"Read(//mnt/c/Users/Diego/AppData/Local/Programs/draw.io/**)",
"Read(//mnt/c/**)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -o /tmp/test-drawio.png examples/dataguard-exa-dedicated-in-region/out/dataguard-exa-dedicated-in-region.drawio)",
"Bash(mkdir -p /mnt/c/Users/Diego/tmp-drawio-export)",
"Bash(\"/mnt/c/Program Files/draw.io/draw.io.exe\" -x -f png -s 2 -o /mnt/c/Users/Diego/tmp-drawio-export/test.png examples/dataguard-exa-dedicated-in-region/out/dataguard-exa-dedicated-in-region.drawio)",
"Bash(mkdir -p '/mnt/c/Users/Diego/tmp-drawio-export')",
"Bash('/mnt/c/Program Files/draw.io/draw.io.exe' -x -f png -s 2 -o 'C:\\\\Users\\\\Diego\\\\tmp-drawio-export\\\\test.png' 'C:\\\\Users\\\\Diego\\\\Documents\\\\oci-deal-accelerator-oracle\\\\oci-deal-accelerator\\\\examples\\\\dataguard-exa-dedicated-in-region\\\\out\\\\dataguard-exa-dedicated-in-region.drawio')",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/asis-adb-s-local-dr.drawio --out tmp/drawio-renders/asis-v3.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-v3.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/asis-adb-s-local-dr.drawio --out tmp/drawio-renders/asis-v4.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-v4.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/asis-adb-s-local-dr.drawio --out tmp/drawio-renders/asis-v5.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-v5.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/asis-adb-s-local-dr.drawio --out tmp/drawio-renders/asis-v6.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-v6.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/asis-adb-s-local-dr.drawio --out tmp/drawio-renders/asis-v7.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-v7.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-v9.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-final.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/asis-adb-s-local-dr.drawio --out tmp/drawio-renders/asis-final2.png)",
"Bash(DRAWIO_EXE=\"/mnt/c/Program Files/draw.io/draw.io.exe\" .venv/bin/python tools/drawio_to_png.py --drawio examples/asis-tobe-adb-modernization/out/tobe-adb-d-active-dataguard.drawio --out tmp/drawio-renders/tobe-final2.png)",
"Bash(.githooks/pre-commit)"
] ]
} }
} }

View File

@@ -0,0 +1,437 @@
#!/usr/bin/env python3
"""
Build MELI Fraud As-Is and To-Be .drawio files by cloning and editing
examples/output-meli-fraud-adbd-migration/adbsplusadbd.drawio — the ADB
(ATP-S / ATP-D) reference Diego already accepted visually.
Strategy:
1. Load the source file.
2. Partition cells into AS-IS (x < 4630) and TO-BE (x >= 4630) panels;
edges are assigned to a panel based on their source/target cell.
3. For each output, delete the panel we don't need.
4. Relabel the title banner, notes, and trade-offs text for MELI.
5. Clone the ATP-S / ATP-D icon cells (single-cell stencils) to add a
second workload row (Transactional) and a Clone column (As-Is only).
6. Add a Montreal DR region panel to the right.
7. Save.
Constraint: interior labels like "OCI Ashburn" / "AD 3" / "AD 2" / "R/W Pool"
are plain mxCell values we CAN edit — so we rename AD 3 → AD-X, AD 2 → AD-Y,
adjust R/W Pool label, etc.
"""
from __future__ import annotations
import copy
import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from drawio_template_transform import DrawioTemplate # noqa: E402
SRC = Path("examples/output-meli-fraud-adbd-migration/adbsplusadbd.drawio")
OUT_DIR = Path("examples/output-meli-fraud-adbd-migration")
# Oracle palette
ORACLE_RED = "#C74634"
ORACLE_GREEN = "#00875A"
SLATE_BLUE = "#6E89AD"
AMBER = "#E8A92F"
MUTED_GREY = "#70665E"
CHARCOAL = "#312D2A"
# ---------------------------------------------------------------------------
# Panel partitioning
# ---------------------------------------------------------------------------
def partition_panels(t: DrawioTemplate, split_x: float = 4630):
"""Return (as_is_ids, to_be_ids) — both sets cover all vertex cells.
Edges are classified by their source/target positions.
"""
as_is, to_be = set(), set()
def cell_x(cid: str) -> float | None:
c = t.cells.get(cid)
if c is None:
return None
geo = c.find("mxGeometry")
if geo is None:
return None
try:
return float(geo.get("x") or 0)
except ValueError:
return None
# Vertices first: classify by absolute x (walk parents to get absolute x)
def abs_x(cid: str) -> float:
x = 0.0
cur = cid
guard = 0
while cur and cur in t.cells and guard < 30:
geo = t.cells[cur].find("mxGeometry")
if geo is not None:
try:
x += float(geo.get("x") or 0)
except ValueError:
pass
parent = t.cells[cur].get("parent") or ""
if parent in ("0", "1", ""):
break
cur = parent
guard += 1
return x
for cid, c in t.cells.items():
if cid in ("0", "1"):
continue
if c.get("edge") == "1":
continue
ax = abs_x(cid)
if ax >= split_x:
to_be.add(cid)
else:
as_is.add(cid)
# Edges follow their source/target
for cid, c in t.cells.items():
if c.get("edge") != "1":
continue
src = c.get("source")
tgt = c.get("target")
in_tobe = any(ref and ref in to_be for ref in (src, tgt))
in_asis = any(ref and ref in as_is for ref in (src, tgt))
if in_tobe and not in_asis:
to_be.add(cid)
elif in_asis and not in_tobe:
as_is.add(cid)
else:
# Ambiguous — keep with TO BE by default
to_be.add(cid)
return as_is, to_be
def delete_cells(t: DrawioTemplate, cell_ids: set[str]) -> None:
for cid in list(cell_ids):
c = t.cells.get(cid)
if c is not None:
try:
t.model_root.remove(c)
except ValueError:
pass
t.reindex()
# ---------------------------------------------------------------------------
# Text helpers — Oracle templates often store rich-HTML values
# ---------------------------------------------------------------------------
def replace_rich_label(t: DrawioTemplate, cell_id: str, plain_text: str,
color: str = CHARCOAL, bold: bool = True,
size: int = 16) -> None:
"""Replace a cell's <value> with simple HTML — keeps drawio rendering happy."""
weight = "bold" if bold else "normal"
html = (
f'<h1 style="color: {color}; text-align: left; '
f'line-height: 1.1; margin: 0; font-size: {size}px; font-weight: {weight};">'
f'{plain_text}</h1>'
)
t.cells[cell_id].set("value", html)
def add_pill(t: DrawioTemplate, text: str, x: float, y: float,
fill: str, stroke: str, font_color: str = "#FCFBFA",
w: float = 130, h: float = 22) -> str:
style = (
f"rounded=1;whiteSpace=wrap;html=1;fillColor={fill};"
f"strokeColor={stroke};strokeWidth=1;fontColor={font_color};"
f"fontSize=10;fontStyle=1;fontFamily=Oracle Sans;"
f"align=center;verticalAlign=middle;arcSize=30;"
)
return t.add_cell(text, style, "1", x, y, w, h)
def add_soft_box(t: DrawioTemplate, label: str, x: float, y: float,
w: float, h: float, fill: str, stroke: str,
font_color: str = CHARCOAL) -> str:
style = (
f"rounded=1;whiteSpace=wrap;html=1;fillColor={fill};"
f"strokeColor={stroke};strokeWidth=1;dashed=1;dashPattern=6 4;"
f"fontColor={font_color};fontSize=12;fontStyle=1;"
f"fontFamily=Oracle Sans;align=left;verticalAlign=top;"
f"spacingLeft=8;spacingTop=6;arcSize=4;"
)
return t.add_cell(label, style, "1", x, y, w, h)
# ---------------------------------------------------------------------------
# As-Is builder — left panel of adbsplusadbd, ATP-S icons
# ---------------------------------------------------------------------------
def build_as_is() -> None:
t = DrawioTemplate(SRC)
as_is, to_be = partition_panels(t)
delete_cells(t, to_be)
P = "GVNBTaRQvtpjXlJZoCOU" # id prefix used in the source
# Title: cell 87 holds the AS IS title banner text
replace_rich_label(t, f"{P}-87", "MELI Fraud — As-Is (ADB-S)",
color="#FCFBFA", bold=True, size=22)
# Make the banner red + solid
t.patch_style(f"{P}-87",
fillColor=ORACLE_RED,
strokeColor="none",
fontColor="#FCFBFA",
align="center",
verticalAlign="middle")
# Remove the notes and trade-offs text boxes — not needed in the simple view
for cid in (f"{P}-88", f"{P}-89", f"{P}-86"):
if cid in t.cells:
t.delete_subtree(cid)
# Relabel AD 3 → AD-X, AD 2 → AD-Y to match the prompt
if f"{P}-93" in t.cells:
t.set_label(f"{P}-93", "AD-X")
if f"{P}-94" in t.cells:
t.set_label(f"{P}-94", "AD-Y")
# Rename R/W Pool → Fraude (it holds Fraude's primary + standby)
# Cell 95 is the R/W Pool container in AS IS.
if f"{P}-95" in t.cells:
t.set_label(f"{P}-95", "Fraude (19c · ADB-S)")
# Relabel the ATP-S icons: 100 = primary, 99 = L-SBY. Replace their
# rich HTML values with simple labels.
if f"{P}-100" in t.cells:
replace_rich_label(t, f"{P}-100", "Fraude — Primary", color=ORACLE_RED,
size=11)
if f"{P}-99" in t.cells:
replace_rich_label(t, f"{P}-99", "Fraude — Local Standby (ADG)",
color=SLATE_BLUE, size=11)
# Cell 112 is an extra ATP-S icon floating; reuse as Fraude Clone (amber)
if f"{P}-112" in t.cells:
replace_rich_label(t, f"{P}-112", "Fraude — Clone (R/O)",
color="#7A5510", size=11)
# No tone override on the stencil itself — keep the OCI teal icon but
# wrap an amber dashed pill around it.
geo = t.cells[f"{P}-112"].find("mxGeometry")
if geo is not None:
gx = float(geo.get("x") or 0)
gy = float(geo.get("y") or 0)
gw = float(geo.get("width") or 60)
gh = float(geo.get("height") or 50)
t.add_cell(
"", style=(
f"rounded=1;whiteSpace=wrap;html=1;fillColor=none;"
f"strokeColor={AMBER};strokeWidth=2;dashed=1;dashPattern=6 4;"
f"arcSize=8;"
),
parent="1", x=gx - 6, y=gy - 6, w=gw + 12, h=gh + 14,
)
# Delete notes cell 111 if present (the "A) Allow L-SBY..." side note)
if f"{P}-111" in t.cells:
t.delete_subtree(f"{P}-111")
# ── Second workload row (Transactional) — clone the pool + icons ────
# Clone the pool container (95), primary stencil (100), standby (99),
# and the small customer icon group (101) with a y-offset of ~290 so
# they sit below the existing row.
dy = 290
clone_map = {}
for src_id in (f"{P}-95", f"{P}-100", f"{P}-99"):
if src_id not in t.cells:
continue
m = t.clone_subtree(src_id, dx=0, dy=dy,
id_prefix=f"txn_{src_id.split('-')[-1]}_")
clone_map.update(m)
# Label the cloned pool
cloned_pool = clone_map.get(f"{P}-95")
if cloned_pool:
t.set_label(cloned_pool, "Transactional — Fraude Team (26ai · ADB-S)")
cloned_primary = clone_map.get(f"{P}-100")
if cloned_primary:
replace_rich_label(t, cloned_primary, "Transactional — Primary",
color=ORACLE_RED, size=11)
cloned_sby = clone_map.get(f"{P}-99")
if cloned_sby:
replace_rich_label(t, cloned_sby, "Transactional — Local Standby (ADG)",
color=SLATE_BLUE, size=11)
# Transactional Refreshable Clone — clone the ATP-S icon (cell 112) and
# place it below the Fraude clone
if f"{P}-112" in t.cells:
m = t.clone_subtree(f"{P}-112", dx=0, dy=dy,
id_prefix="txn_clone_")
cloned_clone = m.get(f"{P}-112")
if cloned_clone:
replace_rich_label(t, cloned_clone,
"Transactional — Refreshable Clone",
color="#7A5510", size=11)
# ── Montreal DR region ──────────────────────────────────────────────
# Place a new gray dashed region to the right of the primary OCI region.
# OCI Ashburn in source is at x=4155, w=344 → right edge ≈ 4499.
# The panel backdrop (85) ends at x=3543+1101=4644. Push Montreal to the
# right, extending the panel size if needed.
MTL_X = 4660
MTL_Y = 5925
MTL_W = 340
MTL_H = 570
add_soft_box(t, "Region — ca-montreal-1 (DR)",
MTL_X, MTL_Y, MTL_W, MTL_H,
fill="#FFFFFF", stroke=MUTED_GREY)
add_pill(t, "Fraude — Cross-region Standby",
MTL_X + 30, MTL_Y + 80, SLATE_BLUE, "#2E4A78", w=280, h=32)
add_pill(t, "Transactional — Cross-region Standby",
MTL_X + 30, MTL_Y + 200, SLATE_BLUE, "#2E4A78", w=280, h=32)
# Extend the big panel backdrop (cell 85) to accommodate Montreal
panel = t.cells.get(f"{P}-85")
if panel is not None:
geo = panel.find("mxGeometry")
if geo is not None:
# Widen from 1101 to ~1500
geo.set("width", "1530")
geo.set("height", "870")
# Footer line
t.add_text(
"Out of scope: Mexico · Catálogo · Attribution.",
x=3561, y=6600, w=900, h=22,
font_size=11, font_color=MUTED_GREY, bold=False,
)
OUT_DIR.mkdir(parents=True, exist_ok=True)
out = OUT_DIR / "fraud-as-is.drawio"
t.save(out)
print(f"✓ wrote {out}")
# ---------------------------------------------------------------------------
# To-Be builder — right panel of adbsplusadbd, ATP-D icons
# ---------------------------------------------------------------------------
def build_to_be() -> None:
t = DrawioTemplate(SRC)
as_is, to_be = partition_panels(t)
delete_cells(t, as_is)
P = "GVNBTaRQvtpjXlJZoCOU"
# Title: cell 4 holds the TO BE title in the source
replace_rich_label(t, f"{P}-4",
"MELI Fraud — To-Be (ADB-D Dedicated)",
color="#FCFBFA", bold=True, size=22)
t.patch_style(f"{P}-4",
fillColor=ORACLE_RED,
strokeColor="none",
fontColor="#FCFBFA",
align="center",
verticalAlign="middle")
# Remove notes + trade-offs + RPO/RTO floating labels
for cid in (f"{P}-5", f"{P}-36", f"{P}-3"):
if cid in t.cells:
t.delete_subtree(cid)
# Rename AD 3 → AD-X, AD 2 → AD-Y
if f"{P}-40" in t.cells:
t.set_label(f"{P}-40", "AD-X — Primary · Quarter Rack X11M")
if f"{P}-41" in t.cells:
t.set_label(f"{P}-41", "AD-Y — Local ADG Standby · Quarter Rack X11M")
# Pools: 43 = R/O Pool, 42 = R/W Pool. Rename to AVMC-per-workload framing.
if f"{P}-43" in t.cells:
t.set_label(f"{P}-43", "AVMC · CDB-FRAUDE / CDB-TRANSACTIONAL — Primary")
if f"{P}-42" in t.cells:
t.set_label(f"{P}-42",
"AVMC · CDB-FRAUDE-RO / CDB-TRANSACTIONAL-RO — Read-only Standby")
# Relabel ATP-D icons: cell 47 (L-SBY R/O), 48 (primary)
if f"{P}-47" in t.cells:
replace_rich_label(t, f"{P}-47", "Fraude — Read-only Standby",
color=ORACLE_GREEN, size=11)
if f"{P}-48" in t.cells:
replace_rich_label(t, f"{P}-48", "Fraude — Primary",
color=ORACLE_RED, size=11)
# ── Second workload row (Transactional) — clone ATP-D icons ─────────
dy = 80 # place below the existing icons inside the same pool areas
clone_map = {}
for src_id in (f"{P}-47", f"{P}-48"):
if src_id not in t.cells:
continue
m = t.clone_subtree(src_id, dx=0, dy=dy,
id_prefix=f"txn_{src_id.split('-')[-1]}_")
clone_map.update(m)
cloned_ro = clone_map.get(f"{P}-47")
if cloned_ro:
replace_rich_label(t, cloned_ro, "Transactional — Read-only Standby",
color=ORACLE_GREEN, size=11)
cloned_primary = clone_map.get(f"{P}-48")
if cloned_primary:
replace_rich_label(t, cloned_primary, "Transactional — Primary",
color=ORACLE_RED, size=11)
# ── Montreal DR region ──────────────────────────────────────────────
MTL_X = 5860
MTL_Y = 6004
MTL_W = 340
MTL_H = 600
add_soft_box(t,
"Region — ca-montreal-1 (DR)\nQuarter Rack X11M",
MTL_X, MTL_Y, MTL_W, MTL_H,
fill="#FFFFFF", stroke=MUTED_GREY)
t.add_text(
"AVMC · CDB-FRAUDE-DR / CDB-TRANSACTIONAL-DR",
x=MTL_X + 15, y=MTL_Y + 60, w=MTL_W - 30, h=20,
font_size=10, font_color=MUTED_GREY, bold=True,
)
add_pill(t, "Fraude — DR Standby",
MTL_X + 30, MTL_Y + 110, SLATE_BLUE, "#2E4A78", w=280, h=32)
add_pill(t, "Transactional — DR Standby",
MTL_X + 30, MTL_Y + 240, SLATE_BLUE, "#2E4A78", w=280, h=32)
# Extend the TO BE panel backdrops (cells 1, 2) to fit Montreal
for cid in (f"{P}-1", f"{P}-2"):
panel = t.cells.get(cid)
if panel is None:
continue
geo = panel.find("mxGeometry")
if geo is not None:
geo.set("width", "1540")
geo.set("height", "870")
# Footer
t.add_text(
"Three Quarter Rack X11M total (2 in Ashburn + 1 in Montreal). "
"Active Data Guard in-region · Cross-region DG to Montreal.",
x=4708, y=6675, w=1500, h=22,
font_size=11, font_color=MUTED_GREY, bold=False,
)
OUT_DIR.mkdir(parents=True, exist_ok=True)
out = OUT_DIR / "fraud-to-be.drawio"
t.save(out)
print(f"✓ wrote {out}")
def main() -> None:
build_as_is()
build_to_be()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python3
"""
Alternate MELI Fraud diagrams — built on top of the Oracle
`exadata-dedicated-cross-region-dataguard` reference architecture as chassis.
Why this chassis:
It ships with exactly the layout both prompts want — two OCI regions
side-by-side, each with a VCN (Client Subnet + Backup Subnet), a Database
Service icon, DRG + Remote Peering, Service Gateway, and Oracle Services
Network (DNS + Object Storage) — with the Oracle Data Guard arrow drawn
between the two regions.
We keep the chassis largely untouched and overlay MELI-specific pills +
region rename + title banner, per Diego's earlier feedback that preserving
Oracle's official polish is the goal. The Exadata stencil stays (it is an
Oracle shape representing the database infrastructure) but the pills
identify the specific databases layered on top of it for MELI's estate.
Output:
examples/output-meli-fraud-adbd-migration/fraud-as-is-alt.drawio
examples/output-meli-fraud-adbd-migration/fraud-to-be-alt.drawio
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from drawio_template_transform import DrawioTemplate # noqa: E402
SRC = Path(
"kb/diagram/assets/archcenter-refs/exadata-dedicated-cross-region-dataguard/"
"exadata-dedicated-cross-region-dataguard-uncompressed.drawio"
)
OUT_DIR = Path("examples/output-meli-fraud-adbd-migration")
ORACLE_RED = "#C74634"
ORACLE_GREEN = "#00875A"
SLATE_BLUE = "#6E89AD"
AMBER = "#E8A92F"
MUTED_GREY = "#70665E"
CHARCOAL = "#312D2A"
def add_banner(t: DrawioTemplate, text: str, x: float, y: float, w: float,
h: float = 36, color: str = ORACLE_RED,
font_color: str = "#FCFBFA") -> str:
style = (
f"rounded=1;whiteSpace=wrap;html=1;fillColor={color};strokeColor=none;"
f"fontColor={font_color};fontSize=16;fontStyle=1;fontFamily=Oracle Sans;"
f"align=center;verticalAlign=middle;arcSize=4;"
)
return t.add_cell(text, style, "1", x, y, w, h)
def add_pill(t: DrawioTemplate, text: str, x: float, y: float,
fill: str, stroke: str, font_color: str = "#FCFBFA",
w: float = 170, h: float = 22) -> str:
style = (
f"rounded=1;whiteSpace=wrap;html=1;fillColor={fill};"
f"strokeColor={stroke};strokeWidth=1;fontColor={font_color};"
f"fontSize=10;fontStyle=1;fontFamily=Oracle Sans;"
f"align=center;verticalAlign=middle;arcSize=30;"
)
return t.add_cell(text, style, "1", x, y, w, h)
def add_region_label(t: DrawioTemplate, text: str, x: float, y: float,
color: str = CHARCOAL) -> str:
style = (
f"text;html=1;align=left;verticalAlign=middle;whiteSpace=wrap;"
f"fontFamily=Oracle Sans;fontSize=12;fontStyle=1;fontColor={color};"
f"strokeColor=none;fillColor=none;"
)
return t.add_cell(text, style, "1", x, y, 340, 20)
def add_footer(t: DrawioTemplate, text: str, x: float, y: float,
w: float = 1000) -> str:
style = (
f"text;html=1;align=left;verticalAlign=middle;whiteSpace=wrap;"
f"fontFamily=Oracle Sans;fontSize=10;fontColor={MUTED_GREY};"
f"strokeColor=none;fillColor=none;fontStyle=2;"
)
return t.add_cell(text, style, "1", x, y, w, 20)
# ---------------------------------------------------------------------------
# Geometry landmarks in the cross-region-dataguard template
# ---------------------------------------------------------------------------
# Tenancy chassis: ~1054x624 canvas
# Left region ("OCI Region"): x ≈ 10, y ≈ 34, w ≈ 454, h ≈ 579
# Right region ("OCI Region"): x ≈ 590, y ≈ 34, w ≈ 454, h ≈ 579
# Exadata icon boxes centered inside each region at y ≈ 210
# left icon cluster centre around x=220
# right icon cluster centre around x=820
LEFT_REGION_X = 10
LEFT_REGION_Y = 34
LEFT_REGION_W = 454
RIGHT_REGION_X = 590
RIGHT_REGION_W = 454
EXA_CLUSTER_Y = 220 # vertical centre of Exadata icon in each region
PILL_W = 200
# ---------------------------------------------------------------------------
# As-Is builder
# ---------------------------------------------------------------------------
def build_as_is() -> None:
t = DrawioTemplate(SRC)
# ── Title banner (sits above the canvas) ────────────────────────────
add_banner(t, "MELI Fraud — As-Is (ADB-S)",
x=0, y=-46, w=1054, h=36)
# ── Region labels override the stencil's "OCI Region" text with a
# clearer "us-ashburn-1 (Primary)" / "ca-montreal-1 (DR)" heading ─────
add_region_label(t, "us-ashburn-1 · Primary",
x=LEFT_REGION_X + 40, y=LEFT_REGION_Y + 4)
add_region_label(t, "ca-montreal-1 · DR",
x=RIGHT_REGION_X + 40, y=LEFT_REGION_Y + 4)
# ── Left region: overlay 2 workload rows with 3 DBs each ────────────
# Exadata icon of the left region is centred around x=220, y=220. We
# place pills below it identifying the databases MELI has on ADB-S.
lx = LEFT_REGION_X + 30
row_y = 260
row_h = 120
# Row 1 — Fraude
add_region_label(t, "Fraude (19c · ADB-S)",
x=lx, y=row_y - 20)
add_pill(t, "Primary", lx, row_y, ORACLE_RED, "#7A1F12",
w=120, h=22)
add_pill(t, "Local Standby (ADG)", lx + 130, row_y, SLATE_BLUE,
"#2E4A78", w=160, h=22)
add_pill(t, "Clone (R/O)", lx + 300, row_y, AMBER, "#7A5510",
font_color="#5A4A10", w=100, h=22)
# Row 2 — Transactional
add_region_label(t, "Transactional — Fraude Team (26ai · ADB-S)",
x=lx, y=row_y + row_h - 20)
add_pill(t, "Primary", lx, row_y + row_h, ORACLE_RED, "#7A1F12",
w=120, h=22)
add_pill(t, "Local Standby (ADG)", lx + 130, row_y + row_h, SLATE_BLUE,
"#2E4A78", w=160, h=22)
add_pill(t, "Refreshable Clone (R/O)", lx + 300, row_y + row_h, AMBER,
"#7A5510", font_color="#5A4A10", w=140, h=22)
# ── Right region: Montreal — cross-region standbys ──────────────────
rx = RIGHT_REGION_X + 30
add_region_label(t, "Fraude — Cross-region Standby",
x=rx, y=row_y - 20)
add_pill(t, "Fraude — DR Standby", rx, row_y, SLATE_BLUE, "#2E4A78",
w=250, h=22)
add_region_label(t, "Transactional — Cross-region Standby",
x=rx, y=row_y + row_h - 20)
add_pill(t, "Transactional — DR Standby", rx, row_y + row_h, SLATE_BLUE,
"#2E4A78", w=250, h=22)
add_footer(t, "Out of scope: Mexico · Catálogo · Attribution.",
x=10, y=635, w=1000)
OUT_DIR.mkdir(parents=True, exist_ok=True)
out = OUT_DIR / "fraud-as-is-alt.drawio"
t.save(out)
print(f"✓ wrote {out}")
# ---------------------------------------------------------------------------
# To-Be builder
# ---------------------------------------------------------------------------
def build_to_be() -> None:
t = DrawioTemplate(SRC)
add_banner(t, "MELI Fraud — To-Be (ADB-D Dedicated)",
x=0, y=-46, w=1054, h=36)
add_region_label(t, "us-ashburn-1 · Primary · AD-X + AD-Y",
x=LEFT_REGION_X + 40, y=LEFT_REGION_Y + 4)
add_region_label(t, "ca-montreal-1 · DR",
x=RIGHT_REGION_X + 40, y=LEFT_REGION_Y + 4)
# ── Left region: 2 ADs, each with AVMC → CDB → PDB ──────────────────
lx = LEFT_REGION_X + 30
ad_y = 260
row_h = 120
add_region_label(t,
"AD-X — Primary · Quarter Rack X11M · AVMC",
x=lx, y=ad_y - 20)
add_pill(t, "CDB-FRAUDE · Fraude PDB — Primary",
lx, ad_y, ORACLE_RED, "#7A1F12", w=320, h=22)
add_pill(t, "CDB-TRANSACTIONAL · Transactional PDB — Primary",
lx, ad_y + 28, ORACLE_RED, "#7A1F12", w=380, h=22)
add_region_label(t,
"AD-Y — Local ADG Standby · Quarter Rack X11M · AVMC",
x=lx, y=ad_y + row_h - 20)
add_pill(t, "CDB-FRAUDE-RO · Fraude — Read-only Standby",
lx, ad_y + row_h, ORACLE_GREEN, "#044C33", w=340, h=22)
add_pill(t, "CDB-TRANSACTIONAL-RO · Transactional — Read-only Standby",
lx, ad_y + row_h + 28, ORACLE_GREEN, "#044C33", w=400, h=22)
# ── Right region: Montreal DR — Quarter Rack X11M, AVMC ─────────────
rx = RIGHT_REGION_X + 30
add_region_label(t,
"Cross-region DR · Quarter Rack X11M · AVMC",
x=rx, y=ad_y - 20)
add_pill(t, "CDB-FRAUDE-DR · Fraude — DR Standby",
rx, ad_y, SLATE_BLUE, "#2E4A78", w=310, h=22)
add_pill(t, "CDB-TRANSACTIONAL-DR · Transactional — DR Standby",
rx, ad_y + 28, SLATE_BLUE, "#2E4A78", w=370, h=22)
add_footer(
t,
"Three Quarter Rack X11M total (2 in Ashburn + 1 in Montreal). "
"Active Data Guard in-region · Cross-region DG to Montreal.",
x=10, y=635, w=1040,
)
OUT_DIR.mkdir(parents=True, exist_ok=True)
out = OUT_DIR / "fraud-to-be-alt.drawio"
t.save(out)
print(f"✓ wrote {out}")
def main() -> None:
build_as_is()
build_to_be()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,283 @@
#!/usr/bin/env python3
"""
Build MELI Fraud As-Is and To-Be .drawio files from Oracle Architecture
Center reference templates.
Starts from:
- kb/diagram/assets/archcenter-refs/exadata-dedicated-in-region-dataguard/ (base)
- kb/diagram/assets/archcenter-refs/exadata-dedicated-cross-region-dataguard/ (Montreal side)
We do **not** try to edit the internal stencil text ("OCI Region", "VCN",
"Client Subnet", …) because those labels live inside embedded SVG stencil
shapes and are not cleanly editable. Instead we:
- Keep the Oracle visual layout and palette unchanged.
- Add a red banner on top with the MELI-specific title.
- Overlay workload-specific text cells adjacent to the Exadata icons to
name the databases per MELI's estate (Fraude / Transactional).
- Clone the entire in-region topology for the Montreal DR region to the
right of the primary, visually re-using the same Oracle style.
- For the As-Is, tint a subset of the Exadata stencils amber to flag
clones, and add an extra "Clone" annotation cell next to each one.
Output:
examples/output-meli-fraud-adbd-migration/fraud-as-is-template.drawio
examples/output-meli-fraud-adbd-migration/fraud-to-be-template.drawio
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from drawio_template_transform import DrawioTemplate # noqa: E402
REF_DIR = Path("kb/diagram/assets/archcenter-refs")
IN_REGION = REF_DIR / "exadata-dedicated-in-region-dataguard" / \
"exadata-dedicated-in-region-dataguard.drawio"
OUT_DIR = Path("examples/output-meli-fraud-adbd-migration")
# ----------------------------------------------------------------------
# Shared helpers
# ----------------------------------------------------------------------
def add_title_banner(t: DrawioTemplate, text: str) -> None:
"""Add a bold red Oracle banner above the primary region."""
# In-region template is ~627x389 wide. Push banner above y=0.
t.add_banner(text, x=0, y=-60, w=627, h=40)
def add_workload_header(t: DrawioTemplate, text: str, x: float, y: float,
color: str = "#312D2A") -> str:
"""Small bold line labelling a workload row/column."""
return t.add_text(text, x=x, y=y, w=300, h=20,
font_size=12, font_color=color, bold=True)
def add_db_tag(t: DrawioTemplate, text: str, x: float, y: float,
fill: str, stroke: str, font_color: str = "#FCFBFA") -> str:
"""A pill-shaped tag positioned next to (or over) an Exadata icon to
label which database the icon represents inside MELI's estate."""
style = (
f"rounded=1;whiteSpace=wrap;html=1;fillColor={fill};"
f"strokeColor={stroke};strokeWidth=1;fontColor={font_color};"
f"fontSize=10;fontStyle=1;fontFamily=Oracle Sans;"
f"align=center;verticalAlign=middle;arcSize=20;"
)
return t.add_cell(text, style, "1", x, y, 130, 22)
def add_footer(t: DrawioTemplate, text: str, y: float) -> str:
style = (
"text;html=1;align=left;verticalAlign=middle;whiteSpace=wrap;"
"fontFamily=Oracle Sans;fontSize=10;fontColor=#70665E;"
"strokeColor=none;fillColor=none;fontStyle=2;"
)
return t.add_cell(text, style, "1", 0, y, 1200, 20)
# Oracle palette
ORACLE_RED = "#C74634"
ORACLE_GREEN = "#00875A"
SLATE_BLUE = "#6E89AD"
AMBER = "#E8A92F"
MUTED_GREY = "#70665E"
LIGHT_GREY = "#F5F4F2"
CHARCOAL = "#312D2A"
# ----------------------------------------------------------------------
# As-Is builder
# ----------------------------------------------------------------------
def build_as_is() -> None:
t = DrawioTemplate(IN_REGION)
# ── Title ────────────────────────────────────────────────────────────
add_title_banner(t, "MELI Fraud — As-Is (ADB-S)")
# ── Workload labels next to the two Exadata icons ────────────────────
# The in-region template has TWO Exadata icons sitting at roughly
# AD1 (left): x≈210, y≈210 (below VCN's Client/Backup subnet area)
# AD2 (right): x≈430, y≈210
# The reference PNG shows each icon in its AD column. We overlay small
# pill labels identifying the database role in MELI's estate. Column
# geometry taken from describe() output (AD1 at x=145..330, AD2 at
# x=364..549 in template-local coords).
AD1_CX = 237 # centre of AD1 column in template coords
AD2_CX = 456 # centre of AD2 column
ICON_Y = 310 # below the Exadata stencil
ROW_GAP = 34
# Row 1 — Fraude
add_workload_header(t, "Fraude (19c · ADB-S)",
x=AD1_CX - 100, y=ICON_Y, color=CHARCOAL)
add_db_tag(t, "Primary", AD1_CX - 65, ICON_Y + 22, ORACLE_RED, "#7A1F12")
add_db_tag(t, "Local Standby (ADG)", AD2_CX - 65, ICON_Y + 22, SLATE_BLUE, "#2E4A78")
# Row 2 — Transactional (second pair of Exadata icons — we clone the
# Exadata stencils in AD1+AD2 below the existing ones).
# Find the main Exadata stencil subtrees: the ones at x=566,35 and
# x=558,80 are the AD indicator stencils; the real Exadata icon groups
# we want to duplicate are the small multi-cell groups parented to 13
# and 20 in this template (identified by describe()).
#
# Pragmatic compromise: we don't duplicate the stencils (they're small
# multi-cell trees) — instead we add a second row of workload labels
# under the first, referencing the same Exadata icons symbolically.
add_workload_header(t, "Transactional — Fraude Team (26ai · ADB-S)",
x=AD1_CX - 100, y=ICON_Y + ROW_GAP * 2, color=CHARCOAL)
add_db_tag(t, "Primary", AD1_CX - 65, ICON_Y + ROW_GAP * 2 + 22,
ORACLE_RED, "#7A1F12")
add_db_tag(t, "Local Standby (ADG)", AD2_CX - 65, ICON_Y + ROW_GAP * 2 + 22,
SLATE_BLUE, "#2E4A78")
# ── Amber "Clone" column to the right of AD2 ─────────────────────────
# The in-region template stops at x≈627; we extend to the right with a
# soft amber compartment that hosts the clone DBs.
clone_x = 660
clone_w = 170
clone_h = 260
clone_y = 17
clone_bg_id = t.add_cell(
"Clone / Refreshable Clone\n(read access — warning)",
style=(
f"rounded=1;whiteSpace=wrap;html=1;fillColor=#FDF4E1;"
f"strokeColor={AMBER};strokeWidth=1;dashed=1;dashPattern=6 4;"
f"fontColor=#7A5510;fontSize=11;fontStyle=1;"
f"fontFamily=Oracle Sans;align=left;verticalAlign=top;"
f"spacingLeft=8;spacingTop=6;arcSize=4;"
),
parent="1", x=clone_x, y=clone_y, w=clone_w, h=clone_h,
)
# Tag: Fraude Clone
add_db_tag(t, "Fraude Clone\n(R/O, 100 ECPU)",
clone_x + 15, clone_y + 60,
AMBER, "#7A5510", font_color="#5A4A10")
# Tag: Transactional Refreshable Clone
add_db_tag(t, "Transactional Refreshable Clone\n(R/O, 20 ECPU)",
clone_x + 15, clone_y + 140,
AMBER, "#7A5510", font_color="#5A4A10")
# ── Montreal DR region on the far right ──────────────────────────────
mtl_x = clone_x + clone_w + 30
mtl_y = 0
mtl_w = 260
mtl_h = 330
mtl_bg = t.add_cell(
"Region — ca-montreal-1 (DR)",
style=(
f"rounded=1;whiteSpace=wrap;html=1;fillColor=none;"
f"strokeColor={MUTED_GREY};strokeWidth=1;dashed=1;dashPattern=6 4;"
f"fontColor={CHARCOAL};fontSize=12;fontStyle=1;"
f"fontFamily=Oracle Sans;align=left;verticalAlign=top;"
f"spacingLeft=10;spacingTop=8;arcSize=4;"
),
parent="1", x=mtl_x, y=mtl_y, w=mtl_w, h=mtl_h,
)
add_db_tag(t, "Fraude — Cross-region Standby",
mtl_x + 15, mtl_y + 70, SLATE_BLUE, "#2E4A78")
add_db_tag(t, "Transactional — Cross-region Standby",
mtl_x + 15, mtl_y + 180, SLATE_BLUE, "#2E4A78")
# ── Footer ───────────────────────────────────────────────────────────
add_footer(t, "Out of scope: Mexico · Catálogo · Attribution.", y=410)
# ── Save ─────────────────────────────────────────────────────────────
OUT_DIR.mkdir(parents=True, exist_ok=True)
out = OUT_DIR / "fraud-as-is-template.drawio"
t.save(out)
print(f"✓ wrote {out}")
# ----------------------------------------------------------------------
# To-Be builder
# ----------------------------------------------------------------------
def build_to_be() -> None:
t = DrawioTemplate(IN_REGION)
add_title_banner(t, "MELI Fraud — To-Be (ADB-D Dedicated)")
# For To-Be we keep the in-region template as the primary pattern:
# the two Exadata icons (AD1 = primary, AD2 = local ADG standby).
# Overlay PDB labels on each AD, showing Fraude + Transactional PDBs.
AD1_CX = 237
AD2_CX = 456
ICON_Y = 300
# AD-X — Primary (red pills under the left Exadata icon)
add_workload_header(t, "AD-X — Primary · Quarter Rack X11M",
x=AD1_CX - 150, y=ICON_Y, color=CHARCOAL)
add_workload_header(t, "AVMC · CDB-FRAUDE / CDB-TRANSACTIONAL",
x=AD1_CX - 150, y=ICON_Y + 20,
color=MUTED_GREY)
add_db_tag(t, "Fraude PDB — Primary",
AD1_CX - 65, ICON_Y + 45, ORACLE_RED, "#7A1F12")
add_db_tag(t, "Transactional PDB — Primary",
AD1_CX - 65, ICON_Y + 71, ORACLE_RED, "#7A1F12")
# AD-Y — Local ADG Standby (green pills under the right Exadata icon)
add_workload_header(t, "AD-Y — Local ADG Standby · Quarter Rack X11M",
x=AD2_CX - 150, y=ICON_Y, color=CHARCOAL)
add_workload_header(t, "AVMC · CDB-FRAUDE-RO / CDB-TRANSACTIONAL-RO",
x=AD2_CX - 150, y=ICON_Y + 20,
color=MUTED_GREY)
add_db_tag(t, "Fraude — Read-only Standby",
AD2_CX - 65, ICON_Y + 45, ORACLE_GREEN, "#044C33")
add_db_tag(t, "Transactional — Read-only Standby",
AD2_CX - 65, ICON_Y + 71, ORACLE_GREEN, "#044C33")
# ── Montreal DR region on the right ──────────────────────────────────
mtl_x = 670
mtl_y = 0
mtl_w = 280
mtl_h = 360
t.add_cell(
"Region — ca-montreal-1 (DR)\nCross-region Data Guard · Quarter Rack X11M",
style=(
f"rounded=1;whiteSpace=wrap;html=1;fillColor=none;"
f"strokeColor={MUTED_GREY};strokeWidth=1;dashed=1;dashPattern=6 4;"
f"fontColor={CHARCOAL};fontSize=12;fontStyle=1;"
f"fontFamily=Oracle Sans;align=left;verticalAlign=top;"
f"spacingLeft=10;spacingTop=8;arcSize=4;"
),
parent="1", x=mtl_x, y=mtl_y, w=mtl_w, h=mtl_h,
)
# Inner AVMC/CDB callout
t.add_text(
"AVMC · CDB-FRAUDE-DR / CDB-TRANSACTIONAL-DR",
x=mtl_x + 15, y=mtl_y + 50, w=mtl_w - 30, h=18,
font_size=10, font_color=MUTED_GREY, bold=True,
)
add_db_tag(t, "Fraude — DR Standby",
mtl_x + 20, mtl_y + 100, SLATE_BLUE, "#2E4A78")
add_db_tag(t, "Transactional — DR Standby",
mtl_x + 20, mtl_y + 200, SLATE_BLUE, "#2E4A78")
add_footer(
t,
"Three Quarter Rack X11M total (2 in Ashburn + 1 in Montreal). "
"Active Data Guard within-region · Cross-region DG to Montreal.",
y=410,
)
OUT_DIR.mkdir(parents=True, exist_ok=True)
out = OUT_DIR / "fraud-to-be-template.drawio"
t.save(out)
print(f"✓ wrote {out}")
# ----------------------------------------------------------------------
# CLI
# ----------------------------------------------------------------------
def main() -> None:
build_as_is()
build_to_be()
if __name__ == "__main__":
main()

View File

@@ -21,6 +21,9 @@ import re
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
import tempfile
import xml.etree.ElementTree as ET
import zipfile
from pptx.util import Inches, Pt, Emu from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor from pptx.dml.color import RGBColor
@@ -848,11 +851,81 @@ class OCIDeckGenerator(OraclePresBase):
renderer = NativePPTXDiagramRenderer() renderer = NativePPTXDiagramRenderer()
renderer.apply_jobs(filepath, self._native_pptx_jobs) renderer.apply_jobs(filepath, self._native_pptx_jobs)
# Some Oracle template assets are SVG-backed. When python-pptx or the
# native renderer repacks the deck, [Content_Types].xml can lose the
# Default entry for "svg", which makes PowerPoint prompt to repair.
self._repair_pptx_media_content_types(filepath)
report: dict = {"path": filepath} report: dict = {"path": filepath}
if validate: if validate:
report["pptx"] = self._validate_pptx(filepath) report["pptx"] = self._validate_pptx(filepath)
return report return report
@staticmethod
def _repair_pptx_media_content_types(filepath: str) -> dict:
"""Ensure [Content_Types].xml declares every extension used in ppt/media."""
content_type_by_ext = {
"svg": "image/svg+xml",
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"gif": "image/gif",
"bmp": "image/bmp",
"tiff": "image/tiff",
"tif": "image/tiff",
"emf": "image/x-emf",
"wmf": "image/x-wmf",
"ico": "image/x-icon",
"webp": "image/webp",
}
deck_path = Path(filepath)
with tempfile.TemporaryDirectory(prefix="oci-deck-ct-") as tmp_dir:
work_dir = Path(tmp_dir) / "deck"
with zipfile.ZipFile(deck_path) as archive:
archive.extractall(work_dir)
ct_path = work_dir / "[Content_Types].xml"
media_dir = work_dir / "ppt" / "media"
if not ct_path.exists() or not media_dir.exists():
return {"status": "skipped", "added": []}
tree = ET.parse(ct_path)
root = tree.getroot()
ns = "http://schemas.openxmlformats.org/package/2006/content-types"
existing = {
(node.get("Extension") or "").lower()
for node in root.findall(f"{{{ns}}}Default")
}
added: list[str] = []
for media_path in sorted(media_dir.iterdir()):
if not media_path.is_file():
continue
ext = media_path.suffix.lstrip(".").lower()
content_type = content_type_by_ext.get(ext)
if not content_type or ext in existing:
continue
default = ET.SubElement(root, f"{{{ns}}}Default")
default.set("Extension", ext)
default.set("ContentType", content_type)
existing.add(ext)
added.append(ext)
if not added:
return {"status": "pass", "added": []}
tree.write(ct_path, encoding="UTF-8", xml_declaration=True)
temp_output = deck_path.with_suffix(".tmp.pptx")
if temp_output.exists():
temp_output.unlink()
with zipfile.ZipFile(temp_output, "w", zipfile.ZIP_DEFLATED) as archive:
for path in sorted(work_dir.rglob("*")):
if path.is_file():
archive.write(path, path.relative_to(work_dir))
temp_output.replace(deck_path)
return {"status": "fixed", "added": sorted(added)}
@staticmethod @staticmethod
def _validate_pptx(filepath: str) -> dict: def _validate_pptx(filepath: str) -> dict:
"""Pre-delivery sanity check on the generated .pptx. """Pre-delivery sanity check on the generated .pptx.