From f4fab1089ecd95c05b5529ab96e7daa9c4b87703 Mon Sep 17 00:00:00 2001 From: hbyang Date: Thu, 3 Sep 2026 10:44:18 +0900 Subject: [PATCH] feat: fold duplicate analysis into the report workbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 별도 파일을 늘리지 않고 기존 결과보고 워크북에 '중복 원고' 시트로 넣는다. 중복 제거 후 재실행하면 28건이 결과에서 사라지므로, 사라지기 전에 어떤 문서 사이의 중복이었는지 원문째로 남겨야 나중에 같은 제출자의 재제출인지 다른 제출자의 표절인지 확인할 수 있다. --no-excerpt-sheet 로 원문 대조 시트 없이 중복 시트만 붙일 수 있게 했다. 결과보고는 원문 없는 배포판이라 이 경로를 쓴다. Co-Authored-By: Claude Opus 5 (1M context) --- reports/침해판별_결과보고_20260902.xlsx | Bin 348328 -> 389460 bytes scripts/build_duplicate_report.py | 175 ------------------------ scripts/build_report_xlsx.py | 96 ++++++++++++- 3 files changed, 95 insertions(+), 176 deletions(-) delete mode 100644 scripts/build_duplicate_report.py diff --git a/reports/침해판별_결과보고_20260902.xlsx b/reports/침해판별_결과보고_20260902.xlsx index 9df3099ab1f8f08b165b1c9101a78b14fe5cf0ae..002b78689ea97eeeb9708c4379e2ba1bf20e82d2 100644 GIT binary patch delta 51920 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

