@things-factory/integration-data-go-kr 10.0.0-beta.95 → 10.0.0-beta.97

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/integration-data-go-kr",
3
- "version": "10.0.0-beta.95",
3
+ "version": "10.0.0-beta.97",
4
4
  "main": "dist-server/index.js",
5
5
  "things-factory": true,
6
6
  "author": "heartyoh <heartyoh@hatiolab.com>",
@@ -24,5 +24,5 @@
24
24
  "dependencies": {
25
25
  "@things-factory/integration-base": "^10.0.0-beta.95"
26
26
  },
27
- "gitHead": "17291189a406eb17709411200d0dc4acb54d92a2"
27
+ "gitHead": "465fc49ad3f7838232f36fc0dc82db1e49bf2149"
28
28
  }
@@ -83,23 +83,55 @@ function zpad(value: string, width: number): string {
83
83
  return s.length >= width ? s : '0'.repeat(width - s.length) + s
84
84
  }
85
85
 
86
+ /*
87
+ * 컨텍스트 데이터 액세서 사용 예시 (이전 step 의 juso-resolve-address 결과 활용):
88
+ * sigunguCd: `${data.pnu.sigunguCd}`
89
+ * bjdongCd: `${data.pnu.bjdongCd}`
90
+ * platGbCd: `${data.pnu.platGbCd}`
91
+ * bun: `${data.pnu.bun}`
92
+ * ji: `${data.pnu.ji}`
93
+ * 시나리오 흐름: [juso-resolve-address] → data.pnu → [data-go-kr-building-ledger]
94
+ *
95
+ * 모든 파라미터는 evaluateTemplate 로 평가되므로 `${...}` 표현식 그대로 입력 가능.
96
+ */
86
97
  BuildingLedgerTask.parameterSpec = [
87
- { type: 'string', name: 'sigunguCd', label: 'label.sigungu-cd', placeholder: '예) 11680' },
88
- { type: 'string', name: 'bjdongCd', label: 'label.bjdong-cd', placeholder: '예) 10300' },
89
98
  {
90
- type: 'select',
99
+ type: 'string',
100
+ name: 'sigunguCd',
101
+ label: 'label.sigungu-cd',
102
+ placeholder: '예) 11680 또는 ${data.pnu.sigunguCd}',
103
+ description: '시군구코드 5자리. 이전 step 결과 액세서 사용 예: `${data.pnu.sigunguCd}`'
104
+ },
105
+ {
106
+ type: 'string',
107
+ name: 'bjdongCd',
108
+ label: 'label.bjdong-cd',
109
+ placeholder: '예) 10300 또는 ${data.pnu.bjdongCd}',
110
+ description: '법정동코드 5자리. 이전 step 결과 액세서 사용 예: `${data.pnu.bjdongCd}`'
111
+ },
112
+ {
113
+ type: 'string',
91
114
  name: 'platGbCd',
92
115
  label: 'label.plat-gb-cd',
93
- property: {
94
- options: [
95
- { value: '0', display: '대지 / Plot' },
96
- { value: '1', display: '산 / Mountain' },
97
- { value: '2', display: '블록 / Block' }
98
- ]
99
- }
116
+ placeholder: '0=대지, 1=산, 2=블록 — 또는 ${data.pnu.platGbCd}',
117
+ description:
118
+ '대지구분 1자리. 정적값 0/1/2 또는 액세서 표현식 입력 가능. ' +
119
+ 'juso → PNU 흐름이면 `${data.pnu.platGbCd}`.'
120
+ },
121
+ {
122
+ type: 'string',
123
+ name: 'bun',
124
+ label: 'label.bun',
125
+ placeholder: '예) 223 또는 ${data.pnu.bun}',
126
+ description: '본번. 자동 4자리 zero-pad. 액세서 사용 예: `${data.pnu.bun}`'
100
127
  },
101
- { type: 'string', name: 'bun', label: 'label.bun', placeholder: '예) 223' },
102
- { type: 'string', name: 'ji', label: 'label.ji', placeholder: '예) 0' }
128
+ {
129
+ type: 'string',
130
+ name: 'ji',
131
+ label: 'label.ji',
132
+ placeholder: '예) 0 또는 ${data.pnu.ji}',
133
+ description: '부번. 없으면 0. 자동 4자리 zero-pad. 액세서 사용 예: `${data.pnu.ji}`'
134
+ }
103
135
  ]
