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) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 14:54:57 -07:00
parent eefa76e460
commit 566ace9969
4 changed files with 194 additions and 17 deletions

View File

@@ -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 <select>; if a saved value isn't one of the presets
// (e.g. legacy free text), add it as an option so the round-trip preserves it.
@@ -290,6 +398,8 @@ function repopulateForm(){
set('qual_hold', state.quality.hold);
set('plat_tracking', state.platforms.tracking);
set('plat_commissioning', state.platforms.commissioning);
set('plat_tracking_url', state.platforms.trackingUrl);
set('plat_commissioning_url', state.platforms.commissioningUrl);
}
// ── TOOL SWITCHING ────────────────────────────────────────────────────────────
@@ -412,8 +522,9 @@ function renderWPTypes(){
container.appendChild(row);
});
const addRow = document.createElement('div');
addRow.style.cssText = 'margin-top:0.85rem;';
addRow.innerHTML = `<button onclick="addCustomWPType()" style="background:var(--primary,#0f62fe); color:#fff; border:none; padding:0.55rem 1rem; border-radius:4px; font-weight:600; cursor:pointer; font-size:13px;">+ Add Custom Type</button>`;
addRow.style.cssText = 'margin-top:0.85rem; display:flex; gap:0.5rem; flex-wrap:wrap;';
addRow.innerHTML = `<button onclick="addCustomWPType()" style="background:var(--primary,#0f62fe); color:#fff; border:none; padding:0.55rem 1rem; border-radius:4px; font-weight:600; cursor:pointer; font-size:13px;">+ Add Custom Type</button>
<button onclick="loadBIMTemplate()" title="Configure this SOP for the BIM / VDC team's model & engineering work packages" style="background:var(--bg); color:var(--text); border:1px solid var(--border); padding:0.55rem 1rem; border-radius:4px; font-weight:600; cursor:pointer; font-size:13px;">Load BIM / VDC template</button>`;
container.appendChild(addRow);
}
@@ -461,7 +572,8 @@ function removeTeamMember(i){
function renderOptionalRoles(){
const container = document.getElementById('optional-roles-list');
const current = state.signoffRoles.filter(r=>r.role!=='Superintendent'&&r.role!=='Foreman');
// The first two entries are the required (editable-title) roles; the rest are optional.
const current = state.signoffRoles.slice(2);
container.innerHTML = current.map((r,i)=>`
<div style="display:grid; grid-template-columns:1fr 200px 30px; gap:1rem; align-items:center; padding:0.75rem; background:var(--bg); border-radius:6px; margin-bottom:0.5rem; border:1px solid var(--border);">
<select onchange="state.signoffRoles[${state.signoffRoles.indexOf(r)}].role=this.value">
@@ -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,