Files
3yearplan/outputs/workspace/generate.js
Joseph Doherty ebc76e9315 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.
2026-04-30 10:54:49 -04:00

156 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const pptxgen = require('pptxgenjs');
const html2pptx = require('/Users/dohertj2/.claude/plugins/cache/anthropic-agent-skills/document-skills/unknown/skills/pptx/scripts/html2pptx.js');
const path = require('path');
const SLIDES_DIR = path.join(__dirname, 'slides');
const OUTPUT = '/Users/dohertj2/Desktop/plan/outputs/generated/plan-presentation.pptx';
// ZB template assets and design constants (see /Users/dohertj2/Desktop/plan/templates/README.md)
const TEMPLATE_ASSETS = '/Users/dohertj2/Desktop/plan/templates/assets';
const COVER_BG = path.join(TEMPLATE_ASSETS, 'cover-background.jpeg');
const BOTTOM_BORDER = path.join(TEMPLATE_ASSETS, 'bottom-border.jpg');
const ZB_BLUE = '0066B3';
const ZB_NAVY = '2B2A80';
const ZB_TITLE_WHITE = 'F4F4F4';
const CONFIDENTIAL_GRAY = '404040';
const CONFIDENTIAL_TEXT = 'CONFIDENTIAL FOR ZIMMER BIOMET INTERNAL USE ONLY';
// Roadmap-table colors (slide 13)
const NAVY = '1C2833';
const SLATE = '2E4053';
const SILVER = 'AAB7B8';
const WHITE = 'FFFFFF';
const ROW_BG = 'FFFFFF';
const ROW_BG_ALT = 'EEF1F3';
async function main() {
const pres = new pptxgen();
pres.layout = 'LAYOUT_16x9'; // 10.0 × 5.625 in — matches the HTML slide CSS
pres.author = 'IT/OT Transformation Team';
pres.subject = 'SCADA IT/OT Transformation — 3-Year Plan';
pres.title = 'SCADA IT/OT Transformation';
// Slide 1 — ZB cover (replaces slide01.html; cover background + title overlay)
console.log('Processing slide 1 (ZB cover)...');
addCoverSlide(pres);
// Slides 216 — html2pptx-rendered content + ZB brand chrome (bottom border, confidentiality, page number)
for (let i = 2; i <= 16; i++) {
const num = String(i).padStart(2, '0');
const file = path.join(SLIDES_DIR, `slide${num}.html`);
console.log(`Processing slide ${i}...`);
const { slide } = await html2pptx(file, pres);
addBrandChrome(slide, i);
if (i === 13) {
addRoadmapTable(pres, slide);
}
}
await pres.writeFile({ fileName: OUTPUT });
console.log(`Saved: ${OUTPUT}`);
}
// Coordinates below are scaled 0.75x from the 13.33×7.5 ZB template (templates/README.md)
// to fit pptxgen's LAYOUT_16x9 (10×5.625 in). The HTML CSS uses 720×405pt = 10×5.625 in.
function addCoverSlide(pres) {
const slide = pres.addSlide();
slide.background = { color: 'FFFFFF' };
slide.addImage({ path: COVER_BG, x: 0, y: 0, w: 10.0, h: 5.625 });
slide.addText('SCADA IT/OT Transformation', {
x: 0.69, y: 1.04, w: 4.5, h: 1.13,
fontFace: 'Arial', fontSize: 24, bold: true,
color: ZB_TITLE_WHITE, valign: 'top',
});
slide.addText('3-Year Plan', {
x: 0.69, y: 2.21, w: 4.5, h: 0.38,
fontFace: 'Arial', fontSize: 14,
color: ZB_TITLE_WHITE,
});
slide.addText('AS OF 2026-04-30', {
x: 0.69, y: 2.59, w: 4.5, h: 0.30,
fontFace: 'Arial', fontSize: 9,
color: ZB_TITLE_WHITE, charSpacing: 1,
});
return slide;
}
function addBrandChrome(slide, pageNumber) {
slide.addImage({
path: BOTTOM_BORDER,
x: 0.01, y: 5.07, w: 9.99, h: 0.555,
});
slide.addText(CONFIDENTIAL_TEXT, {
x: 0.22, y: 4.91, w: 6.0, h: 0.15,
fontFace: 'Arial', fontSize: 6, color: CONFIDENTIAL_GRAY,
});
slide.addText(String(pageNumber), {
x: 9.55, y: 4.91, w: 0.35, h: 0.15,
fontFace: 'Arial', fontSize: 8, color: ZB_NAVY, align: 'right',
});
}
function addRoadmapTable(pres, slide) {
const headerOpts = { bold: true, color: WHITE, fill: { color: NAVY }, fontSize: 10, fontFace: 'Arial', align: 'center', valign: 'middle' };
const wsOpts = { bold: true, fontSize: 9, fontFace: 'Arial', color: NAVY, fill: { color: ROW_BG_ALT }, valign: 'middle' };
const cellOpts = { fontSize: 8, fontFace: 'Arial', color: SLATE, fill: { color: ROW_BG }, valign: 'middle' };
const rows = [
[
{ text: 'Workstream', options: headerOpts },
{ text: 'Year 1 — Foundation', options: headerOpts },
{ text: 'Year 2 — Scale', options: headerOpts },
{ text: 'Year 3 — Completion', options: headerOpts },
],
[
{ text: 'OtOpcUa', options: wsOpts },
{ text: 'Evolve LmxOpcUa; deploy to every site; tier-1 cutover begins; UNS hierarchy walk', options: cellOpts },
{ text: 'Complete tier 1; begin tier 2 (Ignition); long-tail drivers on demand', options: cellOpts },
{ text: 'Complete tier 2; execute tier 3 (System Platform IO) with compliance validation', options: cellOpts },
],
[
{ text: 'Redpanda EventHub', options: wsOpts },
{ text: 'Central cluster in SB; schema registry; canonical model v1 published (FANUC CNC pilot)', options: cellOpts },
{ text: 'Expand topic coverage; WAN-outage replay drill; iterate canonical model', options: cellOpts },
{ text: 'Steady state; canonical model mature; alerting + runbooks hardened', options: cellOpts },
],
[
{ text: 'SnowBridge', options: wsOpts },
{ text: 'Custom build in .NET; first adapter (Historian SQL); first curated tables aligned to canonical model', options: cellOpts },
{ text: 'Add Redpanda adapter; operator UI + approval workflow; canonical-state OEE model; first "not possible before" use case in dev', options: cellOpts },
{ text: 'All source adapters live; hardening. "Not possible before" use case in production — pillar 2 passes', options: cellOpts },
],
[
{ text: 'ScadaBridge Extensions', options: wsOpts },
{ text: 'Deadband / exception-based publishing; EventHub producer with store-and-forward', options: cellOpts },
{ text: 'Roll to all integrated sites; tune deadband from observed Snowflake cost', options: cellOpts },
{ text: 'Steady state; residual cleanup for onboarding / retirement tails', options: cellOpts },
],
[
{ text: 'Site Onboarding', options: wsOpts },
{ text: 'No new sites; define lightweight onboarding pattern for smaller sites', options: cellOpts },
{ text: 'Pilot one smaller site; scale to additional smaller sites', options: cellOpts },
{ text: 'Complete onboarding of all remaining smaller sites — pillar 1 passes', options: cellOpts },
],
[
{ text: 'Legacy Retirement', options: wsOpts },
{ text: 'Inventory populated (3 rows); retire first integration as pattern-proving exercise', options: cellOpts },
{ text: 'Bulk migration against inventory; quarterly burn-down tracking', options: cellOpts },
{ text: 'Inventory reaches zero — pillar 3 passes', options: cellOpts },
],
];
slide.addTable(rows, {
x: 0.42,
y: 1.2,
w: 9.2,
colW: [1.5, 2.566, 2.567, 2.567],
rowH: 0.55,
border: { pt: 0.5, color: SILVER },
});
}
main().catch((err) => { console.error(err); process.exit(1); });