nordabiz/scripts/update_pptx_diagrams.py

200 lines
7.0 KiB
Python

#!/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()