diff --git a/examples/output-demo-pharma-mx/business-case-spec.yaml b/examples/output-demo-pharma-mx/business-case-spec.yaml new file mode 100644 index 0000000..753d1a3 --- /dev/null +++ b/examples/output-demo-pharma-mx/business-case-spec.yaml @@ -0,0 +1,151 @@ +# OCI Deal Accelerator — Business Case +# Demo: PharmaCorp Mexico — ExaCS migration from on-prem Exadata X6 + +business_case: + customer_name: "PharmaCorp Mexico" + prepared_by: "Carlos Mendoza — Solutions Architect" + date: "2026-04-12" + + executive_summary: > + PharmaCorp Mexico operates 3 mission-critical Oracle databases (EBS R12, SAP, DW) + on aging Exadata X6 hardware. The hardware refresh quote is $1.8M and locks the + company into 5 more years of on-prem operations with no DR capability. Migration + to OCI ExaCS X11M reduces annual infrastructure costs by 61% ($1.75M over 3 years), + adds cross-region DR (RPO<15min, RTO<1h), and resolves the pending SOX audit + finding on automated patching — all while eliminating data center costs. + + drivers: + primary: "hardware_refresh_avoidance" + urgency: "HW refresh due in 6 months — must decide now or commit $1.8M" + cost_of_inaction: + financial: "$1.8M HW refresh + $300K/yr support + $80K/yr DC costs = $2.88M over 3yr" + risk: "No DR today — single site, single point of failure for EBS production" + compliance: "SOX audit finding: manual quarterly patching is non-compliant" + + tco_comparison: + current_state: + description: "On-premises Exadata X6 with hardware refresh" + line_items: + - item: "Hardware Refresh (Exadata X8M)" + year_1: 1800000 + year_2: 0 + year_3: 0 + notes: "One-time CapEx" + - item: "Annual Support & Maintenance" + year_1: 220000 + year_2: 220000 + year_3: 220000 + - item: "Data Center (power, cooling, space)" + year_1: 80000 + year_2: 80000 + year_3: 80000 + - item: "DBA Operations (patching)" + year_1: 60000 + year_2: 60000 + year_3: 60000 + - item: "DR Infrastructure" + year_1: 0 + year_2: 0 + year_3: 0 + notes: "No DR — risk accepted" + total_3yr: 2880000 + + proposed_oci: + description: "OCI ExaCS X11M with Data Guard DR" + line_items: + - item: "ExaCS X11M Queretaro (Prod, BYOL)" + year_1: 98400 + year_2: 98400 + year_3: 98400 + notes: "$8,200/mo" + - item: "ExaCS X11M Sao Paulo (DR, BYOL)" + year_1: 98400 + year_2: 98400 + year_3: 98400 + notes: "$8,200/mo — Data Guard standby" + - item: "FastConnect 10Gbps Dual" + year_1: 36720 + year_2: 36720 + year_3: 36720 + notes: "$3,060/mo" + - item: "OCI Vault + Monitoring + Storage" + year_1: 30000 + year_2: 30000 + year_3: 30000 + notes: "$2,500/mo" + - item: "Migration (one-time)" + year_1: 50000 + year_2: 0 + year_3: 0 + notes: "ZDM + Data Guard setup + testing" + total_3yr: 1130400 + + savings_3yr: 1749600 + savings_pct: 61 + + roi: + investment: 50000 + annual_savings: 583200 + payback_months: 1 + three_year_roi: "3,400%" + + value_drivers: + - driver: "Cost Avoidance" + impact: "$1.75M saved over 3 years vs HW refresh" + evidence: "BYOL pricing + elimination of DC costs + automated patching" + - driver: "Risk Reduction" + impact: "Cross-region DR where none exists today" + evidence: "RPO<15min, RTO<1h with Data Guard + FSDR" + - driver: "Compliance" + impact: "SOX audit finding resolved" + evidence: "ExaCS automated patching + OCI Audit + Vault for key management" + - driver: "Operational Agility" + impact: "DBA freed from patching + infrastructure management" + evidence: "Oracle-managed ExaCS infrastructure, automated backups" + + risks: + migration_risks: + - risk: "EBS customizations require testing" + mitigation: "Pre-Prod testing in Phase 2 before cutover" + - risk: "FastConnect provisioning delay (partner-dependent)" + mitigation: "Start in week 1; IPSec VPN as interim backup" + - risk: "HW refresh deadline pressure (6 months)" + mitigation: "16-week migration plan fits within window" + do_nothing_risks: + - risk: "No DR — single site, single point of failure for EBS" + mitigation: "Cannot be mitigated without investment" + - risk: "SOX audit finding remains unresolved (manual patching)" + mitigation: "Remediation requires automated patching — on-prem can't deliver" + - risk: "$1.8M HW refresh locks in 5 more years of on-prem" + mitigation: "Sunk cost — no cloud migration ROI for 5+ years" + + recommendation: + summary: > + Proceed with OCI ExaCS migration. 61% cost reduction over 3 years ($1.75M saved), + cross-region DR capability, and SOX compliance resolution. The alternative + (HW refresh) costs $1.75M more and leaves DR + compliance gaps unresolved. + next_steps: + - "Board approval with TCO presentation — May 2026" + - "Provision OCI tenancy + ExaCS in Queretaro — June 2026" + - "Start FastConnect provisioning with partner — June 2026" + - "EBS customization inventory and Pre-Prod testing — June 2026" + - "Production cutover — September 2026" + + roadmap: + total_duration: "16 weeks (May — September 2026)" + phases: + - phase: "Board Approval" + timeline: "May 2026" + key_action: "Present TCO + risk analysis to board" + - phase: "Foundation (networking + ExaCS)" + timeline: "June 2026 (4 weeks)" + key_action: "Provision OCI tenancy, ExaCS, FastConnect" + - phase: "EBS Migration" + timeline: "July 2026 (6 weeks)" + key_action: "Migrate EBS R12 with ZDM, configure Data Guard" + - phase: "SAP + DW Migration" + timeline: "August 2026 (4 weeks)" + key_action: "Migrate remaining databases" + - phase: "Cutover + Decommission" + timeline: "September 2026 (2 weeks)" + key_action: "Production cutover, decommission Exadata X6" diff --git a/examples/output-demo-pharma-mx/opt01-architecture-proposal.pptx b/examples/output-demo-pharma-mx/opt01-architecture-proposal.pptx index 39c932c..8929e3c 100644 Binary files a/examples/output-demo-pharma-mx/opt01-architecture-proposal.pptx and b/examples/output-demo-pharma-mx/opt01-architecture-proposal.pptx differ diff --git a/examples/output-demo-pharma-mx/opt01-customer-proposal.pdf b/examples/output-demo-pharma-mx/opt01-customer-proposal.pdf index 9f57bcf..18abe1b 100644 --- a/examples/output-demo-pharma-mx/opt01-customer-proposal.pdf +++ b/examples/output-demo-pharma-mx/opt01-customer-proposal.pdf @@ -142,7 +142,7 @@ endobj endobj 19 0 obj << -/Author (Carlos Mendoza) /CreationDate (D:20260412231513-03'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260412231513-03'00') /Producer (ReportLab PDF Library - \(opensource\)) +/Author (Carlos Mendoza) /CreationDate (D:20260413001815-03'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260413001815-03'00') /Producer (ReportLab PDF Library - \(opensource\)) /Subject (OCI Architecture Proposal) /Title (PharmaCorp Mexico \204 Oracle Database Migration to OCI) /Trapped /False >> endobj @@ -181,10 +181,10 @@ Gau0D?!#c?&:N_CbgW/j?!L$g^Wu7?8q>@-7nU((+*^X&-#p2l<48Luq]/7J#`q"tlTI8H[URM:RCOPJ endobj 25 0 obj << -/Filter [ /ASCII85Decode /FlateDecode ] /Length 2236 +/Filter [ /ASCII85Decode /FlateDecode ] /Length 2349 >> stream -Gau`U=``=W%"?O+^dC2I[Y[=KeDYgsS2Atqe"j7Xh9P)8O_I=f%Tc/`m]TuZP'k3VI"\gfRFX2CiqabtKIIK1U\b!5"umX=aa4AZ_-dD1>9!j>i-]M5lbN(=@WrYkbi92P&+tpuc,B^9a+FL7%\D]I%$'B6<;<,DdE@jp`&b><1ChXdRDF6A;q)(/k&QmPC+6BK)Kp[un#&M.r.`c]a,<99U25#cI*nA1tB(#ksn2A\OHh6f.fK]WZY5J$ne=I>dL3pHtNAU'T6/)/l712;lmuE#k5Ctn?=[VDPZc0k$MGAOQg(o7b8[jG.lJjjg&Gk4?";kL]f'kUUGd,27ZF,C$i=&Md(DfRNZaZ%OeL_FUbT$02/2(qrqirh"k1":f'1J`Z>>_K'krdsArfo\9BY)bk0@YQU%'"[2)Nn2I%h`[jD$P`8o8=n_Wp6)MrAlpg8^LG/Y=I3oN&BlB/Saqo5ZYpK2=EHUGRU#R0:[;,(`/D"Y/B/1A-U;=?`5`:b'PsG"=e#;)uSH=0!:)H'-*%X/no,1f;7ro:^PF49FIb.o,M%jLpuBf\Snit@uP9V\hWL-0GXZ(?GH^lZNhJV`[SL5XmkE`'%2'boM)G^bnMs\L\1/3Z'kVq9,oSaC88%Yi!SEA;2DO`_)\3l7tOLWM"$B2!l<)oF%nlM\N<_)(FpuDDi`cfmegj).D8CDa[Y#m%Wa62PWrhN4%1aGL'$Fqj9"o*3[*a`,?bB`&\L19:OqBqh\1LmK/kDVSn/Ffb**C@l(FS/P`TKk:dmO$QhQO?1&g*0OCleH8DK0T7"a*1WHn6YZarl//KK&s(Vmc^)W86t'M:_DNe$g?uO6O'H]<%016e!L^V2Ir--rQA]]gAOK_\@LsTY)U`cS]We>QPT$/R?`3d^.318dd^FFl/)Mbbon:YG"o-oIPW#!B"p"F$Cce+Pl9mQI=Bn*7sP>.Lb7+<&EPSs+KF5f9Z-8$>p^7*>Re:1>E&D10ED?$NlYfhN/1s:2Po2^.+OqU/fnO5gjf9s=f<5se:U?XDOcI,Ss^[UcC\)6/#lcIWG$NN`RU2fZ\F@<.?u`;eZ,=GRb@*nH_h4k!7M#h]p`*>3)p!Qa/IO/[V+?r);3TaAoGYIQ-n-7/4O5]TWNYa`?S@k37Z?UC*':ak<(D-h9Y24[#M>hn*_]d/]cD)594u88]K,@:SF@B_Gqq7W#D_:?c_1'*25-KM&Q=UL;c<3C@7OWP=_4NP[:)eZ0&EqY*"o!%$[&M[3;=P"D*muM5=s@+uDSMdIOo@;TgBKe=)k'MZRfT;$=H@^X\[TksW++RekF,/D@6j8jDa4e*h@8=+fHcP)gfN23Q+5@F@ho%olmIP=\bUPY3MQe`;%/U@u=K^I$?DX+U7W-p_ErH]a<8HYRrb.,:g6Fdpts%fQWdQ&``~>endstream +Gau0F=``=U&:XAWfS="WD%q^)n%QoPijKN@Nhn)iN*ftMZC18[GW9SjIp:sui!u$0Or'*jU:1j"`4Ti]4[K'nY2U"A#A9RV4aRFT+$9/mXZQ4cBoG`SOo-4b&O"tZjn/N6BE#`5f&ss^tjF![;(iD^bYUtV82Ae>^T'LoXPaLJsECU79"%a,iLJg+G\eddb:@hp$(XZ_4k!-]K/'NGhEr@f<0g)phmPN6*nN=so=.U&Q/,?jc9be"3[pY%GWmi-'Yot^tBn=qrV+AXjlle"P/4IOoWMi4Jhpp*mh^=rMT]8`6^u4qfLWSVf'YV`-:K$L>4k.1*=Y!'+maqOieIG7uB.H"k#gm))"U?XT(@`1Q/:L(C\u+YSD&q5P/c:jrReL)l]l*B&F`_g);/.7;"OcmekP(O/^e@ZMl5s/CdV:YmUX3*'poNg"f]G4RgbW?:=Uu1/C:QkLinq[RJU&-+$J>Z@^\(#b1BYCBc\)#1l)`_jR8bXO0/E.e7j*p*Di83W;uJ79#*f^\MT![If!:4GqT:r5mRpI&]V470QqSn8GhqgdBoBn`JYb1+IV^-R[@T9ZL3Z3;YI]Z8'$6QtOf/%Rh,W!QLYG7NrSn@(jECRA-HS)=2I2oekE,o_fqFgBa^oRId#%R0(r:b)+L*CQ+*oJhZO2)pCNkTA$u^uDAMhEHuB>N<:!Nb9a'$D/u9m>D61UTR6-rF(g):dns'7j^4b!cBpi'MC1A^2DU9PgNACkQ@C?_`Yo/C,#=g8OQ'VE&QDJGPq2XO)e#2&uhrABLP?]>Gfc=\[H#AhGI(@T+"nD_.jL=&%.1NFq;ilp2H`!!gEP:BWBU?.M"M,OWHC'eMA-p7>4.3g*`Ma(Mf&sW8d+,^?+]6]%/E@\+uTJdM2[1_-\gq30!qY/g?*#Et56RObqmT!&IWI,2(g,cN=U3:%3e0c:SW_R]aI>Hei!u.888=ed@qGtX*<'i(7n=FMrGqYJ9Q*-*J8L%Q!,tj1C:h(o/k+7naaRT[;#*@3THEi:DR-J\&mu'5jYt$'$Y4ib&TiJ)JXjXFdm.92t5Lf#Xac_H7]W33b[2/UY3F>hQ+-`F>X(TTG<@9dS<_gH[CNeSY.dBI78c&VP'A3[#&3p_"gm.Fe$T>&$h5K7'[H_ehI)bN]c=pBHgm>F*-mLq&fYJu'_&(P!pB*:HAj5dDe%g+pF$cA5-+s%itC/n7Tgn(eN^GY:KN36M4SRW3Od"aA,e'`l]@fFG'c!C%n`69=n!FCbVp$gt.J?Y+c*hi^7OG0)Z05*i*D3<9VYqRs&#heC$jGcVE=l0jMNTo]crBiYN.4g,]mHV6.=4W[AQ>2&X#cXJM,*`CKVC2V,d(m:Bcj)Q/a'er3M)Bm`GL)PGrf*0`>?eMQXX?Bc&k!C-'rKUheh6_k-"PN$aWo]KM$0PHF4mjFaETO(/kJ3-%AGb@$d3g3ZFS[\6s+"oBfWMk\65GDiPiZFohfC?LA53=qL%V/<0LsD+Qkl^[9GME-=5\D_Ei6>G/kJ[PK[_>=/cKDXEY1Fg.V:$qg%$Q!jJR2YH.eeUp#I'iT)FkS)gemIglll5:Y)"_Z>T\Y2--P,UgPs/,S$T.7Xs=Ul5kVI]9`,1R25t)QX%DZLK:gFb9@E&,se"fSj?Hp"\gPI'M;1)q8Tala#Ybhl\!`W:l>ZBRj8#;LYp0"XIDa>8u$<%l>!9j+ahM`L[S`5XhfCV7,LEF23oKk``YC^V=,9f/=.iiO@g6YRTp@*%:8>t)fYL4#Pt/D6S.30..0oW1d,dI%cQs&p6*;?Ii9b0>l~>endstream endobj 26 0 obj << @@ -249,15 +249,15 @@ xref 0000005993 00000 n 0000008355 00000 n 0000010383 00000 n -0000012711 00000 n -0000014950 00000 n -0000017314 00000 n -0000018630 00000 n -0000019167 00000 n +0000012824 00000 n +0000015063 00000 n +0000017427 00000 n +0000018743 00000 n +0000019280 00000 n trailer << /ID -[<058687b5e54d5950ae70b0c7d0f56515><058687b5e54d5950ae70b0c7d0f56515>] +[<8c841a256005e9c14418567efe6f7d83><8c841a256005e9c14418567efe6f7d83>] % ReportLab generated PDF document -- digest (opensource) /Info 19 0 R @@ -265,5 +265,5 @@ trailer /Size 31 >> startxref -20023 +20136 %%EOF diff --git a/examples/output-demo-pharma-mx/opt08-business-case.pptx b/examples/output-demo-pharma-mx/opt08-business-case.pptx index 1f662f3..c1f1178 100644 Binary files a/examples/output-demo-pharma-mx/opt08-business-case.pptx and b/examples/output-demo-pharma-mx/opt08-business-case.pptx differ diff --git a/tools/oci_pdf_gen.py b/tools/oci_pdf_gen.py index a82c0b9..b032e16 100644 --- a/tools/oci_pdf_gen.py +++ b/tools/oci_pdf_gen.py @@ -619,20 +619,38 @@ class OCIPDFGenerator: self._add_bullet_list(items) def add_environment_catalogue(self, environments: list, - cost_notes: list = None): + cost_notes=None): """Environment Catalogue section.""" self._add_section_title("Environment Catalogue") - headers = ["Environment", "Tier", "Databases", "OCPUs", "Isolation"] - rows = [ - [e.get("environment", ""), e.get("tier", ""), - e.get("databases", ""), e.get("ocpus", ""), - e.get("isolation", "")] - for e in environments - ] - avail = self.PAGE_WIDTH - 2 * self.MARGIN - widths = [avail * 0.16, avail * 0.10, avail * 0.20, - avail * 0.10, avail * 0.44] + # Normalize cost_notes to list + if isinstance(cost_notes, str): + cost_notes = [s.strip() for s in cost_notes.split(".") if s.strip()] + + # Detect schema: new (name/sizing/cost_pct) vs legacy (environment/tier/databases/ocpus) + sample = environments[0] if environments else {} + if "sizing" in sample or "cost_pct" in sample: + headers = ["Environment", "Sizing", "Isolation", "Cost %"] + rows = [ + [e.get("name", e.get("environment", "")), + e.get("sizing", ""), + e.get("isolation", ""), + f"{e.get('cost_pct', '')}%" if e.get("cost_pct", "") != "" else ""] + for e in environments + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.18, avail * 0.40, avail * 0.27, avail * 0.15] + else: + headers = ["Environment", "Tier", "Databases", "OCPUs", "Isolation"] + rows = [ + [e.get("environment", ""), e.get("tier", ""), + e.get("databases", ""), e.get("ocpus", ""), + e.get("isolation", "")] + for e in environments + ] + avail = self.PAGE_WIDTH - 2 * self.MARGIN + widths = [avail * 0.16, avail * 0.10, avail * 0.20, + avail * 0.10, avail * 0.44] self.story.append(self._make_table(headers, rows, widths)) if cost_notes: