@dssp/supervision 1.0.20 → 1.0.21
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-client/pages/building-inspection/building-inspection-detail-ai-measurement.d.ts +1 -0
- package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.js +66 -25
- package/dist-client/pages/building-inspection/building-inspection-detail-ai-measurement.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -37,6 +37,22 @@ const SET_FILES = ['video.mov', 'poses.jsonl', 'meta.json'];
|
|
|
37
37
|
function appCaptureLink(tech) {
|
|
38
38
|
return `gamri://${tech}?server=${encodeURIComponent(MEASURE_BASE_URL)}`;
|
|
39
39
|
}
|
|
40
|
+
/* 한 번의 /inspect 에 보낼 검사항목 수. 엔진이 8개까지만 병렬 처리하고, 그 앞단
|
|
41
|
+
Cloudflare 가 100초에서 끊기 때문에(524) 항목이 많으면 나눠 보내야 한다. */
|
|
42
|
+
const INSPECT_BATCH = 8;
|
|
43
|
+
/* 나눠 호출한 /inspect 결과 합치기 — 항목 배열만 이어 붙이고 요약은 다시 센다 */
|
|
44
|
+
function _mergeInspectResults(a, b) {
|
|
45
|
+
const items = [...(a.items || []), ...(b.items || [])];
|
|
46
|
+
const itemsSorted = a.items_sorted || b.items_sorted ? [...(a.items_sorted || a.items || []), ...(b.items_sorted || b.items || [])] : undefined;
|
|
47
|
+
const summary = { pass: 0, fail: 0, hold: 0, pending: 0, error: 0, measure: 0 };
|
|
48
|
+
for (const it of items) {
|
|
49
|
+
summary[it.verdict] = (summary[it.verdict] || 0) + 1;
|
|
50
|
+
if (it.verdict === 'pending' && it.pending_reason === 'measure')
|
|
51
|
+
summary.measure += 1;
|
|
52
|
+
}
|
|
53
|
+
summary.total = items.length;
|
|
54
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, a), b), { items }), (itemsSorted ? { items_sorted: itemsSorted } : {})), { summary });
|
|
55
|
+
}
|
|
40
56
|
/* KCS 기준은 매칭 결과 JSON(kcs_matches[0])으로 저장돼 있다 → 엔진에 보낼 조문 텍스트로 편다.
|
|
41
57
|
문서/조항 라벨을 앞에 붙여야 모델이 어느 기준을 인용하는지 reason 에 쓸 수 있다. */
|
|
42
58
|
function _kcsCriteriaText(raw) {
|
|
@@ -1149,17 +1165,31 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
1149
1165
|
});
|
|
1150
1166
|
}
|
|
1151
1167
|
}
|
|
1152
|
-
const input = { action: 'inspect', checklist, input_kind: 'video_frames' };
|
|
1153
1168
|
// 촬영 배정 되돌리기 — 이걸 안 보내면 모든 항목이 모든 사진을 보게 되어
|
|
1154
1169
|
// "타설 후 항목이 배근 사진으로 판정되는" 오판이 난다 (엔진 실증에서 실제 발생).
|
|
1155
1170
|
// 배정된 촬영이 안 올라온 항목은 엔진이 hold 로 둔다 — 남의 사진으로 판정하지 않게.
|
|
1156
1171
|
const shotItems = this._shotItemsMap();
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1172
|
+
const scoping = !isRecapture && shotItems && imageShots.length === files.length;
|
|
1173
|
+
// 항목을 나눠 여러 번 호출한다. 엔진이 한 번에 8개까지만 병렬 처리하는데,
|
|
1174
|
+
// 그 앞단 Cloudflare 가 100초에서 연결을 끊어 항목이 많으면 524 가 난다.
|
|
1175
|
+
const batches = [];
|
|
1176
|
+
for (let i = 0; i < checklist.length; i += INSPECT_BATCH) {
|
|
1177
|
+
batches.push(checklist.slice(i, i + INSPECT_BATCH));
|
|
1178
|
+
}
|
|
1179
|
+
let result = null;
|
|
1180
|
+
for (let b = 0; b < batches.length; b++) {
|
|
1181
|
+
this.loadingText =
|
|
1182
|
+
batches.length > 1
|
|
1183
|
+
? `AI 자동 판정 중... (${b + 1}/${batches.length} 묶음, 항목 ${batches[b].length}개)`
|
|
1184
|
+
: 'AI 자동 판정 중... (최대 1~2분 소요)';
|
|
1185
|
+
const input = { action: 'inspect', checklist: batches[b], input_kind: 'video_frames' };
|
|
1186
|
+
if (scoping) {
|
|
1187
|
+
input.image_shots = imageShots.join(',');
|
|
1188
|
+
input.shot_items = JSON.stringify(shotItems);
|
|
1189
|
+
}
|
|
1190
|
+
const part = await this._callEngine(input, files);
|
|
1191
|
+
result = result ? _mergeInspectResults(result, part) : part;
|
|
1160
1192
|
}
|
|
1161
|
-
this.loadingText = 'AI 자동 판정 중... (최대 1~2분 소요)';
|
|
1162
|
-
const result = await this._callEngine(input, files);
|
|
1163
1193
|
if (isRecapture) {
|
|
1164
1194
|
this.photos = [...this.photos, ...newPhotos];
|
|
1165
1195
|
this._mergeItems((result === null || result === void 0 ? void 0 : result.items_sorted) || (result === null || result === void 0 ? void 0 : result.items) || [], this.recaptureBase);
|
|
@@ -1441,6 +1471,9 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
1441
1471
|
});
|
|
1442
1472
|
const judged = Object.assign(Object.assign({}, item), result.item);
|
|
1443
1473
|
this._mergeItems([judged]);
|
|
1474
|
+
// 확정 즉시 체크리스트에 기록한다. 「검사 등록」까지 미루면 그 사이에 화면을 벗어나거나
|
|
1475
|
+
// 재판정이 일어났을 때 측정 판정만 유실된다 (VLM 항목은 inspect 결과에 남아 있어 티가 안 남).
|
|
1476
|
+
await this._saveJudgments([judged]);
|
|
1444
1477
|
this._resetMeasureState();
|
|
1445
1478
|
this.panel = 'none';
|
|
1446
1479
|
this.selectedItem = null;
|
|
@@ -1475,9 +1508,33 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
1475
1508
|
return null;
|
|
1476
1509
|
}
|
|
1477
1510
|
}
|
|
1511
|
+
/* 적합/부적합으로 판정된 항목을 체크리스트의 감리자 확인란에 기록한다.
|
|
1512
|
+
보류·미지원·오류는 미검사로 남긴다(자동으로 부적합 처리하지 않는다). */
|
|
1513
|
+
async _saveJudgments(items) {
|
|
1514
|
+
var _a;
|
|
1515
|
+
const judged = items.filter(it => it.verdict === 'pass' || it.verdict === 'fail');
|
|
1516
|
+
if (!judged.length)
|
|
1517
|
+
return 0;
|
|
1518
|
+
const response = await client.mutate({
|
|
1519
|
+
mutation: gql `
|
|
1520
|
+
mutation UpdateChecklistItemsAiJudgment($items: [ChecklistItemAiJudgment!]!) {
|
|
1521
|
+
updateChecklistItemsAiJudgment(items: $items)
|
|
1522
|
+
}
|
|
1523
|
+
`,
|
|
1524
|
+
variables: {
|
|
1525
|
+
items: judged.map(it => ({
|
|
1526
|
+
id: it.item_id,
|
|
1527
|
+
supervisoryConfirmStatus: it.verdict === 'pass' ? 'T' : 'F'
|
|
1528
|
+
}))
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
if (response.errors)
|
|
1532
|
+
throw new Error(((_a = response.errors[0]) === null || _a === void 0 ? void 0 : _a.message) || '검사 결과 저장에 실패했습니다.');
|
|
1533
|
+
return judged.length;
|
|
1534
|
+
}
|
|
1478
1535
|
/* ─── 검사 등록: 감리자 판정 자동 반영 + AI판단 근거 첨부 ─── */
|
|
1479
1536
|
async _registerInspection() {
|
|
1480
|
-
var _a
|
|
1537
|
+
var _a;
|
|
1481
1538
|
const items = this._sortedItems();
|
|
1482
1539
|
if (!items.length) {
|
|
1483
1540
|
notify({ message: '판정 결과가 없습니다.', level: 'warn' });
|
|
@@ -1489,23 +1546,7 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
1489
1546
|
this.loading = true;
|
|
1490
1547
|
this.loadingText = '검사 결과 등록 중...';
|
|
1491
1548
|
// 1) 감리자 적합/부적합 자동 채움 (적합/부적합 판정된 항목만, 없으면 건너뜀)
|
|
1492
|
-
|
|
1493
|
-
const response = await client.mutate({
|
|
1494
|
-
mutation: gql `
|
|
1495
|
-
mutation UpdateChecklistItemsAiJudgment($items: [ChecklistItemAiJudgment!]!) {
|
|
1496
|
-
updateChecklistItemsAiJudgment(items: $items)
|
|
1497
|
-
}
|
|
1498
|
-
`,
|
|
1499
|
-
variables: {
|
|
1500
|
-
items: judged.map(it => ({
|
|
1501
|
-
id: it.item_id,
|
|
1502
|
-
supervisoryConfirmStatus: it.verdict === 'pass' ? 'T' : 'F'
|
|
1503
|
-
}))
|
|
1504
|
-
}
|
|
1505
|
-
});
|
|
1506
|
-
if (response.errors)
|
|
1507
|
-
throw new Error(((_a = response.errors[0]) === null || _a === void 0 ? void 0 : _a.message) || '검사 등록에 실패했습니다.');
|
|
1508
|
-
}
|
|
1549
|
+
await this._saveJudgments(items);
|
|
1509
1550
|
// 2) 항목별 첨부: 판단 근거(AI판단 텍스트) + 근거 프레임/측정 근거 이미지
|
|
1510
1551
|
// ai_report는 pending/error에서 빈 문자열로 오므로 그 항목은 첨부하지 않는다
|
|
1511
1552
|
const attachments = [];
|
|
@@ -1544,7 +1585,7 @@ let BuildingInspectionAiMeasurement = class BuildingInspectionAiMeasurement exte
|
|
|
1544
1585
|
context: { hasUpload: true }
|
|
1545
1586
|
});
|
|
1546
1587
|
if (attachResponse.errors)
|
|
1547
|
-
throw new Error(((
|
|
1588
|
+
throw new Error(((_a = attachResponse.errors[0]) === null || _a === void 0 ? void 0 : _a.message) || 'AI판단 자료 첨부에 실패했습니다.');
|
|
1548
1589
|
}
|
|
1549
1590
|
notify({ message: `검사 결과를 등록하였습니다. (자동 판정 ${judged.length}건 반영)` });
|
|
1550
1591
|
// 등록 후 체크리스트 화면으로 이동
|