@things-factory/integration-data-go-kr 10.0.0-beta.62

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 (67) hide show
  1. package/README.md +73 -0
  2. package/ROADMAP.md +271 -0
  3. package/dist-server/engine/connector/data-go-kr-client.d.ts +27 -0
  4. package/dist-server/engine/connector/data-go-kr-client.js +115 -0
  5. package/dist-server/engine/connector/data-go-kr-client.js.map +1 -0
  6. package/dist-server/engine/connector/data-go-kr-connector.d.ts +35 -0
  7. package/dist-server/engine/connector/data-go-kr-connector.js +59 -0
  8. package/dist-server/engine/connector/data-go-kr-connector.js.map +1 -0
  9. package/dist-server/engine/connector/index.d.ts +1 -0
  10. package/dist-server/engine/connector/index.js +4 -0
  11. package/dist-server/engine/connector/index.js.map +1 -0
  12. package/dist-server/engine/index.d.ts +2 -0
  13. package/dist-server/engine/index.js +5 -0
  14. package/dist-server/engine/index.js.map +1 -0
  15. package/dist-server/engine/spec/building-ledger.d.ts +9 -0
  16. package/dist-server/engine/spec/building-ledger.js +135 -0
  17. package/dist-server/engine/spec/building-ledger.js.map +1 -0
  18. package/dist-server/engine/spec/index.d.ts +16 -0
  19. package/dist-server/engine/spec/index.js +42 -0
  20. package/dist-server/engine/spec/index.js.map +1 -0
  21. package/dist-server/engine/spec/types.d.ts +79 -0
  22. package/dist-server/engine/spec/types.js +30 -0
  23. package/dist-server/engine/spec/types.js.map +1 -0
  24. package/dist-server/engine/task/building-ledger/general.d.ts +41 -0
  25. package/dist-server/engine/task/building-ledger/general.js +92 -0
  26. package/dist-server/engine/task/building-ledger/general.js.map +1 -0
  27. package/dist-server/engine/task/building-ledger/index.d.ts +12 -0
  28. package/dist-server/engine/task/building-ledger/index.js +15 -0
  29. package/dist-server/engine/task/building-ledger/index.js.map +1 -0
  30. package/dist-server/engine/task/call.d.ts +33 -0
  31. package/dist-server/engine/task/call.js +128 -0
  32. package/dist-server/engine/task/call.js.map +1 -0
  33. package/dist-server/engine/task/index.d.ts +19 -0
  34. package/dist-server/engine/task/index.js +22 -0
  35. package/dist-server/engine/task/index.js.map +1 -0
  36. package/dist-server/engine/types.d.ts +63 -0
  37. package/dist-server/engine/types.js +9 -0
  38. package/dist-server/engine/types.js.map +1 -0
  39. package/dist-server/index.d.ts +4 -0
  40. package/dist-server/index.js +10 -0
  41. package/dist-server/index.js.map +1 -0
  42. package/dist-server/tsconfig.tsbuildinfo +1 -0
  43. package/helps/integration/connector/data-go-kr-connector.md +39 -0
  44. package/helps/integration/services/building-ledger.md +109 -0
  45. package/helps/integration/task/building-ledger.md +111 -0
  46. package/helps/integration/task/data-go-kr-call.md +87 -0
  47. package/package.json +28 -0
  48. package/server/engine/connector/data-go-kr-client.ts +133 -0
  49. package/server/engine/connector/data-go-kr-connector.ts +75 -0
  50. package/server/engine/connector/index.ts +1 -0
  51. package/server/engine/index.ts +2 -0
  52. package/server/engine/spec/building-ledger.ts +140 -0
  53. package/server/engine/spec/index.ts +41 -0
  54. package/server/engine/spec/types.ts +84 -0
  55. package/server/engine/task/building-ledger/general.ts +109 -0
  56. package/server/engine/task/building-ledger/index.ts +12 -0
  57. package/server/engine/task/call.ts +157 -0
  58. package/server/engine/task/index.ts +19 -0
  59. package/server/engine/types.ts +64 -0
  60. package/server/index.ts +5 -0
  61. package/things-factory.config.js +1 -0
  62. package/translations/en.json +11 -0
  63. package/translations/ja.json +11 -0
  64. package/translations/ko.json +11 -0
  65. package/translations/ms.json +11 -0
  66. package/translations/zh.json +11 -0
  67. package/tsconfig.json +10 -0
