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

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 (113) hide show
  1. package/README.md +94 -0
  2. package/ROADMAP.md +282 -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 +138 -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 +29 -0
  7. package/dist-server/engine/connector/data-go-kr-connector.js +53 -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/hub.d.ts +12 -0
  16. package/dist-server/engine/spec/building-ledger/hub.js +138 -0
  17. package/dist-server/engine/spec/building-ledger/hub.js.map +1 -0
  18. package/dist-server/engine/spec/building-ledger/index.d.ts +7 -0
  19. package/dist-server/engine/spec/building-ledger/index.js +12 -0
  20. package/dist-server/engine/spec/building-ledger/index.js.map +1 -0
  21. package/dist-server/engine/spec/index.d.ts +17 -0
  22. package/dist-server/engine/spec/index.js +59 -0
  23. package/dist-server/engine/spec/index.js.map +1 -0
  24. package/dist-server/engine/spec/types.d.ts +79 -0
  25. package/dist-server/engine/spec/types.js +30 -0
  26. package/dist-server/engine/spec/types.js.map +1 -0
  27. package/dist-server/engine/spec/weather/asos.d.ts +2 -0
  28. package/dist-server/engine/spec/weather/asos.js +33 -0
  29. package/dist-server/engine/spec/weather/asos.js.map +1 -0
  30. package/dist-server/engine/spec/weather/aws.d.ts +2 -0
  31. package/dist-server/engine/spec/weather/aws.js +28 -0
  32. package/dist-server/engine/spec/weather/aws.js.map +1 -0
  33. package/dist-server/engine/spec/weather/index.d.ts +14 -0
  34. package/dist-server/engine/spec/weather/index.js +23 -0
  35. package/dist-server/engine/spec/weather/index.js.map +1 -0
  36. package/dist-server/engine/spec/weather/mid-term-forecast.d.ts +2 -0
  37. package/dist-server/engine/spec/weather/mid-term-forecast.js +56 -0
  38. package/dist-server/engine/spec/weather/mid-term-forecast.js.map +1 -0
  39. package/dist-server/engine/spec/weather/short-term-forecast.d.ts +2 -0
  40. package/dist-server/engine/spec/weather/short-term-forecast.js +71 -0
  41. package/dist-server/engine/spec/weather/short-term-forecast.js.map +1 -0
  42. package/dist-server/engine/spec/weather/warning.d.ts +2 -0
  43. package/dist-server/engine/spec/weather/warning.js +34 -0
  44. package/dist-server/engine/spec/weather/warning.js.map +1 -0
  45. package/dist-server/engine/task/building-ledger/general.d.ts +30 -0
  46. package/dist-server/engine/task/building-ledger/general.js +123 -0
  47. package/dist-server/engine/task/building-ledger/general.js.map +1 -0
  48. package/dist-server/engine/task/building-ledger/index.d.ts +13 -0
  49. package/dist-server/engine/task/building-ledger/index.js +16 -0
  50. package/dist-server/engine/task/building-ledger/index.js.map +1 -0
  51. package/dist-server/engine/task/building-ledger/summary.d.ts +95 -0
  52. package/dist-server/engine/task/building-ledger/summary.js +174 -0
  53. package/dist-server/engine/task/building-ledger/summary.js.map +1 -0
  54. package/dist-server/engine/task/call.d.ts +40 -0
  55. package/dist-server/engine/task/call.js +144 -0
  56. package/dist-server/engine/task/call.js.map +1 -0
  57. package/dist-server/engine/task/index.d.ts +20 -0
  58. package/dist-server/engine/task/index.js +23 -0
  59. package/dist-server/engine/task/index.js.map +1 -0
  60. package/dist-server/engine/task/weather/grid-converter.d.ts +24 -0
  61. package/dist-server/engine/task/weather/grid-converter.js +69 -0
  62. package/dist-server/engine/task/weather/grid-converter.js.map +1 -0
  63. package/dist-server/engine/task/weather/index.d.ts +13 -0
  64. package/dist-server/engine/task/weather/index.js +18 -0
  65. package/dist-server/engine/task/weather/index.js.map +1 -0
  66. package/dist-server/engine/task/weather/short-term-forecast.d.ts +38 -0
  67. package/dist-server/engine/task/weather/short-term-forecast.js +99 -0
  68. package/dist-server/engine/task/weather/short-term-forecast.js.map +1 -0
  69. package/dist-server/engine/types.d.ts +63 -0
  70. package/dist-server/engine/types.js +9 -0
  71. package/dist-server/engine/types.js.map +1 -0
  72. package/dist-server/index.d.ts +6 -0
  73. package/dist-server/index.js +13 -0
  74. package/dist-server/index.js.map +1 -0
  75. package/dist-server/tsconfig.tsbuildinfo +1 -0
  76. package/helps/integration/connector/data-go-kr-connector.md +38 -0
  77. package/helps/integration/services/building-ledger.md +110 -0
  78. package/helps/integration/services/weather.md +82 -0
  79. package/helps/integration/task/building-ledger.md +135 -0
  80. package/helps/integration/task/data-go-kr-call.md +114 -0
  81. package/helps/integration/task/weather-short-term-forecast.md +96 -0
  82. package/package.json +28 -0
  83. package/server/engine/connector/data-go-kr-client.ts +157 -0
  84. package/server/engine/connector/data-go-kr-connector.ts +72 -0
  85. package/server/engine/connector/index.ts +1 -0
  86. package/server/engine/index.ts +2 -0
  87. package/server/engine/spec/building-ledger/hub.ts +143 -0
  88. package/server/engine/spec/building-ledger/index.ts +7 -0
  89. package/server/engine/spec/index.ts +62 -0
  90. package/server/engine/spec/types.ts +84 -0
  91. package/server/engine/spec/weather/asos.ts +32 -0
  92. package/server/engine/spec/weather/aws.ts +27 -0
  93. package/server/engine/spec/weather/index.ts +14 -0
  94. package/server/engine/spec/weather/mid-term-forecast.ts +56 -0
  95. package/server/engine/spec/weather/short-term-forecast.ts +73 -0
  96. package/server/engine/spec/weather/warning.ts +33 -0
  97. package/server/engine/task/building-ledger/general.ts +141 -0
  98. package/server/engine/task/building-ledger/index.ts +13 -0
  99. package/server/engine/task/building-ledger/summary.ts +277 -0
  100. package/server/engine/task/call.ts +177 -0
  101. package/server/engine/task/index.ts +20 -0
  102. package/server/engine/task/weather/grid-converter.ts +79 -0
  103. package/server/engine/task/weather/index.ts +14 -0
  104. package/server/engine/task/weather/short-term-forecast.ts +110 -0
  105. package/server/engine/types.ts +64 -0
  106. package/server/index.ts +7 -0
  107. package/things-factory.config.js +1 -0
  108. package/translations/en.json +18 -0
  109. package/translations/ja.json +18 -0
  110. package/translations/ko.json +18 -0
  111. package/translations/ms.json +18 -0
  112. package/translations/zh.json +18 -0
  113. package/tsconfig.json +10 -0
