korean-bank-detect 0.1.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.
@@ -0,0 +1,49 @@
1
+ import { type Registry, type AccountInstitutionMatchResult, type InstitutionCategory } from './match.js';
2
+ /** The bundled 금결원 CMS registry (26.06.01). */
3
+ export declare const registry: Registry;
4
+ export interface DetectOptions {
5
+ /**
6
+ * Max candidates to display (default 3 — the density real apps like Toss
7
+ * use). Grade-ordered: matched → unknown → contradicted, each rank-ordered.
8
+ * Raise it (or use Infinity for the full picker list) to see more.
9
+ */
10
+ limit?: number;
11
+ /**
12
+ * Restrict candidates to these categories — e.g. ['bank'] 은행만,
13
+ * ['securities'] 증권사만, ['bank', 'non-bank'] 은행+비은행.
14
+ * Omit (or empty) for 전체. Applied BEFORE shortlisting, so the structural
15
+ * guarantee and padding operate within the filtered set.
16
+ */
17
+ categories?: InstitutionCategory[];
18
+ /**
19
+ * HARD filter: only these 대표코드 institutions are eligible (e.g. the banks
20
+ * your service actually supports for transfers). This is the first, strongest
21
+ * narrowing lever — recommended over relying on the heuristic priority.
22
+ */
23
+ allowedCodes?: string[];
24
+ /**
25
+ * HARD filter, inverse: drop these 대표코드 institutions. Useful for syncing
26
+ * with an existing app's bank list by subtraction (e.g. categories:['bank']
27
+ * plus a few exclusions) instead of maintaining a full whitelist.
28
+ * Applied after allowedCodes/categories.
29
+ */
30
+ excludeCodes?: string[];
31
+ /**
32
+ * Minimum digits before suggestions appear (default 7 — the shortest
33
+ * documented Korean account length; below it no account can even be complete,
34
+ * so suggestions would be pure speculation. Real apps like Toss behave the
35
+ * same). Below the threshold `candidates` is empty. Set 0 to disable.
36
+ */
37
+ minDigits?: number;
38
+ /**
39
+ * Use a different registry than the bundled one — load the SAME JSON shape
40
+ * from your DB / remote storage / config service and pass it here. This
41
+ * decouples DATA updates (금결원 PDF 개정, prefix 추가, 오류 수정) from library
42
+ * releases: replace the stored JSON, no npm update or redeploy needed.
43
+ * The bundled registry remains the fallback.
44
+ */
45
+ registry?: Registry;
46
+ }
47
+ export declare function detect(account: unknown, options?: DetectOptions): AccountInstitutionMatchResult;
48
+ export type { BranchRule, RegistryInstitution, Registry, CandidateMatch, AccountInstitutionMatchResult, InstitutionCategory, } from './match.js';
49
+ export { matchAccount } from './match.js';
package/dist/index.js ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * korean-bank-detect
3
+ *
4
+ * Zero-dependency TypeScript library that identifies the Korean deposit
5
+ * institution(s) a CMS account number belongs to, using the 금융결제원 CMS
6
+ * 계좌번호체계 PDF (26.06.01) as the single source of truth.
7
+ *
8
+ * Pure data in, pure JSON out. No UI, no score/confidence, no check-digit
9
+ * computation. Matching is by documented account length plus machine-executable
10
+ * branch rules (수협 007 ⇄ 030). Partial input is supported (progressive
11
+ * narrowing); the true institution is never dropped from the candidate set.
12
+ *
13
+ * @example
14
+ * import { detect } from 'korean-bank-detect';
15
+ * const { candidates } = detect('212123456789');
16
+ * candidates[0].institutionName; // '수협중앙회' (routed by branch rule)
17
+ *
18
+ * @example // pass your own registry (e.g. a pinned/older revision)
19
+ * import { matchAccount } from 'korean-bank-detect';
20
+ * import registry from 'korean-bank-detect/registry' with { type: 'json' };
21
+ * matchAccount('110-436-387740', registry);
22
+ */
23
+ import registryJson from '../data/registry.json' with { type: 'json' };
24
+ import { matchAccount, } from './match.js';
25
+ /** The bundled 금결원 CMS registry (26.06.01). */
26
+ export const registry = registryJson;
27
+ /**
28
+ * Match against the bundled registry and return a SHORT, pickable candidate list
29
+ * — the point is "show a few options to pick", not 30.
30
+ *
31
+ * Narrowing — evidence-based:
32
+ * 1. hard filters first: `allowedCodes` (your service's supported banks),
33
+ * `categories`, and documented-length compatibility (membership itself).
34
+ * 2. DETERMINED input — an exact-length strong signal exists (exactStrength
35
+ * ≥ 3: 실무 프리픽스 / front 과목코드 / 분기규칙 at exactly this length, which
36
+ * also implies the input is a complete documented length) → return the
37
+ * 'matched' candidates ONLY. A fully-typed 카카오 account is [카카오뱅크],
38
+ * not 8 rows. Unknown/contradicted stay reachable via `limit: Infinity`,
39
+ * the `grade` field on `matchAccount`, or your own full bank picker.
40
+ * 3. NOT determined (typing in progress, or only weak shared codes) → the
41
+ * matched candidates plus a display budget (`limit` − matched) filled by
42
+ * rank with 'unknown' first (no signal capability — no information),
43
+ * then 'contradicted' (codes at this length, none matched — demoted last,
44
+ * never silently unreachable).
45
+ *
46
+ * Ranking within the list is matchAccount's: strength (incl. progressive
47
+ * prefix boosts) → evidence count → priority → 가나다. Note `priority` is a
48
+ * HEURISTIC display order (no official popularity source exists) — for real
49
+ * products, re-rank ties with your own verification stats and confirm via a
50
+ * 예금주조회 API before money moves. Never auto-trust candidates[0]; treat
51
+ * `exactStrength >= 3` as the minimum bar for even suggesting auto-selection.
52
+ *
53
+ * `limit: Infinity` → the full ranked pool (picker UX; 전체/탭 화면용).
54
+ * For the raw unfiltered list use `matchAccount(account, registry)`.
55
+ */
56
+ const STRONG = 3;
57
+ export function detect(account, options = {}) {
58
+ const limit = options.limit ?? 3;
59
+ const minDigits = options.minDigits ?? 7;
60
+ const full = matchAccount(account, options.registry ?? registry);
61
+ const digitCount = typeof account === 'string' ? account.replace(/[-\s]/g, '').length : 0;
62
+ if (digitCount < minDigits)
63
+ return { ...full, candidates: [] };
64
+ let pool = full.candidates;
65
+ if (options.allowedCodes && options.allowedCodes.length > 0) {
66
+ const allowed = new Set(options.allowedCodes);
67
+ pool = pool.filter((c) => allowed.has(c.institutionCode));
68
+ }
69
+ if (options.categories && options.categories.length > 0) {
70
+ pool = pool.filter((c) => options.categories.includes(c.category));
71
+ }
72
+ if (options.excludeCodes && options.excludeCodes.length > 0) {
73
+ const excluded = new Set(options.excludeCodes);
74
+ pool = pool.filter((c) => !excluded.has(c.institutionCode));
75
+ }
76
+ if (limit === Infinity)
77
+ return { ...full, candidates: pool };
78
+ const determined = pool.some((c) => c.exactStrength >= STRONG);
79
+ if (determined) {
80
+ // complete input with an exact strong signal → matched only (top `limit`)
81
+ return { ...full, candidates: pool.filter((c) => c.grade === 'matched').slice(0, limit) };
82
+ }
83
+ // grade-ordered display: matched → unknown → contradicted (rank order within
84
+ // each grade), capped at `limit` — the Toss-style top-N suggestion density
85
+ const ordered = [
86
+ ...pool.filter((c) => c.grade === 'matched'),
87
+ ...pool.filter((c) => c.grade === 'unknown'),
88
+ ...pool.filter((c) => c.grade === 'contradicted'),
89
+ ];
90
+ return { ...full, candidates: ordered.slice(0, limit) };
91
+ }
92
+ export { matchAccount } from './match.js';
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Matching engine for Korean CMS account numbers.
3
+ *
4
+ * Accepts a raw account-number string (may be partial, hyphenated, or malformed)
5
+ * and the institution registry, and returns candidate deposit institutions each
6
+ * with a document-sourced matching-evidence array.
7
+ *
8
+ * Guarantees (per seed contract):
9
+ * - Never throws on any input value.
10
+ * - Empty candidates + isComplete=false for malformed/empty/non-digit/oversized
11
+ * strings and non-string values.
12
+ * - Document-based recall: an institution stays a candidate at every prefix until
13
+ * its documented lengths become unreachable (candidate set only shrinks).
14
+ * - Machine-executable definitive routing for document-declared branch rules
15
+ * (수협 007 ⇄ 030): once the deciding digits are present, the candidate is
16
+ * routed to the target institution.
17
+ * - No score/confidence fields. Deterministic sort: matched-count desc → name 가나다순.
18
+ * - Only internal normalization: strip hyphens/whitespace before matching.
19
+ */
20
+ /** A digit-value branch rule: routes one 대표코드 to another by examined digits. */
21
+ export interface BranchRule {
22
+ /** the length-pattern this rule applies to */
23
+ length: number;
24
+ /** 0-indexed start of the examined digit window */
25
+ position: number;
26
+ /** number of digits examined */
27
+ matchLength: number;
28
+ /** exact values and/or inclusive {from,to} ranges that trigger routing */
29
+ match: Array<number | {
30
+ from: number;
31
+ to: number;
32
+ }>;
33
+ /** institution code routed TO when the digits match */
34
+ targetCode: string;
35
+ /** institution name routed TO when the digits match */
36
+ targetName: string;
37
+ /** verbatim PDF sentence declaring the rule */
38
+ sourceQuote: string;
39
+ }
40
+ /** 계정과목코드 spec: subject code sits at [position, position+codeLength) for this length. */
41
+ export interface SubjectCodeSpec {
42
+ length: number;
43
+ position: number;
44
+ codeLength: number;
45
+ codes: string[];
46
+ /**
47
+ * true for PRACTICAL prefixes (실무 관측 데이터, e.g. 카카오 3333) — sourced
48
+ * outside the PDF and reported with a '프리픽스' evidence token instead of '과목코드'.
49
+ */
50
+ practical?: boolean;
51
+ /**
52
+ * true when the PDF marks this pattern as a 가상계좌 (virtual/deposit-shell
53
+ * account, e.g. 아이엠 14d 937/938/999 모임통장용). A virtual-code match is a
54
+ * weaker prior for user-typed transfer targets, so its ranking strength is
55
+ * HALVED — the candidate stays matched and listed, just below real-account
56
+ * matches (국민 938... 실계좌 vs 아이엠 938 가상계좌 충돌의 원칙적 해소).
57
+ */
58
+ virtual?: boolean;
59
+ }
60
+ /**
61
+ * Institution category:
62
+ * 'bank' 은행 · 'non-bank' 비은행(중앙회·금고·우체국·저축은행) ·
63
+ * 'securities' 증권사 · 'clearing' 결제기관(금융결제원) ·
64
+ * 'foreign-branch' 외은지점(HSBC·도이치·JP모간·BOA·BNP — 개인 리테일 없음)
65
+ */
66
+ export type InstitutionCategory = 'bank' | 'non-bank' | 'securities' | 'clearing' | 'foreign-branch';
67
+ export interface RegistryInstitution {
68
+ representativeCode: string;
69
+ name: string;
70
+ category: InstitutionCategory;
71
+ allCodes: string[];
72
+ accountLengths: number[];
73
+ /** static PDF-transcribed flag; NEVER computed from an account number */
74
+ checkDigitDeclared: boolean | null;
75
+ branchRules: BranchRule[];
76
+ /** subject-code specs for evidence-based narrowing (may be absent for some institutions) */
77
+ subjectCodes?: SubjectCodeSpec[];
78
+ /** tie-break only: major banks outrank minor ones at equal match strength */
79
+ priority?: number;
80
+ logo: string;
81
+ }
82
+ export interface Registry {
83
+ generatedFrom: string;
84
+ sourceSha256: string | null;
85
+ institutions: RegistryInstitution[];
86
+ }
87
+ export interface CandidateMatch {
88
+ /** 3-digit 대표코드 (the target code when routed by a branch rule) */
89
+ institutionCode: string;
90
+ /** official 기관명 (the target name when routed by a branch rule) */
91
+ institutionName: string;
92
+ /** institution category: bank · non-bank · securities · clearing */
93
+ category: InstitutionCategory;
94
+ /** documented full account lengths for the institution */
95
+ accountLengths: number[];
96
+ /** evidence tokens satisfied for this input, e.g. '자릿수', '과목코드', '분기규칙' */
97
+ matched: string[];
98
+ /**
99
+ * Structural match strength: digits of subject-code evidence that matched
100
+ * (3 = near-unique front code, 2/1 = shared, 0 = length only). Higher ranks
101
+ * first. Branch-routed candidates get a high strength so they lead.
102
+ * Includes PROSPECTIVE front matches (a longer pattern's prefix fired while
103
+ * typing) — use for ranking only.
104
+ */
105
+ strength: number;
106
+ /**
107
+ * Strength from EXACT-length signals only (spec.length === input length, or a
108
+ * fired branch rule). A 14-digit prefix firing on a 13-digit input contributes
109
+ * to `strength` (ranking) but NOT here — so a possibly-complete shorter input
110
+ * is never treated as "determined" by a longer pattern's signal.
111
+ */
112
+ exactStrength: number;
113
+ /**
114
+ * Whether this institution COULD have signaled at this exact length (has a
115
+ * subject-code/prefix spec of this length, or a branch rule). If true and it
116
+ * didn't match, that is evidence AGAINST it; if false, absence of a match is
117
+ * no information — such institutions must not be evicted by others' matches.
118
+ */
119
+ signalCapableAtLength: boolean;
120
+ /**
121
+ * Evidence grade — the narrowing policy in one word:
122
+ * 'matched' its signal fits these digits (kept, ranked first)
123
+ * 'unknown' no signal capability at this length (no information)
124
+ * 'contradicted' had codes at this length, none matched (demoted LAST, not
125
+ * dropped — code sets can't be proven exhaustive, 입금전용/신탁
126
+ * codes sprawl into the PDF's 비고 column)
127
+ */
128
+ grade: 'matched' | 'unknown' | 'contradicted';
129
+ /** static PDF-transcribed flag; false where the PDF declares no check-digit verify */
130
+ checkDigitDeclared: boolean | null;
131
+ /** true when a document-declared branch rule definitively selected this institution */
132
+ routedByBranchRule: boolean;
133
+ /**
134
+ * true when the institution declares a branch rule for exactly this input
135
+ * length (수협 11/12/14d). Even when the deciding digits didn't fire the rule,
136
+ * the PDF's "이외의 경우 007 SET" else-branch means this institution remains a
137
+ * documented possibility for this length — shortlists must not drop it.
138
+ */
139
+ branchRuleAtLength: boolean;
140
+ /** institution priority (tie-break signal; higher = more common) */
141
+ priority: number;
142
+ /** institution logo/icon URL, empty string for now */
143
+ logo: string;
144
+ }
145
+ export interface AccountInstitutionMatchResult {
146
+ /** raw account number as provided */
147
+ input: string;
148
+ /** true when the normalized input length is a documented full length of some candidate */
149
+ isComplete: boolean;
150
+ /**
151
+ * Deterministically ordered candidate institutions.
152
+ * Sort: matched.length desc, then institutionName 가나다순.
153
+ * Empty for malformed / non-string / oversized input.
154
+ */
155
+ candidates: CandidateMatch[];
156
+ }
157
+ /**
158
+ * Match a raw account-number string against the institution registry.
159
+ * @returns AccountInstitutionMatchResult — never throws.
160
+ */
161
+ export declare function matchAccount(raw: unknown, registry: Registry): AccountInstitutionMatchResult;
package/dist/match.js ADDED
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Matching engine for Korean CMS account numbers.
3
+ *
4
+ * Accepts a raw account-number string (may be partial, hyphenated, or malformed)
5
+ * and the institution registry, and returns candidate deposit institutions each
6
+ * with a document-sourced matching-evidence array.
7
+ *
8
+ * Guarantees (per seed contract):
9
+ * - Never throws on any input value.
10
+ * - Empty candidates + isComplete=false for malformed/empty/non-digit/oversized
11
+ * strings and non-string values.
12
+ * - Document-based recall: an institution stays a candidate at every prefix until
13
+ * its documented lengths become unreachable (candidate set only shrinks).
14
+ * - Machine-executable definitive routing for document-declared branch rules
15
+ * (수협 007 ⇄ 030): once the deciding digits are present, the candidate is
16
+ * routed to the target institution.
17
+ * - No score/confidence fields. Deterministic sort: matched-count desc → name 가나다순.
18
+ * - Only internal normalization: strip hyphens/whitespace before matching.
19
+ */
20
+ /** Strip hyphens/whitespace; null if empty or non-digit. */
21
+ function normalizeDigits(raw) {
22
+ if (typeof raw !== 'string')
23
+ return null;
24
+ const s = raw.replace(/[-\s]/g, '');
25
+ if (s.length === 0 || !/^\d+$/.test(s))
26
+ return null;
27
+ return s;
28
+ }
29
+ /** Does the examined digit window satisfy any of the rule's values/ranges? */
30
+ function branchMatches(digits, rule) {
31
+ const window = digits.slice(rule.position, rule.position + rule.matchLength);
32
+ if (window.length < rule.matchLength)
33
+ return false; // deciding digits not present yet
34
+ const v = Number(window);
35
+ if (Number.isNaN(v))
36
+ return false;
37
+ for (const m of rule.match) {
38
+ if (typeof m === 'number') {
39
+ if (v === m)
40
+ return true;
41
+ }
42
+ else if (v >= m.from && v <= m.to) {
43
+ return true;
44
+ }
45
+ }
46
+ return false;
47
+ }
48
+ /**
49
+ * Match a raw account-number string against the institution registry.
50
+ * @returns AccountInstitutionMatchResult — never throws.
51
+ */
52
+ export function matchAccount(raw, registry) {
53
+ const inputStr = typeof raw === 'string' ? raw : '';
54
+ const normalized = normalizeDigits(raw);
55
+ if (normalized === null)
56
+ return { input: inputStr, isComplete: false, candidates: [] };
57
+ const len = normalized.length;
58
+ const candidates = [];
59
+ for (const inst of registry.institutions) {
60
+ // candidate if some documented length is still reachable from the current input
61
+ if (!inst.accountLengths.some((l) => l >= len))
62
+ continue;
63
+ // '기관코드' = identity evidence; '자릿수' = a documented length is still reachable
64
+ const matched = ['기관코드', '자릿수'];
65
+ // Subject-code / prefix evidence. Two firing modes:
66
+ // • front specs (position 0): PROGRESSIVE — fire as soon as the prefix
67
+ // digits are typed, while the pattern length is still reachable
68
+ // (spec.length >= len). This is how real apps surface 카카오 at "3333…".
69
+ // • middle/end specs: fire only at the exact full pattern length (their
70
+ // position is meaningless until the number is complete).
71
+ // strength = codeLength (+0.5 front bonus) — a 3/4-digit front code is
72
+ // near-unique to one institution; shared 2/1-digit codes are weaker.
73
+ // Evidence only — never drops a candidate (recall stays intact).
74
+ let strength = 0;
75
+ let exactStrength = 0;
76
+ let signalCapableAtLength = false;
77
+ let evidenceToken = null;
78
+ for (const spec of inst.subjectCodes ?? []) {
79
+ if (spec.length === len)
80
+ signalCapableAtLength = true;
81
+ const end = spec.position + spec.codeLength;
82
+ if (spec.position === 0) {
83
+ if (spec.length < len || normalized.length < end)
84
+ continue; // unreachable or digits missing
85
+ }
86
+ else {
87
+ if (spec.length !== len || normalized.length < end)
88
+ continue;
89
+ }
90
+ const code = normalized.slice(spec.position, end);
91
+ if (spec.codes.includes(code)) {
92
+ let s = spec.codeLength + (spec.position === 0 ? 0.5 : 0);
93
+ // 가상계좌 코드는 순위 강도 절반 — 실계좌 매칭(국민 02 등)보다 아래로,
94
+ // 단 matched 등급과 리스트 잔류는 유지 (가상계좌도 유효한 이체 대상)
95
+ if (spec.virtual)
96
+ s *= 0.5;
97
+ if (s > strength) {
98
+ strength = s;
99
+ evidenceToken = spec.practical ? '프리픽스' : spec.virtual ? '가상계좌코드' : '과목코드';
100
+ }
101
+ // exact-length signals only — a longer pattern's prefix firing on a
102
+ // shorter (possibly complete) input must not count as determination
103
+ if (spec.length === len && s > exactStrength)
104
+ exactStrength = s;
105
+ }
106
+ }
107
+ if (evidenceToken)
108
+ matched.push(evidenceToken);
109
+ // Branch routing: if a rule applies to the current length and its deciding
110
+ // digits are present and match, route the candidate to the target institution.
111
+ let institutionCode = inst.representativeCode;
112
+ let institutionName = inst.name;
113
+ let category = inst.category ?? 'bank';
114
+ let routedByBranchRule = false;
115
+ let branchRuleAtLength = false;
116
+ for (const rule of inst.branchRules) {
117
+ if (rule.length !== len)
118
+ continue;
119
+ branchRuleAtLength = true;
120
+ if (branchMatches(normalized, rule)) {
121
+ institutionCode = rule.targetCode;
122
+ institutionName = rule.targetName;
123
+ // the candidate now IS the target institution — carry its category
124
+ // (수협중앙회 is non-bank while 수협은행 is bank)
125
+ category =
126
+ registry.institutions.find((t) => t.representativeCode === rule.targetCode)
127
+ ?.category ?? category;
128
+ routedByBranchRule = true;
129
+ matched.push('분기규칙');
130
+ // a definitive document rule is the strongest possible structural signal
131
+ // (branch rules only fire at their exact length, so exactStrength too)
132
+ strength = Math.max(strength, 4);
133
+ exactStrength = Math.max(exactStrength, 4);
134
+ }
135
+ // rule exists for this length but digits don't match → stays source institution
136
+ }
137
+ candidates.push({
138
+ institutionCode,
139
+ institutionName,
140
+ category,
141
+ accountLengths: inst.accountLengths,
142
+ matched,
143
+ strength,
144
+ exactStrength,
145
+ signalCapableAtLength: signalCapableAtLength || branchRuleAtLength,
146
+ grade: strength > 0 || branchRuleAtLength
147
+ ? 'matched'
148
+ : signalCapableAtLength
149
+ ? 'contradicted'
150
+ : 'unknown',
151
+ checkDigitDeclared: inst.checkDigitDeclared,
152
+ routedByBranchRule,
153
+ branchRuleAtLength,
154
+ priority: typeof inst.priority === 'number' ? inst.priority : 0,
155
+ logo: typeof inst.logo === 'string' ? inst.logo : '',
156
+ });
157
+ }
158
+ // Sort: match strength desc (branch rule > front 3-digit code > 2/1-digit code
159
+ // > length only) → matched-count desc → priority desc (major banks first at a
160
+ // tie) → institutionName 가나다순.
161
+ candidates.sort((a, b) => {
162
+ if (b.strength !== a.strength)
163
+ return b.strength - a.strength;
164
+ if (b.matched.length !== a.matched.length)
165
+ return b.matched.length - a.matched.length;
166
+ if (b.priority !== a.priority)
167
+ return b.priority - a.priority;
168
+ return a.institutionName.localeCompare(b.institutionName, 'ko');
169
+ });
170
+ const isComplete = candidates.some((c) => c.accountLengths.includes(len));
171
+ return { input: inputStr, isComplete, candidates };
172
+ }
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "korean-bank-detect",
3
+ "version": "0.1.0",
4
+ "description": "Korean bank account number detection via 금결원 CMS 계좌번호체계",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./registry": "./data/registry.json"
14
+ },
15
+ "scripts": {
16
+ "test": "vitest run",
17
+ "build": "node scripts/build-registry.mjs && tsc",
18
+ "generate": "node scripts/build-registry.mjs",
19
+ "prepublishOnly": "npm run build && npm test"
20
+ },
21
+ "devDependencies": {
22
+ "korean-account": "^0.1.2",
23
+ "typescript": "^5.5.0",
24
+ "vitest": "^2.0.0"
25
+ },
26
+ "allowScripts": {
27
+ "esbuild@0.21.5": true,
28
+ "fsevents@2.3.3": true
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "data/registry.json",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "keywords": [
37
+ "korean",
38
+ "bank",
39
+ "account",
40
+ "계좌번호",
41
+ "은행",
42
+ "금융결제원",
43
+ "cms",
44
+ "fintech"
45
+ ],
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/LESANF/korean-bank-detect.git"
50
+ },
51
+ "homepage": "https://github.com/LESANF/korean-bank-detect#readme",
52
+ "bugs": {
53
+ "url": "https://github.com/LESANF/korean-bank-detect/issues"
54
+ },
55
+ "author": "LESANF",
56
+ "sideEffects": false
57
+ }