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(), "현재")
|