@@ -0,0 +1,157 @@
1
+ import { DataGoKrResponse } from '../types'
2
+
3
+ /**
4
+ * 공공데이터포털(data.go.kr) 공용 HTTP 클라이언트.
5
+ *
6
+ * 모든 data.go.kr Open API 는 아래 공통 규약을 따른다:
7
+ * - URL: `https://apis.data.go.kr/<agency>/<service>/<operation>`
8
+ * - 인증: `serviceKey` 쿼리 파라미터 (Encoding/Decoding 두 형태 모두 수용)
9
+ * - 포맷: `_type=json` 없으면 XML
10
+ * - 에러: `<OpenAPI_ServiceResponse>` XML 또는 `response.header.resultCode !== '00'`
11
+ *
12
+ * 본 클라이언트는 서비스·오퍼레이션에 독립적으로 동작하며, 경로를 파라미터로 받는다.
13
+ * 서비스별 도메인 로직(파라미터 검증, 응답 매핑) 은 task 레이어에서 처리.
14
+ */
15
+
16
+ const DEFAULT_ORIGIN = 'https://apis.data.go.kr'
17
+
18
+ export interface DataGoKrClientOptions {
19
+ serviceKey: string
20
+ /** 오리진 오버라이드 (모의 서버/사설 프록시 용도). 기본 `https://apis.data.go.kr`. */
21
+ origin?: string
22
+ }
23
+
24
+ export class DataGoKrClient {
25
+ private readonly serviceKey: string
26
+ private readonly origin: string
27
+
28
+ constructor(options: DataGoKrClientOptions) {
29
+ if (!options.serviceKey) {
30
+ throw new Error('DataGoKrClient requires serviceKey')
31
+ }
32
+ /* Encoding 키(%2B 포함)를 붙여넣어도 Decoding 키처럼 동작하도록 정규화.
33
+ base64 키는 `%` 를 포함하지 않으므로 raw Decoding 키는 영향 없음. */
34
+ this.serviceKey = normalizeServiceKey(options.serviceKey.trim())
35
+ this.origin = resolveOrigin(options.origin)
36
+ }
37
+
38
+ /**
39
+ * 범용 GET 호출.
40
+ *
41
+ * @param servicePath `<agency>/<service>/<operation>` (예: `1613000/BldRgstHubService/getBrTitleInfo`)
42
+ * @param params 쿼리 파라미터 (serviceKey / _type 은 자동 추가)
43
+ */
44
+ async get<T>(servicePath: string, params: Record<string, any>): Promise<DataGoKrResponse<T>> {
45
+ const path = servicePath.startsWith('/') ? servicePath : `/${servicePath}`
46
+ const url = new URL(`${this.origin}${path}`)
47
+ url.searchParams.set('serviceKey', this.serviceKey)
48
+ url.searchParams.set('_type', 'json')
49
+ for (const [k, v] of Object.entries(params)) {
50
+ if (v === undefined || v === null || v === '') continue
51
+ url.searchParams.set(k, String(v))
52
+ }
53
+
54
+ const response = await fetch(url.toString(), {
55
+ method: 'GET',
56
+ headers: {
57
+ 'User-Agent': '@things-factory/integration-data-go-kr',
58
+ Accept: 'application/json'
59
+ }
60
+ })
61
+ const raw = await response.text()
62
+
63
+ const nonSensitiveParams = Object.entries(params)
64
+ .filter(([, v]) => v !== undefined && v !== null && v !== '')
65
+ .map(([k, v]) => `${k}=${v}`)
66
+ .join(' ')
67
+
68
+ if (!response.ok) {
69
+ throw new Error(
70
+ `data.go.kr ${servicePath} failed (${response.status}): ${raw.slice(0, 300)} — params: ${nonSensitiveParams}`
71
+ )
72
+ }
73
+
74
+ let parsed: DataGoKrResponse<T>
75
+ try {
76
+ parsed = JSON.parse(raw) as DataGoKrResponse<T>
77
+ } catch (err) {
78
+ if (raw.includes('<OpenAPI_ServiceResponse') || raw.includes('<returnReasonCode>')) {
79
+ throw new Error(`data.go.kr ${servicePath}: ${parseOpenApiError(raw)} — params: ${nonSensitiveParams}`)
80
+ }
81
+ throw new Error(
82
+ `data.go.kr ${servicePath}: non-JSON response: ${raw.slice(0, 300)} — params: ${nonSensitiveParams}`
83
+ )
84
+ }
85
+
86
+ const header = parsed?.response?.header
87
+ if (!header) {
88
+ throw new Error(`data.go.kr ${servicePath}: response missing header — body: ${raw.slice(0, 300)}`)
89
+ }
90
+ if (header.resultCode !== '00') {
91
+ throw new Error(
92
+ `data.go.kr ${servicePath}: resultCode=${header.resultCode} msg=${header.resultMsg} — params: ${nonSensitiveParams}`
93
+ )
94
+ }
95
+
96
+ return parsed
97
+ }
98
+
99
+ /**
100
+ * 단건 또는 복수 items 를 배열로 정규화해서 반환한다.
101
+ *
102
+ * data.go.kr 공통 특성으로, body.items 가 아래 세 가지 형태로 올 수 있다:
103
+ * - `""` (빈 문자열, 결과 없음)
104
+ * - `{ item: { ... } }` (단건)
105
+ * - `{ item: [ ... ] }` (복수)
106
+ */
107
+ async getItems<T>(servicePath: string, params: Record<string, any>): Promise<T[]> {
108
+ const response = await this.get<T>(servicePath, params)
109
+ const items = response.response?.body?.items
110
+ if (!items) return []
111
+ const arr = (items as any).item
112
+ if (!arr) return []
113
+ return Array.isArray(arr) ? arr : [arr]
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Connection.endpoint 값을 검증하여 유효한 origin URL 로 정규화한다.
119
+ *
120
+ * - 비어있거나 공백만: 기본 오리진 사용
121
+ * - 유효한 절대 URL: trailing slash 제거 후 사용
122
+ * - 유효하지 않은 값: 친절한 에러 throw (단순 오타·누락 방지)
123
+ */
124
+ function resolveOrigin(value: string | undefined): string {
125
+ if (!value || !value.trim()) return DEFAULT_ORIGIN
126
+ const trimmed = value.trim()
127
+ try {
128
+ const parsed = new URL(trimmed)
129
+ if (!parsed.protocol.startsWith('http')) {
130
+ throw new Error('protocol must be http or https')
131
+ }
132
+ return trimmed.replace(/\/$/, '')
133
+ } catch (err) {
134
+ throw new Error(
135
+ `Invalid Connection.endpoint value '${value}': must be a full URL (예: https://apis.data.go.kr) ` +
136
+ `or empty (기본값 ${DEFAULT_ORIGIN} 사용). Reason: ${(err as Error).message}`
137
+ )
138
+ }
139
+ }
140
+
141
+ /** Encoding 키(%2B) 를 붙여넣어도 Decoding 키로 정규화. */
142
+ function normalizeServiceKey(key: string): string {
143
+ if (!key.includes('%')) return key
144
+ try {
145
+ return decodeURIComponent(key)
146
+ } catch {
147
+ return key
148
+ }
149
+ }
150
+
151
+ /** data.go.kr 공용 OpenAPI 에러 XML 에서 원인 추출. */
152
+ function parseOpenApiError(xml: string): string {
153
+ const code = /<returnReasonCode>([^<]+)<\/returnReasonCode>/.exec(xml)?.[1]
154
+ const msg = /<errMsg>([^<]+)<\/errMsg>/.exec(xml)?.[1]
155
+ const auth = /<returnAuthMsg>([^<]+)<\/returnAuthMsg>/.exec(xml)?.[1]
156
+ return `code=${code} msg=${msg} auth=${auth}`
157
+ }
@@ -0,0 +1,72 @@
1
+ import { ConnectionManager, Connector } from '@things-factory/integration-base'
2
+
3
+ import { DataGoKrClient } from './data-go-kr-client'
4
+
5
+ /**
6
+ * 공공데이터포털(data.go.kr) Open API 커넥터.
7
+ *
8
+ * 하나의 ServiceKey 로 data.go.kr 산하 **모든 오퍼레이션** 을 호출할 수 있으므로,
9
+ * 사용자는 Connection 을 보통 한 개만 만들고 (앱·법인별로 1개) 여러 태스크에서 재사용한다.
10
+ *
11
+ * Connection 의 `endpoint` 필드를 API 오리진(host) 으로 사용한다. 비워두면 기본값
12
+ * `https://apis.data.go.kr` 가 사용되며, 모의 서버/사설 프록시로 라우팅하려면
13
+ * 해당 오리진을 endpoint 에 입력한다.
14
+ */
15
+ export interface DataGoKrConnectionInstance {
16
+ client: DataGoKrClient
17
+ }
18
+
19
+ export class DataGoKrConnector implements Connector {
20
+ async ready(connectionConfigs) {
21
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
22
+ ConnectionManager.logger.info('data-go-kr-connector connections are ready')
23
+ }
24
+
25
+ async connect(connection) {
26
+ const { endpoint, params } = connection
27
+ try {
28
+ const client = new DataGoKrClient({
29
+ serviceKey: params.serviceKey,
30
+ origin: endpoint
31
+ })
32
+ const instance: DataGoKrConnectionInstance = { client }
33
+ ConnectionManager.addConnectionInstance(connection, instance)
34
+ ConnectionManager.logger.info(
35
+ `data-go-kr-connector connection(${connection.name}) is connected (endpoint: ${endpoint || '(default)'})`
36
+ )
37
+ } catch (ex) {
38
+ ConnectionManager.logger.error(`data-go-kr-connector connection(${connection.name}) failed`, ex)
39
+ throw ex
40
+ }
41
+ }
42
+
43
+ async disconnect(connection) {
44
+ ConnectionManager.removeConnectionInstance(connection)
45
+ ConnectionManager.logger.info(`data-go-kr-connector connection(${connection.name}) is disconnected`)
46
+ }
47
+
48
+ get parameterSpec() {
49
+ return [
50
+ {
51
+ type: 'secret',
52
+ name: 'serviceKey',
53
+ label: 'label.service-key',
54
+ useDomainAttribute: true
55
+ }
56
+ ]
57
+ }
58
+
59
+ get taskPrefixes() {
60
+ return ['data-go-kr']
61
+ }
62
+
63
+ get help() {
64
+ return 'integration/connector/data-go-kr-connector'
65
+ }
66
+
67
+ get description() {
68
+ return 'Korean Public Data Portal (data.go.kr) Connector'
69
+ }
70
+ }
71
+
72
+ ConnectionManager.registerConnector('data-go-kr-connector', new DataGoKrConnector())
@@ -0,0 +1 @@
1
+ import './data-go-kr-connector'
@@ -0,0 +1,2 @@
1
+ import './connector'
2
+ import './task'
@@ -0,0 +1,143 @@
1
+ import { OperationParamSpec, ServiceSpec } from '../types'
2
+
3
+ /**
4
+ * PNU 5요소 파라미터 스펙 — BldRgstHubService 의 모든 오퍼레이션이 공유.
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
+ * 국토교통부_건축물대장 허브 서비스 (data.go.kr 데이터셋 15134735).
57
+ *
58
+ * 세움터(EAIS) DB 를 원천으로 하는 공공 건축물대장 통합 Hub Open API.
59
+ * 모든 오퍼레이션은 동일한 PNU(필지고유번호) 구성요소로 조회한다:
60
+ * - sigunguCd (5) + bjdongCd (5) + platGbCd (1) + bun (4) + ji (4)
61
+ *
62
+ * 구버전 `BldRgstService_v2` (`1613000/BldRgstService_v2/`) 는 본 허브 서비스로
63
+ * 사실상 통합·승격되었다. 신규 사용자는 허브 서비스 활용신청을 권장.
64
+ */
65
+ export const BldRgstHubService: ServiceSpec = {
66
+ serviceId: '1613000/BldRgstHubService',
67
+ label: '국토교통부_건축물대장 허브 서비스',
68
+ description:
69
+ '세움터(EAIS) 원천의 건축물대장 정보를 조회한다. 표제부·총괄표제부·층별개요·부속지번·전유공용면적·주택가격·소유자현황·기본개요 등 8개 오퍼레이션 제공.',
70
+ homepage: 'https://www.data.go.kr/data/15134735/openapi.do',
71
+ operations: {
72
+ getBrTitleInfo: {
73
+ operationId: 'getBrTitleInfo',
74
+ label: '표제부 조회',
75
+ description:
76
+ '건축물대장 표제부 (건물 본문) 를 조회한다. 연면적·용적률·지상층수·지하층수·주구조·주용도 등 건물 개요를 반환. 한 필지에 동이 여러 개이면 복수 레코드로 내려온다.',
77
+ params: [...pnuParams],
78
+ responseNotes:
79
+ '응답 `response.body.items.item` 이 배열 또는 단일 객체로 올 수 있음. 모든 숫자 필드가 문자열(`"114000.00"`)로 내려옴. 집합건축물은 `regstrKindCd=3`, 일반건축물은 `regstrKindCd=2`.',
80
+ externalDocsUrl: 'https://www.data.go.kr/data/15134735/openapi.do',
81
+ examples: [
82
+ {
83
+ label: '서울 강남구 역삼동 823',
84
+ params: { sigunguCd: '11680', bjdongCd: '10300', bun: '823', ji: '0' }
85
+ }
86
+ ]
87
+ },
88
+ getBrRecapTitleInfo: {
89
+ operationId: 'getBrRecapTitleInfo',
90
+ label: '총괄표제부 조회',
91
+ description:
92
+ '집합건축물(아파트 단지 등) 의 총괄표제부를 조회한다. 단지 전체 연면적·대지면적·동 수·세대수 등을 반환. 일반건축물에는 없음.',
93
+ params: [...pnuParams],
94
+ responseNotes: '일반건축물대장(단동) 의 경우 레코드가 없음. 집합건축물일 때만 유효.',
95
+ examples: [
96
+ {
97
+ label: '아파트 단지 (예시)',
98
+ params: { sigunguCd: '11680', bjdongCd: '10300', bun: '823', ji: '0' }
99
+ }
100
+ ]
101
+ },
102
+ getBrFlrOulnInfo: {
103
+ operationId: 'getBrFlrOulnInfo',
104
+ label: '층별개요 조회',
105
+ description:
106
+ '층별로 주용도·구조·면적을 조회. 지하 N층부터 지상 M층까지 각 층의 상세 구성을 반환. `regstrKindCd`, `regstrGbCd` 로 어느 대장의 층인지 구분.',
107
+ params: [...pnuParams],
108
+ responseNotes: '한 필지에 동이 여러 개이면 각 동의 각 층이 모두 나옴 → 결과 건수가 많을 수 있음.'
109
+ },
110
+ getBrExposPubuseAreaInfo: {
111
+ operationId: 'getBrExposPubuseAreaInfo',
112
+ label: '전유공용면적 조회',
113
+ description:
114
+ '집합건축물의 호(세대) 별 전유면적·공용면적을 조회. 아파트 세대별 실제 면적 산출에 사용.',
115
+ params: [...pnuParams],
116
+ responseNotes: '집합건축물일 때만 레코드 존재. 일반건축물에는 해당 없음.'
117
+ },
118
+ getBrAtchJibunInfo: {
119
+ operationId: 'getBrAtchJibunInfo',
120
+ label: '부속지번 조회',
121
+ description: '대장에 연결된 부속지번(인접 필지 등) 목록을 조회.',
122
+ params: [...pnuParams]
123
+ },
124
+ getBrHsprcInfo: {
125
+ operationId: 'getBrHsprcInfo',
126
+ label: '주택가격 조회',
127
+ description: '공동주택/단독주택 공시가격 이력을 조회. 연도별 개별주택가격·공동주택가격 제공.',
128
+ params: [...pnuParams]
129
+ },
130
+ getBrBasisOulnInfo: {
131
+ operationId: 'getBrBasisOulnInfo',
132
+ label: '기본개요 조회',
133
+ description: '대장의 기본개요(대장 구분, 대장 종류, 변동일, 변동원인) 를 조회. 변동 이력 추적 용도.',
134
+ params: [...pnuParams]
135
+ },
136
+ getBrExposInfo: {
137
+ operationId: 'getBrExposInfo',
138
+ label: '전유부 조회',
139
+ description: '집합건축물의 호(세대) 목록과 호별 개요를 조회. 전유공용면적보다 상위 요약 수준.',
140
+ params: [...pnuParams]
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 건축물대장 도메인 서비스 스펙 묶음.
3
+ *
4
+ * 새 변종(예: BldRgstService_v2 호환 유지, 멸실대장 서비스 등) 추가 시
5
+ * 이 폴더 안에 새 파일을 만들고 본 index 에 export + 아래로 SpecRegistry 에 등록.
6
+ */
7
+ export { BldRgstHubService } from './hub'
@@ -0,0 +1,62 @@
1
+ import { OperationSpec, ServiceSpec, parseOperationRef } from './types'
2
+
3
+ /* 도메인별 서비스 스펙 — 새 도메인 추가 시 폴더 추가 + 본 import 한 줄 */
4
+ import * as buildingLedger from './building-ledger'
5
+ import * as weather from './weather'
6
+
7
+ export * from './types'
8
+ export * from './building-ledger'
9
+ export * from './weather'
10
+
11
+ const services = new Map<string, ServiceSpec>()
12
+
13
+ function registerService(spec: ServiceSpec) {
14
+ services.set(spec.serviceId, spec)
15
+ }
16
+
17
+ /* 도메인 모듈에서 export 된 모든 ServiceSpec 인스턴스를 자동 등록.
18
+ 추가 도메인 import 만 늘리면 이하 코드는 그대로. */
19
+ function registerDomain(domain: Record<string, unknown>) {
20
+ for (const value of Object.values(domain)) {
21
+ if (isServiceSpec(value)) registerService(value)
22
+ }
23
+ }
24
+ function isServiceSpec(value: unknown): value is ServiceSpec {
25
+ return (
26
+ typeof value === 'object' &&
27
+ value !== null &&
28
+ typeof (value as any).serviceId === 'string' &&
29
+ typeof (value as any).operations === 'object'
30
+ )
31
+ }
32
+
33
+ registerDomain(buildingLedger)
34
+ registerDomain(weather)
35
+
36
+ export const SpecRegistry = {
37
+ /** 등록된 모든 서비스 스펙을 반환. */
38
+ listServices(): ServiceSpec[] {
39
+ return Array.from(services.values())
40
+ },
41
+
42
+ /** 서비스 ID 로 서비스 스펙 조회. */
43
+ getService(serviceId: string): ServiceSpec | undefined {
44
+ return services.get(serviceId)
45
+ },
46
+
47
+ /** `<serviceId>.<operationId>` 참조 문자열로 오퍼레이션 스펙 조회. */
48
+ resolve(operationRef: string): { service: ServiceSpec; operation: OperationSpec } | undefined {
49
+ const parts = parseOperationRef(operationRef)
50
+ if (!parts) return undefined
51
+ const service = services.get(parts.serviceId)
52
+ if (!service) return undefined
53
+ const operation = service.operations[parts.operationId]
54
+ if (!operation) return undefined
55
+ return { service, operation }
56
+ },
57
+
58
+ /** (확장용) 외부에서 서비스 스펙을 추가 등록. */
59
+ register(spec: ServiceSpec) {
60
+ registerService(spec)
61
+ }
62
+ }
@@ -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/BldRgstHubService`). */
60
+ serviceId: string
61
+ /** 화면 라벨 (예: '국토교통부_건축물대장 허브 서비스'). */
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/BldRgstHubService.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,32 @@
1
+ import { OperationParamSpec, ServiceSpec } from '../types'
2
+
3
+ /**
4
+ * 기상청_종관기상관측 시간자료 (ASOS) (data.go.kr 데이터셋 15057210).
5
+ *
6
+ * 지상 종관기상관측 (Automated Surface Observing System) 의 시간단위 관측 자료.
7
+ * 전국 약 100개 지점에서 표준화된 정밀 관측. 기온·강수량·풍향풍속·습도·기압 등.
8
+ */
9
+ const params: OperationParamSpec[] = [
10
+ { name: 'dataCd', required: true, description: "자료 코드. 'ASOS' 고정.", example: 'ASOS' },
11
+ { name: 'dateCd', required: true, description: "자료 단위. 시간자료='HR'.", example: 'HR' },
12
+ { name: 'startDt', required: true, length: 8, description: '검색 시작일 YYYYMMDD', example: '20260420' },
13
+ { name: 'startHh', required: true, length: 2, description: '검색 시작시 HH', example: '00' },
14
+ { name: 'endDt', required: true, length: 8, description: '검색 종료일 YYYYMMDD', example: '20260425' },
15
+ { name: 'endHh', required: true, length: 2, description: '검색 종료시 HH', example: '23' },
16
+ { name: 'stnIds', required: true, description: 'KMA ASOS 지점 코드. 서울=108, 부산=159 등.', example: '108' }
17
+ ]
18
+
19
+ export const KmaAsosHourlyService: ServiceSpec = {
20
+ serviceId: '1360000/AsosHourlyInfoService',
21
+ label: '기상청_종관기상관측 시간자료 (ASOS)',
22
+ description: '지상 종관기상관측(ASOS) 의 시간단위 관측 자료.',
23
+ homepage: 'https://www.data.go.kr/data/15057210/openapi.do',
24
+ operations: {
25
+ getWthrDataList: {
26
+ operationId: 'getWthrDataList',
27
+ label: '시간자료 조회',
28
+ description: '특정 지점·기간의 시간단위 관측 데이터.',
29
+ params
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,27 @@
1
+ import { OperationParamSpec, ServiceSpec } from '../types'
2
+
3
+ /**
4
+ * 기상청_방재기상관측 시간자료 (AWS) (data.go.kr 데이터셋 15059093).
5
+ *
6
+ * 자동기상관측장비(AWS) 의 시간단위 관측 자료. ASOS 보다 조밀한 격자 (전국 500+ 지점).
7
+ * 강수·기온·풍향풍속·습도 등을 시간 단위로 제공.
8
+ */
9
+ const params: OperationParamSpec[] = [
10
+ { name: 'tm', required: true, length: 12, description: '관측 시각 YYYYMMDDHHMM', example: '202604251200' },
11
+ { name: 'stn', description: '지점 번호 (생략 시 전국).', example: '108' }
12
+ ]
13
+
14
+ export const KmaAwsHourlyService: ServiceSpec = {
15
+ serviceId: '1360000/AwsHrInfoService',
16
+ label: '기상청_방재기상관측 시간자료 (AWS)',
17
+ description: '자동기상관측장비(AWS) 의 시간단위 관측 자료. ASOS 대비 조밀한 격자.',
18
+ homepage: 'https://www.data.go.kr/data/15059093/openapi.do',
19
+ operations: {
20
+ getAwsRltmList: {
21
+ operationId: 'getAwsRltmList',
22
+ label: 'AWS 시간자료 조회',
23
+ description: 'AWS 지점의 시간단위 관측 자료. 강수·기온·풍향풍속·습도 등.',
24
+ params
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 기상청(KMA) 도메인 서비스 스펙 묶음.
3
+ *
4
+ * 새 KMA 서비스(예: 황사·자외선·미세먼지·해양 등) 추가 시:
5
+ * 1. 본 폴더에 `<service-slug>.ts` 파일을 만들어 ServiceSpec export
6
+ * 2. 본 index 의 export 와 (필요 시) 외부 SpecRegistry 등록만 추가
7
+ *
8
+ * 한 파일에 여러 서비스를 합치지 말 것 — 확장성 보호.
9
+ */
10
+ export { KmaShortTermForecastService } from './short-term-forecast'
11
+ export { KmaMidTermForecastService } from './mid-term-forecast'
12
+ export { KmaWeatherWarningService } from './warning'
13
+ export { KmaAsosHourlyService } from './asos'
14
+ export { KmaAwsHourlyService } from './aws'
@@ -0,0 +1,56 @@
1
+ import { OperationParamSpec, ServiceSpec } from '../types'
2
+
3
+ /**
4
+ * 기상청_중기예보 조회서비스 (data.go.kr 데이터셋 15059468).
5
+ *
6
+ * 3~10일 전망. 광역 권역(regId) 단위로 06·18시 발표.
7
+ */
8
+ const sharedParams: OperationParamSpec[] = [
9
+ {
10
+ name: 'regId',
11
+ required: true,
12
+ description:
13
+ '광역 권역 코드. 육상은 `11B00000`(서울/인천/경기) 등, 기온은 `11B10101`(서울) 등 더 세분화. KMA 권역 코드 표 참조.',
14
+ example: '11B00000'
15
+ },
16
+ {
17
+ name: 'tmFc',
18
+ required: true,
19
+ length: 12,
20
+ description: '발표시각 YYYYMMDDHHMM. 중기예보는 06·18시 발표.',
21
+ example: '202604250600'
22
+ }
23
+ ]
24
+
25
+ export const KmaMidTermForecastService: ServiceSpec = {
26
+ serviceId: '1360000/MidFcstInfoService',
27
+ label: '기상청_중기예보 조회서비스',
28
+ description: '3~10일 중기예보. 육상·기온·해상 별 오퍼레이션.',
29
+ homepage: 'https://www.data.go.kr/data/15059468/openapi.do',
30
+ operations: {
31
+ getMidFcst: {
32
+ operationId: 'getMidFcst',
33
+ label: '중기예보 종합',
34
+ description: '중기 전망 텍스트 (해석본).',
35
+ params: sharedParams
36
+ },
37
+ getMidLandFcst: {
38
+ operationId: 'getMidLandFcst',
39
+ label: '육상 중기예보',
40
+ description: '육상 권역의 강수확률·하늘상태 (3~10일).',
41
+ params: sharedParams
42
+ },
43
+ getMidTa: {
44
+ operationId: 'getMidTa',
45
+ label: '중기 기온예보',
46
+ description: '아침 최저·낮 최고 기온 (3~10일).',
47
+ params: sharedParams
48
+ },
49
+ getMidSeaFcst: {
50
+ operationId: 'getMidSeaFcst',
51
+ label: '해상 중기예보',
52
+ description: '해상 권역의 풍향·풍속·파고.',
53
+ params: sharedParams
54
+ }
55
+ }
56
+ }