From 566ace9969e48ddedf30b061ff119c02a8da1602 Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Tue, 30 Jun 2026 14:54:57 -0700 Subject: [PATCH] Add BIM/VDC work packages, platform links, and AWP traceability SOP tool: - "Load BIM / VDC template" (Step 4): one click sets BIM deliverable types, install-phase disciplines, BIM release-gate constraints (with EN06 citations), the BIM process sequence, reference sources, BIM sign-off roles, and MWP##-[Area]-[PHASE] numbering. Tags the SOP mode as 'bim'. - Step 3 required sign-off role titles are now editable (default Superintendent/Foreman); the BIM template sets them to BIM Coordinator and Construction Lead (CRS). - Step 7: capture a project-homepage link for the chosen tracking and commissioning platforms. Work Package Creator: - BIM mode (SOP.mode==='bim'): hides controls.dev Assets, Material List, and Kitting/MIMO; shows a BIM Details card (LOD, model area, clash / coordination status, linked scan). - Project tracking/commissioning links are copied onto every WP and shown in the WP output. - "Enabled by - BIM package(s)" field links a field IWP back to the BIM package that enabled it (EWP -> IWP traceability). Co-Authored-By: Claude Opus 4.8 (1M context) --- html/work-package-suite-app.js | 144 ++++++++++++++++++++++++++++++--- html/work-package-suite.html | 16 +++- html/wp-creation-app.js | 30 ++++++- html/wp-creation-index.html | 21 ++++- 4 files changed, 194 insertions(+), 17 deletions(-) diff --git a/html/work-package-suite-app.js b/html/work-package-suite-app.js index 205ea22..2137be5 100644 --- a/html/work-package-suite-app.js +++ b/html/work-package-suite-app.js @@ -24,6 +24,7 @@ function onSizePresetChange(){ } let state = { + mode: 'standard', // 'standard' (field IWP) | 'bim' (BIM/VDC EWP) — set by the BIM template project: {name:'', number:'', client:'', division:'', site:''}, team: {pm:'', apm:'', cm:'', qm:''}, teamMembers: [], @@ -31,7 +32,7 @@ let state = { wpTypes: [], governance: {woformat:'', wosize:'', issuance:[], disciplines:['Mechanical','Electrical','Tech'], discMode:'choice', instanceSuffix:'letter', sizeHoursMax:''}, quality: {qcreq:'', photo:'', hold:''}, - platforms: {tracking:'CxAlloy', commissioning:'CxAlloy'}, + platforms: {tracking:'CxAlloy', commissioning:'CxAlloy', trackingUrl:'', commissioningUrl:''}, sequence: [], constraints: [], sources: [] @@ -92,7 +93,14 @@ const OPTIONAL_ROLES = [ 'Quality Representative', 'Planner', 'Safety Manager', - 'Project Controls Manager' + 'Project Controls Manager', + // BIM / VDC roles (used by the BIM template; also selectable on any SOP) + 'BIM Coordinator', + 'BIM Modeler / Detailer', + 'VDC Manager', + 'Construction Lead (CRS)', + 'Field Lead', + 'General Contractor' ]; const LABOR_COST_CODES = [ @@ -137,6 +145,101 @@ const LABOR_COST_CODES = [ '9000|Administration' ]; +// ── BIM / VDC TEMPLATE ────────────────────────────────────────────────────────── +// The BIM/VDC department also produces work packages under Advanced Work Packaging — +// model & engineering deliverables (EWPs) that feed the field's install packages. +// These defaults are drawn from the EN06 SOP + work instructions and are applied by +// loadBIMTemplate() (the "Load BIM / VDC template" button on Step 4). Everything +// remains editable afterward. +const BIM_WP_TYPES = [ + 'Model Area Package', 'Conduit Routing Package', 'Coordination / Clash Package', + 'First-of-Kind (FOK) Package', '2D Installation Sheet Set', 'Field Detailing / Markup Package', + 'Laser Scan Package', 'As-Built Model / Drawings' +]; +// BIM "disciplines" are the install phases work is broken into (EN06-SOP §4.1.2.5). +const BIM_PHASES = [ + 'Cable Tray & Hangers', 'Conduits & Hangers', 'Wall & Slab Penetrations', + 'Panels & Instrument Racks', 'In-wall Instruments & Stud-ups' +]; +const BIM_TEMPLATE_ROLES = [ + 'BIM Coordinator', 'BIM Modeler / Detailer', 'VDC Manager', + 'Construction Lead (CRS)', 'Field Lead', 'General Contractor' +]; +// Release-gate constraints for a BIM/model package (the BIM equivalent of the field's +// AWP constraints). Citations point back to the EN06 documents. +const BIM_CONSTRAINTS = [ + {name:'Required Docs Received (IO list, P&IDs, drawings, models, specs)', description:'Project-start inputs available — EN06-SOP §3.1'}, + {name:'Conduit Schedule & Schematic Redlines Received', description:'Hard gate: no conduit modeled without these — EN06-SOP §3'}, + {name:'LOD Defined & Agreed', description:'Level of detail set at kick-off — EN06-G-01'}, + {name:'Field Coordination / Laser Scan Complete', description:'Field walk or scan done — EN06-WI-01 / WI-03'}, + {name:'Clash-Free / Coordinated with GC & Trades', description:'Coordination complete — EN06-SOP §8.1'}, + {name:'Constructability Review (CRS) Signed', description:'Internal construction-lead sign-off before GC — EN06-SOP §8.4'}, + {name:'GC / Trade Sign-Off', description:'GC review and approval — EN06-SOP §8.2'}, + {name:'Issued-For-Fabrication (IFF) Granted', description:'Model approved for field use — EN06-SOP §9.4'} +]; +const BIM_SEQUENCE = [ + 'Project start — gather required docs', 'Kick-off (LOD, schedule, cost code)', + 'Field coordination / laser scan', 'Model racks, instruments & panels', + 'Model conduit (after schedule + redlines)', 'BIM coordination / clash with GC & trades', + 'Constructability review (CRS)', 'GC submission & sign-off (IFF)', + '2D installation sheets', 'As-built (scan / redlines)' +]; +const BIM_SOURCES = [ + {label:'IO List (Point Matrix DB)', ph:'controls.dev / SharePoint'}, + {label:'P&IDs', ph:'Procore / SharePoint'}, + {label:'Contract / Design Drawings', ph:'Procore / Bluebeam'}, + {label:'Navisworks / Revit Models', ph:'BIM360 / SharePoint'}, + {label:'Specs & Submittals', ph:'client portal'}, + {label:'Conduit Schedule', ph:'Excel on SharePoint'}, + {label:'Bluebeam Project', ph:'Bluebeam Studio'}, + {label:'Pre-Construction Tracker', ph:'SharePoint'}, + {label:'Constructability Review Sheet (CRS)', ph:'SharePoint'} +]; + +function loadBIMTemplate(){ + if(!confirm('Load the BIM / VDC template?\n\nThis sets the WP types, release-gate constraints, sign-off roles, construction sequence, reference sources, disciplines (install phases) and numbering format to BIM/VDC defaults from the EN06 SOPs. Your project details (Step 1) and team (Step 2) are left alone, and you can edit everything afterward.')) return; + + state.mode = 'bim'; // flags the Creator to show BIM fields / hide field-only sections + + // Step 4 — deliverable package types + state.wpTypes = BIM_WP_TYPES.map(n => ({name:n, enabled:true, notes:'', approval:''})); + renderWPTypes(); + + // Step 5 — disciplines become install phases; multi-discipline; model numbering + state.governance.disciplines = BIM_PHASES.slice(); + state.governance.discMode = 'multi'; + state.governance.woformat = 'MWP##-[Area]-[PHASE]'; + const setVal = (id, v) => { const el = document.getElementById(id); if(el) el.value = v; }; + setVal('gov_disciplines', BIM_PHASES.join(', ')); + setVal('gov_discmode', 'multi'); + setVal('gov_woformat', 'MWP##-[Area]-[PHASE]'); + + // Step 3 — set the two required roles to BIM titles, then add the rest as optional + state.signoffRoles[0] = {role:'BIM Coordinator', name: (state.signoffRoles[0] && state.signoffRoles[0].name) || ''}; + state.signoffRoles[1] = {role:'Construction Lead (CRS)', name: (state.signoffRoles[1] && state.signoffRoles[1].name) || ''}; + setVal('role_super_title', 'BIM Coordinator'); + setVal('role_foreman_title', 'Construction Lead (CRS)'); + BIM_TEMPLATE_ROLES.filter(r => r !== 'BIM Coordinator' && r !== 'Construction Lead (CRS)') + .forEach(r => { if(!state.signoffRoles.some(x => x.role === r)) state.signoffRoles.push({role:r, name:''}); }); + renderOptionalRoles(); + + // Step 9 — BIM release-gate constraints (replaces the field's standard 10) + state.constraints = BIM_CONSTRAINTS.map(c => ({...c})); + _constraintsSeeded = true; + renderStandardConstraints(); + + // Step 8 — BIM process sequence + state.sequence = BIM_SEQUENCE.map(s => ({label:s, kind:'step'})); + renderSequenceSteps(); + + // Step 10 — reference sources + state.sources = BIM_SOURCES.map(s => ({label:s.label, system:'', notes:'', link:'', ph:s.ph, preset:true})); + renderSources(); + + track('bim_template_loaded'); + alert('BIM / VDC template loaded.\n\nReview Step 5 (disciplines are now the install phases; numbering = MWP##-[Area]-[PHASE]) and Step 3 (BIM Coordinator + Construction Lead were added; leave the built-in Superintendent/Foreman blank if not used). Adjust anything as needed, then finish the SOP.'); +} + // ── INITIALIZATION ──────────────────────────────────────────────────────────── window.addEventListener('DOMContentLoaded',()=>{ initializeWPTypes(); @@ -204,7 +307,12 @@ function loadSampleData(){ document.getElementById('proj_cm').value = 'K. Boyd'; document.getElementById('proj_qm').value = 'D. Nguyen'; - // Step 3 already has defaults + // Step 3 — standard required roles + state.mode = 'standard'; + if(state.signoffRoles[0]) state.signoffRoles[0].role = 'Superintendent'; + if(state.signoffRoles[1]) state.signoffRoles[1].role = 'Foreman'; + const stEl = document.getElementById('role_super_title'); if(stEl) stEl.value = 'Superintendent'; + const ftEl = document.getElementById('role_foreman_title'); if(ftEl) ftEl.value = 'Foreman'; document.getElementById('role_super_name').value = 'John Smith'; document.getElementById('role_foreman_name').value = 'Mike Jones'; @@ -272,8 +380,8 @@ function repopulateForm(){ set('proj_apm', state.team.apm); set('proj_cm', state.team.cm); set('proj_qm', state.team.qm); - if(state.signoffRoles[0]) set('role_super_name', state.signoffRoles[0].name); - if(state.signoffRoles[1]) set('role_foreman_name', state.signoffRoles[1].name); + if(state.signoffRoles[0]){ set('role_super_title', state.signoffRoles[0].role); set('role_super_name', state.signoffRoles[0].name); } + if(state.signoffRoles[1]){ set('role_foreman_title', state.signoffRoles[1].role); set('role_foreman_name', state.signoffRoles[1].name); } set('gov_woformat', state.governance.woformat); // gov_wosize is now a @@ -732,7 +844,10 @@ function collectStepData(){ state.team.qm = document.getElementById('proj_qm').value; break; case 3: + // The two required roles now have editable titles (default Superintendent/Foreman). + state.signoffRoles[0].role = (document.getElementById('role_super_title').value || 'Role 1').trim(); state.signoffRoles[0].name = document.getElementById('role_super_name').value; + state.signoffRoles[1].role = (document.getElementById('role_foreman_title').value || 'Role 2').trim(); state.signoffRoles[1].name = document.getElementById('role_foreman_name').value; break; case 5: @@ -753,6 +868,8 @@ function collectStepData(){ case 7: state.platforms.tracking = document.getElementById('plat_tracking').value; state.platforms.commissioning = document.getElementById('plat_commissioning').value; + state.platforms.trackingUrl = (document.getElementById('plat_tracking_url').value || '').trim(); + state.platforms.commissioningUrl = (document.getElementById('plat_commissioning_url').value || '').trim(); break; } } @@ -783,6 +900,7 @@ function completeSOP(){ sop = { meta: {tool:'Work Package Configuration', sample:false}, + mode: state.mode || 'standard', // 'bim' hides field-only sections + shows BIM fields in the Creator project: { name: state.project.name, number: state.project.number, @@ -813,8 +931,14 @@ function completeSOP(){ approval: t.approval || '' })), sources: state.sources.filter(s=>s.label), - field: {trackPlatform: state.platforms.tracking}, - commissioning: {tool: state.platforms.commissioning}, + field: {trackPlatform: state.platforms.tracking, trackPlatformUrl: state.platforms.trackingUrl || ''}, + commissioning: {tool: state.platforms.commissioning, toolUrl: state.platforms.commissioningUrl || ''}, + // Project homepage links in the tracking / commissioning systems. The Creator + // copies these onto every Work Package created for this project. + projectLinks: [ + state.platforms.trackingUrl ? {label:'Tracking — '+state.platforms.tracking, system:state.platforms.tracking, url:state.platforms.trackingUrl} : null, + state.platforms.commissioningUrl ? {label:'Commissioning — '+state.platforms.commissioning, system:state.platforms.commissioning, url:state.platforms.commissioningUrl} : null + ].filter(Boolean), quality: { qcReq: state.quality.qcreq, photo: state.quality.photo, diff --git a/html/work-package-suite.html b/html/work-package-suite.html index 9114c5b..ab3888c 100644 --- a/html/work-package-suite.html +++ b/html/work-package-suite.html @@ -130,19 +130,19 @@ +
+ + + Optional. Saved with every Work Package on this project for one-click access. +
+
+ + + Optional. Saved with every Work Package on this project for one-click access. +
diff --git a/html/wp-creation-app.js b/html/wp-creation-app.js index 6367548..58225d4 100644 --- a/html/wp-creation-app.js +++ b/html/wp-creation-app.js @@ -92,12 +92,24 @@ function applySOP(){ lockQuality('wp_qc'); lockQuality('wp_photo'); lockQuality('wp_hold'); const hint=document.getElementById('wp_number_hint'); hint.textContent = SOP.governance&&SOP.governance.woFormat ? 'auto-built · format: '+SOP.governance.woFormat : ''; buildConstraints(); buildSignoffs(); + applyBimMode(); if(!pkgMaterials.length){ pkgMaterials=[{qty:'',unit:'',desc:''}]; buildMaterials(); } if(!pkgAttach.length){ pkgAttach=[{doc:'',rev:'',link:''}]; buildAttach(); } if(!pkgAssets.length){ pkgAssets=[{tag:'',desc:'',link:''}]; buildAssets(); } if(!pkgWorkSteps.length){ pkgWorkSteps=['']; buildWorkSteps(); } updateNumber(); updateReleaseBanner(); } +// In a BIM/VDC SOP, hide the field-only sections (controls.dev assets, materials, +// kitting/MIMO) and reveal the BIM Details card. Driven by SOP.mode==='bim'. +function isBimSOP(){ return !!(SOP && SOP.mode === 'bim'); } +function applyBimMode(){ + const bim = isBimSOP(); + const show = (id, on) => { const el = document.getElementById(id); if(el) el.style.display = on ? '' : 'none'; }; + show('bim-card', bim); + show('asset-card', !bim); + show('material-card', !bim); + show('mimo-card', !bim); +} function buildCostCodes(){ const sel=document.getElementById('wp_cost'); const cur=sel.value; sel.innerHTML=``+COST_CODES.map(c=>{const [code,desc]=c.split('|'); return ``;}).join(''); @@ -677,7 +689,14 @@ function collectPackage(){ signoffs:pkgSignoffs.map(s=>({role:s.role,name:s.name,date:s.date,signed:s.signed,dateReason:s.dateReason||''})), holds:pkgHolds.map(h=>({...h})), actualHrs:gv('wp_actual_hrs'), installedQty:gv('wp_installed_qty'), redlines:gv('wp_redlines'), lessons:gv('wp_lessons'), + // AWP traceability: which BIM/model package(s) enabled this install package. + bimlink:gv('wp_bimlink'), + // BIM/VDC package details (only meaningful on a BIM SOP). + lod:gv('wp_lod'), modelArea:gv('wp_model_area'), clash:gv('wp_clash'), scanLink:gv('wp_scan_link'), project:(SOP&&SOP.project&&SOP.project.name)||'', track:(SOP&&SOP.field&&SOP.field.trackPlatform)||'', + // Project homepage links in the tracking / commissioning systems, copied from the + // SOP so they travel with every Work Package created for this project. + projectLinks:(SOP&&SOP.projectLinks)?SOP.projectLinks.map(l=>({...l})):[], updatedAt:new Date().toISOString() }; } @@ -696,6 +715,8 @@ function renderPackage(pkg){ let h=`

