@things-factory/integration-juso 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 (48) hide show
  1. package/README.md +63 -0
  2. package/dist-server/engine/connector/index.d.ts +1 -0
  3. package/dist-server/engine/connector/index.js +4 -0
  4. package/dist-server/engine/connector/index.js.map +1 -0
  5. package/dist-server/engine/connector/juso-client.d.ts +20 -0
  6. package/dist-server/engine/connector/juso-client.js +74 -0
  7. package/dist-server/engine/connector/juso-client.js.map +1 -0
  8. package/dist-server/engine/connector/juso-connector.d.ts +26 -0
  9. package/dist-server/engine/connector/juso-connector.js +50 -0
  10. package/dist-server/engine/connector/juso-connector.js.map +1 -0
  11. package/dist-server/engine/index.d.ts +2 -0
  12. package/dist-server/engine/index.js +5 -0
  13. package/dist-server/engine/index.js.map +1 -0
  14. package/dist-server/engine/task/address/index.d.ts +7 -0
  15. package/dist-server/engine/task/address/index.js +10 -0
  16. package/dist-server/engine/task/address/index.js.map +1 -0
  17. package/dist-server/engine/task/address/resolve.d.ts +54 -0
  18. package/dist-server/engine/task/address/resolve.js +111 -0
  19. package/dist-server/engine/task/address/resolve.js.map +1 -0
  20. package/dist-server/engine/task/index.d.ts +6 -0
  21. package/dist-server/engine/task/index.js +9 -0
  22. package/dist-server/engine/task/index.js.map +1 -0
  23. package/dist-server/engine/types.d.ts +86 -0
  24. package/dist-server/engine/types.js +10 -0
  25. package/dist-server/engine/types.js.map +1 -0
  26. package/dist-server/index.d.ts +4 -0
  27. package/dist-server/index.js +11 -0
  28. package/dist-server/index.js.map +1 -0
  29. package/dist-server/tsconfig.tsbuildinfo +1 -0
  30. package/helps/integration/connector/juso-connector.md +28 -0
  31. package/helps/integration/task/juso-resolve-address.md +110 -0
  32. package/package.json +28 -0
  33. package/server/engine/connector/index.ts +1 -0
  34. package/server/engine/connector/juso-client.ts +98 -0
  35. package/server/engine/connector/juso-connector.ts +66 -0
  36. package/server/engine/index.ts +2 -0
  37. package/server/engine/task/address/index.ts +7 -0
  38. package/server/engine/task/address/resolve.ts +133 -0
  39. package/server/engine/task/index.ts +6 -0
  40. package/server/engine/types.ts +86 -0
  41. package/server/index.ts +5 -0
  42. package/things-factory.config.js +1 -0
  43. package/translations/en.json +5 -0
  44. package/translations/ja.json +5 -0
  45. package/translations/ko.json +5 -0
  46. package/translations/ms.json +5 -0
  47. package/translations/zh.json +5 -0
  48. package/tsconfig.json +10 -0
