@dssp/dkpi 1.0.26 → 1.0.29

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.
Files changed (56) hide show
  1. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.d.ts +2 -0
  2. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +19 -4
  3. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -1
  4. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.d.ts +2 -0
  5. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +19 -4
  6. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -1
  7. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.d.ts +2 -0
  8. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +19 -4
  9. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -1
  10. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +2 -0
  11. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +78 -4
  12. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
  13. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +2 -0
  14. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +35 -4
  15. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
  16. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +2 -0
  17. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +29 -1
  18. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  19. package/dist-client/pages/kpi-dashboard/kpi-dashboard.d.ts +11 -0
  20. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +65 -12
  21. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  22. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +18 -0
  23. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +138 -64
  24. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
  25. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +80 -8
  26. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
  27. package/dist-client/pages/sv-project-detail.d.ts +33 -0
  28. package/dist-client/pages/sv-project-detail.js +130 -8
  29. package/dist-client/pages/sv-project-detail.js.map +1 -1
  30. package/dist-client/pages/sv-project-update.js +98 -31
  31. package/dist-client/pages/sv-project-update.js.map +1 -1
  32. package/dist-client/shared/complete-api.d.ts +7 -0
  33. package/dist-client/shared/complete-api.js +27 -0
  34. package/dist-client/shared/complete-api.js.map +1 -1
  35. package/dist-client/shared/project-axis.d.ts +23 -0
  36. package/dist-client/shared/project-axis.js +42 -0
  37. package/dist-client/shared/project-axis.js.map +1 -0
  38. package/dist-client/shared/sentiment-index.d.ts +47 -0
  39. package/dist-client/shared/sentiment-index.js +119 -0
  40. package/dist-client/shared/sentiment-index.js.map +1 -0
  41. package/dist-client/tsconfig.tsbuildinfo +1 -1
  42. package/dist-server/index.js +14 -0
  43. package/dist-server/index.js.map +1 -1
  44. package/dist-server/scripts/load-grade-data-migration.js +17 -3
  45. package/dist-server/scripts/load-grade-data-migration.js.map +1 -1
  46. package/dist-server/scripts/propagate-parent-kpi-values.js +13 -2
  47. package/dist-server/scripts/propagate-parent-kpi-values.js.map +1 -1
  48. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +22 -0
  49. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +93 -1
  50. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  51. package/dist-server/service/kpi-stat/kpi-stat-query.d.ts +9 -9
  52. package/dist-server/service/kpi-stat/kpi-stat-query.js +71 -84
  53. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
  54. package/dist-server/tsconfig.tsbuildinfo +1 -1
  55. package/package.json +3 -3
  56. package/schema.graphql +2011 -67
@@ -3,7 +3,7 @@ import { __decorate, __metadata } from "tslib";
3
3
  import { css, html, LitElement } from 'lit';
4
4
  import { customElement, property, state } from 'lit/decorators.js';
5
5
  import { calcDateDiff } from '../../shared/func';
6
- import { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1, collectProjectExternalData } from '../../shared/complete-api';
6
+ import { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1, collectProjectExternalData, collectProjectSupervisionData } from '../../shared/complete-api';
7
7
  import { INTEGRATION_SOURCES } from '../../shared/integration-fetch';
8
8
  import { syncKpiToProjectInfo } from '../../shared/kpi-project-sync';
9
9
  import moment from 'moment-timezone';
@@ -22,7 +22,14 @@ const KPI_METRIC_KEY_MAPPING = [
22
22
  { label: '계획공사비', projectKey: 'constructionCost' },
23
23
  { label: '연면적', projectKey: 'area' },
24
24
  { label: '지상층수', projectKey: 'upperFloorCount' },
25
- { label: '지하층수', projectKey: 'lowerFloorCount' }
25
+ { label: '지하층수', projectKey: 'lowerFloorCount' },
26
+ // 시공감리 플랫폼(DCSP)에서 가져오는 항목 — 2026-08-07 회의
27
+ { label: '실제공사기간', projectKey: 'actualConstructionPeriod' },
28
+ { label: '실제공사비', projectKey: 'actualConstructionCost' },
29
+ { label: '재해율', projectKey: 'accidentRate' },
30
+ { label: '재해강도율', projectKey: 'accidentSeverityRate' },
31
+ { label: '품질검측 건수', projectKey: 'inspectionCount' },
32
+ { label: '품질검측 불합격 건수', projectKey: 'inspectionFailCount' }
26
33
  ];
27
34
  // 계획값이 없는 것들 (실제값으로 표시할 필드)
28
35
  const NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount'];
@@ -147,13 +154,23 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
147
154
  <div class="collect-panel">
148
155
  <div class="collect-head">
149
156
  <div class="ttl">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>
150
- <div
151
- class="collect-btn ${this.canAutoCollect ? '' : 'disabled'}"
152
- ?disabled=${this.collecting}
153
- title=${this.canAutoCollect ? '' : 'kpi:auto-collect 권한 필요'}
154
- @click=${() => (this.canAutoCollect && !this.collecting ? this._autoCollect() : null)}
155
- >
156
- ${this.collecting ? '수집 중…' : '⟳ 자동 수집'}
157
+ <div class="collect-actions">
158
+ <div
159
+ class="collect-btn supervision ${this.canAutoCollect ? '' : 'disabled'}"
160
+ ?disabled=${this.collecting}
161
+ title=${this.canAutoCollect ? '시공감리 플랫폼에 입력된 정보를 그대로 가져옵니다' : 'kpi:auto-collect 권한 필요'}
162
+ @click=${() => (this.canAutoCollect && !this.collecting ? this._collectSupervision() : null)}
163
+ >
164
+ ${this.collecting ? '수집 중…' : '시공감리 플랫폼 자동 수집'}
165
+ </div>
166
+ <div
167
+ class="collect-btn ${this.canAutoCollect ? '' : 'disabled'}"
168
+ ?disabled=${this.collecting}
169
+ title=${this.canAutoCollect ? '' : 'kpi:auto-collect 권한 필요'}
170
+ @click=${() => (this.canAutoCollect && !this.collecting ? this._autoCollect() : null)}
171
+ >
172
+ ${this.collecting ? '수집 중…' : '⟳ 자동 수집'}
173
+ </div>
157
174
  </div>
158
175
  </div>
159
176
  <div class="source-list">
@@ -229,61 +246,7 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
229
246
  // 시나리오 result 객체 = { projectKey: value, ... }.
230
247
  // KPI_METRIC_KEY_MAPPING 매칭 키 → form 채움. 미매칭 키 (siteType,
231
248
  // structureType 등 프로젝트/BuildingComplex 기본 속성) → 카드 요약만.
232
- const summary = [];
233
- // KPI_METRIC_KEY_MAPPING 에 없는 키들의 한글 라벨. BuildingComplex/Project 직접 속성용.
234
- const NON_KPI_LABEL = {
235
- siteType: '건축프로젝트유형',
236
- structureType: '구조형태',
237
- coverageRatio: '건폐율',
238
- householdCount: '세대수',
239
- buildingCount: '동수',
240
- startDate: '착공일',
241
- endDate: '준공일',
242
- permitDate: '건축허가일',
243
- bldNm: '건물명'
244
- };
245
- for (const [projectKey, rawValue] of Object.entries(r.data || {})) {
246
- if (rawValue === null || rawValue === undefined || rawValue === '')
247
- continue;
248
- // 비-primitive (객체/배열) 값은 시나리오 측 return 형태 오류 — form 매핑/표시
249
- // 모두 skip 하고 console 에 진단 로그만 남김. 흔한 원인은 시나리오 마지막 step 이
250
- // `return { result: { ... } }` 처럼 wrap 한 케이스.
251
- if (typeof rawValue === 'object') {
252
- console.warn(`[자동수집] '${r.label}'.${projectKey} 가 object — 시나리오 return 형태 점검`, rawValue);
253
- continue;
254
- }
255
- const mapping = KPI_METRIC_KEY_MAPPING.find(x => x.projectKey === projectKey);
256
- const label = (mapping === null || mapping === void 0 ? void 0 : mapping.label) || NON_KPI_LABEL[projectKey] || projectKey;
257
- const isPlan = SvProjectCompleteTab1Plan_1.PLAN_KEYS.includes(projectKey);
258
- // summary 카드 표시용 — string 값은 그대로, number 는 toLocaleString
259
- const numericValue = typeof rawValue === 'number' ? rawValue : Number(rawValue);
260
- const isNumeric = Number.isFinite(numericValue) && typeof rawValue !== 'string';
261
- const valueText = isNumeric ? numericValue.toLocaleString() : String(rawValue);
262
- summary.push({ label, text: valueText });
263
- // KPI 매핑이 없는 키 (siteType/structureType 등) 는 form 채움 skip — 단지 노출만.
264
- if (!mapping)
265
- continue;
266
- if (isPlan) {
267
- // 계획값 (키스콘 제공) — 계획 칼럼 표시 + KpiMetric value 에도 upsert
268
- // 하여 _save 시 patches 에 포함. 사용자가 계획 input 직접 편집해도 같은 흐름.
269
- this.collectedPlan = Object.assign(Object.assign({}, this.collectedPlan), { [projectKey]: numericValue });
270
- this.planSources = Object.assign(Object.assign({}, this.planSources), { [projectKey]: r.label });
271
- this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [`plan:${projectKey}`]: true });
272
- const planMetric = this.kpiMetrics.find((m) => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === projectKey; });
273
- if (planMetric)
274
- this._setMetricValue(planMetric, numericValue);
275
- }
276
- else {
277
- // 실제값 (세움터/올바로/기타) — 매칭되는 metric 의 실제 칼럼에 반영
278
- const metric = this.kpiMetrics.find((m) => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === projectKey; });
279
- if (!metric)
280
- continue;
281
- this._setMetricValue(metric, numericValue);
282
- this.valueSources = Object.assign(Object.assign({}, this.valueSources), { [metric.id]: r.label });
283
- this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [metric.id]: true });
284
- }
285
- }
286
- this.collectedSummary = Object.assign(Object.assign({}, this.collectedSummary), { [r.label]: summary });
249
+ this._applyCollectionResult(r);
287
250
  }
