From 28269e429a79aceebc19d4948388b9fb01c906af Mon Sep 17 00:00:00 2001 From: cproudlock Date: Thu, 3 Sep 2026 08:14:18 -0400 Subject: [PATCH] Import the WINC batches CLI and its README One commit from the sister site, purely additive: a read-only `batches` command so an operator working from the shell can find a batchid to undo, and a README covering the four WINC commands. Two new files under scripts/site_imports/winc/, nothing existing touched. Verified rather than assumed: it changes none of the files fixed in 13be0ef9 (the workflow YAML, the approval check, the agentpackage description, the upload audit), the gate passes, all five workflows still parse, and the project map is current. Suite 2369. Scope: product --- scripts/site_imports/winc/README.md | 67 ++++++++++++++++++++ scripts/site_imports/winc/batches.py | 91 ++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 scripts/site_imports/winc/README.md create mode 100644 scripts/site_imports/winc/batches.py diff --git a/scripts/site_imports/winc/README.md b/scripts/site_imports/winc/README.md new file mode 100644 index 00000000..d487213b --- /dev/null +++ b/scripts/site_imports/winc/README.md @@ -0,0 +1,67 @@ +# WINC machine-adoption import (CLI) + +Adopts machines from a Maximo asset export (validated against the WINC / West +Manufacturing site). It only updates machines that already exist; it never +creates canonical per-table rows. Every commit is journaled so it can be undone +as one batch. + +Run all commands from the repo root. Each reads `.env` and connects to the +configured database. + +## 1. Preview (read-only) + +``` +python -m scripts.site_imports.winc.dry_run --export EXPORT.csv +``` + +| switch | what it does | +| --- | --- | +| `--export PATH` | the Maximo ASSET export CSV to analyze (required) | +| `--rows` | print the per-row plan, not just the summary | +| `--csv PATH` | write the full plan to a CSV so runs can be diffed | + +Nothing is written. Use this to see what would change and which values are +unresolved before committing. + +## 2. Commit (writes, reversibly) + +``` +python -m scripts.site_imports.winc.writer --export EXPORT.csv --commit --acknowledge businessunit +``` + +| switch | what it does | +| --- | --- | +| `--export PATH` | the same export CSV (required) | +| `--commit` | actually write; without it this is a dry run | +| `--acknowledge GROUP` | approve a needs-create group (repeatable), e.g. `businessunit` | +| `--createdby NAME` | who approved the import; stamped on the batch | + +On success it prints the new `batchid` and the exact undo command. + +## 3. List batches + +``` +python -m scripts.site_imports.winc.batches +``` + +| switch | what it does | +| --- | --- | +| `--limit N` | show at most N batches, newest first (default 50) | +| `--json` | emit JSON instead of a table | + +Read-only. Use it to find the `batchid` to undo. + +## 4. Undo a batch + +``` +python -m scripts.site_imports.winc.undo --batch BATCHID --commit +``` + +| switch | what it does | +| --- | --- | +| `--batch BATCHID` | the batch to reverse (required) | +| `--commit` | actually reverse; without it this is a no-op | + +Reversal is conservative: a cell a human changed since the import is left as-is +and reported, and a created business unit is deleted only when nothing outside +the batch still references it. diff --git a/scripts/site_imports/winc/batches.py b/scripts/site_imports/winc/batches.py new file mode 100644 index 00000000..00a4545d --- /dev/null +++ b/scripts/site_imports/winc/batches.py @@ -0,0 +1,91 @@ +"""WINC machine adoption - BATCHES (list applied/undone import batches). + +A read-only companion to writer.py and undo.py: it prints the WINC batches +newest-first so an operator can find a batchid to feed `--batch` into the undo +CLI. Like the other WINC commands it is WINC-named only for discoverability; the +underlying importbatches table is import-agnostic, so this simply filters to the +winc-machine-adoption kind. +""" + +import argparse +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname( + os.path.dirname(os.path.abspath(__file__)))))) + +from dotenv import load_dotenv +from pathlib import Path + +load_dotenv(Path(__file__).resolve().parents[3] / '.env') + +from sqlalchemy import text + +from scripts.site_imports.winc.dry_run import build_app_connection + +_KIND = 'winc-machine-adoption' + + +def _iso(value): + # PyMySQL hands back datetime objects; SQLite hands back the stored ISO + # string. Normalise both to an ISO string so the output is stable. + if value is None: + return '-' + if hasattr(value, 'isoformat'): + return value.isoformat() + return str(value) + + +def list_batches(connection, limit): + rows = connection.execute(text( + 'SELECT batchid, kind, rowcount, createdat, createdby, status, ' + 'undoneat FROM importbatches WHERE kind = :kind ' + 'ORDER BY createdat DESC LIMIT :limit'), + {'kind': _KIND, 'limit': limit}).fetchall() + return [ + {'batchid': r[0], 'kind': r[1], 'rowcount': r[2], + 'createdat': _iso(r[3]), 'createdby': r[4], 'status': r[5], + 'undoneat': _iso(r[6])} + for r in rows + ] + + +def _print_table(batches): + if not batches: + print('No WINC import batches found.') + return + header = '%-36s %-8s %5s %-20s %-16s %s' % ( + 'batchid', 'status', 'rows', 'createdat', 'createdby', 'undoneat') + print(header) + print('-' * len(header)) + for batch in batches: + print('%-36s %-8s %5s %-20s %-16s %s' % ( + batch['batchid'], batch['status'], batch['rowcount'], + batch['createdat'], batch['createdby'] or '-', batch['undoneat'])) + + +def main(argv=None): + parser = argparse.ArgumentParser( + description='List applied/undone WINC import batches, newest first.') + parser.add_argument('--limit', type=int, default=50, + help='Maximum number of batches to show (default 50).') + parser.add_argument('--json', action='store_true', + help='Emit the batches as JSON instead of a table.') + args = parser.parse_args(argv) + + if args.limit < 1: + parser.error('--limit must be a positive integer') + + _app, connection = build_app_connection() + batches = list_batches(connection, args.limit) + + if args.json: + import json + print(json.dumps(batches, indent=2)) + else: + _print_table(batches) + return 0 + + +if __name__ == '__main__': + sys.exit(main())