@jeongpd/korean-kinship 0.1.0 → 0.1.2
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 +56 -4
- package/package.json +1 -1
- package/src/data/fallback-terms.js +8 -4
- package/src/resolve.js +15 -25
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
**Resolve any Korean family-relationship path to its correct kinship term, reverse
|
|
4
4
|
term, and 촌수 (degree of kinship) — including in-laws and 사돈.**
|
|
5
5
|
|
|
6
|
+
[](https://github.com/tiger-dreams/korean-kinship/actions/workflows/ci.yml)
|
|
6
7
|
[](https://www.npmjs.com/package/@jeongpd/korean-kinship)
|
|
7
8
|
[](https://github.com/tiger-dreams/korean-kinship/blob/main/LICENSE)
|
|
8
9
|
[](https://www.npmjs.com/package/@jeongpd/korean-kinship)
|
|
@@ -71,6 +72,36 @@ Chain them to describe any relative — `['mother', 'olderSis', 'spouse']` is "
|
|
|
71
72
|
`opts.selfGender` (`'M'` or `'F'`) disambiguates terms that depend on the speaker's own
|
|
72
73
|
gender — e.g. your spouse is 아내 if you're male, 남편 if you're female.
|
|
73
74
|
|
|
75
|
+
## Judging confidence: `source` and `note`
|
|
76
|
+
|
|
77
|
+
This library doesn't decide for you whether an answer is trustworthy enough to show
|
|
78
|
+
as-is — it tells you where the answer came from, and your app decides what to do with
|
|
79
|
+
that:
|
|
80
|
+
|
|
81
|
+
- **`source: 'golden'`** — an exact match against one of the 145 entries ported from a
|
|
82
|
+
real, shipping app and checked against real usage (see [Where this data comes
|
|
83
|
+
from](#where-this-data-comes-from)). This is the answer for the overwhelming
|
|
84
|
+
majority of relationships anyone will actually ask about.
|
|
85
|
+
- **`source: 'fallback-table'`** — a curated Tier-2 entry for a specific pattern beyond
|
|
86
|
+
the 145 (currently empty in v1; see [`src/data/fallback-terms.js`](./src/data/fallback-terms.js)).
|
|
87
|
+
- **`source: 'generic'`** — nothing in either table matched, so `resolve()` computed a
|
|
88
|
+
generic pattern (e.g. `"6촌 손자"`) from 촌수 and generation distance instead of
|
|
89
|
+
failing. It's a defensible answer, not a verified one — Korean doesn't have a
|
|
90
|
+
single dedicated term for every possible relationship past a certain distance.
|
|
91
|
+
|
|
92
|
+
`note`, where present, is the original entry's own usage context (e.g. `"큰아버지의
|
|
93
|
+
아내"`) — not generated, and only ever present on `'golden'` results.
|
|
94
|
+
|
|
95
|
+
Whether to surface any of this to *your* end users — a "verified" badge, a footnote, or
|
|
96
|
+
nothing at all — is your call, not the library's:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
const result = resolve(path, { selfGender });
|
|
100
|
+
const isVerified = result.source === 'golden';
|
|
101
|
+
// e.g.: render result.label normally either way, but only show a
|
|
102
|
+
// "✓ 검증된 호칭" badge when isVerified — your app, your judgment call.
|
|
103
|
+
```
|
|
104
|
+
|
|
74
105
|
## Other exports
|
|
75
106
|
|
|
76
107
|
Alongside `resolve`, the package exports the underlying data so you can drive your own
|
|
@@ -125,16 +156,37 @@ Korean family-tree app, validated against actual usage. See
|
|
|
125
156
|
[`docs/DATA_PROVENANCE.md`](./docs/DATA_PROVENANCE.md) for details, or
|
|
126
157
|
`scripts/import-golden-terms.mjs` for the exact extraction method.
|
|
127
158
|
|
|
159
|
+
## Testing
|
|
160
|
+
|
|
161
|
+
`npm test` runs the full suite (`node --test`, no test framework dependency), covering:
|
|
162
|
+
|
|
163
|
+
- All 145 golden entries, both `selfGender` values, checked field-by-field
|
|
164
|
+
(`label`, `reverseLabel`, `formal`, `chon`) — not just a spot check.
|
|
165
|
+
- A dedicated sweep asserting no relation path ever resolves to an `undefined` or
|
|
166
|
+
empty label, across every golden path plus a broad set of fallback-path
|
|
167
|
+
combinations.
|
|
168
|
+
- The compositional fallback's chon arithmetic (plain edge-sum, verified by hand
|
|
169
|
+
against a manual review) covering paths outside the golden table.
|
|
170
|
+
|
|
171
|
+
CI (see badge above) runs this suite on every push, on Node 18 and 20 — the badge
|
|
172
|
+
reflects the actual current state of `main`, not a claim in this README. Separately,
|
|
173
|
+
`resolve(['father', 'youngerBro'])` was checked against a real, independently
|
|
174
|
+
maintained Android app's own verified data contract before this library was wired
|
|
175
|
+
into production use (see [`docs/DATA_PROVENANCE.md`](./docs/DATA_PROVENANCE.md)).
|
|
176
|
+
|
|
128
177
|
## Why not just a lookup table?
|
|
129
178
|
|
|
130
179
|
For the ~145 most common relationships, it is exactly a lookup table — that's Tier 1,
|
|
131
180
|
and it's returned verbatim because it encodes real, occasionally non-obvious Korean
|
|
132
181
|
usage conventions (for example: your sibling's spouse and your spouse's parents are
|
|
133
182
|
conventionally given `chon: 0`, "just family," while your uncle's wife keeps your
|
|
134
|
-
uncle's own `chon: 3` — these aren't derivable from one clean formula
|
|
135
|
-
past that table, `resolve()` falls back to a compositional
|
|
136
|
-
촌수 and blood/in-law class from the path
|
|
137
|
-
|
|
183
|
+
uncle's own `chon: 3` — these aren't derivable from one clean formula, they're curated
|
|
184
|
+
per entry). For anything past that table, `resolve()` falls back to a compositional
|
|
185
|
+
algorithm that computes 촌수 as a plain edge sum and blood/in-law class from the path
|
|
186
|
+
directly — it does not try to re-derive the golden table's "no number for close
|
|
187
|
+
in-laws" convention (an earlier version tried, and silently mis-scored real 4th-degree
|
|
188
|
+
relatives as chon 0), so arbitrarily deep or unusual relationship chains still get an
|
|
189
|
+
arithmetically correct answer instead of an error or a guessed zero.
|
|
138
190
|
|
|
139
191
|
## License
|
|
140
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jeongpd/korean-kinship",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Resolve any Korean family-relationship path to its correct kinship term, reverse term, and 촌수 (degree of kinship) — including in-laws and 사돈.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -51,16 +51,20 @@ function baseTerm(generationDelta, terminalGender) {
|
|
|
51
51
|
/**
|
|
52
52
|
* Last-resort label for a path with no Tier-1 or Tier-2 entry.
|
|
53
53
|
*
|
|
54
|
-
* `chon: 0` means "
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* `chon: 0` means "no numeral" in this codebase's convention (the golden entries with
|
|
55
|
+
* chon 0 — 장인어른, 며느리, 동서 — never carry a number), so the numeral is omitted
|
|
56
|
+
* entirely rather than rendered as a nonsensical "0촌". Note this does NOT mean the
|
|
57
|
+
* relation is necessarily close — the chon-cutoff rule in resolve.js also assigns
|
|
58
|
+
* chon: 0 to any relation reached past the first spouse hop, including genuinely
|
|
59
|
+
* distant ones (e.g. an uncle-by-marriage's own sibling's child) — so the label omits
|
|
60
|
+
* the numeral but must not claim closeness either.
|
|
57
61
|
*/
|
|
58
62
|
export function genericFallback({ chon, generationDelta, relationClass, terminalGender }) {
|
|
59
63
|
const base = baseTerm(generationDelta, terminalGender);
|
|
60
64
|
const numeral = chon > 0 ? `${chon}촌 ` : '';
|
|
61
65
|
|
|
62
66
|
if (relationClass === 'inlaw') {
|
|
63
|
-
return
|
|
67
|
+
return `인척 ${numeral}${base}`;
|
|
64
68
|
}
|
|
65
69
|
return `${numeral}${base}`;
|
|
66
70
|
}
|
package/src/resolve.js
CHANGED
|
@@ -24,9 +24,6 @@ function pickBySelfGender(record, selfGender) {
|
|
|
24
24
|
export function walkPath(path) {
|
|
25
25
|
let generationDelta = 0;
|
|
26
26
|
let chonEdges = 0;
|
|
27
|
-
let bloodChonSoFar = 0;
|
|
28
|
-
let bloodChonAtFirstSpouse = null;
|
|
29
|
-
let firstSpouseIndex = null;
|
|
30
27
|
let relationClass = 'blood';
|
|
31
28
|
let olderYounger = null;
|
|
32
29
|
// Gender of the person the path lands on. Every primitive but `spouse` fixes it
|
|
@@ -34,7 +31,7 @@ export function walkPath(path) {
|
|
|
34
31
|
// hops has no gendered person to anchor on).
|
|
35
32
|
let terminalGender = null;
|
|
36
33
|
|
|
37
|
-
for (const
|
|
34
|
+
for (const id of path) {
|
|
38
35
|
const rel = PRIMITIVES[id];
|
|
39
36
|
generationDelta += rel.generationDelta;
|
|
40
37
|
chonEdges += rel.edgeCost;
|
|
@@ -44,36 +41,29 @@ export function walkPath(path) {
|
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
if (rel.category === 'spouse') {
|
|
47
|
-
if (firstSpouseIndex === null) {
|
|
48
|
-
firstSpouseIndex = index;
|
|
49
|
-
bloodChonAtFirstSpouse = bloodChonSoFar;
|
|
50
|
-
}
|
|
51
44
|
relationClass = 'inlaw';
|
|
52
45
|
if (terminalGender !== null) {
|
|
53
46
|
terminalGender = terminalGender === 'M' ? 'F' : 'M';
|
|
54
47
|
}
|
|
55
48
|
} else {
|
|
56
|
-
bloodChonSoFar += rel.edgeCost;
|
|
57
49
|
terminalGender = rel.gender;
|
|
58
50
|
}
|
|
59
51
|
}
|
|
60
52
|
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
// (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return { generationDelta, relationClass, olderYounger, chon, terminalGender };
|
|
53
|
+
// 촌수 here is always the plain edge sum — spouse hops cost nothing (부부는
|
|
54
|
+
// 무촌), so a spouse's blood relatives count from the spouse exactly as a
|
|
55
|
+
// careful speaker would by hand (배우자의 오빠의 딸의 딸 = 0+2+1+1 = 4촌).
|
|
56
|
+
//
|
|
57
|
+
// The golden table (Tier 1) separately encodes a real but *uncomputable*
|
|
58
|
+
// convention: Korean omits the number entirely for a specific, curated set of
|
|
59
|
+
// close relations (며느리, 처남, 사돈 — the golden entries with chon: 0). That
|
|
60
|
+
// convention does not generalize into a formula — an earlier version of this
|
|
61
|
+
// function tried to derive one ("zero any hop past the first spouse hop") and
|
|
62
|
+
// it silently mis-scored genuine 4th-degree relatives like the example above
|
|
63
|
+
// as chon 0. Since Tier 1 always resolves before walkPath() ever runs, this
|
|
64
|
+
// function doesn't need to reproduce that convention — it only has to be
|
|
65
|
+
// arithmetically correct for paths the golden table doesn't already cover.
|
|
66
|
+
return { generationDelta, relationClass, olderYounger, chon: chonEdges, terminalGender };
|
|
77
67
|
}
|
|
78
68
|
|
|
79
69
|
// The descriptor key deliberately omits 친가/외가/처가/시가 `side`. Detecting it
|