288
251
  this.collectStatus = status;
289
252
  this.collectError = errors;
@@ -324,6 +287,108 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
324
287
  /**
325
288
  * 메트릭의 실제값 set/add — _onInputChange 와 동일하게 periodType 별 row upsert.
326
289
  */
290
+ /**
291
+ * 시공감리 플랫폼(DCSP) 에서 기본정보를 가져온다.
292
+ *
293
+ * 외부 연동 3종과 버튼을 나눈 이유: 이건 네트워크 호출이 아니라 같은 DB 의 같은 프로젝트를
294
+ * 읽는 것이고, 실패 사유도 다르다 (자격증명 문제가 아니라 "시공감리 쪽에 아직 입력이
295
+ * 없다"). 한 버튼에 묶으면 사용자가 무엇을 고쳐야 할지 알 수 없다.
296
+ *
297
+ * 단위 변환은 서버가 한다 — 재해율 %→비율, 공사기간 날짜→개월.
298
+ */
299
+ async _collectSupervision() {
300
+ var _a, _b;
301
+ if (!((_a = this.project) === null || _a === void 0 ? void 0 : _a.id)) {
302
+ notify({ message: '프로젝트 정보가 없습니다.' });
303
+ return;
304
+ }
305
+ this.collecting = true;
306
+ this.collectStatus = Object.assign(Object.assign({}, this.collectStatus), { supervision: 'collecting' });
307
+ this.collectError = Object.assign(Object.assign({}, this.collectError), { supervision: '' });
308
+ try {
309
+ const result = await collectProjectSupervisionData(this.project.id, (_b = this.project) === null || _b === void 0 ? void 0 : _b.code);
310
+ this.collectStatus = Object.assign(Object.assign({}, this.collectStatus), { supervision: result.ok ? 'done' : 'error' });
311
+ if (!result.ok) {
312
+ this.collectError = Object.assign(Object.assign({}, this.collectError), { supervision: result.message });
313
+ notify({ message: result.message });
314
+ return;
315
+ }
316
+ this._applyCollectionResult(result);
317
+ notify({ message: '시공감리 플랫폼에서 가져왔습니다. 확인 후 저장해 주세요.' });
318
+ }
319
+ catch (e) {
320
+ this.collectStatus = Object.assign(Object.assign({}, this.collectStatus), { supervision: 'error' });
321
+ this.collectError = Object.assign(Object.assign({}, this.collectError), { supervision: (e === null || e === void 0 ? void 0 : e.message) || '수집에 실패했습니다' });
322
+ notify({ message: (e === null || e === void 0 ? void 0 : e.message) || '수집에 실패했습니다' });
323
+ }
324
+ finally {
325
+ this.collecting = false;
326
+ }
327
+ }
328
+ /**
329
+ * 수집 결과 한 건을 폼에 반영한다.
330
+ *
331
+ * 외부 시스템 수집(자동 수집)과 시공감리 플랫폼 복사가 같은 결과 모양
332
+ * ({ source, label, ok, message, data })을 쓰므로 반영 규칙도 하나로 둔다. 둘이
333
+ * 갈라지면 같은 값이 어느 버튼을 눌렀느냐에 따라 다르게 들어간다.
334
+ */
335
+ _applyCollectionResult(r) {
336
+ const summary = [];
337
+ // KPI_METRIC_KEY_MAPPING 에 없는 키들의 한글 라벨. BuildingComplex/Project 직접 속성용.
338
+ const NON_KPI_LABEL = {
339
+ siteType: '건축프로젝트유형',
340
+ structureType: '구조형태',
341
+ coverageRatio: '건폐율',
342
+ householdCount: '세대수',
343
+ buildingCount: '동수',
344
+ startDate: '착공일',
345
+ endDate: '준공일',
346
+ permitDate: '건축허가일',
347
+ bldNm: '건물명'
348
+ };
349
+ for (const [projectKey, rawValue] of Object.entries(r.data || {})) {
350
+ if (rawValue === null || rawValue === undefined || rawValue === '')
351
+ continue;
352
+ // 비-primitive (객체/배열) 값은 시나리오 측 return 형태 오류 — form 매핑/표시
353
+ // 모두 skip 하고 console 에 진단 로그만 남김. 흔한 원인은 시나리오 마지막 step 이
354
+ // `return { result: { ... } }` 처럼 wrap 한 케이스.
355
+ if (typeof rawValue === 'object') {
356
+ console.warn(`[자동수집] '${r.label}'.${projectKey} 가 object — 시나리오 return 형태 점검`, rawValue);
357
+ continue;
358
+ }
359
+ const mapping = KPI_METRIC_KEY_MAPPING.find(x => x.projectKey === projectKey);
360
+ const label = (mapping === null || mapping === void 0 ? void 0 : mapping.label) || NON_KPI_LABEL[projectKey] || projectKey;
361
+ const isPlan = SvProjectCompleteTab1Plan_1.PLAN_KEYS.includes(projectKey);
362
+ // summary 카드 표시용 — string 값은 그대로, number 는 toLocaleString
363
+ const numericValue = typeof rawValue === 'number' ? rawValue : Number(rawValue);
364
+ const isNumeric = Number.isFinite(numericValue) && typeof rawValue !== 'string';
365
+ const valueText = isNumeric ? numericValue.toLocaleString() : String(rawValue);
366
+ summary.push({ label, text: valueText });
367
+ // KPI 매핑이 없는 키 (siteType/structureType 등) 는 form 채움 skip — 단지 노출만.
368
+ if (!mapping)
369
+ continue;
370
+ if (isPlan) {
371
+ // 계획값 (키스콘 제공) — 계획 칼럼 표시 + KpiMetric value 에도 upsert
372
+ // 하여 _save 시 patches 에 포함. 사용자가 계획 input 직접 편집해도 같은 흐름.
373
+ this.collectedPlan = Object.assign(Object.assign({}, this.collectedPlan), { [projectKey]: numericValue });
374
+ this.planSources = Object.assign(Object.assign({}, this.planSources), { [projectKey]: r.label });
375
+ this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [`plan:${projectKey}`]: true });
376
+ const planMetric = this.kpiMetrics.find((m) => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === projectKey; });
377
+ if (planMetric)
378
+ this._setMetricValue(planMetric, numericValue);
379
+ }
380
+ else {
381
+ // 실제값 (세움터/올바로/기타) — 매칭되는 metric 의 실제 칼럼에 반영
382
+ const metric = this.kpiMetrics.find((m) => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === projectKey; });
383
+ if (!metric)
384
+ continue;
385
+ this._setMetricValue(metric, numericValue);
386
+ this.valueSources = Object.assign(Object.assign({}, this.valueSources), { [metric.id]: r.label });
387
+ this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [metric.id]: true });
388
+ }
389
+ }
390
+ this.collectedSummary = Object.assign(Object.assign({}, this.collectedSummary), { [r.label]: summary });
391
+ }
327
392
  _setMetricValue(metric, value) {
328
393
  this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, value);
329
394
  }
@@ -604,6 +669,15 @@ SvProjectCompleteTab1Plan.styles = [
604
669
  font-size: 13px;
605
670
  font-weight: 600;
606
671
  }
