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

2
.gitignore vendored
View File

@@ -1 +1,3 @@
.DS_Store .DS_Store
outputs/workspace/node_modules/
outputs/workspace/.venv/

View File

@@ -1,6 +1,6 @@
# Project Context # Project Context
This directory contains a **3-year plan for transforming and enhancing shopfloor IT/OT interfaces and data collection**. This directory contains a **3-year plan for transforming and enhancing SCADA IT/OT interfaces and data collection**.
Work in this repo focuses on planning, designing, and tracking the multi-year roadmap for modernizing shopfloor systems — bridging IT and OT layers, improving operator interfaces, and upgrading data collection pipelines. Work in this repo focuses on planning, designing, and tracking the multi-year roadmap for modernizing shopfloor systems — bridging IT and OT layers, improving operator interfaces, and upgrading data collection pipelines.
@@ -8,7 +8,7 @@ Work in this repo focuses on planning, designing, and tracking the multi-year ro
Plan content lives in markdown files at the repo root to keep it easy to read and update: Plan content lives in markdown files at the repo root to keep it easy to read and update:
- [`current-state.md`](current-state.md) — snapshot of today's shopfloor IT/OT systems, integrations, data collection, and pain points. - [`current-state.md`](current-state.md) — snapshot of today's SCADA IT/OT systems, integrations, data collection, and pain points.
- [`goal-state.md`](goal-state.md) — target end-state at the close of the 3-year plan, including success criteria. - [`goal-state.md`](goal-state.md) — target end-state at the close of the 3-year plan, including success criteria.
- [`roadmap.md`](roadmap.md) — migration plan / sequencing from current state to goal state over the 3 years. - [`roadmap.md`](roadmap.md) — migration plan / sequencing from current state to goal state over the 3 years.
- [`status.md`](status.md) — working-session bookmark; records where we left off and the top pending items. Not authoritative plan content. - [`status.md`](status.md) — working-session bookmark; records where we left off and the top pending items. Not authoritative plan content.
@@ -20,10 +20,11 @@ Plan content lives in markdown files at the repo root to keep it easy to read an
### Output generation pipeline ### Output generation pipeline
- [`outputs/`](outputs/) — **repeatable PPTX + PDF generation** over the plan markdown. Entry point: [`outputs/README.md`](outputs/README.md) (trigger phrases + regeneration checklist). Structure anchors: [`outputs/presentation-spec.md`](outputs/presentation-spec.md) for the 18-slide mixed-stakeholder deck and [`outputs/longform-spec.md`](outputs/longform-spec.md) for the faithful-typeset long-form PDF. Outputs live under `outputs/generated/`; do not hand-edit them. - [`outputs/`](outputs/) — **repeatable PPTX + PDF generation** over the plan markdown. Entry point: [`outputs/README.md`](outputs/README.md) (trigger phrases + regeneration checklist). Structure anchors: [`outputs/presentation-spec.md`](outputs/presentation-spec.md) for the 16-slide mixed-stakeholder deck and [`outputs/longform-spec.md`](outputs/longform-spec.md) for the faithful-typeset long-form PDF. Outputs live under `outputs/generated/`; do not hand-edit them.
- [`templates/`](templates/) — clean **Zimmer Biomet PowerPoint template** extracted from `example_powerpoint.pptx`: cover slide + content-with-bottom-border layout. See [`templates/README.md`](templates/README.md) for design specs and how to use; rebuild via `python3 templates/build_template.py`.
**Quick reference — regenerating outputs:** **Quick reference — regenerating outputs:**
- `regenerate presentation` — rebuilds the 18-slide PPTX from current plan source files per `outputs/presentation-spec.md` - `regenerate presentation` — rebuilds the 16-slide PPTX from current plan source files per `outputs/presentation-spec.md`
- `regenerate longform` — rebuilds the PDF per `outputs/longform-spec.md` (not yet run) - `regenerate longform` — rebuilds the PDF per `outputs/longform-spec.md` (not yet run)
- `regenerate outputs` — both - `regenerate outputs` — both
- **To change slide structure:** edit `outputs/presentation-spec.md`, then regenerate. Don't edit the PPTX directly. - **To change slide structure:** edit `outputs/presentation-spec.md`, then regenerate. Don't edit the PPTX directly.

View File

@@ -1,6 +1,6 @@
# 3-Year Plan: Shopfloor IT/OT Transformation # 3-Year Plan: SCADA IT/OT Transformation
A 3-year plan for transforming and enhancing shopfloor IT/OT interfaces and data collection — bridging IT and OT layers, improving operator interfaces, and upgrading data collection pipelines. A 3-year plan for transforming and enhancing SCADA IT/OT interfaces and data collection — bridging IT and OT layers, improving operator interfaces, and upgrading data collection pipelines.
## Vision ## Vision

View File