@@ -0,0 +1,28 @@
1
+ # juso Connector
2
+
3
+ 행정안전부 **도로명주소 안내시스템 (juso.go.kr)** Open API 커넥터.
4
+
5
+ - **운영주체**: 행정안전부
6
+ - **API 호스트**: `https://business.juso.go.kr`
7
+ - **인증**: 발급받은 `confmKey` (승인키) 를 쿼리 파라미터로 전달
8
+ - **비용**: 무료 (활용신청 후 승인)
9
+
10
+ ## Connection 필드
11
+
12
+ | 필드 | 필수 | 설명 |
13
+ | --- | --- | --- |
14
+ | `name` | O | Connection 이름 |
15
+ | `endpoint` | △ | API 오리진. 비우면 기본 `https://business.juso.go.kr`. 모의 서버 라우팅 시에만 입력 |
16
+ | `confmKey` (params) | O | juso.go.kr 활용신청 후 발급된 승인키 (도메인 시크릿 권장) |
17
+
18
+ ## 관련 태스크
19
+
20
+ - [`juso-resolve-address`](../task/juso-resolve-address.md) — 주소 → PNU + admCd + 우편번호 통합 변환
21
+
22
+ ## 활용신청 (요약)
23
+
24
+ 1. [https://business.juso.go.kr](https://business.juso.go.kr) 접속 → 회원가입
25
+ 2. **OPEN API → 신청** → 사용 목적·서비스 정보 입력 (자동 승인 ~ 1영업일)
26
+ 3. 마이페이지 → **승인키 (confmKey)** 복사 → Connection 의 `confmKey` 에 붙여넣기
27
+
28
+ > juso.go.kr 의 승인키는 data.go.kr ServiceKey 와 **별개의 키 시스템** 입니다. 같은 사용자도 서비스별로 따로 신청해야 합니다.
@@ -0,0 +1,110 @@
1
+ # juso-resolve-address
2
+
3
+ 도로명/지번 주소 문자열을 받아 한국 부동산·행정 API 에서 공통으로 쓰이는 표준 식별자 (PNU 5요소 + 법정동코드 + 우편번호) 로 변환하는 태스크.
4
+
5
+ ## 입력
6
+
7
+ | 이름 | 필수 | 설명 | 예시 |
8
+ | --- | --- | --- | --- |
9
+ | `query` | O | 도로명 또는 지번 주소 | `서울특별시 강남구 테헤란로 223` 또는 `서울특별시 강남구 역삼동 823` |
10
+ | `pickFirst` | △ | 다중 매칭 시 처리. `true`(기본) 첫 결과 자동 선택, `false` 다중 매칭 시 에러 | `true` |
11
+
12
+ ### 데이터 바인딩
13
+
14
+ `query` 등 모든 필드는 **JS 템플릿 리터럴 `${...}`** 표현식 지원 (Things-Factory `evaluateTemplate` 표준):
15
+
16
+ ```
17
+ query: ${data.project.address}
18
+ ```
19
+
20
+ `{{...}}` 가 아닌 `${...}` 입니다. 임의 JS 표현식도 가능 (`${data.foo + '-' + variables.suffix}` 등).
21
+
22
+ ## 출력 (`data`)
23
+
24
+ ```jsonc
25
+ {
26
+ "address": {
27
+ "road": "서울특별시 강남구 테헤란로 223",
28
+ "jibun": "서울특별시 강남구 역삼동 823",
29
+ "buildingName": "한국타이어빌딩",
30
+ "siNm": "서울특별시",
31
+ "sggNm": "강남구",
32
+ "emdNm": "역삼동"
33
+ },
34
+ "pnu": {
35
+ "sigunguCd": "11680",
36
+ "bjdongCd": "10300",
37
+ "platGbCd": "0", // '1' = 산 지번
38
+ "bun": "823",
39
+ "ji": "0"
40
+ },
41
+ "admCd": "1168010300", // 법정동코드 10자리
42
+ "bdMgtSn": "1168010300100823000000001",
43
+ "rnMgtSn": "116803122023",
44
+ "zipNo": "06168",
45
+ "raw": { /* juso.go.kr 원본 응답 */ }
46
+ }
47
+ ```
48
+
49
+ > `coord` 필드는 본 태스크에서 제공하지 않습니다 (juso 응답이 WGS84 좌표를 직접 주지 않음). 위경도가 필요하면 별도 VWorld geocoder task 와 체이닝하거나, 동일 출력 형태의 `vworld-resolve-address` 태스크가 추가되면 그걸 사용.
50
+
51
+ ## 매핑 규칙
52
+
53
+ PNU 5요소는 juso 응답에서 다음과 같이 도출됩니다:
54
+
55
+ | PNU 필드 | juso 응답 |
56
+ | --- | --- |
57
+ | `sigunguCd` | `admCd[0..4]` |
58
+ | `bjdongCd` | `admCd[5..9]` |
59
+ | `platGbCd` | `mtYn === '1' ? '1' : '0'` |
60
+ | `bun` | `lnbrMnnm` |
61
+ | `ji` | `lnbrSlno` |
62
+
63
+ ## 시나리오 예시 — 건축물대장 조회 체인
64
+
65
+ ```jsonc
66
+ [
67
+ {
68
+ "name": "resolve",
69
+ "task": "juso-resolve-address",
70
+ "connection": "juso",
71
+ "params": { "query": "${data.project.address}" }
72
+ },
73
+ {
74
+ "name": "fetch-ledger",
75
+ "task": "data-go-kr-building-ledger",
76
+ "connection": "data-go-kr",
77
+ "params": {
78
+ "sigunguCd": "${data.pnu.sigunguCd}",
79
+ "bjdongCd": "${data.pnu.bjdongCd}",
80
+ "platGbCd": "${data.pnu.platGbCd}",
81
+ "bun": "${data.pnu.bun}",
82
+ "ji": "${data.pnu.ji}"
83
+ }
84
+ }
85
+ ]
86
+ ```
87
+
88
+ 이 패턴은 dkpi 의 KPI 산정 파이프라인에 그대로 적용됩니다. **현장 등록 1회** 시점에 resolve → 결과 PNU 를 Project 엔티티에 저장하면 이후 KPI 호출은 저장된 PNU 만 사용 (resolve 반복 호출 불필요).
89
+
90
+ ## 트러블슈팅
91
+
92
+ ### `no match for '<query>'`
93
+
94
+ - 도로명/지번 표기에 띄어쓰기 또는 도시명 누락이 있는 경우. 더 구체적으로 입력.
95
+ - 신축 단지 등 신규 주소가 아직 도로명주소 DB 에 등록되지 않은 경우 — 사용승인 후 며칠 지연될 수 있음.
96
+
97
+ ### 매칭 다중 발생
98
+
99
+ - 같은 동에 같은 도로명·번지가 여러 동(棟) 일 때 (예: 아파트 단지). 동/호 정보까지 query 에 포함하거나 `pickFirst=true` 로 자동 선택.
100
+
101
+ ### 좌표가 필요한 경우
102
+
103
+ 본 태스크는 좌표를 반환하지 않습니다. 위경도가 필요하면:
104
+ 1. VWorld geocoder task 와 체이닝 (별도 패키지 추가 시)
105
+ 2. juso `entX/entY` (TM 좌표계) 를 WGS84 로 변환하는 후속 task 추가
106
+
107
+ ## 관련
108
+
109
+ - [`juso-connector`](../connector/juso-connector.md) — Connection 설정
110
+ - [`data-go-kr-building-ledger`](../../../../integration-data-go-kr/helps/integration/task/building-ledger.md) — PNU → 건축물대장 표제부 (체이닝 후속 step)
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@things-factory/integration-juso",
3
+ "version": "10.0.0-beta.108",
4
+ "main": "dist-server/index.js",
5
+ "things-factory": true,
6
+ "author": "heartyoh <heartyoh@hatiolab.com>",
7
+ "description": "Korean address standardization (juso.go.kr) connector for Things-Factory integration engine. Resolves road-name/jibun addresses into PNU 5-element + admCd + coordinates.",
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "public",
11
+ "@things-factory:registry": "https://registry.npmjs.org"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/hatiolab/things-factory.git",
16
+ "directory": "packages/integration-juso"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc --p tsconfig.json",
20
+ "build:server": "npm run clean:server && tsc",
21
+ "clean:server": "rm -rf dist-server",
22
+ "clean": "npm run clean:server"
23
+ },
24
+ "dependencies": {
25
+ "@things-factory/integration-base": "^10.0.0-beta.108"
26
+ },
27
+ "gitHead": "7de47925ebd3f39dc232d4dbfa43f9687f3603c4"
28
+ }
@@ -0,0 +1 @@
1
+ import './juso-connector'
@@ -0,0 +1,98 @@
1
+ import { JusoItem, JusoResponse } from '../types'
2
+
3
+ /**
4
+ * juso.go.kr (도로명주소 안내시스템) Open API 호출 클라이언트.
5
+ *
6
+ * - 엔드포인트: `https://business.juso.go.kr/addrlink/addrLinkApi.do`
7
+ * - 인증: `confmKey` 쿼리 파라미터 (활용신청 후 발급)
8
+ * - 응답: JSON (기본은 XML 이라 `resultType=json` 필수)
9
+ */
10
+
11
+ const DEFAULT_ORIGIN = 'https://business.juso.go.kr'
12
+ const SEARCH_PATH = '/addrlink/addrLinkApi.do'
13
+
14
+ export interface JusoClientOptions {
15
+ confmKey: string
16
+ origin?: string
17
+ }
18
+
19
+ export interface JusoSearchOptions {
20
+ /** 검색어 (도로명주소 또는 지번주소). */
21
+ keyword: string
22
+ /** 페이지당 결과수 (기본 10). */
23
+ countPerPage?: number
24
+ /** 현재 페이지 (기본 1). */
25
+ currentPage?: number
26
+ }
27
+
28
+ export class JusoClient {
29
+ private readonly confmKey: string
30
+ private readonly origin: string
31
+
32
+ constructor(options: JusoClientOptions) {
33
+ if (!options.confmKey) {
34
+ throw new Error('JusoClient requires confmKey')
35
+ }
36
+ this.confmKey = options.confmKey.trim()
37
+ this.origin = resolveOrigin(options.origin)
38
+ }
39
+
40
+ /** 도로명/지번 주소 검색. 응답의 juso 배열을 반환 (없으면 빈 배열). */
41
+ async search(options: JusoSearchOptions): Promise<JusoItem[]> {
42
+ const url = new URL(`${this.origin}${SEARCH_PATH}`)
43
+ url.searchParams.set('confmKey', this.confmKey)
44
+ url.searchParams.set('resultType', 'json')
45
+ url.searchParams.set('keyword', options.keyword)
46
+ url.searchParams.set('currentPage', String(options.currentPage ?? 1))
47
+ url.searchParams.set('countPerPage', String(options.countPerPage ?? 10))
48
+
49
+ const response = await fetch(url.toString(), {
50
+ method: 'GET',
51
+ headers: {
52
+ 'User-Agent': '@things-factory/integration-juso',
53
+ Accept: 'application/json'
54
+ }
55
+ })
56
+ const raw = await response.text()
57
+
58
+ if (!response.ok) {
59
+ throw new Error(`juso.go.kr search failed (${response.status}): ${raw.slice(0, 300)}`)
60
+ }
61
+
62
+ let parsed: JusoResponse
63
+ try {
64
+ parsed = JSON.parse(raw) as JusoResponse
65
+ } catch (err) {
66
+ throw new Error(`juso.go.kr search returned non-JSON: ${raw.slice(0, 300)}`)
67
+ }
68
+
69
+ const common = parsed?.results?.common
70
+ if (!common) {
71
+ throw new Error('juso.go.kr response missing results.common')
72
+ }
73
+ if (common.errorCode !== '0') {
74
+ throw new Error(
75
+ `juso.go.kr error: code=${common.errorCode} msg=${common.errorMessage} keyword='${options.keyword}'`
76
+ )
77
+ }
78
+
79
+ return parsed.results.juso || []
80
+ }
81
+ }
82
+
83
+ function resolveOrigin(value: string | undefined): string {
84
+ if (!value || !value.trim()) return DEFAULT_ORIGIN
85
+ const trimmed = value.trim()
86
+ try {
87
+ const parsed = new URL(trimmed)
88
+ if (!parsed.protocol.startsWith('http')) {
89
+ throw new Error('protocol must be http or https')
90
+ }
91
+ return trimmed.replace(/\/$/, '')
92
+ } catch (err) {
93
+ throw new Error(
94
+ `Invalid Connection.endpoint value '${value}': must be a full URL (예: https://business.juso.go.kr) ` +
95
+ `or empty (기본값 ${DEFAULT_ORIGIN} 사용). Reason: ${(err as Error).message}`
96
+ )
97
+ }
98
+ }
@@ -0,0 +1,66 @@
1
+ import { ConnectionManager, Connector } from '@things-factory/integration-base'
2
+
3
+ import { JusoClient } from './juso-client'
4
+
5
+ /**
6
+ * juso.go.kr (행정안전부 도로명주소 안내시스템) Open API 커넥터.
7
+ *
8
+ * - 활용신청: https://business.juso.go.kr/addrlink/openApi/apiReqstRule.do
9
+ * - confmKey (승인키) 발급 후 본 커넥터 params 에 입력.
10
+ * - Connection 의 `endpoint` 필드는 API 오리진 오버라이드용 (비우면 기본 `https://business.juso.go.kr`).
11
+ */
12
+ export interface JusoConnectionInstance {
13
+ client: JusoClient
14
+ }
15
+
16
+ export class JusoConnector implements Connector {
17
+ async ready(connectionConfigs) {
18
+ await Promise.all(connectionConfigs.map(this.connect.bind(this)))
19
+ ConnectionManager.logger.info('juso-connector connections are ready')
20
+ }
21
+
22
+ async connect(connection) {
23
+ const { endpoint, params } = connection
24
+ try {
25
+ const client = new JusoClient({ confmKey: params.confmKey, origin: endpoint })
26
+ const instance: JusoConnectionInstance = { client }
27
+ ConnectionManager.addConnectionInstance(connection, instance)
28
+ ConnectionManager.logger.info(
29
+ `juso-connector connection(${connection.name}) is connected (endpoint: ${endpoint || '(default)'})`
30
+ )
31
+ } catch (ex) {
32
+ ConnectionManager.logger.error(`juso-connector connection(${connection.name}) failed`, ex)
33
+ throw ex
34
+ }
35
+ }
36
+
37
+ async disconnect(connection) {
38
+ ConnectionManager.removeConnectionInstance(connection)
39
+ ConnectionManager.logger.info(`juso-connector connection(${connection.name}) is disconnected`)
40
+ }
41
+
42
+ get parameterSpec() {
43
+ return [
44
+ {
45
+ type: 'secret',
46
+ name: 'confmKey',
47
+ label: 'label.confm-key',
48
+ useDomainAttribute: true
49
+ }
50
+ ]
51
+ }
52
+
53
+ get taskPrefixes() {
54
+ return ['juso']
55
+ }
56
+
57
+ get help() {
58
+ return 'integration/connector/juso-connector'
59
+ }
60
+
61
+ get description() {
62
+ return '도로명주소 안내시스템 (juso.go.kr) Connector'
63
+ }
64
+ }
65
+
66
+ ConnectionManager.registerConnector('juso-connector', new JusoConnector())
@@ -0,0 +1,2 @@
1
+ import './connector'
2
+ import './task'
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 주소 도메인 태스크 그룹.
3
+ *
4
+ * 새 변형(예: 좌표→주소 reverse, 우편번호 단독 검색 등) 추가 시 본 폴더에 파일 한 개 +
5
+ * 본 index 의 import 한 줄. 폴더 구조 자체가 확장 포인트.
6
+ */
7
+ import './resolve'
@@ -0,0 +1,133 @@
1
+ import { ConnectionManager, Context, TaskRegistry, evaluateTemplate } from '@things-factory/integration-base'
2
+
3
+ import { JusoConnectionInstance } from '../../connector/juso-connector'
4
+ import { JusoItem, ResolvedAddress } from '../../types'
5
+
6
+ /**
7
+ * 도로명/지번 주소 → 표준 식별자 (PNU 5요소 + 법정동코드 + 우편번호) 변환 태스크.
8
+ *
9
+ * 다른 백엔드 (VWorld 등) 가 이 task 와 **같은 출력 형태(`ResolvedAddress`)** 를 반환하면
10
+ * 소비자 시나리오는 백엔드 교체 시 매핑 변경이 필요 없도록 설계됨.
11
+ *
12
+ * 입력:
13
+ * - `query` : 검색할 주소 문자열 (도로명 또는 지번 모두 가능)
14
+ * - `pickFirst` : 다수 매칭 시 첫 결과 선택 (기본 true). false 면 결과가 둘 이상일 때 에러.
15
+ *
16
+ * 출력 (`data`):
17
+ * - `ResolvedAddress` (단건). 매칭 0건이면 에러.
18
+ */
19
+ async function JusoResolveAddressTask(step, context: Context): Promise<{ data: ResolvedAddress }> {
20
+ const { connection: connectionName, params } = step
21
+ const { domain, user, data, variables, lng, logger } = context
22
+
23
+ const instance: JusoConnectionInstance = await ConnectionManager.getConnectionInstanceByName(
24
+ domain,
25
+ connectionName
26
+ )
27
+ if (!instance?.client) {
28
+ throw new Error(`juso connection '${connectionName}' is not established.`)
29
+ }
30
+
31
+ const scope = { domain, user, lng, data, variables, console }
32
+ const evalString = (value: string | undefined) => {
33
+ if (value === undefined || value === null || value === '') return undefined
34
+ try {
35
+ return evaluateTemplate(String(value), scope)
36
+ } catch (err) {
37
+ throw new Error(`juso-resolve-address: failed to evaluate parameter: ${(err as Error).message}`)
38
+ }
39
+ }
40
+
41
+ const query = evalString(params.query)
42
+ if (!query) {
43
+ throw new Error('juso-resolve-address: query is required (address string).')
44
+ }
45
+
46
+ const pickFirst = params.pickFirst === undefined ? true : Boolean(params.pickFirst)
47
+
48
+ logger?.info?.(`[juso] search keyword='${query}'`)
49
+
50
+ const items = await instance.client.search({ keyword: query, countPerPage: pickFirst ? 1 : 10 })
51
+
52
+ if (items.length === 0) {
53
+ throw new Error(`juso-resolve-address: no match for '${query}'. 도로명/지번 표기가 정확한지 확인.`)
54
+ }
55
+ if (!pickFirst && items.length > 1) {
56
+ throw new Error(
57
+ `juso-resolve-address: '${query}' 매칭 ${items.length}건. pickFirst=true 로 강제 선택 또는 query 더 구체적으로 입력.`
58
+ )
59
+ }
60
+
61
+ const resolved = mapToResolvedAddress(items[0])
62
+
63
+ logger?.info?.(
64
+ `[juso] resolved → admCd=${resolved.admCd} pnu=${resolved.pnu.sigunguCd}-${resolved.pnu.bjdongCd}-${resolved.pnu.platGbCd}-${resolved.pnu.bun}-${resolved.pnu.ji}`
65
+ )
66
+
67
+ return { data: resolved }
68
+ }
69
+
70
+ /**
71
+ * juso 단일 결과 → 표준 ResolvedAddress.
72
+ *
73
+ * 매핑 규칙:
74
+ * - sigunguCd = admCd[0..4], bjdongCd = admCd[5..9]
75
+ * - platGbCd = mtYn === '1' ? '1' : '0' (산 지번 여부)
76
+ * - bun = lnbrMnnm, ji = lnbrSlno
77
+ * - 좌표는 juso 응답에 없으므로 undefined
78
+ */
79
+ export function mapToResolvedAddress(item: JusoItem): ResolvedAddress {
80
+ const admCd = (item.admCd || '').padEnd(10, '0').slice(0, 10)
81
+ const sigunguCd = admCd.slice(0, 5)
82
+ const bjdongCd = admCd.slice(5, 10)
83
+ const platGbCd = item.mtYn === '1' ? '1' : '0'
84
+ const bun = String(item.lnbrMnnm ?? '').replace(/^0+/, '') || '0'
85
+ const ji = String(item.lnbrSlno ?? '').replace(/^0+/, '') || '0'
86
+
87
+ return {
88
+ address: {
89
+ road: item.roadAddrPart1 || item.roadAddr,
90
+ jibun: item.jibunAddr,
91
+ buildingName: item.bdNm || undefined,
92
+ siNm: item.siNm,
93
+ sggNm: item.sggNm,
94
+ emdNm: item.emdNm
95
+ },
96
+ pnu: { sigunguCd, bjdongCd, platGbCd, bun, ji },
97
+ admCd,
98
+ bdMgtSn: item.bdMgtSn,
99
+ rnMgtSn: item.rnMgtSn,
100
+ zipNo: item.zipNo,
101
+ raw: item
102
+ }
103
+ }
104
+
105
+ JusoResolveAddressTask.parameterSpec = [
106
+ {
107
+ type: 'string',
108
+ name: 'query',
109
+ label: 'label.address-query',
110
+ placeholder: '예) 서울특별시 강남구 테헤란로 223',
111
+ description:
112
+ '검색할 주소 문자열 (도로명/지번). 도메인 EnvVar `Step::<scenario>::<step>::query` 로 ' +
113
+ '프로젝트별 주소 override 가능. 시나리오 변수도 사용 가능 (예: `${variables.address}`).',
114
+ useDomainAttribute: true
115
+ },
116
+ {
117
+ type: 'select',
118
+ name: 'pickFirst',
119
+ label: 'label.pick-first',
120
+ property: {
121
+ options: [
122
+ { value: 'true', display: '첫 결과 자동 선택' },
123
+ { value: 'false', display: '다중 매칭 시 에러' }
124
+ ]
125
+ }
126
+ }
127
+ ]
128
+
129
+ JusoResolveAddressTask.help = 'integration/task/juso-resolve-address'
130
+
131
+ TaskRegistry.registerTaskHandler('juso-resolve-address', JusoResolveAddressTask)
132
+
133
+ export default JusoResolveAddressTask
@@ -0,0 +1,6 @@
1
+ /**
2
+ * juso 도메인 태스크 배럴.
3
+ *
4
+ * 새 도메인 추가 시: 폴더 + 본 index 의 import 한 줄.
5
+ */
6
+ import './address'
@@ -0,0 +1,86 @@
1
+ /**
2
+ * juso.go.kr (도로명주소 안내시스템) Open API 응답 타입.
3
+ *
4
+ * 본 패키지는 행정안전부 도로명주소조회 API 의 결과를 받아 우리 표준 결과 형태로
5
+ * 매핑한다. 다른 주소 resolver (예: VWorld) 와 호환되는 통일된 출력 형태를 사용해
6
+ * 소비자가 백엔드를 바꿔 끼울 수 있도록 한다.
7
+ */
8
+
9
+ /** juso.go.kr 도로명주소 단일 항목 (응답의 results.juso 배열 원소). */
10
+ export interface JusoItem {
11
+ roadAddr: string
12
+ roadAddrPart1: string
13
+ roadAddrPart2?: string
14
+ jibunAddr: string
15
+ engAddr?: string
16
+ zipNo?: string
17
+ admCd: string /* 법정동코드 10자리 = sigunguCd 5 + bjdongCd 5 */
18
+ rnMgtSn?: string
19
+ bdMgtSn?: string /* 건물관리번호 25자리 (PNU 정보 인코딩됨) */
20
+ bdNm?: string /* 건물명 */
21
+ bdKdcd?: string
22
+ siNm?: string
23
+ sggNm?: string
24
+ emdNm?: string
25
+ liNm?: string
26
+ rn?: string
27
+ udrtYn?: string
28
+ buldMnnm?: string /* 도로명 건물 본번 */
29
+ buldSlno?: string
30
+ mtYn?: string /* 산 여부 ('1' 산, '0' 일반) */
31
+ lnbrMnnm: string /* 지번 본번 */
32
+ lnbrSlno: string /* 지번 부번 */
33
+ emdNo?: string
34
+ [key: string]: any
35
+ }
36
+
37
+ /** juso.go.kr 응답 봉투. */
38
+ export interface JusoResponse {
39
+ results: {
40
+ common: {
41
+ errorMessage: string
42
+ countPerPage: string
43
+ totalCount: string
44
+ errorCode: string
45
+ currentPage: string
46
+ }
47
+ juso: JusoItem[] | null
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 주소 resolver 의 통일 출력 형태.
53
+ *
54
+ * 다른 백엔드 (juso, VWorld 등) 가 동일 형태로 반환해 소비자가 자유롭게 교체 가능.
55
+ * coord 는 백엔드에 따라 누락 가능 (juso 는 별도 변환 없이는 WGS84 좌표를 못 줌).
56
+ */
57
+ export interface ResolvedAddress {
58
+ address: {
59
+ road?: string
60
+ jibun?: string
61
+ buildingName?: string
62
+ siNm?: string
63
+ sggNm?: string
64
+ emdNm?: string
65
+ }
66
+ /** 건축물대장 등 부동산 API 호출용 PNU 5요소. */
67
+ pnu: {
68
+ sigunguCd: string
69
+ bjdongCd: string
70
+ platGbCd: string
71
+ bun: string
72
+ ji: string
73
+ }
74
+ /** 법정동코드 10자리. */
75
+ admCd: string
76
+ /** 건물관리번호 25자리. */
77
+ bdMgtSn?: string
78
+ /** 도로명관리번호. */
79
+ rnMgtSn?: string
80
+ /** 우편번호 5자리. */
81
+ zipNo?: string
82
+ /** WGS84 위경도 (백엔드가 줄 때만). */
83
+ coord?: { latitude: number; longitude: number }
84
+ /** 백엔드 원본 응답 (디버깅·확장용). */
85
+ raw?: unknown
86
+ }
@@ -0,0 +1,5 @@
1
+ import './engine'
2
+
3
+ export * from './engine/types'
4
+ export { JusoClient } from './engine/connector/juso-client'
5
+ export { mapToResolvedAddress } from './engine/task/address/resolve'
@@ -0,0 +1 @@
1
+ export default {}
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.confm-key": "Confirmation Key (juso.go.kr)",
3
+ "label.address-query": "Address to search",
4
+ "label.pick-first": "Multi-match behavior"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.confm-key": "承認キー (juso.go.kr)",
3
+ "label.address-query": "検索する住所",
4
+ "label.pick-first": "複数マッチ時の挙動"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.confm-key": "승인키 (juso.go.kr)",
3
+ "label.address-query": "검색할 주소",
4
+ "label.pick-first": "다중 매칭 시 처리"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.confm-key": "Confirmation Key (juso.go.kr)",
3
+ "label.address-query": "Alamat untuk dicari",
4
+ "label.pick-first": "Tingkah laku padanan berganda"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "label.confm-key": "认证密钥 (juso.go.kr)",
3
+ "label.address-query": "查询地址",
4
+ "label.pick-first": "多匹配处理"
5
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../tsconfig-base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist-server",
5
+ "baseUrl": "./",
6
+ "skipLibCheck": true
7
+ },
8
+ "include": ["./server/**/*"],
9
+ "exclude": ["**/*.spec.ts"]
10
+ }