672
+ .collect-actions {
673
+ display: flex;
674
+ align-items: center;
675
+ gap: 8px;
676
+ }
677
+ /* 외부 연동이 아니라 같은 DB 에서 가져오는 것이라 색을 달리해 구분한다. */
678
+ .collect-btn.supervision {
679
+ background: #c53030;
680
+ }
607
681
  .collect-btn[disabled] {
608
682
  background: #9bb4d2;
609
683
  cursor: default;
@@ -1 +1 @@
1
- {"version":3,"file":"pc-tab1-plan.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab1-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { calcDateDiff } from '../../shared/func'\nimport {\n getKpiMetricValues,\n getKpiMetrics,\n updateProjectCompleteStep1,\n collectProjectExternalData\n} from '../../shared/complete-api'\nimport { INTEGRATION_SOURCES } from '../../shared/integration-fetch'\nimport { syncKpiToProjectInfo } from '../../shared/kpi-project-sync'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\nconst KPI_METRIC_KEY_MAPPING = [\n { label: '공사기간', projectKey: 'constructionPeriod' },\n { label: '총 근로자수', projectKey: 'totalWorkerCount' },\n { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },\n { label: '총 투입인력(M/M)', projectKey: 'workerCount' },\n { label: '재해 건수', projectKey: 'accidentCount' },\n { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },\n { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },\n { label: '용적률', projectKey: 'floorAreaRatio' },\n { label: '총 설계 변경 건', projectKey: 'designChangeCount' },\n { label: '계획공사비', projectKey: 'constructionCost' },\n { label: '연면적', projectKey: 'area' },\n { label: '지상층수', projectKey: 'upperFloorCount' },\n { label: '지하층수', projectKey: 'lowerFloorCount' }\n]\n// 계획값이 없는 것들 (실제값으로 표시할 필드)\nconst NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount']\n\n@customElement('sv-pc-tab1-plan')\nexport class SvProjectCompleteTab1Plan extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 220px 320px 1fr;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n /* '기본정보' / '월별 데이터' 섹션 사이 간격 */\n .row.header.section-gap {\n margin-top: 24px;\n }\n .row {\n display: grid;\n grid-template-columns: 220px 320px 1fr;\n gap: 6px 10px;\n align-items: center;\n padding: 8px 25px;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n flex-wrap: nowrap;\n white-space: nowrap;\n gap: 6px;\n padding-right: 12px;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: flex-end;\n padding-right: 12px;\n }\n input {\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n background: #ffffff;\n color: #212529;\n font-size: 16px;\n margin-right: 5px;\n text-align: right;\n\n &:disabled {\n background: #f6f6f6;\n }\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .desc {\n color: #5a6168;\n font-size: 12px;\n line-height: 1.4;\n padding-left: 8px;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled,\n .collect-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n /* ── 자동 수집 패널 ── */\n .collect-panel {\n margin: 4px 6px 14px;\n border: 1px solid #d7e3f2;\n border-radius: 8px;\n background: linear-gradient(180deg, #f7fafe 0%, #ffffff 100%);\n overflow: hidden;\n }\n .collect-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: #eaf2fb;\n border-bottom: 1px solid #d7e3f2;\n }\n .collect-head .ttl {\n color: #0c4da2;\n font-weight: 700;\n font-size: 14px;\n letter-spacing: -0.03em;\n }\n .collect-head .ttl small {\n color: #5a7da6;\n font-weight: 400;\n margin-left: 8px;\n }\n .collect-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n background: #0c4da2;\n color: #fff;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n }\n .collect-btn[disabled] {\n background: #9bb4d2;\n cursor: default;\n }\n .source-list {\n display: flex;\n gap: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n }\n .source-card {\n flex: 1 1 200px;\n border: 1px solid #e3e9f0;\n border-radius: 7px;\n padding: 10px 12px;\n background: #fff;\n transition:\n border-color 0.2s,\n box-shadow 0.2s;\n }\n .source-card.collecting {\n border-color: #2e79be;\n box-shadow: 0 0 0 3px rgba(46, 121, 190, 0.12);\n }\n .source-card.done {\n border-color: #24be7b;\n }\n .source-card .sc-head {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: #212529;\n font-size: 13px;\n }\n .source-card .sc-head .sys-icon {\n font-size: 16px;\n }\n .source-card .sc-status {\n margin-left: auto;\n font-size: 12px;\n }\n .sc-status.idle {\n color: #aab4c0;\n }\n .sc-status.collecting {\n color: #2e79be;\n }\n .sc-status.done {\n color: #24be7b;\n font-weight: 700;\n }\n .sc-status.error {\n color: #8a8a8a;\n font-weight: 600;\n }\n .source-card.error {\n background: #fafafa;\n border-color: #e0e0e0;\n }\n .source-card .sc-error-msg {\n margin-top: 6px;\n padding: 6px 8px;\n border-radius: 4px;\n background: #f3f4f6;\n color: #6b7280;\n font-size: 12px;\n line-height: 1.4;\n word-break: break-word;\n }\n .source-card .sc-fields {\n margin-top: 8px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n .sc-field {\n font-size: 12px;\n color: #5a6b7b;\n display: flex;\n justify-content: space-between;\n }\n .sc-field b {\n color: #0c4da2;\n }\n .sc-desc {\n font-size: 11px;\n color: #9aa7b4;\n margin-top: 2px;\n }\n /* 가져온 값 출처 칩 */\n .src-chip {\n display: inline-flex;\n align-items: center;\n margin-left: 6px;\n padding: 1px 7px;\n font-size: 11px;\n border-radius: 10px;\n background: #e7f3ec;\n color: #1d9c63;\n white-space: nowrap;\n }\n .cell.just-filled input {\n animation: flash 1.1s ease;\n }\n /* 미입력 — kpiMetricValue 가 비어있는 metric 행 */\n .label.pending::before {\n content: '⚠';\n color: #e74c3c;\n margin-right: 4px;\n }\n .cell.pending input {\n border-color: #e74c3c;\n background-color: #fff5f5;\n }\n @keyframes flash {\n 0% {\n background: #fff7cc;\n }\n 100% {\n background: #ffffff;\n }\n }\n `\n ]\n\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n @property({ type: Object }) project: any = {}\n\n /** 연동 진행 상태: source → 'idle' | 'collecting' | 'done' */\n @state() collectStatus: Record<string, 'idle' | 'collecting' | 'done' | 'error'> = {}\n @state() collectError: Record<string, string> = {}\n /** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */\n @state() valueSources: Record<string, string> = {}\n /** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */\n @state() collectedPlan: Record<string, number> = {}\n /** 계획값 출처: projectKey → 시스템 라벨 */\n @state() planSources: Record<string, string> = {}\n /** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */\n @state() collectedSummary: Record<string, { label: string; text: string }[]> = {}\n /** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */\n @state() justFilled: Record<string, boolean> = {}\n @state() collecting = false\n\n /** kpi:input — Step1 계획값 입력/저장 권한 */\n @state() canSave = false\n /** kpi:auto-collect — 외부 시스템 자동 수집 권한 */\n @state() canAutoCollect = false\n\n /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */\n private static readonly PLAN_KEYS = ['constructionPeriod', 'constructionCost']\n\n render() {\n // 전월 (last month) YYYY-MM. 월별 metric 의 \"전월 데이터 입력\" 검사 기준.\n const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n ${this._renderCollectPanel()}\n\n <div class=\"rows\">\n ${this._renderSection(\n '기본정보',\n this.kpiMetrics.filter((m: any) => m.periodType !== 'MONTH'),\n lastMonth\n )}\n ${\n // '월별 데이터' 섹션 당분간 숨김. 복원하려면 아래 주석을 해제하고 이 '' 를 제거.\n // this._renderSection('월별 데이터', this.kpiMetrics.filter((m: any) => m.periodType === 'MONTH'), lastMonth, true)\n ''\n }\n\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:input 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n /** '기본정보' / '월별 데이터' 섹션 렌더. 해당 그룹에 metric 이 없으면 섹션 자체를 생략. */\n private _renderSection(title: string, metrics: any[], lastMonth: string, topGap = false) {\n if (!metrics.length) return ''\n return html`\n <div class=\"row header ${topGap ? 'section-gap' : ''}\">\n <div class=\"header-label\">${title}</div>\n <div class=\"header-label\">값</div>\n <div class=\"header-label\">설명</div>\n </div>\n ${metrics.map(metric => this._renderMetricRow(metric, lastMonth))}\n `\n }\n\n private _renderMetricRow(metric: any, lastMonth: string) {\n // 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const isMonthly = metric.periodType === 'MONTH'\n // 현재값 lookup:\n // MONTH → 전월 row (단일 진실원)\n // ALLTIME → 단일 ALLTIME row\n // 그 외 → metricId+periodType 매칭\n const kpiMetricValue = isMonthly\n ? this.kpiMetricValues.find(\n (item: any) =>\n item.metricId === metric.id && item.periodType === 'MONTH' && (item.valueDate || '').startsWith(lastMonth)\n ) || {}\n : this.kpiMetricValues.find((item: any) => item.metricId === metric.id && item.periodType === metric.periodType) || {}\n const isDisplayInput = projectKey === 'constructionPeriod' || projectKey === 'constructionCost' // 유효한 계획 필드\n\n // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n let planValue = basePlanValue\n const unit = kpiMetricValue.unit || metric.unit || ''\n\n // 2. 계획 값 처리\n // 공사기간은 기간을 일 단위로 계산\n if (projectKey === 'constructionPeriod') {\n planValue = Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30)\n } else if (!isDisplayInput) {\n planValue = 0\n }\n\n // 1. 실제 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)\n const actualValue = kpiMetricValue.value ?? (NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0)\n\n const src = this.valueSources[metric.id]\n\n // periodType 무관하게 값 없으면 pending. 메시지는 MONTH 만 \"전월 데이터 입력 필요\".\n const isPending = kpiMetricValue.value === undefined || kpiMetricValue.value === null\n\n return html`<div class=\"row\">\n <div\n class=\"label ${isPending ? 'pending' : ''}\"\n title=${isPending ? (isMonthly ? '전월 데이터 입력 필요' : '아직 입력되지 않은 항목') : ''}\n >\n ${metric.name}\n </div>\n <div class=\"cell ${this.justFilled[metric.id] ? 'just-filled' : ''} ${isPending ? 'pending' : ''}\">\n <input numeric .value=${actualValue ?? 0} @input=${(e: InputEvent) => this._onInputChange(e, metric)} />\n ${unit} ${src ? html`<span class=\"src-chip\">🔗 ${src}</span>` : ''}\n </div>\n <div class=\"desc\">${metric.description || ''}</div>\n </div>`\n }\n\n private _renderCollectPanel() {\n return html`\n <div class=\"collect-panel\">\n <div class=\"collect-head\">\n <div class=\"ttl\">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>\n <div\n class=\"collect-btn ${this.canAutoCollect ? '' : 'disabled'}\"\n ?disabled=${this.collecting}\n title=${this.canAutoCollect ? '' : 'kpi:auto-collect 권한 필요'}\n @click=${() => (this.canAutoCollect && !this.collecting ? this._autoCollect() : null)}\n >\n ${this.collecting ? '수집 중…' : '⟳ 자동 수집'}\n </div>\n </div>\n <div class=\"source-list\">\n ${INTEGRATION_SOURCES.map(meta => {\n const status = this.collectStatus[meta.source] || 'idle'\n const errMsg = this.collectError[meta.source]\n // 카드 안의 수집 필드 summary — 실제/계획 무관 collectedSummary 기준으로 통일.\n // (valueSources 만 보면 키스콘처럼 계획값만 채우는 시스템은 표시 안 됨)\n const collectedFields = this._collectedFieldsOf(meta.label)\n const statusText =\n status === 'collecting' ? '연동 중…' : status === 'done' ? '완료 ✓' : status === 'error' ? '정보 없음 ⓘ' : '대기'\n return html`\n <div class=\"source-card ${status}\">\n <div class=\"sc-head\">\n <span class=\"sys-icon\">${meta.icon}</span>\n <span>${meta.label}</span>\n <span class=\"sc-status ${status}\">${statusText}</span>\n </div>\n <div class=\"sc-desc\">${meta.desc}</div>\n ${status === 'error' && errMsg ? html`<div class=\"sc-error-msg\">${errMsg}</div>` : ''}\n ${\n status === 'done' && collectedFields.length\n ? html`<div class=\"sc-fields\">\n ${collectedFields.map(f => html`<div class=\"sc-field\"><span>${f.label}</span><b>${f.text}</b></div>`)}\n </div>`\n : ''\n }\n </div>\n `\n })}\n </div>\n </div>\n `\n }\n\n /** 패널 카드에 표시할, 해당 시스템에서 수집한 값 요약 */\n private _collectedFieldsOf(sourceLabel: string): { label: string; text: string }[] {\n return this.collectedSummary[sourceLabel] || []\n }\n\n /** 자동 수집 — 백엔드의 collectProjectExternalData mutation 1번 호출.\n * 서버가 시나리오 3종 (세움터/올바로/키스콘) 을 순차 실행하고 시스템별 ok/메시지를 반환.\n * 성공/실패와 무관하게 시나리오 step 이 KPI Value 를 DB 에 적재하므로 호출 후\n * KPI Metric Values 를 재조회해 form 을 자동 갱신.\n *\n * 운영 현실: 키스콘·올바로는 시공사 자격증명 미확보가 많아 오류 빈도 높음.\n * 세움터는 공공데이터 서비스키만으로 동작 가능. 시스템별로 카드에 독립 표시.\n */\n private async _autoCollect() {\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n this.collecting = true\n this.collectStatus = {}\n this.collectError = {}\n this.collectedSummary = {}\n this.collectedPlan = {}\n this.planSources = {}\n this.valueSources = {}\n\n // 모든 카드를 collecting 상태로 표시\n const collecting: Record<string, 'collecting'> = {}\n for (const meta of INTEGRATION_SOURCES) collecting[meta.source] = 'collecting'\n this.collectStatus = collecting\n\n try {\n const results = await collectProjectExternalData(this.project.id)\n\n // 시스템별 status / error 메시지 업데이트 + result data 를 form 에 매핑\n const status: Record<string, 'done' | 'error'> = {}\n const errors: Record<string, string> = {}\n for (const r of results) {\n status[r.source] = r.ok ? 'done' : 'error'\n if (!r.ok) {\n errors[r.source] = r.message\n continue\n }\n // 시나리오 result 객체 = { projectKey: value, ... }.\n // KPI_METRIC_KEY_MAPPING 매칭 키 → form 채움. 미매칭 키 (siteType,\n // structureType 등 프로젝트/BuildingComplex 기본 속성) → 카드 요약만.\n const summary: { label: string; text: string }[] = []\n // KPI_METRIC_KEY_MAPPING 에 없는 키들의 한글 라벨. BuildingComplex/Project 직접 속성용.\n const NON_KPI_LABEL: Record<string, string> = {\n siteType: '건축프로젝트유형',\n structureType: '구조형태',\n coverageRatio: '건폐율',\n householdCount: '세대수',\n buildingCount: '동수',\n startDate: '착공일',\n endDate: '준공일',\n permitDate: '건축허가일',\n bldNm: '건물명'\n }\n for (const [projectKey, rawValue] of Object.entries(r.data || {})) {\n if (rawValue === null || rawValue === undefined || rawValue === '') continue\n // 비-primitive (객체/배열) 값은 시나리오 측 return 형태 오류 — form 매핑/표시\n // 모두 skip 하고 console 에 진단 로그만 남김. 흔한 원인은 시나리오 마지막 step 이\n // `return { result: { ... } }` 처럼 wrap 한 케이스.\n if (typeof rawValue === 'object') {\n console.warn(`[자동수집] '${r.label}'.${projectKey} 가 object — 시나리오 return 형태 점검`, rawValue)\n continue\n }\n const mapping = KPI_METRIC_KEY_MAPPING.find(x => x.projectKey === projectKey)\n const label = mapping?.label || NON_KPI_LABEL[projectKey] || projectKey\n const isPlan = SvProjectCompleteTab1Plan.PLAN_KEYS.includes(projectKey)\n\n // summary 카드 표시용 — string 값은 그대로, number 는 toLocaleString\n const numericValue = typeof rawValue === 'number' ? rawValue : Number(rawValue)\n const isNumeric = Number.isFinite(numericValue) && typeof rawValue !== 'string'\n const valueText = isNumeric ? numericValue.toLocaleString() : String(rawValue)\n summary.push({ label, text: valueText })\n\n // KPI 매핑이 없는 키 (siteType/structureType 등) 는 form 채움 skip — 단지 노출만.\n if (!mapping) continue\n\n if (isPlan) {\n // 계획값 (키스콘 제공) — 계획 칼럼 표시 + KpiMetric value 에도 upsert\n // 하여 _save 시 patches 에 포함. 사용자가 계획 input 직접 편집해도 같은 흐름.\n this.collectedPlan = { ...this.collectedPlan, [projectKey]: numericValue }\n this.planSources = { ...this.planSources, [projectKey]: r.label }\n this.justFilled = { ...this.justFilled, [`plan:${projectKey}`]: true }\n const planMetric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === projectKey\n )\n if (planMetric) this._setMetricValue(planMetric, numericValue)\n } else {\n // 실제값 (세움터/올바로/기타) — 매칭되는 metric 의 실제 칼럼에 반영\n const metric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === projectKey\n )\n if (!metric) continue\n this._setMetricValue(metric, numericValue)\n this.valueSources = { ...this.valueSources, [metric.id]: r.label }\n this.justFilled = { ...this.justFilled, [metric.id]: true }\n }\n }\n this.collectedSummary = { ...this.collectedSummary, [r.label]: summary }\n }\n this.collectStatus = status\n this.collectError = errors\n\n // 재조회 안 함 — 시나리오는 fetch + result 반환만 책임 (DB 적재 X). 직전에\n // _setMetricValue 가 memory 에 채운 값들을 DB 의 옛 값으로 덮어쓰면 자동수집\n // 결과가 폼에 반영되지 않음. 사용자가 [저장] 버튼 누르면 그제서야 DB 반영.\n this.requestUpdate()\n setTimeout(() => (this.justFilled = {}), 1300)\n\n const okCount = results.filter(r => r.ok).length\n if (okCount === results.length) {\n notify({ message: `외부 시스템 ${okCount}건 모두 수집 완료. 검토 후 [저장]을 눌러주세요.` })\n } else if (okCount === 0) {\n notify({ message: '외부 시스템에서 가져올 정보가 없습니다.' })\n } else {\n notify({ message: `외부 시스템 수집 — ${okCount}건 완료, 나머지는 정보 없음.` })\n }\n } catch (e) {\n // mutation 자체 실패 (네트워크/권한 등) — 모든 카드를 정보 없음 으로\n const msg = e instanceof Error ? e.message : String(e)\n console.warn('[자동수집] 전체 호출 실패', e)\n const status: Record<string, 'error'> = {}\n const errors: Record<string, string> = {}\n for (const meta of INTEGRATION_SOURCES) {\n status[meta.source] = 'error'\n errors[meta.source] = '업데이트할 정보가 없습니다'\n }\n this.collectStatus = status\n this.collectError = errors\n notify({ message: '외부 시스템 연동이 일시적으로 안 됩니다.' })\n } finally {\n this.collecting = false\n }\n }\n\n /**\n * 메트릭의 실제값 set/add — _onInputChange 와 동일하게 periodType 별 row upsert.\n */\n private _setMetricValue(metric: any, value: number | string) {\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, value)\n }\n\n async connectedCallback() {\n super.connectedCallback()\n const [canSave, canAutoCollect] = await Promise.all([\n hasPrivilege({ category: 'kpi', privilege: 'input', domainOwnerGranted: true, superUserGranted: true }),\n hasPrivilege({ category: 'kpi', privilege: 'auto-collect', domainOwnerGranted: true, superUserGranted: true })\n ])\n this.canSave = canSave\n this.canAutoCollect = canAutoCollect\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getInitData()\n }\n }\n\n private async _getInitData() {\n // getKpiMetrics 가 이미 active=true 만 반환. 여기선 평가 (Step2) 만 추가 제외.\n // 비활성화는 KPI 관리 admin 에서 active=false 처리.\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n // 평가(Step2) 만 제외. 나머지는 기본정보 탭에서 '기본정보'/'월별 데이터' 두 섹션으로 구분 렌더.\n this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || []\n this.kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n }\n\n /**\n * 계획공사비/계획공사기간 의 \"계획\" 컬럼 input 편집 핸들러 — collectedPlan 표시값 동기화 +\n * 해당 KpiMetric 의 value 도 upsert 하여 _save 시 patches 에 포함되도록.\n */\n private _onPlanInputChange(event: InputEvent, metric: any, projectKey: string) {\n const target = event.target as HTMLInputElement\n const inputVal = Number(target.value.replace(/[^\\d.-]/g, ''))\n this.collectedPlan = { ...this.collectedPlan, [projectKey]: inputVal }\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal)\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent, metric: any) {\n const target = event.target as HTMLInputElement\n let inputVal: any = target.value\n\n // 숫자 타입은 다른 문자 입력 제거\n if (target.hasAttribute('numeric')) {\n inputVal = Number(inputVal.replace(/[^\\d.-]/g, ''))\n }\n\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal)\n }\n\n /**\n * metric.periodType 에 따라 적절한 row 를 upsert.\n * - MONTH : 전월 row (YYYY-MM-01). \"오늘 입력 = 지난달 데이터\" 운영 원칙.\n * - ALLTIME : 단일 row (valueDate sentinel 무관).\n * - 그 외 (DAY/WEEK/QUARTER/YEAR/RANGE) : metric.periodType 그대로, valueDate=today.\n * 매칭은 (metricId, periodType) 기준 — MONTH 만 추가로 valueDate prefix.\n */\n private _upsertValueForMetric(values: any[], metric: any, value: any): any[] {\n const today = moment().tz('Asia/Seoul')\n const todayYmd = today.format('YYYY-MM-DD')\n const periodType = metric.periodType\n let updated = [...values]\n\n if (periodType === 'MONTH') {\n const lastMonth = today.clone().subtract(1, 'month')\n const lastMonth1 = lastMonth.format('YYYY-MM-01')\n const lastMonthYm = lastMonth.format('YYYY-MM')\n const idx = updated.findIndex(\n (i: any) => i.metricId === metric.id && i.periodType === 'MONTH' && (i.valueDate || '').startsWith(lastMonthYm)\n )\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: 'MONTH',\n valueDate: lastMonth1\n })\n }\n } else {\n // ALLTIME / DAY / WEEK / QUARTER / YEAR / RANGE — metric.periodType 그대로 단일 row upsert.\n const idx = updated.findIndex((i: any) => i.metricId === metric.id && i.periodType === periodType)\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType,\n valueDate: todayYmd\n })\n }\n }\n\n return updated\n }\n\n private async _save() {\n // 사용자가 실제 편집한 row 만 저장. 자동 산정 metric (계획/실제 공사비·공기) 는\n // 별도의 데이터 출처(project 정보, 키스콘 수집, 다른 metric 의 전월 row 등)에서\n // 도출돼야 하므로 이 화면에서 하드코딩 derive 하지 않음.\n const response = await updateProjectCompleteStep1(this.kpiMetricValues, this.project?.code)\n if (!response.errors) {\n // 양방향 동기 — 연면적/용적률/투입인력/설계변경건 값을 BuildingComplex(프로젝트 정보)에도 반영.\n // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.\n try {\n await syncKpiToProjectInfo(this.project, this.kpiMetrics, this.kpiMetricValues, this.project?.code)\n } catch (e) {\n console.warn('[KPI→프로젝트정보] BuildingComplex 동기 실패', e)\n }\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
1
+ {"version":3,"file":"pc-tab1-plan.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab1-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { calcDateDiff } from '../../shared/func'\nimport {\n getKpiMetricValues,\n getKpiMetrics,\n updateProjectCompleteStep1,\n collectProjectExternalData,\n collectProjectSupervisionData\n} from '../../shared/complete-api'\nimport { INTEGRATION_SOURCES } from '../../shared/integration-fetch'\nimport { syncKpiToProjectInfo } from '../../shared/kpi-project-sync'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\nconst KPI_METRIC_KEY_MAPPING = [\n { label: '공사기간', projectKey: 'constructionPeriod' },\n { label: '총 근로자수', projectKey: 'totalWorkerCount' },\n { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },\n { label: '총 투입인력(M/M)', projectKey: 'workerCount' },\n { label: '재해 건수', projectKey: 'accidentCount' },\n { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },\n { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },\n { label: '용적률', projectKey: 'floorAreaRatio' },\n { label: '총 설계 변경 건', projectKey: 'designChangeCount' },\n { label: '계획공사비', projectKey: 'constructionCost' },\n { label: '연면적', projectKey: 'area' },\n { label: '지상층수', projectKey: 'upperFloorCount' },\n { label: '지하층수', projectKey: 'lowerFloorCount' },\n // 시공감리 플랫폼(DCSP)에서 가져오는 항목 — 2026-08-07 회의\n { label: '실제공사기간', projectKey: 'actualConstructionPeriod' },\n { label: '실제공사비', projectKey: 'actualConstructionCost' },\n { label: '재해율', projectKey: 'accidentRate' },\n { label: '재해강도율', projectKey: 'accidentSeverityRate' },\n { label: '품질검측 건수', projectKey: 'inspectionCount' },\n { label: '품질검측 불합격 건수', projectKey: 'inspectionFailCount' }\n]\n// 계획값이 없는 것들 (실제값으로 표시할 필드)\nconst NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount']\n\n@customElement('sv-pc-tab1-plan')\nexport class SvProjectCompleteTab1Plan extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 220px 320px 1fr;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n /* '기본정보' / '월별 데이터' 섹션 사이 간격 */\n .row.header.section-gap {\n margin-top: 24px;\n }\n .row {\n display: grid;\n grid-template-columns: 220px 320px 1fr;\n gap: 6px 10px;\n align-items: center;\n padding: 8px 25px;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n flex-wrap: nowrap;\n white-space: nowrap;\n gap: 6px;\n padding-right: 12px;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: flex-end;\n padding-right: 12px;\n }\n input {\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n background: #ffffff;\n color: #212529;\n font-size: 16px;\n margin-right: 5px;\n text-align: right;\n\n &:disabled {\n background: #f6f6f6;\n }\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .desc {\n color: #5a6168;\n font-size: 12px;\n line-height: 1.4;\n padding-left: 8px;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled,\n .collect-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n /* ── 자동 수집 패널 ── */\n .collect-panel {\n margin: 4px 6px 14px;\n border: 1px solid #d7e3f2;\n border-radius: 8px;\n background: linear-gradient(180deg, #f7fafe 0%, #ffffff 100%);\n overflow: hidden;\n }\n .collect-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: #eaf2fb;\n border-bottom: 1px solid #d7e3f2;\n }\n .collect-head .ttl {\n color: #0c4da2;\n font-weight: 700;\n font-size: 14px;\n letter-spacing: -0.03em;\n }\n .collect-head .ttl small {\n color: #5a7da6;\n font-weight: 400;\n margin-left: 8px;\n }\n .collect-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n background: #0c4da2;\n color: #fff;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n }\n .collect-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n /* 외부 연동이 아니라 같은 DB 에서 가져오는 것이라 색을 달리해 구분한다. */\n .collect-btn.supervision {\n background: #c53030;\n }\n .collect-btn[disabled] {\n background: #9bb4d2;\n cursor: default;\n }\n .source-list {\n display: flex;\n gap: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n }\n .source-card {\n flex: 1 1 200px;\n border: 1px solid #e3e9f0;\n border-radius: 7px;\n padding: 10px 12px;\n background: #fff;\n transition:\n border-color 0.2s,\n box-shadow 0.2s;\n }\n .source-card.collecting {\n border-color: #2e79be;\n box-shadow: 0 0 0 3px rgba(46, 121, 190, 0.12);\n }\n .source-card.done {\n border-color: #24be7b;\n }\n .source-card .sc-head {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: #212529;\n font-size: 13px;\n }\n .source-card .sc-head .sys-icon {\n font-size: 16px;\n }\n .source-card .sc-status {\n margin-left: auto;\n font-size: 12px;\n }\n .sc-status.idle {\n color: #aab4c0;\n }\n .sc-status.collecting {\n color: #2e79be;\n }\n .sc-status.done {\n color: #24be7b;\n font-weight: 700;\n }\n .sc-status.error {\n color: #8a8a8a;\n font-weight: 600;\n }\n .source-card.error {\n background: #fafafa;\n border-color: #e0e0e0;\n }\n .source-card .sc-error-msg {\n margin-top: 6px;\n padding: 6px 8px;\n border-radius: 4px;\n background: #f3f4f6;\n color: #6b7280;\n font-size: 12px;\n line-height: 1.4;\n word-break: break-word;\n }\n .source-card .sc-fields {\n margin-top: 8px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n .sc-field {\n font-size: 12px;\n color: #5a6b7b;\n display: flex;\n justify-content: space-between;\n }\n .sc-field b {\n color: #0c4da2;\n }\n .sc-desc {\n font-size: 11px;\n color: #9aa7b4;\n margin-top: 2px;\n }\n /* 가져온 값 출처 칩 */\n .src-chip {\n display: inline-flex;\n align-items: center;\n margin-left: 6px;\n padding: 1px 7px;\n font-size: 11px;\n border-radius: 10px;\n background: #e7f3ec;\n color: #1d9c63;\n white-space: nowrap;\n }\n .cell.just-filled input {\n animation: flash 1.1s ease;\n }\n /* 미입력 — kpiMetricValue 가 비어있는 metric 행 */\n .label.pending::before {\n content: '⚠';\n color: #e74c3c;\n margin-right: 4px;\n }\n .cell.pending input {\n border-color: #e74c3c;\n background-color: #fff5f5;\n }\n @keyframes flash {\n 0% {\n background: #fff7cc;\n }\n 100% {\n background: #ffffff;\n }\n }\n `\n ]\n\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n @property({ type: Object }) project: any = {}\n\n /** 연동 진행 상태: source → 'idle' | 'collecting' | 'done' */\n @state() collectStatus: Record<string, 'idle' | 'collecting' | 'done' | 'error'> = {}\n @state() collectError: Record<string, string> = {}\n /** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */\n @state() valueSources: Record<string, string> = {}\n /** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */\n @state() collectedPlan: Record<string, number> = {}\n /** 계획값 출처: projectKey → 시스템 라벨 */\n @state() planSources: Record<string, string> = {}\n /** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */\n @state() collectedSummary: Record<string, { label: string; text: string }[]> = {}\n /** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */\n @state() justFilled: Record<string, boolean> = {}\n @state() collecting = false\n\n /** kpi:input — Step1 계획값 입력/저장 권한 */\n @state() canSave = false\n /** kpi:auto-collect — 외부 시스템 자동 수집 권한 */\n @state() canAutoCollect = false\n\n /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */\n private static readonly PLAN_KEYS = ['constructionPeriod', 'constructionCost']\n\n render() {\n // 전월 (last month) YYYY-MM. 월별 metric 의 \"전월 데이터 입력\" 검사 기준.\n const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n ${this._renderCollectPanel()}\n\n <div class=\"rows\">\n ${this._renderSection(\n '기본정보',\n this.kpiMetrics.filter((m: any) => m.periodType !== 'MONTH'),\n lastMonth\n )}\n ${\n // '월별 데이터' 섹션 당분간 숨김. 복원하려면 아래 주석을 해제하고 이 '' 를 제거.\n // this._renderSection('월별 데이터', this.kpiMetrics.filter((m: any) => m.periodType === 'MONTH'), lastMonth, true)\n ''\n }\n\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:input 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n /** '기본정보' / '월별 데이터' 섹션 렌더. 해당 그룹에 metric 이 없으면 섹션 자체를 생략. */\n private _renderSection(title: string, metrics: any[], lastMonth: string, topGap = false) {\n if (!metrics.length) return ''\n return html`\n <div class=\"row header ${topGap ? 'section-gap' : ''}\">\n <div class=\"header-label\">${title}</div>\n <div class=\"header-label\">값</div>\n <div class=\"header-label\">설명</div>\n </div>\n ${metrics.map(metric => this._renderMetricRow(metric, lastMonth))}\n `\n }\n\n private _renderMetricRow(metric: any, lastMonth: string) {\n // 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const isMonthly = metric.periodType === 'MONTH'\n // 현재값 lookup:\n // MONTH → 전월 row (단일 진실원)\n // ALLTIME → 단일 ALLTIME row\n // 그 외 → metricId+periodType 매칭\n const kpiMetricValue = isMonthly\n ? this.kpiMetricValues.find(\n (item: any) =>\n item.metricId === metric.id && item.periodType === 'MONTH' && (item.valueDate || '').startsWith(lastMonth)\n ) || {}\n : this.kpiMetricValues.find((item: any) => item.metricId === metric.id && item.periodType === metric.periodType) || {}\n const isDisplayInput = projectKey === 'constructionPeriod' || projectKey === 'constructionCost' // 유효한 계획 필드\n\n // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n let planValue = basePlanValue\n const unit = kpiMetricValue.unit || metric.unit || ''\n\n // 2. 계획 값 처리\n // 공사기간은 기간을 일 단위로 계산\n if (projectKey === 'constructionPeriod') {\n planValue = Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30)\n } else if (!isDisplayInput) {\n planValue = 0\n }\n\n // 1. 실제 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)\n const actualValue = kpiMetricValue.value ?? (NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0)\n\n const src = this.valueSources[metric.id]\n\n // periodType 무관하게 값 없으면 pending. 메시지는 MONTH 만 \"전월 데이터 입력 필요\".\n const isPending = kpiMetricValue.value === undefined || kpiMetricValue.value === null\n\n return html`<div class=\"row\">\n <div\n class=\"label ${isPending ? 'pending' : ''}\"\n title=${isPending ? (isMonthly ? '전월 데이터 입력 필요' : '아직 입력되지 않은 항목') : ''}\n >\n ${metric.name}\n </div>\n <div class=\"cell ${this.justFilled[metric.id] ? 'just-filled' : ''} ${isPending ? 'pending' : ''}\">\n <input numeric .value=${actualValue ?? 0} @input=${(e: InputEvent) => this._onInputChange(e, metric)} />\n ${unit} ${src ? html`<span class=\"src-chip\">🔗 ${src}</span>` : ''}\n </div>\n <div class=\"desc\">${metric.description || ''}</div>\n </div>`\n }\n\n private _renderCollectPanel() {\n return html`\n <div class=\"collect-panel\">\n <div class=\"collect-head\">\n <div class=\"ttl\">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>\n <div class=\"collect-actions\">\n <div\n class=\"collect-btn supervision ${this.canAutoCollect ? '' : 'disabled'}\"\n ?disabled=${this.collecting}\n title=${this.canAutoCollect ? '시공감리 플랫폼에 입력된 정보를 그대로 가져옵니다' : 'kpi:auto-collect 권한 필요'}\n @click=${() => (this.canAutoCollect && !this.collecting ? this._collectSupervision() : null)}\n >\n ${this.collecting ? '수집 중…' : '시공감리 플랫폼 자동 수집'}\n </div>\n <div\n class=\"collect-btn ${this.canAutoCollect ? '' : 'disabled'}\"\n ?disabled=${this.collecting}\n title=${this.canAutoCollect ? '' : 'kpi:auto-collect 권한 필요'}\n @click=${() => (this.canAutoCollect && !this.collecting ? this._autoCollect() : null)}\n >\n ${this.collecting ? '수집 중…' : '⟳ 자동 수집'}\n </div>\n </div>\n </div>\n <div class=\"source-list\">\n ${INTEGRATION_SOURCES.map(meta => {\n const status = this.collectStatus[meta.source] || 'idle'\n const errMsg = this.collectError[meta.source]\n // 카드 안의 수집 필드 summary — 실제/계획 무관 collectedSummary 기준으로 통일.\n // (valueSources 만 보면 키스콘처럼 계획값만 채우는 시스템은 표시 안 됨)\n const collectedFields = this._collectedFieldsOf(meta.label)\n const statusText =\n status === 'collecting' ? '연동 중…' : status === 'done' ? '완료 ✓' : status === 'error' ? '정보 없음 ⓘ' : '대기'\n return html`\n <div class=\"source-card ${status}\">\n <div class=\"sc-head\">\n <span class=\"sys-icon\">${meta.icon}</span>\n <span>${meta.label}</span>\n <span class=\"sc-status ${status}\">${statusText}</span>\n </div>\n <div class=\"sc-desc\">${meta.desc}</div>\n ${status === 'error' && errMsg ? html`<div class=\"sc-error-msg\">${errMsg}</div>` : ''}\n ${\n status === 'done' && collectedFields.length\n ? html`<div class=\"sc-fields\">\n ${collectedFields.map(f => html`<div class=\"sc-field\"><span>${f.label}</span><b>${f.text}</b></div>`)}\n </div>`\n : ''\n }\n </div>\n `\n })}\n </div>\n </div>\n `\n }\n\n /** 패널 카드에 표시할, 해당 시스템에서 수집한 값 요약 */\n private _collectedFieldsOf(sourceLabel: string): { label: string; text: string }[] {\n return this.collectedSummary[sourceLabel] || []\n }\n\n /** 자동 수집 — 백엔드의 collectProjectExternalData mutation 1번 호출.\n * 서버가 시나리오 3종 (세움터/올바로/키스콘) 을 순차 실행하고 시스템별 ok/메시지를 반환.\n * 성공/실패와 무관하게 시나리오 step 이 KPI Value 를 DB 에 적재하므로 호출 후\n * KPI Metric Values 를 재조회해 form 을 자동 갱신.\n *\n * 운영 현실: 키스콘·올바로는 시공사 자격증명 미확보가 많아 오류 빈도 높음.\n * 세움터는 공공데이터 서비스키만으로 동작 가능. 시스템별로 카드에 독립 표시.\n */\n private async _autoCollect() {\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n this.collecting = true\n this.collectStatus = {}\n this.collectError = {}\n this.collectedSummary = {}\n this.collectedPlan = {}\n this.planSources = {}\n this.valueSources = {}\n\n // 모든 카드를 collecting 상태로 표시\n const collecting: Record<string, 'collecting'> = {}\n for (const meta of INTEGRATION_SOURCES) collecting[meta.source] = 'collecting'\n this.collectStatus = collecting\n\n try {\n const results = await collectProjectExternalData(this.project.id)\n\n // 시스템별 status / error 메시지 업데이트 + result data 를 form 에 매핑\n const status: Record<string, 'done' | 'error'> = {}\n const errors: Record<string, string> = {}\n for (const r of results) {\n status[r.source] = r.ok ? 'done' : 'error'\n if (!r.ok) {\n errors[r.source] = r.message\n continue\n }\n // 시나리오 result 객체 = { projectKey: value, ... }.\n // KPI_METRIC_KEY_MAPPING 매칭 키 → form 채움. 미매칭 키 (siteType,\n // structureType 등 프로젝트/BuildingComplex 기본 속성) → 카드 요약만.\n this._applyCollectionResult(r)\n }\n this.collectStatus = status\n this.collectError = errors\n\n // 재조회 안 함 — 시나리오는 fetch + result 반환만 책임 (DB 적재 X). 직전에\n // _setMetricValue 가 memory 에 채운 값들을 DB 의 옛 값으로 덮어쓰면 자동수집\n // 결과가 폼에 반영되지 않음. 사용자가 [저장] 버튼 누르면 그제서야 DB 반영.\n this.requestUpdate()\n setTimeout(() => (this.justFilled = {}), 1300)\n\n const okCount = results.filter(r => r.ok).length\n if (okCount === results.length) {\n notify({ message: `외부 시스템 ${okCount}건 모두 수집 완료. 검토 후 [저장]을 눌러주세요.` })\n } else if (okCount === 0) {\n notify({ message: '외부 시스템에서 가져올 정보가 없습니다.' })\n } else {\n notify({ message: `외부 시스템 수집 — ${okCount}건 완료, 나머지는 정보 없음.` })\n }\n } catch (e) {\n // mutation 자체 실패 (네트워크/권한 등) — 모든 카드를 정보 없음 으로\n const msg = e instanceof Error ? e.message : String(e)\n console.warn('[자동수집] 전체 호출 실패', e)\n const status: Record<string, 'error'> = {}\n const errors: Record<string, string> = {}\n for (const meta of INTEGRATION_SOURCES) {\n status[meta.source] = 'error'\n errors[meta.source] = '업데이트할 정보가 없습니다'\n }\n this.collectStatus = status\n this.collectError = errors\n notify({ message: '외부 시스템 연동이 일시적으로 안 됩니다.' })\n } finally {\n this.collecting = false\n }\n }\n\n /**\n * 메트릭의 실제값 set/add — _onInputChange 와 동일하게 periodType 별 row upsert.\n */\n\n /**\n * 시공감리 플랫폼(DCSP) 에서 기본정보를 가져온다.\n *\n * 외부 연동 3종과 버튼을 나눈 이유: 이건 네트워크 호출이 아니라 같은 DB 의 같은 프로젝트를\n * 읽는 것이고, 실패 사유도 다르다 (자격증명 문제가 아니라 \"시공감리 쪽에 아직 입력이\n * 없다\"). 한 버튼에 묶으면 사용자가 무엇을 고쳐야 할지 알 수 없다.\n *\n * 단위 변환은 서버가 한다 — 재해율 %→비율, 공사기간 날짜→개월.\n */\n private async _collectSupervision() {\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n\n this.collecting = true\n this.collectStatus = { ...this.collectStatus, supervision: 'collecting' }\n this.collectError = { ...this.collectError, supervision: '' }\n\n try {\n const result = await collectProjectSupervisionData(this.project.id, this.project?.code)\n\n this.collectStatus = { ...this.collectStatus, supervision: result.ok ? 'done' : 'error' }\n\n if (!result.ok) {\n this.collectError = { ...this.collectError, supervision: result.message }\n notify({ message: result.message })\n return\n }\n\n this._applyCollectionResult(result)\n notify({ message: '시공감리 플랫폼에서 가져왔습니다. 확인 후 저장해 주세요.' })\n } catch (e: any) {\n this.collectStatus = { ...this.collectStatus, supervision: 'error' }\n this.collectError = { ...this.collectError, supervision: e?.message || '수집에 실패했습니다' }\n notify({ message: e?.message || '수집에 실패했습니다' })\n } finally {\n this.collecting = false\n }\n }\n\n /**\n * 수집 결과 한 건을 폼에 반영한다.\n *\n * 외부 시스템 수집(자동 수집)과 시공감리 플랫폼 복사가 같은 결과 모양\n * ({ source, label, ok, message, data })을 쓰므로 반영 규칙도 하나로 둔다. 둘이\n * 갈라지면 같은 값이 어느 버튼을 눌렀느냐에 따라 다르게 들어간다.\n */\n private _applyCollectionResult(r: any) {\n const summary: { label: string; text: string }[] = []\n // KPI_METRIC_KEY_MAPPING 에 없는 키들의 한글 라벨. BuildingComplex/Project 직접 속성용.\n const NON_KPI_LABEL: Record<string, string> = {\n siteType: '건축프로젝트유형',\n structureType: '구조형태',\n coverageRatio: '건폐율',\n householdCount: '세대수',\n buildingCount: '동수',\n startDate: '착공일',\n endDate: '준공일',\n permitDate: '건축허가일',\n bldNm: '건물명'\n }\n for (const [projectKey, rawValue] of Object.entries(r.data || {})) {\n if (rawValue === null || rawValue === undefined || rawValue === '') continue\n // 비-primitive (객체/배열) 값은 시나리오 측 return 형태 오류 — form 매핑/표시\n // 모두 skip 하고 console 에 진단 로그만 남김. 흔한 원인은 시나리오 마지막 step 이\n // `return { result: { ... } }` 처럼 wrap 한 케이스.\n if (typeof rawValue === 'object') {\n console.warn(`[자동수집] '${r.label}'.${projectKey} 가 object — 시나리오 return 형태 점검`, rawValue)\n continue\n }\n const mapping = KPI_METRIC_KEY_MAPPING.find(x => x.projectKey === projectKey)\n const label = mapping?.label || NON_KPI_LABEL[projectKey] || projectKey\n const isPlan = SvProjectCompleteTab1Plan.PLAN_KEYS.includes(projectKey)\n\n // summary 카드 표시용 — string 값은 그대로, number 는 toLocaleString\n const numericValue = typeof rawValue === 'number' ? rawValue : Number(rawValue)\n const isNumeric = Number.isFinite(numericValue) && typeof rawValue !== 'string'\n const valueText = isNumeric ? numericValue.toLocaleString() : String(rawValue)\n summary.push({ label, text: valueText })\n\n // KPI 매핑이 없는 키 (siteType/structureType 등) 는 form 채움 skip — 단지 노출만.\n if (!mapping) continue\n\n if (isPlan) {\n // 계획값 (키스콘 제공) — 계획 칼럼 표시 + KpiMetric value 에도 upsert\n // 하여 _save 시 patches 에 포함. 사용자가 계획 input 직접 편집해도 같은 흐름.\n this.collectedPlan = { ...this.collectedPlan, [projectKey]: numericValue }\n this.planSources = { ...this.planSources, [projectKey]: r.label }\n this.justFilled = { ...this.justFilled, [`plan:${projectKey}`]: true }\n const planMetric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === projectKey\n )\n if (planMetric) this._setMetricValue(planMetric, numericValue)\n } else {\n // 실제값 (세움터/올바로/기타) — 매칭되는 metric 의 실제 칼럼에 반영\n const metric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === projectKey\n )\n if (!metric) continue\n this._setMetricValue(metric, numericValue)\n this.valueSources = { ...this.valueSources, [metric.id]: r.label }\n this.justFilled = { ...this.justFilled, [metric.id]: true }\n }\n }\n this.collectedSummary = { ...this.collectedSummary, [r.label]: summary }\n }\n\n private _setMetricValue(metric: any, value: number | string) {\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, value)\n }\n\n async connectedCallback() {\n super.connectedCallback()\n const [canSave, canAutoCollect] = await Promise.all([\n hasPrivilege({ category: 'kpi', privilege: 'input', domainOwnerGranted: true, superUserGranted: true }),\n hasPrivilege({ category: 'kpi', privilege: 'auto-collect', domainOwnerGranted: true, superUserGranted: true })\n ])\n this.canSave = canSave\n this.canAutoCollect = canAutoCollect\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getInitData()\n }\n }\n\n private async _getInitData() {\n // getKpiMetrics 가 이미 active=true 만 반환. 여기선 평가 (Step2) 만 추가 제외.\n // 비활성화는 KPI 관리 admin 에서 active=false 처리.\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n // 평가(Step2) 만 제외. 나머지는 기본정보 탭에서 '기본정보'/'월별 데이터' 두 섹션으로 구분 렌더.\n this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || []\n this.kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n }\n\n /**\n * 계획공사비/계획공사기간 의 \"계획\" 컬럼 input 편집 핸들러 — collectedPlan 표시값 동기화 +\n * 해당 KpiMetric 의 value 도 upsert 하여 _save 시 patches 에 포함되도록.\n */\n private _onPlanInputChange(event: InputEvent, metric: any, projectKey: string) {\n const target = event.target as HTMLInputElement\n const inputVal = Number(target.value.replace(/[^\\d.-]/g, ''))\n this.collectedPlan = { ...this.collectedPlan, [projectKey]: inputVal }\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal)\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent, metric: any) {\n const target = event.target as HTMLInputElement\n let inputVal: any = target.value\n\n // 숫자 타입은 다른 문자 입력 제거\n if (target.hasAttribute('numeric')) {\n inputVal = Number(inputVal.replace(/[^\\d.-]/g, ''))\n }\n\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal)\n }\n\n /**\n * metric.periodType 에 따라 적절한 row 를 upsert.\n * - MONTH : 전월 row (YYYY-MM-01). \"오늘 입력 = 지난달 데이터\" 운영 원칙.\n * - ALLTIME : 단일 row (valueDate sentinel 무관).\n * - 그 외 (DAY/WEEK/QUARTER/YEAR/RANGE) : metric.periodType 그대로, valueDate=today.\n * 매칭은 (metricId, periodType) 기준 — MONTH 만 추가로 valueDate prefix.\n */\n private _upsertValueForMetric(values: any[], metric: any, value: any): any[] {\n const today = moment().tz('Asia/Seoul')\n const todayYmd = today.format('YYYY-MM-DD')\n const periodType = metric.periodType\n let updated = [...values]\n\n if (periodType === 'MONTH') {\n const lastMonth = today.clone().subtract(1, 'month')\n const lastMonth1 = lastMonth.format('YYYY-MM-01')\n const lastMonthYm = lastMonth.format('YYYY-MM')\n const idx = updated.findIndex(\n (i: any) => i.metricId === metric.id && i.periodType === 'MONTH' && (i.valueDate || '').startsWith(lastMonthYm)\n )\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: 'MONTH',\n valueDate: lastMonth1\n })\n }\n } else {\n // ALLTIME / DAY / WEEK / QUARTER / YEAR / RANGE — metric.periodType 그대로 단일 row upsert.\n const idx = updated.findIndex((i: any) => i.metricId === metric.id && i.periodType === periodType)\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType,\n valueDate: todayYmd\n })\n }\n }\n\n return updated\n }\n\n private async _save() {\n // 사용자가 실제 편집한 row 만 저장. 자동 산정 metric (계획/실제 공사비·공기) 는\n // 별도의 데이터 출처(project 정보, 키스콘 수집, 다른 metric 의 전월 row 등)에서\n // 도출돼야 하므로 이 화면에서 하드코딩 derive 하지 않음.\n const response = await updateProjectCompleteStep1(this.kpiMetricValues, this.project?.code)\n if (!response.errors) {\n // 양방향 동기 — 연면적/용적률/투입인력/설계변경건 값을 BuildingComplex(프로젝트 정보)에도 반영.\n // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.\n try {\n await syncKpiToProjectInfo(this.project, this.kpiMetrics, this.kpiMetricValues, this.project?.code)\n } catch (e) {\n console.warn('[KPI→프로젝트정보] BuildingComplex 동기 실패', e)\n }\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
@@ -2,6 +2,7 @@ import { __decorate, __metadata } from "tslib";
2
2
  import { css, html, LitElement } from 'lit';
3
3
  import { customElement, property, state } from 'lit/decorators.js';
4
4
  import { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api';
5
+ import { fetchSentimentGrades, matchSentimentBand, sentimentBandsOf, SENTIMENT_METRICS } from '../../shared/sentiment-index';
5
6
  import { notify } from '@operato/layout';
6
7
  import { hasPrivilege } from '@things-factory/auth-base/dist-client';
7
8
  let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitElement {
@@ -57,9 +58,31 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
57
58
  <div class="slpa-title">AI 분석 결과 (SL-PA 관련 항목)</div>
58
59
  ${this.slpaData.map(item => html `
59
60
  <div class="slpa-item">
60
- <div class="slpa-label">${item.name}</div>
61
- <div class="slpa-value">${item.value.toFixed(2)} ${item.unit || ''}</div>
61
+ <div class="slpa-label">${item.label}</div>
62
+ <div class="slpa-readout">
63
+ <span class="slpa-value">${item.value.toFixed(2)} ${item.unit || ''}</span>
64
+ ${item.matched ? html `<span class="slpa-result">${item.matched.result}</span>` : ''}
65
+ </div>
62
66
  </div>
67
+ ${item.bands.length > 0
68
+ ? html `
69
+ <table class="slpa-legend">
70
+ <tr>
71
+ <th>지수 범위</th>
72
+ <th>결과</th>
73
+ </tr>
74
+ ${item.bands.map((band) => {
75
+ var _a;
76
+ return html `
77
+ <tr class=${((_a = item.matched) === null || _a === void 0 ? void 0 : _a.score) === band.score ? 'current' : ''}>
78
+ <td>${band.range}</td>
79
+ <td>${band.result}</td>
80
+ </tr>
81
+ `;
82
+ })}
83
+ </table>
84
+ `
85
+ : ''}
63
86
  `)}
64
87
  </div>
65
88
  `
@@ -124,21 +147,31 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
124
147
  }
125
148
  }
126
149
  async _loadSlpaData() {
127
- var _a, _b;
150
+ var _a, _b, _c;
128
151
  // KPI 메트릭들 가져오기
129
152
  const kpiMetrics = await getKpiMetrics((_a = this.project) === null || _a === void 0 ? void 0 : _a.code);
130
153
  // "SL-PA"가 포함된 메트릭들만 필터링
131
154
  const slpaMetrics = kpiMetrics.filter(metric => metric.name.includes('SL-PA'));
132
155
  if (slpaMetrics.length > 0) {
133
- // 해당 프로젝트의 KPI 값들 가져오기
134
- const kpiMetricValues = await getKpiMetricValues(this.project.id, (_b = this.project) === null || _b === void 0 ? void 0 : _b.code);
156
+ // 해당 프로젝트의 KPI 값들과, 지수 → 5단계 환산에 쓸 구간표를 함께 가져온다
157
+ const [kpiMetricValues, grades] = await Promise.all([
158
+ getKpiMetricValues(this.project.id, (_b = this.project) === null || _b === void 0 ? void 0 : _b.code),
159
+ fetchSentimentGrades((_c = this.project) === null || _c === void 0 ? void 0 : _c.code)
160
+ ]);
135
161
  // SL-PA 메트릭들과 해당 값들을 매칭하여 표시용 데이터 생성
136
162
  this.slpaData = slpaMetrics.map(metric => {
137
163
  const metricValue = kpiMetricValues.find((item) => item.metricId === metric.id);
164
+ const value = (metricValue === null || metricValue === void 0 ? void 0 : metricValue.value) || 0;
165
+ // 체감지수로 정의된 metric 만 화면 이름과 등급을 갖는다. 그 밖의 SL-PA 항목은
166
+ // 환산 기준이 없으므로 이름과 값 그대로 (기존 동작).
167
+ const sentiment = SENTIMENT_METRICS[metric.name];
168
+ const metricGrades = sentiment ? grades[sentiment.kpiCode] : undefined;
138
169
  return {
139
- name: metric.name,
140
- value: (metricValue === null || metricValue === void 0 ? void 0 : metricValue.value) || 0,
141
- unit: (metricValue === null || metricValue === void 0 ? void 0 : metricValue.unit) || metric.unit || ''
170
+ label: (sentiment === null || sentiment === void 0 ? void 0 : sentiment.label) || metric.name,
171
+ value,
172
+ unit: (metricValue === null || metricValue === void 0 ? void 0 : metricValue.unit) || metric.unit || '',
173
+ matched: matchSentimentBand(value, metricGrades),
174
+ bands: sentimentBandsOf(metricGrades)
142
175
  };
143
176
  });
144
177
  }
@@ -302,10 +335,49 @@ SvProjectCompleteTab3Upload.styles = [
302
335
  color: #333;
303
336
  }
304
337
 
338
+ .slpa-readout {
339
+ display: flex;
340
+ align-items: baseline;
341
+ gap: 10px;
342
+ }
343
+
305
344
  .slpa-value {
306
345
  font-weight: 600;
307
346
  color: #1976d2;
308
347
  }
348
+
349
+ .slpa-result {
350
+ font-weight: 700;
351
+ color: #35618e;
352
+ }
353
+
354
+ /* 지수 → 5단계 환산 기준. 회의 자료의 "지수 범위 / 결과" 표를 그대로 옮긴 것. */
355
+ .slpa-legend {
356
+ margin: 6px 0 10px auto;
357
+ border-collapse: collapse;
358
+ font-size: 12px;
359
+ color: #555;
360
+ }
361
+
362
+ .slpa-legend th,
363
+ .slpa-legend td {
364
+ padding: 3px 12px;
365
+ border: 1px solid #e0e0e0;
366
+ text-align: center;
367
+ font-weight: 400;
368
+ }
369
+
370
+ .slpa-legend th {
371
+ background: #eef2f7;
372
+ color: #35618e;
373
+ font-weight: 600;
374
+ }
375
+
376
+ .slpa-legend tr.current td {
377
+ background: #e3f2fd;
378
+ color: #1976d2;
379
+ font-weight: 700;
380
+ }
309
381
  `
310
382
  ];
311
383
  __decorate([