${esc(pkg.number||'(no number)')} — Work Package

${esc(pkg.project)} · TYPE: ${esc(pkg.type).toUpperCase()} · STATUS: ${esc(pkg.status).toUpperCase()} · ${readyTxt}
`; const costDesc = (COST_CODES.find(c=>c.split('|')[0]===pkg.cost)||'').split('|')[1]; + // Project system links travel with the WP; fall back to the live SOP for older packages. + const plinks = (pkg.projectLinks&&pkg.projectLinks.length) ? pkg.projectLinks : ((SOP&&SOP.projectLinks)||[]); h+=`

1.0 General Information

@@ -710,6 +731,9 @@ function renderPackage(pkg){ + ${plinks.length?``:''} + ${pkg.bimlink?``:''} + ${(pkg.lod||pkg.modelArea||pkg.clash||pkg.scanLink)?``:''}
WP Number${cell(pkg.number)}
Subject${cell(pkg.subject)}
Due Date${cell(pkg.due)}
Specification Section${cell(pkg.spec)}
Description${cell(pkg.desc)}
Project Systems${plinks.map(l=>esc(l.label)+': '+linkify(l.url)).join('
')}
Enabled by (BIM)${/^https?:\/\//i.test(pkg.bimlink)?linkify(pkg.bimlink):cell(pkg.bimlink)}
BIM / Model${[pkg.lod?'LOD: '+esc(pkg.lod):'', pkg.modelArea?'Area: '+esc(pkg.modelArea):'', pkg.clash?'Coordination: '+esc(pkg.clash):'', pkg.scanLink?'Scan: '+linkify(pkg.scanLink):''].filter(Boolean).join('
')}
`; if(pkg.assets&&pkg.assets.length){ h+=`

2.0 Assets (controls.dev)

`; pkg.assets.forEach(a=>h+=``); h+=`
Asset Tag / IDDescriptioncontrols.dev Link
${cell(a.tag)}${cell(a.desc)}${a.link?linkify(a.link):ns()}
`; } @@ -857,6 +881,8 @@ function loadPackageIntoForm(p){ set('wp_due',p.due); set('wp_spec',p.spec); set('wp_desc',p.desc); set('wp_hours',p.hours); 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_lod',p.lod); set('wp_model_area',p.modelArea); set('wp_clash',p.clash); set('wp_scan_link',p.scanLink); + applyBimMode(); buildTypePicker(); document.getElementById('wp_type').value=p.type||''; buildCostCodes(); document.getElementById('wp_cost').value=p.cost||''; set('wp_wbs',p.wbs); @@ -923,8 +949,10 @@ function duplicateWP(){ function newPackage(){ editingId=null; - ['wp_subject','wp_system','wp_location','wp_wbs','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'].forEach(id=>{const el=document.getElementById(id); if(el) el.value='';}); + ['wp_subject','wp_system','wp_location','wp_wbs','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=''; + ['wp_lod','wp_clash'].forEach(id=>{const el=document.getElementById(id); if(el) el.value='';}); + applyBimMode(); setRadio('status','Draft'); numberDims={}; buildNumberDims(); pkgDisciplines=[]; pkgScope={}; pkgDiscStatus={}; buildDisciplinePicker(); renderScope(); onHoursChange(); diff --git a/html/wp-creation-index.html b/html/wp-creation-index.html index 5a2ad01..0ea07e5 100644 --- a/html/wp-creation-index.html +++ b/html/wp-creation-index.html @@ -80,10 +80,25 @@
+
+ + + + -
+
Assets
Every work package is based on one or more assets managed in controls.dev. Paste the controls.dev link for each asset this package covers. A direct integration to pick assets from a list is planned — for now, link them manually.
Asset Tag / IDDescriptioncontrols.dev Link *
@@ -116,7 +131,7 @@
-
+
Material Listi
Structured bill of materials. Feeds kitting and the delivery forecast. Unit is from the Acumatica unit list.
QtyUnitDescription
@@ -147,7 +162,7 @@
-
+
Kitting & Material Movement (MIMO)