@@ -0,0 +1,140 @@
1
+ import { OperationParamSpec, ServiceSpec } from './types'
2
+
3
+ /**
4
+ * PNU 5요소 파라미터 스펙 — BldRgstServiceV2 의 모든 오퍼레이션이 공유.
5
+ * 별도 배열 분리로 작성해두면 타 서비스(예: 건축물대장 허브) 도 재사용 가능.
6
+ */
7
+ const pnuParams: OperationParamSpec[] = [
8
+ {
9
+ name: 'sigunguCd',
10
+ label: 'label.sigungu-cd',
11
+ required: true,
12
+ length: 5,
13
+ description: '시군구코드 5자리 (법정동코드 10자리의 앞 5자리).',
14
+ example: '11680'
15
+ },
16
+ {
17
+ name: 'bjdongCd',
18
+ label: 'label.bjdong-cd',
19
+ required: true,
20
+ length: 5,
21
+ description: '법정동코드 5자리 (법정동코드 10자리의 뒤 5자리).',
22
+ example: '10300'
23
+ },
24
+ {
25
+ name: 'platGbCd',
26
+ label: 'label.plat-gb-cd',
27
+ default: '0',
28
+ length: 1,
29
+ description: '대지구분 1자리. `0` 대지 / `1` 산 / `2` 블록.',
30
+ example: '0',
31
+ enum: [
32
+ { value: '0', label: '대지' },
33
+ { value: '1', label: '산' },
34
+ { value: '2', label: '블록' }
35
+ ]
36
+ },
37
+ {
38
+ name: 'bun',
39
+ label: 'label.bun',
40
+ required: true,
41
+ pad: 4,
42
+ description: '본번. 자동으로 4자리 zero-pad 됨 (예: `6` → `0006`). 하이픈 포함 지번은 본번/부번을 각각 입력.',
43
+ example: '223'
44
+ },
45
+ {
46
+ name: 'ji',
47
+ label: 'label.ji',
48
+ default: '0',
49
+ pad: 4,
50
+ description: '부번. 없으면 `0`. 자동 4자리 zero-pad.',
51
+ example: '0'
52
+ }
53
+ ]
54
+
55
+ /**
56
+ * 국토교통부_건축물대장정보 서비스 v2 (data.go.kr 데이터셋 15044713).
57
+ *
58
+ * 세움터(EAIS) DB 를 원천으로 하는 공공 건축물대장 Open API.
59
+ * 모든 오퍼레이션은 동일한 PNU(필지고유번호) 구성요소로 조회한다:
60
+ * - sigunguCd (5) + bjdongCd (5) + platGbCd (1) + bun (4) + ji (4)
61
+ */
62
+ export const BldRgstServiceV2: ServiceSpec = {
63
+ serviceId: '1613000/BldRgstService_v2',
64
+ label: '국토교통부_건축물대장정보 서비스 v2',
65
+ description:
66
+ '세움터(EAIS) 원천의 건축물대장 정보를 조회한다. 표제부·총괄표제부·층별개요·부속지번·전유공용면적·주택가격·소유자현황·기본개요 등 8개 오퍼레이션 제공.',
67
+ homepage: 'https://www.data.go.kr/data/15044713/openapi.do',
68
+ operations: {
69
+ getBrTitleInfo: {
70
+ operationId: 'getBrTitleInfo',
71
+ label: '표제부 조회',
72
+ description:
73
+ '건축물대장 표제부 (건물 본문) 를 조회한다. 연면적·용적률·지상층수·지하층수·주구조·주용도 등 건물 개요를 반환. 한 필지에 동이 여러 개이면 복수 레코드로 내려온다.',
74
+ params: [...pnuParams],
75
+ responseNotes:
76
+ '응답 `response.body.items.item` 이 배열 또는 단일 객체로 올 수 있음. 모든 숫자 필드가 문자열(`"114000.00"`)로 내려옴. 집합건축물은 `regstrKindCd=3`, 일반건축물은 `regstrKindCd=2`.',
77
+ externalDocsUrl: 'https://www.data.go.kr/data/15044713/openapi.do',
78
+ examples: [
79
+ {
80
+ label: '서울 강남구 역삼동 823',
81
+ params: { sigunguCd: '11680', bjdongCd: '10300', bun: '823', ji: '0' }
82
+ }
83
+ ]
84
+ },
85
+ getBrRecapTitleInfo: {
86
+ operationId: 'getBrRecapTitleInfo',
87
+ label: '총괄표제부 조회',
88
+ description:
89
+ '집합건축물(아파트 단지 등) 의 총괄표제부를 조회한다. 단지 전체 연면적·대지면적·동 수·세대수 등을 반환. 일반건축물에는 없음.',
90
+ params: [...pnuParams],
91
+ responseNotes: '일반건축물대장(단동) 의 경우 레코드가 없음. 집합건축물일 때만 유효.',
92
+ examples: [
93
+ {
94
+ label: '아파트 단지 (예시)',
95
+ params: { sigunguCd: '11680', bjdongCd: '10300', bun: '823', ji: '0' }
96
+ }
97
+ ]
98
+ },
99
+ getBrFlrOulnInfo: {
100
+ operationId: 'getBrFlrOulnInfo',
101
+ label: '층별개요 조회',
102
+ description:
103
+ '층별로 주용도·구조·면적을 조회. 지하 N층부터 지상 M층까지 각 층의 상세 구성을 반환. `regstrKindCd`, `regstrGbCd` 로 어느 대장의 층인지 구분.',
104
+ params: [...pnuParams],
105
+ responseNotes: '한 필지에 동이 여러 개이면 각 동의 각 층이 모두 나옴 → 결과 건수가 많을 수 있음.'
106
+ },
107
+ getBrExposPubuseAreaInfo: {
108
+ operationId: 'getBrExposPubuseAreaInfo',
109
+ label: '전유공용면적 조회',
110
+ description:
111
+ '집합건축물의 호(세대) 별 전유면적·공용면적을 조회. 아파트 세대별 실제 면적 산출에 사용.',
112
+ params: [...pnuParams],
113
+ responseNotes: '집합건축물일 때만 레코드 존재. 일반건축물에는 해당 없음.'
114
+ },
115
+ getBrAtchJibunInfo: {
116
+ operationId: 'getBrAtchJibunInfo',
117
+ label: '부속지번 조회',
118
+ description: '대장에 연결된 부속지번(인접 필지 등) 목록을 조회.',
119
+ params: [...pnuParams]
120
+ },
121
+ getBrHsprcInfo: {
122
+ operationId: 'getBrHsprcInfo',
123
+ label: '주택가격 조회',
124
+ description: '공동주택/단독주택 공시가격 이력을 조회. 연도별 개별주택가격·공동주택가격 제공.',
125
+ params: [...pnuParams]
126
+ },
127
+ getBrBasisOulnInfo: {
128
+ operationId: 'getBrBasisOulnInfo',
129
+ label: '기본개요 조회',
130
+ description: '대장의 기본개요(대장 구분, 대장 종류, 변동일, 변동원인) 를 조회. 변동 이력 추적 용도.',
131
+ params: [...pnuParams]
132
+ },
133
+ getBrExposInfo: {
134
+ operationId: 'getBrExposInfo',
135
+ label: '전유부 조회',
136
+ description: '집합건축물의 호(세대) 목록과 호별 개요를 조회. 전유공용면적보다 상위 요약 수준.',
137
+ params: [...pnuParams]
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,41 @@
1
+ import { OperationSpec, ServiceSpec, parseOperationRef } from './types'
2
+ import { BldRgstServiceV2 } from './building-ledger'
3
+
4
+ export { BldRgstServiceV2 } from './building-ledger'
5
+ export * from './types'
6
+
7
+ const services = new Map<string, ServiceSpec>()
8
+
9
+ function registerService(spec: ServiceSpec) {
10
+ services.set(spec.serviceId, spec)
11
+ }
12
+
13
+ registerService(BldRgstServiceV2)
14
+
15
+ export const SpecRegistry = {
16
+ /** 등록된 모든 서비스 스펙을 반환. */
17
+ listServices(): ServiceSpec[] {
18
+ return Array.from(services.values())
19
+ },
20
+
21
+ /** 서비스 ID 로 서비스 스펙 조회. */
22
+ getService(serviceId: string): ServiceSpec | undefined {
23
+ return services.get(serviceId)
24
+ },
25
+
26
+ /** `<serviceId>.<operationId>` 참조 문자열로 오퍼레이션 스펙 조회. */
27
+ resolve(operationRef: string): { service: ServiceSpec; operation: OperationSpec } | undefined {
28
+ const parts = parseOperationRef(operationRef)
29
+ if (!parts) return undefined
30
+ const service = services.get(parts.serviceId)
31
+ if (!service) return undefined
32
+ const operation = service.operations[parts.operationId]
33
+ if (!operation) return undefined
34
+ return { service, operation }
35
+ },
36
+
37
+ /** (확장용) 외부에서 서비스 스펙을 추가 등록. */
38
+ register(spec: ServiceSpec) {
39
+ registerService(spec)
40
+ }
41
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 공공데이터포털(data.go.kr) Open API 오퍼레이션 스펙 타입.
3
+ *
4
+ * "선언적 레지스트리 + 단일 범용 태스크" 패턴의 핵심.
5
+ *
6
+ * - 런타임에는 `data-go-kr-call` 태스크가 이 스펙으로 파라미터 검증·정규화
7
+ * - 빌드/CI 에서는 이 스펙으로부터 서비스 카탈로그 markdown 을 자동 생성 가능
8
+ * - (장기) 통합 UI 가 이 스펙을 읽어 오퍼레이션 선택 시 **동적 폼 + 인라인 도움말** 을 렌더
9
+ *
10
+ * 즉 스펙 하나가 검증·문서·UI 3가지의 진실의 원천(single source of truth) 이다.
11
+ */
12
+
13
+ /** 단일 파라미터 스펙 (도움말용 메타 포함). */
14
+ export interface OperationParamSpec {
15
+ /** 쿼리 파라미터 이름 (예: `sigunguCd`). */
16
+ name: string
17
+ /** 화면 라벨 키 (i18n). 생략 시 `name` 사용. */
18
+ label?: string
19
+ /** 필수 여부. */
20
+ required?: boolean
21
+ /** 기본값 (입력 생략 시 적용). */
22
+ default?: string
23
+ /** zero-pad 길이. 예: `bun=6` + pad=4 → `"0006"`. */
24
+ pad?: number
25
+ /** 정확히 이 길이여야 함. 위반 시 태스크가 ValidationError 던짐. */
26
+ length?: number
27
+ /** 사람이 읽는 파라미터 설명 (도움말·UI 툴팁). */
28
+ description: string
29
+ /** 예시 값. */
30
+ example?: string | number
31
+ /** enum 선택지. 제공 시 태스크가 해당 값만 허용. */
32
+ enum?: Array<{ value: string; label: string }>
33
+ }
34
+
35
+ /** 오퍼레이션(엔드포인트) 스펙. */
36
+ export interface OperationSpec {
37
+ /** 오퍼레이션 경로명 (예: `getBrTitleInfo`). */
38
+ operationId: string
39
+ /** 화면 라벨 (예: '표제부 조회'). */
40
+ label: string
41
+ /** 사람이 읽는 설명 — 이 오퍼레이션이 무엇을 돌려주는지. */
42
+ description: string
43
+ /** 파라미터 목록 (검증 + 문서). */
44
+ params: OperationParamSpec[]
45
+ /** 응답 구조 주의사항 (예: 다동 필지는 items 복수, 숫자는 문자열 등). */
46
+ responseNotes?: string
47
+ /** data.go.kr 원문 상세 페이지 URL. */
48
+ externalDocsUrl?: string
49
+ /** 자주 쓰는 예시 호출. */
50
+ examples?: Array<{
51
+ label: string
52
+ params: Record<string, any>
53
+ comment?: string
54
+ }>
55
+ }
56
+
57
+ /** 서비스 스펙 (오퍼레이션 묶음). */
58
+ export interface ServiceSpec {
59
+ /** 서비스 경로 (예: `1613000/BldRgstService_v2`). */
60
+ serviceId: string
61
+ /** 화면 라벨 (예: '국토교통부_건축물대장정보 서비스 v2'). */
62
+ label: string
63
+ /** 사람이 읽는 설명. */
64
+ description: string
65
+ /** data.go.kr 활용신청 페이지 URL. */
66
+ homepage?: string
67
+ /** 오퍼레이션 맵 (operationId → spec). */
68
+ operations: Record<string, OperationSpec>
69
+ }
70
+
71
+ /**
72
+ * 오퍼레이션 참조 문자열 파서.
73
+ *
74
+ * 레지스트리 조회 키: `"<serviceId>.<operationId>"` — 점으로 구분.
75
+ * 예: `"1613000/BldRgstService_v2.getBrTitleInfo"`
76
+ */
77
+ export function parseOperationRef(ref: string): { serviceId: string; operationId: string } | null {
78
+ const idx = ref.lastIndexOf('.')
79
+ if (idx < 0) return null
80
+ return {
81
+ serviceId: ref.substring(0, idx),
82
+ operationId: ref.substring(idx + 1)
83
+ }
84
+ }
@@ -0,0 +1,109 @@
1
+ import { ConnectionManager, Context, TaskRegistry, evaluateTemplate } from '@things-factory/integration-base'
2
+
3
+ import { DataGoKrConnectionInstance } from '../../connector/data-go-kr-connector'
4
+ import { BldRgstServiceV2 } from '../../spec/building-ledger'
5
+ import { BrTitleItem, BuildingLedgerResult } from '../../types'
6
+
7
+ /**
8
+ * 건축물대장 표제부(`getBrTitleInfo`) 조회 태스크.
9
+ *
10
+ * 순수 fetch 만 수행 — 응답 items 를 그대로 반환한다. 다동 필지 집계·단위 변환
11
+ * 같은 도메인 가공은 소비자 측 후속 스텝(data-mapper / jsonata / 자체 태스크) 에서
12
+ * 처리하도록 책임을 분리.
13
+ *
14
+ * 왜 전용 태스크인가:
15
+ * - 범용 `data-go-kr-call` 로도 호출 가능하지만, 필요한 operationRef 와 PNU 5요소
16
+ * 파라미터를 매번 입력하는 대신 파라미터 폼을 명시적으로 제공해 운영/시나리오
17
+ * 작성 부담을 줄인다.
18
+ * - 건축물대장은 dssp/dkpi·건산연·공인중개·자산관리 등 자주 쓰이는 도메인이므로
19
+ * 범용보다 한 단계 전용화된 표면을 둘 가치가 충분.
20
+ */
21
+ async function BuildingLedgerTask(step, context: Context): Promise<{ data: BuildingLedgerResult }> {
22
+ const { connection: connectionName, params } = step
23
+ const { domain, user, data, variables, lng, logger } = context
24
+
25
+ const instance: DataGoKrConnectionInstance = await ConnectionManager.getConnectionInstanceByName(
26
+ domain,
27
+ connectionName
28
+ )
29
+ if (!instance?.client) {
30
+ throw new Error(`data-go-kr connection '${connectionName}' is not established.`)
31
+ }
32
+
33
+ const scope = { domain, user, lng, data, variables, console }
34
+ const evalString = (value: string | undefined) => {
35
+ if (value === undefined || value === null || value === '') return undefined
36
+ try {
37
+ return evaluateTemplate(String(value), scope)
38
+ } catch (err) {
39
+ throw new Error(`data-go-kr-building-ledger: failed to evaluate parameter: ${(err as Error).message}`)
40
+ }
41
+ }
42
+
43
+ const sigunguCd = evalString(params.sigunguCd)
44
+ const bjdongCd = evalString(params.bjdongCd)
45
+ const bun = evalString(params.bun)
46
+
47
+ if (!sigunguCd) throw new Error('data-go-kr-building-ledger: sigunguCd is required')
48
+ if (!bjdongCd) throw new Error('data-go-kr-building-ledger: bjdongCd is required')
49
+ if (!bun) throw new Error('data-go-kr-building-ledger: bun is required')
50
+
51
+ const servicePath = `${BldRgstServiceV2.serviceId}/getBrTitleInfo`
52
+
53
+ const normalizedParams = {
54
+ sigunguCd,
55
+ bjdongCd,
56
+ platGbCd: evalString(params.platGbCd) || '0',
57
+ bun: zpad(bun, 4),
58
+ ji: zpad(evalString(params.ji) || '0', 4),
59
+ numOfRows: 100,
60
+ pageNo: 1
61
+ }
62
+
63
+ logger?.info?.(`[data-go-kr] ${servicePath} params=${JSON.stringify(normalizedParams)}`)
64
+
65
+ const items: BrTitleItem[] = await instance.client.getItems(servicePath, normalizedParams)
66
+
67
+ if (items.length === 0) {
68
+ throw new Error(
69
+ `data-go-kr-building-ledger: no 표제부 records for PNU ` +
70
+ `${normalizedParams.sigunguCd}-${normalizedParams.bjdongCd}-${normalizedParams.platGbCd}-` +
71
+ `${normalizedParams.bun}-${normalizedParams.ji}. ` +
72
+ `Possible causes: PNU is wrong, building is pre-approval, or the lot has no registered building.`
73
+ )
74
+ }
75
+
76
+ logger?.info?.(`[data-go-kr] items=${items.length} first.bldNm='${items[0].bldNm || ''}'`)
77
+
78
+ return { data: { items } }
79
+ }
80
+
81
+ function zpad(value: string, width: number): string {
82
+ const s = String(value)
83
+ return s.length >= width ? s : '0'.repeat(width - s.length) + s
84
+ }
85
+
86
+ 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
+ {
90
+ type: 'select',
91
+ name: 'platGbCd',
92
+ 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
+ }
100
+ },
101
+ { type: 'string', name: 'bun', label: 'label.bun', placeholder: '예) 223' },
102
+ { type: 'string', name: 'ji', label: 'label.ji', placeholder: '예) 0' }
103
+ ]
104
+
105
+ BuildingLedgerTask.help = 'integration/task/building-ledger'
106
+
107
+ TaskRegistry.registerTaskHandler('data-go-kr-building-ledger', BuildingLedgerTask)
108
+
109
+ export default BuildingLedgerTask
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 건축물대장 도메인 전용 태스크 그룹.
3
+ *
4
+ * 공통 경로(`data-go-kr-call` + SpecRegistry) 로 커버 가능한 호출은 굳이 전용 태스크를
5
+ * 만들지 말 것 — 이 폴더는 **공통으로 표현하기 어려운 도메인 특화 로직** 이 필요한
6
+ * 태스크만 들어가는 자리이다.
7
+ *
8
+ * 향후 추가 예시:
9
+ * - apt.ts : data-go-kr-building-ledger-apt (집합건축물/아파트 전용)
10
+ * - recap.ts : data-go-kr-building-ledger-recap (총괄표제부 전용 변형)
11
+ */
12
+ import './general'
@@ -0,0 +1,157 @@
1
+ import { ConnectionManager, Context, TaskRegistry, evaluateTemplate } from '@things-factory/integration-base'
2
+
3
+ import { DataGoKrConnectionInstance } from '../connector/data-go-kr-connector'
4
+ import { DataGoKrResponse } from '../types'
5
+ import { OperationSpec, SpecRegistry } from '../spec'
6
+
7
+ /**
8
+ * 범용 공공데이터포털 호출 태스크.
9
+ *
10
+ * 하나의 태스크가 등록된 레지스트리의 **모든 오퍼레이션** 을 커버한다. TaskRegistry 가
11
+ * 수백 개 태스크로 폭발하는 것을 방지하는 핵심 설계.
12
+ *
13
+ * 입력:
14
+ * - `operationRef` : `"<serviceId>.<operationId>"` 형식 (예: `"1613000/BldRgstService_v2.getBrTitleInfo"`)
15
+ * - `params` : 오퍼레이션 파라미터 객체. 스펙 등록된 오퍼레이션이면 자동 검증·기본값·zero-pad 적용
16
+ *
17
+ * 레지스트리에 없는 오퍼레이션도 호출 가능 (스펙 없음 → 검증 스킵). 새 data.go.kr 서비스가
18
+ * 등장해도 스펙 추가 전까지 범용 호출로 즉시 사용 가능하다는 장점.
19
+ */
20
+ async function DataGoKrCall(step, context: Context): Promise<{ data: DataGoKrResponse<any> }> {
21
+ const { connection: connectionName, params: stepParams } = step
22
+ const { domain, user, data, variables, lng, logger } = context
23
+
24
+ const instance: DataGoKrConnectionInstance = await ConnectionManager.getConnectionInstanceByName(
25
+ domain,
26
+ connectionName
27
+ )
28
+ if (!instance?.client) {
29
+ throw new Error(`data-go-kr connection '${connectionName}' is not established.`)
30
+ }
31
+
32
+ const operationRef = stepParams?.operationRef
33
+ if (!operationRef || typeof operationRef !== 'string') {
34
+ throw new Error('data-go-kr-call: operationRef is required (format: "<serviceId>.<operationId>")')
35
+ }
36
+
37
+ const resolved = SpecRegistry.resolve(operationRef)
38
+ const rawParams = stepParams?.params || {}
39
+
40
+ const scope = { domain, user, lng, data, variables, console }
41
+ const evalValue = (value: any) => {
42
+ if (value === undefined || value === null) return value
43
+ if (typeof value !== 'string') return value
44
+ try {
45
+ return evaluateTemplate(value, scope)
46
+ } catch (err) {
47
+ throw new Error(`data-go-kr-call: failed to evaluate parameter: ${(err as Error).message}`)
48
+ }
49
+ }
50
+
51
+ const evaluatedParams: Record<string, any> = {}
52
+ for (const [k, v] of Object.entries(rawParams)) {
53
+ evaluatedParams[k] = evalValue(v)
54
+ }
55
+
56
+ const { service, operation } = resolved || { service: null, operation: null }
57
+ const finalParams = operation
58
+ ? normalizeAndValidateParams(operation, evaluatedParams)
59
+ : evaluatedParams
60
+
61
+ const servicePath = resolved
62
+ ? `${service!.serviceId}/${operation!.operationId}`
63
+ : operationRef.replace('.', '/')
64
+
65
+ logger?.info?.(`[data-go-kr] call ${servicePath} params=${JSON.stringify(finalParams)}`)
66
+
67
+ const response = await instance.client.get(servicePath, finalParams)
68
+ return { data: response }
69
+ }
70
+
71
+ /**
72
+ * 스펙 기반 파라미터 정규화·검증.
73
+ *
74
+ * 1. required 필수값 체크
75
+ * 2. default 기본값 주입
76
+ * 3. pad zero-padding (예: bun=6 → "0006")
77
+ * 4. length 정확 길이 검증
78
+ * 5. enum 허용 값 검증
79
+ */
80
+ function normalizeAndValidateParams(operation: OperationSpec, input: Record<string, any>): Record<string, any> {
81
+ const output: Record<string, any> = {}
82
+ const errors: string[] = []
83
+
84
+ for (const p of operation.params) {
85
+ let value = input[p.name]
86
+
87
+ if (value === undefined || value === null || value === '') {
88
+ if (p.default !== undefined) {
89
+ value = p.default
90
+ } else if (p.required) {
91
+ errors.push(`'${p.name}' is required (${p.description})`)
92
+ continue
93
+ } else {
94
+ continue
95
+ }
96
+ }
97
+
98
+ value = String(value)
99
+
100
+ if (p.pad && value.length < p.pad) {
101
+ value = '0'.repeat(p.pad - value.length) + value
102
+ }
103
+
104
+ if (p.length !== undefined && value.length !== p.length) {
105
+ errors.push(
106
+ `'${p.name}' must be exactly ${p.length} characters (got "${value}", length ${value.length})`
107
+ )
108
+ continue
109
+ }
110
+
111
+ if (p.enum && !p.enum.some(e => e.value === value)) {
112
+ errors.push(
113
+ `'${p.name}' must be one of [${p.enum.map(e => e.value).join(', ')}] (got "${value}")`
114
+ )
115
+ continue
116
+ }
117
+
118
+ output[p.name] = value
119
+ }
120
+
121
+ /* 스펙에 없는 파라미터도 pass-through (미등록 필드 허용) */
122
+ for (const [k, v] of Object.entries(input)) {
123
+ if (!(k in output) && !operation.params.find(p => p.name === k)) {
124
+ if (v !== undefined && v !== null && v !== '') {
125
+ output[k] = v
126
+ }
127
+ }
128
+ }
129
+
130
+ if (errors.length > 0) {
131
+ throw new Error(
132
+ `data-go-kr-call parameter validation failed for ${operation.operationId}:\n - ${errors.join('\n - ')}`
133
+ )
134
+ }
135
+
136
+ return output
137
+ }
138
+
139
+ DataGoKrCall.parameterSpec = [
140
+ {
141
+ type: 'string',
142
+ name: 'operationRef',
143
+ label: 'label.operation-ref',
144
+ placeholder: '<serviceId>.<operationId> (예: 1613000/BldRgstService_v2.getBrTitleInfo)'
145
+ },
146
+ {
147
+ type: 'scenario-step-input',
148
+ name: 'params',
149
+ label: 'label.operation-params'
150
+ }
151
+ ]
152
+
153
+ DataGoKrCall.help = 'integration/task/data-go-kr-call'
154
+
155
+ TaskRegistry.registerTaskHandler('data-go-kr-call', DataGoKrCall)
156
+
157
+ export default DataGoKrCall
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 태스크 배럴.
3
+ *
4
+ * 구성 규약:
5
+ * - 범용 태스크 (여러 서비스에 공통 적용) 는 이 폴더 **최상단** 에 파일로 배치
6
+ * 예) call.ts → data-go-kr-call
7
+ *
8
+ * - 특정 도메인/서비스의 전용 태스크는 **도메인 이름 폴더** 아래 배치
9
+ * 예) building-ledger/ , (향후) property-trade/ , address/ ...
10
+ *
11
+ * 새 서비스 지원 우선순위:
12
+ * 1) 먼저 SpecRegistry 에 서비스 스펙을 등록 → `data-go-kr-call` 로 즉시 호출 가능
13
+ * 2) 그래도 불충분한 경우(입력 폼 필요, 응답 구조가 비표준, 도메인 가공 필수 등)만
14
+ * 도메인 폴더를 새로 만들고 전용 태스크 작성
15
+ *
16
+ * 즉 이 폴더에 새 파일/폴더가 추가되는 것은 "공통 경로로 풀 수 없는 예외" 뿐이다.
17
+ */
18
+ import './call'
19
+ import './building-ledger'
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 공공데이터포털(data.go.kr) 공용 응답 타입.
3
+ *
4
+ * 대부분의 data.go.kr 서비스가 동일한 봉투(envelope) 구조를 사용한다:
5
+ * { response: { header: { resultCode, resultMsg }, body: { items: { item[] }, numOfRows, pageNo, totalCount } } }
6
+ */
7
+
8
+ /** 공공데이터포털 공용 응답 봉투. */
9
+ export interface DataGoKrResponse<T> {
10
+ response: {
11
+ header: {
12
+ resultCode: string /* '00' = 정상 */
13
+ resultMsg: string
14
+ }
15
+ body: {
16
+ items?: { item?: T | T[] } | ''
17
+ numOfRows: number
18
+ pageNo: number
19
+ totalCount: number
20
+ }
21
+ }
22
+ }
23
+
24
+ /**
25
+ * 건축물대장 서비스 표제부(`getBrTitleInfo`) 레코드.
26
+ *
27
+ * 총 30+ 필드이지만 도메인에서 자주 쓰는 필드만 명시하고 나머지는 인덱스 시그니처.
28
+ */
29
+ export interface BrTitleItem {
30
+ mgmBldrgstPk: string /* 관리건축물대장PK */
31
+ platPlc?: string /* 대지위치 */
32
+ newPlatPlc?: string /* 도로명대지위치 */
33
+ bldNm?: string /* 건물명 */
34
+ dongNm?: string /* 동명칭 */
35
+ platArea?: string /* 대지면적 */
36
+ archArea?: string /* 건축면적 */
37
+ bcRat?: string /* 건폐율(%) */
38
+ totArea?: string /* 연면적 ★ */
39
+ vlRatEstmTotArea?: string
40
+ vlRat?: string /* 용적률(%) ★ */
41
+ strctCd?: string
42
+ strctCdNm?: string
43
+ mainPurpsCd?: string
44
+ mainPurpsCdNm?: string
45
+ grndFlrCnt?: string /* 지상층수 ★ */
46
+ ugrndFlrCnt?: string /* 지하층수 ★ */
47
+ regstrKindCd?: string /* 2=일반 / 3=집합 */
48
+ regstrGbCd?: string
49
+ useAprDay?: string
50
+ mainBldCnt?: string
51
+ atchBldCnt?: string
52
+ totPkngCnt?: string
53
+ [key: string]: any
54
+ }
55
+
56
+ /**
57
+ * 건축물대장 표제부(`getBrTitleInfo`) 조회 결과.
58
+ *
59
+ * 다동(多棟) 필지는 items 가 여러 건, 단동 필지는 1건.
60
+ * 집계·요약은 소비자 스텝(data-mapper / jsonata / 도메인 전용 태스크) 에서 수행한다.
61
+ */
62
+ export interface BuildingLedgerResult {
63
+ items: BrTitleItem[]
64
+ }
@@ -0,0 +1,5 @@
1
+ import './engine'
2
+
3
+ export * from './engine/types'
4
+ export * from './engine/spec'
5
+ export { DataGoKrClient } from './engine/connector/data-go-kr-client'
@@ -0,0 +1 @@
1
+ export default {}
@@ -0,0 +1,11 @@
1
+ {
2
+ "label.service-key": "Service Key (data.go.kr)",
3
+ "label.api-origin": "API Origin (optional)",
4
+ "label.operation-ref": "Operation Reference",
5
+ "label.operation-params": "Operation Parameters",
6
+ "label.sigungu-cd": "Sigungu Code",
7
+ "label.bjdong-cd": "Beopjeongdong Code",
8
+ "label.plat-gb-cd": "Plot Type",
9
+ "label.bun": "Parcel Bun (main)",
10
+ "label.ji": "Parcel Ji (sub)"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "label.service-key": "サービスキー (data.go.kr)",
3
+ "label.api-origin": "APIオリジン(任意)",
4
+ "label.operation-ref": "オペレーション参照",
5
+ "label.operation-params": "オペレーションパラメータ",
6
+ "label.sigungu-cd": "市郡区コード",
7
+ "label.bjdong-cd": "法定洞コード",
8
+ "label.plat-gb-cd": "土地区分",
9
+ "label.bun": "本番",
10
+ "label.ji": "副番"
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "label.service-key": "서비스키 (data.go.kr)",
3
+ "label.api-origin": "API 오리진 (선택)",
4
+ "label.operation-ref": "오퍼레이션 참조",
5
+ "label.operation-params": "오퍼레이션 파라미터",
6
+ "label.sigungu-cd": "시군구코드",
7
+ "label.bjdong-cd": "법정동코드",
8
+ "label.plat-gb-cd": "대지 구분",
9
+ "label.bun": "본번",
10
+ "label.ji": "부번"
11
+ }