@floless/app 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/floless-server.cjs
CHANGED
|
@@ -52856,7 +52856,7 @@ function appVersion() {
|
|
|
52856
52856
|
return resolveVersion({
|
|
52857
52857
|
isSea: isSea2(),
|
|
52858
52858
|
sqVersionXml: readSqVersionXml(),
|
|
52859
|
-
define: true ? "0.
|
|
52859
|
+
define: true ? "0.48.0" : void 0,
|
|
52860
52860
|
pkgVersion: readPkgVersion()
|
|
52861
52861
|
});
|
|
52862
52862
|
}
|
|
@@ -52866,7 +52866,7 @@ function resolveChannel(s) {
|
|
|
52866
52866
|
return "dev";
|
|
52867
52867
|
}
|
|
52868
52868
|
function appChannel() {
|
|
52869
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52869
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.48.0" : void 0 });
|
|
52870
52870
|
}
|
|
52871
52871
|
|
|
52872
52872
|
// workflow-update.ts
|
|
@@ -54309,9 +54309,54 @@ function rollup(members, countWeighted = false) {
|
|
|
54309
54309
|
}
|
|
54310
54310
|
return { score: den > 0 ? Math.round(num2 / den * 100) : null, tons, counts };
|
|
54311
54311
|
}
|
|
54312
|
+
function bandEnd(end, rows) {
|
|
54313
|
+
const row = end.conn ? rows.get(end.conn) : void 0;
|
|
54314
|
+
const complete = !!(row && row.type && row.detail && row.targets && Object.keys(row.targets).length > 0);
|
|
54315
|
+
const hasNote = !!(end.note && end.note.trim() !== "");
|
|
54316
|
+
const evidence = hasNote || row?.source === "drawing";
|
|
54317
|
+
if (end.connVerified === true) return { band: "verified", reason: "" };
|
|
54318
|
+
if (!end.conn && !hasNote) return { band: "rfi", reason: "no connection assigned" };
|
|
54319
|
+
if (complete && evidence) return { band: "high", reason: "" };
|
|
54320
|
+
if (complete) return { band: "med", reason: "connection type assumed (no drawing note)" };
|
|
54321
|
+
if (hasNote && end.detail && end.detail.trim() !== "") return { band: "med", reason: "free-text note + detail (not a mapped library row)" };
|
|
54322
|
+
if (end.conn && !row) return { band: "low", reason: "references an unknown connection row" };
|
|
54323
|
+
return { band: "low", reason: row ? "library row has a type only (no detail/component id)" : "connection type only (no detail)" };
|
|
54324
|
+
}
|
|
54325
|
+
function scoreConnections(contractInput) {
|
|
54326
|
+
const contract = contractInput ?? {};
|
|
54327
|
+
const rows = /* @__PURE__ */ new Map();
|
|
54328
|
+
for (const c of contract.connections ?? []) if (c && c.id) rows.set(c.id, c);
|
|
54329
|
+
const byEnd = [];
|
|
54330
|
+
(contract.plans ?? []).forEach((plan, planIdx) => {
|
|
54331
|
+
for (const m of plan.members ?? []) {
|
|
54332
|
+
if (m.role === "column") {
|
|
54333
|
+
const c = m.col ?? {};
|
|
54334
|
+
const { band, reason } = bandEnd(c, rows);
|
|
54335
|
+
byEnd.push({ id: `${m.id}#col`, planIdx, sheet: plan.sheet ?? "", memberId: m.id, which: "col", band, reason });
|
|
54336
|
+
} else {
|
|
54337
|
+
const ends = m.ends ?? [];
|
|
54338
|
+
for (const which of [0, 1]) {
|
|
54339
|
+
const { band, reason } = bandEnd(ends[which] ?? {}, rows);
|
|
54340
|
+
byEnd.push({ id: `${m.id}#${which}`, planIdx, sheet: plan.sheet ?? "", memberId: m.id, which, band, reason });
|
|
54341
|
+
}
|
|
54342
|
+
}
|
|
54343
|
+
}
|
|
54344
|
+
});
|
|
54345
|
+
const counts = emptyCounts();
|
|
54346
|
+
let num2 = 0, den = 0;
|
|
54347
|
+
for (const e of byEnd) {
|
|
54348
|
+
counts[e.band]++;
|
|
54349
|
+
if (e.band === "rfi") continue;
|
|
54350
|
+
num2 += BAND_WEIGHT[e.band];
|
|
54351
|
+
den += 1;
|
|
54352
|
+
}
|
|
54353
|
+
const category = { score: den > 0 ? Math.round(num2 / den * 100) : null, tons: 0, counts };
|
|
54354
|
+
return { byEnd, category };
|
|
54355
|
+
}
|
|
54312
54356
|
function scoreContract(contractInput) {
|
|
54313
54357
|
const contract = contractInput ?? {};
|
|
54314
54358
|
const plans = contract.plans ?? [];
|
|
54359
|
+
const conn = scoreConnections(contract);
|
|
54315
54360
|
const byMember = [];
|
|
54316
54361
|
const byDetail = [];
|
|
54317
54362
|
let totalSegments = 0;
|
|
@@ -54357,11 +54402,12 @@ function scoreContract(contractInput) {
|
|
|
54357
54402
|
return {
|
|
54358
54403
|
byMember,
|
|
54359
54404
|
byDetail,
|
|
54405
|
+
byConnection: conn.byEnd,
|
|
54360
54406
|
byCategory: {
|
|
54361
54407
|
beams,
|
|
54362
54408
|
columns,
|
|
54363
54409
|
details,
|
|
54364
|
-
connections:
|
|
54410
|
+
connections: conn.category
|
|
54365
54411
|
},
|
|
54366
54412
|
coverage: {
|
|
54367
54413
|
members: byMember.length,
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"plans": { "type": "array", "items": { "$ref": "#/$defs/plan" } },
|
|
40
40
|
"filter": { "$ref": "#/$defs/filter", "description": "Layer/thickness/colour filter pre-stage. `sheets[]` holds per-framing-plan geometry; the facets + `mode` + on-flag selection are GLOBAL across sheets. The saved selection scopes which lines the reader binds profile labels to per sheet; replaces the length heuristic, fallback when absent. CONFIDENTIAL: each sheet's `page.bg_b64` is machine-local, never committed." },
|
|
41
|
+
"connections": { "type": "array", "items": { "$ref": "#/$defs/connection" }, "description": "Vendor-neutral connection library: each row maps a connection type → the firm's design detail# → per-platform component ids. Member ends/columns reference a row by id via `ends[].conn` / `col.conn` (legacy `note`/`detail` = fallback when `conn` is empty)." },
|
|
41
42
|
"dims3d": { "type": "array", "items": { "$ref": "#/$defs/dim3" }, "description": "Draft-only 3D dimensions (editor annotations, model-global). World-scene mm. NOT baked into the lock / 3D scene / IFC / BOM." }
|
|
42
43
|
},
|
|
43
44
|
"$defs": {
|
|
@@ -164,6 +165,19 @@
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
},
|
|
168
|
+
"connection": {
|
|
169
|
+
"type": "object",
|
|
170
|
+
"required": ["id", "type"],
|
|
171
|
+
"additionalProperties": true,
|
|
172
|
+
"description": "One connection-library row. `detail` is the firm's VENDOR-NEUTRAL design detail#; `targets` is an open map platform→component id (tekla/sds2/revit/…), never hardcoded to one platform.",
|
|
173
|
+
"properties": {
|
|
174
|
+
"id": { "type": "string", "description": "Stable row id, e.g. cx-moment-1." },
|
|
175
|
+
"type": { "type": "string", "description": "moment | drag | shear | braced | pinned | fixed (free string allowed)." },
|
|
176
|
+
"detail": { "type": "string", "description": "Design detail number, e.g. 50S504 — the firm's document, platform-neutral." },
|
|
177
|
+
"targets": { "type": "object", "additionalProperties": { "type": "string" }, "description": "platform → component id, e.g. { \"tekla\": \"146\" }. Open map; add sds2/revit freely." },
|
|
178
|
+
"source": { "enum": ["company-rules", "user", "drawing"], "description": "Where the row came from (drives the connection confidence band)." }
|
|
179
|
+
}
|
|
180
|
+
},
|
|
167
181
|
"point2": {
|
|
168
182
|
"type": "array",
|
|
169
183
|
"items": { "type": "number" },
|
|
@@ -313,7 +327,9 @@
|
|
|
313
327
|
"tos": { "type": ["number", "null"], "description": "Top-of-steel elevation in inches at this end." },
|
|
314
328
|
"note": { "type": "string", "description": "Connection note (e.g. moment, shear, braced)." },
|
|
315
329
|
"tosDef": { "type": "boolean", "description": "True = follows the plan default; false = read from a drawing callout or set by hand." },
|
|
316
|
-
"detail": { "type": "string", "description": "Connection detail reference, e.g. \"5-S504\"." }
|
|
330
|
+
"detail": { "type": "string", "description": "Connection detail reference, e.g. \"5-S504\"." },
|
|
331
|
+
"conn": { "type": "string", "description": "Connection-library row id this end uses (references `connections[].id`). Empty = fall back to `note`/`detail`." },
|
|
332
|
+
"connVerified": { "type": "boolean", "description": "True = a human confirmed this end's connection (scores Verified)." }
|
|
317
333
|
}
|
|
318
334
|
},
|
|
319
335
|
"col": {
|
|
@@ -323,7 +339,9 @@
|
|
|
323
339
|
"bos": { "type": ["number", "null"], "description": "Bottom-of-steel elevation in inches." },
|
|
324
340
|
"tos": { "type": ["number", "null"], "description": "Top-of-steel elevation in inches." },
|
|
325
341
|
"note": { "type": "string" },
|
|
326
|
-
"detail": { "type": "string" }
|
|
342
|
+
"detail": { "type": "string" },
|
|
343
|
+
"conn": { "type": "string", "description": "Connection-library row id for this column's connection (references `connections[].id`)." },
|
|
344
|
+
"connVerified": { "type": "boolean", "description": "True = a human confirmed this column's connection (scores Verified)." }
|
|
327
345
|
}
|
|
328
346
|
}
|
|
329
347
|
}
|
|
@@ -891,7 +891,7 @@ function panel(){
|
|
|
891
891
|
else if(allBeam){const s0=beams.map(m=>m.ends[0]),s1=beams.map(m=>m.ends[1]);
|
|
892
892
|
wMTos('tosA',s0);wMText('ntA',s0,(o,v)=>o.note=v);wMText('dtA',s0,(o,v)=>o.detail=v);
|
|
893
893
|
wMTos('tosB',s1);wMText('ntB',s1,(o,v)=>o.note=v);wMText('dtB',s1,(o,v)=>o.detail=v);
|
|
894
|
-
{const me=document.getElementById('matchEnds');if(me)me.onclick=()=>edit(()=>{for(const m of selArr())if(m.role!=='column')m.ends[1]={tos:m.ends[0].tos,note:m.ends[0].note,tosDef:m.ends[0].tosDef,detail:m.ends[0].detail};});}}
|
|
894
|
+
{const me=document.getElementById('matchEnds');if(me)me.onclick=()=>edit(()=>{for(const m of selArr())if(m.role!=='column')m.ends[1]={tos:m.ends[0].tos,note:m.ends[0].note,tosDef:m.ends[0].tosDef,detail:m.ends[0].detail,conn:m.ends[0].conn,connVerified:m.ends[0].connVerified};});}}
|
|
895
895
|
return;}
|
|
896
896
|
const m=ensureMeta(arr[0]), wpf=WT[m.profile], L=(len(m.wp[0],m.wp[1])/FT).toFixed(1), col=(m.role==='column'), pos=posDefault(m);
|
|
897
897
|
const _lvl=({'S-202':'second','S-203':'roof'})[P.sheet];
|
|
@@ -951,7 +951,7 @@ function panel(){
|
|
|
951
951
|
wireTos('tosB',m.ends[1]);
|
|
952
952
|
document.getElementById('ntB').onchange=e=>edit(()=>{m.ends[1].note=e.target.value;});
|
|
953
953
|
wireDet('dtB',m.ends[1]);
|
|
954
|
-
document.getElementById('matchEnds').onclick=()=>edit(()=>{m.ends[1]={tos:m.ends[0].tos,note:m.ends[0].note,tosDef:m.ends[0].tosDef,detail:m.ends[0].detail};});
|
|
954
|
+
document.getElementById('matchEnds').onclick=()=>edit(()=>{m.ends[1]={tos:m.ends[0].tos,note:m.ends[0].note,tosDef:m.ends[0].tosDef,detail:m.ends[0].detail,conn:m.ends[0].conn,connVerified:m.ends[0].connVerified};});
|
|
955
955
|
}
|
|
956
956
|
document.getElementById('del').onclick=()=>edit(()=>{P.members=P.members.filter(x=>x.id!==m.id);selIds.clear();if(selDimIds.size){P.dims=P.dims.filter(d=>!selDimIds.has(d.id));selDimIds.clear();}}); // also drop any dims a mixed marquee selected
|
|
957
957
|
{const vb=document.getElementById('verifyBtn');if(vb)vb.onclick=()=>edit(()=>{m.verified=!m.verified;});}
|
|
@@ -1527,7 +1527,29 @@ function _mTons(m,ptPerFt){const plf=_wt(m.profile);if(plf==null)return 0;let ft
|
|
|
1527
1527
|
return ft*plf/2000;}
|
|
1528
1528
|
function _detSheet(t){const m=String(t||'').toUpperCase().match(/S-?\s?\d{2,3}/);return m?m[0].replace(/\s/g,''):null;}
|
|
1529
1529
|
const _cnt0=()=>({verified:0,high:0,med:0,low:0,rfi:0});
|
|
1530
|
+
function _scoreConnections(){
|
|
1531
|
+
const rows={};for(const c of (C.connections||[]))if(c&&c.id)rows[c.id]=c;
|
|
1532
|
+
const bandEnd=(end)=>{const row=end.conn?rows[end.conn]:null;
|
|
1533
|
+
const complete=!!(row&&row.type&&row.detail&&row.targets&&Object.keys(row.targets).length>0);
|
|
1534
|
+
const hasNote=!!(end.note&&end.note.trim()!=='');
|
|
1535
|
+
// Only a drawing callout counts as evidence — a company-rules/user row assignment is a mapping, not drawing-confirmed.
|
|
1536
|
+
const evidence=hasNote||(row&&row.source==='drawing');
|
|
1537
|
+
if(end.connVerified===true)return{band:'verified',reason:''};
|
|
1538
|
+
if(!end.conn&&!hasNote)return{band:'rfi',reason:'no connection assigned'};
|
|
1539
|
+
if(complete&&evidence)return{band:'high',reason:''};
|
|
1540
|
+
if(complete)return{band:'med',reason:'connection type assumed (no drawing note)'};
|
|
1541
|
+
if(hasNote&&end.detail&&end.detail.trim()!=='')return{band:'med',reason:'free-text note + detail (not a mapped library row)'};
|
|
1542
|
+
if(end.conn&&!row)return{band:'low',reason:'references an unknown connection row'};
|
|
1543
|
+
return{band:'low',reason:row?'library row has a type only (no detail/component id)':'connection type only (no detail)'};};
|
|
1544
|
+
const byEnd=[];
|
|
1545
|
+
(C.plans||[]).forEach((plan,pi)=>{for(const m of (plan.members||[])){
|
|
1546
|
+
if(m.role==='column'){const r=bandEnd(m.col||{});byEnd.push({id:m.id+'#col',pi,sheet:plan.sheet||'',memberId:m.id,which:'col',band:r.band,reason:r.reason});}
|
|
1547
|
+
else{const ends=m.ends||[];for(const w of [0,1]){const r=bandEnd(ends[w]||{});byEnd.push({id:m.id+'#'+w,pi,sheet:plan.sheet||'',memberId:m.id,which:w,band:r.band,reason:r.reason});}}}});
|
|
1548
|
+
const counts=_cnt0();let num=0,den=0;
|
|
1549
|
+
for(const e of byEnd){counts[e.band]++;if(e.band==='rfi')continue;num+=BANDW[e.band];den+=1;}
|
|
1550
|
+
return{cat:{score:den>0?Math.round(num/den*100):null,tons:0,counts},ends:byEnd};}
|
|
1530
1551
|
function scoreContractJS(){const plans=C.plans||[];const byMember=[],byDetail=[];let segs=0;
|
|
1552
|
+
const _conn=_scoreConnections();
|
|
1531
1553
|
const known=new Set();for(const p of plans)if(p.sheet)known.add(p.sheet.toUpperCase());for(const s of Object.keys(C.detail_bubbles||{}))known.add(s.toUpperCase());
|
|
1532
1554
|
plans.forEach((plan,pi)=>{segs+=(plan.segments||[]).length;const ms=plan.members||[];const dup=_confDupIds(ms);
|
|
1533
1555
|
for(const m of ms){const r=_scoreMember(m,dup);byMember.push({id:m.id,planIdx:pi,sheet:plan.sheet||'',role:m.role==='column'?'column':'beam',profile:m.profile||'',band:r.band,tons:r.band==='rfi'?0:_mTons(m,plan.pt_per_ft),factors:r.factors});}
|
|
@@ -1536,7 +1558,7 @@ function scoreContractJS(){const plans=C.plans||[];const byMember=[],byDetail=[]
|
|
|
1536
1558
|
const dRoll=arr=>{const c=_cnt0();let n=0,d=0;for(const x of arr){c[x.band]++;n+=BANDW[x.band];d++;}return {score:d>0?Math.round(n/d*100):null,tons:0,counts:c};};
|
|
1537
1559
|
const beams=roll(byMember.filter(m=>m.role==='beam'));const columns=roll(byMember.filter(m=>m.role==='column'));const details=dRoll(byDetail);
|
|
1538
1560
|
const sc=byMember.filter(m=>m.band!=='rfi');const n=sc.reduce((s,m)=>s+m.tons*BANDW[m.band],0),d=sc.reduce((s,m)=>s+m.tons,0);
|
|
1539
|
-
return {byMember,byDetail,byCategory:{beams,columns,details,connections:
|
|
1561
|
+
return {byMember,byDetail,byConnection:_conn.ends,byCategory:{beams,columns,details,connections:_conn.cat},coverage:{members:byMember.length,segments:segs,note:'Approximate in v1 — precise per-segment binding is Slice 2.'},overall:{score:d>0?Math.round(n/d*100):null,tons:d,rfiCount:byMember.filter(m=>m.band==='rfi').length}};}
|
|
1540
1562
|
function bandColorForPct(p){return p==null?'var(--mut)':p>=95?BANDC.verified:p>=80?BANDC.high:p>=50?BANDC.med:BANDC.low;}
|
|
1541
1563
|
// Effective target: the user's per-read override (in the contract) wins; else the app's
|
|
1542
1564
|
// target_confidence input default (read in boot()); else none. Editing the override never touches
|
|
@@ -1569,7 +1591,7 @@ function renderConf(){const s=scoreContractJS();const cat=s.byCategory;
|
|
|
1569
1591
|
'<div class=cc-score style="color:'+(stub?'var(--mut)':bandColorForPct(cs.score))+'">'+(stub||cs.score==null?'—':cs.score+'%')+'</div>'+
|
|
1570
1592
|
_bar(stub?null:cs.score)+
|
|
1571
1593
|
'<div class=cc-counts>'+(stub?'<span>reserved slot</span>':_countsHtml(cs.counts))+'</div></div>';
|
|
1572
|
-
document.getElementById('confCats').innerHTML=card('beams','Beams',cat.beams)+card('columns','Columns',cat.columns)+card('details','Details',cat.details)+card('connections','Connections',cat.connections
|
|
1594
|
+
document.getElementById('confCats').innerHTML=card('beams','Beams',cat.beams)+card('columns','Columns',cat.columns)+card('details','Details',cat.details)+card('connections','Connections',cat.connections);
|
|
1573
1595
|
const bands=['all','verified','high','med','low','rfi'],cats=['all','beams','columns','details','connections'];
|
|
1574
1596
|
document.getElementById('confFilter').innerHTML=
|
|
1575
1597
|
'<span class=glab>Band</span><span class=grp>'+bands.map(b=>'<button data-band="'+b+'" class="'+(confBand===b?'on':'')+'">'+(b==='all'?'All':BANDLAB[b])+'</button>').join('')+'</span>'+
|
|
@@ -1579,7 +1601,21 @@ function renderConf(){const s=scoreContractJS();const cat=s.byCategory;
|
|
|
1579
1601
|
wireConf();}
|
|
1580
1602
|
function confBodyHtml(s){
|
|
1581
1603
|
if(!s.byMember.length&&!s.byDetail.length)return '<div class=hint style="padding:14px 0">No members in the takeoff yet.</div>';
|
|
1582
|
-
if(confCat==='connections')
|
|
1604
|
+
if(confCat==='connections'){
|
|
1605
|
+
// worst-first so the RFIs / unmapped ends surface at the top (the "what to fix next" view)
|
|
1606
|
+
const _ord={rfi:0,low:1,med:2,high:3,verified:4};
|
|
1607
|
+
let conn=s.byConnection.slice().sort((a,b)=>_ord[a.band]-_ord[b.band]);
|
|
1608
|
+
if(confBand!=='all')conn=conn.filter(e=>e.band===confBand);
|
|
1609
|
+
if(!conn.length)return '<div class=hint style="padding:14px 0">Nothing matches this filter.</div>';
|
|
1610
|
+
const whichLabel=w=>w==='col'?'Column':w===0?'End 1':'End 2';
|
|
1611
|
+
let h='<table class=ftab><thead><tr><th>Band</th><th>Member</th><th>End</th><th>Sheet</th><th>Reason</th><th></th></tr></thead><tbody>';
|
|
1612
|
+
conn.forEach(e=>{h+='<tr><td><span class=pill style="'+BANDPILL[e.band]+'">'+BANDLAB[e.band]+'</span></td>'+
|
|
1613
|
+
'<td>'+esc(e.memberId)+'</td>'+
|
|
1614
|
+
'<td>'+whichLabel(e.which)+'</td>'+
|
|
1615
|
+
'<td>'+esc(e.sheet)+'</td>'+
|
|
1616
|
+
'<td><span class=hint>'+esc(e.reason||'—')+'</span></td>'+
|
|
1617
|
+
'<td><button class=ghost data-loc="'+esc(e.memberId)+'" data-pi="'+e.pi+'">Locate</button></td></tr>';});
|
|
1618
|
+
h+='</tbody></table>';return h;}
|
|
1583
1619
|
let mem=s.byMember.slice();
|
|
1584
1620
|
if(confCat==='beams')mem=mem.filter(m=>m.role==='beam');else if(confCat==='columns')mem=mem.filter(m=>m.role==='column');else if(confCat==='details')mem=[];
|
|
1585
1621
|
if(confBand!=='all')mem=mem.filter(m=>m.band===confBand);
|