From 79c4a36c70dc4079973a33fa3b46a377a35e7f61 Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Wed, 19 Aug 2026 11:55:39 -0700 Subject: [PATCH] T8.1 - CR-009: kitting statuses are a set, and Micron EUV is not kitting The statuses: the proposed five, adopted as proposed - Not Started, Picking, Staged, In Transit, Delivered - as one named constant (KIT_STATUSES) building the select. They describe fulfillment; the old four ('Open', 'In Progress', 'Kitted', 'Delivered') mixed fulfillment with workflow. A value stored before the set existed is kept, selected, and shown as "(legacy)" - CR-016's rule that renamed vocabularies must not orphan recorded data - and round-trips through collect unchanged. Micron EUV: the sample SOP is the Micron configuration on record (CR-016 / T5.7), and it now names kitting:false beside assets:false - off by CR-006 TOGGLE, in both the creator sample and the wizard sample. The section leaves the form, the rail and the export; its data and model stay exactly where they are (the probe loads the example package under the Micron sample and finds its kitting values intact through collect). Any other SOP turns the section on and it works fully - driven against sopA with everything enabled. sections_check re-pointed, not relaxed: its sample-map pin said "naming only assets"; it now says "naming exactly assets (CR-016) and kitting (CR-009)" - still refusing any section that goes off without a recorded item behind it. Verification (each probe run alone): NEW tests/kitting_check.py 14/14. Regression: sections_check 95/95. Items: CR-009 Co-Authored-By: Claude Fable 5 --- docs/reference/file-map.md | 6 ++ html/work-package-suite-app.js | 2 +- html/wp-creation-app.js | 22 ++++- html/wp-creation-index.html | 3 +- tests/kitting_check.py | 158 +++++++++++++++++++++++++++++++++ tests/sections_check.py | 12 ++- 6 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 tests/kitting_check.py diff --git a/docs/reference/file-map.md b/docs/reference/file-map.md index 76d9445..6ce4bbb 100644 --- a/docs/reference/file-map.md +++ b/docs/reference/file-map.md @@ -287,6 +287,12 @@ python tests/usage_check.py # D5 - one analytics core, admin report python tests/creator_dialogs_check.py # S1 creator - 0 natives, errors at fields 20 checks ``` +Wave 8 adds these: + +```bash +python tests/kitting_check.py # CR-009 - status set + off for Micron EUV 14 checks +``` + **Three probes were re-pointed at `T7.1`.** `sections_check.py` 5b drove the live cross-frame toggle hand-off, which no longer exists; it now pins the surviving path and the fact that an UNSAVED toggle correctly does not travel - a behaviour change, recorded diff --git a/html/work-package-suite-app.js b/html/work-package-suite-app.js index cfc0e80..f9cdb2e 100644 --- a/html/work-package-suite-app.js +++ b/html/work-package-suite-app.js @@ -455,7 +455,7 @@ function loadSampleData(){ // A partial map on purpose. WPSections.normalize fills the rest in as ON, so a // section added after today is not silently off for this SOP. if(typeof WPSections !== 'undefined'){ - state.sections = WPSections.normalize({assets: false}); + state.sections = WPSections.normalize({assets: false, kitting: false}); // CR-009: Micron EUV is not kitting today } // The Micron FMCS sample includes BIM/VDC — enable it so the sequence shows the diff --git a/html/wp-creation-app.js b/html/wp-creation-app.js index 1dfd561..7d2874b 100644 --- a/html/wp-creation-app.js +++ b/html/wp-creation-app.js @@ -15,7 +15,7 @@ const SAMPLE_SOP = { // // Written as a partial map, not a full one: WPSections.normalize fills the rest // in as ON, so a section added later is not silently off for this SOP. - sections:{assets:false}, + sections:{assets:false, kitting:false}, // kitting off: Micron EUV is not kitting today (CR-009/T8.1) project:{name:'Micron — INC Construction Work Packages', number:'26-67-008', client:'Micron Technology, Inc.', division:'Semiconductor', pm:'Nick Siegfried', cm:'K. Boyd', qm:'D. Nguyen', site:'Boise, ID — Fab'}, roles:[{role:'General Foreman',name:'M. Torres'},{role:'Superintendent',name:'K. Boyd'},{role:'Safety Manager / Lead',name:'A. Reyes'},{role:'Quality Manager',name:'D. Nguyen'},{role:'Planner',name:'L. Graver'}], governance:{ issuance:['By Sector / Area','By Discipline'], woSize:'Standard — 3–5 days (≈40–80 hrs)', woFormat:'WP##-[Sector]-[TYPE]', disciplines:['Mechanical','Electrical','Tech'], discMode:'choice', instanceSuffix:'letter', sizeHoursMax:'80' }, @@ -52,6 +52,23 @@ const ISSUED_IDX = STATUS_ORDER.indexOf('Issued'); // the most urgent last). Do not add a fourth: the meeting settled on three, and a // fourth level is how a priority scale stops meaning anything. const WP_PRIORITIES = ['Normal', 'High', 'Urgent']; + +// CR-009 / T8.1: kitting statuses are an explicit set, not free text. The +// proposed five, adopted as proposed: they describe FULFILLMENT (what the +// warehouse is doing), where the old four mixed fulfillment with workflow +// ('Open'). A value stored before this set existed is kept and shown as a +// legacy option - renamed sets must not orphan recorded data (CR-016's rule). +const KIT_STATUSES = ['Not Started', 'Picking', 'Staged', 'In Transit', 'Delivered']; +function buildKitStatusOptions(current){ + const sel=document.getElementById('wp_kit_status'); if(!sel) return; + const cur=(current!=null?current:sel.value)||''; + let html=''+KIT_STATUSES.map(k=> + `${esc(k)}`).join(''); + if(cur && KIT_STATUSES.indexOf(cur)<0){ + html+=``; + } + sel.innerHTML=html; +} const WP_PRIORITY_DEFAULT = 'Normal'; const COST_CODES = ['1000|Project Management','2000|Design and Development','2100|Design','2110|Control System Design','2120|Instrument Design','2130|Electrical Design','2140|Panel Design','2141|Panel Design Rework','2150|BIM','2151|BIM Rework','2160|Documentation','2200|Development','2210|PLC Programming','2220|OIT Programming','2230|SCADA Programming','2240|Simulation Development','2290|Programming Subcontract','2300|Customer Training','3000|Operational Technology','3100|OT Design','3200|Rack Assembly','3300|Network Configuration','3400|Computer Configuration','4000|Construction','4010|Instruments Install','4020|Network & Computers Install','4040|PLC Install','4050|Panel Install','4060|Electrical Install','4070|Mechanical Install','4080|Security Install','4090|Radio Install','4100|Commissioning','4940|Contract Labor','4960|Electrical Subcontract','4970|Mechanical Subcontract','4980|Security Subcontract','4990|Other/Radio Subcontract']; @@ -2689,6 +2706,7 @@ function loadPackageIntoForm(p){ loadPeopleFromPkg(p); set('wp_assignee',p.assigneeId); set('wp_due',p.due); set('wp_spec',p.spec); set('wp_desc',p.desc); set('wp_hours',p.hours); + buildKitStatusOptions(p.kitStatus||''); set('wp_kit_owner',p.kitOwner); set('wp_kit_date',p.kitDate); set('wp_mimo_time',p.mimoTime); set('wp_mimo_loc',p.mimoLoc); set('wp_actual_hrs',p.actualHrs); set('wp_installed_qty',p.installedQty); set('wp_redlines',p.redlines); set('wp_lessons',p.lessons); set('wp_bimlink',p.bimlink); set('wp_iff',p.iff); set('wp_model_area',p.modelArea); @@ -2775,7 +2793,7 @@ function newPackage(){ if(typeof WPUrl !== 'undefined' && window.wpCreatorReady && WPUrl.get('wp')) urlSyncPackage('', {replace:true}); editingId=null; ['wp_subject','wp_system','wp_location','wp_p6_id','wp_p6_desc','wp_wbs','wp_assignee','wp_assignees','wp_distribution','wp_due','wp_spec','wp_desc','wp_hours','wp_kit_owner','wp_kit_date','wp_mimo_time','wp_mimo_loc','wp_actual_hrs','wp_installed_qty','wp_redlines','wp_lessons','wp_bimlink','wp_model_area','wp_scan_link'].forEach(id=>{const el=document.getElementById(id); if(el) el.value='';}); - document.getElementById('wp_type').value=''; document.getElementById('wp_kit_status').value=''; document.getElementById('wp_cost').value=''; + document.getElementById('wp_type').value=''; buildKitStatusOptions(''); document.getElementById('wp_cost').value=''; const prio=document.getElementById('wp_priority'); if(prio) prio.value=WP_PRIORITY_DEFAULT; // CR-003 buildLocationPickers({building:'', floor:'', sector:''}); // CR-004 ['wp_iff','wp_clash'].forEach(id=>{const el=document.getElementById(id); if(el) el.value='';}); diff --git a/html/wp-creation-index.html b/html/wp-creation-index.html index 7802700..b2e04b3 100644 --- a/html/wp-creation-index.html +++ b/html/wp-creation-index.html @@ -387,7 +387,8 @@
Kitting & Material Movement (MIMO)
-
+ +
Options come from KIT_STATUSES (CR-009): an explicit set, never free text. A pre-CR-009 value on a saved package is kept as a legacy option.
scheduled material-move date & time
diff --git a/tests/kitting_check.py b/tests/kitting_check.py new file mode 100644 index 0000000..37dc31c --- /dev/null +++ b/tests/kitting_check.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Are kitting statuses a set, and is kitting off for Micron EUV? — CR-009, T8.1. + +The statuses become an explicit five (Not Started / Picking / Staged / +In Transit / Delivered) - fulfillment states, not free text. A value stored +before the set existed is kept and shown as a legacy option: renamed sets must +not orphan recorded data. The Micron EUV configuration (the sample SOP, per +CR-016/T5.7) turns the section OFF through the CR-006 toggle; any other SOP +can turn it on and everything works; nothing is deleted either way. + +Self-contained: throwaway SQLite, its own uvicorn, headless Edge or Chrome. +Exit 0 all passed, 1 a failure, 2 could not run. +""" +import json +import os +import re +import sys +import tempfile +import time + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import cdp # noqa: E402 +from browser_check import seed, start_server, chk, _PASS, _FAIL # noqa: E402 +from sections_check import set_sop # noqa: E402 +from stepper_check import dismiss_dialogs # noqa: E402 + +KIT = ["Not Started", "Picking", "Staged", "In Transit", "Delivered"] + + +def ascii_(v, n=280): + return re.sub(r"\s+", " ", str(v)).encode("ascii", "replace").decode()[:n] + + +def settle(seconds=0.5): + time.sleep(seconds) + + +def wait_creator(page, tries=40): + for _ in range(tries): + if page.eval("!!window.wpCreatorReady"): + return True + time.sleep(0.3) + return False + + +def kit_options(page): + return json.loads(page.eval( + "JSON.stringify([...document.getElementById('wp_kit_status').options]" + ".map(o=>o.textContent))")) + + +def main(): + exe = cdp.find_browser() + if not exe: + print("no headless-capable browser found; set WP_BROWSER.") + return 2 + + tmpdir = tempfile.mkdtemp(prefix="wpsuite-kit-") + db_path = os.path.join(tmpdir, "check.db") + server = None + browser = None + try: + tok = seed(db_path) + set_sop(db_path, {}) # sopA: every section ON, kitting included + port = cdp.free_port() + base = "http://127.0.0.1:%d" % port + server = start_server(port, db_path) + + browser = cdp.Browser(exe) + page = browser.page() + page.clear_cookies() + page.set_cookie("wp_session", tok["root"]) + page.viewport(1440, 900) + page.goto(base + "/wp-creation-index.html?project=projA") + dismiss_dialogs(page) + chk("the creator boots", wait_creator(page)) + settle(1.6) + page.eval("window.alert=()=>{}; window.confirm=()=>false; window.prompt=()=>null;") + + # ── 1. the set ──────────────────────────────────────────────────────── + print("\n1. an explicit set, not free text") + opts = kit_options(page) + chk("the kitting status control is a select offering exactly the five states", + opts == ["—"] + KIT, ascii_(opts)) + chk("...and the set is one named constant", + json.loads(page.eval("JSON.stringify(KIT_STATUSES)")) == KIT) + + # a pre-CR-009 value survives as a legacy option, selected + page.eval("""loadPackageIntoForm({subject:'legacy kit', type:'Conduit Install', + kitStatus:'Kitted', kitOwner:'Paul Coonrod', constraints:[], holds:[]})""") + settle(0.8) + chk("a stored pre-CR-009 value is kept, selected, and marked legacy", + page.eval("document.getElementById('wp_kit_status').value") == "Kitted" + and any("legacy" in o for o in kit_options(page)), ascii_(kit_options(page))) + chk("...and it round-trips through collect unchanged - nothing re-written", + page.eval("collectPackage().kitStatus") == "Kitted") + page.eval("document.getElementById('wp_kit_status').value='Staged'") + chk("...and picking a new value works from the same control", + page.eval("collectPackage().kitStatus") == "Staged") + + # ── 2. this SOP (kitting ON) has the section, working ───────────────── + print("\n2. an SOP with kitting on") + vis = page.eval("""(() => { const c=document.getElementById('mimo-card'); + return c && !c.hidden && getComputedStyle(c).display !== 'none'; })()""") + chk("the Kitting & MIMO section is present and rendered", bool(vis)) + chk("...and appears in the section rail", page.eval( + "!!document.querySelector(%s)" % json.dumps('.sec-rail-item[data-sec="mimo-card"]'))) + + # ── 3. Micron EUV (the sample): kitting OFF ─────────────────────────── + print("\n3. Micron EUV: off, hidden, not deleted") + page.eval("loadSampleSOP()") + settle(1.0) + chk("the sample IS the Micron configuration and turns kitting off", + page.eval("SOP.sections && SOP.sections.kitting === false")) + hidden = page.eval("""(() => { const c=document.getElementById('mimo-card'); + return c && (c.hidden || getComputedStyle(c).display === 'none'); })()""") + chk("the section is gone from the form", bool(hidden)) + chk("...and from the rail", page.eval( + "!document.querySelector(%s)" % json.dumps('.sec-rail-item[data-sec="mimo-card"]'))) + page.eval("loadExample()") + settle(1.0) + doc = page.eval("(() => { renderPackage(collectPackage()); " + "return document.getElementById('pkg-doc').innerHTML; })()") + chk("...and from the export", "Kitting & MIMO" not in doc) + chk("the example package's kitting DATA is still on the record - hidden, " + "never deleted", + page.eval("collectPackage().kitStatus") != "" + and bool(page.eval("collectPackage().kitOwner")), ascii_( + (page.eval("collectPackage().kitStatus"), + page.eval("collectPackage().kitOwner")))) + + js_errors = [e for e in page.js_errors() if "beforeunload" not in e] + chk("no JavaScript errors anywhere in this run", not js_errors, + ascii_(js_errors[:2])) + + finally: + if browser is not None: + try: + browser.close() + except Exception: + pass + if server is not None: + try: + server.terminate() + except Exception: + pass + + print("\n" + "-" * 54) + print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL))) + for f in _FAIL: + print(" - " + f) + return 1 if _FAIL else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/sections_check.py b/tests/sections_check.py index 6a11922..70fbc4f 100644 --- a/tests/sections_check.py +++ b/tests/sections_check.py @@ -600,8 +600,10 @@ def run(page, base, tok, db_path): chk("...nor in the detail view", MARKERS["assets"] not in mdoc) chk("...nor in the PDF export, which is that document", page.eval("document.getElementById('pkg-doc').innerHTML") == mdoc) + # T8.1 added kitting to the sample's off-list (CR-009), so "every other" + # now means every section not deliberately off by a RECORDED item. chk("every other section is untouched by CR-016", - all(v in mdoc for k, v in MARKERS.items() if k != "assets"), + all(v in mdoc for k, v in MARKERS.items() if k not in ("assets", "kitting")), [k for k, v in MARKERS.items() if v not in mdoc]) chk("the section and its model are still in the codebase, not deleted", page.eval("!!document.getElementById('asset-card')") @@ -609,9 +611,11 @@ def run(page, base, tok, db_path): chk("...and the sample switches it off by TOGGLE, not by removing the data", page.eval("JSON.stringify((SOP||{}).sections||{})").find("assets") >= 0, page.eval("JSON.stringify((SOP||{}).sections||{})")) - chk("...naming only assets, so a section added later is not silently off too", - json.loads(page.eval("JSON.stringify(Object.keys((SOP||{}).sections||{})" - ".filter(k => (SOP.sections||{})[k] === false))")) == ["assets"], + chk("...naming exactly assets (CR-016) and kitting (CR-009), so a section " + "added later is not silently off too", + sorted(json.loads(page.eval("JSON.stringify(Object.keys((SOP||{}).sections||{})" + ".filter(k => (SOP.sections||{})[k] === false))"))) + == ["assets", "kitting"], page.eval("JSON.stringify((SOP||{}).sections||{})")) print(" another SOP enables Assets and it works")