File size: 4,106 Bytes
46d572c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# -*- coding: utf-8 -*-
"""조인 평가 + 회귀 검증 공용 모듈.

각 정규화 단계 전후로 호출해 조인율을 재측정하고, (board, lineno, refpos)
단위로 '이전에 조인되던 인용이 깨졌는지'(회귀)를 정확히 비교한다.
"""
import json
import re

BOARDS = ["016001", "016002", "016003", "016005", "016006"]
DATA = __import__("pathlib").Path(__file__).resolve().parent / "data" / "parsed"


def load_std_index():
    s3001 = [json.loads(l) for l in (DATA / "3001.jsonl").open(encoding="utf-8")]
    s3003 = [json.loads(l) for l in (DATA / "3003.jsonl").open(encoding="utf-8")]
    return {
        "k_para": {r["ref_key"] for r in s3001 if r["record_type"] == "paragraph"},
        "k_tsec": {r["section_key"] for r in s3001 if r["record_type"] == "term"},
        "k_nos": {r["standard_no"] for r in s3001},
        "g_para": {r["ref_key"] for r in s3003},
        "g_ch": {re.search(r"제(\d+)장", r["standard_no"]).group(1) for r in s3003},
    }


def classify(ref):
    """참조 유형 판정: (kind, precision). precision=True면 문단/용어/장문단(정밀)."""
    if re.match(r"^제\d{1,2}장", ref):
        if "문단" in ref:
            return "장문단", True
        return ("장제목", False) if "'" in ref else ("단독장", False)
    if re.match(r"^제\d{3,4}호", ref):
        if ref.endswith("용어의 정의"):
            return "용어섹션", True
        if "문단" in ref:
            return "문단", True
        return ("제목", False) if "'" in ref else ("단독호", False)
    return "기타", False


def matches(ref, idx):
    """현재 ref가 기준서 레코드와 조인되는지 (정밀은 exact, 상위는 식별)."""
    kind, _ = classify(ref)
    if kind == "장문단":
        return ref in idx["g_para"]
    if kind in ("장제목", "단독장"):
        ch = re.match(r"제(\d+)장", ref).group(1)
        return ch in idx["g_ch"]
    if kind == "용어섹션":
        return ref in idx["k_tsec"]
    if kind == "문단":
        return ref in idx["k_para"]
    if kind in ("제목", "단독호"):
        no = re.match(r"제(\d+)호", ref).group(1)
        return no in idx["k_nos"]
    return False


def evaluate():
    """현재 JSONL 상태로 조인 측정. 반환: dict(rates, joined_precise set, joined_any set)."""
    idx = load_std_index()
    prec_tot = prec_hit = any_tot = any_hit = 0
    joined_precise = set()   # (board, lineno, refpos)
    joined_any = set()
    per_kind = {}
    for b in BOARDS:
        for ln, line in enumerate((DATA / (b + ".jsonl")).open(encoding="utf-8")):
            refs = json.loads(line).get("standard_refs", [])
            for pos, ref in enumerate(refs):
                kind, precise = classify(ref)
                ok = matches(ref, idx)
                d = per_kind.setdefault(kind, [0, 0])
                d[1] += 1
                if ok:
                    d[0] += 1
                    joined_any.add((b, ln, pos))
                any_tot += 1
                any_hit += ok
                if precise:
                    prec_tot += 1
                    prec_hit += ok
                    if ok:
                        joined_precise.add((b, ln, pos))
    return {
        "prec": (prec_hit, prec_tot), "any": (any_hit, any_tot),
        "per_kind": per_kind,
        "joined_precise": joined_precise, "joined_any": joined_any,
    }


def report(r, label=""):
    ph, pt = r["prec"]; ah, at = r["any"]
    print(f"[{label}] 정밀 {ph}/{pt}={100*ph/pt:.1f}% | 전체 {ah}/{at}={100*ah/at:.1f}%")
    for k in ["문단", "용어섹션", "제목", "단독호", "장문단", "장제목", "단독장"]:
        if k in r["per_kind"]:
            h, t = r["per_kind"][k]
            print(f"    {k}: {h}/{t} ({100*h/t:.0f}%)")


def regression(before, after):
    """이전에 조인되던 (board,lineno,refpos)가 깨진 건수."""
    lost_prec = before["joined_precise"] - after["joined_precise"]
    lost_any = before["joined_any"] - after["joined_any"]
    return lost_prec, lost_any


if __name__ == "__main__":
    report(evaluate(), "현재")