@things-factory/integration-juso 10.0.0-beta.63
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 +63 -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/connector/juso-client.d.ts +20 -0
- package/dist-server/engine/connector/juso-client.js +74 -0
- package/dist-server/engine/connector/juso-client.js.map +1 -0
- package/dist-server/engine/connector/juso-connector.d.ts +26 -0
- package/dist-server/engine/connector/juso-connector.js +50 -0
- package/dist-server/engine/connector/juso-connector.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/task/address/index.d.ts +7 -0
- package/dist-server/engine/task/address/index.js +10 -0
- package/dist-server/engine/task/address/index.js.map +1 -0
- package/dist-server/engine/task/address/resolve.d.ts +50 -0
- package/dist-server/engine/task/address/resolve.js +103 -0
- package/dist-server/engine/task/address/resolve.js.map +1 -0
- package/dist-server/engine/task/index.d.ts +6 -0
- package/dist-server/engine/task/index.js +9 -0
- package/dist-server/engine/task/index.js.map +1 -0
- package/dist-server/engine/types.d.ts +86 -0
- package/dist-server/engine/types.js +10 -0
- package/dist-server/engine/types.js.map +1 -0
- package/dist-server/index.d.ts +4 -0
- package/dist-server/index.js +11 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/helps/integration/connector/juso-connector.md +28 -0
- package/helps/integration/task/juso-resolve-address.md +110 -0
- package/package.json +28 -0
- package/server/engine/connector/index.ts +1 -0
- package/server/engine/connector/juso-client.ts +98 -0
- package/server/engine/connector/juso-connector.ts +66 -0
- package/server/engine/index.ts +2 -0
- package/server/engine/task/address/index.ts +7 -0
- package/server/engine/task/address/resolve.ts +124 -0
- package/server/engine/task/index.ts +6 -0
- package/server/engine/types.ts +86 -0
- package/server/index.ts +5 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +5 -0
- package/translations/ja.json +5 -0
- package/translations/ko.json +5 -0
- package/translations/ms.json +5 -0
- package/translations/zh.json +5 -0
- package/tsconfig.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# @things-factory/integration-juso
|
|
2
|
+
|
|
3
|
+
행정안전부 **도로명주소 안내시스템 (juso.go.kr)** Open API 를 Things-Factory 통합 엔진에서 호출하기 위한 커넥터 + 태스크.
|
|
4
|
+
|
|
5
|
+
## 핵심 기능
|
|
6
|
+
|
|
7
|
+
도로명/지번 주소 문자열 → **PNU 5요소 + 법정동코드 + 우편번호** 변환. 한국 공공·부동산 API 들이 요구하는 표준 식별자를 한 번의 호출로 추출합니다.
|
|
8
|
+
|
|
9
|
+
## 제공 컴포넌트
|
|
10
|
+
|
|
11
|
+
### Connector
|
|
12
|
+
- `juso-connector` — confmKey 기반 클라이언트 (활용신청 후 무료 발급)
|
|
13
|
+
|
|
14
|
+
### Tasks
|
|
15
|
+
- `juso-resolve-address` — 주소 → 표준 식별자 + 행정구역명 통합 변환
|
|
16
|
+
|
|
17
|
+
### 출력 형태 (백엔드 호환)
|
|
18
|
+
|
|
19
|
+
```jsonc
|
|
20
|
+
{
|
|
21
|
+
"address": { "road": "...", "jibun": "...", "buildingName": "...", "siNm": "...", "sggNm": "...", "emdNm": "..." },
|
|
22
|
+
"pnu": { "sigunguCd": "...", "bjdongCd": "...", "platGbCd": "...", "bun": "...", "ji": "..." },
|
|
23
|
+
"admCd": "1168010300",
|
|
24
|
+
"bdMgtSn": "...",
|
|
25
|
+
"rnMgtSn": "...",
|
|
26
|
+
"zipNo": "06168",
|
|
27
|
+
"raw": { /* 원본 */ }
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
이 형태는 향후 다른 resolver(예: `vworld-resolve-address`) 가 추가될 때 동일하게 사용할 표준 출력입니다. 소비자는 백엔드를 바꿔도 매핑 변경이 필요 없습니다.
|
|
32
|
+
|
|
33
|
+
## 빠른 시작
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
1. https://business.juso.go.kr 가입 + OPEN API 활용신청 (~1영업일)
|
|
37
|
+
2. 마이페이지에서 confmKey 복사
|
|
38
|
+
3. Things-Factory 통합 UI → Connection 생성 (type=juso-connector)
|
|
39
|
+
4. Scenario → Step 추가 (juso-resolve-address)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
자세한 가이드는 [helps/integration/connector/juso-connector.md](./helps/integration/connector/juso-connector.md) 참조.
|
|
43
|
+
|
|
44
|
+
## 사용 예시 — 건축물대장 조회 체인
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
[juso-resolve-address] (주소 → PNU)
|
|
48
|
+
↓ ${data.pnu.*}
|
|
49
|
+
[data-go-kr-building-ledger] (PNU → 표제부)
|
|
50
|
+
↓ items
|
|
51
|
+
[data-mapper / 도메인 task] (4속성 → KPI 등)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
dkpi 같은 소비자에서는 **현장 등록 시 1회** resolve → 결과를 엔티티에 저장 → KPI 호출 시는 저장된 PNU 직접 사용. resolve 반복 호출 불필요.
|
|
55
|
+
|
|
56
|
+
## 향후 — VWorld 등 다른 resolver 와 호환
|
|
57
|
+
|
|
58
|
+
위경도까지 필요하면 별도 `@things-factory/integration-vworld` 패키지의 `vworld-resolve-address` 태스크 추가 예정 (동일 출력 형태). 소비자는 임포트만 바꾸면 됨.
|
|
59
|
+
|
|
60
|
+
## 레퍼런스
|
|
61
|
+
|
|
62
|
+
- [도로명주소 안내시스템 OPEN API](https://business.juso.go.kr/addrlink/openApi/apiReqstRule.do)
|
|
63
|
+
- 행정안전부 표준 행정구역코드 / 법정동코드 [code.go.kr](https://www.code.go.kr)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './juso-connector';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/connector/index.ts"],"names":[],"mappings":";;AAAA,4BAAyB","sourcesContent":["import './juso-connector'\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { JusoItem } from '../types';
|
|
2
|
+
export interface JusoClientOptions {
|
|
3
|
+
confmKey: string;
|
|
4
|
+
origin?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface JusoSearchOptions {
|
|
7
|
+
/** 검색어 (도로명주소 또는 지번주소). */
|
|
8
|
+
keyword: string;
|
|
9
|
+
/** 페이지당 결과수 (기본 10). */
|
|
10
|
+
countPerPage?: number;
|
|
11
|
+
/** 현재 페이지 (기본 1). */
|
|
12
|
+
currentPage?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare class JusoClient {
|
|
15
|
+
private readonly confmKey;
|
|
16
|
+
private readonly origin;
|
|
17
|
+
constructor(options: JusoClientOptions);
|
|
18
|
+
/** 도로명/지번 주소 검색. 응답의 juso 배열을 반환 (없으면 빈 배열). */
|
|
19
|
+
search(options: JusoSearchOptions): Promise<JusoItem[]>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JusoClient = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* juso.go.kr (도로명주소 안내시스템) Open API 호출 클라이언트.
|
|
6
|
+
*
|
|
7
|
+
* - 엔드포인트: `https://business.juso.go.kr/addrlink/addrLinkApi.do`
|
|
8
|
+
* - 인증: `confmKey` 쿼리 파라미터 (활용신청 후 발급)
|
|
9
|
+
* - 응답: JSON (기본은 XML 이라 `resultType=json` 필수)
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_ORIGIN = 'https://business.juso.go.kr';
|
|
12
|
+
const SEARCH_PATH = '/addrlink/addrLinkApi.do';
|
|
13
|
+
class JusoClient {
|
|
14
|
+
constructor(options) {
|
|
15
|
+
if (!options.confmKey) {
|
|
16
|
+
throw new Error('JusoClient requires confmKey');
|
|
17
|
+
}
|
|
18
|
+
this.confmKey = options.confmKey.trim();
|
|
19
|
+
this.origin = resolveOrigin(options.origin);
|
|
20
|
+
}
|
|
21
|
+
/** 도로명/지번 주소 검색. 응답의 juso 배열을 반환 (없으면 빈 배열). */
|
|
22
|
+
async search(options) {
|
|
23
|
+
const url = new URL(`${this.origin}${SEARCH_PATH}`);
|
|
24
|
+
url.searchParams.set('confmKey', this.confmKey);
|
|
25
|
+
url.searchParams.set('resultType', 'json');
|
|
26
|
+
url.searchParams.set('keyword', options.keyword);
|
|
27
|
+
url.searchParams.set('currentPage', String(options.currentPage ?? 1));
|
|
28
|
+
url.searchParams.set('countPerPage', String(options.countPerPage ?? 10));
|
|
29
|
+
const response = await fetch(url.toString(), {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
headers: {
|
|
32
|
+
'User-Agent': '@things-factory/integration-juso',
|
|
33
|
+
Accept: 'application/json'
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const raw = await response.text();
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw new Error(`juso.go.kr search failed (${response.status}): ${raw.slice(0, 300)}`);
|
|
39
|
+
}
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(raw);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
throw new Error(`juso.go.kr search returned non-JSON: ${raw.slice(0, 300)}`);
|
|
46
|
+
}
|
|
47
|
+
const common = parsed?.results?.common;
|
|
48
|
+
if (!common) {
|
|
49
|
+
throw new Error('juso.go.kr response missing results.common');
|
|
50
|
+
}
|
|
51
|
+
if (common.errorCode !== '0') {
|
|
52
|
+
throw new Error(`juso.go.kr error: code=${common.errorCode} msg=${common.errorMessage} keyword='${options.keyword}'`);
|
|
53
|
+
}
|
|
54
|
+
return parsed.results.juso || [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.JusoClient = JusoClient;
|
|
58
|
+
function resolveOrigin(value) {
|
|
59
|
+
if (!value || !value.trim())
|
|
60
|
+
return DEFAULT_ORIGIN;
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
try {
|
|
63
|
+
const parsed = new URL(trimmed);
|
|
64
|
+
if (!parsed.protocol.startsWith('http')) {
|
|
65
|
+
throw new Error('protocol must be http or https');
|
|
66
|
+
}
|
|
67
|
+
return trimmed.replace(/\/$/, '');
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
throw new Error(`Invalid Connection.endpoint value '${value}': must be a full URL (예: https://business.juso.go.kr) ` +
|
|
71
|
+
`or empty (기본값 ${DEFAULT_ORIGIN} 사용). Reason: ${err.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=juso-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juso-client.js","sourceRoot":"","sources":["../../../server/engine/connector/juso-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { JusoItem, JusoResponse } from '../types'\n\n/**\n * juso.go.kr (도로명주소 안내시스템) Open API 호출 클라이언트.\n *\n * - 엔드포인트: `https://business.juso.go.kr/addrlink/addrLinkApi.do`\n * - 인증: `confmKey` 쿼리 파라미터 (활용신청 후 발급)\n * - 응답: JSON (기본은 XML 이라 `resultType=json` 필수)\n */\n\nconst DEFAULT_ORIGIN = 'https://business.juso.go.kr'\nconst SEARCH_PATH = '/addrlink/addrLinkApi.do'\n\nexport interface JusoClientOptions {\n confmKey: string\n origin?: string\n}\n\nexport interface JusoSearchOptions {\n /** 검색어 (도로명주소 또는 지번주소). */\n keyword: string\n /** 페이지당 결과수 (기본 10). */\n countPerPage?: number\n /** 현재 페이지 (기본 1). */\n currentPage?: number\n}\n\nexport class JusoClient {\n private readonly confmKey: string\n private readonly origin: string\n\n constructor(options: JusoClientOptions) {\n if (!options.confmKey) {\n throw new Error('JusoClient requires confmKey')\n }\n this.confmKey = options.confmKey.trim()\n this.origin = resolveOrigin(options.origin)\n }\n\n /** 도로명/지번 주소 검색. 응답의 juso 배열을 반환 (없으면 빈 배열). */\n async search(options: JusoSearchOptions): Promise<JusoItem[]> {\n const url = new URL(`${this.origin}${SEARCH_PATH}`)\n url.searchParams.set('confmKey', this.confmKey)\n url.searchParams.set('resultType', 'json')\n url.searchParams.set('keyword', options.keyword)\n url.searchParams.set('currentPage', String(options.currentPage ?? 1))\n url.searchParams.set('countPerPage', String(options.countPerPage ?? 10))\n\n const response = await fetch(url.toString(), {\n method: 'GET',\n headers: {\n 'User-Agent': '@things-factory/integration-juso',\n Accept: 'application/json'\n }\n })\n const raw = await response.text()\n\n if (!response.ok) {\n throw new Error(`juso.go.kr search failed (${response.status}): ${raw.slice(0, 300)}`)\n }\n\n let parsed: JusoResponse\n try {\n parsed = JSON.parse(raw) as JusoResponse\n } catch (err) {\n throw new Error(`juso.go.kr search returned non-JSON: ${raw.slice(0, 300)}`)\n }\n\n const common = parsed?.results?.common\n if (!common) {\n throw new Error('juso.go.kr response missing results.common')\n }\n if (common.errorCode !== '0') {\n throw new Error(\n `juso.go.kr error: code=${common.errorCode} msg=${common.errorMessage} keyword='${options.keyword}'`\n )\n }\n\n return parsed.results.juso || []\n }\n}\n\nfunction resolveOrigin(value: string | undefined): string {\n if (!value || !value.trim()) return DEFAULT_ORIGIN\n const trimmed = value.trim()\n try {\n const parsed = new URL(trimmed)\n if (!parsed.protocol.startsWith('http')) {\n throw new Error('protocol must be http or https')\n }\n return trimmed.replace(/\\/$/, '')\n } catch (err) {\n throw new Error(\n `Invalid Connection.endpoint value '${value}': must be a full URL (예: https://business.juso.go.kr) ` +\n `or empty (기본값 ${DEFAULT_ORIGIN} 사용). Reason: ${(err as Error).message}`\n )\n }\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Connector } from '@things-factory/integration-base';
|
|
2
|
+
import { JusoClient } from './juso-client';
|
|
3
|
+
/**
|
|
4
|
+
* juso.go.kr (행정안전부 도로명주소 안내시스템) Open API 커넥터.
|
|
5
|
+
*
|
|
6
|
+
* - 활용신청: https://business.juso.go.kr/addrlink/openApi/apiReqstRule.do
|
|
7
|
+
* - confmKey (승인키) 발급 후 본 커넥터 params 에 입력.
|
|
8
|
+
* - Connection 의 `endpoint` 필드는 API 오리진 오버라이드용 (비우면 기본 `https://business.juso.go.kr`).
|
|
9
|
+
*/
|
|
10
|
+
export interface JusoConnectionInstance {
|
|
11
|
+
client: JusoClient;
|
|
12
|
+
}
|
|
13
|
+
export declare class JusoConnector implements Connector {
|
|
14
|
+
ready(connectionConfigs: any): Promise<void>;
|
|
15
|
+
connect(connection: any): Promise<void>;
|
|
16
|
+
disconnect(connection: any): Promise<void>;
|
|
17
|
+
get parameterSpec(): {
|
|
18
|
+
type: string;
|
|
19
|
+
name: string;
|
|
20
|
+
label: string;
|
|
21
|
+
useDomainAttribute: boolean;
|
|
22
|
+
}[];
|
|
23
|
+
get taskPrefixes(): string[];
|
|
24
|
+
get help(): string;
|
|
25
|
+
get description(): string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JusoConnector = void 0;
|
|
4
|
+
const integration_base_1 = require("@things-factory/integration-base");
|
|
5
|
+
const juso_client_1 = require("./juso-client");
|
|
6
|
+
class JusoConnector {
|
|
7
|
+
async ready(connectionConfigs) {
|
|
8
|
+
await Promise.all(connectionConfigs.map(this.connect.bind(this)));
|
|
9
|
+
integration_base_1.ConnectionManager.logger.info('juso-connector connections are ready');
|
|
10
|
+
}
|
|
11
|
+
async connect(connection) {
|
|
12
|
+
const { endpoint, params } = connection;
|
|
13
|
+
try {
|
|
14
|
+
const client = new juso_client_1.JusoClient({ confmKey: params.confmKey, origin: endpoint });
|
|
15
|
+
const instance = { client };
|
|
16
|
+
integration_base_1.ConnectionManager.addConnectionInstance(connection, instance);
|
|
17
|
+
integration_base_1.ConnectionManager.logger.info(`juso-connector connection(${connection.name}) is connected (endpoint: ${endpoint || '(default)'})`);
|
|
18
|
+
}
|
|
19
|
+
catch (ex) {
|
|
20
|
+
integration_base_1.ConnectionManager.logger.error(`juso-connector connection(${connection.name}) failed`, ex);
|
|
21
|
+
throw ex;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async disconnect(connection) {
|
|
25
|
+
integration_base_1.ConnectionManager.removeConnectionInstance(connection);
|
|
26
|
+
integration_base_1.ConnectionManager.logger.info(`juso-connector connection(${connection.name}) is disconnected`);
|
|
27
|
+
}
|
|
28
|
+
get parameterSpec() {
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
type: 'secret',
|
|
32
|
+
name: 'confmKey',
|
|
33
|
+
label: 'label.confm-key',
|
|
34
|
+
useDomainAttribute: true
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
get taskPrefixes() {
|
|
39
|
+
return ['juso'];
|
|
40
|
+
}
|
|
41
|
+
get help() {
|
|
42
|
+
return 'integration/connector/juso-connector';
|
|
43
|
+
}
|
|
44
|
+
get description() {
|
|
45
|
+
return '도로명주소 안내시스템 (juso.go.kr) Connector';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.JusoConnector = JusoConnector;
|
|
49
|
+
integration_base_1.ConnectionManager.registerConnector('juso-connector', new JusoConnector());
|
|
50
|
+
//# sourceMappingURL=juso-connector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"juso-connector.js","sourceRoot":"","sources":["../../../server/engine/connector/juso-connector.ts"],"names":[],"mappings":";;;AAAA,uEAA+E;AAE/E,+CAA0C;AAa1C,MAAa,aAAa;IACxB,KAAK,CAAC,KAAK,CAAC,iBAAiB;QAC3B,MAAM,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACjE,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAA;IACvE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,UAAU;QACtB,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,UAAU,CAAA;QACvC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,wBAAU,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC9E,MAAM,QAAQ,GAA2B,EAAE,MAAM,EAAE,CAAA;YACnD,oCAAiB,CAAC,qBAAqB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;YAC7D,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAC3B,6BAA6B,UAAU,CAAC,IAAI,6BAA6B,QAAQ,IAAI,WAAW,GAAG,CACpG,CAAA;QACH,CAAC;QAAC,OAAO,EAAE,EAAE,CAAC;YACZ,oCAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,UAAU,CAAC,IAAI,UAAU,EAAE,EAAE,CAAC,CAAA;YAC1F,MAAM,EAAE,CAAA;QACV,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,UAAU;QACzB,oCAAiB,CAAC,wBAAwB,CAAC,UAAU,CAAC,CAAA;QACtD,oCAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,UAAU,CAAC,IAAI,mBAAmB,CAAC,CAAA;IAChG,CAAC;IAED,IAAI,aAAa;QACf,OAAO;YACL;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,UAAU;gBAChB,KAAK,EAAE,iBAAiB;gBACxB,kBAAkB,EAAE,IAAI;aACzB;SACF,CAAA;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,CAAC,MAAM,CAAC,CAAA;IACjB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,sCAAsC,CAAA;IAC/C,CAAC;IAED,IAAI,WAAW;QACb,OAAO,oCAAoC,CAAA;IAC7C,CAAC;CACF;AAhDD,sCAgDC;AAED,oCAAiB,CAAC,iBAAiB,CAAC,gBAAgB,EAAE,IAAI,aAAa,EAAE,CAAC,CAAA","sourcesContent":["import { ConnectionManager, Connector } from '@things-factory/integration-base'\n\nimport { JusoClient } from './juso-client'\n\n/**\n * juso.go.kr (행정안전부 도로명주소 안내시스템) Open API 커넥터.\n *\n * - 활용신청: https://business.juso.go.kr/addrlink/openApi/apiReqstRule.do\n * - confmKey (승인키) 발급 후 본 커넥터 params 에 입력.\n * - Connection 의 `endpoint` 필드는 API 오리진 오버라이드용 (비우면 기본 `https://business.juso.go.kr`).\n */\nexport interface JusoConnectionInstance {\n client: JusoClient\n}\n\nexport class JusoConnector implements Connector {\n async ready(connectionConfigs) {\n await Promise.all(connectionConfigs.map(this.connect.bind(this)))\n ConnectionManager.logger.info('juso-connector connections are ready')\n }\n\n async connect(connection) {\n const { endpoint, params } = connection\n try {\n const client = new JusoClient({ confmKey: params.confmKey, origin: endpoint })\n const instance: JusoConnectionInstance = { client }\n ConnectionManager.addConnectionInstance(connection, instance)\n ConnectionManager.logger.info(\n `juso-connector connection(${connection.name}) is connected (endpoint: ${endpoint || '(default)'})`\n )\n } catch (ex) {\n ConnectionManager.logger.error(`juso-connector connection(${connection.name}) failed`, ex)\n throw ex\n }\n }\n\n async disconnect(connection) {\n ConnectionManager.removeConnectionInstance(connection)\n ConnectionManager.logger.info(`juso-connector connection(${connection.name}) is disconnected`)\n }\n\n get parameterSpec() {\n return [\n {\n type: 'secret',\n name: 'confmKey',\n label: 'label.confm-key',\n useDomainAttribute: true\n }\n ]\n }\n\n get taskPrefixes() {\n return ['juso']\n }\n\n get help() {\n return 'integration/connector/juso-connector'\n }\n\n get description() {\n return '도로명주소 안내시스템 (juso.go.kr) Connector'\n }\n}\n\nConnectionManager.registerConnector('juso-connector', new JusoConnector())\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/engine/index.ts"],"names":[],"mappings":";;AAAA,uBAAoB;AACpB,kBAAe","sourcesContent":["import './connector'\nimport './task'\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* 주소 도메인 태스크 그룹.
|
|
5
|
+
*
|
|
6
|
+
* 새 변형(예: 좌표→주소 reverse, 우편번호 단독 검색 등) 추가 시 본 폴더에 파일 한 개 +
|
|
7
|
+
* 본 index 의 import 한 줄. 폴더 구조 자체가 확장 포인트.
|
|
8
|
+
*/
|
|
9
|
+
require("./resolve");
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../server/engine/task/address/index.ts"],"names":[],"mappings":";;AAAA;;;;;GAKG;AACH,qBAAkB","sourcesContent":["/**\n * 주소 도메인 태스크 그룹.\n *\n * 새 변형(예: 좌표→주소 reverse, 우편번호 단독 검색 등) 추가 시 본 폴더에 파일 한 개 +\n * 본 index 의 import 한 줄. 폴더 구조 자체가 확장 포인트.\n */\nimport './resolve'\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Context } from '@things-factory/integration-base';
|
|
2
|
+
import { JusoItem, ResolvedAddress } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* 도로명/지번 주소 → 표준 식별자 (PNU 5요소 + 법정동코드 + 우편번호) 변환 태스크.
|
|
5
|
+
*
|
|
6
|
+
* 다른 백엔드 (VWorld 등) 가 이 task 와 **같은 출력 형태(`ResolvedAddress`)** 를 반환하면
|
|
7
|
+
* 소비자 시나리오는 백엔드 교체 시 매핑 변경이 필요 없도록 설계됨.
|
|
8
|
+
*
|
|
9
|
+
* 입력:
|
|
10
|
+
* - `query` : 검색할 주소 문자열 (도로명 또는 지번 모두 가능)
|
|
11
|
+
* - `pickFirst` : 다수 매칭 시 첫 결과 선택 (기본 true). false 면 결과가 둘 이상일 때 에러.
|
|
12
|
+
*
|
|
13
|
+
* 출력 (`data`):
|
|
14
|
+
* - `ResolvedAddress` (단건). 매칭 0건이면 에러.
|
|
15
|
+
*/
|
|
16
|
+
declare function JusoResolveAddressTask(step: any, context: Context): Promise<{
|
|
17
|
+
data: ResolvedAddress;
|
|
18
|
+
}>;
|
|
19
|
+
declare namespace JusoResolveAddressTask {
|
|
20
|
+
var parameterSpec: ({
|
|
21
|
+
type: string;
|
|
22
|
+
name: string;
|
|
23
|
+
label: string;
|
|
24
|
+
placeholder: string;
|
|
25
|
+
property?: undefined;
|
|
26
|
+
} | {
|
|
27
|
+
type: string;
|
|
28
|
+
name: string;
|
|
29
|
+
label: string;
|
|
30
|
+
property: {
|
|
31
|
+
options: {
|
|
32
|
+
value: string;
|
|
33
|
+
display: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
placeholder?: undefined;
|
|
37
|
+
})[];
|
|
38
|
+
var help: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* juso 단일 결과 → 표준 ResolvedAddress.
|
|
42
|
+
*
|
|
43
|
+
* 매핑 규칙:
|
|
44
|
+
* - sigunguCd = admCd[0..4], bjdongCd = admCd[5..9]
|
|
45
|
+
* - platGbCd = mtYn === '1' ? '1' : '0' (산 지번 여부)
|
|
46
|
+
* - bun = lnbrMnnm, ji = lnbrSlno
|
|
47
|
+
* - 좌표는 juso 응답에 없으므로 undefined
|
|
48
|
+
*/
|
|
49
|
+
export declare function mapToResolvedAddress(item: JusoItem): ResolvedAddress;
|
|
50
|
+
export default JusoResolveAddressTask;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapToResolvedAddress = mapToResolvedAddress;
|
|
4
|
+
const integration_base_1 = require("@things-factory/integration-base");
|
|
5
|
+
/**
|
|
6
|
+
* 도로명/지번 주소 → 표준 식별자 (PNU 5요소 + 법정동코드 + 우편번호) 변환 태스크.
|
|
7
|
+
*
|
|
8
|
+
* 다른 백엔드 (VWorld 등) 가 이 task 와 **같은 출력 형태(`ResolvedAddress`)** 를 반환하면
|
|
9
|
+
* 소비자 시나리오는 백엔드 교체 시 매핑 변경이 필요 없도록 설계됨.
|
|
10
|
+
*
|
|
11
|
+
* 입력:
|
|
12
|
+
* - `query` : 검색할 주소 문자열 (도로명 또는 지번 모두 가능)
|
|
13
|
+
* - `pickFirst` : 다수 매칭 시 첫 결과 선택 (기본 true). false 면 결과가 둘 이상일 때 에러.
|
|
14
|
+
*
|
|
15
|
+
* 출력 (`data`):
|
|
16
|
+
* - `ResolvedAddress` (단건). 매칭 0건이면 에러.
|
|
17
|
+
*/
|
|
18
|
+
async function JusoResolveAddressTask(step, context) {
|
|
19
|
+
const { connection: connectionName, params } = step;
|
|
20
|
+
const { domain, user, data, variables, lng, logger } = context;
|
|
21
|
+
const instance = await integration_base_1.ConnectionManager.getConnectionInstanceByName(domain, connectionName);
|
|
22
|
+
if (!instance?.client) {
|
|
23
|
+
throw new Error(`juso connection '${connectionName}' is not established.`);
|
|
24
|
+
}
|
|
25
|
+
const scope = { domain, user, lng, data, variables, console };
|
|
26
|
+
const evalString = (value) => {
|
|
27
|
+
if (value === undefined || value === null || value === '')
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
return (0, integration_base_1.evaluateTemplate)(String(value), scope);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
throw new Error(`juso-resolve-address: failed to evaluate parameter: ${err.message}`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const query = evalString(params.query);
|
|
37
|
+
if (!query) {
|
|
38
|
+
throw new Error('juso-resolve-address: query is required (address string).');
|
|
39
|
+
}
|
|
40
|
+
const pickFirst = params.pickFirst === undefined ? true : Boolean(params.pickFirst);
|
|
41
|
+
logger?.info?.(`[juso] search keyword='${query}'`);
|
|
42
|
+
const items = await instance.client.search({ keyword: query, countPerPage: pickFirst ? 1 : 10 });
|
|
43
|
+
if (items.length === 0) {
|
|
44
|
+
throw new Error(`juso-resolve-address: no match for '${query}'. 도로명/지번 표기가 정확한지 확인.`);
|
|
45
|
+
}
|
|
46
|
+
if (!pickFirst && items.length > 1) {
|
|
47
|
+
throw new Error(`juso-resolve-address: '${query}' 매칭 ${items.length}건. pickFirst=true 로 강제 선택 또는 query 더 구체적으로 입력.`);
|
|
48
|
+
}
|
|
49
|
+
const resolved = mapToResolvedAddress(items[0]);
|
|
50
|
+
logger?.info?.(`[juso] resolved → admCd=${resolved.admCd} pnu=${resolved.pnu.sigunguCd}-${resolved.pnu.bjdongCd}-${resolved.pnu.platGbCd}-${resolved.pnu.bun}-${resolved.pnu.ji}`);
|
|
51
|
+
return { data: resolved };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* juso 단일 결과 → 표준 ResolvedAddress.
|
|
55
|
+
*
|
|
56
|
+
* 매핑 규칙:
|
|
57
|
+
* - sigunguCd = admCd[0..4], bjdongCd = admCd[5..9]
|
|
58
|
+
* - platGbCd = mtYn === '1' ? '1' : '0' (산 지번 여부)
|
|
59
|
+
* - bun = lnbrMnnm, ji = lnbrSlno
|
|
60
|
+
* - 좌표는 juso 응답에 없으므로 undefined
|
|
61
|
+
*/
|
|
62
|
+
function mapToResolvedAddress(item) {
|
|
63
|
+
const admCd = (item.admCd || '').padEnd(10, '0').slice(0, 10);
|
|
64
|
+
const sigunguCd = admCd.slice(0, 5);
|
|
65
|
+
const bjdongCd = admCd.slice(5, 10);
|
|
66
|
+
const platGbCd = item.mtYn === '1' ? '1' : '0';
|
|
67
|
+
const bun = String(item.lnbrMnnm ?? '').replace(/^0+/, '') || '0';
|
|
68
|
+
const ji = String(item.lnbrSlno ?? '').replace(/^0+/, '') || '0';
|
|
69
|
+
return {
|
|
70
|
+
address: {
|
|
71
|
+
road: item.roadAddrPart1 || item.roadAddr,
|
|
72
|
+
jibun: item.jibunAddr,
|
|
73
|
+
buildingName: item.bdNm || undefined,
|
|
74
|
+
siNm: item.siNm,
|
|
75
|
+
sggNm: item.sggNm,
|
|
76
|
+
emdNm: item.emdNm
|
|
77
|
+
},
|
|
78
|
+
pnu: { sigunguCd, bjdongCd, platGbCd, bun, ji },
|
|
79
|
+
admCd,
|
|
80
|
+
bdMgtSn: item.bdMgtSn,
|
|
81
|
+
rnMgtSn: item.rnMgtSn,
|
|
82
|
+
zipNo: item.zipNo,
|
|
83
|
+
raw: item
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
JusoResolveAddressTask.parameterSpec = [
|
|
87
|
+
{ type: 'string', name: 'query', label: 'label.address-query', placeholder: '예) 서울특별시 강남구 테헤란로 223' },
|
|
88
|
+
{
|
|
89
|
+
type: 'select',
|
|
90
|
+
name: 'pickFirst',
|
|
91
|
+
label: 'label.pick-first',
|
|
92
|
+
property: {
|
|
93
|
+
options: [
|
|
94
|
+
{ value: 'true', display: '첫 결과 자동 선택' },
|
|
95
|
+
{ value: 'false', display: '다중 매칭 시 에러' }
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
];
|
|
100
|
+
JusoResolveAddressTask.help = 'integration/task/juso-resolve-address';
|
|
101
|
+
integration_base_1.TaskRegistry.registerTaskHandler('juso-resolve-address', JusoResolveAddressTask);
|
|
102
|
+
exports.default = JusoResolveAddressTask;
|
|
103
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../../server/engine/task/address/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import { ConnectionManager, Context, TaskRegistry, evaluateTemplate } from '@things-factory/integration-base'\n\nimport { JusoConnectionInstance } from '../../connector/juso-connector'\nimport { JusoItem, ResolvedAddress } from '../../types'\n\n/**\n * 도로명/지번 주소 → 표준 식별자 (PNU 5요소 + 법정동코드 + 우편번호) 변환 태스크.\n *\n * 다른 백엔드 (VWorld 등) 가 이 task 와 **같은 출력 형태(`ResolvedAddress`)** 를 반환하면\n * 소비자 시나리오는 백엔드 교체 시 매핑 변경이 필요 없도록 설계됨.\n *\n * 입력:\n * - `query` : 검색할 주소 문자열 (도로명 또는 지번 모두 가능)\n * - `pickFirst` : 다수 매칭 시 첫 결과 선택 (기본 true). false 면 결과가 둘 이상일 때 에러.\n *\n * 출력 (`data`):\n * - `ResolvedAddress` (단건). 매칭 0건이면 에러.\n */\nasync function JusoResolveAddressTask(step, context: Context): Promise<{ data: ResolvedAddress }> {\n const { connection: connectionName, params } = step\n const { domain, user, data, variables, lng, logger } = context\n\n const instance: JusoConnectionInstance = await ConnectionManager.getConnectionInstanceByName(\n domain,\n connectionName\n )\n if (!instance?.client) {\n throw new Error(`juso connection '${connectionName}' is not established.`)\n }\n\n const scope = { domain, user, lng, data, variables, console }\n const evalString = (value: string | undefined) => {\n if (value === undefined || value === null || value === '') return undefined\n try {\n return evaluateTemplate(String(value), scope)\n } catch (err) {\n throw new Error(`juso-resolve-address: failed to evaluate parameter: ${(err as Error).message}`)\n }\n }\n\n const query = evalString(params.query)\n if (!query) {\n throw new Error('juso-resolve-address: query is required (address string).')\n }\n\n const pickFirst = params.pickFirst === undefined ? true : Boolean(params.pickFirst)\n\n logger?.info?.(`[juso] search keyword='${query}'`)\n\n const items = await instance.client.search({ keyword: query, countPerPage: pickFirst ? 1 : 10 })\n\n if (items.length === 0) {\n throw new Error(`juso-resolve-address: no match for '${query}'. 도로명/지번 표기가 정확한지 확인.`)\n }\n if (!pickFirst && items.length > 1) {\n throw new Error(\n `juso-resolve-address: '${query}' 매칭 ${items.length}건. pickFirst=true 로 강제 선택 또는 query 더 구체적으로 입력.`\n )\n }\n\n const resolved = mapToResolvedAddress(items[0])\n\n logger?.info?.(\n `[juso] resolved → admCd=${resolved.admCd} pnu=${resolved.pnu.sigunguCd}-${resolved.pnu.bjdongCd}-${resolved.pnu.platGbCd}-${resolved.pnu.bun}-${resolved.pnu.ji}`\n )\n\n return { data: resolved }\n}\n\n/**\n * juso 단일 결과 → 표준 ResolvedAddress.\n *\n * 매핑 규칙:\n * - sigunguCd = admCd[0..4], bjdongCd = admCd[5..9]\n * - platGbCd = mtYn === '1' ? '1' : '0' (산 지번 여부)\n * - bun = lnbrMnnm, ji = lnbrSlno\n * - 좌표는 juso 응답에 없으므로 undefined\n */\nexport function mapToResolvedAddress(item: JusoItem): ResolvedAddress {\n const admCd = (item.admCd || '').padEnd(10, '0').slice(0, 10)\n const sigunguCd = admCd.slice(0, 5)\n const bjdongCd = admCd.slice(5, 10)\n const platGbCd = item.mtYn === '1' ? '1' : '0'\n const bun = String(item.lnbrMnnm ?? '').replace(/^0+/, '') || '0'\n const ji = String(item.lnbrSlno ?? '').replace(/^0+/, '') || '0'\n\n return {\n address: {\n road: item.roadAddrPart1 || item.roadAddr,\n jibun: item.jibunAddr,\n buildingName: item.bdNm || undefined,\n siNm: item.siNm,\n sggNm: item.sggNm,\n emdNm: item.emdNm\n },\n pnu: { sigunguCd, bjdongCd, platGbCd, bun, ji },\n admCd,\n bdMgtSn: item.bdMgtSn,\n rnMgtSn: item.rnMgtSn,\n zipNo: item.zipNo,\n raw: item\n }\n}\n\nJusoResolveAddressTask.parameterSpec = [\n { type: 'string', name: 'query', label: 'label.address-query', placeholder: '예) 서울특별시 강남구 테헤란로 223' },\n {\n type: 'select',\n name: 'pickFirst',\n label: 'label.pick-first',\n property: {\n options: [\n { value: 'true', display: '첫 결과 자동 선택' },\n { value: 'false', display: '다중 매칭 시 에러' }\n ]\n }\n }\n]\n\nJusoResolveAddressTask.help = 'integration/task/juso-resolve-address'\n\nTaskRegistry.registerTaskHandler('juso-resolve-address', JusoResolveAddressTask)\n\nexport default JusoResolveAddressTask\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../server/engine/task/index.ts"],"names":[],"mappings":";;AAAA;;;;GAIG;AACH,qBAAkB","sourcesContent":["/**\n * juso 도메인 태스크 배럴.\n *\n * 새 도메인 추가 시: 폴더 + 본 index 의 import 한 줄.\n */\nimport './address'\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* juso.go.kr (도로명주소 안내시스템) Open API 응답 타입.
|
|
3
|
+
*
|
|
4
|
+
* 본 패키지는 행정안전부 도로명주소조회 API 의 결과를 받아 우리 표준 결과 형태로
|
|
5
|
+
* 매핑한다. 다른 주소 resolver (예: VWorld) 와 호환되는 통일된 출력 형태를 사용해
|
|
6
|
+
* 소비자가 백엔드를 바꿔 끼울 수 있도록 한다.
|
|
7
|
+
*/
|
|
8
|
+
/** juso.go.kr 도로명주소 단일 항목 (응답의 results.juso 배열 원소). */
|
|
9
|
+
export interface JusoItem {
|
|
10
|
+
roadAddr: string;
|
|
11
|
+
roadAddrPart1: string;
|
|
12
|
+
roadAddrPart2?: string;
|
|
13
|
+
jibunAddr: string;
|
|
14
|
+
engAddr?: string;
|
|
15
|
+
zipNo?: string;
|
|
16
|
+
admCd: string;
|
|
17
|
+
rnMgtSn?: string;
|
|
18
|
+
bdMgtSn?: string;
|
|
19
|
+
bdNm?: string;
|
|
20
|
+
bdKdcd?: string;
|
|
21
|
+
siNm?: string;
|
|
22
|
+
sggNm?: string;
|
|
23
|
+
emdNm?: string;
|
|
24
|
+
liNm?: string;
|
|
25
|
+
rn?: string;
|
|
26
|
+
udrtYn?: string;
|
|
27
|
+
buldMnnm?: string;
|
|
28
|
+
buldSlno?: string;
|
|
29
|
+
mtYn?: string;
|
|
30
|
+
lnbrMnnm: string;
|
|
31
|
+
lnbrSlno: string;
|
|
32
|
+
emdNo?: string;
|
|
33
|
+
[key: string]: any;
|
|
34
|
+
}
|
|
35
|
+
/** juso.go.kr 응답 봉투. */
|
|
36
|
+
export interface JusoResponse {
|
|
37
|
+
results: {
|
|
38
|
+
common: {
|
|
39
|
+
errorMessage: string;
|
|
40
|
+
countPerPage: string;
|
|
41
|
+
totalCount: string;
|
|
42
|
+
errorCode: string;
|
|
43
|
+
currentPage: string;
|
|
44
|
+
};
|
|
45
|
+
juso: JusoItem[] | null;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 주소 resolver 의 통일 출력 형태.
|
|
50
|
+
*
|
|
51
|
+
* 다른 백엔드 (juso, VWorld 등) 가 동일 형태로 반환해 소비자가 자유롭게 교체 가능.
|
|
52
|
+
* coord 는 백엔드에 따라 누락 가능 (juso 는 별도 변환 없이는 WGS84 좌표를 못 줌).
|
|
53
|
+
*/
|
|
54
|
+
export interface ResolvedAddress {
|
|
55
|
+
address: {
|
|
56
|
+
road?: string;
|
|
57
|
+
jibun?: string;
|
|
58
|
+
buildingName?: string;
|
|
59
|
+
siNm?: string;
|
|
60
|
+
sggNm?: string;
|
|
61
|
+
emdNm?: string;
|
|
62
|
+
};
|
|
63
|
+
/** 건축물대장 등 부동산 API 호출용 PNU 5요소. */
|
|
64
|
+
pnu: {
|
|
65
|
+
sigunguCd: string;
|
|
66
|
+
bjdongCd: string;
|
|
67
|
+
platGbCd: string;
|
|
68
|
+
bun: string;
|
|
69
|
+
ji: string;
|
|
70
|
+
};
|
|
71
|
+
/** 법정동코드 10자리. */
|
|
72
|
+
admCd: string;
|
|
73
|
+
/** 건물관리번호 25자리. */
|
|
74
|
+
bdMgtSn?: string;
|
|
75
|
+
/** 도로명관리번호. */
|
|
76
|
+
rnMgtSn?: string;
|
|
77
|
+
/** 우편번호 5자리. */
|
|
78
|
+
zipNo?: string;
|
|
79
|
+
/** WGS84 위경도 (백엔드가 줄 때만). */
|
|
80
|
+
coord?: {
|
|
81
|
+
latitude: number;
|
|
82
|
+
longitude: number;
|
|
83
|
+
};
|
|
84
|
+
/** 백엔드 원본 응답 (디버깅·확장용). */
|
|
85
|
+
raw?: unknown;
|
|
86
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* juso.go.kr (도로명주소 안내시스템) Open API 응답 타입.
|
|
4
|
+
*
|
|
5
|
+
* 본 패키지는 행정안전부 도로명주소조회 API 의 결과를 받아 우리 표준 결과 형태로
|
|
6
|
+
* 매핑한다. 다른 주소 resolver (예: VWorld) 와 호환되는 통일된 출력 형태를 사용해
|
|
7
|
+
* 소비자가 백엔드를 바꿔 끼울 수 있도록 한다.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../server/engine/types.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG","sourcesContent":["/**\n * juso.go.kr (도로명주소 안내시스템) Open API 응답 타입.\n *\n * 본 패키지는 행정안전부 도로명주소조회 API 의 결과를 받아 우리 표준 결과 형태로\n * 매핑한다. 다른 주소 resolver (예: VWorld) 와 호환되는 통일된 출력 형태를 사용해\n * 소비자가 백엔드를 바꿔 끼울 수 있도록 한다.\n */\n\n/** juso.go.kr 도로명주소 단일 항목 (응답의 results.juso 배열 원소). */\nexport interface JusoItem {\n roadAddr: string\n roadAddrPart1: string\n roadAddrPart2?: string\n jibunAddr: string\n engAddr?: string\n zipNo?: string\n admCd: string /* 법정동코드 10자리 = sigunguCd 5 + bjdongCd 5 */\n rnMgtSn?: string\n bdMgtSn?: string /* 건물관리번호 25자리 (PNU 정보 인코딩됨) */\n bdNm?: string /* 건물명 */\n bdKdcd?: string\n siNm?: string\n sggNm?: string\n emdNm?: string\n liNm?: string\n rn?: string\n udrtYn?: string\n buldMnnm?: string /* 도로명 건물 본번 */\n buldSlno?: string\n mtYn?: string /* 산 여부 ('1' 산, '0' 일반) */\n lnbrMnnm: string /* 지번 본번 */\n lnbrSlno: string /* 지번 부번 */\n emdNo?: string\n [key: string]: any\n}\n\n/** juso.go.kr 응답 봉투. */\nexport interface JusoResponse {\n results: {\n common: {\n errorMessage: string\n countPerPage: string\n totalCount: string\n errorCode: string\n currentPage: string\n }\n juso: JusoItem[] | null\n }\n}\n\n/**\n * 주소 resolver 의 통일 출력 형태.\n *\n * 다른 백엔드 (juso, VWorld 등) 가 동일 형태로 반환해 소비자가 자유롭게 교체 가능.\n * coord 는 백엔드에 따라 누락 가능 (juso 는 별도 변환 없이는 WGS84 좌표를 못 줌).\n */\nexport interface ResolvedAddress {\n address: {\n road?: string\n jibun?: string\n buildingName?: string\n siNm?: string\n sggNm?: string\n emdNm?: string\n }\n /** 건축물대장 등 부동산 API 호출용 PNU 5요소. */\n pnu: {\n sigunguCd: string\n bjdongCd: string\n platGbCd: string\n bun: string\n ji: string\n }\n /** 법정동코드 10자리. */\n admCd: string\n /** 건물관리번호 25자리. */\n bdMgtSn?: string\n /** 도로명관리번호. */\n rnMgtSn?: string\n /** 우편번호 5자리. */\n zipNo?: string\n /** WGS84 위경도 (백엔드가 줄 때만). */\n coord?: { latitude: number; longitude: number }\n /** 백엔드 원본 응답 (디버깅·확장용). */\n raw?: unknown\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapToResolvedAddress = exports.JusoClient = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
require("./engine");
|
|
6
|
+
tslib_1.__exportStar(require("./engine/types"), exports);
|
|
7
|
+
var juso_client_1 = require("./engine/connector/juso-client");
|
|
8
|
+
Object.defineProperty(exports, "JusoClient", { enumerable: true, get: function () { return juso_client_1.JusoClient; } });
|
|
9
|
+
var resolve_1 = require("./engine/task/address/resolve");
|
|
10
|
+
Object.defineProperty(exports, "mapToResolvedAddress", { enumerable: true, get: function () { return resolve_1.mapToResolvedAddress; } });
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;;AAAA,oBAAiB;AAEjB,yDAA8B;AAC9B,8DAA2D;AAAlD,yGAAA,UAAU,OAAA;AACnB,yDAAoE;AAA3D,+GAAA,oBAAoB,OAAA","sourcesContent":["import './engine'\n\nexport * from './engine/types'\nexport { JusoClient } from './engine/connector/juso-client'\nexport { mapToResolvedAddress } from './engine/task/address/resolve'\n"]}
|