PLC Fleet Archive
Git-backed Product Data Management (PDM) for PSI’s fleet of Allen-Bradley PLC programs. Automated version control, change tracking, and attribution for 489+ Logix 5000 projects — essentially “Git for PLC code.”
Single fleet analyzer as of 2026-06-23 (ADR-001)
This repo is now the single analyzer for the whole PLC fleet. The analysis toolkit (
l5x_parser.js,acd_analyzer.py,compute-scorecards.js,generate-fleet-summaries.js) is vendored in-repo atscripts/analysis/, and the nightly job producesanalysis.json+scorecard.json+summary.jsonfor every project. psi-plc-runner is conversion-only and no longer analyzes. The MCP-dir sync usesrobocopy /E(never deletes). Seedocs/ADR-001-single-analyzer.mdin the repo.
Overview
Rockwell Automation’s ACD files have no built-in version control, no change history, and no author attribution. Engineers save ACD files to the K: drive and there’s no record of who changed what or when.
The PLC Fleet Archive solves this by creating a git-backed repository that automatically:
- Scans K: drive nightly for new or modified ACD files (via MD5 hash comparison)
- Analyzes each changed program (modules, tags, programs, routines, I/O mapping)
- Diffs against the previous version to detect specific structural changes
- Attributes changes (NTFS file owner, assigned controls engineer, timestamps)
- Commits everything with structured git history
| Property | Value |
|---|---|
| Repository | ProgressiveSurface/plc-fleet-archive |
| Projects archived | 88 (from L5X analysis), expanding to 489+ via ACD analyzer |
| Total rungs tracked | 100,226 |
| Total I/O modules | 1,637 |
| Nightly scan | 11pm ET via GitHub Actions on PS-PROXY |
| Analysis engine | acd_analyzer.py (100% coverage) + l5x_parser.js (richer L5X data) |
How It Works
K: Drive (engineers save ACD files as usual)
│
├─ scan-fleet.ps1 (nightly, detect changes via MD5)
│ ↓
├─ compute-attribution.ps1 (NTFS owner + AFTEC engineer)
│ ↓
├─ analyze-project.ps1 (acd_analyzer.py or l5x_parser.js)
│ ↓
├─ diff-analysis.js (structural diff: tags, modules, rungs)
│ ↓
├─ commit-changes.ps1 (git commit with attribution)
│ ↓
└─ MCP Server → "Ask the Fleet" chatbot + PSI Explorer
Engineers don’t change their workflow. They save ACD files to K: drive exactly as they always have. The archive detects and tracks changes automatically.
Attribution: “Who Changed It?”
ACD files do NOT store editor identity. The archive layers multiple sources to determine attribution:
| Layer | Source | Confidence | Always Available? |
|---|---|---|---|
| 1 | NTFS file owner (Get-Acl) | Medium | Yes |
| 2 | ACD header save timestamps | Low (no user) | Yes |
| 3 | AFTEC CONT.ENGR (assigned controls engineer) | Medium | Yes — via UniData API |
| 4 | Windows Security Event Log (Event ID 4663) | High | If NTFS auditing enabled |
| 5 | Manual override (git notes) | Confirmed | On-demand |
Each change record stores:
{
"date": "2026-02-28T23:15:00-05:00",
"acdHash": "a1b2c3d4...",
"attribution": {
"confidence": "assigned",
"ntfsOwner": "PTI_DOMAIN\\jsmith",
"controlsEngineer": "John Smith"
},
"changes": {
"summary": "3 tags added, +5 rungs in Pressure_Control"
}
}Structural Diffing
The archive doesn’t just detect that a file changed — it tells you what changed inside the PLC program:
- Tags: Added, removed, data type changed, description changed
- Rungs: Count changes per routine (e.g., “+5 rungs in Pressure_Control”)
- Modules: I/O modules added, removed, slot changed, RPI changed
- Programs: Programs/routines added or removed
- Controller: Firmware version, processor type, security code changes
- Data Types: UDTs added or removed
Example diff output:
1 tag(s) added, 1 tag(s) removed, +5 rungs in Pressure_Control, firmware 34.11 → 34.12
Repository Structure
plc-fleet-archive/
manifest.json # Fleet index: all projects + latest state
config/fleet-config.json # Paths, schedules, thresholds
projects/
{job}/
analysis.json # Latest PLC analysis (unified schema)
metadata.json # ACD file metadata + attribution
history.json # Array of change records with diffs
scripts/
scan-fleet.ps1 # K: drive change detector
compute-attribution.ps1 # NTFS + AFTEC attribution engine
analyze-project.ps1 # Analysis wrapper (ACD + L5X)
diff-analysis.js # Structural JSON differ
commit-changes.ps1 # Git commit with structured messages
generate-report.js # Nightly change report
seed-archive.js # One-time: populate from existing data
reports/
nightly/{YYYY-MM-DD}.json # Daily change reports
fleet-summary.json # 90-day scan history
.github/workflows/
nightly-scan.yml # Scheduled GitHub Actions pipeline
Nightly Pipeline
The GitHub Actions workflow runs on the [self-hosted, ps-proxy] runner at 11pm ET:
- Scan — Compare K: drive ACD file MD5 hashes against
manifest.json - Attribute — Extract NTFS metadata, query AFTEC for assigned controls engineer
- Analyze — Run
acd_analyzer.py(always) +l5x_parser.js(if L5X exists) - Diff — Compare new analysis against previous version
- Commit — Update project files, history, manifest; git commit with structured message
- Report — Generate nightly change report + update fleet summary
- Push — Push to GHE
Typical runtime: ~5 min for change detection, ~30-60 sec per changed project.
MCP Server Integration
The archive data is exposed through 5 MCP tools deployed on PS-PROXY:3100, alongside the existing 37 machine intelligence tools. The get_plc_analysis tool now checks the archive first (preferred source), falling back to legacy flat files and on-demand ACD analysis.
| Tool | Purpose | Example |
|---|---|---|
get_plc_changes | Change history for a project (what changed and when) | “What changed in 2399’s PLC code this month?” |
get_plc_history | Version timeline with attribution (who changed it) | “Who last modified the PLC program for project 2386?” |
search_fleet_tags | Cross-fleet tag/module/program search | ”Which projects use module 1769-IQ16/A?” (found in 55 projects) |
compare_plc_versions | Side-by-side structural diff between projects | ”Compare the PLC programs of 2326 and 2327” |
get_fleet_summary | Aggregate fleet statistics (overview, modules, changes, processors) | “How many projects are in the archive?” |
The archive data lives at C:\Services\plc-fleet-archive on PS-PROXY, synced automatically after each nightly scan.
Complexity Analysis Pipeline
A batch complexity scorer runs against the entire ACD fleet to produce per-project scores and a historical trend.
| Script | Purpose |
|---|---|
batch-complexity-scan.py | Scans all 489 ACDs, extracts 7 sub-metrics via acd-tools, outputs fleet_complexity_scores.csv |
build-complexity-trend.js | Joins scores to comprehensive_dataset.csv, outputs yearly trend and per-project joined CSV |
Run: C:/Python311/python.exe batch-complexity-scan.py --resume (supports incremental resume; ~4–8 hrs full fleet)
Output files (C:\git\PLC\):
| File | Rows | Contents |
|---|---|---|
fleet_complexity_scores.csv | 487 | Job, Complexity (0–100), TotalRungs, TagCount, UdtDepth, RoutineCount, AvgRungs, JsrDepth, TimerDensity |
fleet_complexity_trend.csv | 26 years | ShipYear, N, Mean, Median, Min, Max, P25, P75 |
fleet_complexity_joined.csv | 451 | Per-project scores joined to lead time dataset (ShipYear, Customer, MachineType) |
ACD version handling:
- v29+ (109 projects): Full extraction — all 7 sub-metrics including tags/UDTs via
TagInfo.XML - v12–v20 (378 projects): Fallback path via partial sqlite DB — 5 sub-metrics (tagCount and udtDepth unavailable)
Key finding (2026-03-03): Median complexity was stable at 32–35 from 2010–2022, then surged: 37 (2022) → 41 (2024) → 48 (2025). P75 jumped from 37 to 55. The top quarter of current projects are 50% more complex than anything built pre-2022.
See Data Brain §1.7 for full schema and analysis record.
Interactive Workbench (render-ladder.py)
Added 2026-05-28. Renders any archived project (or an L5X file directly) as a self-contained HTML ladder-logic workbench:
- Every tag is clickable → side panel shows description, data type, scope, alias chain, and every rung that uses it (split into input/output)
- “Trace this output” walks back recursively through driving rungs to answer “why isn’t this on?”
- JSR routine refs are clickable jumps; each routine shows “Called from” backlinks
- Full-text search across tags, rung comments, and routine names (
/to focus) - Diff mode:
--compare-with <previous-analysis.json>highlights added/modified/removed rungs and surfaces the prior text on each modified rung — the natural PR-review surface for PLC changes
# From a refreshed archive entry
python scripts/render-ladder.py --analysis projects/2353/analysis.json --out 2353.html
# Diff against the previous git revision
git show HEAD~1:projects/2353/analysis.json > /tmp/prev.json
python scripts/render-ladder.py --analysis projects/2353/analysis.json \
--compare-with /tmp/prev.json \
--out 2353-diff.htmlSchema 1.1 — descriptions and comments restored (2026-05-28)
The 2026-02-28 seed used an older l5x_parser.js whose UDT member extractor missed ~25% of visible bit members (regex didn’t match across nested attribute orderings) and never emitted AliasFor. The downstream effect was that archived analysis.json files were structurally complete but stripped of every human-readable description and rung comment — the most valuable engineering knowledge in the L5X.
Fixed in this round:
- Rewrote the UDT member extractor in
l5x_parser.js(two-pass walk over<Member …>start tags + their matching</Member>closes; the regex-only approach was inherently fragile). - Added
AliasForcapture on tags. - Re-ran
refresh-analysis.pyacross all 83 archived projects with an L5X on K:.
Result for proj2353 alone: 22 → 30 visible FLT members captured, 438 → 518 member descriptions, 54 rung comments restored. Across the fleet: tens of thousands of recovered descriptions/comments.
analysis.json now carries:
| Field | Source |
|---|---|
controllerTags[].description, …aliasFor | parser v2 |
programs[].tags[].description, …aliasFor | parser v2 |
dataTypes[].description | parser v2 |
dataTypes[].members[].description | parser v2 (fixed) |
programs[].routines[].rungs[].comment | parser v2 |
Related Pages
- plc-fleet-catalog — Fleet inventory (all 1,391 projects, all platforms)
- l5x-tools — ACD/L5X conversion toolchain
- mcp-server — MCP Server (42 tools, including PLC analysis and fleet archive)
- psi-explorer — PSI Explorer with “Ask the Fleet” chatbot