@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.
- package/README.md +94 -0
- package/ROADMAP.md +282 -0
- package/dist-server/engine/connector/data-go-kr-client.d.ts +27 -0
- package/dist-server/engine/connector/data-go-kr-client.js +138 -0
- package/dist-server/engine/connector/data-go-kr-client.js.map +1 -0
- package/dist-server/engine/connector/data-go-kr-connector.d.ts +29 -0
- package/dist-server/engine/connector/data-go-kr-connector.js +53 -0
- package/dist-server/engine/connector/data-go-kr-connector.js.map +1 -0
- package/dist-server/engine/connector/index.d.ts +1 -0
- package/dist-server/engine/connector/index.js +4 -0
- package/dist-server/engine/connector/index.js.map +1 -0
- package/dist-server/engine/index.d.ts +2 -0
- package/dist-server/engine/index.js +5 -0
- package/dist-server/engine/index.js.map +1 -0
- package/dist-server/engine/spec/building-ledger/hub.d.ts +12 -0
- package/dist-server/engine/spec/building-ledger/hub.js +138 -0
- package/dist-server/engine/spec/building-ledger/hub.js.map +1 -0
- package/dist-server/engine/spec/building-ledger/index.d.ts +7 -0
- package/dist-server/engine/spec/building-ledger/index.js +12 -0
- package/dist-server/engine/spec/building-ledger/index.js.map +1 -0
- package/dist-server/engine/spec/index.d.ts +17 -0
- package/dist-server/engine/spec/index.js +59 -0
- package/dist-server/engine/spec/index.js.map +1 -0
- package/dist-server/engine/spec/types.d.ts +79 -0
- package/dist-server/engine/spec/types.js +30 -0
- package/dist-server/engine/spec/types.js.map +1 -0
- package/dist-server/engine/spec/weather/asos.d.ts +2 -0
- package/dist-server/engine/spec/weather/asos.js +33 -0
- package/dist-server/engine/spec/weather/asos.js.map +1 -0
- package/dist-server/engine/spec/weather/aws.d.ts +2 -0
- package/dist-server/engine/spec/weather/aws.js +28 -0
- package/dist-server/engine/spec/weather/aws.js.map +1 -0
- package/dist-server/engine/spec/weather/index.d.ts +14 -0
- package/dist-server/engine/spec/weather/index.js +23 -0
- package/dist-server/engine/spec/weather/index.js.map +1 -0
- package/dist-server/engine/spec/weather/mid-term-forecast.d.ts +2 -0
- package/dist-server/engine/spec/weather/mid-term-forecast.js +56 -0
- package/dist-server/engine/spec/weather/mid-term-forecast.js.map +1 -0
- package/dist-server/engine/spec/weather/short-term-forecast.d.ts +2 -0
- package/dist-server/engine/spec/weather/short-term-forecast.js +71 -0
- package/dist-server/engine/spec/weather/short-term-forecast.js.map +1 -0
- package/dist-server/engine/spec/weather/warning.d.ts +2 -0
- package/dist-server/engine/spec/weather/warning.js +34 -0
- package/dist-server/engine/spec/weather/warning.js.map +1 -0
- package/dist-server/engine/task/building-ledger/general.d.ts +30 -0
- package/dist-server/engine/task/building-ledger/general.js +123 -0
- package/dist-server/engine/task/building-ledger/general.js.map +1 -0
- package/dist-server/engine/task/building-ledger/index.d.ts +13 -0
- package/dist-server/engine/task/building-ledger/index.js +16 -0
- package/dist-server/engine/task/building-ledger/index.js.map +1 -0
- package/dist-server/engine/task/building-ledger/summary.d.ts +95 -0
- package/dist-server/engine/task/building-ledger/summary.js +174 -0
- package/dist-server/engine/task/building-ledger/summary.js.map +1 -0
- package/dist-server/engine/task/call.d.ts +40 -0
- package/dist-server/engine/task/call.js +144 -0
- package/dist-server/engine/task/call.js.map +1 -0
- package/dist-server/engine/task/index.d.ts +20 -0
- package/dist-server/engine/task/index.js +23 -0
- package/dist-server/engine/task/index.js.map +1 -0
- package/dist-server/engine/task/weather/grid-converter.d.ts +24 -0
- package/dist-server/engine/task/weather/grid-converter.js +69 -0
- package/dist-server/engine/task/weather/grid-converter.js.map +1 -0
- package/dist-server/engine/task/weather/index.d.ts +13 -0
- package/dist-server/engine/task/weather/index.js +18 -0
- package/dist-server/engine/task/weather/index.js.map +1 -0
- package/dist-server/engine/task/weather/short-term-forecast.d.ts +38 -0
- package/dist-server/engine/task/weather/short-term-forecast.js +99 -0
- package/dist-server/engine/task/weather/short-term-forecast.js.map +1 -0
- package/dist-server/engine/types.d.ts +63 -0
- package/dist-server/engine/types.js +9 -0
- package/dist-server/engine/types.js.map +1 -0
- package/dist-server/index.d.ts +6 -0
- package/dist-server/index.js +13 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/helps/integration/connector/data-go-kr-connector.md +38 -0
- package/helps/integration/services/building-ledger.md +110 -0
- package/helps/integration/services/weather.md +82 -0
- package/helps/integration/task/building-ledger.md +135 -0
- package/helps/integration/task/data-go-kr-call.md +114 -0
- package/helps/integration/task/weather-short-term-forecast.md +96 -0
- package/package.json +28 -0
- package/server/engine/connector/data-go-kr-client.ts +157 -0
- package/server/engine/connector/data-go-kr-connector.ts +72 -0
- package/server/engine/connector/index.ts +1 -0
- package/server/engine/index.ts +2 -0
- package/server/engine/spec/building-ledger/hub.ts +143 -0
- package/server/engine/spec/building-ledger/index.ts +7 -0
- package/server/engine/spec/index.ts +62 -0
- package/server/engine/spec/types.ts +84 -0
- package/server/engine/spec/weather/asos.ts +32 -0
- package/server/engine/spec/weather/aws.ts +27 -0
- package/server/engine/spec/weather/index.ts +14 -0
- package/server/engine/spec/weather/mid-term-forecast.ts +56 -0
- package/server/engine/spec/weather/short-term-forecast.ts +73 -0
- package/server/engine/spec/weather/warning.ts +33 -0
- package/server/engine/task/building-ledger/general.ts +141 -0
- package/server/engine/task/building-ledger/index.ts +13 -0
- package/server/engine/task/building-ledger/summary.ts +277 -0
- package/server/engine/task/call.ts +177 -0
- package/server/engine/task/index.ts +20 -0
- package/server/engine/task/weather/grid-converter.ts +79 -0
- package/server/engine/task/weather/index.ts +14 -0
- package/server/engine/task/weather/short-term-forecast.ts +110 -0
- package/server/engine/types.ts +64 -0
- package/server/index.ts +7 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +18 -0
- package/translations/ja.json +18 -0
- package/translations/ko.json +18 -0
- package/translations/ms.json +18 -0
- package/translations/zh.json +18 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,177 @@
|
|
|
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/BldRgstHubService.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
|
+
/**
|
|
140
|
+
* SpecRegistry 에 등록된 모든 오퍼레이션을 (operationRef → display) 옵션으로 펼친다.
|
|
141
|
+
* 모듈 로딩 시 한 번 평가되며, 이후 register() 로 추가된 spec 은 반영되지 않음
|
|
142
|
+
* (드문 케이스라 수용 — 향후 동적 parameterSpec 도입 시 해결).
|
|
143
|
+
*/
|
|
144
|
+
function buildOperationRefOptions(): Array<{ value: string; display: string }> {
|
|
145
|
+
return SpecRegistry.listServices()
|
|
146
|
+
.flatMap(svc =>
|
|
147
|
+
Object.values(svc.operations).map(op => ({
|
|
148
|
+
value: `${svc.serviceId}.${op.operationId}`,
|
|
149
|
+
display: `[${svc.label}] ${op.label}`
|
|
150
|
+
}))
|
|
151
|
+
)
|
|
152
|
+
.sort((a, b) => a.display.localeCompare(b.display, 'ko'))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
DataGoKrCall.parameterSpec = [
|
|
156
|
+
{
|
|
157
|
+
type: 'select',
|
|
158
|
+
name: 'operationRef',
|
|
159
|
+
label: 'label.operation-ref',
|
|
160
|
+
property: {
|
|
161
|
+
options: buildOperationRefOptions()
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: 'scenario-step-input',
|
|
166
|
+
name: 'params',
|
|
167
|
+
label: 'label.operation-params',
|
|
168
|
+
placeholder:
|
|
169
|
+
'오퍼레이션 파라미터를 JSON 객체로. 예) 건축물대장: {"sigunguCd":"11680","bjdongCd":"10300","bun":"223"} · 단기예보: {"base_date":"20260425","base_time":"1100","nx":"60","ny":"127"}. 정확한 키는 helps/integration/services/<domain>.md 참고.'
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
DataGoKrCall.help = 'integration/task/data-go-kr-call'
|
|
174
|
+
|
|
175
|
+
TaskRegistry.registerTaskHandler('data-go-kr-call', DataGoKrCall)
|
|
176
|
+
|
|
177
|
+
export default DataGoKrCall
|
|
@@ -0,0 +1,20 @@
|
|
|
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'
|
|
20
|
+
import './weather'
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KMA(기상청) 5km 격자좌표 ↔ 위경도 변환.
|
|
3
|
+
*
|
|
4
|
+
* 동네예보·초단기 시리즈는 위경도가 아닌 KMA 자체 격자(nx, ny) 로 호출하므로,
|
|
5
|
+
* 일반적으로 알고 있는 위경도 좌표를 격자로 바꿔주는 헬퍼가 필요하다.
|
|
6
|
+
*
|
|
7
|
+
* 변환식 출처: 기상청 공식 동네예보 격자 변환 문서 (Lambert Conformal Conic).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const RE = 6371.00877 /* 지구 반경 (km) */
|
|
11
|
+
const GRID = 5.0 /* 격자 간격 (km) */
|
|
12
|
+
const SLAT1 = 30.0 /* 표준 위도 1 (degree) */
|
|
13
|
+
const SLAT2 = 60.0 /* 표준 위도 2 (degree) */
|
|
14
|
+
const OLON = 126.0 /* 기준점 경도 (degree) */
|
|
15
|
+
const OLAT = 38.0 /* 기준점 위도 (degree) */
|
|
16
|
+
const XO = 43 /* 기준점 X좌표 (격자 단위) */
|
|
17
|
+
const YO = 136 /* 기준점 Y좌표 (격자 단위) */
|
|
18
|
+
|
|
19
|
+
const DEGRAD = Math.PI / 180.0
|
|
20
|
+
const RADDEG = 180.0 / Math.PI
|
|
21
|
+
|
|
22
|
+
const re = RE / GRID
|
|
23
|
+
const slat1 = SLAT1 * DEGRAD
|
|
24
|
+
const slat2 = SLAT2 * DEGRAD
|
|
25
|
+
const olon = OLON * DEGRAD
|
|
26
|
+
const olat = OLAT * DEGRAD
|
|
27
|
+
|
|
28
|
+
const sn =
|
|
29
|
+
Math.log(Math.cos(slat1) / Math.cos(slat2)) /
|
|
30
|
+
Math.log(Math.tan(Math.PI * 0.25 + slat2 * 0.5) / Math.tan(Math.PI * 0.25 + slat1 * 0.5))
|
|
31
|
+
const sf = (Math.pow(Math.tan(Math.PI * 0.25 + slat1 * 0.5), sn) * Math.cos(slat1)) / sn
|
|
32
|
+
const ro = (re * sf) / Math.pow(Math.tan(Math.PI * 0.25 + olat * 0.5), sn)
|
|
33
|
+
|
|
34
|
+
export interface LatLon {
|
|
35
|
+
/** 위도 (degree). */
|
|
36
|
+
latitude: number
|
|
37
|
+
/** 경도 (degree). */
|
|
38
|
+
longitude: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface KmaGrid {
|
|
42
|
+
/** 격자 X. */
|
|
43
|
+
nx: number
|
|
44
|
+
/** 격자 Y. */
|
|
45
|
+
ny: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 위경도 → KMA 5km 격자좌표 (nx, ny). */
|
|
49
|
+
export function latLonToGrid(input: LatLon): KmaGrid {
|
|
50
|
+
const ra = (re * sf) / Math.pow(Math.tan(Math.PI * 0.25 + input.latitude * DEGRAD * 0.5), sn)
|
|
51
|
+
let theta = input.longitude * DEGRAD - olon
|
|
52
|
+
if (theta > Math.PI) theta -= 2.0 * Math.PI
|
|
53
|
+
if (theta < -Math.PI) theta += 2.0 * Math.PI
|
|
54
|
+
theta *= sn
|
|
55
|
+
const x = ra * Math.sin(theta) + XO
|
|
56
|
+
const y = ro - ra * Math.cos(theta) + YO
|
|
57
|
+
return { nx: Math.floor(x + 1.5), ny: Math.floor(y + 1.5) }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** KMA 5km 격자좌표 → 위경도 (격자 중심점). */
|
|
61
|
+
export function gridToLatLon(input: KmaGrid): LatLon {
|
|
62
|
+
const xn = input.nx - XO
|
|
63
|
+
const yn = ro - (input.ny - YO)
|
|
64
|
+
let ra = Math.sqrt(xn * xn + yn * yn)
|
|
65
|
+
if (sn < 0.0) ra = -ra
|
|
66
|
+
let alat = Math.pow((re * sf) / ra, 1.0 / sn)
|
|
67
|
+
alat = 2.0 * Math.atan(alat) - Math.PI * 0.5
|
|
68
|
+
let theta: number
|
|
69
|
+
if (Math.abs(xn) <= 0.0) {
|
|
70
|
+
theta = 0.0
|
|
71
|
+
} else if (Math.abs(yn) <= 0.0) {
|
|
72
|
+
theta = Math.PI * 0.5
|
|
73
|
+
if (xn < 0.0) theta = -theta
|
|
74
|
+
} else {
|
|
75
|
+
theta = Math.atan2(xn, yn)
|
|
76
|
+
}
|
|
77
|
+
const alon = theta / sn + olon
|
|
78
|
+
return { latitude: alat * RADDEG, longitude: alon * RADDEG }
|
|
79
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 기상청(KMA) 도메인 전용 태스크 그룹.
|
|
3
|
+
*
|
|
4
|
+
* 새 KMA 전용 태스크(예: AWS 자동수집, 특보 알림 등) 추가 시:
|
|
5
|
+
* - 본 폴더에 `<variant>.ts` 파일 추가 → TaskRegistry 등록
|
|
6
|
+
* - 본 index 에 `import './<variant>'` 한 줄 추가
|
|
7
|
+
*
|
|
8
|
+
* 단순 조회는 범용 `data-go-kr-call` + 스펙 레지스트리로 충분하므로,
|
|
9
|
+
* 이 폴더에는 입력 폼·헬퍼·도메인 가공이 가치 있는 태스크만 배치.
|
|
10
|
+
*/
|
|
11
|
+
import './short-term-forecast'
|
|
12
|
+
|
|
13
|
+
export { latLonToGrid, gridToLatLon } from './grid-converter'
|
|
14
|
+
export type { LatLon, KmaGrid } from './grid-converter'
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ConnectionManager, Context, TaskRegistry, evaluateTemplate } from '@things-factory/integration-base'
|
|
2
|
+
|
|
3
|
+
import { DataGoKrConnectionInstance } from '../../connector/data-go-kr-connector'
|
|
4
|
+
import { KmaShortTermForecastService } from '../../spec/weather/short-term-forecast'
|
|
5
|
+
import { latLonToGrid } from './grid-converter'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 기상청 단기예보 (`getVilageFcst`) 조회 태스크.
|
|
9
|
+
*
|
|
10
|
+
* 입력 폼이 명시된 전용 태스크. 위경도 입력 시 KMA 격자로 자동 변환.
|
|
11
|
+
*
|
|
12
|
+
* 입력 (둘 중 하나):
|
|
13
|
+
* - `nx`, `ny` : KMA 5km 격자좌표
|
|
14
|
+
* - `latitude`, `longitude`: 위경도 (자동으로 nx/ny 로 변환)
|
|
15
|
+
*
|
|
16
|
+
* 추가:
|
|
17
|
+
* - `baseDate` : 발표일자 YYYYMMDD (필수)
|
|
18
|
+
* - `baseTime` : 발표시각 HHMM (필수, 0200/0500/0800/1100/1400/1700/2000/2300)
|
|
19
|
+
* - `numOfRows` : 페이지당 결과수 (기본 1000 — 3일치 카테고리×시점 합계 약 800+)
|
|
20
|
+
*
|
|
21
|
+
* 다른 단기예보 오퍼레이션(getUltraSrtFcst, getUltraSrtNcst) 은 동일 서비스 안에 있으므로
|
|
22
|
+
* 범용 `data-go-kr-call` 로 호출 권장.
|
|
23
|
+
*/
|
|
24
|
+
async function ShortTermForecastTask(step, context: Context): Promise<{ data: { items: any[] } }> {
|
|
25
|
+
const { connection: connectionName, params } = step
|
|
26
|
+
const { domain, user, data, variables, lng, logger } = context
|
|
27
|
+
|
|
28
|
+
const instance: DataGoKrConnectionInstance = await ConnectionManager.getConnectionInstanceByName(
|
|
29
|
+
domain,
|
|
30
|
+
connectionName
|
|
31
|
+
)
|
|
32
|
+
if (!instance?.client) {
|
|
33
|
+
throw new Error(`data-go-kr connection '${connectionName}' is not established.`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const scope = { domain, user, lng, data, variables, console }
|
|
37
|
+
const evalString = (value: string | undefined) => {
|
|
38
|
+
if (value === undefined || value === null || value === '') return undefined
|
|
39
|
+
try {
|
|
40
|
+
return evaluateTemplate(String(value), scope)
|
|
41
|
+
} catch (err) {
|
|
42
|
+
throw new Error(`weather-short-term-forecast: failed to evaluate parameter: ${(err as Error).message}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const evalNumber = (value: any): number | undefined => {
|
|
46
|
+
const s = evalString(value as string | undefined)
|
|
47
|
+
if (s === undefined) return undefined
|
|
48
|
+
const n = Number(s)
|
|
49
|
+
return Number.isFinite(n) ? n : undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const baseDate = evalString(params.baseDate)
|
|
53
|
+
const baseTime = evalString(params.baseTime)
|
|
54
|
+
if (!baseDate) throw new Error('weather-short-term-forecast: baseDate is required')
|
|
55
|
+
if (!baseTime) throw new Error('weather-short-term-forecast: baseTime is required')
|
|
56
|
+
|
|
57
|
+
const explicitNx = evalNumber(params.nx)
|
|
58
|
+
const explicitNy = evalNumber(params.ny)
|
|
59
|
+
const lat = evalNumber(params.latitude)
|
|
60
|
+
const lon = evalNumber(params.longitude)
|
|
61
|
+
|
|
62
|
+
let nx: number
|
|
63
|
+
let ny: number
|
|
64
|
+
if (explicitNx !== undefined && explicitNy !== undefined) {
|
|
65
|
+
nx = explicitNx
|
|
66
|
+
ny = explicitNy
|
|
67
|
+
} else if (lat !== undefined && lon !== undefined) {
|
|
68
|
+
const grid = latLonToGrid({ latitude: lat, longitude: lon })
|
|
69
|
+
nx = grid.nx
|
|
70
|
+
ny = grid.ny
|
|
71
|
+
logger?.info?.(`[data-go-kr] latLonToGrid lat=${lat} lon=${lon} → nx=${nx} ny=${ny}`)
|
|
72
|
+
} else {
|
|
73
|
+
throw new Error(
|
|
74
|
+
'weather-short-term-forecast: provide either (nx, ny) or (latitude, longitude). 둘 다 비어있음.'
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const servicePath = `${KmaShortTermForecastService.serviceId}/getVilageFcst`
|
|
79
|
+
const queryParams = {
|
|
80
|
+
base_date: baseDate,
|
|
81
|
+
base_time: baseTime,
|
|
82
|
+
nx,
|
|
83
|
+
ny,
|
|
84
|
+
numOfRows: evalNumber(params.numOfRows) ?? 1000,
|
|
85
|
+
pageNo: evalNumber(params.pageNo) ?? 1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
logger?.info?.(`[data-go-kr] ${servicePath} params=${JSON.stringify(queryParams)}`)
|
|
89
|
+
|
|
90
|
+
const items = await instance.client.getItems<any>(servicePath, queryParams)
|
|
91
|
+
|
|
92
|
+
return { data: { items } }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
ShortTermForecastTask.parameterSpec = [
|
|
96
|
+
{ type: 'string', name: 'baseDate', label: 'label.base-date', placeholder: '예) 20260425' },
|
|
97
|
+
{ type: 'string', name: 'baseTime', label: 'label.base-time', placeholder: '예) 1100' },
|
|
98
|
+
{ type: 'string', name: 'nx', label: 'label.nx', placeholder: '예) 60' },
|
|
99
|
+
{ type: 'string', name: 'ny', label: 'label.ny', placeholder: '예) 127' },
|
|
100
|
+
{ type: 'string', name: 'latitude', label: 'label.latitude', placeholder: '위경도 입력 시 nx/ny 자동 계산' },
|
|
101
|
+
{ type: 'string', name: 'longitude', label: 'label.longitude' },
|
|
102
|
+
{ type: 'string', name: 'numOfRows', label: 'label.num-of-rows', placeholder: '기본 1000' },
|
|
103
|
+
{ type: 'string', name: 'pageNo', label: 'label.page-no', placeholder: '기본 1' }
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
ShortTermForecastTask.help = 'integration/task/weather-short-term-forecast'
|
|
107
|
+
|
|
108
|
+
TaskRegistry.registerTaskHandler('data-go-kr-weather-short-term-forecast', ShortTermForecastTask)
|
|
109
|
+
|
|
110
|
+
export default ShortTermForecastTask
|
|
@@ -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
|
+
}
|
package/server/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import './engine'
|
|
2
|
+
|
|
3
|
+
export * from './engine/types'
|
|
4
|
+
export * from './engine/spec'
|
|
5
|
+
export { DataGoKrClient } from './engine/connector/data-go-kr-client'
|
|
6
|
+
export { latLonToGrid, gridToLatLon } from './engine/task/weather'
|
|
7
|
+
export type { LatLon, KmaGrid } from './engine/task/weather'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label.service-key": "Service Key (data.go.kr)",
|
|
3
|
+
"label.operation-ref": "Operation Reference",
|
|
4
|
+
"label.operation-params": "Operation Parameters",
|
|
5
|
+
"label.sigungu-cd": "Sigungu Code",
|
|
6
|
+
"label.bjdong-cd": "Beopjeongdong Code",
|
|
7
|
+
"label.plat-gb-cd": "Plot Type",
|
|
8
|
+
"label.bun": "Parcel Bun (main)",
|
|
9
|
+
"label.ji": "Parcel Ji (sub)",
|
|
10
|
+
"label.base-date": "Base Date (YYYYMMDD)",
|
|
11
|
+
"label.base-time": "Base Time (HHMM)",
|
|
12
|
+
"label.nx": "Grid X (KMA 5km)",
|
|
13
|
+
"label.ny": "Grid Y (KMA 5km)",
|
|
14
|
+
"label.latitude": "Latitude (auto-converts to grid if filled)",
|
|
15
|
+
"label.longitude": "Longitude",
|
|
16
|
+
"label.num-of-rows": "Results per page",
|
|
17
|
+
"label.page-no": "Page number"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label.service-key": "サービスキー (data.go.kr)",
|
|
3
|
+
"label.operation-ref": "オペレーション参照",
|
|
4
|
+
"label.operation-params": "オペレーションパラメータ",
|
|
5
|
+
"label.sigungu-cd": "市郡区コード",
|
|
6
|
+
"label.bjdong-cd": "法定洞コード",
|
|
7
|
+
"label.plat-gb-cd": "土地区分",
|
|
8
|
+
"label.bun": "本番",
|
|
9
|
+
"label.ji": "副番",
|
|
10
|
+
"label.base-date": "発表日付 (YYYYMMDD)",
|
|
11
|
+
"label.base-time": "発表時刻 (HHMM)",
|
|
12
|
+
"label.nx": "格子 X (KMA 5km)",
|
|
13
|
+
"label.ny": "格子 Y (KMA 5km)",
|
|
14
|
+
"label.latitude": "緯度 (緯経度入力時は自動格子変換)",
|
|
15
|
+
"label.longitude": "経度",
|
|
16
|
+
"label.num-of-rows": "ページあたり件数",
|
|
17
|
+
"label.page-no": "ページ番号"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label.service-key": "서비스키 (data.go.kr)",
|
|
3
|
+
"label.operation-ref": "오퍼레이션 참조",
|
|
4
|
+
"label.operation-params": "오퍼레이션 파라미터",
|
|
5
|
+
"label.sigungu-cd": "시군구코드",
|
|
6
|
+
"label.bjdong-cd": "법정동코드",
|
|
7
|
+
"label.plat-gb-cd": "대지 구분",
|
|
8
|
+
"label.bun": "본번",
|
|
9
|
+
"label.ji": "부번",
|
|
10
|
+
"label.base-date": "발표일자 (YYYYMMDD)",
|
|
11
|
+
"label.base-time": "발표시각 (HHMM)",
|
|
12
|
+
"label.nx": "격자 X (KMA 5km)",
|
|
13
|
+
"label.ny": "격자 Y (KMA 5km)",
|
|
14
|
+
"label.latitude": "위도 (위경도 입력 시 자동 격자 변환)",
|
|
15
|
+
"label.longitude": "경도",
|
|
16
|
+
"label.num-of-rows": "페이지당 결과수",
|
|
17
|
+
"label.page-no": "페이지 번호"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label.service-key": "Service Key (data.go.kr)",
|
|
3
|
+
"label.operation-ref": "Rujukan Operasi",
|
|
4
|
+
"label.operation-params": "Parameter Operasi",
|
|
5
|
+
"label.sigungu-cd": "Kod Sigungu",
|
|
6
|
+
"label.bjdong-cd": "Kod Beopjeongdong",
|
|
7
|
+
"label.plat-gb-cd": "Jenis Lot",
|
|
8
|
+
"label.bun": "No. Bun",
|
|
9
|
+
"label.ji": "No. Ji",
|
|
10
|
+
"label.base-date": "Tarikh Asas (YYYYMMDD)",
|
|
11
|
+
"label.base-time": "Masa Asas (HHMM)",
|
|
12
|
+
"label.nx": "Grid X (KMA 5km)",
|
|
13
|
+
"label.ny": "Grid Y (KMA 5km)",
|
|
14
|
+
"label.latitude": "Latitud (auto-tukar ke grid bila diisi)",
|
|
15
|
+
"label.longitude": "Longitud",
|
|
16
|
+
"label.num-of-rows": "Bilangan rekod per muka",
|
|
17
|
+
"label.page-no": "No. muka surat"
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label.service-key": "服务密钥 (data.go.kr)",
|
|
3
|
+
"label.operation-ref": "操作引用",
|
|
4
|
+
"label.operation-params": "操作参数",
|
|
5
|
+
"label.sigungu-cd": "市郡区代码",
|
|
6
|
+
"label.bjdong-cd": "法定洞代码",
|
|
7
|
+
"label.plat-gb-cd": "用地类型",
|
|
8
|
+
"label.bun": "本番",
|
|
9
|
+
"label.ji": "副番",
|
|
10
|
+
"label.base-date": "发布日期 (YYYYMMDD)",
|
|
11
|
+
"label.base-time": "发布时刻 (HHMM)",
|
|
12
|
+
"label.nx": "网格 X (KMA 5km)",
|
|
13
|
+
"label.ny": "网格 Y (KMA 5km)",
|
|
14
|
+
"label.latitude": "纬度 (输入经纬度自动转换为网格)",
|
|
15
|
+
"label.longitude": "经度",
|
|
16
|
+
"label.num-of-rows": "每页条数",
|
|
17
|
+
"label.page-no": "页码"
|
|
18
|
+
}
|