feat: finalize report at 9-eojeol threshold
기준을 9어절(35자)로 확정하고 문서를 그 기준으로 다시 만든다. 중복 제거 + 9어절 재실행 결과는 검사 대상 6087건, 침해의심 80건이다. 9어절 근거는 문서쌍 전수 대조다. 545문서 148,240쌍을 모두 대조하니 100어절(390자)까지 올려도 18쌍이 계속 겹친다. 겹침이 0이 되는 어절 수는 없다. 10~15어절에서 27쌍으로 고정되어 더 줄지 않는데, 오탐이라면 기준을 올릴수록 계속 줄어야 한다. 즉 그 지점부터 남는 것은 실제 유사 원고다. 상투어 노이즈는 4->5어절에서 대부분 걷히므로 9어절이 실질적 하한이다. 배포 파일을 하나로 합친다. 원문 대조 시트를 결과보고에 넣어 의심 80건의 원문을 같은 파일에서 볼 수 있게 하고, 숫자가 맞지 않는 중복 제거 전 파일 두 개는 배포 위치에서 내린다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
bf3bd5b99b
commit
94a660dc35
6087
reports/batch_final_9eojeol.jsonl
Normal file
6087
reports/batch_final_9eojeol.jsonl
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -179,8 +179,8 @@ def build_summary_sheet(wb: Workbook, rows: list[dict], *, backend: str, notes:
|
||||
CRITERIA_ROWS = [
|
||||
(1, "결합유사도", "≥ 0.65", "persistent_similarity_threshold",
|
||||
"네 가지 유사도를 가중 합산한 값이 임계값 이상"),
|
||||
(2, "최장 연속 일치 길이", "≥ 80자", "persistent_min_exact_span",
|
||||
"두 글이 끊기지 않고 그대로 이어지는 최장 구간"),
|
||||
(2, "최장 연속 일치 길이", "≥ 35자 (9어절)", "persistent_min_exact_span",
|
||||
"두 글이 끊기지 않고 그대로 이어지는 최장 구간. 근거는 '어절 기준 비교' 시트"),
|
||||
(3, "문서 단위 일치 커버리지", "≥ 0.30 (30%)", "persistent_min_coverage",
|
||||
"질의 문서 전체에서 일치 구간이 차지하는 비율(중복 제외)"),
|
||||
]
|
||||
@ -264,7 +264,7 @@ def build_criteria_sheet(wb: Workbook, rows: list[dict], excerpts: list[dict] |
|
||||
note("아래 세 줄은 중복 집계입니다. 한 건이 여러 조건을 동시에 충족할 수 있어 합계가 103을 넘습니다.")
|
||||
sheet.append(["조건 ① 유사도 (0.65 이상) 충족", condition_counts["①유사도"], ""])
|
||||
if excerpts:
|
||||
sheet.append(["조건 ② 연속일치 (80자 이상) 충족", condition_counts["②연속일치"], "원문 대조로 실측"])
|
||||
sheet.append(["조건 ② 연속일치 (35자·9어절 이상) 충족", condition_counts["②연속일치"], "원문 대조로 실측"])
|
||||
sheet.append(["조건 ③ 커버리지 (30% 이상) 충족", condition_counts["③커버리지"], ""])
|
||||
sheet.append([])
|
||||
sheet.append(["판정 사유 조합별 건수", "", "'원문 대조' 시트의 판정 사유 컬럼과 같은 값. 합계 = 103"])
|
||||
@ -390,7 +390,11 @@ def build_duplicate_sheet(wb: Workbook, excerpts: list[dict], records: dict[str,
|
||||
|
||||
|
||||
|
||||
def build_sweep_sheet(wb: Workbook, sweep: list[dict], rows: list[dict]) -> None:
|
||||
TOTAL_DOC_PAIRS = 148240 # 545문서 전수 조합
|
||||
|
||||
|
||||
def build_sweep_sheet(wb: Workbook, sweep: list[dict], rows: list[dict],
|
||||
exhaustive: list[dict] | None = None) -> None:
|
||||
"""어절 기준을 3~9로 바꿔가며 재판정한 결과.
|
||||
|
||||
기준값은 검색 후보를 바꾸지 않고 채택 여부만 정하므로, 한 번 실행해 남긴
|
||||
@ -436,6 +440,36 @@ def build_sweep_sheet(wb: Workbook, sweep: list[dict], rows: list[dict]) -> None
|
||||
)
|
||||
sheet.append([f"기준과 무관하게 걸리는 건 (유사도 0.65 또는 커버리지 30% 충족): {base}건"])
|
||||
sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True)
|
||||
if exhaustive:
|
||||
sheet.append([])
|
||||
sheet.append(["기준을 계속 올리면 겹침이 사라지는가 — 전수 확인"])
|
||||
sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True, size=12)
|
||||
sheet.append([f"표본이 아니라 545개 문서의 전체 조합 {TOTAL_DOC_PAIRS:,}쌍을 모두 대조한 결과입니다."])
|
||||
sheet.cell(row=sheet.max_row, column=1).font = NOTE_FONT
|
||||
header2 = ["어절", "중복 제거 전 겹치는 문서쌍", "중복 제거 후", "전체 문서쌍 대비"]
|
||||
sheet.append(header2)
|
||||
for index in range(1, len(header2) + 1):
|
||||
cell = sheet.cell(row=sheet.max_row, column=index)
|
||||
cell.font = Font(bold=True)
|
||||
cell.fill = HEADER_FILL
|
||||
for item in exhaustive:
|
||||
sheet.append([
|
||||
f"{item['어절']}어절", item["제거전"], item["제거후"],
|
||||
item["제거후"] / TOTAL_DOC_PAIRS,
|
||||
])
|
||||
sheet.cell(row=sheet.max_row, column=4).number_format = "0.00000%"
|
||||
for line in (
|
||||
"100어절(약 390자)까지 올려도 18개 문서쌍은 계속 겹칩니다. 겹침이 0이 되는 어절 수는 존재하지 않습니다.",
|
||||
"10~15어절 구간에서 27쌍으로 고정되어 더 줄지 않습니다. 오탐이라면 기준을 올릴수록 계속 줄어야 합니다.",
|
||||
"서로 다른 문서에 390자가 연속으로 같다는 것은 우연이 아니라 실제로 같은 내용이 들어 있다는 뜻입니다.",
|
||||
"따라서 기준은 '겹침이 0이 되는 값'이 아니라, 상투어 노이즈가 걷히는 지점으로 정해야 합니다.",
|
||||
"상투어 노이즈는 4→5어절에서 대부분 걷히고, 9어절부터는 남는 것이 전부 실제 유사 원고입니다.",
|
||||
"이 근거로 9어절(35자)을 적용했습니다.",
|
||||
):
|
||||
sheet.append([line])
|
||||
sheet.cell(row=sheet.max_row, column=1).font = NOTE_FONT
|
||||
sheet.append([])
|
||||
|
||||
for line in (
|
||||
"어절 기준을 아무리 낮춰도 이 건수는 줄지 않습니다. 어절 기준이 더하는 몫이 '연속일치로만 걸린 건'입니다.",
|
||||
"3어절과 4어절은 임의 조합의 99% 이상이 겹쳐, 침해 신호로 쓸 수 없습니다. 겹친 표현은 '할 수 있는', '할 수 있을 것' 같은 상투어였습니다.",
|
||||
@ -450,7 +484,7 @@ def build_sweep_sheet(wb: Workbook, sweep: list[dict], rows: list[dict]) -> None
|
||||
|
||||
|
||||
SCORE_MIN = 0.65
|
||||
SPAN_MIN = 80
|
||||
SPAN_MIN = 35
|
||||
COVERAGE_MIN = 0.30
|
||||
|
||||
|
||||
@ -591,6 +625,8 @@ def main() -> int:
|
||||
parser.add_argument("--backend", default="", help="검색 백엔드 표기 (미지정 시 JSONL 값 사용)")
|
||||
parser.add_argument("--note", action="append", default=[], help="요약 시트에 남길 유의사항")
|
||||
parser.add_argument("--sweep-json", type=Path, help="어절 기준 비교 시트용 데이터")
|
||||
parser.add_argument("--exhaustive-json", type=Path,
|
||||
help="문서쌍 전수 대조 결과 (겹침이 0이 되는지 확인한 표)")
|
||||
parser.add_argument("--dup-batch-jsonl", type=Path,
|
||||
help="중복 원고 시트를 만들 때 참조할 이전 회차 배치 JSONL")
|
||||
parser.add_argument("--no-excerpt-sheet", action="store_true",
|
||||
@ -621,6 +657,8 @@ def main() -> int:
|
||||
if args.sweep_json:
|
||||
build_sweep_sheet(
|
||||
wb, json.loads(args.sweep_json.read_text(encoding="utf-8")), rows,
|
||||
json.loads(args.exhaustive_json.read_text(encoding="utf-8"))
|
||||
if args.exhaustive_json else None,
|
||||
)
|
||||
build_criteria_sheet(wb, rows, excerpts if args.excerpts_jsonl else None)
|
||||
build_summary_sheet(wb, rows, backend=backend, notes=args.note)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user