Seed UNS hierarchy across 6 sites; rebrand outputs to SCADA IT/OT with ZB template

Lands per-site UNS subtree files (Warsaw West/North, Shannon, Galway, TMT,
Ponce) seeded from OpenText facility docs — Warsaw split confirmed as
numbered = legacy Zimmer = West, lettered = legacy Biomet = North. Renames
project framing from "Shopfloor IT/OT" to "SCADA IT/OT" for accuracy.
Extracts a ZB-branded PowerPoint template from example_powerpoint.pptx and
wires it into the outputs pipeline. Trims deck from 18 to 16 slides
(BOBJ->Power BI transferred to another team, Non-Goals and Asks dropped);
goal-state BOBJ analysis pruned to a stub.
This commit is contained in:
Joseph Doherty
2026-04-30 10:54:49 -04:00
parent 98bf2d0da4
commit ebc76e9315
46 changed files with 2554 additions and 122 deletions

54
templates/README.md Normal file
View File

@@ -0,0 +1,54 @@
# Zimmer Biomet PowerPoint Template
Clean reusable template extracted from `example_powerpoint.pptx` (a representative ZB-branded staff-meeting deck). Captures the two-layout pattern observed there:
- **Cover slide** — full-bleed branded background image with the deck title overlaid in white in the upper-left.
- **Content slide** — title at top in Zimmer blue, content area, blue branded strip across the bottom, slide number in the lower-right, "CONFIDENTIAL FOR ZIMMER BIOMET INTERNAL USE ONLY" in the lower-left margin above the strip.
## Files
```
templates/
├── README.md # this file
├── build_template.py # regenerates zb-template.pptx from the assets
├── zb-template.pptx # the template (3 slides: 1 cover + 2 content examples)
└── assets/
├── cover-background.jpeg # 2667×1500, full-bleed cover image (Zimmer Biomet branded)
└── bottom-border.jpg # 2664×147, narrow horizontal strip for content slides
```
## How to use
1. **Pick the deck up by `zb-template.pptx`** and replace the title text on slide 1, then duplicate slide 2 for each content slide you need.
2. **Or write a slide-generator** that places the assets in the same positions (see `build_template.py` for the canonical positions in inches).
3. **To update the template** (e.g., new ZB branding lands), drop replacement files into `assets/` with the same filenames and re-run `python3 build_template.py`.
## Design specifications
| Property | Value |
|---|---|
| Slide size | 13.33" × 7.5" (16:9) |
| Title font | Arial Bold |
| Cover title | 28pt, color `#F4F4F4` (near-white), positioned at (0.92", 1.38") size 6.0"×1.89" |
| Cover subtitle | 14pt, color `#F4F4F4`, positioned at (0.92", 3.30") |
| Content title | 28pt, color `#0066B3` (Zimmer blue), positioned at (0.92", 0.40") size 11.5"×0.85" |
| Content body | 18pt, color `#333333`, positioned at (0.92", 1.50") size 11.5"×4.80" |
| Bottom border | image, positioned at (0.01", 6.76") size 13.32"×0.74" |
| Confidentiality text | 8pt, color `#404040`, "CONFIDENTIAL FOR ZIMMER BIOMET INTERNAL USE ONLY", positioned at (0.29", 6.55") |
| Slide number | 10pt, color `#2B2A80` (dark navy), positioned in the lower-right at (12.85", 6.55") |
Brand colors picked up from the example deck's slide layout XML:
- Zimmer blue: `#0066B3`
- Dark navy / Z-purple: `#2B2A80`
- Cover-text near-white: `#F4F4F4`
## What's intentionally left out
The example deck contained 14 slide masters and 12+ layouts accumulated over many years (multiple legacy decks merged together). This template captures **only the two patterns the user identified as universal** (cover + content-with-bottom-border) and discards the rest. If a specific historical layout is needed (e.g., the "Living Our Mission and Guiding Principles" branded chart on slide 3, or the "Title Slide" layout used on slide 13), copy the relevant slide out of `example_powerpoint.pptx` separately — that one-off content shouldn't pollute the reusable template.
## Relationship to `outputs/`
The `outputs/` auto-generation pipeline **uses this template** (as of 2026-04-30): `outputs/workspace/generate.js` references `templates/assets/cover-background.jpeg` for slide 1 and `templates/assets/bottom-border.jpg` as the bottom-border strip on slides 218, plus reskins content-slide HTMLs to the ZB color palette. See the `2026-04-30` entry in `outputs/run-log.md` for the integration details.
**Coordinate note:** the template's canonical coordinates are at 13.33"×7.5". The current pipeline runs at 10"×5.625" (pptxgen's `LAYOUT_16x9`, matching the existing 720×405pt HTML CSS). `generate.js` scales the template coordinates by 0.75× to fit. If the pipeline ever moves to native 13.33"×7.5", drop the scaling and use template coords as published in the table above.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