?t`nm|WkI9JBS@*2*CdK&udA7!vm_ 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 N|)ES3e|6Mh;9y?IFZW1-^K4f%J-@Z@cUeC03s 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%0DZ#wAOWQMou|BL+=^NcaIs5Eg*MC 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

HLxcO1r%K6-yNn|Yl-5ehm zUrg$5;(hUPwx;_u0Etnvo#5i`5=SQ3`ofB5MBQjr2OdIIS-@1rZLWJ2y~m71d~}m5-P>ZvRU%u80JUUS?4Fs?w$4jwcDe zD368Tr$z71L(IR8=!FJw&fh@w2{oAW%Z1+UsNyQ3OlBIn0AgVy_-M#18#PCLUQQe~ z(&drKUQ1j2e({z$JcB8zL!>mrOd4$YJtrl}AV<{vVUxB3l=?d}nqo;$A$tvxMzm;! zp$>z}Bo*yABis7OQ_fqV?w4V4J(`Vv0L7gMe-e&s*tn5=$&6KRng?*GY|d+sj*>X9 zng(u4M&fxG06)i!42PM4+WJV*+BXe-yUh+M?bdWb_(e1Q;#wZXcJ0m~{5X;gpsP?3 z)r)CKQ)wGNqycfOClE1=qi*XELbbD-YP8cLDby)9lRCGo!rzG&-p3J7bKsy&LOJgWan4y?w0I*-H@QUpU3~e6d7XE`cR;b4z zH{Ze_FZ>!8$!T4Brp~1V7nKa&kVT`G)$fx>UW?e6etkA}+E;6yy_OLRlNaOCmrE zekh1FFyi6ivJb{Y?^8|9eX(#O{<${t9DNjIXh9(r&ky5mUzHYi#vt&#Kp$8e_D3>P zu^xjpTByI#d0{WGVYV_)>dcT|xsk>{oT1^P zv7Ic_GX~^vpr0v1jaYhu4=HWYtT_i`}vWD0vtXQ|g3rVd~NZ^tC+YmDrldnYUKMce8T<={yma6JaK zPu?6Ryq>C9!J*)HeYY3d??$1KBoy-HSCIVaU{5k^R?~m z#YTNTi9bagg|&S5x7$J7mc6Z0I0N7afi8GSTPWAb#k%);Pv!hc*=56kKT$*)V}kDc z5%}jw&f<7=2X7gi)Hh6yMPa{as!mm|k$RRvqy5t&0tYJ!z3sHCctdadlAol-vKfJ4 z{l2!x`D&rPT@M4mRaFRAWJxf@#Q)*kcC_tU2{=N7W}luI4hJmpRf5FsC#pmDz(49% z(rZzcg0)7(UxOEE3IH;!0&``MlP5eex@C}UBFbct%iB`Egi@6Ld2l+%ELLr!{x`gJ*=IZXS85DR=*#=z-=S7a zX>XH%YxyU|Bfaup?}rEjKq1tH^~Ar$sB~Gx7?r`^>BiBz-G36NQ3agiW>W>!j3-n5 z^?slV_$Tfs@lrF^_d+N;fuB?XmtQ%51 z(M-*CYX+;5I^ju+H_JXHK|~F?5A_vW5bIn3Kwe=jhu>hbHBTwqrx`Od)1Ba%f_uA* zkwH!pz656qZB6%rm@&{qd{g-ChF)?R#!+NQrf$snV5X$8n?YD(+r>mCw3C|Mzk~jZ z|E<^q_c=l;bPmzinetj9&|! zA$=pnQF5&7Q zXEM6EL6X_F0~ZOqlFf-~QOlxas>C-`)ZwY?#kq!C%P24i*O*9JIX4fu`WIFdK$t+^f{<97gF)v%41$r%lap%}13iFV3UfzQV-D-t6q= z)uxno&Q@1QvdtAi``wU+B<8OlvBYw1b`NoDV=Bvb z=t*X6g?ohcXh};x;QK=E4Z;6*T5XQNPp+pMa`|<1mFn0T+-5_)R> z-22|=_!a&Z)4$5|w>TX0_NANp^6yq?rY2~nYv?yi3PV#u_6#-%MV*wg!b{|6dHP7f zIC;A}_>lmQRXd#}4%}B@m286VWf?lVG59@-kAAU60`ipn3_!|hM>7IjbjGk2f?Eg7 zla6!(k_meV<7{qQwy9i8UD4d=D^^R0?Fr{KfxNg2i}(v!h^HI8<(pfgdlfaZ#7sv@ z$2~3Q_x3^Da}o?2rIZM<_Khw=Eda1eD$=_QM$ejd!-e*(nZ(!_*c@X0Clx`Rg z@8^I`z{k_|Eg}Iw;OSl9^SoD{;Om1y;QhJt^W$bQ7T|SpciuYaybE}~l^2-(5_?(g zeA}HJeu|aP5dgeYy#u}!Yzqi{)V_D!Zhqb!&zisW+UM+kP8R8Z)B<)NKVcQVgMy&} z0Rce)%_x)TE}n*2Kf?k6rLh75VF9J4GlSy*`n9#4SO1`Y(BQeJFD0shI1+UypZ{#g zvTI7rGE0zpEjF}SIS@tBO=zrRcs~bsa3FyWuzHya?L(3BJN(h%jNtOIQoq?USbCCp z`MRFh%ISIbT9mVJdht-RlDb~%S&FbT;X8XVs^?=%u&}PIo$PKk{iA6B%JkG%lR%Ch zFgf24d%o{^yLNdZa$h6ca`@h11L!Pu8$a!r@-m|LDJA&k?P=)i`JuwEl2i6EIy7``mUa zz*YsQU=@+Ma9=&+z6adEOzxbKHv3+@m{noI!aUpqf23?S5Wa!Cm;o=qURel@m}4Nd z`$b_OvDW#WfVE&wZgI-EIaG2`rWjBpm&x{oH+uZ~vba+L_sr$x#S!Ffb z_pfV}pEAs<-W(vpIY+SXFpN{K55ZCqYv|;P`Ny>gZi9ShlG}z(D5bEFem?M7ht=AP zIn}e^kS?kn@Y89pA$biSGzxE`TCSqI!wbDAgFTsPS(f z8qofEC@O%oujwnHQXv^B!!O7MnAKJgO3{)EBPO5Zd`m{m{&=4a3T2_%_xu1;)M=NV z3fqFm%NETnR##Fo`Gy&QM2FoM=ast^@kvBi7N!?A53A@kd~n|rOglgNyDvn$6u&aX zEetzyW!ozrVg#mIU__8G;Cf1qBa#O_X~v%nl{yU8=Kf=;BIqTkLd--KP*L6uZ+iEm zS!QyPp#$wuN5zR|d|eYyL<~WT0_zN7JD0Wc@No~XMm9hV8&=BEZe4la&2>>d^vCM_ ztCxDC>*s4Dz^3e?W_dlh0L$=u*tfGyaZKw4A_%@g2O1p{AagXD`eMczdFJy8?3o`3 z-pb|KL6GD%r1^4Y^TWKZfSq{mY$u%{R=!W} zDOL$9HpG|B@ii;?P2y*pvPyUZUXp zLb5D6#TGQb=+k&_eMkzPpBHfnC@8tej$+M;#wRPjS$;HnmT39oiFn0=e?$p&2*`%y}{FV-=`4+WK=cNpZBAu1@pZfCsUMj3H3gj6chv(8-M)Clsh~627lr_b|t}X?vbJ zj!$11XIcC1(hi}h4rEJ9ne~8v6m-Ri3j|8C4;M3qb3T#q57gpm%8qC`2yj>Qqo#_0 z3C-xpW0qa_F{D7kM3eiiP3I6~0I9?is4U)Kq+bfE0H#ND)`UsWfZCZB@IE9x3#K&A zPT{uH<7_wi`)_J|`r7V?r^perSp5UVjQ+gmH4$7S>fPGR7uv{RU>Y7>+7I)1W> zB?!5uS=loYqL^NlD~RT5?vf!3!OOC#IS;X|dCfdaVnY=Bw?_0=>FLT+|B66r`V^kb=T=xVNOiz(#Kqj_4+YtN5sai5esYd_O^BbX3MmX7m8iQgKD`a~t znBa81zlp6)^P;VFq>qd*2S(bf?^A6{!CZ~U%RNW~_RwAWVrw2c2}@U*PZ6LdJ%gs2 z928EU;Wi!rZ3_p@HsgWqUK1@A80@`ahd;Ad4d~zc5nMDpy=y}6+@U-7xY`%VRKR$S z8qJ-;)gx9J4FN*a-xxgLB# z2H+TCab6%`0fubo8~hvw`=L*NK}_d?;u@Mlclp2({iT8v|B+CxN>AJ1Qd) zD!7=~GpF6zG4{#~Mjc7j>Hcqt$u_%~4S;nGx>y^uG1z6~TnKAeXjI{enn)S57WYs@ z-gOj)p-p4?VegdtAf?T%R<`r22j6w0)o5>%?Iy2nC-^>=;j+Vka@M8oyaY3UIcaqX zYOR5UH*tG^42Gja_*fH$DAxCI1iBWNZLe~(jPo&a%O7(=Csq%>RO|{o#nH{CtALZ= z9c2hj$vBd(MNTp`46mz$TbU#y6%duu5rw${0SSnNGe{9n5m-<*K+`20NmmD2MrFfe zZpBogrIMse^w!dk5WpfJ6SJJY2qOa<=698Cia;WD`U?s?OMpV(oZB*nRIMytk2Jx2cL zfz&^>*Vra2rUpe;c?GEa8IO8U2h5noNG1Mcre71L5oMJ`Qq3%z6h9jh`H}d+%nMss~F772~p%t}~2;o4j2-@kc zrz{ogIR)eRr_vjuuqiJR)RBNfPY1szFwG9XoB~;aZ=qs|K&j$<`R6Znf5F^j{N-<^ zVB=uE71PcAgI25q8S>5gf>?l38C<0Yh(8$Aq9RsWUL$TrOrrAfy}KuIVg5QFPyg#+{JufFf?Oz>n8rc| zex+~HlV~{Cq4Z)8`v}R#f~7kQIB5qFpPV08K*=N}%uZIh*0()S+H(F-NZqul}lg(#{Eu!a`E-DxnErrIcYyI$R35-i?8j zqjAKgU?d0#&m`f?89y3;o+7CdMEDp2f37xU8FyIesaOC(4k6%b)%6xQiR)Z`uCO6E z5lKBO_09vS9}zQM>zOpz`o76qq95*K`_i#o78|STsop?;1MK+OVd-@%_Tkd^fpvT% z@V{RfHih>=ts#JbLNNcnG61Ct$b(}88Z>O}SHDrGBnmy+ABm_EqI; z%brABe0-+o$;3c>{-$OZ>6EJKpuI7Uh-jLQ z_wa1`6YraQYx&zF?IwnN8FtP*;)eaj1cDJR_D2SvUD;#*?4_rJq_^*4|ASxW zFlswZA8qpwUX^}&s6g=6@$8Q}2H-FaKX|AxLc$oZKR8HnLW195;eHkb_<3C*a6{#u zI^W>?dnc+cdud|GOC4`Q*nWUa>yMAo0=Kby=TNGvLYLvjl`Sy*P>?P(0|0-@IoD}S z&`g~y9xMv=E+;|Iv3|!4SMN{0Emhjp$tk_HRFz|z%1u@rDVxib@~2iFcv0#2sj@u1 zRpFB2Nz-jn^rE6CBT_gmeaWCzqC}za+lc&(s3Mo|jH%ceN)eS}x=$*~gD~x8B*>^8 zA}79_B6!l8Bw1%dV6b>_6yPWBejhDpKYN7lHu47<#V0Q6cSuG(jT17BOQGhO2RZKK zo`ww`SyTc)8 zrQL$?6v{A+zse@k3q2Tzm8MTiBYcr1Vg@45hGg#>G9={gzq|V|M+4*`=8uslb*PKe zq6>{ubZD)zkDP6lejB}Te&CvzNp5h}sZmPKgF^lbPy7ia9AI z0*Px;${3NqeXJR-{j4D_@Q7gRlBrN=un(WhEk}hY`5K(DIdOAkNQFh#X2qW8p)@Li zN8tlijg7g|#7N~a3IIx@detGxvVN`+V^rnvua2LkD&$F^N}`}RFU5R{NQQUDa8|jk zL}K56hsX07w4AE}aUS(lD6ipsBiy964ki%=xBrfcIyc(!x}j!pb$%(snPm+H#xiOD4|6(A%sfFY;TTT6oUBRlc;^usfnm zcuts=7=_kIqny-i*}4Y1}+uDr-t6#^lbGvQ;9gG}i=gVPpU8nLj~cQG=9m z$6XukW-&sW6V_`oLRF%=(-MKm?4L`CdB`v*N>@|Mzw8TIx=4@&Q*i~~K5>BaKQ744 z0~pPGKcN7A5ARXSQf$XUU*3XJ;vsh0p{>gug%U1#3`p?l!c$S>J>}z6i5)~jEAiO$ z6etCYS7`&$Xl>`vAF@^vIK0NPtv#j4%qk|lmrCJI%Q;*@w9j+PY^IG2R^xLwt}Wc@ zT#ULr)41akOn9r7IP?@AoyPKOCRb4)7dQ)#kuU%#CgjS{r)J}mX18d^)}x!!EdcmyVARTJz&vHe0)^+wHW#I?}A z6g-HS8t@vwfZgWh?zy9wyR5yjs}mqyeZwFglB0`)C&Pc4kyYj0`gt^%UmAh0w{f^E ztM`Iw@ZLyS`Z7R}#4d4p`U&)(nF;VOH0umBOF``m&C*l;XKDiSuwihwcd~Z2Fg0~? zX87leo!-ONrae>BepM1>urp!{H~_9X?4`J42UKIwZ7&o&0Q>BMg)F{?F=0PB$>U(v z{_J&{_Vb7K)c0b=Fsf@PbYxan-edaU%u$IU!+Cq zA*WIP_!q-)W2>ywmA(6B(QA=zI&GwM#=Qz-VNr(3y@#8tqR)e^yKBw6qej)YS^L)E zk3qAD4^H^uOxHx5=AjpUpC?-UHM}dwchP@@3G@(mvPlzcu=1|5g>Ier#H* zeC+Q`Je*PMS~j^h1}xc?j&y)$%o{%gZp`PAqhZ>r0*V3DVY$NgV4A9NhF}}Yz@;#Z z^#P^S#nl04CAnoVUKZfWs*Hx<>&o3~=x0+fm8DxScq6CkI&T?!aXPwZjgyJGcIWN; z3@`x^qHk>ad+M_QXV$KFWrl-ncCWoHRe&bwP3!Ghm z_*U4#Hz*DWH~u9W%kr^}Nfs1x8xH9Hi59Fqh_zBZ^PN8%pr`qR={>wJX)vvpqVP$Z z$&{p!V<51HiSq6t-Do83mbbu|Hx{071hJqICK^6^X<#dFo~a%uj)BuU@;hWiaB5{# z2@X^6fbog(7{6r9BHN@kC`}8NNq3gwhcj)xT@VC-x0X-55iC1kpNo1*2?O4t5bCU8 z2p23JT203XX)P(MTWp#~4MuIQ9_JE&)q?o75AV{lh^KKDJ+LIU9}iMt=PkHz5>@dL zk8)SSLdx=|Kl=Xs>`bCE;okBv4Xl9}e6OymNcBwsrm^eHB5bqJwWyeyO|`6qBcY?Pa;@LZB*?8D_W9B`;Hl z+HS1}0q(D1X*8b35DXMsodVhKkZNrvRU?Xk!`JhEr6gTFUD%wg3VJzyJpPtcxetl> zu!_ot%gxeHHX4dD>e^Z|2{H3`X;U=@&=1pWZ1cXY3i=|ZZ!HOX31r4zX2Y{`zU=)8 zMM3qd>=X-K44_(8NpypH{CMfRzmI+r$#!y~|8C&T2gSPf@*XMkh+b?ZT8uHd*k#H9 z=p%pX558h1eJ<0y(QJ<>8i)aRcJ3+RnImxLygpsO+*rppdV%(psD@f@d^0Q0lq^+k z(Gpx*qisNe!FMn49@B2eR>-9W1)q-yx$Nu^l?);HJF7}Fxy}iaPd*EiyP`?Jrdr2+?8EVUgOLrukgiQJ?#({ z7o>mn=eQe~5${h55kgv4JQ2L$G|-V-Q$FzRr8)zYrQms#5+CcFJ8YZ1H*z?{j4vw= zF{#jWt^$q@6sVcmn<)NHE+rls8ue0%R{T4sc|Wd^;6?JLY0-#qG*|)h#3HPWJP=BE)rygf zZTi93ZCs$ci!`>1>|aG(3X1~-WtpqdU2FCl*-&m6g4WGux5Pnyzn-GqBSO>wgUGMz z=e1ls$irxT8|BFKtFnBY_6Um;{Z#Vk#UBX_ih~@JvoLr5T(RsA6#J~S zqMr~cW6Y$Pf?B&aU~iE)PgCaOky9+o{Mff}C9czu0Y zED)O;#HmQb0J0(Yz<&<7L$7`GOA(K2mlyNQI`bVlJvF%`TC|Hg*=3Pj#8$opRwUk1vC|Dr?%^!CDTzgKA2YDnr zV5YIS2OEP(6gauTYoa1dYaV46oaj6>(h+CVwWbr7v1gl@5Uz3|O_n z#(44zlAzL5*@lZm3K0BRPgQ(R!=(3OsM(SR>ypx5xGwR_zz4-EuVPduLdow$J;aq# z@Set6_+47z1zi=u*-IRRra*2BV>K@wh3S(L8LXg5tO&Cq)({aVI?&JwYv`N=qS9d0 z_Dd$WG#NgpXcJnx1-@GK3q>YimZ3}RGZ)5+NJP*Mc9psTl~5>OXEDML zJkePd*%p6Wmy*u;Cy5`(Oo6OWh4k=sK+K+I;Tno6fag`Ma-Sndig4ifWa>;qWMLS}M0W6Kdt^~g(ZPEs#XD%>?pD0~$he?A7_h`8 z+skPEzKH^VXad@ee7BV_1);b(D(+H;5OIT^U>Fdy#Xw=Y-4Q7BNsQ$+t6c@OjeQ{9 zAX24NO~B$GLB%eF0xE}Xe&mMP5T2dm!h)Dt#jimHCvh9P;&s-|mfdPvv7;jOQ3#w7 zpwDLM@KvRHQE^~(nKY>eQfrbbdUsesXt_7U!oXwGwLoh-zV5&cw~oqEt}o6iB7A!ZE?^b^stqwHDy(uQ;R>c656>8-REx(N%CE5z z$!GYtusWC^+rR}rm{n0S+$1mXzcmX2Th0{bE?^n9@k*@IOkDteDKhehH=Fx0fQQ_` z=i>Z=LkFLep_%sgOTa4m+u(x?s#~BMD#7j?n-T?br{3fmr#cL7kon;_Qy`)@OFbfu zEsL@`b~IMLdMam%8}-O(%XZ;%1oMaLL8NH;xd zzh^t=$3fgpWDNg4PAvA@xNDEV(3TSam)nnD$S`12Du4iwAy6{ZJmiAltN2^XR(8Y& z1F+o&jFTwpzX_4SNKQ3c`oKxLf(U_;V8n~F^eKf)fYQ_B;5>7|NLT~R*x#)9%`kHr zZQsiEKSmQjrQJ5wV_%24$9?c0hM(rm)IZXUpAV{isee#7bnxLdX6)>Xq|?so+HH;2 zoj+UwZpc5cyLRQR^xg^nv+(}cy1O{>!SNXc2nY=k2nhabZ~1H8?d;-dW9s~OvAwRY zXrIA|^g$Cwpg`zCD+6X}9LZ{#L0y69?HV9Oi=BZzOZIwyYbY$a6WLNS-YW?I;QP_h zS~uC4-rzNP{7CnEWp!Ss9u@#7bG@a9TDebIC}>ZFM~5gWC=F-VZGc`8S5#^gEcIlB z?IkUSM*Wc;Ua7axa=%cE>a{KLYq&0F@;4LtV~6=ZjOI)*!A9`Gbs#S+#E)!MB|Os$ zUu#CAg2D~dZP-r2%nCG6H8HD!#|2){UM;5*UfZ1_q`;c`PCC5hZw2gtqd6JL`Pas8 zXO~u3KY**D4c3dtM+%TSbbGv2E#QpUu@?Y@h6B%O4rkgT*=(ZLrF}ClUUK$rer`&!RtVglV{Fy7vpy3h$WS(dcZ;QBNpW05W?tCDNsG3v1~&QM1yxEk+r1 zJ`FY?Kn*DkmPt`I1%$o;Bgla%##-xnVL%V_dlEO zv1$IK`4ruZVz3nBt|X9f*d|!jw;XQa#+Tp|)aorZeud4vRb<|X9>7E;Tc57QYB@0D zYBo9p4RSVF7O(>Y?0z{A0EIkv6L)c@1V2CL-2D9YtN|Bkdm(y2X&&Q5I~t7k9{KF( ztaST_MRt}`4#h@1vIkqpe_T7ED@PkX5*{m!;OfrZIl78nk7)4}NPE^AJ-{?Voj8Sx zf6?#HAv~7liJt8k;@4uh%L?+X^<7Z5nadHbETTW;+yGBXNEyyw%Vp6bfv_&I>r{`x zCbiEoy#={z4Y`jgq|2@knHD4rSY+o6Nu%akG2v*d3db-rMaLY1gQ!HK-0aDAag#am*)JAy*)1!ZqW> zqVwZ^*GSUnH^w5OctZMMP{VOpjh2nkNJgdiEx#9lIZA;O0YiytipgUr5?Imwm#?&7 zB(&9$&?%F6bf4ssphSh7eda^%tjeIo2D`cg*Vw;M5=3HIgz?6y>0^*mmtIMH(}ehg zD~DK6TI6#<9P1lPz#!9Le|8St=NaJ<-Tw&-N#N@eCGG$v^BP)a?78DiR8+-EX1?K1 zGN0~4_)KN51#|;>TZDS{A}MO##gileDd0R&u1JnXZ!7)@`iVs z?o)O0byBKQ4@mENK$gLF93IxQCHQ*fbbP=iFsKvyJ`-o@s8^rCYq*VTVA`i%gA@=H zN%`mrS=k6$%|#ac(DUL49lqyn_xOsk6SP`b`;B-Bm|1qO_G*yakpeGi04`BwNJySw zcqN%`nuku|NHk%p@N3}kYq+hUHJ_FMm5J}H61A5Q@O>!?3R!*EzlGPOOu{i-hM+p1 zLxHNkBuS6fus^j|{^KgqS)`5ct8UMcK3=Kl_+qbZJ?`e)Cq3!A&b&w$y&6oTdFdB; zHk5o$Z#LRe_OKBb_e%Yk#&0ympsv*%<1O%CeB)oktSLatYV2!x9Z!9d1}6s0wIc*D zB0*lM6ZrzkBTZu&I61Pl2?h>I0Q+ zZSC;~fc1*3#^8uz2oc#vXF>z8UVqD8@%M~p(guvup*}dlfzoNs}&2xncsr0AI(`8 zY|w)4uY(*Tdl&oIRl7vNUq;j@?W|T`21E>vN#z>|-x#f;8q@mG;{iw@w=hwCbDq_B zRJx1_PaHPEsh)IAgc!HaN_!J18q&q{VT)xHl>2hZcWYPd)aG@Zegm9dXQfx&&AApO?D7OKHIU!FHJ)HGMk|%NaSg|?*Gh6uAKDM zA%Q9NO7Nm;dfh$eX1M|PGc2!dAYz`_eWT=UG4+YN8ex*vwg7ZTE9f<^oi1;p2V`ahF3I5*x z)X5}4`l|L!ihou8SMC3E`2laG?#Y4^gUl$WLdk*Sfe=9@@)NwMGt{g6RKmi}qg`AV2@m0r1oe zW^ioU|Eu@^>_z=Ijh}zxOe()TI62_2;{OW^{zuHuUws+;{|OTQ-@N|sBImzafOq%) z(egh_p8rV-_P6Jz=E;9m801lE=hs#l;oo2MKf^x|(AS#jzn35I)luq`JUIS874qNy z4h95-^ZyO5hJm4h(f&2-^nYHg(ij1Po^xQj8W^45Os@@KmF44O;A5D>%)qedEh|tG E00~SoqyPW_ delta 10431 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 str: - """공백 차이만 있는 원고를 같은 것으로 본다.""" - return re.sub(r"\s+", "", text or "") - - -def text_key(text: str) -> str: - return hashlib.sha256(normalize(text).encode("utf-8")).hexdigest()[:16] - - -def load_jsonl(path: Path) -> dict[str, dict]: - rows = {} - for line in path.read_text(encoding="utf-8").splitlines(): - if line.strip(): - row = json.loads(line) - rows[row["query_key"]] = row - return rows - - -def write_header(sheet, columns) -> None: - sheet.append([label for label, _ in columns]) - for index, (_, width) in enumerate(columns, start=1): - cell = sheet.cell(row=1, column=index) - cell.font = Font(bold=True) - cell.fill = HEADER_FILL - sheet.column_dimensions[get_column_letter(index)].width = width - sheet.freeze_panes = "A2" - - -GROUP_COLUMNS = [ - ("중복 그룹", 10), ("중복 건수", 10), ("문서 수", 9), ("성격", 22), - ("가명 제목들", 40), ("문서 ID들", 60), ("본문 길이", 10), ("본문", 80), -] - -SUSPECT_COLUMNS = [ - ("가명 제목", 20), ("문서 ID", 28), ("매칭 상대 제목", 20), ("매칭 상대 문서 ID", 28), - ("결합유사도", 11), ("본문 완전 동일", 13), ("본문 길이", 10), - ("검사 대상 원문", 70), ("매칭 상대 원문", 70), -] - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--excerpts-jsonl", type=Path, required=True) - parser.add_argument("--batch-jsonl", type=Path, required=True) - parser.add_argument("--out-xlsx", type=Path, required=True) - parser.add_argument("--out-exclude", type=Path, required=True, - help="재실행 시 제외할 segment_id 목록 (한 줄에 하나)") - args = parser.parse_args() - - excerpts = load_jsonl(args.excerpts_jsonl) - batch = load_jsonl(args.batch_jsonl) - - groups: dict[str, list[str]] = defaultdict(list) - for key, row in excerpts.items(): - if normalize(row["검사 대상 원문"]): - groups[text_key(row["검사 대상 원문"])].append(key) - duplicates = {key: keys for key, keys in groups.items() if len(keys) > 1} - - # 그룹마다 첫 건만 남기고 나머지를 제외 대상으로 삼는다. - excluded = [key for keys in duplicates.values() for key in sorted(keys)[1:]] - segment_ids = [batch[key].get("query_segment_id") or batch[key]["doc_id"] for key in excluded] - args.out_exclude.parent.mkdir(parents=True, exist_ok=True) - args.out_exclude.write_text("\n".join(segment_ids) + "\n", encoding="utf-8") - - wb = Workbook() - wb.remove(wb.active) - - # 1) 중복 때문에 침해의심이 된 건 — 사라지기 전에 원문째로 남긴다. - sheet = wb.create_sheet("중복 기인 의심 건") - write_header(sheet, SUSPECT_COLUMNS) - affected = [ - key for key, row in excerpts.items() - if row["침해 여부"] == "침해 의심" and len(groups[text_key(row["검사 대상 원문"])]) > 1 - ] - for key in sorted(affected, key=lambda k: -(excerpts[k]["결합유사도"] or 0)): - row, meta = excerpts[key], batch[key] - identical = normalize(row["검사 대상 원문"]) == normalize(row["매칭 상대 원문"]) - sheet.append([ - row["가명 제목"], meta["doc_id"], row.get("매칭 상대 제목") or "-", - meta.get("매칭 상대 doc") or "-", row["결합유사도"], - "예" if identical else "아니오", len(normalize(row["검사 대상 원문"])), - row["검사 대상 원문"], row["매칭 상대 원문"], - ]) - for column in (8, 9): - sheet.cell(row=sheet.max_row, column=column).alignment = Alignment( - wrap_text=True, vertical="top") - sheet.cell(row=sheet.max_row, column=5).number_format = "0.0000" - sheet.auto_filter.ref = f"A1:{get_column_letter(len(SUSPECT_COLUMNS))}{sheet.max_row}" - - # 2) 중복 그룹 전체 목록 - sheet = wb.create_sheet("중복 그룹 전체") - write_header(sheet, GROUP_COLUMNS) - for index, (_, keys) in enumerate( - sorted(duplicates.items(), key=lambda item: (-len(item[1]), item[0])), start=1 - ): - docs = sorted({batch[key]["doc_id"] for key in keys}) - titles = sorted({excerpts[key]["가명 제목"] for key in keys}) - body = excerpts[keys[0]]["검사 대상 원문"] - sheet.append([ - index, len(keys), len(docs), - "서로 다른 문서 사이" if len(docs) > 1 else "같은 문서 안 (적재 오류)", - ", ".join(titles), ", ".join(docs), len(normalize(body)), body, - ]) - sheet.cell(row=sheet.max_row, column=8).alignment = Alignment( - wrap_text=True, vertical="top") - sheet.auto_filter.ref = f"A1:{get_column_letter(len(GROUP_COLUMNS))}{sheet.max_row}" - - # 3) 요약 - sheet = wb.create_sheet("요약") - cross = sum(1 for keys in duplicates.values() - if len({batch[key]["doc_id"] for key in keys}) > 1) - for label, value in ( - ("검사 대상 전체", len(excerpts)), - ("고유 본문", len(groups)), - ("중복 그룹", len(duplicates)), - (" 서로 다른 문서 사이", cross), - (" 같은 문서 안 (적재 오류)", len(duplicates) - cross), - ("중복에 속한 건", sum(len(keys) for keys in duplicates.values())), - ("재실행 시 제외할 건", len(excluded)), - ("제외 후 검사 대상", len(excerpts) - len(excluded)), - ("", ""), - ("침해의심 전체", sum(1 for r in excerpts.values() if r["침해 여부"] == "침해 의심")), - (" 중복에서 비롯된 건", len(affected)), - (" 중복과 무관한 건", - sum(1 for r in excerpts.values() if r["침해 여부"] == "침해 의심") - len(affected)), - ): - sheet.append([label, value]) - sheet.append([]) - for line in ( - "중복 제거는 원본 코퍼스를 지우는 것이 아니라, 재실행 시 해당 세그먼트를 검사 대상에서 빼는 것입니다.", - "'서로 다른 문서 사이'의 중복은 같은 제출자가 두 번 낸 것일 수도, 다른 제출자의 표절일 수도 있습니다.", - "후자라면 그것이 곧 침해 사례이므로, 이 목록의 문서 출처를 확인한 뒤 판단해야 합니다.", - ): - sheet.append([line]) - sheet.cell(row=sheet.max_row, column=1).font = NOTE_FONT - sheet.column_dimensions["A"].width = 30 - sheet.column_dimensions["B"].width = 14 - - args.out_xlsx.parent.mkdir(parents=True, exist_ok=True) - wb.save(args.out_xlsx) - print(f"wrote {args.out_xlsx}") - print(f"wrote {args.out_exclude} ({len(segment_ids)}건 제외 대상)") - print(f"중복 그룹 {len(duplicates)} / 중복 기인 의심 건 {len(affected)}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/build_report_xlsx.py b/scripts/build_report_xlsx.py index f29e080..8d486dc 100644 --- a/scripts/build_report_xlsx.py +++ b/scripts/build_report_xlsx.py @@ -6,7 +6,9 @@ from __future__ import annotations import argparse +import hashlib import json +import re from collections import Counter from pathlib import Path @@ -299,6 +301,94 @@ def build_criteria_sheet(wb: Workbook, rows: list[dict], excerpts: list[dict] | sheet.column_dimensions[get_column_letter(index)].width = width + +def _norm(text: str) -> str: + """공백 차이만 있는 원고를 같은 것으로 본다.""" + return re.sub(r"\s+", "", text or "") + + +def _text_key(text: str) -> str: + return hashlib.sha256(_norm(text).encode("utf-8")).hexdigest()[:16] + + +def duplicate_groups(excerpts: list[dict]) -> dict[str, list[dict]]: + groups: dict[str, list[dict]] = {} + for row in excerpts: + if _norm(row.get("검사 대상 원문", "")): + groups.setdefault(_text_key(row["검사 대상 원문"]), []).append(row) + return {key: rows for key, rows in groups.items() if len(rows) > 1} + + +DUP_COLUMNS = [ + ("가명 제목", 20), ("문서 ID", 28), ("매칭 상대 제목", 20), ("매칭 상대 문서 ID", 28), + ("결합유사도", 11), ("본문 완전 동일", 13), ("본문 길이", 10), + ("같은 본문을 가진 문서", 46), ("검사 대상 원문", 70), ("매칭 상대 원문", 70), +] + + +def build_duplicate_sheet(wb: Workbook, excerpts: list[dict], records: dict[str, dict]) -> None: + """중복 제거 후 재실행하면 사라질 건들을 원문째로 남긴다.""" + sheet = wb.create_sheet("중복 원고") + groups = duplicate_groups(excerpts) + member_of = {r["query_key"]: rows for rows in groups.values() for r in rows} + affected = [ + r for r in excerpts + if r.get("침해 여부") == "침해 의심" and r["query_key"] in member_of + ] + cross = sum(1 for rows in groups.values() + if len({records[r["query_key"]]["doc_id"] for r in rows}) > 1) + suspected = sum(1 for r in excerpts if r.get("침해 여부") == "침해 의심") + + sheet.append(["중복 원고 현황"]) + sheet.cell(row=1, column=1).font = Font(bold=True, size=12) + for label, value, note in ( + ("검사 대상 전체", len(excerpts), ""), + ("고유 본문", len(excerpts) - sum(len(v) - 1 for v in groups.values()), ""), + ("중복 그룹", len(groups), ""), + (" 서로 다른 문서 사이", cross, "같은 제출자의 재제출인지 다른 제출자의 표절인지 확인 필요"), + (" 같은 문서 안", len(groups) - cross, "적재 오류로 보임"), + ("중복에 속한 건", sum(len(v) for v in groups.values()), ""), + ("침해의심 전체", suspected, ""), + (" 중복에서 비롯된 건", len(affected), "중복 제거 후 재실행하면 사라짐"), + (" 중복과 무관한 건", suspected - len(affected), "중복을 제거해도 남음"), + ): + sheet.append([label, value, note]) + + sheet.append([]) + sheet.append([f"중복에서 비롯된 침해의심 {len(affected)}건 — 사라지기 전 원문 보존"]) + sheet.cell(row=sheet.max_row, column=1).font = Font(bold=True) + header_row = sheet.max_row + 1 + sheet.append([label for label, _ in DUP_COLUMNS]) + for index, (_, width) in enumerate(DUP_COLUMNS, start=1): + cell = sheet.cell(row=header_row, column=index) + cell.font = Font(bold=True) + cell.fill = HEADER_FILL + sheet.column_dimensions[get_column_letter(index)].width = width + for row in sorted(affected, key=lambda r: -(r.get("결합유사도") or 0)): + meta = records[row["query_key"]] + peers = sorted({records[r["query_key"]]["doc_id"] for r in member_of[row["query_key"]]}) + sheet.append([ + row["가명 제목"], meta["doc_id"], row.get("매칭 상대 제목") or "-", + meta.get("매칭 상대 doc") or "-", row.get("결합유사도"), + "예" if _norm(row["검사 대상 원문"]) == _norm(row.get("매칭 상대 원문", "")) else "아니오", + len(_norm(row["검사 대상 원문"])), ", ".join(peers), + row["검사 대상 원문"], row.get("매칭 상대 원문") or "-", + ]) + for column in (8, 9, 10): + sheet.cell(row=sheet.max_row, column=column).alignment = Alignment( + wrap_text=True, vertical="top") + sheet.cell(row=sheet.max_row, column=5).number_format = "0.0000" + + sheet.append([]) + for line in ( + "중복 제거는 원본 코퍼스를 지우는 것이 아니라, 재실행 시 해당 세그먼트를 검사 대상에서 빼는 작업입니다.", + "'서로 다른 문서 사이'의 중복은 같은 제출자가 두 번 낸 것일 수도, 다른 제출자의 표절일 수도 있습니다.", + "후자라면 그 자체가 침해 사례이므로, 위 목록의 문서 출처를 확인한 뒤 판단해야 합니다.", + ): + sheet.append([line]) + sheet.cell(row=sheet.max_row, column=1).font = NOTE_FONT + + SCORE_MIN = 0.65 SPAN_MIN = 80 COVERAGE_MIN = 0.30 @@ -437,6 +527,8 @@ def main() -> int: parser.add_argument("--compare-jsonl", type=Path, help="비교 시트를 붙일 이전 회차 JSONL") parser.add_argument("--backend", default="", help="검색 백엔드 표기 (미지정 시 JSONL 값 사용)") parser.add_argument("--note", action="append", default=[], help="요약 시트에 남길 유의사항") + parser.add_argument("--no-excerpt-sheet", action="store_true", + help="원문 대조 시트는 만들지 않고 중복 원고 시트만 붙인다") parser.add_argument("--excerpts-jsonl", type=Path, help="원문 대조 시트를 붙일 발췌 JSONL (extract_suspected_excerpts.py 산출물)") args = parser.parse_args() @@ -456,7 +548,9 @@ def main() -> int: for line in args.excerpts_jsonl.read_text(encoding="utf-8").splitlines() if line.strip() ] - build_excerpt_sheet(wb, excerpts, records) + if not args.no_excerpt_sheet: + build_excerpt_sheet(wb, excerpts, records) + build_duplicate_sheet(wb, excerpts, records) build_criteria_sheet(wb, rows, excerpts if args.excerpts_jsonl else None) build_summary_sheet(wb, rows, backend=backend, notes=args.note) if args.compare_jsonl: