#!/usr/bin/env python3 """ Update NORDA presentation: remove old diagram slides, add 10 new diagram slides. Each slide has dark background matching presentation style, large fonts, and diagram image. """ from pptx import Presentation from pptx.util import Inches, Pt, Emu from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN import copy import os PPTX_PATH = '/Users/maciejpi/Documents/INPI-WEWNETRZNE/NORDA-Business/NORDA_Prezentacja_Rada_2026-02-13.pptx' DIAGRAMS_DIR = '/Users/maciejpi/claude/projects/active/nordabiz/docs/architecture/diagrams' # Colors matching presentation style BG_DARK = RGBColor(0x0F, 0x17, 0x2A) # #0F172A - dark background HEADER_NAVY = RGBColor(0x1A, 0x36, 0x5D) # #1A365D - navy header GOLD = RGBColor(0xD6, 0x9E, 0x2E) # #D69E2E - gold accent WHITE = RGBColor(0xFF, 0xFF, 0xFF) GRAY = RGBColor(0xA0, 0xAE, 0xC0) # #A0AEC0 - subtitle gray # 10 diagrams: (filename, title, subtitle) DIAGRAMS = [ ('a9-detailed-it-architecture.png', 'Szczegółowa architektura IT', 'Azure AD · M365 · SSL · FortiGate · Proxmox · Aplikacje · Systemy wspierające'), ('b1-platform-overview.png', 'Architektura platformy NordaBiznes', 'Przegląd modułów, użytkowników i integracji'), ('a3-network-topology.png', 'Infrastruktura sieciowa INPI', 'Topologia sieci, firewalle, serwery, backup'), ('b3-feature-map.png', 'Mapa funkcjonalności platformy', 'Wszystkie moduły i funkcje dla firm członkowskich'), ('b5-ai-capabilities.png', 'Sztuczna inteligencja — NordaGPT', 'Asystent AI, audyty SEO, analiza danych'), ('a5-auth-oauth.png', 'Bezpieczeństwo i uwierzytelnianie', 'RBAC · OAuth 2.0 · CSRF · Rate Limiting'), ('a6-api-integration-map.png', 'Integracje z zewnętrznymi API', 'Google · Azure · PageSpeed · YouTube · CrUX'), ('a8-backup-dr.png', 'Backup i Disaster Recovery', 'Proxmox PBS · Hetzner · RTO 2h · RPO 24h'), ('b8-access-levels.png', 'Matryca uprawnień — pakiety', 'Starter · Premium · Business Pro · Admin'), ('a7-module-structure.png', 'Architektura modułowa aplikacji', '17 blueprintów · 49 plików routów · Flask'), ] # Slide dimensions in EMU SLIDE_W = 9144000 # 10 inches SLIDE_H = 5143500 # ~5.63 inches def add_dark_background(slide, prs): """Add dark background to slide.""" from pptx.oxml.ns import qn bg = slide.background fill = bg.fill fill.solid() fill.fore_color.rgb = BG_DARK def add_header_bar(slide, title_text, subtitle_text): """Add navy header bar with title and subtitle.""" from pptx.oxml.ns import qn # Header background rectangle header_h = Emu(720000) # ~0.79 inches header = slide.shapes.add_shape( 1, # MSO_SHAPE.RECTANGLE Emu(0), Emu(0), Emu(SLIDE_W), header_h ) header.fill.solid() header.fill.fore_color.rgb = HEADER_NAVY header.line.fill.background() # Title text title_box = slide.shapes.add_textbox( Emu(300000), Emu(80000), Emu(SLIDE_W - 600000), Emu(380000) ) tf = title_box.text_frame tf.word_wrap = True p = tf.paragraphs[0] p.text = title_text p.font.size = Pt(24) p.font.color.rgb = WHITE p.font.bold = True p.alignment = PP_ALIGN.LEFT # Subtitle text sub_box = slide.shapes.add_textbox( Emu(300000), Emu(440000), Emu(SLIDE_W - 600000), Emu(250000) ) tf2 = sub_box.text_frame tf2.word_wrap = True p2 = tf2.paragraphs[0] p2.text = subtitle_text p2.font.size = Pt(14) p2.font.color.rgb = GRAY p2.alignment = PP_ALIGN.LEFT # Gold accent line under header line = slide.shapes.add_shape( 1, # RECTANGLE Emu(0), header_h, Emu(SLIDE_W), Emu(25000) ) line.fill.solid() line.fill.fore_color.rgb = GOLD line.line.fill.background() def add_diagram_image(slide, img_path): """Add diagram image centered below header, filling available space.""" from PIL import Image # Available space below header + accent line top_margin = Emu(800000) # below header bottom_margin = Emu(100000) # small bottom margin side_margin = Emu(150000) # small side margins avail_w = SLIDE_W - 2 * side_margin avail_h = SLIDE_H - top_margin - bottom_margin # Get image dimensions with Image.open(img_path) as img: img_w, img_h = img.size # Scale to fit available space scale_w = avail_w / img_w scale_h = avail_h / img_h scale = min(scale_w, scale_h) final_w = int(img_w * scale) final_h = int(img_h * scale) # Center horizontally left = side_margin + (avail_w - final_w) // 2 top = top_margin + (avail_h - final_h) // 2 slide.shapes.add_picture(img_path, Emu(left), Emu(top), Emu(final_w), Emu(final_h)) def add_slide_number(slide, num, total): """Add slide number in bottom right.""" num_box = slide.shapes.add_textbox( Emu(SLIDE_W - 1200000), Emu(SLIDE_H - 300000), Emu(1000000), Emu(200000) ) tf = num_box.text_frame p = tf.paragraphs[0] p.text = f'Załącznik {num}/{total}' p.font.size = Pt(10) p.font.color.rgb = GRAY p.alignment = PP_ALIGN.RIGHT def main(): prs = Presentation(PPTX_PATH) # Step 1: Remove slides 16, 17, 18 (indices 15, 16, 17) — old diagram appendices # Remove from highest index first to avoid shifting slides_to_remove = [] for i, slide in enumerate(prs.slides): if i >= 15: # slides 16, 17, 18 (0-indexed: 15, 16, 17) slides_to_remove.append(slide) # Access XML directly to remove slides pres_xml = prs._element ns_r = '{http://schemas.openxmlformats.org/officeDocument/2006/relationships}' ns_p = '{http://schemas.openxmlformats.org/presentationml/2006/main}' sldIdLst = pres_xml.find(f'{ns_p}sldIdLst') # Get rIds for slides 16,17,18 (indices 15,16,17) sldId_elements = list(sldIdLst) for idx in reversed([15, 16, 17]): sldId_elem = sldId_elements[idx] rId = sldId_elem.get(f'{ns_r}id') print(f' Removing slide {idx+1} (rId={rId})') sldIdLst.remove(sldId_elem) # Also remove the relationship if rId in prs.part.rels: prs.part.drop_rel(rId) print(f'Removed 3 old diagram slides. Now have {len(prs.slides)} slides.') # Step 2: Add 10 new diagram slides # Use blank layout blank_layout = prs.slide_layouts[0] # DEFAULT layout for idx, (filename, title, subtitle) in enumerate(DIAGRAMS): img_path = os.path.join(DIAGRAMS_DIR, filename) if not os.path.exists(img_path): print(f'WARNING: {filename} not found, skipping') continue slide = prs.slides.add_slide(blank_layout) add_dark_background(slide, prs) add_header_bar(slide, title, subtitle) add_diagram_image(slide, img_path) add_slide_number(slide, idx + 1, 10) print(f'Added slide {idx+1}: {title}') # Save prs.save(PPTX_PATH) print(f'\nSaved: {PPTX_PATH}') print(f'Total slides: {len(prs.slides)}') if __name__ == '__main__': main()