133
templates/build_template.py Normal file
View File

@@ -0,0 +1,133 @@
"""Build the Zimmer Biomet PowerPoint template from extracted assets.
Reads `assets/cover-background.jpeg` and `assets/bottom-border.jpg`,
emits `zb-template.pptx` with two slides demonstrating the cover and
content layouts. Re-run this script whenever assets change.
"""
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 os
HERE = os.path.dirname(os.path.abspath(__file__))
ASSET = os.path.join(HERE, "assets")
OUT = os.path.join(HERE, "zb-template.pptx")
# Slide-size constants in EMU (English Metric Units; 914400 EMU = 1 inch)
SLIDE_W = Inches(13.33)
SLIDE_H = Inches(7.5)
# Brand colors from the example deck's slideLayout22 / slideLayout33 XML
COVER_TITLE_COLOR = RGBColor(0xF4, 0xF4, 0xF4) # near-white for cover title over dark bg
CONTENT_TITLE_COLOR = RGBColor(0x00, 0x66, 0xB3) # Zimmer blue
SLIDE_NUMBER_COLOR = RGBColor(0x2B, 0x2A, 0x80) # dark navy/purple
CONFIDENTIAL_COLOR = RGBColor(0x40, 0x40, 0x40) # dark gray on the white margin above the border
CONFIDENTIAL_TEXT = "CONFIDENTIAL FOR ZIMMER BIOMET INTERNAL USE ONLY"
def add_textbox(slide, left, top, width, height, text, *, font_size=12,
color=None, bold=False, align=None, font_name="Arial"):
tx = slide.shapes.add_textbox(left, top, width, height)
tf = tx.text_frame
tf.margin_left = tf.margin_right = tf.margin_top = tf.margin_bottom = 0
p = tf.paragraphs[0]
if align is not None:
p.alignment = align
r = p.add_run()
r.text = text
r.font.name = font_name
r.font.size = Pt(font_size)
r.font.bold = bold
if color is not None:
r.font.color.rgb = color
return tx
def build_cover(slide):
"""Cover slide: full-bleed background image + title in upper-left, white text."""
slide.shapes.add_picture(
os.path.join(ASSET, "cover-background.jpeg"),
left=0, top=0, width=SLIDE_W, height=SLIDE_H,
)
add_textbox(
slide,
left=Inches(0.92), top=Inches(1.38),
width=Inches(6.0), height=Inches(1.89),
text="Deck Title",
font_size=28, bold=True, color=COVER_TITLE_COLOR,
)
add_textbox(
slide,
left=Inches(0.92), top=Inches(3.30),
width=Inches(6.0), height=Inches(0.5),
text="Subtitle / date / author",
font_size=14, color=COVER_TITLE_COLOR,
)
def build_content(slide, *, title="Section title", page_number="2"):
"""Content slide: title at top, content area, bottom border + slide number + confidentiality text."""
add_textbox(
slide,
left=Inches(0.92), top=Inches(0.40),
width=Inches(11.5), height=Inches(0.85),
text=title,
font_size=28, bold=True, color=CONTENT_TITLE_COLOR,
)
placeholder = add_textbox(
slide,
left=Inches(0.92), top=Inches(1.50),
width=Inches(11.5), height=Inches(4.80),
text=("Body content goes here.\n"
"• Bullet one\n"
"• Bullet two\n"
"• Bullet three"),
font_size=18, color=RGBColor(0x33, 0x33, 0x33),
)
placeholder.text_frame.word_wrap = True
slide.shapes.add_picture(
os.path.join(ASSET, "bottom-border.jpg"),
left=Inches(0.01), top=Inches(6.76),
width=Inches(13.32), height=Inches(0.74),
)
add_textbox(
slide,
left=Inches(0.29), top=Inches(6.55),
width=Inches(8.0), height=Inches(0.20),
text=CONFIDENTIAL_TEXT,
font_size=8, color=CONFIDENTIAL_COLOR,
)
add_textbox(
slide,
left=Inches(12.85), top=Inches(6.55),
width=Inches(0.40), height=Inches(0.20),
text=page_number,
font_size=10, color=SLIDE_NUMBER_COLOR, align=PP_ALIGN.RIGHT,
)
def main():
prs = Presentation()
prs.slide_width = SLIDE_W
prs.slide_height = SLIDE_H
blank = prs.slide_layouts[6]
cover = prs.slides.add_slide(blank)
build_cover(cover)
content = prs.slides.add_slide(blank)
build_content(content, title="Content slide example", page_number="2")
second_content = prs.slides.add_slide(blank)
build_content(second_content, title="Another content slide", page_number="3")
prs.save(OUT)
print(f"Wrote {OUT}")
if __name__ == "__main__":
main()

BIN
templates/zb-template.pptx Normal file

Binary file not shown.