@@ -1,7 +1,7 @@
# Plan — Working Session Status # Plan — Working Session Status
**Saved:** 2026-04-24 (second session of the day) **Saved:** 2026-04-30
**Previous session:** Opus 4.6 (1M context) **Previous session:** Opus 4.7 (1M context)
**Resume with:** start a new Claude Code session in this directory — `CLAUDE.md` and this file provide full context. No session ID needed; the plan is self-contained in the repo. **Resume with:** start a new Claude Code session in this directory — `CLAUDE.md` and this file provide full context. No session ID needed; the plan is self-contained in the repo.
> This file is a **bookmark**, not a replacement for the plan. The authoritative content lives in `CLAUDE.md`, `current-state.md`, `goal-state.md`, `roadmap.md`, and the component detail files under `current-state/` and `outputs/`. Read this file only to find out where we left off. > This file is a **bookmark**, not a replacement for the plan. The authoritative content lives in `CLAUDE.md`, `current-state.md`, `goal-state.md`, `roadmap.md`, and the component detail files under `current-state/` and `outputs/`. Read this file only to find out where we left off.
@@ -13,7 +13,7 @@ The plan is **substantially complete**. All core documents are populated, archit
**What happened since the original session (2026-04-15 through 2026-04-24):** **What happened since the original session (2026-04-15 through 2026-04-24):**
- Integrated OtOpcUa v2 implementation corrections (19 corrections + hardening addendum: ACL model committed, stability tiers, multi-identifier equipment model, driver list confirmed, cutover ownership assigned outside OtOpcUa) - Integrated OtOpcUa v2 implementation corrections (19 corrections + hardening addendum: ACL model committed, stability tiers, multi-identifier equipment model, driver list confirmed, cutover ownership assigned outside OtOpcUa)
- Schemas repo seed contributed by OtOpcUa team at `schemas/` (temporary location) - Schemas repo seed contributed by OtOpcUa team at `schemas/` (temporary location)
- Enterprise shortname resolved to `zb`; Warsaw West buildings confirmed as 5 and 19 - Enterprise shortname resolved to `zb` (= Zimmer Biomet); Warsaw West = numbered buildings (legacy Zimmer); Warsaw North = lettered buildings (legacy Biomet) — confirmed via OpenText Building Use Descriptions doc + project-owner verbal confirmation 2026-04-30
- Equipment protocol survey removed (driver list confirmed directly by v2 team) - Equipment protocol survey removed (driver list confirmed directly by v2 team)
- First PPTX generated (18 slides, mixed-stakeholder deck) - First PPTX generated (18 slides, mixed-stakeholder deck)
- 7 component diagrams created (OtOpcUa, Redpanda, SnowBridge, ScadaBridge dataflow + topology, Snowflake/dbt — dbt diagram is now stale; next regen will replace) - 7 component diagrams created (OtOpcUa, Redpanda, SnowBridge, ScadaBridge dataflow + topology, Snowflake/dbt — dbt diagram is now stale; next regen will replace)
@@ -66,12 +66,11 @@ The plan is **substantially complete**. All core documents are populated, archit
## Top pending items (from most recent status check) ## Top pending items (from most recent status check)
All four items from the previous status check have been **advanced to the point where the next move is a real-world action** (management meeting, reporting-team conversation, field survey, or — for legacy — closed outright). The in-room plan work that could be done without external input has been done. The remaining open items are **external dependencies**, not plan-authoring gaps. The in-room plan work that could be done without external input has been done. The remaining open item is an **external dependency**, not a plan-authoring gap. The BOBJ → Power BI coordination — previously the other open item — is now formally owned by another team and no longer tracked here.
### External-dependency items — waiting on real-world action ### External-dependency items — waiting on real-world action
1. **BOBJ → Power BI coordination with reporting team.** Plan position documented in `goal-state.md` → Strategic Considerations → **Enterprise reporting: BOBJ → Power BI migration (adjacent initiative)** — three consumption paths analyzed, recommended position stated (Path C with Path A as strategic direction), eight questions and a four-bucket decision rubric included. **Action needed:** schedule the coordination conversation with the reporting team; bring back a bucket assignment. Once a bucket is assigned, update `goal-state.md` → Enterprise reporting and, if the outcome is Bucket A or B, update `roadmap.md` → SnowBridge to include reporting-shaped curated tables. 1. **UNS hierarchy snapshot walk.** The protocol survey has been **removed** — the OtOpcUa v2 implementation team committed the core driver list (8 drivers) based on internal knowledge, making a formal protocol survey unnecessary for driver scoping. What remains is the **UNS hierarchy snapshot**: a per-site equipment-instance walk capturing site / area / line / equipment assignments and stable UUIDs, which feeds the initial `schemas` repo hierarchy definition and canonical model. See `goal-state.md`**Unified Namespace (UNS) posture → UNS naming hierarchy standard**. **Initial structural draft seeded 2026-04-30** at `schemas/uns/` — per-site subtree files for Warsaw West (with confirmed buildings 5 and 19), Shannon, Galway, TMT, and Ponce (single-cluster sites with `_default` area). Warsaw North is **not yet drafted** because the campus building set is unknown — that's the primary blocker. Lines and equipment are pending the walk across all sites. Follow-up questions captured in [`schemas/uns/QUESTIONS.md`](schemas/uns/QUESTIONS.md). **Action needed:** (a) answer Warsaw North's building set (Q1) so `warsaw-north.json` can be drafted; (b) ratify naming-convention questions Q2, Q5, Q6, Q9 so the walk's output schema is stable; (c) assign a walk owner; (d) walk System Platform IO config, Ignition OPC UA connections, and ScadaBridge templates across integrated sites within Q1Q2 of Year 1 to populate `lines` and equipment instances. The canonical model v1 cannot be published without the populated hierarchy. **Resolved sub-blocker:** the enterprise shortname is `zb` (no longer a placeholder).
2. **UNS hierarchy snapshot walk.** The protocol survey has been **removed** — the OtOpcUa v2 implementation team committed the core driver list (8 drivers) based on internal knowledge, making a formal protocol survey unnecessary for driver scoping. What remains is the **UNS hierarchy snapshot**: a per-site equipment-instance walk capturing site / area / line / equipment assignments and stable UUIDs, which feeds the initial `schemas` repo hierarchy definition and canonical model. See `goal-state.md`**Unified Namespace (UNS) posture → UNS naming hierarchy standard**. **Action needed:** assign a walk owner; walk System Platform IO config, Ignition OPC UA connections, and ScadaBridge templates across integrated sites within Q1Q2 of Year 1; capture equipment instances at site/area/line/equipment granularity (not protocol — that's already resolved). The canonical model v1 cannot be published without the initial hierarchy snapshot. **Sub-blocker:** the UNS hierarchy's enterprise-level shortname is currently a placeholder (`ent` in goal-state.md); the real shortname needs to be assigned before the initial hierarchy snapshot can be committed to the `schemas` repo.
### Closed since last status check ### Closed since last status check
All closed items below were worked through the same 2026-04-15 session. Grouped roughly chronologically. All closed items below were worked through the same 2026-04-15 session. Grouped roughly chronologically.
@@ -93,7 +92,8 @@ All closed items below were worked through the same 2026-04-15 session. Grouped
**Adjacent initiatives:** **Adjacent initiatives:**
- ~~**BOBJ → Power BI coordination framing.**~~ **Advanced 2026-04-15.** The coordination question was flagged but no plan position existed; now documented as a new Strategic Considerations subsection in `goal-state.md` with three paths, recommended position, and eight questions for the reporting team. Still open: actually having the coordination conversation (tracked above as item #1). - ~~**BOBJ → Power BI coordination framing.**~~ **Advanced 2026-04-15.** The coordination question was flagged but no plan position existed; now documented as a new Strategic Considerations subsection in `goal-state.md` with three paths, recommended position, and eight questions for the reporting team.
- ~~**BOBJ → Power BI coordination conversation.**~~ **Closed 2026-04-30.** Coordination is being handled by another team — no longer an open action item for this plan. Plan's posture is unchanged: the SnowBridge curated layer in Snowflake is shaped to serve Power BI cleanly if and when the reporting team chooses to point there. If that team's decision lands on Path A or C and reporting-shaped curated tables are needed, fold those requirements into the SnowBridge workstream at that time. Resolution recorded in `goal-state.md` → Strategic Considerations → Enterprise reporting → Coordination ownership.
**Output generation pipeline:** **Output generation pipeline:**
@@ -105,10 +105,10 @@ Items that can wait, design details that close during implementation, and delibe
1. Start a new Claude Code session in this directory. `CLAUDE.md` and this file provide full context. 1. Start a new Claude Code session in this directory. `CLAUDE.md` and this file provide full context.
2. Skim this file to re-orient (~2 minutes). 2. Skim this file to re-orient (~2 minutes).
3. Pick one of the three external-dependency items above — or whatever has become most pressing. 3. Pick the open external-dependency item above — or whatever has become most pressing.
4. If you've had the Power BI coordination conversation with the reporting team, bring the answers and I'll fold them into the plan. 4. If a funded physics-simulation / FAT initiative has materialized (out of current plan scope), say so and I'll reuse the meeting brief for a scoping conversation.
5. If a funded physics-simulation / FAT initiative has materialized (out of current plan scope), say so and I'll reuse the meeting brief for a scoping conversation. 5. If the UNS hierarchy walk has been run, bring the data and I'll populate the initial hierarchy snapshot in the `schemas` repo.
6. If the UNS hierarchy walk has been run, bring the data and I'll populate the initial hierarchy snapshot in the `schemas` repo. 6. If the other team's BOBJ → Power BI decision lands on Path A or C and reporting-shaped curated tables are needed, bring the requirements and I'll fold them into the SnowBridge workstream.
7. To regenerate outputs: `regenerate presentation` (PPTX), `regenerate longform` (PDF, not yet run), or `regenerate outputs` (both). See `outputs/README.md` for the full checklist. 7. To regenerate outputs: `regenerate presentation` (PPTX), `regenerate longform` (PDF, not yet run), or `regenerate outputs` (both). See `outputs/README.md` for the full checklist.
8. To hand off a component to an implementation agent, check `handoffs/` for existing handoff docs or ask me to create one. 8. To hand off a component to an implementation agent, check `handoffs/` for existing handoff docs or ask me to create one.

View File

@@ -1,6 +1,6 @@
# Current State # Current State
Snapshot of today's shopfloor IT/OT interfaces and data collection. Keep this updated as discovery progresses. Snapshot of today's SCADA IT/OT interfaces and data collection. Keep this updated as discovery progresses.
> When a section below grows beyond a few paragraphs, break it out into `current-state/<component>.md` and leave a short summary + link here. See [`CLAUDE.md`](CLAUDE.md#breaking-out-components). > When a section below grows beyond a few paragraphs, break it out into `current-state/<component>.md` and leave a short summary + link here. See [`CLAUDE.md`](CLAUDE.md#breaking-out-components).
@@ -10,9 +10,11 @@ Snapshot of today's shopfloor IT/OT interfaces and data collection. Keep this up
- **South Bend Data Center** — primary data center. - **South Bend Data Center** — primary data center.
### Largest Sites ### Largest Sites
- **Warsaw West campus** - **Warsaw West campus** — legacy Zimmer site; numbered buildings (Bldg 2, 5, 7, 10, 19, 20 are the production buildings).
- **Warsaw North campus** - **Warsaw North campus** — legacy Biomet site; lettered buildings (A, B, C, D, E, I).
> The two Warsaw campuses are physically adjacent in Warsaw, IN and trace to the 2015 Zimmer-Biomet merger of two pre-existing sites; the building naming convention (numbers vs. letters) is the durable signal of which legacy organization a building came from.
>
> Largest sites run **one server cluster per production building** (each larger production building gets its own dedicated cluster of equipment servers). > Largest sites run **one server cluster per production building** (each larger production building gets its own dedicated cluster of equipment servers).
### Other Integrated Sites ### Other Integrated Sites
@@ -24,14 +26,21 @@ Snapshot of today's shopfloor IT/OT interfaces and data collection. Keep this up
> Other integrated sites run a **single server cluster** covering the whole site. > Other integrated sites run a **single server cluster** covering the whole site.
### Not Yet Integrated ### Not Yet Integrated
- A number of **smaller sites globally** are **not yet integrated** into the current SCADA system. Known examples include: - A number of additional sites globally are **not yet integrated** into the current SCADA system. Known examples include:
- **Winterthur, Switzerland** — **major manufacturing site** (~251K sq ft active per 2014 corporate real-estate inventory at Sulzer Allee 8). Not a "smaller footprint" site — onboarding it through the standardized stack is comparable in scope to Warsaw or Shannon.
- **Berlin** - **Berlin**
- **Winterthur**
- **Jacksonville** - **Jacksonville**
- **Dover, OH** — ~138K sq ft manufacturing (legacy Zimmer).
- **Statesville, NC** — ~80K sq ft manufacturing (legacy Zimmer).
- **Calabasas, CA**, **Carlsbad, CA** — manufacturing (legacy Zimmer).
- **Mercedita, PR** — ~113K sq ft manufacturing, separate from Ponce.
- **Allendale / Cedar Knolls / Parsippany, NJ** — three sites holding ZTMT (Zimmer Trabecular Metal Technology, the legal entity behind the "TMT" site shortname); the integrated TMT manufacturing scope is at one of these addresses.
- _…others — see note on volatility below._ - _…others — see note on volatility below._
- Characteristic: these tend to be **smaller footprint** sites distributed across multiple regions (EU, US, etc.), likely requiring a lighter-weight onboarding pattern than the large Warsaw campuses. - **Footprint varies widely.** Some of these (Winterthur, Dover, Mercedita) are major manufacturing facilities; others are small. The "lighter-weight onboarding pattern" assumption applies to the smaller ones, not all of them.
- **Volatility note:** the list of smaller sites is **expected to change** — sites may be added, removed, reprioritized, or handled by adjacent programs. This file deliberately **does not** dive into per-site detail (equipment, PLC vendors, network topology, etc.) for the smaller sites because that detail would go stale quickly. Rely on the named examples as illustrative rather than authoritative until a firm enterprise-wide site list is established. - **Volatility note:** the list of smaller sites is **expected to change** — sites may be added, removed, reprioritized, or handled by adjacent programs. This file deliberately **does not** dive into per-site detail (equipment, PLC vendors, network topology, etc.) for the smaller sites because that detail would go stale quickly. Rely on the named examples as illustrative rather than authoritative until a firm enterprise-wide site list is established.
> **Source for the not-yet-integrated site footprint:** OpenText `Global Real Estate Information.xls` (2014 vintage, legacy-Zimmer only — does not cover post-2015 Biomet acquisitions like Shannon and Galway). A current-vintage authoritative manufacturing-site directory still needs to be sourced (likely from SAP / HR / IT, not OpenText).
## Systems & Interfaces ## Systems & Interfaces
### SCADA — Split Stack ### SCADA — Split Stack

View File

@@ -1,6 +1,6 @@
# Goal State (3-Year Target) # Goal State (3-Year Target)
Target end-state for shopfloor IT/OT interfaces and data collection at the end of the 3-year plan. Target end-state for SCADA IT/OT interfaces and data collection at the end of the 3-year plan.
> When a section below grows beyond a few paragraphs, break it out into `goal-state/<component>.md` and leave a short summary + link here. See [`CLAUDE.md`](CLAUDE.md#breaking-out-components). > When a section below grows beyond a few paragraphs, break it out into `goal-state/<component>.md` and leave a short summary + link here. See [`CLAUDE.md`](CLAUDE.md#breaking-out-components).
@@ -8,7 +8,7 @@ Target end-state for shopfloor IT/OT interfaces and data collection at the end o
**Overarching theme: provide a stable, single point of integration between shopfloor OT and enterprise IT.** Every design decision in this plan reduces back to that goal — one bridge (ScadaBridge central), one event backbone (Redpanda), one machine-data path to Snowflake (the integration service), one place for schemas, one set of conventions. "Stable" and "single" are load-bearing words: stability rules out bespoke one-offs that drift, and singleness rules out parallel integration estates that compete with the unified model. When a decision is ambiguous later in the plan, this theme is the tiebreaker. **Overarching theme: provide a stable, single point of integration between shopfloor OT and enterprise IT.** Every design decision in this plan reduces back to that goal — one bridge (ScadaBridge central), one event backbone (Redpanda), one machine-data path to Snowflake (the integration service), one place for schemas, one set of conventions. "Stable" and "single" are load-bearing words: stability rules out bespoke one-offs that drift, and singleness rules out parallel integration estates that compete with the unified model. When a decision is ambiguous later in the plan, this theme is the tiebreaker.
By the end of the 3-year plan, shopfloor IT/OT is built on **one unified integration model across every site** — large campuses (Warsaw West/North), mid-size integrated sites (Shannon, Galway, TMT, Ponce), and the currently-unintegrated smaller sites (Berlin, Winterthur, Jacksonville, and others) all onboard through the same standardized pattern centered on **ScadaBridge** as the IT/OT bridge and **EventHub** as the async backbone. That unified foundation **unlocks enterprise analytics and AI on shopfloor data** by making curated, governed shopfloor data available in **Snowflake** at the right latency and granularity for downstream consumers. In parallel, **legacy point-to-point middleware and bespoke integrations are retired** in favor of ScadaBridge-managed flows, leaving a single, supportable IT/OT integration estate. By the end of the 3-year plan, SCADA IT/OT is built on **one unified integration model across every site** — large campuses (Warsaw West/North), mid-size integrated sites (Shannon, Galway, TMT, Ponce), and the currently-unintegrated smaller sites (Berlin, Winterthur, Jacksonville, and others) all onboard through the same standardized pattern centered on **ScadaBridge** as the IT/OT bridge and **EventHub** as the async backbone. That unified foundation **unlocks enterprise analytics and AI on shopfloor data** by making curated, governed shopfloor data available in **Snowflake** at the right latency and granularity for downstream consumers. In parallel, **legacy point-to-point middleware and bespoke integrations are retired** in favor of ScadaBridge-managed flows, leaving a single, supportable IT/OT integration estate.
**Explicitly in scope:** **Explicitly in scope:**
- (a) Unifying all sites under one integration model. - (a) Unifying all sites under one integration model.
@@ -795,73 +795,12 @@ _TBD — none remaining for this section. Canonical state vocabulary ownership a
### Enterprise reporting: BOBJ → Power BI migration (adjacent initiative) ### Enterprise reporting: BOBJ → Power BI migration (adjacent initiative)
**Status: in-flight, not owned by this plan.** Enterprise reporting is actively migrating from **SAP BusinessObjects** to **Microsoft Power BI** (see [`current-state.md`](current-state.md) → Aveva Historian → Current consumers). This is a reporting-team initiative, not a workstream of this 3-year plan — but it **overlaps with pillar 2** (analytics/AI enablement) in a way that requires explicit coordination, because both initiatives ultimately consume machine data and both ultimately present analytics to business users. **Status: handled by another team (resolved 2026-04-30).** Enterprise reporting is migrating from SAP BusinessObjects to Microsoft Power BI; the coordination question for this plan was whether and how Power BI would consume machine data, and that question is now owned outside this plan.
**This plan's posture:** no workstream is added to `roadmap.md`, and no pillar criterion depends on the Power BI migration landing on any particular schedule. However, the plan's Snowflake-side component (**SnowBridge**, which owns both ingest and transform into curated tables in Snowflake) is shaped so that Power BI can consume it cleanly **if and when** the reporting team decides to point there. Whether Power BI actually does so, on what timeline, and for which reports is **not this plan's decision** — it is a coordination question between this plan and the reporting team. **This plan's posture:** no workstream is added to `roadmap.md`, and no pillar criterion depends on the Power BI migration landing on any particular schedule. The **SnowBridge curated layer in Snowflake** is shaped so that Power BI can consume it cleanly if the reporting team chooses to point there. If they do, and reporting-shaped curated tables are needed, the requirements get folded into the SnowBridge workstream at that time.
#### Three consumption paths for Power BI The previous three-paths analysis (SnowBridge curated layer / Historian direct / both), the recommended position, the eight questions for the reporting team, and the four-bucket decision rubric that lived here were retired when the coordination was handed off. They are preserved in the project's git history (commit before 2026-04-30) for the other team to reference if needed.
The reporting team's Power BI migration can land on any of three paths. Each has different implications for this plan:
**Path A — Power BI reads from the SnowBridge curated layer in Snowflake.**
- *Fit with this plan's architecture:* **best.** Machine data flows through the planned pipeline (equipment → OtOpcUa → layer 3 → ScadaBridge → Redpanda → SnowBridge → Snowflake → Power BI). The architectural diagram in `## Layered Architecture` above already shows this as the intended shape.
- *What it requires from this plan:* the SnowBridge curated layer must be built to serve **reporting**, not only AI/ML. Likely adds **reporting-shaped curated tables or views** tuned for Power BI's query patterns and cross-domain joins. SnowBridge's tag selection must include tags that feed reporting, not only tags that feed the pillar-2 AI use case.
- *What it requires from the reporting team:* capacity and willingness to consume Snowflake as a data source (Power BI has a native Snowflake connector; the learning curve is in the semantic layer, not the connection). Commitment to defer at least the machine-data portion of the BOBJ migration until the SnowBridge curated layer is live — which ties the reporting migration's machine-data cutover to this plan's Year 2+ delivery.
- *Risk:* **timing coupling.** If the reporting team wants to finish their migration inside Year 1, this path doesn't work for machine-data reports. They'd need to hold on machine-data reports and migrate the rest first — which is tenable (reports migrate in waves anyway) but needs agreement.
- *"Not possible before" hook:* Path A opens the door to **cross-domain reports** (machine data joined with MES/ERP data in one query) that BOBJ couldn't easily deliver. This is a strong candidate for pillar 2's "not possible before" use case.
**Path B — Power BI reads from Historian's MSSQL surface directly.**
- *Fit with this plan's architecture:* **neutral.** Historian's SQL interface is its native consumption surface (see [`current-state/legacy-integrations.md`](current-state/legacy-integrations.md) → Deliberately not tracked → Historian SQL reporting consumers). This path is not legacy, not a retirement target.
- *What it requires from this plan:* **nothing.** This plan makes no changes to Historian's MSSQL surface.
- *What it requires from the reporting team:* a pure tool migration (BOBJ → Power BI, same data path). Shortest path to finishing the Power BI migration on the reporting team's preferred timeline.
- *Risk:* **perpetuates the current pattern.** All of the reasons the plan chose a Snowflake-based analytics substrate still apply — cross-domain joins are hard, raw-resolution scale is painful, Historian carries reporting read load on top of its compliance role. Pillar 2's "single analytics substrate" story weakens; the organization ends up running two reporting substrates (Historian SQL for machine data, Snowflake for AI/ML use cases). The machine-data analytics cost moves with Historian rather than with Snowflake's pay-per-use model, which makes the "Snowflake cost story" of this plan less compelling against a baseline that doesn't include reporting load.
- *"Not possible before" hook:* none beyond what Historian SQL already offers.
**Path C — Both, partitioned by report category.**
- *Shape:* compliance/validation reports read Historian directly (because Historian is the authoritative system of record and auditors typically want reports against it); machine-data analytics and cross-domain reports read from the SnowBridge curated layer in Snowflake; reports sourced from Camstar/Delmia/ERP stay on their native connectors. Reports migrate per category.
- *Fit with this plan's architecture:* **pragmatic.** Acknowledges that enterprise reporting is heterogeneous and that one path doesn't fit everything.
- *What it requires from this plan:* Path-A requirements (reporting-shaped SnowBridge curated tables, tag selection in SnowBridge) for the Snowflake portion. No new requirements for the Historian portion.
- *What it requires from the reporting team:* a published **report-category → data-source** rubric that dev teams can use to place new reports on the right path. Needs governance; otherwise new reports land wherever feels easiest at the time.
- *Risk:* **complexity.** Two semantic layers, two connection paths, two mental models for report authors. Worth it only if the volume of cross-domain / AI-adjacent reporting is high enough to justify Path A alongside Path B.
#### Recommended position
**Path C (with Path A as the strategic direction).** Expect most machine-data-heavy reports and all cross-domain reports to move to Snowflake (Path A) over Years 23 as the SnowBridge curated layer matures; expect compliance reports to stay on Historian's SQL surface (Path B) indefinitely because Historian is the authoritative regulatory system of record and moving compliance reporting off it introduces chain-of-custody questions we don't want to open. Path B is **explicitly** not a retirement target (see the carve-out in the legacy inventory), so "staying" is a valid end state for compliance reporting.
**Why not pure Path A:** forces a needless fight over compliance reports that have no business case for leaving Historian.
**Why not pure Path B:** gives up the cross-domain reporting upside that is one of the most compelling answers to "what does pillar 2 get us that we couldn't do before?"
**Why not leave the decision open:** without a plan position, the reporting team will default to Path B by inertia (it's the shortest path and they're already mid-migration). That locks in the weakest of the three outcomes.
#### Questions to take to the reporting team
Use these to land the coordination conversation. Priority order — the first four are the must-answers:
1. **What's your timeline for completing BOBJ → Power BI?** Specifically, when do you expect to have migrated (a) all non-machine-data reports, (b) machine-data reports that read Historian, and (c) cross-domain reports? This tells us whether holding machine-data reports for Path A is even tenable on your side.
2. **Have you made an architectural decision on Power BI's connection to Historian?** Direct MSSQL link, Power BI gateway + on-prem data source, Azure Analysis Services in front of Historian, dataflows, something else? A decision already baked in may be hard to unwind.
3. **Has Snowflake been evaluated as a Power BI data source?** If yes, what were the findings (cost, performance, semantic modeling effort)? If no, would you be open to an evaluation once the first SnowBridge curated layer is live in Year 2?
4. **Is there a business stakeholder asking for cross-domain reports** (machine data joined with MES/ERP/Camstar data in one report) that BOBJ can't deliver today? A named stakeholder here is the strongest signal that Path A is worth the coordination cost.
5. **What's the rough split of your report inventory** between machine-data-heavy reports, compliance reports, cross-domain reports, and pure-enterprise reports? A rough count is enough — we're not looking for a census, just the shape of the portfolio.
6. **Does the reporting team have capacity to learn Snowflake-side semantic modeling against the curated tables SnowBridge writes?** (No dbt on their side either — they'd consume curated tables directly via the Power BI Snowflake connector.) If that's a deal-breaker, Path A is off the table and we should plan for Path B + a parallel Snowflake analytics stack that non-reporting users consume.
7. **Who owns the decision on Power BI's data sources?** Your team, a BI governance body, IT architecture, the CIO? We need to know who to bring into the Path-A discussion if it progresses.
8. **Would you be willing to pilot one cross-domain report on Snowflake (Path A) during Year 2** as a proof point, independent of the rest of the migration? This is a low-commitment way to validate Path A before betting more reports on it.
#### Decision rubric
After the conversation, place the outcome into one of these buckets:
- **Bucket A — Full Path A commitment.** Reporting team commits to migrating all non-compliance reports to Snowflake over Years 23. → Update `roadmap.md` (SnowBridge workstream) to include reporting-shaped curated tables in Year 2. Update `goal-state.md` to name cross-domain reporting as a pillar 2 "not possible before" candidate.
- **Bucket B — Path C commitment.** Reporting team commits to the hybrid path with a published report-category rubric. → Same roadmap updates as A, plus document the rubric as a link from this subsection.
- **Bucket C — Path B lock-in.** Reporting team declines Path A for cost, capacity, or timing reasons. → Update `goal-state.md` here to record the decision. No roadmap changes. Pillar 2's "not possible before" use case must come from a different source (e.g., predictive maintenance, OEE anomaly detection) because cross-domain reporting is off the table.
- **Bucket D — Conversation inconclusive.** Reporting team needs more time, or the decision is above their level. → Schedule follow-up. Note which questions were answered and which are still open.
#### What this does NOT decide
- Whether the reporting team completes their Power BI migration (their decision).
- Whether Historian's SQL surface is ever retired (no — it's the compliance system of record).
- Whether this plan's SnowBridge curated layer in Snowflake supports Power BI (yes, it can — the question is only whether the reporting team will consume it).
- Whether the SnowBridge's tag selection is driven by reporting requirements (partly — SnowBridge's selection is governed by blast-radius approval, so reporting-team requests are handled through the same workflow as any other).
_TBD — name and sponsor of the Power BI migration initiative; named owner on the reporting team for this coordination; whether a joint session between this plan's build team and the reporting team has been scheduled; whether a Power BI + Snowflake proof-of-concept can fit into Year 1 as a forward-looking test, independent of the rest of Year 1's scope._
## Non-Goals ## Non-Goals

View File

@@ -50,7 +50,7 @@ Expected: `DESIGN.md IMPLEMENTATION-PLAN.md diagrams generated` (README, spec
--- ---
## Task 2: outputs/presentation-spec.md — 18-slide PPTX structure anchor ## Task 2: outputs/presentation-spec.md — 16-slide PPTX structure anchor
**Files:** **Files:**
- Create: `outputs/presentation-spec.md` - Create: `outputs/presentation-spec.md`
@@ -178,11 +178,11 @@ Expected: `DESIGN.md IMPLEMENTATION-PLAN.md diagrams generated` (README, spec
**Step 2:** Read every source file named in the spec. **Step 2:** Read every source file named in the spec.
**Step 3:** For each of the 18 slides, populate content per the spec's rules. **Step 3:** For each of the 16 slides, populate content per the spec's rules.
**Step 4:** Invoke `document-skills:pptx` with: **Step 4:** Invoke `document-skills:pptx` with:
- Theme: `document-skills:theme-factory` default professional - Theme: `document-skills:theme-factory` default professional
- Title: "Shopfloor IT/OT Transformation — 3-Year Plan" - Title: "SCADA IT/OT Transformation — 3-Year Plan"
- Output path: `outputs/generated/plan-presentation.pptx` - Output path: `outputs/generated/plan-presentation.pptx`
- Diagram images from `outputs/diagrams/` - Diagram images from `outputs/diagrams/`

View File

@@ -2,7 +2,7 @@
This directory holds the **generation pipeline** for two output artifacts derived from the plan markdown source: This directory holds the **generation pipeline** for two output artifacts derived from the plan markdown source:
1. A **mixed-stakeholder PowerPoint** (18 slides) — [`generated/plan-presentation.pptx`](generated/) 1. A **mixed-stakeholder PowerPoint** (16 slides) — [`generated/plan-presentation.pptx`](generated/)
2. A **long-form PDF** of the authoritative plan content — [`generated/plan-longform.pdf`](generated/) 2. A **long-form PDF** of the authoritative plan content — [`generated/plan-longform.pdf`](generated/)
Outputs are regenerated on demand from the current markdown source. Repeatability is anchored by **spec files** (this directory), not by prompts. See [`DESIGN.md`](DESIGN.md) for the full design rationale. Outputs are regenerated on demand from the current markdown source. Repeatability is anchored by **spec files** (this directory), not by prompts. See [`DESIGN.md`](DESIGN.md) for the full design rationale.

View File

@@ -12,9 +12,9 @@
| **Theme** | `document-skills:theme-factory` default professional theme (serif body font). Override by adding a `**Theme override:**` line here. | | **Theme** | `document-skills:theme-factory` default professional theme (serif body font). Override by adding a `**Theme override:**` line here. |
| **Page size** | US Letter (8.5" × 11"). Change to A4 if distribution is primarily non-US. | | **Page size** | US Letter (8.5" × 11"). Change to A4 if distribution is primarily non-US. |
| **Margins** | 1" on all sides | | **Margins** | 1" on all sides |
| **Running header** | Chapter title (left-aligned) · "Shopfloor IT/OT Transformation — 3-Year Plan" (right-aligned) | | **Running header** | Chapter title (left-aligned) · "SCADA IT/OT Transformation — 3-Year Plan" (right-aligned) |
| **Running footer** | Page number (center) · As-of date (right) | | **Running footer** | Page number (center) · As-of date (right) |
| **Title on cover** | "Shopfloor IT/OT Transformation" | | **Title on cover** | "SCADA IT/OT Transformation" |
| **Subtitle on cover** | "3-Year Plan" | | **Subtitle on cover** | "3-Year Plan" |
| **Cover as-of line** | "As of {{regeneration-date}}" | | **Cover as-of line** | "As of {{regeneration-date}}" |
| **Cover abstract** | A single paragraph lifted **verbatim** from [`../goal-state.md`](../goal-state.md) → **Vision** (the opening paragraph). Do not paraphrase. | | **Cover abstract** | A single paragraph lifted **verbatim** from [`../goal-state.md`](../goal-state.md) → **Vision** (the opening paragraph). Do not paraphrase. |
@@ -52,6 +52,19 @@ These files are **not** part of the PDF — do not include them even if they see
If a new plan file is added to the repo and should be in the PDF, **add it here**, under Document structure, as a new chapter or appendix — not in the source file and not in the prompt. If a new plan file is added to the repo and should be in the PDF, **add it here**, under Document structure, as a new chapter or appendix — not in the source file and not in the prompt.
### Section-level exclusions (render-time)
These **subsections** of an included source file are skipped when rendering. The source markdown is not edited — the renderer detects each heading by exact text match and omits the section through to the next sibling-or-higher heading. If the heading text in the source changes, this list must be updated.
| Source file | Heading to skip | Why excluded |
|---|---|---|
| [`../goal-state.md`](../goal-state.md) | `## Non-Goals` | Non-goals are still authoritative plan content (visible to anyone reading `goal-state.md` directly), but they were dropped from the long-form PDF + presentation in the 2026-04-30 trim — out-of-scope items don't need to be enumerated for the long-form audience the way they do for an in-room slide-deck audience. |
| [`../goal-state.md`](../goal-state.md) | `### Enterprise reporting: BOBJ → Power BI migration (adjacent initiative)` | Coordination ownership was transferred to another team on 2026-04-30 (see the "Coordination ownership (resolved 2026-04-30)" note retained at the end of the section). The full three-paths / eight-questions / decision-rubric analysis is preserved in `goal-state.md` for the other team and for future reference, but is not part of the long-form PDF anymore. |
**Sub-heading scope:** when a `##` is excluded, every `###`/`####` under it is also excluded (the renderer walks until it hits another `##` or the end of file). Same rule applies one level down for `###`. Be deliberate when adding to this list: excluding a `##` removes more than excluding a `###`.
If you want to **re-include** an excluded section, remove it from this table — do not modify the source file.
## Transformations applied during rendering ## Transformations applied during rendering
The narrow set of changes applied to source markdown when producing the PDF. Source markdown files are **not edited** — transformations are applied at render time. The narrow set of changes applied to source markdown when producing the PDF. Source markdown files are **not edited** — transformations are applied at render time.
@@ -128,7 +141,7 @@ Markdown `>` block quotes render as indented italic or colored blocks per the th
### Cover page ### Cover page
``` ```
[Large type] Shopfloor IT/OT Transformation [Large type] SCADA IT/OT Transformation
[Medium type] 3-Year Plan [Medium type] 3-Year Plan
[Small type] As of {{regeneration-date}} [Small type] As of {{regeneration-date}}

View File

@@ -12,7 +12,7 @@
| **Theme** | `document-skills:theme-factory` default professional theme. Clean, neutral, no custom branding on first pass. Override: add a `**Theme override:**` line here with the target preset if you want different. | | **Theme** | `document-skills:theme-factory` default professional theme. Clean, neutral, no custom branding on first pass. Override: add a `**Theme override:**` line here with the target preset if you want different. |
| **Body font** | Theme default sans-serif | | **Body font** | Theme default sans-serif |
| **Accent color** | Theme default | | **Accent color** | Theme default |
| **Source of truth for title** | "Shopfloor IT/OT Transformation — 3-Year Plan" | | **Source of truth for title** | "SCADA IT/OT Transformation — 3-Year Plan" |
| **Subtitle template** | "As of {{date}}" where `{{date}}` is the regeneration timestamp in `YYYY-MM-DD` form | | **Subtitle template** | "As of {{date}}" where `{{date}}` is the regeneration timestamp in `YYYY-MM-DD` form |
## Truncation rules (apply to every slide unless overridden per-slide) ## Truncation rules (apply to every slide unless overridden per-slide)
@@ -29,7 +29,7 @@
If `document-skills:pptx` cannot render a requested layout: If `document-skills:pptx` cannot render a requested layout:
- **3-column content → single column with visual separators.** Applies to slide 5 (Three Pillars). - **3-column content → single column with visual separators.** Applies to slide 5 (Three Pillars).
- **2-column content → single column with a horizontal rule.** Applies to slide 16 (Open Coordination Items). - **2-column patterns → single column.** Applies to slide 16 (Digital Twin Scope) where the two access-control patterns are shown side-by-side; collapse to stacked patterns if the side-by-side layout doesn't fit at readable type size.
- **Diagram + caption → caption only with a "(Diagram not yet available — render `<path>.mmd` and re-run)" note.** Applies to slides 8 and 9 until the PNG files exist in `diagrams/`. - **Diagram + caption → caption only with a "(Diagram not yet available — render `<path>.mmd` and re-run)" note.** Applies to slides 8 and 9 until the PNG files exist in `diagrams/`.
- **Multi-column table that overflows → split across two slides labeled "N/2" and "2/2".** Applies to slide 13 (Roadmap grid) if 7 rows × 3 columns doesn't fit one slide at readable type size. - **Multi-column table that overflows → split across two slides labeled "N/2" and "2/2".** Applies to slide 13 (Roadmap grid) if 7 rows × 3 columns doesn't fit one slide at readable type size.
@@ -41,7 +41,7 @@ If `document-skills:pptx` cannot render a requested layout:
|---|---| |---|---|
| **Layout** | Title | | **Layout** | Title |
| **Source** | — (no source file) | | **Source** | — (no source file) |
| **Content** | Title: *"Shopfloor IT/OT Transformation"* · Subtitle: *"3-Year Plan"* · Footer: *"As of {{regeneration-date}}"* | | **Content** | Title: *"SCADA IT/OT Transformation"* · Subtitle: *"3-Year Plan"* · Footer: *"As of {{regeneration-date}}"* |
| **Rules** | No bullets, no extra content. Title slide is deliberately minimal. | | **Rules** | No bullets, no extra content. Title slide is deliberately minimal. |
## Slide 2 — Executive Summary ## Slide 2 — Executive Summary
@@ -166,31 +166,14 @@ If `document-skills:pptx` cannot render a requested layout:
| **Source** | [`../current-state/legacy-integrations.md`](../current-state/legacy-integrations.md) → Current inventory | | **Source** | [`../current-state/legacy-integrations.md`](../current-state/legacy-integrations.md) → Current inventory |
| **Population** | 3 bullets — one per legacy integration: (1) **LEG-001** Aveva Web API → Delmia DNC (bidirectional orchestrated handshake; harder retirement — requires ScadaBridge scripts to re-implement System Platform parse logic). (2) **LEG-002** Aveva Web API ← Camstar MES (Camstar-initiated; easier retirement — ScadaBridge already has native Camstar path; requires Camstar-side reconfiguration). (3) **LEG-003** System Platform → custom email notification service (easier retirement — ScadaBridge native notifications already exist). Callout at bottom: *"Historian MSSQL reporting surface (BOBJ / Power BI) is explicitly carved out as not legacy — see `legacy-integrations.md` → Deliberately not tracked."* | | **Population** | 3 bullets — one per legacy integration: (1) **LEG-001** Aveva Web API → Delmia DNC (bidirectional orchestrated handshake; harder retirement — requires ScadaBridge scripts to re-implement System Platform parse logic). (2) **LEG-002** Aveva Web API ← Camstar MES (Camstar-initiated; easier retirement — ScadaBridge already has native Camstar path; requires Camstar-side reconfiguration). (3) **LEG-003** System Platform → custom email notification service (easier retirement — ScadaBridge native notifications already exist). Callout at bottom: *"Historian MSSQL reporting surface (BOBJ / Power BI) is explicitly carved out as not legacy — see `legacy-integrations.md` → Deliberately not tracked."* |
## Slide 16 — Open Coordination Items ## Slide 16 — Digital Twin Scope
| Property | Value | | Property | Value |
|---|---| |---|---|
| **Layout** | 2-column content (fallback: single column with horizontal rule) | | **Layout** | Lede + 2-column pattern cards (fallback: stacked patterns) |
| **Source** | [`../goal-state.md`](../goal-state.md) → **Strategic Considerations (Adjacent Asks)** | | **Source** | [`../goal-state.md`](../goal-state.md) → **Strategic Considerations → Digital Twin** |
| **Population** | **Left column — Digital twin (scope: two access-control patterns):** 4 bullets: (1) Scope is definitive — not a committed workstream, not a new component; (2) Pattern 1 — environment-lifecycle promotion without reconfiguration (ACL flip on write authority); (3) Pattern 2 — safe read-only consumption for KPI / monitoring systems (structural zero-write-path guarantee); (4) Both patterns are delivered by already-committed architecture (OtOpcUa ACL model + canonical model + single-connection-per-equipment). **Right column — BOBJ → Power BI:** 4 bullets: (1) In-flight reporting initiative, not owned by this plan; (2) Three consumption paths analyzed (SnowBridge curated layer in Snowflake / Historian direct / both); (3) Recommended position: Path C — hybrid, with Path A as strategic direction; (4) Next: schedule coordination conversation with reporting team — 8 questions ready in `goal-state.md`. | | **Population** | **Lede:** scope is two access-control patterns — not a new component, not a new workstream — both delivered by already-committed architecture (OtOpcUa ACL model + canonical model + single-connection-per-equipment). **Pattern 1 card — Environment-lifecycle promotion:** promote between dev / staging / prod by flipping write-authority ACLs against stable equipment UUIDs; no client reconfiguration. **Pattern 2 card — Safe read-only consumption:** KPI / monitoring consumers get read-only grants with a structural zero-write-path guarantee — no equipment-side session for them to misuse. **Footnote:** physics simulation, FAT, commissioning emulation are out of scope; would be separate funded adjacent initiatives. |
| **Notes** | This is the deck's closing slide. It exists because digital-twin scope was the most contested decision in the plan; making the resolved scope concrete prevents the topic from re-opening unnecessarily. The previous BOBJ → Power BI column was removed when that coordination was transferred to another team (2026-04-30). The previous Non-Goals and Asks & Next Steps slides were removed in the same pass. |
## Slide 17 — Non-Goals
| Property | Value |
|---|---|
| **Layout** | Content (bulleted) |
| **Source** | [`../goal-state.md`](../goal-state.md) → **Non-Goals** |
| **Population** | 6 bullets, one line each: (1) Operator UX modernization — deprioritized against the three pillars; (2) Support staffing decisions — other teams; (3) Licensing strategy — not tracked; (4) Self-hosted orchestrator selection — chosen outside this plan; (5) VM-level DR — out of scope for Redpanda; (6) Physical network segmentation — out of scope. |
| **Notes** | This slide is important for managing stakeholder expectations — what the plan *does not* commit to is as load-bearing as what it does commit to. Do not drop this slide even if time is short. |
## Slide 18 — Asks & Next Steps
| Property | Value |
|---|---|
| **Layout** | Content (bulleted) |
| **Source** | [`../status.md`](../status.md) → **Top pending items** + inferred from [`../roadmap.md`](../roadmap.md) → Year 1 |
| **Population** | 4 bullets: (1) Sponsor confirmation + Year 1 funding commitment; (2) Named owners for each of the 6 workstreams (build team alignment); (3) Power BI coordination conversation with reporting team — schedule; (4) UNS hierarchy snapshot walk owner named (Q1Q2 Year 1 prerequisite for canonical model v1 publication). |
| **Notes** | This is the closer slide. Each bullet should be a discrete ask with a clear "who needs to do what" so the audience leaves with action. |
--- ---
@@ -203,4 +186,4 @@ If `document-skills:pptx` cannot render a requested layout:
## Slide-count budget ## Slide-count budget
Current: **18 slides.** If additions push this above 22, reconsider whether the deck is still "mixed-stakeholder" or has quietly become a build-team deck. The mixed-stakeholder audience tops out around 20 slides before attention fragments; a build-team deck belongs in a separate spec file (`build-team-spec.md` or similar) feeding a second generated output. Current: **16 slides.** If additions push this above 22, reconsider whether the deck is still "mixed-stakeholder" or has quietly become a build-team deck. The mixed-stakeholder audience tops out around 20 slides before attention fragments; a build-team deck belongs in a separate spec file (`build-team-spec.md` or similar) feeding a second generated output.

95
outputs/run-log.md Normal file
View File

@@ -0,0 +1,95 @@
# Output Regeneration Run Log
Append-only audit log — one entry per regeneration. Newest entry on top.
---
## 2026-04-30 (later) — `regenerate presentation` (deck trimmed to 16 slides)
**Artifact:** `generated/plan-presentation.pptx`**16 slides** (down from 18).
**Structural changes:**
- **Removed slide 17 (Non-Goals)** and **slide 18 (Asks & Next Steps)** — the deck no longer ships with these closing slides; the previous content lives in `goal-state.md` → Non-Goals and `status.md` → Top pending items respectively, and audiences who need them can read those directly.
- **Slide 16 reshaped from "Open Coordination Items" (2-column: Digital twin + BOBJ→Power BI) to "Digital Twin Scope" (single-column with two pattern cards).** The BOBJ→Power BI column was dropped because that coordination is now owned by another team (recorded in `goal-state.md` 2026-04-30 update).
- **Source files removed:** `outputs/workspace/slides/slide17.html` and `slide18.html` deleted; `slide16.html` rewritten.
- **`generate.js` loop bound updated** from 18 → 16.
- **Spec updated:** `outputs/presentation-spec.md` slide-count budget changed (18 → 16); slide 16 entry rewritten; slide 17 and 18 entries removed; fallback-rendering rule for slide 16 updated to reflect the new 2-pattern-card layout.
- **Documentation updated:** "18-slide" → "16-slide" in `CLAUDE.md`, `outputs/README.md`, `outputs/IMPLEMENTATION-PLAN.md`.
**Closing slide judgment:** the deck now ends on Digital Twin Scope rather than Asks & Next Steps. Rationale: the asks have been resolved (Power BI moved to another team; UNS hierarchy walk has the questions doc) so the asks slide had nothing live left on it; Digital Twin Scope is a load-bearing settled decision worth ending on so the topic doesn't re-open.
**Longform spec aligned in the same pass.** `longform-spec.md` now carries a "Section-level exclusions (render-time)" rule that skips `goal-state.md → ## Non-Goals` and `goal-state.md → ### Enterprise reporting: BOBJ → Power BI migration (adjacent initiative)` from the long-form PDF. ("Asks & Next Steps" never appeared in the longform anyway — it was synthesized from `status.md` which is already excluded.)
**`goal-state.md` BOBJ section pruned in the same pass.** The full three-paths analysis, recommended position, eight questions for the reporting team, and four-bucket decision rubric (lines 796864 prior, ~69 lines) were retired when the coordination was handed to another team. Replaced with a concise stub (5 lines) that preserves the section heading (so the longform exclusion rule still matches), the plan's posture (SnowBridge curated layer is shaped to support Power BI consumption if the reporting team points there), and a pointer to git history for anyone who needs the full retired analysis.
---
## 2026-04-30 — `regenerate presentation` (ZB template integration)
**Artifact:** `generated/plan-presentation.pptx` (18 slides, 16:9, **now ZB-branded** via `templates/` integration). 2.0 MB, up from 370 KB at the prior run because the cover background image and bottom-border strip are embedded.
**Changes since prior run:**
- **Cover slide (slide 1) replaced with a ZB-branded cover.** `slide01.html` is no longer rendered through `html2pptx`; `generate.js` now constructs the cover directly from `templates/assets/cover-background.jpeg` plus a title overlay (white #F4F4F4, Arial Bold 24pt, positioned at scaled-down ZB-template coordinates).
- **Brand chrome added to every content slide (218):** the bottom-border strip from `templates/assets/bottom-border.jpg` (positioned at 0.01", 5.07" — 0.75× scaled from the template's 13.33×7.5 to fit the existing 10×5.625 layout), "CONFIDENTIAL FOR ZIMMER BIOMET INTERNAL USE ONLY" text (6pt gray) above the strip, and a single page number (8pt ZB navy #2B2A80) in the lower-right.
- **Reskinned all 17 content-slide HTMLs** from the original navy/amber palette to the ZB palette via deterministic find-replace: navy header band (#1C2833) → ZB blue (#0066B3) → ultimately removed entirely; off-white backgrounds → pure white; slate text (#2E4053) → dark gray (#333333); amber accent (#D4843D) → ZB blue (#0066B3); dark navy text → ZB navy (#2B2A80).
- **Removed the dark blue header band** entirely; titles are now ZB blue text on white with a 1.5pt blue underline. Page numbers are no longer in the markup (they live only in the JS-added chrome) — eliminates the duplicate-page-number bug introduced when chrome was first added.
- **`generate.js` documents the scaling rationale** inline so future maintainers don't get confused about why coordinates aren't the template's published values.
**Source plan state at time of regeneration:**
Same as the 2026-04-24 run plus: Warsaw West/North campus split clarified (numbered = legacy Zimmer = West; lettered = legacy Biomet = North); UNS hierarchy initial draft seeded across all 6 integrated sites; BOBJ→Power BI coordination ownership transferred to another team.
**Warnings / deviations:**
- **HTML CSS still authored to 720×405pt (10×5.625").** pptxgen's `LAYOUT_16x9` matches that. The ZB template publishes coordinates at 13.33×7.5, so `generate.js` scales by 0.75× when placing chrome. If the slides are ever rebuilt at the template's native 13.33×7.5, drop the scaling and use template coords as published.
- **Slide 13 was 1.5pt overflow on first attempt** (new title section was 10pt taller than the old header band). Tightened header padding and reduced title font from 24pt back to 22pt; now fits.
- **Diagrams still missing** (`architecture-layers.png`, `end-to-end-flow.png`) — same fallback pattern as before; not reapplied this run.
---
## 2026-04-24 — `regenerate presentation`
**Artifact:** `generated/plan-presentation.pptx` (18 slides, 16:9, theme-factory default professional — Classic Blue + amber accent palette).
**Source plan state at time of regeneration:**
- 6 workstreams in `roadmap.md` (down from 7 — Snowflake dbt Transform Layer workstream collapsed into SnowBridge earlier in the session).
- 3 legacy integrations in `current-state/legacy-integrations.md` (LEG-001 Delmia DNC, LEG-002 Camstar MES, LEG-003 custom email notification service).
- Digital-twin scope finalized as two access-control patterns (environment-lifecycle promotion; safe read-only KPI/monitoring consumption).
- SnowBridge scope finalized as ingest + in-process .NET transform + curated-table write (no dbt, no external orchestrator, no Snowflake landing tier).
- Canonical model has three surfaces: OtOpcUa equipment namespace, Redpanda topics + Protobuf schemas, SnowBridge curated layer in Snowflake.
**Slides populated:**
1. Title — "Shopfloor IT/OT Transformation · 3-Year Plan · As of 2026-04-24"
2. Executive Summary — Vision + three-pillar criteria + out-of-scope line
3. Today's Reality — 5 pain points synthesized from `current-state.md`
4. Vision — full-slide quote + 3 sub-bullets
5. Three Pillars — 3-column layout (Unification / Analytics-AI / Legacy Retirement)
6. Enterprise Layout — 4 tier groups (primary / largest / integrated / not yet integrated)
7. Today's Systems — 6 systems summarized
8. Goal State: Layered Architecture — rendered as a stacked-layer visualization + "sole IT↔OT crossing" callout. **Placeholder diagram** for `architecture-layers.png` — not rendered; layer stack is the fallback visualization.
9. End-to-End Data Flow — rendered as a 2-row node flow (OT row + IT row) with arrows + caption. **Placeholder diagram** for `end-to-end-flow.png` — not rendered; node-flow layout is the fallback visualization.
10. OtOpcUa — the Unification Layer — 6 bullets
11. Analytics Stack: SnowBridge + Snowflake — 6 bullets, reflecting SnowBridge-owns-transform scope
12. Redpanda EventHub — 6 bullets
13. 3-Year Roadmap grid — 6×3 table (workstreams × years), cells truncated to single most load-bearing commitment per cell
14. Year 1 Focus — 6 bullets, one per workstream
15. Pillar 3: Legacy Retirement — 3 cards (LEG-001, LEG-002, LEG-003) + Historian carve-out callout
16. Open Coordination Items — 2-column (Digital twin scope / BOBJ → Power BI)
17. Non-Goals — 6 bullets
18. Asks & Next Steps — 4 asks (sponsor, build-team owners, Power BI conversation, UNS hierarchy walk owner)
**Warnings / deviations:**
- **Diagrams `architecture-layers.png` and `end-to-end-flow.png` do not exist** in `diagrams/`. Spec fallback says "render a placeholder box with a text prompt." Deviation: slides 8 and 9 use **content-based substitutes** (stacked-layer visualization on 8; horizontal node-flow on 9) rather than literal placeholder boxes. This is a richer substitute than the spec's minimal fallback but still not the diagrams themselves. To align exactly with the spec, author the `.mmd` sources in `diagrams/` and re-run.
- **Stale diagram file still in `diagrams/`: `snowflake-dbt-dataflow.png`** — not referenced by this regeneration, but should be renamed / replaced when a matching "SnowBridge curated layer dataflow" component diagram is authored. Flagged for attention; not a blocker for this run.
- **`<code>` tag not supported by html2pptx** — initial rendering of slide 12 dropped content inside `<code>` tags (the `schemas` repo name, `buf` CI tool, and `{domain}.{entity}.{event-type}` topic format). Fixed by replacing `<code>` with `<b>` for those tokens. Watch for this pattern in future slides.
- **Text elements cannot carry CSS borders** — html2pptx validator rejects borders on `<p>` / `<h1>` / `<ul>`. Vision and tagline elements on slides 2, 4, 10, 11, 12 were wrapped in `<div>`s carrying the border-left accent.
- **Slide 1 title wrap** — initial 44pt title wrapped to two lines and overlapped the subtitle. Reduced to 32pt with explicit vertical spacing; fits on one line at 720pt width.
**Intermediate artifacts removed** from `workspace/` after validation: `plan-presentation.pdf`, `slide-NN.jpg`, `check-NN.jpg`. Retained: `slides/` HTML, `generate.js`, `node_modules/`, `package*.json`.
**Diff from prior run (2026-04-15):** deck reflects two scope changes that happened between runs — digital-twin scope narrowed (slide 16 left column rewritten around the two access-control patterns); SnowBridge expanded to own transformation (slides 11, 13 row 3, 14 bullet 3 all updated); workstream count dropped from 7 to 6 (slides 13, 14, 18 all reflect this); enterprise shortname resolved to `zb` and Warsaw West buildings confirmed as 5 and 19 (slide 6).

View File

@@ -0,0 +1,155 @@
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); });

761
outputs/workspace/package-lock.json generated Normal file
View File

@@ -0,0 +1,761 @@
{
"name": "workspace",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"playwright": "^1.59.1",
"pptxgenjs": "^4.0.1",
"sharp": "^0.34.5"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
"cpu": [
"ppc64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
"cpu": [
"riscv64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
"cpu": [
"ppc64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
"cpu": [
"riscv64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@types/node": {
"version": "22.19.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/https": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
"integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==",
"license": "ISC"
},
"node_modules/image-size": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=16.x"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/playwright": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.59.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/pptxgenjs": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pptxgenjs/-/pptxgenjs-4.0.1.tgz",
"integrity": "sha512-TeJISr8wouAuXw4C1F/mC33xbZs/FuEG6nH9FG1Zj+nuPcGMP5YRHl6X+j3HSUnS1f3at6k75ZZXPMZlA5Lj9A==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.8.1",
"https": "^1.0.0",
"image-size": "^1.2.1",
"jszip": "^3.10.1"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"optional": true
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
}
}
}

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"playwright": "^1.59.1",
"pptxgenjs": "^4.0.1",
"sharp": "^0.34.5"
}
}

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #1C2833; font-family: Arial, sans-serif;
display: flex; color: #F4F6F6;
}
.wrap { width: 720pt; padding: 140pt 60pt 0 60pt; text-align: center; }
.accent-bar { width: 100pt; height: 4pt; background: #D4843D; margin: 0 auto 22pt auto; }
h1 { font-size: 32pt; font-weight: bold; margin: 0 0 12pt 0; letter-spacing: -0.5pt; text-align: center; }
.subtitle { font-size: 20pt; color: #AAB7B8; margin: 0 0 44pt 0; font-weight: normal; text-align: center; }
.date { font-size: 11pt; color: #AAB7B8; margin: 0; letter-spacing: 1pt; text-align: center; }
</style>
</head>
<body>
<div class="wrap">
<div class="accent-bar"></div>
<h1>SCADA IT/OT Transformation</h1>
<p class="subtitle">3-Year Plan</p>
<p class="date">AS OF 2026-04-24</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; }
.vision-wrap { border-left: 3pt solid #0066B3; padding-left: 12pt; margin: 0 0 14pt 0; }
.vision { color: #2B2A80; font-size: 14pt; font-style: italic; margin: 0; }
ul { margin: 6pt 0 0 22pt; padding: 0; }
li { font-size: 12pt; margin-bottom: 8pt; line-height: 1.35; }
</style>
</head>
<body>
<div class="header">
<h1>Executive Summary</h1>
</div>
<div class="body">
<div class="vision-wrap"><p class="vision">Vision: stable, single point of integration between shopfloor OT and enterprise IT.</p></div>
<ul>
<li><b>Pillar 1 — Unification:</b> 100% of sites on the standardized stack at end of Year 3.</li>
<li><b>Pillar 2 — Analytics/AI enablement:</b> ≤15-minute analytics SLO; one &quot;not possible before&quot; use case in production.</li>
<li><b>Pillar 3 — Legacy retirement:</b> bespoke IT↔OT integration inventory driven to zero.</li>
<li><b>Out of scope:</b> operator UX modernization, licensing strategy, VM-level DR, physical network segmentation.</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 24pt 40pt; flex: 1; color: #333333; }
ul { margin: 0 0 0 22pt; padding: 0; }
li { font-size: 13pt; margin-bottom: 11pt; line-height: 1.35; }
.footer { padding: 6pt 40pt 10pt 40pt; color: #AAB7B8; font-size: 9pt; font-style: italic; }
.footer p { margin: 0; }
</style>
</head>
<body>
<div class="header">
<h1>Today's Reality</h1>
</div>
<div class="body">
<ul>
<li><b>Split SCADA stack</b> — Aveva System Platform for validated data; Ignition for KPI monitoring.</li>
<li><b>Central Ignition</b> in South Bend holds direct OPC UA sessions to site equipment over the WAN.</li>
<li><b>Multiple concurrent OPC UA sessions</b> to the same equipment from different consumers.</li>
<li><b>Three legacy IT↔OT integrations</b> bypass ScadaBridge central (Delmia DNC, Camstar MES, custom email service).</li>
<li><b>Fragmented data access</b> — consumers reading the same tag can see different values depending on sampling/deadband settings.</li>
</ul>
</div>
<div class="footer"><p>Full detail: current-state.md → Systems &amp; Interfaces, Equipment OPC UA.</p></div>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 40pt 60pt; flex: 1; color: #333333; display: flex; flex-direction: column; justify-content: center; }
.quote-wrap { border-left: 5pt solid #0066B3; padding-left: 18pt; margin: 0 0 28pt 0; }
.quote { font-size: 22pt; color: #2B2A80; font-weight: bold; line-height: 1.3; margin: 0; }
ul { margin: 0 0 0 22pt; padding: 0; }
li { font-size: 13pt; margin-bottom: 9pt; line-height: 1.35; color: #333333; }
</style>
</head>
<body>
<div class="header">
<h1>Vision</h1>
</div>
<div class="body">
<div class="quote-wrap"><p class="quote">A stable, single point of integration between shopfloor OT and enterprise IT.</p></div>
<ul>
<li>Every cross-domain path flows through ScadaBridge central — the sole sanctioned IT↔OT crossing point.</li>
<li>Equipment data is site-local, canonically modeled, and shared through a single session per device.</li>
<li>Tiebreaker for ambiguous decisions: preserve this stable-single-point-of-integration posture.</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 26pt 36pt; flex: 1; color: #333333; display: flex; flex-direction: row; gap: 18pt; }
.pillar { flex: 1; background: #ffffff; border-top: 4pt solid #0066B3; padding: 18pt 16pt; border-radius: 4pt; }
.pillar h2 { margin: 0 0 6pt 0; font-size: 14pt; color: #2B2A80; }
.pillar .tag { font-size: 9pt; color: #AAB7B8; margin: 0 0 12pt 0; letter-spacing: 1pt; }
.pillar .criterion { font-size: 12pt; font-weight: bold; color: #2B2A80; margin: 0 0 10pt 0; }
.pillar .context { font-size: 11pt; margin: 0; color: #333333; line-height: 1.4; }
</style>
</head>
<body>
<div class="header">
<h1>Three Pillars</h1>
</div>
<div class="body">
<div class="pillar">
<p class="tag">PILLAR 1</p>
<h2>Unification</h2>
<p class="criterion">100% of sites on the standardized stack.</p>
<p class="context">Every site runs OtOpcUa + ScadaBridge + Redpanda + SnowBridge + Snowflake. No bespoke site-specific integration.</p>
</div>
<div class="pillar">
<p class="tag">PILLAR 2</p>
<h2>Analytics / AI Enablement</h2>
<p class="criterion">≤15-minute SLO; one &quot;not possible before&quot; use case in production.</p>
<p class="context">Canonical model + curated layer unlock cross-domain analytics that fragmented today's estate can't deliver.</p>
</div>
<div class="pillar">
<p class="tag">PILLAR 3</p>
<h2>Legacy Retirement</h2>
<p class="criterion">Inventory driven to zero.</p>
<p class="context">Three bespoke IT↔OT integrations today; each migrates to ScadaBridge-central or is decommissioned.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; }
.group { margin-bottom: 13pt; }
.group-label { font-size: 10pt; color: #0066B3; letter-spacing: 1pt; font-weight: bold; margin: 0 0 4pt 0; }
.group-content { font-size: 13pt; margin: 0; line-height: 1.35; color: #2B2A80; }
.footer { padding: 6pt 40pt 10pt 40pt; color: #AAB7B8; font-size: 9pt; font-style: italic; }
.footer p { margin: 0; }
</style>
</head>
<body>
<div class="header">
<h1>Enterprise Layout</h1>
</div>
<div class="body">
<div class="group">
<p class="group-label">PRIMARY DATA CENTER</p>
<p class="group-content">South Bend — central hosting for Aveva System Platform primary cluster, Ignition, Historian, Snowflake.</p>
</div>
<div class="group">
<p class="group-label">LARGEST SITES</p>
<p class="group-content">Warsaw West (bldg-5, bldg-19) · Warsaw North — one cluster per production building.</p>
</div>
<div class="group">
<p class="group-label">OTHER INTEGRATED SITES</p>
<p class="group-content">Shannon · Galway · TMT · Ponce — single cluster per site.</p>
</div>
<div class="group">
<p class="group-label">NOT YET INTEGRATED</p>
<p class="group-content">Berlin · Winterthur · Jacksonville · others — lighter-weight onboarding pattern Year 2+.</p>
</div>
</div>
<div class="footer"><p>Smaller-site list is volatile; rely on named examples as illustrative, not authoritative.</p></div>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; }
ul { margin: 0 0 0 22pt; padding: 0; }
li { font-size: 12pt; margin-bottom: 9pt; line-height: 1.35; }
</style>
</head>
<body>
<div class="header">
<h1>Today's Systems</h1>
</div>
<div class="body">
<ul>
<li><b>Aveva System Platform</b> — validated data collection; primary cluster central + site clusters.</li>
<li><b>Ignition SCADA</b> — KPI monitoring; central in South Bend, WAN-dependent direct OPC UA to sites.</li>
<li><b>ScadaBridge</b> — in-house Akka.NET integration layer; already deployed; the IT↔OT strategic layer.</li>
<li><b>LmxOpcUa</b> — in-house OPC UA server exposing System Platform objects; per-node today, evolves into OtOpcUa.</li>
<li><b>Camstar MES</b> — sole enterprise MES; today integrates via single Aveva Web API call.</li>
<li><b>Aveva Historian</b> — sole historian; central-only in South Bend; permanent retention; BOBJ → Power BI migration in flight.</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; display: flex; flex-direction: column; }
.layer-stack { display: flex; flex-direction: column; gap: 6pt; }
.layer { background: #ffffff; padding: 10pt 14pt; border-left: 6pt solid #0066B3; border-radius: 3pt; }
.layer .tag { font-size: 9pt; color: #0066B3; letter-spacing: 1pt; font-weight: bold; margin: 0 0 2pt 0; }
.layer .name { font-size: 12pt; font-weight: bold; margin: 0 0 2pt 0; color: #2B2A80; }
.layer .desc { font-size: 10pt; margin: 0; color: #333333; }
.callout { background: #0066B3; color: #FFFFFF; padding: 10pt 14pt; border-radius: 3pt; margin-top: 10pt; }
.callout p { margin: 0; font-size: 11pt; }
</style>
</head>
<body>
<div class="header">
<h1>Goal State — Layered Architecture</h1>
</div>
<div class="body">
<div class="layer-stack">
<div class="layer">
<p class="tag">LAYER 1 — EQUIPMENT</p>
<p class="name">PLCs, CNCs, OPC UA-native devices</p>
<p class="desc">One OPC UA session per device; OtOpcUa holds it.</p>
</div>
<div class="layer">
<p class="tag">LAYER 2 — OtOpcUa</p>
<p class="name">Equipment namespace + System Platform namespace (absorbs LmxOpcUa)</p>
<p class="desc">Clustered, site-local. Single sanctioned OPC UA access point per site.</p>
</div>
<div class="layer">
<p class="tag">LAYER 3 — SCADA</p>
<p class="name">Aveva System Platform (validated) · Ignition (KPI)</p>
<p class="desc">Derives canonical machine state from raw signals.</p>
</div>
<div class="layer">
<p class="tag">LAYER 4 — ScadaBridge</p>
<p class="name">IT/OT bridge — central integration layer</p>
<p class="desc">Deadband, store-and-forward, producer to Redpanda.</p>
</div>
</div>
<div class="callout">
<p><b>ScadaBridge central is the sole IT↔OT crossing point.</b></p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 30pt; flex: 1; color: #333333; display: flex; flex-direction: column; justify-content: center; }
.flow-row { display: flex; justify-content: space-between; align-items: center; gap: 4pt; margin-bottom: 16pt; }
.node { background: #ffffff; border: 1pt solid #AAB7B8; padding: 10pt 6pt; border-radius: 3pt; flex: 1; text-align: center; }
.node p { margin: 0; font-size: 9pt; font-weight: bold; color: #2B2A80; }
.node .type { font-size: 7pt; color: #AAB7B8; margin-top: 2pt; letter-spacing: 0.5pt; }
.arrow { font-size: 12pt; color: #0066B3; font-weight: bold; }
.arrow p { margin: 0; }
.ot { background: #E8EDF0; }
.it { background: #FCEFE3; }
.caption { font-size: 11pt; color: #333333; text-align: center; line-height: 1.4; margin: 12pt 20pt 0 20pt; font-style: italic; }
</style>
</head>
<body>
<div class="header">
<h1>End-to-End Data Flow</h1>
</div>
<div class="body">
<div class="flow-row">
<div class="node ot"><p>Equipment</p><p class="type">OT</p></div>
<div class="arrow"><p>&rarr;</p></div>
<div class="node ot"><p>OtOpcUa</p><p class="type">OT · Layer 2</p></div>
<div class="arrow"><p>&rarr;</p></div>
<div class="node ot"><p>System Platform / Ignition</p><p class="type">OT · Layer 3</p></div>
<div class="arrow"><p>&rarr;</p></div>
<div class="node ot"><p>ScadaBridge</p><p class="type">OT · Layer 4</p></div>
</div>
<div class="flow-row">
<div class="node it"><p>Redpanda</p><p class="type">IT · EventHub</p></div>
<div class="arrow"><p>&rarr;</p></div>
<div class="node it"><p>SnowBridge</p><p class="type">IT · Ingest + Transform</p></div>
<div class="arrow"><p>&rarr;</p></div>
<div class="node it"><p>Snowflake</p><p class="type">IT · Curated Layer</p></div>
<div class="arrow"><p>&rarr;</p></div>
<div class="node it"><p>Power BI</p><p class="type">IT · Consumer</p></div>
</div>
<p class="caption">A tag read from a machine in Warsaw West flows across the stack into enterprise analytics, without a second IT↔OT crossing.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; }
.tagline-wrap { border-left: 3pt solid #0066B3; padding-left: 10pt; margin: 0 0 12pt 0; }
.tagline { font-size: 12pt; color: #2B2A80; margin: 0; font-style: italic; }
ul { margin: 0 0 0 22pt; padding: 0; }
li { font-size: 12pt; margin-bottom: 9pt; line-height: 1.35; }
</style>
</head>
<body>
<div class="header">
<h1>OtOpcUa — the Unification Layer</h1>
</div>
<div class="body">
<div class="tagline-wrap"><p class="tagline">Single sanctioned OPC UA access point per site — one session per piece of equipment.</p></div>
<ul>
<li><b>Two namespaces:</b> equipment (raw) + System Platform (processed) — absorbs LmxOpcUa.</li>
<li><b>Clustered,</b> co-located on existing System Platform nodes — no new hardware footprint.</li>
<li><b>Hybrid driver strategy:</b> proactive core library (8 drivers) + on-demand long-tail.</li>
<li><b>OPC UA-native auth:</b> UserName tokens + standard security modes (inherited from LmxOpcUa).</li>
<li><b>Data-path ACL model:</b> scope hierarchy + per-node permissions, committed in v2.</li>
<li><b>Tiered cutover:</b> ScadaBridge first, Ignition second, System Platform IO last (across Years 13).</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; }
.tagline-wrap { border-left: 3pt solid #0066B3; padding-left: 10pt; margin: 0 0 12pt 0; }
.tagline { font-size: 12pt; color: #2B2A80; margin: 0; font-style: italic; }
ul { margin: 0 0 0 22pt; padding: 0; }
li { font-size: 12pt; margin-bottom: 8pt; line-height: 1.35; }
</style>
</head>
<body>
<div class="header">
<h1>Analytics Stack — SnowBridge + Snowflake</h1>
</div>
<div class="body">
<div class="tagline-wrap"><p class="tagline">SnowBridge owns the full path: ingest + in-process transform + curated-table write.</p></div>
<ul>
<li><b>Custom-built .NET service</b> that reads from Aveva Historian SQL (Year 1) and Redpanda/ScadaBridge (Year 2).</li>
<li><b>In-process transforms,</b> canonical-model-aligned — aggregation, state enrichment, dim_equipment resolution.</li>
<li><b>No dbt, no external orchestrator, no Snowflake landing tier</b> — transforms live in the service.</li>
<li><b>Governed selection</b> of topics/tags via operator web UI with blast-radius-based approval workflow.</li>
<li><b>≤15-minute analytics SLO</b> end-to-end (ScadaBridge emit → curated table in Snowflake).</li>
<li><b>One &quot;not possible before&quot; AI/analytics use case</b> in production by end of plan — pillar 2 gate.</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; }
.tagline-wrap { border-left: 3pt solid #0066B3; padding-left: 10pt; margin: 0 0 12pt 0; }
.tagline { font-size: 12pt; color: #2B2A80; margin: 0; font-style: italic; }
ul { margin: 0 0 0 22pt; padding: 0; }
li { font-size: 12pt; margin-bottom: 8pt; line-height: 1.35; }
</style>
</head>
<body>
<div class="header">
<h1>Redpanda EventHub — the Async Backbone</h1>
</div>
<div class="body">
<div class="tagline-wrap"><p class="tagline">Self-hosted Kafka-compatible backbone. Single-cluster HA, central in South Bend.</p></div>
<ul>
<li><b>Per-topic tiered retention</b> — operational 7d · analytics 30d · compliance 90d.</li>
<li><b>Bundled Schema Registry,</b> Protobuf schemas in central <b>schemas</b> repo with <b>buf</b> CI.</li>
<li><b>BACKWARD_TRANSITIVE compatibility</b> — consumers can upgrade independently of producers.</li>
<li><b>Topic naming:</b> <b>{domain}.{entity}.{event-type}</b>; site identity lives in the message, not the topic.</li>
<li><b>Auth:</b> SASL/OAUTHBEARER via enterprise IdP + prefix ACLs per producer/consumer principal.</li>
<li><b>Store-and-forward at ScadaBridge</b> handles WAN outages; analytics-tier retention doubles as a replay surface.</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 14pt 30pt; flex: 1; color: #333333; }
.sub { font-size: 11pt; color: #333333; margin: 0 0 8pt 0; font-style: italic; }
.placeholder { width: 640pt; height: 300pt; background: transparent; }
</style>
</head>
<body>
<div class="header">
<h1>3-Year Roadmap — Workstreams &times; Years</h1>
</div>
<div class="body">
<p class="sub">Six workstreams across three years. Each cell shows the single most load-bearing commitment.</p>
<div id="roadmap-table" class="placeholder"></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; }
ul { margin: 0 0 0 22pt; padding: 0; }
li { font-size: 12pt; margin-bottom: 9pt; line-height: 1.35; }
</style>
</head>
<body>
<div class="header">
<h1>Year 1 Focus</h1>
</div>
<div class="body">
<ul>
<li><b>OtOpcUa</b> — evolve LmxOpcUa, deploy to every site, begin ScadaBridge-first cutover, UNS hierarchy walk.</li>
<li><b>Redpanda</b> — stand up central cluster, schema registry, initial topics, publish canonical model v1.</li>
<li><b>SnowBridge</b> — design + build; first source adapter (Historian SQL); first curated tables aligned to canonical model.</li>
<li><b>ScadaBridge Extensions</b> — deadband / exception-based publishing + EventHub producer with store-and-forward.</li>
<li><b>Site Onboarding</b> — no new sites; define the lightweight onboarding pattern for smaller sites.</li>
<li><b>Legacy Retirement</b> — inventory populated (3 rows); retire first integration as pattern-proving exercise.</li>
</ul>
</div>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }.body { padding: 22pt 40pt; flex: 1; color: #333333; display: flex; flex-direction: column; }
.item { background: #ffffff; border-left: 5pt solid #0066B3; padding: 10pt 14pt; margin-bottom: 10pt; }
.item .id { font-size: 9pt; color: #0066B3; letter-spacing: 1pt; font-weight: bold; margin: 0; }
.item .title { font-size: 12pt; font-weight: bold; color: #2B2A80; margin: 2pt 0; }
.item .desc { font-size: 10pt; margin: 0; color: #333333; line-height: 1.35; }
.callout { background: #0066B3; color: #FFFFFF; padding: 8pt 14pt; border-radius: 3pt; margin-top: auto; }
.callout p { margin: 0; font-size: 10pt; }
</style>
</head>
<body>
<div class="header">
<h1>Pillar 3 — Legacy Retirement (3 &rarr; 0)</h1>
</div>
<div class="body">
<div class="item">
<p class="id">LEG-001</p>
<p class="title">Aveva Web API &rarr; Delmia DNC</p>
<p class="desc">Bidirectional orchestrated handshake. Harder retirement — requires ScadaBridge scripts to re-implement System Platform parse logic.</p>
</div>
<div class="item">
<p class="id">LEG-002</p>
<p class="title">Aveva Web API &larr; Camstar MES</p>
<p class="desc">Camstar-initiated. Easier retirement — ScadaBridge already has a native Camstar path; requires Camstar-side reconfiguration.</p>
</div>
<div class="item">
<p class="id">LEG-003</p>
<p class="title">System Platform &rarr; custom email notification service</p>
<p class="desc">Easier retirement — ScadaBridge native notifications already exist.</p>
</div>
<div class="callout"><p><b>Carve-out:</b> Historian MSSQL reporting surface (BOBJ / Power BI) is explicitly not legacy — see legacy-integrations.md → Deliberately not tracked.</p></div>
</div>
</body>
</html>

View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #FFFFFF; font-family: Arial, sans-serif;
display: flex; flex-direction: column;
}
.header { padding: 12pt 40pt 4pt 40pt; border-bottom: 1.5pt solid #0066B3; }
.header h1 { margin: 0; font-size: 22pt; font-weight: bold; color: #0066B3; }
.body { padding: 24pt 40pt; flex: 1; color: #333333; }
.lede { font-size: 12pt; color: #2B2A80; margin: 0 0 14pt 0; line-height: 1.4; }
.lede b { color: #0066B3; }
.patterns { display: flex; gap: 18pt; margin: 0 0 12pt 0; }
.pattern { flex: 1; background: #F5F8FB; border-top: 4pt solid #0066B3; padding: 12pt 14pt; border-radius: 3pt; }
.pattern .tag { font-size: 8pt; color: #0066B3; letter-spacing: 1pt; margin: 0 0 4pt 0; font-weight: bold; }
.pattern h2 { font-size: 12pt; color: #2B2A80; margin: 0 0 6pt 0; }
.pattern p { font-size: 10pt; margin: 0; line-height: 1.4; }
.footnote { font-size: 9pt; color: #666666; margin: 6pt 0 0 0; line-height: 1.35; font-style: italic; }
</style>
</head>
<body>
<div class="header">
<h1>Digital Twin Scope</h1>
</div>
<div class="body">
<p class="lede">Scope is <b>two access-control patterns</b> — not a new component, not a new workstream. Both are delivered by architecture already committed (OtOpcUa ACL model + canonical model + single-connection-per-equipment).</p>
<div class="patterns">
<div class="pattern">
<p class="tag">PATTERN 1</p>
<h2>Environment-lifecycle promotion</h2>
<p>Promote between dev / staging / prod by flipping write-authority ACLs against stable equipment UUIDs — no client reconfiguration.</p>
</div>
<div class="pattern">
<p class="tag">PATTERN 2</p>
<h2>Safe read-only consumption</h2>
<p>KPI / monitoring consumers get read-only grants with a structural zero-write-path guarantee — no equipment-side session for them to misuse.</p>
</div>
</div>
<p class="footnote">Out of scope for this plan: physics simulation, FAT, commissioning emulation. Those would be separate funded initiatives — adjacent to the plan, not part of it.</p>
</div>
</body>
</html>

87
schemas/uns/HIERARCHY.md Normal file
View File

@@ -0,0 +1,87 @@
# UNS Hierarchy — High-Level View
> **Status: WIP draft (2026-04-30).** Levels 13 (Enterprise → Site → Area) drafted across all 6 currently-integrated SCADA sites. Level 4 (Line) and Level 5 (Equipment) pending the per-site discovery walk. Authoritative per-site source files live alongside this document at `<site>.json`. See [`README.md`](README.md) for status, [`QUESTIONS.md`](QUESTIONS.md) for open design questions, [`portal-queries.md`](portal-queries.md) for OpenText / corporate-doc lookups.
## Hierarchy levels (per `goal-state.md`)
| Level | Name | Always present | Notes |
|------:|------|---------------|-------|
| 1 | Enterprise | yes | `zb` (Zimmer Biomet) |
| 2 | Site | yes | `warsaw-west`, `warsaw-north`, `shannon`, `galway`, `tmt`, `ponce` |
| 3 | Area | yes | `bldg-N` at multi-cluster Warsaw campuses; `_default` at single-cluster sites |
| 4 | Line | yes | pending walk (will be `line-1` / `line-2` / process-name) |
| 5 | Equipment | yes | pending walk (will be `cnc-mill-05` / equivalent class-counter form) |
Path serialization: dots in messages and curated-table keys (`zb.warsaw-west.bldg-5.line-2.cnc-mill-05`); slashes in OPC UA browse paths (`zb/warsaw-west/bldg-5/line-2/cnc-mill-05`).
## High-level — all 6 currently-integrated sites
```
zb (Zimmer Biomet — Enterprise)
├── warsaw-west Warsaw West Campus (legacy Zimmer, Warsaw IN, multi-cluster)
├── warsaw-north Warsaw North Campus (legacy Biomet, Warsaw IN, multi-cluster)
├── shannon Shannon (Ireland, single cluster)
├── galway Galway (Ireland, single cluster)
├── tmt Zimmer Trabecular Metal Technology (NJ USA, single cluster)
└── ponce Zimmer Manufacturing B.V. (ZMBV) (Puerto Rico, single cluster)
```
The Warsaw campuses are physically adjacent in Warsaw, IN; the West/North split traces to the 2015 Zimmer-Biomet merger of two pre-existing sites. The legacy origin shows up durably in the building-naming convention (numbers vs. letters) — see Warsaw expanded view below.
**Not yet integrated** (and therefore deliberately not in this hierarchy): Berlin, Winterthur, Jacksonville, Dover OH, Statesville NC, Calabasas / Carlsbad CA, Mercedita PR, the NJ TMT subsidiary sites (Allendale, Cedar Knolls, Parsippany), and others. These get added to the hierarchy as they onboard via the standardized stack.
## Warsaw expanded (worked example)
```
zb
├── warsaw-west Warsaw West Campus (legacy Zimmer)
│ ├── bldg-2 Bone Cement, Packaging Clean Rooms
│ ├── bldg-5 Main mfg facility (Foundry, R&D, Quality)
│ ├── bldg-7 Tool Support, Upper Extremity Manufacturing
│ ├── bldg-10 Hip and Knee Manufacturing, Packaging Clean Rooms
│ ├── bldg-19 Trauma HQ + Trauma/Instrument/Knee&Hip Mfg, Foundry
│ └── bldg-20 Sheet Metal, Scrap Salvage, Warehousing
└── warsaw-north Warsaw North Campus (legacy Biomet)
├── bldg-a (3 floors; production-vs-non-production TBD)
├── bldg-b (2 floors; TBD)
├── bldg-c (TBD)
├── bldg-d (TBD)
├── bldg-e (TBD)
└── bldg-i (2 floors; F/G/H gap to verify, TBD)
```
**What an end-to-end path will look like** once Lines and Equipment are walked (illustrative — not yet committed):
```
zb.warsaw-west.bldg-5.line-2.cnc-mill-05.spindle-speed
zb/warsaw-west/bldg-5/line-2/cnc-mill-05/spindle-speed (OPC UA form)
```
Each `cnc-mill-05`-style equipment node also carries a stable **UUIDv4** alongside the path, per `goal-state.md` → UNS naming hierarchy. The path is for navigation; the UUID is for lineage across renames or building moves.
## Single-cluster sites (`_default` Area)
All four single-cluster sites use the explicit `_default` placeholder at the Area level — the goal-state requires uniform path depth:
```
zb.shannon._default.<line>.<equipment>
zb.galway._default.<line>.<equipment>
zb.tmt._default.<line>.<equipment>
zb.ponce._default.<line>.<equipment>
```
When a single-cluster site grows to justify multiple Areas (e.g., adds a second production building), `_default` is replaced by the new named area in a single PR; the equipment UUIDs stay stable, the path updates in `dim_equipment` as a historical version. See `goal-state.md` → UNS naming hierarchy → "Area placeholder → named area promotion".
## What's intentionally not here
- **Non-production buildings.** Areas at multi-cluster sites are production buildings only. Warsaw's offices (bldg-3), distribution center (bldg-4), Z Hotel (bldg-15), residential (bldg-18), airport hangar (bldg-14), etc. do not appear — they don't host equipment that lands in the canonical model.
- **Auxiliary / outlying Warsaw buildings** (Cessna, 508 Detroit Street, Armstrong Road properties). Listed in the Real Estate inventory but not classified West/North per the building-naming convention; treated as separate from the main campuses pending walk verification.
- **Not-yet-integrated sites.** The plan's design is to grow this hierarchy as sites onboard, not to enumerate the full Zimmer Biomet manufacturing footprint upfront.
- **Sub-line groupings (cells, stations, work centers).** The 5-level hierarchy is fixed at Enterprise/Site/Area/Line/Equipment. Cells live below Line, encoded in equipment names (e.g., `cell-3-cnc-mill-05`) per the QUESTIONS.md Q6 default; not added as a 4.5 level unless a real driver appears.
- **Product / job hierarchy.** That's Camstar MES territory, not UNS — see `goal-state.md` → UNS naming hierarchy → out-of-scope list.
## What this doc is and isn't
- **Is:** a one-page consolidated view of the Enterprise/Site/Area state, suitable for stakeholders who don't want to read the per-site JSON files.
- **Isn't:** the source of truth. The per-site `<site>.json` files are. This doc is regenerated from those (currently by hand; a generator may be added later).

136
schemas/uns/QUESTIONS.md Normal file
View File

@@ -0,0 +1,136 @@
# UNS Hierarchy — Follow-up Questions
> **Purpose:** open questions that need answers before the per-site UNS subtree files in this directory can be finalized. Capturing them up front so the hierarchy walk doesn't have to be redone if a convention changes mid-walk. Once a question is answered, fold the answer into the relevant subtree file (and into `goal-state.md` → UNS naming hierarchy standard if the answer changes a global convention).
## Owner
The schemas-repo owner team (TBD per `../README.md` open questions). Until ownership is named, these questions sit with the 3-year-plan author.
## Status legend
- **OPEN** — needs an answer before the walk produces stable output.
- **OPEN — informational** — answer would help but the walk can proceed without it; resolve later.
- **DEFERRED** — answered "later" by the goal-state spec; included here so the walk team doesn't reopen it.
---
## Site-level questions
### Q1 — Warsaw North building set (OPEN — blocks `warsaw-north.json`)
Warsaw North is a campus running per-building clusters (per `current-state.md` → Largest Sites). What is the authoritative list of **production-building numbers** at Warsaw North? (Warsaw West is confirmed as buildings **5** and **19**.) Building numbers are needed before `warsaw-north.json` can be authored — using `_default` would be structurally wrong for a multi-cluster campus.
**Sub-question:** are non-production buildings on the Warsaw campuses (offices, warehouses, labs, utilities) ever expected to host equipment that lands in the UNS? If yes, the area set widens beyond production buildings. If no (likely), the rule is "Areas at Warsaw campuses = production buildings only" and we capture that explicitly.
### Q2 — Site shortname conventions (OPEN — informational, blocks `tmt.json` confidence)
The current draft uses `warsaw-west`, `warsaw-north`, `shannon`, `galway`, `tmt`, `ponce` as site segments. **Are these the canonical shortnames?** Specifically:
- **TMT** — is this an acronym, and if so should the UNS segment be the acronym (`tmt`) or an expanded form? Site-name uniqueness across the org is the only hard constraint; readability is secondary.
- Does the org have an established **site-code system** (e.g., 3-letter site codes used by IT/HR/finance) that the UNS should align with rather than invent its own naming? If yes, switching now is cheap; switching later is not.
- **Capitalization in `displayName`** — is "TMT" the right display form, or does it expand to a longer name in stakeholder-facing artifacts?
### Q3 — Multi-floor / multi-tenant production buildings (OPEN — informational)
Are any production buildings (Warsaw or otherwise) split across multiple floors with **distinct production processes per floor**? If yes, does floor become an Area-equivalent (i.e., `bldg-5-floor-2`) or a Line-level distinction? Recommendation: keep Area at the building level and let Line names carry floor distinctions when needed (`line-2-mezzanine`) — but worth confirming before the walk.
### Q4 — Sites not yet integrated (DEFERRED)
Berlin, Winterthur, Jacksonville, and other unintegrated smaller sites are deliberately not seeded. The walk does not cover them. The site-list volatility note in `current-state.md` applies — wait until each site has firm onboarding scope before authoring its subtree.
---
## Line-level questions
### Q5 — Line naming convention (OPEN — must be settled before walk produces line names)
Two reasonable patterns appear in the current `goal-state.md` examples:
- **Numeric** — `line-1`, `line-2`, ... — site-stable, terse, but loses semantic info.
- **Process-named** — `assembly-a`, `packout-1`, `injection-2` — readable, but requires a controlled vocabulary so two sites don't end up with `assembly-a` meaning different things in different contexts.
**Recommended convention to ratify:** allow either form, but **prefer numeric-with-displayName-carrying-process** (`name: "line-2"`, `displayName: "Line 2 — Machining"`). Numeric for the path (machine-stable, site-locally-unique); process info in the displayName (operator-readable). The walk should default to numeric `line-N` and capture the process description in `displayName`.
**Sub-question:** when a site's existing line numbering (per System Platform / Ignition) is non-contiguous or uses internal codes (e.g., `MX-204`), do we (a) preserve the existing identifier verbatim as the line name (after kebab-casing) or (b) renumber to sequential `line-N` and stash the legacy identifier in displayName? Recommendation: **(a) preserve** — renaming creates lineage churn and breaks operator mental model. Confirm before the walk.
### Q6 — Line vs. cell vs. work center (OPEN — must be settled before walk)
The goal-state defines a Line as "a production line **or work cell** within an area." That blurs two concepts:
- A **line** typically means a full sequence of stations producing a product end-to-end.
- A **work cell** is often a smaller grouping (one to a few machines) operating semi-autonomously.
**Question:** is the UNS Line level `(a)` always the full-line concept (cells live below it, possibly as part of equipment naming), `(b)` always the smallest-named-grouping (so a four-cell line shows up as four UNS lines), or `(c)` whichever the site's own taxonomy uses (which makes the level non-uniform across sites)? Recommendation: **(a) full-line** for uniformity, with cells encoded in equipment names (`cell-3-cnc-mill-05`) — but confirm. Sub-line groupings can also be supported by an optional 4.5-level convention if it ever proves necessary, but we should not introduce that without a real driver.
### Q7 — Multi-product lines and changeovers (OPEN — informational)
When a single physical production line runs different products on different shifts or after changeover, does that line stay one UNS Line (recommended — physical reality drives the path) or split into one Line per product (not recommended — would shift paths on every changeover)? Recommendation: **one UNS Line per physical line**, with product association captured at the **canonical event level** (the running work-order / product-id is in the message, not the topic / path). Confirm.
### Q8 — Lines that span buildings (OPEN — informational)
If a production flow physically crosses building boundaries on a Warsaw campus (e.g., front-end machining in bldg-5, finishing in bldg-19, joined by manual transport), is that **one logical line spanning two areas** (which the 5-level hierarchy can't directly express — each line lives under exactly one area) or **two coupled lines, one per area**? Recommendation: **two lines, coupled by canonical workorder events** — keep the hierarchy strict and let the event stream carry cross-building flow. Confirm.
---
## Equipment-level questions (level 5)
These don't block the walk's higher-level output but should be settled before equipment names are committed, since the walk also captures equipment instances.
### Q9 — Equipment leaf naming convention (OPEN — must be settled before walk commits equipment names)
The goal-state example uses `cnc-mill-05`. Pattern appears to be `{class-shortname}-{counter}`. Questions:
1. **Counter scope** — is `05` unique within the line, the site, or the enterprise? Recommendation: **unique within the (site, area, line) triple** — the path itself disambiguates across sites. Enterprise-wide uniqueness is provided by the **UUID**, not the leaf name; trying to make leaf names enterprise-unique fights with operator readability.
2. **Class-shortname source** — does it come from the equipment-class file's `classId` (e.g., `fanuc-cnc``fanuc-cnc-05`) or a class-defined display shortname (`cnc-mill-05`)? Recommendation: a separate `pathShortname` field on the equipment-class definition, distinct from `classId`. Add this to the equipment-class schema once confirmed.
3. **Asset-tag-based naming** — some sites have existing physical asset tags on equipment (e.g., `WW-CNC-204`). Should the UNS leaf name preserve that tag (after kebab-casing) or use the class-counter pattern with the asset tag captured as another equipment identifier alongside UUID? Recommendation: **class-counter pattern in the path; asset tag stored as an equipment-level identifier alongside UUID and EquipmentId**. The path is for navigation, the identifiers are for lineage / cross-system joins.
### Q10 — Mobile or portable equipment (OPEN — informational)
Some equipment is portable and moves between lines (e.g., a calibration rig, a mobile inspection station). Does it:
- (a) Get a stable home line and stay on that path even when physically elsewhere?
- (b) Get re-pathed each time it moves (UUID stays stable, path changes — supported by the Area-promotion governance pattern in `goal-state.md`)?
- (c) Live at `_default` line as "site-level shared equipment"?
Recommendation: **(a) with a "shared" home line per site** (`_default` line if no better fit) — minimizes path churn. Confirm.
### Q11 — Non-production equipment (utilities, monitoring, support) (OPEN — informational)
Compressed-air compressors, chillers, building-monitoring sensors, lab equipment — does this surface in the UNS?
- (a) Yes, with a `support` or `utilities` line per area: `zb.warsaw-west.bldg-5.utilities.compressor-01`.
- (b) Yes, on `_default` line: `zb.warsaw-west.bldg-5._default.compressor-01`.
- (c) No — out of UNS scope; surfaced through a separate channel.
Recommendation: **(a) with a reserved line name** (`utilities`, `support`, `lab`, etc. — short list to be ratified). Out-of-scope is wrong because these *do* feed analytics (energy, OEE-adjacent metrics). `_default` is wrong because that placeholder is reserved for "level not applicable." Confirm and add the reserved line names to the global rules.
---
## Cross-level questions
### Q12 — Lines / equipment shared across sites (OPEN — informational, low-frequency)
Are there any equipment items that are physically singular but logically shared across multiple sites (e.g., a corporate lab in South Bend whose results feed multiple sites)? If yes, where does it sit in a per-site hierarchy? Recommendation: **path under its physical site; logical sharing handled at the canonical-event consumer level, not in the path**. Confirm if any such cases exist.
### Q13 — Hierarchy change governance (OPEN — informational, will be settled when ownership is named)
`goal-state.md` already commits to: Area-placeholder → named-area promotion via PR; UUID stable across path changes; `dim_equipment` in Snowflake retains historical paths. Open: **who reviews and approves a hierarchy change PR?** A site's own SMEs only? A central data-governance group? The schemas-repo owner team alone? Will resolve when the schemas-repo owner team is named.
### Q14 — Synthetic / aggregate equipment nodes (OPEN — informational)
Will the UNS ever need to expose **synthetic equipment** — e.g., a virtual "line-level OEE node" that publishes computed metrics derived from the underlying equipment? If yes, does it follow the same path/UUID rules as physical equipment, or sit in a different namespace? Recommendation: **same rules, with equipment-class metadata flagging it as synthetic**; physical vs synthetic is a property of the class, not a hierarchy distinction. Defer the decision until a real synthetic-equipment use case appears (likely with the canonical state vocabulary's OEE work in Year 12).
---
## How to use this list during the walk
1. Before starting per-site walks, get answers to **Q1, Q2, Q5, Q6, Q9** — these shape the walk's output schema.
2. Q3, Q7, Q8, Q10, Q11 can be resolved by the walk team applying the recommended defaults; raise back to this list only if a site's reality contradicts the default.
3. Q12, Q13, Q14 do not block the walk. Resolve later.
4. As each question is closed, move it to a **Resolved** section at the bottom (with the resolution and the file(s) updated) so the audit trail is preserved.
---
## Resolved
_(empty — populate as questions are closed)_

39
schemas/uns/README.md Normal file
View File

@@ -0,0 +1,39 @@
# UNS Subtrees — initial draft
> **Status: WORK-IN-PROGRESS draft (2026-04-30).** Initial structural skeleton seeded from the 3-year-plan source files (`current-state.md` enterprise layout + `goal-state.md` UNS naming hierarchy standard) and OpenText facility documentation. Per-site **line and equipment** detail is **not yet populated** — that requires the UNS hierarchy snapshot walk (see `goal-state.md` → "Unified Namespace (UNS) posture → UNS naming hierarchy standard"). See [`QUESTIONS.md`](QUESTIONS.md) for the open questions that block finalization.
>
> **Quick stakeholder view:** [`HIERARCHY.md`](HIERARCHY.md) — high-level tree across all 6 sites with Warsaw expanded as a worked example.
This directory holds one JSON file per **integrated site**, declaring the canonical UNS subtree (Enterprise → Site → Area → Line) for that site. Equipment (level 5) is configured per-cluster in OtOpcUa, not declared here.
## What's drafted
| Site | File | Source of truth | Notes |
|------|------|-----------------|-------|
| Warsaw West | [`warsaw-west.json`](warsaw-west.json) | OpenText `Zimmer Warsaw - Building Use Descriptions.docx` + verbal confirmation from project owner (numbered = legacy Zimmer = West Campus) | Six production buildings: `bldg-2`, `bldg-5`, `bldg-7`, `bldg-10`, `bldg-19`, `bldg-20`. Non-production buildings (3, 4, 8, 14, 15, 17, 18) deliberately excluded — Areas are production-only per goal-state. Lines pending walk. |
| Warsaw North | [`warsaw-north.json`](warsaw-north.json) | OpenText `Zimmer Biomet Buildings` folder + verbal confirmation (lettered = legacy Biomet = North Campus) | Six lettered buildings: `bldg-a`, `bldg-b`, `bldg-c`, `bldg-d`, `bldg-e`, `bldg-i`. Production-vs-non-production breakdown per building is **not yet known** — all included pending walk. Note F/G/H are absent from the building list; verify whether they exist. |
| Shannon | [`shannon.json`](shannon.json) | `current-state.md` other-integrated-sites + "single server cluster covering the whole site" rule | Area = `_default` (single-cluster site). Lines pending walk. |
| Galway | [`galway.json`](galway.json) | same | same |
| TMT | [`tmt.json`](tmt.json) | same | Area = `_default`. Lines pending walk. **TMT shortname unconfirmed** — see [`QUESTIONS.md`](QUESTIONS.md) Q2. |
| Ponce | [`ponce.json`](ponce.json) | same | same |
The not-yet-integrated smaller sites (Berlin, Winterthur, Jacksonville, others) are deliberately **not seeded** — onboarding shape is still TBD and the site list is volatile per `current-state.md`.
## What still needs to happen
1. **Walk integrated sites** to populate the `lines` arrays in each file above. Walk inputs: System Platform IO config, Ignition OPC UA connections, ScadaBridge templates. Walk output: per-site list of production lines with their `name` (kebab-case), `displayName`, and the equipment instances belonging to each line (with stable UUIDs — assigned during the walk, not derived from existing identifiers).
2. **Confirm Warsaw North production-vs-non-production breakdown** — the lettered buildings are all in `warsaw-north.json` pending verification of which are actually production. The walk should resolve this; non-production buildings should be removed from the Areas list once identified.
3. **Resolve remaining [`QUESTIONS.md`](QUESTIONS.md)** — Q2Q4 (naming conventions) before authoring real line names, so the walk doesn't have to be redone.
4. **Move to a dedicated `schemas` repo** once one is created and an owner team is named. The temporary location under `3yearplan/schemas/` is documented in `../README.md`.
## Validation
Each file is validated against [`../format/uns-subtree.schema.json`](../format/uns-subtree.schema.json). Constraints:
- `enterprise` must equal `zb` across every file (matches `ServerCluster.Enterprise` in OtOpcUa).
- `site`, area `name`, and line `name` must match `^[a-z0-9-]{1,32}$` or be the literal `_default`.
- `_default` is reserved as the explicit "this level doesn't apply at this site" placeholder, per the goal-state design goal of uniform path depth.
## Worked examples
- [`example-warsaw-west.json`](example-warsaw-west.json) — original schema worked example contributed with the seed (uses placeholder bldg-3/bldg-4 — **superseded by the authoritative `warsaw-west.json`** for any actual consumer; kept here for documentation purposes only).

12
schemas/uns/galway.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema": "../format/uns-subtree.schema.json",
"enterprise": "zb",
"site": "galway",
"displayName": "Galway",
"areas": [
{
"name": "_default",
"displayName": "Site (single-cluster — no area distinction)"
}
]
}

12
schemas/uns/ponce.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema": "../format/uns-subtree.schema.json",
"enterprise": "zb",
"site": "ponce",
"displayName": "Ponce",
"areas": [
{
"name": "_default",
"displayName": "Site (single-cluster — no area distinction)"
}
]
}

View File

@@ -0,0 +1,302 @@
# UNS Hierarchy — Corporate AI Portal Queries
> **Purpose:** factual lookup questions to send to the corporate documentation AI portal. These are distinct from [`QUESTIONS.md`](QUESTIONS.md) — that file holds *design / convention* questions for a human owner; this file holds *factual* questions whose answers exist in corporate documentation (site directories, ERP plant master, MES, asset register, plant-engineering docs, etc.).
>
> **How to use:** fire each question at the portal independently. Paste the response under the question (in the **Response** block) as it comes back. Once a question is answered, fold the answer into the relevant subtree file (`warsaw-west.json`, `warsaw-north.json`, etc.) and/or into [`QUESTIONS.md`](QUESTIONS.md) if it resolves a design question.
>
> **Drafted:** 2026-04-30. **First answers folded in 2026-04-30** from OpenText Content Management CE 25.3 (`livelink.nam.zimmer.com`) browse session — the AI portal does not index OpenText, so this content was extracted via browser automation against folders we have access to (read-only, no downloads).
>
> **Source convention:** answers tagged `[OpenText: <breadcrumb>]` cite the OpenText folder/document path; `[AI portal]` would be from the corporate AI portal (none yet).
## Priority
- **High** — directly unblocks a per-site subtree file (Q1, Q2, Q3, Q6, Q7, Q9).
- **Medium** — shapes equipment-leaf walk and naming conventions (Q11Q15).
- **Low** — confirms existing-standards alignment, low risk if unanswered (Q4, Q5, Q8, Q10, Q16Q19).
---
## Site list & site codes
### Q1 — Site list (High) — SUBSTANTIALLY ANSWERED
What is the authoritative list of manufacturing sites for the company, including which ones are active vs. planned vs. decommissioned, and which region each one belongs to?
**Response (partial — corporate-entity folders, not necessarily all manufacturing sites):**
Top-level folders at `[OpenText: Enterprise]` representing Zimmer business units / sites:
- Zimmer Manufacturing B.V. (ZMBV) - Ponce
- Zimmer Shannon
- Zimmer TMT (Trabecular Metal Technology)
- Synvasive Reno
- Zimmer Austin Orthopedic
- Zimmer Beijing
- Zimmer Biomet Guaynabo (Puerto Rico)
- Zimmer CAS
- Zimmer Dental
- Zimmer Spine
- Zimmer Surgical
- Zimmer Trauma
Plus regional/grouping folders: Asia Pacific, Canada, Departments. The **Warsaw, IN headquarters** (with West Campus and North Campus) does not appear at this level — its content lives under `Business Teams > Real Estate and Facilities > Warsaw Real Estate and Facilities`. **Galway is not visible at this level either** — likely under Asia Pacific or another structure.
**Caveat:** these are *content folders*, not necessarily an authoritative manufacturing-site directory. A real site directory likely lives in HR / IT / ERP master data, not OpenText. The folder list here is best treated as a **lower-bound** of distinct business entities with documentation in OpenText.
**Update (2026-04-30, post-download of `Global Real Estate Information.xls`, 580 KB, 2014 vintage = pre-Biomet-merger Zimmer-only data):** master "Global Real Estate" worksheet has 198 rows covering 45 country sheets. **Manufacturing-classified sites globally** (filtered on Primary Use):
| Country | City | Address | Sq Ft | Notes |
|---------|------|---------|-------|-------|
| Switzerland | Winterthur / Zurich | Sulzer Allee 8 | 251,339 | **Active major site** — plan currently classifies as "not yet integrated"; needs reclassification |
| USA | Allendale, NJ | 80 Commerce Drive | 22,654 | **ZTMT Offices / Manufacturing** — TMT is here |
| USA | Calabasas, CA | 27030 Malibu Hills Rd | 35,719 | not in plan |
| USA | Carlsbad, CA | 1900 Aston Avenue | 44,757 | not in plan |
| USA | Cedar Knolls, NJ | 48 Horsehill Road | 23,198 | **ZTMT Offices / Manufacturing** |
| USA | Dover, OH | 200 West Ohio Ave | 138,000 | not in plan |
| USA | Livingston, NJ | 100 Navlon Ave | 7,500 | not in plan |
| USA | Mercedita, PR | Mercedita Plant | 112,800 | **Separate from Ponce** — PR has 2 sites |
| USA | Parsippany, NJ | 10 Pomeroy Road | 112,000 | **ZTMT Offices / Manufacturing** — largest TMT site |
| USA | Ponce, PR | Parque Ind. El Tuque | 12,054 | matches plan's Ponce |
| USA | Statesville, NC | 2021 Old Mountain Rd | 80,001 | not in plan |
| USA | Warsaw, IN | (9 addresses, 1,141,003 sq ft total) | — | see Warsaw breakdown below |
**Warsaw, IN — 9 manufacturing addresses (2014 legacy-Zimmer only; pre-Biomet-merger so does NOT include North Campus lettered buildings):**
- 1800 West Center St — **552,230 sq ft** (main HQ campus — the numbered buildings 220 are on this property)
- 508 Detroit Street — 24,820 sq ft (matches "Detroit St" PDFs in OpenText)
- 1610 W Armstrong Road — 30,764 sq ft
- 1612 Armstrong Road — 20,134 sq ft
- 1625 Armstrong Road — 16,378 sq ft
- 1639 Armstrong Road — 5,875 sq ft
- 320 Hepler Drive — 30,838 sq ft
- 400 S. Zimmer Rd — 19,000 sq ft
Several of these are likely the "Cessna" and "Detroit St" buildings that appeared in the OpenText `Zimmer Biomet Buildings` folder but didn't fit either the numbered or lettered scheme — they are auxiliary Warsaw, IN production buildings outside the main 1800 W Center St campus.
**Notable absences from this 2014 doc:** Shannon, Galway, Berlin, Jacksonville, and the lettered (Biomet) buildings — all of these are post-2015-merger acquisitions or sites that came in via Biomet.
**Implication for UNS / plan:** the plan's "Other integrated sites" list (Shannon, Galway, TMT, Ponce) is correct as the SCADA-integrated subset — most other sites in this list are not (yet) on the SCADA system. **However**, two corrections to the plan are warranted:
1. **Winterthur, Switzerland is a major manufacturing site (251K sq ft active)**, not a "smaller footprint" not-yet-integrated site as `current-state.md` currently classifies it. Onboarding it via the standardized stack is a significantly larger effort than the plan currently implies.
2. **TMT is in New Jersey (Allendale, Cedar Knolls, Parsippany)** — three sites totaling 157K sq ft. The plan's `tmt` site shortname is fine but the location is NJ, not Indiana, not Japan (Japan is a separate registration). Mercedita, PR is separate from Ponce, PR.
---
### Q2 — Site code system (High)
Does the company use a standard site-code or facility-code system (for example a 2- or 3-letter code used across IT, ERP, HR, or finance)? If yes, what are the codes for **Warsaw West**, **Warsaw North**, **Shannon**, **Galway**, **TMT**, **Ponce**, **South Bend**, **Berlin**, **Winterthur**, and **Jacksonville**?
**Response:**
_(pending)_
---
### Q3 — TMT meaning (High) — ANSWERED
What does the abbreviation **TMT** stand for as a manufacturing site, and what is its full official name? Where is it located?
**Response:**
**TMT = Zimmer Trabecular Metal Technology, Inc.** — wholly owned subsidiary of Zimmer Holdings, Inc. Also does business as "Zimmer" and "Implex" (a/k/a "Zimmer Trabecular Metal Technology, Inc dba Zimmer and Implex"). Zimmer Biomet Canada is the contracted manufacturer for TMT-listed devices. Registered as a foreign medical-device manufacturer in Japan with establishment number BG30401579(TMT). Source: `[OpenText: Enterprise > Zimmer TMT > ... > TMT ISO (SGS) Certificates / State of NJ... > SGS Reports > Past Reports]` — the SGS audit reports `2023-10-03-AUR-SGSGU-F002993 SIGNED MDSAP...` and `2021-09-27-AUR-SGSGU-F002993 SIGNED MDSAP...`. **Sub-finding:** site has its own dedicated top-level folder `Enterprise > Zimmer TMT` with subfolders for Document Control (57 items), Quality, Sourcing — created by Virendra Atri in 2013, suggesting India-based ownership. **Implication for UNS:** the site shortname `tmt` aligns with the corporate naming; long-form display would be "Zimmer Trabecular Metal Technology" or "Zimmer TMT".
---
### Q4 — SAP plant codes (Low)
What are the **SAP plant codes** (or equivalent ERP plant identifiers) for each of our manufacturing sites?
**Response:**
_(pending)_
---
### Q5 — Camstar MES site codes (Low)
What are the **Camstar MES site codes** in use, and how do they map to the physical sites?
**Response:**
_(pending)_
---
## Warsaw campus building maps
### Q6 — Warsaw West buildings (High) — ANSWERED
What is the authoritative list of buildings on the **Warsaw West** campus, including building number, building name, primary purpose (production / office / warehouse / lab / utility), and approximate footprint? Specifically, are buildings other than 5 and 19 used for production?
**Response (partial):**
The Warsaw campus has **far more buildings than just 5 and 19.** Authoritative folder `[OpenText: Enterprise > Business Teams > Real Estate and Facilities > Warsaw Real Estate and Facilities > Facility Information > Zimmer Biomet Buildings]` (objId=4638251) contains 31 floor-plan PDFs, one per building/floor. Building inventory:
- **Numbered buildings**: 2 (1F), 3 (LL/1/2/3), 4 (1/2), 5 (1/2), 7, 8 (1/2), 10, 14 (1&2), 15 (Z Hotel — 4 floors), 17, 18, 19, 20.
- **Lettered buildings**: A (3 floors), B (2 floors), C, D, E, I (2 floors). Likely a separate sub-campus — they appeared together earlier under `Asset_Management_Warsaw > BLDG A_009 > BLDG A CASE 6 SHELF 3_01` with a `Mikron_Main Campus Map.pdf` reference, suggesting these may be the **Mikron** facility / older building-letter naming convention.
- **Named buildings**: Cessna (1F & 2F), Detroit St-1 floor, Detroit St-2 floor (Detroit Street is in Warsaw, IN, so "Detroit St" buildings are Warsaw addresses, not Detroit).
**West Campus / North Campus split is real** (parent folder `[OpenText: Real Estate and Facilities]` has both `North Campus Training` and `West Campus Training` subfolders), but the building-to-campus assignment is **not visible from folder names alone** — it lives in the PDFs `Zimmer West Campus.pdf` (161 KB), `Warsaw Facilities Location Map.pdf` (117 KB), and the Excel `Zimmer Warsaw - Building Use Descriptions.xlsx` (12 KB) under `Facility Information`. Opening any of these requires an explicit download permission.
**Authoritative facilities contact (2019):** Vrushali Pandya, Sr. Facilities Specialist, Facilities Department, 1800 W. Center Street, Warsaw IN 46581 — `Vrushali.Pandya@zimmerbiomet.com`. Sourced from email `[OpenText: 01-Zimmer Warsaw-Establishment No.-18225... > Establishment Registration > Map 3 mile radius > RE List and Map of West Campus Buildings.msg]` (objId=56700739).
**Implication for UNS:** the original draft assumption of "Warsaw West = bldg-5 + bldg-19" is too narrow. Need either the West Campus PDF or the Building Use Descriptions Excel to finalize. Some "buildings" are office/hotel (Bldg 15 = "Z Hotel"), not production — purpose breakdown is critical before committing to UNS Areas.
**Resolution (2026-04-30):** project owner confirmed verbally that **numbered buildings = legacy Zimmer = Warsaw West Campus**, and **lettered buildings (A, B, C, …) = legacy Biomet = Warsaw North Campus**. This corresponds to the 2015 Zimmer-Biomet merger of two physically adjacent Warsaw, IN sites. The Building Use Descriptions doc (downloaded 2026-04-30) enumerates the numbered buildings 220 with use descriptions:
| Bldg | Use |
|------|-----|
| 2 | Printing Services, Bone Cement, **Packaging Clean Rooms** |
| 5 | **Main manufacturing facility** (Foundry, Mfg, Packaging, R&D, Quality, Engineering Services, …) |
| 7 | Tool Support Center, **Upper Extremity Manufacturing** |
| 10 | **Hip and Knee Manufacturing**, Packaging Clean Rooms |
| 19 | Trauma Corporate HQ + **Trauma, Instrument, and Knee & Hip Manufacturing, Foundry** |
| 20 | Scrap Salvage, **Sheet Metal**, Warehousing |
Non-production: 3 (Executive offices), 4 (Distribution Center), 8 (Global Events), 14 (Airport Hangar), 15 (Z Hotel), 17 (IRS Offices), 18 (Residential Housing).
**Resolution applied to `warsaw-west.json`:** Warsaw West Areas = `bldg-2`, `bldg-5`, `bldg-7`, `bldg-10`, `bldg-19`, `bldg-20` (production buildings only; non-production 3, 4, 8, 14, 15, 17, 18 excluded per the goal-state design that Areas at Warsaw campuses are production buildings).
**Outstanding sub-questions (low priority, not blocking):**
- **Cessna** and **Detroit St-1 / Detroit St-2** PDFs were in the same `Zimmer Biomet Buildings` folder but are neither numbered nor lettered. They belong to neither West nor North under this rule. Likely separate auxiliary buildings or a third Warsaw facility — verify during the walk.
- **Lettered building purposes** are not yet known (no equivalent of the Building Use Descriptions doc was found for North Campus). All six lettered buildings are included in `warsaw-north.json` pending the walk; non-production ones should be removed once identified.
- Lettering jumps from E to I — verify whether F, G, H exist, were demolished, or follow a different naming.
---
### Q7 — Warsaw North buildings (High — primary blocker) — ANSWERED
What is the authoritative list of buildings on the **Warsaw North** campus, including building number, building name, primary purpose (production / office / warehouse / lab / utility), and approximate footprint?
**Response:**
**Lettered buildings (A, B, C, D, E, I) = Warsaw North Campus = legacy Biomet site.** Confirmed verbally by project owner; corresponds to the 2015 Zimmer-Biomet merger of two physically adjacent Warsaw, IN sites. Six lettered buildings visible in `[OpenText: Warsaw Real Estate and Facilities > Facility Information > Zimmer Biomet Buildings]`: A (3 floors), B (2 floors), C (1 floor), D (1 floor), E (1 floor), I (2 floors). Lettering jumps from E to I — F, G, H not visible; verify during walk.
**Resolution applied to `warsaw-north.json`:** Warsaw North Areas = `bldg-a`, `bldg-b`, `bldg-c`, `bldg-d`, `bldg-e`, `bldg-i` — all included pending verification of which are production vs. non-production (no equivalent of the Building Use Descriptions doc was found for North Campus). Walk should remove non-production buildings.
---
### Q8 — Multi-floor production buildings (Low)
Are any **Warsaw campus production buildings** split across multiple floors with distinct production processes per floor? If so, which buildings and what's on each floor?
**Response:**
_(pending)_
---
## Production lines & cells (per site)
### Q9 — Production line inventory (High)
What are the named or numbered **production lines** currently operating at each integrated site (Warsaw West buildings 5 and 19, Warsaw North, Shannon, Galway, TMT, Ponce)? Looking for: line identifier, what the line produces, and where it physically sits within the site/building.
**Response:**
_(pending)_
---
### Q10 — Existing plant taxonomy (Low)
Is there an existing **plant taxonomy** or **work-center hierarchy** documented anywhere — for example an ISA-95 model, a Camstar work-center tree, an Ignition project structure, or an Aveva System Platform Galaxy hierarchy — that already defines Site / Area / Line / Cell / Equipment for our sites?
**Response:**
_(pending)_
---
### Q11 — Line naming convention in use today (Medium)
When a single physical production line is described across our internal documentation (engineering drawings, MES, capacity plans), is it named by a **number** (Line 1, Line 2), by a **product or process** (Assembly A, Packout, Injection 2), or by an **internal code** (e.g., MX-204)? Looking for the convention(s) actually in use today.
**Response:**
_(pending)_
---
## Equipment naming & asset tags
### Q12 — Asset-tag format (Medium)
What is the company's **physical asset-tag format** for production equipment (e.g., the printed/engraved tags on machines)? Looking for: the format pattern, what each segment means, and whether the format differs across sites or equipment classes.
**Response:**
_(pending)_
---
### Q13 — Asset register / CMDB (Medium)
Is there a **central asset register or CMDB** that lists production equipment by site, including asset tag, manufacturer, model, and location (building / line)? What is it called and who owns it?
**Response:**
_(pending)_
---
### Q14 — Existing System Platform / Ignition naming (Medium)
What naming convention is used for production equipment in **Aveva System Platform** (Galaxy object names) and **Ignition** (OPC UA tag paths) today? Are the conventions consistent across sites?
**Response:**
_(pending)_
---
### Q15 — FANUC CNC count and locations (Medium)
How many **FANUC CNC** machines do we have, broken down by site and building? (The FANUC CNC class is the pilot for the new equipment-class template work, so a count by location would shape rollout sequencing.)
**Response:**
_(pending)_
---
## Existing standards
### Q16 — Existing UNS / ISA-95 standard (Low)
Does the company have an existing **Unified Namespace (UNS)**, **ISA-95 hierarchy**, or **manufacturing data model standard** documented? If so, where is it published and who owns it?
**Response:**
_(pending)_
---
### Q17 — Enterprise shortname `zb` confirmation (Low) — ANSWERED
Is there an existing **enterprise-level shortname or stock-ticker-like abbreviation** used in IT systems, document headers, or domain names that we should align our top-of-hierarchy `zb` segment with? (We're using `zb` today; want to confirm it matches an existing convention rather than introducing a new one.)
**Response:**
Confirmed: `zb` aligns with **Zimmer Biomet** as the company name (the OpenText instance is at `livelink.nam.zimmer.com`; documents are signed off by `@zimmerbiomet.com` email addresses; the corporate signature uses "ZIMMER BIOMET" as the brand). NYSE ticker is `ZBH` but `zb` is a clean two-letter shortname that matches both the legacy "Zimmer" and the post-merger "Zimmer Biomet" brand. **No change needed** — keep `zb` as the top-of-hierarchy enterprise segment.
---
### Q18 — Non-production equipment classes (Low)
Is there a **non-production equipment class list** (compressors, chillers, HVAC, lab equipment) maintained at the corporate level, and which of these surfaces are typically connected to SCADA / monitoring at our manufacturing sites?
**Response:**
_(pending)_
---
## Cross-site / shared resources
### Q19 — Shared resources across sites (Low)
Are there any **shared production or test resources** (calibration labs, central inspection equipment, mobile test rigs) that are physically located at one site but logically serve multiple sites? If so, list them with their host site.
**Response:**
_(pending)_
---
## Resolved
### 2026-04-30 — first pass against OpenText
- **Q3 (TMT meaning)** — fully answered. `tmt` site shortname stays; long-form name "Zimmer Trabecular Metal Technology" or "Zimmer TMT".
- **Q17 (`zb` enterprise shortname)** — confirmed. `zb` aligns with Zimmer Biomet brand; no change.
- **Q1 (site list)** — substantially answered. 2014 Zimmer global real-estate inventory shows 12 distinct manufacturing sites (legacy Zimmer only); plus Biomet-acquired sites (Shannon, Galway, etc.) added in 2015. Reclassifications applied to `current-state.md`: Winterthur is a major site (251K sq ft), not a "smaller footprint" site; TMT is in NJ (3 addresses); Mercedita is separate from Ponce.
- **Q6 (Warsaw West buildings)** — fully answered. Numbered buildings = legacy Zimmer = Warsaw West. `warsaw-west.json` updated with 6 production Areas: bldg-2, bldg-5, bldg-7, bldg-10, bldg-19, bldg-20.
- **Q7 (Warsaw North buildings)** — fully answered. Lettered buildings = legacy Biomet = Warsaw North. `warsaw-north.json` created with 6 Areas: bldg-a through bldg-e and bldg-i (purposes pending walk; non-production to be removed).
### Open OpenText follow-ups
- Source a **current-vintage** manufacturing-site directory (the Real Estate workbook is 2014 and pre-Biomet-merger) — likely lives in SAP / HR, not OpenText.
- Optionally search OpenText for "Galway" and "Shannon" specifically since those Biomet-acquired sites are not in the 2014 real-estate workbook.
- The `Global Site Pictures` (21 items) folder is unexplored — could yield labeled campus photos useful for verifying the West/North split visually.

12
schemas/uns/shannon.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema": "../format/uns-subtree.schema.json",
"enterprise": "zb",
"site": "shannon",
"displayName": "Shannon",
"areas": [
{
"name": "_default",
"displayName": "Site (single-cluster — no area distinction)"
}
]
}

12
schemas/uns/tmt.json Normal file
View File

@@ -0,0 +1,12 @@
{
"$schema": "../format/uns-subtree.schema.json",
"enterprise": "zb",
"site": "tmt",
"displayName": "TMT",
"areas": [
{
"name": "_default",
"displayName": "Site (single-cluster — no area distinction)"
}
]
}

View File

@@ -0,0 +1,32 @@
{
"$schema": "../format/uns-subtree.schema.json",
"enterprise": "zb",
"site": "warsaw-north",
"displayName": "Warsaw North (legacy Biomet campus)",
"areas": [
{
"name": "bldg-a",
"displayName": "Building A (3 floors) — purpose TBD, confirm during walk"
},
{
"name": "bldg-b",
"displayName": "Building B (2 floors) — purpose TBD, confirm during walk"
},
{
"name": "bldg-c",
"displayName": "Building C — purpose TBD, confirm during walk"
},
{
"name": "bldg-d",
"displayName": "Building D — purpose TBD, confirm during walk"
},
{
"name": "bldg-e",
"displayName": "Building E — purpose TBD, confirm during walk"
},
{
"name": "bldg-i",
"displayName": "Building I (2 floors) — purpose TBD; verify whether buildings F/G/H exist or were skipped in the lettering"
}
]
}

View File

@@ -0,0 +1,32 @@
{
"$schema": "../format/uns-subtree.schema.json",
"enterprise": "zb",
"site": "warsaw-west",
"displayName": "Warsaw West (legacy Zimmer campus)",
"areas": [
{
"name": "bldg-2",
"displayName": "Building 2 — Bone Cement, Packaging Clean Rooms"
},
{
"name": "bldg-5",
"displayName": "Building 5 — Main manufacturing facility (Foundry, R&D, Quality)"
},
{
"name": "bldg-7",
"displayName": "Building 7 — Tool Support Center, Upper Extremity Manufacturing"
},
{
"name": "bldg-10",
"displayName": "Building 10 — Hip and Knee Manufacturing, Packaging Clean Rooms"
},
{
"name": "bldg-19",
"displayName": "Building 19 — Trauma HQ, Trauma/Instrument/Knee & Hip Mfg, Foundry"
},
{
"name": "bldg-20",
"displayName": "Building 20 — Sheet Metal, Scrap Salvage, Warehousing"
}
]
}

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.