docs: 적재본 545건의 출처 내역 추가 (컴북스 31 / 위원회 514)
문제: 리스트업이 선별 57건만 다뤄서, "표절 탐지에 실제로 쓰이는 판례가 무엇인가"에
답할 수 없었다. 엔진이 로드하는 것은 57건이 아니라 적재본 545건 전부이고
(PRECEDENTS_PATH), 판정 1건마다 유형·태그가 맞는 상위 5건을 인용한다.
또 컴북스 제공분이 얼마나 반영됐는지가 어디에도 기재돼 있지 않았다. 데이터에는
레코드의 excel_duplicate 필드로 남아 있는데 문서에 나오지 않아, 회의에서
"우리가 준 판례는 어떻게 됐나"라는 질문에 답할 수 없는 상태였다.
컴북스 제공 고유 사건번호 142
공식 상세 페이지 미확인 → 제외 -103
엔진 라벨 부족 → 제외 -8
엔진 적재본에 포함 31 (21.8%)
이 중 리스트업 선정 10 (A 3 · B 6 · C 1)
제외된 103건은 사건번호만으로는 인용 출처가 되지 않아 적재하지 못했다. 판결문
원문이나 확인 경로를 받으면 적재 가능하므로 컴북스에 회신을 요청해야 하고, 그
목록을 Excel 시트에 그대로 실었다.
Excel 에 시트 2개 추가:
적재본 전체 545건 전수. 출처(컴북스/위원회)와 리스트업 등급을 함께 표시
컴북스 제공분 단계별 반영 내역, 적재된 31건, 회신 요청할 103건 목록
주의로 남긴 것: 컴북스 목록의 주제 분류와 실제 판시가 어긋나는 건이 리스트업에도
흘러들어갔다. 컴북스 유래 A등급 3건 중 2012다73493(깃털 공예품)은 어문 사건이
아니며 2017다212095(게임)도 마찬가지다. 인용 전 원문 대조가 필요하다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ba1a0bbc2f
commit
a45c79211c
@ -56,6 +56,43 @@
|
||||
|
||||
---
|
||||
|
||||
## 2-1. 적재본 545건의 출처 — 표절 탐지에 실제로 쓰이는 판례
|
||||
|
||||
**엔진이 런타임에 로드하는 것은 이 리스트업 57건이 아니라 적재본 545건 전부다**
|
||||
(`PRECEDENTS_PATH=./data/precedents/precedents.jsonl`). 판정 1건마다 저작물 유형과
|
||||
법적 태그가 맞는 **상위 5건**을 골라 근거로 인용한다(`app/engine/legal_risk.py`).
|
||||
리스트업 57건은 사람이 읽는 선별 문서이며 엔진 동작에 관여하지 않는다.
|
||||
|
||||
| 출처 | 건수 | 비고 |
|
||||
|---|---:|---|
|
||||
| 한국저작권위원회 공식 목록 수집분 | 514 | 공식 판례 목록 2,085건에서 제목·라벨 기준으로 선별 |
|
||||
| **컴북스 제공 엑셀 유래** | **31** | 레코드의 `excel_duplicate` 필드가 `true` |
|
||||
| **합계 (엔진 적재본)** | **545** | |
|
||||
|
||||
### 컴북스 제공분의 반영 내역
|
||||
|
||||
| 단계 | 건수 | 설명 |
|
||||
|---|---:|---|
|
||||
| 제공 엑셀 고유 사건번호 | 142 | 중복 제거 기준 |
|
||||
| 공식 상세 페이지 미확인 → 제외 | −103 | 사건번호는 있으나 공식 상세 페이지를 찾지 못한 건. 출처를 임의 생성하지 않기 위해 제외했다 |
|
||||
| 엔진 라벨 부족 → 제외 | −8 | 저작물 유형·판단 기준·법적 태그가 모두 없는 건 |
|
||||
| **엔진 적재본에 포함** | **31** | 런타임에 실제로 로드된다 |
|
||||
| 이 중 본 리스트업에 선정 | 10 | A 3 · B 6 · C 1 |
|
||||
|
||||
즉 컴북스가 제공한 142건 중 실제로 엔진에 들어간 것은 **31건(21.8%)** 이다.
|
||||
|
||||
**되돌려야 할 항목** — 제외된 103건은 사건번호만으로는 엔진이 인용할 출처가 되지
|
||||
않아 적재하지 못했다. **판결문 원문이나 확인 경로를 받으면 그대로 적재할 수 있다.**
|
||||
전체 사건번호 목록은 `selection_audit.json` 의
|
||||
`excel_without_official_detail_case_ids` 와 배포용 Excel 의 「컴북스 제공분」 시트에 있다.
|
||||
|
||||
> ⚠️ 컴북스 제공 목록의 주제 분류와 실제 판시가 어긋나는 건이 리스트업에도 흘러들어갔다.
|
||||
> 컴북스 유래 A등급 3건 중 `2012다73493`(깃털 공예품)은 어문 사건이 아니며,
|
||||
> `2017다212095`(게임)도 마찬가지다. 6-3의 2번 항목과 같은 문제이므로 인용 전
|
||||
> 원문 대조가 필요하다.
|
||||
|
||||
---
|
||||
|
||||
## 3. A등급 — 직접 적용 (24건)
|
||||
|
||||
| 사건번호 | 법원 | 선고일 | 주제 | 쟁점 요약 | 적용 판단 근거·한계 |
|
||||
|
||||
Binary file not shown.
@ -22,6 +22,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
@ -249,6 +250,183 @@ def sheet_list(wb: Workbook, rows: list[dict]) -> None:
|
||||
ws.sheet_view.zoomScale = 90
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 시트 — 적재본 전체 (엔진이 실제로 쓰는 판례)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
CORPUS_COLUMNS = [
|
||||
("_source", "출처", 12, False),
|
||||
("_grade", "등급", 6, False),
|
||||
("case_id", "사건번호", 15, False),
|
||||
("title", "사건명", 44, True),
|
||||
("_work", "저작물 유형", 14, True),
|
||||
("_tags", "법적 태그", 24, True),
|
||||
("_criteria", "판단 기준", 26, True),
|
||||
("holding_summary", "판시 요약", 74, True),
|
||||
("source_registered_date", "등록일", 11, False),
|
||||
("source_url", "출처", 12, False),
|
||||
]
|
||||
|
||||
|
||||
def sheet_corpus(wb: Workbook, corpus: list[dict], rows: list[dict]) -> None:
|
||||
"""545건 전수. 어느 것이 컴북스 제공분이고 어느 것이 리스트업에 뽑혔는지 표시한다."""
|
||||
ws = wb.create_sheet("적재본 전체")
|
||||
grade_of = {r["case_id"]: r["grade"] for r in rows}
|
||||
n_excel = sum(1 for r in corpus if r.get("excel_duplicate"))
|
||||
|
||||
head = _title_block(
|
||||
ws,
|
||||
f"엔진 적재본 전체 {len(corpus)}건 — 표절 탐지에 실제로 사용되는 판례",
|
||||
f"런타임에 이 {len(corpus)}건을 모두 로드하고, 판정 1건마다 저작물 유형·법적 태그가 "
|
||||
f"맞는 상위 5건을 근거로 인용한다(app/engine/legal_risk.py). "
|
||||
f"출처는 컴북스 제공 {n_excel}건, 한국저작권위원회 공식 목록 {len(corpus) - n_excel}건. "
|
||||
"'등급'이 채워진 행이 리스트업 57건이다.",
|
||||
width_cols=len(CORPUS_COLUMNS),
|
||||
)
|
||||
_header_row(ws, head, [label for _, label, _, _ in CORPUS_COLUMNS])
|
||||
_widths(ws, [w for _, _, w, _ in CORPUS_COLUMNS])
|
||||
|
||||
order = {"A": 0, "B": 1, "C": 2}
|
||||
corpus = sorted(
|
||||
corpus,
|
||||
key=lambda r: (
|
||||
0 if r.get("excel_duplicate") else 1,
|
||||
order.get(grade_of.get(r["case_id"], ""), 9),
|
||||
r["case_id"],
|
||||
),
|
||||
)
|
||||
|
||||
for i, rec in enumerate(corpus):
|
||||
r = head + 1 + i
|
||||
grade = grade_of.get(rec["case_id"], "")
|
||||
derived = {
|
||||
"_source": "컴북스" if rec.get("excel_duplicate") else "위원회",
|
||||
"_grade": grade,
|
||||
"_work": " / ".join(rec.get("work_types") or []),
|
||||
"_tags": " / ".join(rec.get("legal_tags") or []),
|
||||
"_criteria": " / ".join(rec.get("criteria") or []),
|
||||
}
|
||||
for c, (field, _, _, wrap) in enumerate(CORPUS_COLUMNS, start=1):
|
||||
cell = ws.cell(row=r, column=c)
|
||||
if field == "source_url":
|
||||
url = rec.get("source_url") or ""
|
||||
if url:
|
||||
cell.value = "원문 링크"
|
||||
cell.hyperlink = url
|
||||
cell.font = LINK_FONT
|
||||
cell.alignment = TOP_LEFT
|
||||
cell.border = CELL_BORDER
|
||||
continue
|
||||
cell.value = derived.get(field, rec.get(field, "") or "")
|
||||
_body(cell, wrap=wrap)
|
||||
if field == "_grade":
|
||||
cell.alignment = CENTER
|
||||
if grade in GRADE_FILL:
|
||||
cell.fill = GRADE_FILL[grade]
|
||||
cell.font = Font(name="맑은 고딕", size=10, bold=True, color=INK)
|
||||
elif field == "_source":
|
||||
cell.alignment = CENTER
|
||||
if rec.get("excel_duplicate"):
|
||||
cell.font = Font(name="맑은 고딕", size=10, bold=True, color="B03A22")
|
||||
|
||||
last = head + len(corpus)
|
||||
ws.auto_filter.ref = f"A{head}:{get_column_letter(len(CORPUS_COLUMNS))}{last}"
|
||||
ws.freeze_panes = ws.cell(row=head + 1, column=4)
|
||||
ws.sheet_view.zoomScale = 85
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 시트 — 컴북스 제공분 내역
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def sheet_combooks(wb: Workbook, corpus: list[dict], rows: list[dict], audit: dict) -> None:
|
||||
ws = wb.create_sheet("컴북스 제공분")
|
||||
_widths(ws, [40, 10, 74])
|
||||
grade_of = {r["case_id"]: r["grade"] for r in rows}
|
||||
kept = [r for r in corpus if r.get("excel_duplicate")]
|
||||
picked = [r for r in kept if r["case_id"] in grade_of]
|
||||
missing = audit.get("excel_without_official_detail_case_ids", [])
|
||||
total = audit.get("excel_unique_case_count", 0)
|
||||
|
||||
r = _title_block(
|
||||
ws, "컴북스 제공 판례의 반영 내역",
|
||||
"회의에서 '우리가 준 판례는 어떻게 됐는가'에 답하기 위한 표다. "
|
||||
"제공받은 사건번호가 모두 적재된 것은 아니며, 빠진 건에는 사유가 있다.",
|
||||
width_cols=3,
|
||||
)
|
||||
|
||||
ws.cell(row=r, column=1, value="■ 단계별 반영").font = SECTION_FONT
|
||||
r += 1
|
||||
_header_row(ws, r, ["단계", "건수", "설명"])
|
||||
r += 1
|
||||
funnel = [
|
||||
("제공 엑셀 고유 사건번호", total, "중복 제거 기준"),
|
||||
("공식 상세 페이지 미확인 → 제외", len(missing),
|
||||
"한국저작권위원회 공식 사이트에서 상세 페이지를 찾지 못한 건. 출처를 임의 생성하지 않기 위해 제외했다. 아래 목록 참조."),
|
||||
("엔진 라벨 부족 → 제외", total - len(missing) - len(kept),
|
||||
"저작물 유형·판단 기준·법적 태그가 모두 없어 무차별 매칭을 유발하는 건"),
|
||||
("엔진 적재본에 포함", len(kept), "런타임에 실제로 로드된다"),
|
||||
("리스트업에 선정", len(picked),
|
||||
f"A {sum(1 for x in picked if grade_of[x['case_id']] == 'A')} · "
|
||||
f"B {sum(1 for x in picked if grade_of[x['case_id']] == 'B')} · "
|
||||
f"C {sum(1 for x in picked if grade_of[x['case_id']] == 'C')}"),
|
||||
]
|
||||
for name, n, note in funnel:
|
||||
ws.cell(row=r, column=1, value=name)
|
||||
ws.cell(row=r, column=2, value=n)
|
||||
ws.cell(row=r, column=3, value=note)
|
||||
for c in (1, 2, 3):
|
||||
_body(ws.cell(row=r, column=c))
|
||||
ws.cell(row=r, column=2).alignment = CENTER
|
||||
if name.startswith("엔진 적재본"):
|
||||
for c in (1, 2, 3):
|
||||
ws.cell(row=r, column=c).font = Font(name="맑은 고딕", size=10, bold=True, color=INK)
|
||||
r += 1
|
||||
|
||||
r += 1
|
||||
ws.cell(row=r, column=1, value=f"■ 적재된 {len(kept)}건").font = SECTION_FONT
|
||||
r += 1
|
||||
_header_row(ws, r, ["사건번호", "등급", "사건명"])
|
||||
r += 1
|
||||
for rec in sorted(kept, key=lambda x: (grade_of.get(x["case_id"], "Z"), x["case_id"])):
|
||||
g = grade_of.get(rec["case_id"], "")
|
||||
ws.cell(row=r, column=1, value=rec["case_id"])
|
||||
ws.cell(row=r, column=2, value=g or "—")
|
||||
ws.cell(row=r, column=3, value=rec.get("title", ""))
|
||||
for c in (1, 2, 3):
|
||||
_body(ws.cell(row=r, column=c))
|
||||
ws.cell(row=r, column=2).alignment = CENTER
|
||||
if g in GRADE_FILL:
|
||||
ws.cell(row=r, column=2).fill = GRADE_FILL[g]
|
||||
r += 1
|
||||
|
||||
r += 1
|
||||
ask = ws.cell(
|
||||
row=r, column=1,
|
||||
value=f"■ 출처 확보가 필요한 {len(missing)}건 — 컴북스 회신 요청 항목",
|
||||
)
|
||||
ask.font = Font(name="맑은 고딕", size=11, bold=True, color="B03A22")
|
||||
r += 1
|
||||
note = ws.cell(
|
||||
row=r, column=1,
|
||||
value="사건번호는 받았으나 한국저작권위원회 공식 상세 페이지를 찾지 못해 적재하지 못한 건이다. "
|
||||
"판결문 원문이나 확인 경로를 주시면 그대로 적재할 수 있다. "
|
||||
"사건번호만으로는 엔진이 인용할 출처가 되지 않아 임의로 만들지 않았다.",
|
||||
)
|
||||
note.font = NOTE_FONT
|
||||
note.alignment = TOP_WRAP
|
||||
ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=3)
|
||||
ws.row_dimensions[r].height = 32
|
||||
r += 2
|
||||
per_row = 3
|
||||
for i in range(0, len(missing), per_row):
|
||||
for j, cid in enumerate(missing[i:i + per_row]):
|
||||
cell = ws.cell(row=r, column=1 + j, value=cid)
|
||||
_body(cell, wrap=False)
|
||||
r += 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 시트 2 — 요약·방법
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -427,6 +605,19 @@ def sheet_questions(wb: Workbook) -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def load_corpus(path: Path) -> list[dict]:
|
||||
"""엔진이 실제로 로드하는 적재본. 리스트업 57건이 아니라 이쪽이 런타임 후보 풀이다."""
|
||||
if not path.exists():
|
||||
raise SystemExit(f"{path} 가 없습니다.")
|
||||
return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
||||
|
||||
|
||||
def load_audit(path: Path) -> dict:
|
||||
if not path.exists():
|
||||
raise SystemExit(f"{path} 가 없습니다.")
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def load_rows(path: Path) -> list[dict]:
|
||||
if not path.exists():
|
||||
raise SystemExit(
|
||||
@ -445,14 +636,23 @@ def main() -> int:
|
||||
)
|
||||
ap.add_argument("--csv", type=Path,
|
||||
default=ROOT / "data/precedents/precedent_listup.csv")
|
||||
ap.add_argument("--corpus", type=Path,
|
||||
default=ROOT / "data/precedents/precedents.jsonl",
|
||||
help="엔진이 실제로 로드하는 적재본")
|
||||
ap.add_argument("--audit", type=Path,
|
||||
default=ROOT / "data/precedents/selection_audit.json")
|
||||
ap.add_argument("--out", type=Path, default=ROOT / "docs/판례_리스트업.xlsx")
|
||||
args = ap.parse_args()
|
||||
|
||||
rows = load_rows(args.csv)
|
||||
corpus = load_corpus(args.corpus)
|
||||
audit = load_audit(args.audit)
|
||||
|
||||
wb = Workbook()
|
||||
wb.remove(wb.active) # 기본 시트 제거
|
||||
sheet_list(wb, rows)
|
||||
sheet_corpus(wb, corpus, rows)
|
||||
sheet_combooks(wb, corpus, rows, audit)
|
||||
sheet_summary(wb, rows)
|
||||
sheet_excluded(wb)
|
||||
sheet_gaps(wb)
|
||||
@ -462,8 +662,10 @@ def main() -> int:
|
||||
wb.save(args.out)
|
||||
|
||||
counts = Counter(r["grade"] for r in rows)
|
||||
n_excel = sum(1 for r in corpus if r.get("excel_duplicate"))
|
||||
print(f"생성: {args.out}")
|
||||
print(f" 판례 {len(rows)}건 (A {counts['A']} / B {counts['B']} / C {counts['C']})")
|
||||
print(f" 적재본 {len(corpus)}건 — 컴북스 {n_excel} / 위원회 {len(corpus) - n_excel}")
|
||||
print(f" 리스트업 {len(rows)}건 (A {counts['A']} / B {counts['B']} / C {counts['C']})")
|
||||
print(f" 시트 {len(wb.sheetnames)}개: {', '.join(wb.sheetnames)}")
|
||||
return 0
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user