@things-factory/integration-juso 10.0.0-zeta.3 → 10.0.0

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.
@@ -41,6 +41,15 @@ declare namespace JusoResolveAddressTask {
41
41
  })[];
42
42
  var help: string;
43
43
  }
44
+ /**
45
+ * 블록형 주소 파싱. "서울 강동구 강일동 1블록", "…강일동 1블럭", "3 블록" 등에서
46
+ * 블록번호를 추출하고, 블록 토큰 이후를 떼어낸 **법정동까지의 기본 주소**를 돌려준다.
47
+ * 블록 표기가 없으면 null (일반 지번/도로명 해석).
48
+ */
49
+ export declare function parseBlockQuery(query: string): {
50
+ baseQuery: string;
51
+ nameFilter: string;
52
+ } | null;
44
53
  /**
45
54
  * juso 단일 결과 → 표준 ResolvedAddress.
46
55
  *
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseBlockQuery = parseBlockQuery;
3
4
  exports.mapToResolvedAddress = mapToResolvedAddress;
4
5
  const integration_base_1 = require("@things-factory/integration-base");
5
6
  /**
@@ -30,26 +31,68 @@ async function JusoResolveAddressTask(step, context) {
30
31
  return (0, integration_base_1.evaluateTemplate)(String(value), scope);
31
32
  }
32
33
  catch (err) {
33
- throw new Error(`juso-resolve-address: failed to evaluate parameter: ${err.message}`);
34
+ // HALT 방지: 표현식 평가 실패는 치명 에러로 올리지 않고 undefined 로 강등.
35
+ logger?.warn?.(`juso-resolve-address: 파라미터 평가 실패 → 무시: ${err.message}`);
36
+ return undefined;
34
37
  }
35
38
  };
36
39
  const query = evalString(params.query);
37
40
  if (!query) {
38
- throw new Error('juso-resolve-address: query is required (address string).');
41
+ // HALT 방지: 주소가 비어있으면(도메인 EnvVar 미설정/주입 실패 등) 시나리오를 멈추지
42
+ // 않고 빈 결과를 반환한다. 다음 step 들은 found=false 를 보고 스스로 skip/빈처리.
43
+ logger?.warn?.('juso-resolve-address: query 비어있음 → 빈 결과 반환 (HALT 방지)');
44
+ return { data: emptyResolved() };
39
45
  }
40
46
  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 });
47
+ // 블록형 주소 감지: "…강일동 1블록" / "1블럭" 처럼 지번이 아닌 **블록번호**로 지정된
48
+ // 택지지구/정비사업 단지. juso 는 지번 주소만 해석하므로, 블록 토큰을 떼어낸
49
+ // **법정동까지만** 검색해 sigunguCd/bjdongCd 를 얻고, PNU 를 platGbCd=2 + bun=블록번호
50
+ // 로 재정의한다. (주택인허가 HsPmsHubService 가 이 PNU 로 단지 연면적/용적률 조회)
51
+ const block = parseBlockQuery(query);
52
+ const searchKeyword = block ? block.baseQuery : query;
53
+ logger?.info?.(`[juso] search keyword='${searchKeyword}'${block ? ` (block, name='${block.nameFilter}')` : ''}`);
54
+ const items = await instance.client.search({ keyword: searchKeyword, countPerPage: pickFirst ? 1 : 10 });
43
55
  if (items.length === 0) {
44
- throw new Error(`juso-resolve-address: no match for '${query}'. 도로명/지번 표기가 정확한지 확인.`);
56
+ // HALT 방지: 무매칭도 "데이터 없음"이지 오류가 아님 → 빈 결과.
57
+ logger?.warn?.(`juso-resolve-address: '${searchKeyword}' 무매칭 → 빈 결과 반환 (HALT 방지)`);
58
+ return { data: emptyResolved() };
45
59
  }
46
- if (!pickFirst && items.length > 1) {
60
+ if (!block && !pickFirst && items.length > 1) {
47
61
  throw new Error(`juso-resolve-address: '${query}' 매칭 ${items.length}건. pickFirst=true 로 강제 선택 또는 query 더 구체적으로 입력.`);
48
62
  }
49
63
  const resolved = mapToResolvedAddress(items[0]);
64
+ if (block) {
65
+ // 블록 조회로 PNU 재정의 — 법정동(sigunguCd/bjdongCd)은 juso 결과 유지, platGbCd=2 +
66
+ // bun/ji 는 비운다. (주택인허가 블록 조회는 bun 없이 법정동만으로 그 동의 블록 사업 전체를
67
+ // 받고, 소비 태스크가 nameFilter(괄호 단지명)로 필터한다. bun 을 넣으면 0건)
68
+ resolved.pnu = { ...resolved.pnu, platGbCd: '2', bun: '', ji: '' };
69
+ // 괄호 단지명을 출력에 실어 다음 step(주택인허가)이 ${data.nameFilter} 로 참조.
70
+ resolved.nameFilter = block.nameFilter;
71
+ }
50
72
  logger?.info?.(`[juso] resolved → admCd=${resolved.admCd} pnu=${resolved.pnu.sigunguCd}-${resolved.pnu.bjdongCd}-${resolved.pnu.platGbCd}-${resolved.pnu.bun}-${resolved.pnu.ji}`);
51
73
  return { data: resolved };
52
74
  }
75
+ /**
76
+ * 블록형 주소 파싱. "서울 강동구 강일동 1블록", "…강일동 1블럭", "3 블록" 등에서
77
+ * 블록번호를 추출하고, 블록 토큰 이후를 떼어낸 **법정동까지의 기본 주소**를 돌려준다.
78
+ * 블록 표기가 없으면 null (일반 지번/도로명 해석).
79
+ */
80
+ function parseBlockQuery(query) {
81
+ // "블럭"/"블록" 감지 (앞 블록번호 유무 무관). 주택인허가 블록 조회는 번호(bun) 없이
82
+ // 법정동 + platGbCd=2 로 그 동의 블록 사업 전체를 받으므로, baseQuery(법정동까지)만 필요.
83
+ //
84
+ // 단지명은 주소에 **괄호**로 넣는다: "…강일동 블럭 (고덕강일3단지)".
85
+ // - baseQuery : 괄호·블럭 토큰을 뗀 법정동 문자열 (juso 로 sigunguCd/bjdongCd 확보)
86
+ // - nameFilter: 괄호 안 단지명 (블록 조회 결과 여러 사업 중 대상 단지 필터)
87
+ if (!/블\s*[록럭]/.test(query))
88
+ return null;
89
+ const nameMatch = query.match(/[((]\s*([^))]+?)\s*[))]/);
90
+ const nameFilter = nameMatch ? nameMatch[1].trim() : '';
91
+ let base = query.replace(/[((][^))]*[))]/g, ''); // 괄호 부분 제거
92
+ const m = base.match(/(\d+)?\s*블\s*[록럭]/);
93
+ base = (m ? base.slice(0, m.index) : base).trim().replace(/[,\s]+$/, '');
94
+ return { baseQuery: base || query, nameFilter };
95
+ }
53
96
  /**
54
97
  * juso 단일 결과 → 표준 ResolvedAddress.
55
98
  *
@@ -83,6 +126,22 @@ function mapToResolvedAddress(item) {
83
126
  raw: item
84
127
  };
85
128
  }
129
+ /**
130
+ * 빈 결과 (HALT 방지용). PNU 5요소를 빈 문자열로 채워 다음 step 의 `${data.pnu.*}`
131
+ * 평가가 에러 없이 빈 값이 되도록 한다. `found=false` 로 미해결을 명시.
132
+ */
133
+ function emptyResolved() {
134
+ return {
135
+ address: null,
136
+ pnu: { sigunguCd: '', bjdongCd: '', platGbCd: '', bun: '', ji: '' },
137
+ admCd: null,
138
+ bdMgtSn: null,
139
+ rnMgtSn: null,
140
+ zipNo: null,
141
+ found: false,
142
+ raw: null
143
+ };
144
+ }
86
145
  JusoResolveAddressTask.parameterSpec = [
87
146
  {
88
147
  type: 'string',
@@ -1 +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 {\n type: 'string',\n name: 'query',\n label: 'label.address-query',\n placeholder: '예) 서울특별시 강남구 테헤란로 223',\n description:\n '검색할 주소 문자열 (도로명/지번). 도메인 EnvVar `Step::<scenario>::<step>::query` 로 ' +\n '프로젝트별 주소 override 가능. 시나리오 변수도 사용 가능 (예: `${variables.address}`).',\n useDomainAttribute: true\n },\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"]}
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 // HALT 방지: 표현식 평가 실패는 치명 에러로 올리지 않고 undefined 로 강등.\n logger?.warn?.(`juso-resolve-address: 파라미터 평가 실패 → 무시: ${(err as Error).message}`)\n return undefined\n }\n }\n\n const query = evalString(params.query)\n if (!query) {\n // HALT 방지: 주소가 비어있으면(도메인 EnvVar 미설정/주입 실패 등) 시나리오를 멈추지\n // 않고 빈 결과를 반환한다. 다음 step 들은 found=false 를 보고 스스로 skip/빈처리.\n logger?.warn?.('juso-resolve-address: query 비어있음 → 빈 결과 반환 (HALT 방지)')\n return { data: emptyResolved() }\n }\n\n const pickFirst = params.pickFirst === undefined ? true : Boolean(params.pickFirst)\n\n // 블록형 주소 감지: \"…강일동 1블록\" / \"1블럭\" 처럼 지번이 아닌 **블록번호**로 지정된\n // 택지지구/정비사업 단지. juso 는 지번 주소만 해석하므로, 블록 토큰을 떼어낸\n // **법정동까지만** 검색해 sigunguCd/bjdongCd 를 얻고, PNU 를 platGbCd=2 + bun=블록번호\n // 로 재정의한다. (주택인허가 HsPmsHubService 가 이 PNU 로 단지 연면적/용적률 조회)\n const block = parseBlockQuery(query)\n const searchKeyword = block ? block.baseQuery : query\n\n logger?.info?.(`[juso] search keyword='${searchKeyword}'${block ? ` (block, name='${block.nameFilter}')` : ''}`)\n\n const items = await instance.client.search({ keyword: searchKeyword, countPerPage: pickFirst ? 1 : 10 })\n\n if (items.length === 0) {\n // HALT 방지: 무매칭도 \"데이터 없음\"이지 오류가 아님 → 빈 결과.\n logger?.warn?.(`juso-resolve-address: '${searchKeyword}' 무매칭 → 빈 결과 반환 (HALT 방지)`)\n return { data: emptyResolved() }\n }\n if (!block && !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 if (block) {\n // 블록 조회로 PNU 재정의 — 법정동(sigunguCd/bjdongCd)은 juso 결과 유지, platGbCd=2 +\n // bun/ji 는 비운다. (주택인허가 블록 조회는 bun 없이 법정동만으로 그 동의 블록 사업 전체를\n // 받고, 소비 태스크가 nameFilter(괄호 단지명)로 필터한다. bun 을 넣으면 0건)\n resolved.pnu = { ...resolved.pnu, platGbCd: '2', bun: '', ji: '' }\n // 괄호 단지명을 출력에 실어 다음 step(주택인허가)이 ${data.nameFilter} 로 참조.\n resolved.nameFilter = block.nameFilter\n }\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 * 블록형 주소 파싱. \"서울 강동구 강일동 1블록\", \"…강일동 1블럭\", \"3 블록\" 등에서\n * 블록번호를 추출하고, 블록 토큰 이후를 떼어낸 **법정동까지의 기본 주소**를 돌려준다.\n * 블록 표기가 없으면 null (일반 지번/도로명 해석).\n */\nexport function parseBlockQuery(query: string): { baseQuery: string; nameFilter: string } | null {\n // \"블럭\"/\"블록\" 감지 (앞 블록번호 유무 무관). 주택인허가 블록 조회는 번호(bun) 없이\n // 법정동 + platGbCd=2 로 그 동의 블록 사업 전체를 받으므로, baseQuery(법정동까지)만 필요.\n //\n // 단지명은 주소에 **괄호**로 넣는다: \"…강일동 블럭 (고덕강일3단지)\".\n // - baseQuery : 괄호·블럭 토큰을 뗀 법정동 문자열 (juso 로 sigunguCd/bjdongCd 확보)\n // - nameFilter: 괄호 안 단지명 (블록 조회 결과 여러 사업 중 대상 단지 필터)\n if (!/블\\s*[록럭]/.test(query)) return null\n const nameMatch = query.match(/[((]\\s*([^))]+?)\\s*[))]/)\n const nameFilter = nameMatch ? nameMatch[1].trim() : ''\n let base = query.replace(/[((][^))]*[))]/g, '') // 괄호 부분 제거\n const m = base.match(/(\\d+)?\\s*블\\s*[록럭]/)\n base = (m ? base.slice(0, m.index) : base).trim().replace(/[,\\s]+$/, '')\n return { baseQuery: base || query, nameFilter }\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\n/**\n * 빈 결과 (HALT 방지용). PNU 5요소를 빈 문자열로 채워 다음 step 의 `${data.pnu.*}`\n * 평가가 에러 없이 빈 값이 되도록 한다. `found=false` 로 미해결을 명시.\n */\nfunction emptyResolved(): ResolvedAddress {\n return {\n address: null,\n pnu: { sigunguCd: '', bjdongCd: '', platGbCd: '', bun: '', ji: '' },\n admCd: null,\n bdMgtSn: null,\n rnMgtSn: null,\n zipNo: null,\n found: false,\n raw: null\n } as any\n}\n\nJusoResolveAddressTask.parameterSpec = [\n {\n type: 'string',\n name: 'query',\n label: 'label.address-query',\n placeholder: '예) 서울특별시 강남구 테헤란로 223',\n description:\n '검색할 주소 문자열 (도로명/지번). 도메인 EnvVar `Step::<scenario>::<step>::query` 로 ' +\n '프로젝트별 주소 override 가능. 시나리오 변수도 사용 가능 (예: `${variables.address}`).',\n useDomainAttribute: true\n },\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"]}
@@ -81,6 +81,12 @@ export interface ResolvedAddress {
81
81
  latitude: number;
82
82
  longitude: number;
83
83
  };
84
+ /**
85
+ * 블록형 주소 조회 시 대상 단지 필터. 주소 괄호 안 단지명에서 추출
86
+ * (예: "…강일동 블럭 (고덕강일3단지)" → "고덕강일3단지"). 주택인허가 등 블록 조회가
87
+ * 그 동의 여러 사업을 반환할 때 소비 태스크가 이 값으로 대상 단지를 선택한다.
88
+ */
89
+ nameFilter?: string;
84
90
  /** 백엔드 원본 응답 (디버깅·확장용). */
85
91
  raw?: unknown;
86
92
  }
@@ -1 +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"]}
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 * 블록형 주소 조회 시 대상 단지 필터. 주소 괄호 안 단지명에서 추출\n * (예: \"…강일동 블럭 (고덕강일3단지)\" → \"고덕강일3단지\"). 주택인허가 등 블록 조회가\n * 그 동의 여러 사업을 반환할 때 소비 태스크가 이 값으로 대상 단지를 선택한다.\n */\n nameFilter?: string\n /** 백엔드 원본 응답 (디버깅·확장용). */\n raw?: unknown\n}\n"]}