104
136
 
105
137
  BuildingLedgerTask.help = 'integration/task/building-ledger'
@@ -10,3 +10,4 @@
10
10
  * - recap.ts : data-go-kr-building-ledger-recap (총괄표제부 전용 변형)
11
11
  */
12
12
  import './general'
13
+ import './summary'
@@ -0,0 +1,277 @@
1
+ import { ConnectionManager, Context, TaskRegistry, evaluateTemplate } from '@things-factory/integration-base'
2
+
3
+ import { DataGoKrConnectionInstance } from '../../connector/data-go-kr-connector'
4
+ import { BldRgstHubService } from '../../spec/building-ledger'
5
+ import { BrTitleItem } from '../../types'
6
+
7
+ /**
8
+ * 건축물 4 핵심 KPI (연면적·용적률·지상층수·지하층수) 만 **단일 객체** 로 반환하는
9
+ * 요약 태스크. PNU 한 번 입력으로 케이스 (집합·단동) 자동 분기 + 단일값 보장.
10
+ *
11
+ * 내부 흐름:
12
+ * 1) `getBrRecapTitleInfo` (총괄표제부) 호출
13
+ * - 응답 items 가 1건 이상이면 단지 단위 row 그대로 사용 (source='recap')
14
+ * - 0건이면 일반건축물 가능성 → 2 단계로
15
+ * 2) `getBrTitleInfo` (표제부) 호출
16
+ * - items[0] 사용 (source='title')
17
+ * - 단동이면 1건, 다동이면 첫 동 (일반건축물 분기라 보통 단동)
18
+ * 3) 둘 다 0건이면 에러
19
+ *
20
+ * 출력 (`data`):
21
+ * {
22
+ * totArea: number | null, // 연면적 (㎡)
23
+ * vlRat: number | null, // 용적률 (%)
24
+ * grndFlrCnt: number | null, // 지상층수
25
+ * ugrndFlrCnt:number | null, // 지하층수
26
+ * bldNm: string | null,
27
+ * useAprDay: string | null, // 사용승인일 YYYYMMDD
28
+ * source: 'recap' | 'title',
29
+ * raw: BrTitleItem // 원본 (집합·단동 케이스 모두의 단일 row)
30
+ * }
31
+ *
32
+ * 시나리오 사용 예 (juso 결합):
33
+ * [juso-resolve-address] → data.pnu
34
+ * [data-go-kr-building-summary]
35
+ * sigunguCd = ${data.pnu.sigunguCd}
36
+ * bjdongCd = ${data.pnu.bjdongCd}
37
+ * platGbCd = ${data.pnu.platGbCd}
38
+ * bun = ${data.pnu.bun}
39
+ * ji = ${data.pnu.ji}
40
+ * 다음 step 에서 ${data.totArea}, ${data.vlRat} 등 직접 접근.
41
+ */
42
+
43
+ export interface BuildingSummary {
44
+ totArea: number | null
45
+ /** 대지면적 (㎡) — 집합건축물 총괄표제부에 있음 */
46
+ platArea: number | null
47
+ /** 건축면적 (㎡) */
48
+ archArea: number | null
49
+ /** 건폐율 (%) */
50
+ bcRat: number | null
51
+ vlRat: number | null
52
+ grndFlrCnt: number | null
53
+ ugrndFlrCnt: number | null
54
+ /** 주구조 코드 (예: '21'=철근콘크리트구조, '22'=철골철근콘크리트구조(SRC), '14'=프리캐스트콘크리트(PC) …) */
55
+ strctCd: string | null
56
+ /** 주구조 이름 (예: '철근콘크리트구조', '철골철근콘크리트구조') */
57
+ strctCdNm: string | null
58
+ /** 기타구조 자유텍스트 (도면상 상세 표기) */
59
+ etcStrct: string | null
60
+ /** 주용도 코드 (예: '02000'=공동주택, '03000'=제1종근린생활시설 …) */
61
+ mainPurpsCd: string | null
62
+ /** 주용도 이름 (예: '공동주택', '제1종근린생활시설') — DSSP SiteType 매핑 원천 */
63
+ mainPurpsCdNm: string | null
64
+ /** 기타용도 자유텍스트 (도면상 상세 표기) */
65
+ etcPurps: string | null
66
+ /** 세대수 — 집합건축물 */
67
+ hhldCnt: number | null
68
+ /** 가구수 */
69
+ fmlyCnt: number | null
70
+ /** 호수 (상가/근린 등) */
71
+ hoCnt: number | null
72
+ /** 주건축물수 (단지 동 수) — recap 에서 노출 */
73
+ mainBldCnt: number | null
74
+ /** 부속건축물수 */
75
+ atchBldCnt: number | null
76
+ /** 건물 높이 (m) */
77
+ heit: number | null
78
+ bldNm: string | null
79
+ useAprDay: string | null
80
+ /** 건축허가일 YYYYMMDD */
81
+ pmsDay: string | null
82
+ /** 착공일 YYYYMMDD */
83
+ stcnsDay: string | null
84
+ source: 'recap' | 'title'
85
+ raw: BrTitleItem
86
+ }
87
+
88
+ async function BuildingSummaryTask(step, context: Context): Promise<{ data: BuildingSummary }> {
89
+ const { connection: connectionName, params } = step
90
+ const { domain, user, data, variables, lng, logger } = context
91
+
92
+ const instance: DataGoKrConnectionInstance = await ConnectionManager.getConnectionInstanceByName(
93
+ domain,
94
+ connectionName
95
+ )
96
+ if (!instance?.client) {
97
+ throw new Error(`data-go-kr connection '${connectionName}' is not established.`)
98
+ }
99
+
100
+ const scope = { domain, user, lng, data, variables, console }
101
+ const evalString = (value: string | undefined) => {
102
+ if (value === undefined || value === null || value === '') return undefined
103
+ try {
104
+ return evaluateTemplate(String(value), scope)
105
+ } catch (err) {
106
+ throw new Error(`data-go-kr-building-summary: failed to evaluate parameter: ${(err as Error).message}`)
107
+ }
108
+ }
109
+
110
+ const sigunguCd = evalString(params.sigunguCd)
111
+ const bjdongCd = evalString(params.bjdongCd)
112
+ const bun = evalString(params.bun)
113
+ if (!sigunguCd) throw new Error('data-go-kr-building-summary: sigunguCd is required')
114
+ if (!bjdongCd) throw new Error('data-go-kr-building-summary: bjdongCd is required')
115
+ if (!bun) throw new Error('data-go-kr-building-summary: bun is required')
116
+
117
+ const pnu = {
118
+ sigunguCd,
119
+ bjdongCd,
120
+ platGbCd: evalString(params.platGbCd) || '0',
121
+ bun: zpad(bun, 4),
122
+ ji: zpad(evalString(params.ji) || '0', 4),
123
+ numOfRows: 100,
124
+ pageNo: 1
125
+ }
126
+
127
+ // 두 API 모두 호출 — recap 결과가 있어도 strctCdNm 등 동 단위 정보는
128
+ // 표제부의 주건축물 row 에서 보완.
129
+ const recapPath = `${BldRgstHubService.serviceId}/getBrRecapTitleInfo`
130
+ const titlePath = `${BldRgstHubService.serviceId}/getBrTitleInfo`
131
+ logger?.info?.(`[data-go-kr] fetch recap + title params=${JSON.stringify(pnu)}`)
132
+
133
+ const [recapItems, titleItems] = await Promise.all([
134
+ instance.client
135
+ .getItems<BrTitleItem>(recapPath, pnu)
136
+ .catch(err => {
137
+ logger?.warn?.(`[data-go-kr] recap call failed: ${(err as Error).message}`)
138
+ return [] as BrTitleItem[]
139
+ }),
140
+ instance.client
141
+ .getItems<BrTitleItem>(titlePath, pnu)
142
+ .catch(err => {
143
+ logger?.warn?.(`[data-go-kr] title call failed: ${(err as Error).message}`)
144
+ return [] as BrTitleItem[]
145
+ })
146
+ ])
147
+
148
+ if (recapItems.length === 0 && titleItems.length === 0) {
149
+ throw new Error(
150
+ `data-go-kr-building-summary: PNU ${pnu.sigunguCd}-${pnu.bjdongCd}-${pnu.platGbCd}-${pnu.bun}-${pnu.ji} ` +
151
+ `에 대해 총괄표제부·표제부 모두 0건. PNU 오류, 미준공 건물, 또는 미등록 필지 가능성.`
152
+ )
153
+ }
154
+
155
+ // 집합건축물 = recap 있음 → 단지 단위 4 KPI 정확.
156
+ // 일반건축물 = recap 없음 → title 첫 row 가 답.
157
+ // 두 경우 모두 구조정보(strctCdNm 등) 는 title 의 **주건축물** row 에서 가져오는
158
+ // 게 정확. recap 에는 단지 대표 구조가 노출 안 될 수 있음.
159
+ const recap = recapItems[0]
160
+ const mainTitle =
161
+ titleItems.find(t => (t as any).mainAtchGbCd === '0') || // 주건축물 우선
162
+ titleItems[0]
163
+ const source: 'recap' | 'title' = recap ? 'recap' : 'title'
164
+ logger?.info?.(
165
+ `[data-go-kr] summary source=${source} recap=${recapItems.length} title=${titleItems.length} mainBldNm='${(mainTitle as any)?.bldNm || ''}'`
166
+ )
167
+
168
+ return { data: toSummary(recap, mainTitle, source) }
169
+ }
170
+
171
+ /**
172
+ * 4 KPI + 구조 정보 + 메타를 단일 객체로 추출.
173
+ *
174
+ * - 4 KPI (totArea/vlRat/grndFlrCnt/ugrndFlrCnt) : 집합이면 recap 우선, 단동이면 title.
175
+ * - 구조 정보 (strctCd/Nm/etcStrct) : 동 단위라 title 의 주건축물 row 에서 가져옴.
176
+ * title 에 정보 없으면 recap 에서 fallback.
177
+ */
178
+ function toSummary(
179
+ recap: BrTitleItem | undefined,
180
+ mainTitle: BrTitleItem | undefined,
181
+ source: 'recap' | 'title'
182
+ ): BuildingSummary {
183
+ const num = (v: any): number | null => {
184
+ if (v === undefined || v === null || v === '' || v === ' ') return null
185
+ const n = Number(v)
186
+ return Number.isFinite(n) ? n : null
187
+ }
188
+ const str = (v: any): string | null => {
189
+ if (v === undefined || v === null) return null
190
+ const s = String(v).trim()
191
+ return s ? s : null
192
+ }
193
+ // KPI 베이스 — 집합이면 recap, 아니면 title
194
+ const base = recap || mainTitle || ({} as BrTitleItem)
195
+ // 구조 — title 의 주건축물 우선, 없으면 recap
196
+ const structSrc = mainTitle || recap || ({} as BrTitleItem)
197
+
198
+ // 단지 차원 수치 (대지면적/건폐율/세대수/동수 등) 는 recap 에 명시. 단동이면 title.
199
+ return {
200
+ totArea: num(base.totArea),
201
+ platArea: num((base as any).platArea) || num((structSrc as any).platArea),
202
+ archArea: num((base as any).archArea) || num((structSrc as any).archArea),
203
+ bcRat: num((base as any).bcRat) || num((structSrc as any).bcRat),
204
+ vlRat: num(base.vlRat),
205
+ grndFlrCnt: num(base.grndFlrCnt),
206
+ ugrndFlrCnt: num(base.ugrndFlrCnt),
207
+ strctCd: str(structSrc.strctCd) || str(base.strctCd),
208
+ strctCdNm: str(structSrc.strctCdNm) || str(base.strctCdNm),
209
+ etcStrct: str((structSrc as any).etcStrct) || str((base as any).etcStrct),
210
+ // 주용도도 구조와 같이 title 의 주건축물 row 가 신뢰할 만함 — 단지 단위 recap
211
+ // 에 단일값으로 노출 안 되는 경우가 있음.
212
+ mainPurpsCd: str((structSrc as any).mainPurpsCd) || str((base as any).mainPurpsCd),
213
+ mainPurpsCdNm: str(structSrc.mainPurpsCdNm) || str(base.mainPurpsCdNm),
214
+ etcPurps: str((structSrc as any).etcPurps) || str((base as any).etcPurps),
215
+ hhldCnt: num((base as any).hhldCnt) || num((structSrc as any).hhldCnt),
216
+ fmlyCnt: num((base as any).fmlyCnt) || num((structSrc as any).fmlyCnt),
217
+ hoCnt: num((base as any).hoCnt) || num((structSrc as any).hoCnt),
218
+ mainBldCnt: num((base as any).mainBldCnt) || num((structSrc as any).mainBldCnt),
219
+ atchBldCnt: num((base as any).atchBldCnt) || num((structSrc as any).atchBldCnt),
220
+ heit: num((structSrc as any).heit) || num((base as any).heit),
221
+ bldNm: str(base.bldNm) || str(structSrc.bldNm),
222
+ useAprDay: str(base.useAprDay) || str(structSrc.useAprDay),
223
+ pmsDay: str((base as any).pmsDay) || str((structSrc as any).pmsDay),
224
+ stcnsDay: str((base as any).stcnsDay) || str((structSrc as any).stcnsDay),
225
+ source,
226
+ raw: base
227
+ }
228
+ }
229
+
230
+ function zpad(value: string, width: number): string {
231
+ const s = String(value)
232
+ return s.length >= width ? s : '0'.repeat(width - s.length) + s
233
+ }
234
+
235
+ BuildingSummaryTask.parameterSpec = [
236
+ {
237
+ type: 'string',
238
+ name: 'sigunguCd',
239
+ label: 'label.sigungu-cd',
240
+ placeholder: '예) 11680 또는 ${data.pnu.sigunguCd}',
241
+ description: '시군구코드 5자리. 이전 step 결과 액세서 사용 예: `${data.pnu.sigunguCd}`'
242
+ },
243
+ {
244
+ type: 'string',
245
+ name: 'bjdongCd',
246
+ label: 'label.bjdong-cd',
247
+ placeholder: '예) 10300 또는 ${data.pnu.bjdongCd}',
248
+ description: '법정동코드 5자리. 이전 step 결과 액세서 사용 예: `${data.pnu.bjdongCd}`'
249
+ },
250
+ {
251
+ type: 'string',
252
+ name: 'platGbCd',
253
+ label: 'label.plat-gb-cd',
254
+ placeholder: '0=대지, 1=산, 2=블록 — 또는 ${data.pnu.platGbCd}',
255
+ description: '대지구분 1자리. 정적값 0/1/2 또는 액세서 표현식.'
256
+ },
257
+ {
258
+ type: 'string',
259
+ name: 'bun',
260
+ label: 'label.bun',
261
+ placeholder: '예) 223 또는 ${data.pnu.bun}',
262
+ description: '본번. 자동 4자리 zero-pad. 액세서 사용 예: `${data.pnu.bun}`'
263
+ },
264
+ {
265
+ type: 'string',
266
+ name: 'ji',
267
+ label: 'label.ji',
268
+ placeholder: '예) 0 또는 ${data.pnu.ji}',
269
+ description: '부번. 없으면 0. 자동 4자리 zero-pad. 액세서 사용 예: `${data.pnu.ji}`'
270
+ }
271
+ ]
272
+
273
+ BuildingSummaryTask.help = 'integration/task/building-summary'
274
+
275
+ TaskRegistry.registerTaskHandler('data-go-kr-building-summary', BuildingSummaryTask)
276
+
277
+ export default BuildingSummaryTask
@@ -1,15 +0,0 @@
1
- {
2
- "keep": {
3
- "days": true,
4
- "amount": 14
5
- },
6
- "auditLog": "logs/.5e5d741d8b7784a2fbad65eedc0fd46946aaf6f2-audit.json",
7
- "files": [
8
- {
9
- "date": 1777076185165,
10
- "name": "logs/connections-2026-04-25-09.log",
11
- "hash": "e3a2bbfa80736f8ffb8cb0effb0f787d4c97d7a571ab5ed50feba08ea9c75b05"
12
- }
13
- ],
14
- "hashType": "sha256"
15
- }
File without changes