Spaces:
Running
Running
Commit
·
38d5865
1
Parent(s):
5818013
UI updated
Browse files- config/model_config.py +4 -2
- data/reports/analysis_1761903397224_20251031_150713.pdf +0 -175
- data/reports/analysis_1762204265013_20251104_024139.pdf +175 -0
- detector/attribution.py +58 -64
- detector/highlighter.py +309 -139
- logs/application/app_2025-10-29.log +0 -105
- logs/application/app_2025-10-31.log +0 -0
- logs/application/app_2025-11-03.log +0 -0
- logs/application/app_2025-11-04.log +135 -0
- metrics/multi_perturbation_stability.py +326 -165
- models/model_manager.py +81 -2
- reporter/report_generator.py +75 -62
- requirements.txt +34 -29
- ui/static/index.html +568 -104
config/model_config.py
CHANGED
|
@@ -20,6 +20,8 @@ class ModelType(Enum):
|
|
| 20 |
EMBEDDING = "embedding"
|
| 21 |
RULE_BASED = "rule_based"
|
| 22 |
SEQUENCE_CLASSIFICATION = "sequence_classification"
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
@dataclass
|
|
@@ -99,7 +101,7 @@ MODEL_REGISTRY : Dict[str, ModelConfig] = {"perplexity_gpt2" : ModelC
|
|
| 99 |
quantizable = True,
|
| 100 |
),
|
| 101 |
"multi_perturbation_base" : ModelConfig(model_id = "gpt2",
|
| 102 |
-
model_type = ModelType.
|
| 103 |
description = "MultiPerturbationStability model (reuses gpt2)",
|
| 104 |
size_mb = 0,
|
| 105 |
required = True,
|
|
@@ -108,7 +110,7 @@ MODEL_REGISTRY : Dict[str, ModelConfig] = {"perplexity_gpt2" : ModelC
|
|
| 108 |
batch_size = 4,
|
| 109 |
),
|
| 110 |
"multi_perturbation_mask" : ModelConfig(model_id = "distilroberta-base",
|
| 111 |
-
model_type = ModelType.
|
| 112 |
description = "Masked LM for text perturbation",
|
| 113 |
size_mb = 330,
|
| 114 |
required = True,
|
|
|
|
| 20 |
EMBEDDING = "embedding"
|
| 21 |
RULE_BASED = "rule_based"
|
| 22 |
SEQUENCE_CLASSIFICATION = "sequence_classification"
|
| 23 |
+
CAUSAL_LM = "causal_lm"
|
| 24 |
+
MASKED_LM = "masked_lm"
|
| 25 |
|
| 26 |
|
| 27 |
@dataclass
|
|
|
|
| 101 |
quantizable = True,
|
| 102 |
),
|
| 103 |
"multi_perturbation_base" : ModelConfig(model_id = "gpt2",
|
| 104 |
+
model_type = ModelType.CAUSAL_LM,
|
| 105 |
description = "MultiPerturbationStability model (reuses gpt2)",
|
| 106 |
size_mb = 0,
|
| 107 |
required = True,
|
|
|
|
| 110 |
batch_size = 4,
|
| 111 |
),
|
| 112 |
"multi_perturbation_mask" : ModelConfig(model_id = "distilroberta-base",
|
| 113 |
+
model_type = ModelType.MASKED_LM,
|
| 114 |
description = "Masked LM for text perturbation",
|
| 115 |
size_mb = 330,
|
| 116 |
required = True,
|
data/reports/analysis_1761903397224_20251031_150713.pdf
DELETED
|
@@ -1,175 +0,0 @@
|
|
| 1 |
-
%PDF-1.4
|
| 2 |
-
%���� ReportLab Generated PDF document http://www.reportlab.com
|
| 3 |
-
1 0 obj
|
| 4 |
-
<<
|
| 5 |
-
/F1 2 0 R /F2 3 0 R /F3 4 0 R
|
| 6 |
-
>>
|
| 7 |
-
endobj
|
| 8 |
-
2 0 obj
|
| 9 |
-
<<
|
| 10 |
-
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
| 11 |
-
>>
|
| 12 |
-
endobj
|
| 13 |
-
3 0 obj
|
| 14 |
-
<<
|
| 15 |
-
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
| 16 |
-
>>
|
| 17 |
-
endobj
|
| 18 |
-
4 0 obj
|
| 19 |
-
<<
|
| 20 |
-
/BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
| 21 |
-
>>
|
| 22 |
-
endobj
|
| 23 |
-
5 0 obj
|
| 24 |
-
<<
|
| 25 |
-
/Contents 14 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 26 |
-
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 27 |
-
>> /Rotate 0 /Trans <<
|
| 28 |
-
|
| 29 |
-
>>
|
| 30 |
-
/Type /Page
|
| 31 |
-
>>
|
| 32 |
-
endobj
|
| 33 |
-
6 0 obj
|
| 34 |
-
<<
|
| 35 |
-
/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 36 |
-
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 37 |
-
>> /Rotate 0 /Trans <<
|
| 38 |
-
|
| 39 |
-
>>
|
| 40 |
-
/Type /Page
|
| 41 |
-
>>
|
| 42 |
-
endobj
|
| 43 |
-
7 0 obj
|
| 44 |
-
<<
|
| 45 |
-
/Contents 16 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 46 |
-
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 47 |
-
>> /Rotate 0 /Trans <<
|
| 48 |
-
|
| 49 |
-
>>
|
| 50 |
-
/Type /Page
|
| 51 |
-
>>
|
| 52 |
-
endobj
|
| 53 |
-
8 0 obj
|
| 54 |
-
<<
|
| 55 |
-
/Contents 17 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 56 |
-
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 57 |
-
>> /Rotate 0 /Trans <<
|
| 58 |
-
|
| 59 |
-
>>
|
| 60 |
-
/Type /Page
|
| 61 |
-
>>
|
| 62 |
-
endobj
|
| 63 |
-
9 0 obj
|
| 64 |
-
<<
|
| 65 |
-
/Contents 18 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 66 |
-
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 67 |
-
>> /Rotate 0 /Trans <<
|
| 68 |
-
|
| 69 |
-
>>
|
| 70 |
-
/Type /Page
|
| 71 |
-
>>
|
| 72 |
-
endobj
|
| 73 |
-
10 0 obj
|
| 74 |
-
<<
|
| 75 |
-
/Contents 19 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 76 |
-
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 77 |
-
>> /Rotate 0 /Trans <<
|
| 78 |
-
|
| 79 |
-
>>
|
| 80 |
-
/Type /Page
|
| 81 |
-
>>
|
| 82 |
-
endobj
|
| 83 |
-
11 0 obj
|
| 84 |
-
<<
|
| 85 |
-
/PageMode /UseNone /Pages 13 0 R /Type /Catalog
|
| 86 |
-
>>
|
| 87 |
-
endobj
|
| 88 |
-
12 0 obj
|
| 89 |
-
<<
|
| 90 |
-
/Author (\(anonymous\)) /CreationDate (D:20251031150713-05'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20251031150713-05'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
| 91 |
-
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
| 92 |
-
>>
|
| 93 |
-
endobj
|
| 94 |
-
13 0 obj
|
| 95 |
-
<<
|
| 96 |
-
/Count 6 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages
|
| 97 |
-
>>
|
| 98 |
-
endobj
|
| 99 |
-
14 0 obj
|
| 100 |
-
<<
|
| 101 |
-
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1206
|
| 102 |
-
>>
|
| 103 |
-
stream
|
| 104 |
-
Gatm;gMYb*&:Ml+bf^'%Yu*rIUr9pBD;+D%6*muQ49i+8\1D_n-+]*9IRb+6aL7m087TT87l+cfLNB-qq"Q',4j=#a`pi*m!C@sD#JrS-nJ,<K-U1C_e)UQs_GD+]pt0)>&JBXB8LR[HebTD'a:8u:EeL5Rr<AG5"u)2]CECJG35,(l@eFEm]eV&p\h)d68@!*7C\XVFb0%AKp]na@iN-[GN;g`H*\.iMQBcB",=HKhQ3a``:-YaF<JaR'5f]Kq;_D&R!Vm]!;@\Na5</C)\@M$0%!i]'B-VFF&"FUajU:to_=LsB#cG0n@`c3#@>GkgO_+>&S&H`#PA32%L/7n%,WiIjUmt"=S]D<eD*B0_nA]=mki74+`o*q$<!r&#mfh&.kX>lmouII_JYd_i60!g<2cZu%C<h1]\ni;(+PbkJI+mXQEHYQ#@oRFRndH*C=:!-IS#T^?9:]e'S^56j"N'2Kc!qm\diMi6_#\Q?d?t\8@P+eV,A-O?-o`>#rg=2A[P@^X$VMtmi*BL@l]iqt24K1b'&8[,3DaEA^S9?&,e>[6=JTLcT7R.P\4:./Lo_cf^/d\9WSpJJ;V>md/P7G(W:0_`XnRH3_g"/fI2EFn7.7s??p0ra=.X4k!VMM#nFU$A?F1*-FnEDln_=W6[2563^MVf[Y[5e+X]C?<Sr8#)B-NC3r!AXr^$<&-<f":8%bk82cKP[*a3\eD1`?bPlo\:o,!,[QNGdH6a5Tlp?0br/Z-F_@D"T#K7.kUm(AQYu")14l-\#j)?"1M.b=k'kBoQ1h3j3VErO"E<bLLkk*T14dl",2F\6,pb[8*/V46`6>_/W7BeBQf@:;Q@EeT%U2$F`X2^RL)N&em[s:#<PL9Fh2t;1p`2\rf`+leY1!'8)O\+_!G@k7,Bl>c62:mcbbCrKJtMXlG,72Q>AgQEp2HFR_[T?COer^6aPQb(o@DR*uH;46q>9[AA5FWeX82iXd,#F/kHHh&1NEUT8IK*>k@(%:o57Wj@dW1Uu%!g>]f_1>h<L9HBHu-.EU'E+++_S':(Xgc$:qdT46@_RPNB3.>Y$58V9;X<R-err6q?n`;!Eq7TGLgnnelb$\YS^6AA>'#1?n5!?>"=A)DHX%k.T'B5aMKu1rNdE-?T[PD*0Z640C>jT,EcACo_&CL%l@`N,SG7$O\/cu$-IMsIm_rg(7a@PoUPQ(Z.-7dp~>endstream
|
| 105 |
-
endobj
|
| 106 |
-
15 0 obj
|
| 107 |
-
<<
|
| 108 |
-
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1105
|
| 109 |
-
>>
|
| 110 |
-
stream
|
| 111 |
-
Gb"/&?#S1G'RfGR\1^An$tDBE8,"oDq%?%[]8_TZG_t#Ud\KO+620\b*2g/LS.qqi6:+ZklfS!;`]j-gL&b6rU4:'@^rn1>GYN]bQsKl'Lr&]cN2M<_!AR-s".rHp<\^[cq.u_$OVO(K4'*uYMP];L:1R[cZHRT7!7Pj?I9P$I<hJ:!A/'RWOGDUk8gDSL9g3>#WdL=O`SnAKUH"K.C]%s%U@L"Rds#0>-ZD(.IL"3WmFgaoa30,/8RD)"eJ&2cP\4(q.SaH`lr_-_h#QC]2GRn\0lP5q:NrTl;]+npV0_0WYHmd]N^OeC%R,O<4lfiC>R_RKKpZ3n!\Ql']BS&*)Xh'%egYP1+b/?JO@Pf]'VoF4mo(;LV`\,=MZO6Jb3Jeq0U`W0OGJ!*Dd^DX'R<k%!`(%`T?Fa+EQAg'(;)sL<qWH!gej")g4[/2eqoJN:.,5_6KW8i*-LP9GsT'V"1u8R%Vjhb"Y9o:iYCmO-`+&<Wd7&bkdq^+RXP+,2pY68k)]R'^5^`W)DdN'm_!.,J=(*eTta4o,0Q`f8qUct[o+uob&f"'K;Zes;GttpZB/FY$."qlM=3#d:f/,n%1'i'7Tt#!l#IqTRXGu$Hr4Y(m%cK[,N`g,++a1Oqe^jg9L^(SmH^)tlLp9)>4I[db^gC&NtRiD-oNfm&]&7MqX27h"Oe]t7SP^Y9i):fR&Z$)V?Tc=,poJn:!@#mV?X)$r+6Pu`!Bj6TBb.ke'`r;LQSaZmC:&199o32gc1RE[TI^=^VcR"l9-)Vp9ZtE)Pq->#`bd@P4$fHPXBbDFu19E]nYZGG:1LK=k`XG[;%=ZVb(1Q;1'b^6>]tE?fJNDIC)Za\YtPFChoAm3Oi'tqfOV+@%3,TZB)]1iAj$%]QOBeLfh9knKR?+]mL%N,Kb=kX7"cVKEgfgAgK8l<"ki[6Ktm0gd,XjB[Z,2bc\9q"7*ELoAjl9o9cR9qX&'Nn\9M*m>L`LnlkC<0Y`YJR=0Jrq'$D8]@onXqQAF>r\#+Us!Y']l4E[c9'3_+h6V[[/]r5A$[*0-Q@YS6?;tiC+N7+_S)"1h,#2d4n8q_%R=KPuM$1[X.uNMaotM+JIK=`cdFS~>endstream
|
| 112 |
-
endobj
|
| 113 |
-
16 0 obj
|
| 114 |
-
<<
|
| 115 |
-
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1160
|
| 116 |
-
>>
|
| 117 |
-
stream
|
| 118 |
-
Gb"/fhfIL2&BE](/,#eqktOX4A6ZM>`fXNpDYbS0[!bu66Ia^EB8\\?YHKM#":2lAPNtnT3[CA$G/#Q'eUB%NfY!#gcO,1eBCP7@!eeGW#"XP:R(\0rg$$0_ELmbpR"iW6fW_b`Mie;j)^Q/%(mP(,[OSZ334f`jb?W9MQ-bf?lK:rOp'G>\2!T#(lTPDB'4@1i9[V'8\X,.(>BuW.QSb,Vg&tXN>@&Sl)nunG?\u[<$rUP_hL#nMcZ?T/eM7qb;Qp&1/gQ6LOSWMJd/(C-X+KO*Rotapat=$r+SH+3Z#pq6=hmub/Q=&$8\6sFUaRENnISp&WLf_/mc#XO;9F:G1h_)-Wmeml*$m"-I+kZek'*^p[p_ir&`2BdVF:GAs.;b/_B`[qp-7HJ0OK;MWCD`?:]MfcNcLLj.s<h`A&2A5mC*TN$)X9DN&k_e3MRI62O6=l)0E<C31Ce-[NKZM1Y?5p]-OYfOYF06Bg00)]\PZSVC+cGJ<K'9mb:n"gAAL9lT)CX9LZd_Q5PVrce#S+O*6F8AquO+r?Cq/Mmt@3&#,N99L&99K:.gBr;6GnnS@UhCMRP[%pW,r28>HfWN[V9c9DRHIcAfbdPgR:XMmG-(W+f:=_a<$Cf78P3H*3]&O#:r;T-4oO`e36[4&2!!?[##g`B\FeiuU^PF]%&TW#^U),r_/(H%P']T>b8:j5p(Q+*K&Id/rWEbQ!*'NNTof&sj!*:e+n1S2KY?@MRu=u3#5@&Z:Ar;mW,lM2h!Dpg3W9XD%qG]1FE+NiI&*Q9B"9A_,.iEkO2n;^lE`):9JAZ>M_>l[>2pY]q$/*3Lg2nDf77:6gV)<u0PI_?M@H=]]cCE\!QLCs2/ZOQ^X[k%+h2LF![33ucHDnHOr>C[T##Ok1rgtGQAgInL]/$nt3a7Nd^BV*?VW;cp(UC7>+#M6g;*;T]=71@R6jr2o?j[$,5QjbI64g9q3=u:`5W-D0YD2G[]>)Q#'k>8@Ik#om>_EHtgFK>)gr.9N%T1.jkP\MN\*iull`>`C&WrC+^]..U+OQMF%&%U=1AC)C+21h/e=66af2A[M$Q#i%UDDlUBg!F'2R7E%2LMT&]`$*Rb71P$"I!`+uRAU'<p0O3BQ85MNOgBlD=B9Kf?h;5:`8TW5id?Q7d<EI7-ZT~>endstream
|
| 119 |
-
endobj
|
| 120 |
-
17 0 obj
|
| 121 |
-
<<
|
| 122 |
-
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1392
|
| 123 |
-
>>
|
| 124 |
-
stream
|
| 125 |
-
Gat=*9lo&I&A@C2lm4FpB8ciK[r/@<VJ5'IZ'Ft@$RNY&;L[7@C"8dRpb"gnU23_n.ubj$[<T=!^3p@n']7,^Y&f,cQLO;Y#)pIj(77TS=FPkrbLuiQ=\h84O@LQ_pY".GG04%drJVYqg<:+ioe@"87sqedfBDhL-EAG?&ZT`u.]Hu^^ic3LA1$W_\$5c291PoLocP64,7#,*Ml/KF)glP0+!cD?\iPcFGG.pFXqu]r"#ktF=iXma?(QNepBpPq[EnY=pnGor:&5-molkgNhb7IiE>uC/PN"u4]>TV`3sj*Tl-D$*i`;qN=GnDKei2Y8T#C5[bPS"Hq'm'IVi[D8Z/][!O]0(-o%qmh$'G/<4c)]89_X7401AlB:lE)(W&auG;kuarh2ko:-&W56)tf;c;*\#)fjc>/8[@TF_GRIqEI4)$c_=0F-;q>f[?BijEKnhA(JPK&RO0kdOZD,R`'aWRgA?:oZ:7Mf2.jkk%ja.INs._h^I0EXAXQ9qgGNVG1j99co\)c[fQ<[ELN3a3?$I(=%unmV%^'oMVa9\O7OXhEoVcd*>cKp.j?En&^'&B+;W:Kho2]+BTnDZRW@"X>+g(c!aAuIsH<S(6(lSK155p:d5A"SAEPUMspXKNHGrI61%Z;J8c3!U%_6pOGCSM+M3q-j5ID1kAhHBBJZUVYdi^7>d,&>)OcK6ou_hZP"HIdf]I?^rD&m?7G4Hkj*md_5m--p'H(`@ZAZhF86T`Mko+>cQ'"GHRjT:Umnf89_j,7s`5lGQ#jNC$p-UacaOWFM+PqL..=8TLR\RH2<\Bc,dHfF?oRdm/uuNl@e0($oJEKe9;cXS\Cd2tOi*TlBT=l>9GZLU;qshi<ob(3tS8*aND?-0n_KNe<7KV:uF/TfJ^()n:Gjj)-R4;#L^@!2S(;W4(dtne-'bo?t`[U8XLfP.X1':]")67D1;%MSM0p<2&dsfN4Jc&D7*qMm,/$+9P*md)D?ni4RfoGmf2D@99I.*sQUt,79C"iBG[F8'l.&oS7Iq1U@T<,2$dsfX+Ss*1pNA7L<3H@m<Lm0"UXmF!jKDm07eWHd4jBdFS<.CY/%7&O*YB0%JA&a5`9NOh_rL,(G%!r=&nT:RJeba"Kge`b8+p(3RT=SAD<4%"Y[Ps'fgM9X,SB5?r:]><j$e8=MhWjWCire_JuG8sas[M_.R3b"H397s``o%_3`=1.2G2Mk\4O1MF;q>5u44nQg``UC-)P%1>P]mfLRI-]GO1p?EU-g$e-dC6]hFG*D<^[_E"UAUDC4ph37O(_9dA5;)uR<]u-G/>TJa(corRV]LJ8NjF9%1)KY++dLTl3i.EC8q9a+/5UDrM+Z?$'eC-OJUjVQe/-:R9r5!2?Bt""[1Y[j@@6uu:WR*c~>endstream
|
| 126 |
-
endobj
|
| 127 |
-
18 0 obj
|
| 128 |
-
<<
|
| 129 |
-
/Filter [ /ASCII85Decode /FlateDecode ] /Length 784
|
| 130 |
-
>>
|
| 131 |
-
stream
|
| 132 |
-
GatU19lo#B&A@Zcp<jiDRCN+!A`ZBpRqX7@-E2@X`F+fhnHZ>DAFf3$Q=MZM1Sr%m=rsa=<mk2<J`&o?kHY)tMW)Hc$C!ih!\/QunI9l'-dNIk14p,`NA)5)T7m!&i"ICbJj'V\7Sl<COGIebOek<LL5^jGl<'sj0CZ)9FUkadXg?!Dc)U=t`/bIVS`CeT4'B]m#R,F`9!U]1n5Vld@bhkU*d"t>Aqq@["C0keCKPiq@Rt<8V@6qV?uBt:>!iQd=7#RfenMR/F_Y^)*#-umd3o;i%S(T(-Ui'lhp+Dk!`N_!?s@#FQ)cIdj\h^VM.9IJH$<R4OdYsK.[$ldT8cUlQF74R]$tkc%jjN1_nFfK<QG:"q!'G+PjeN.4NF#)Qus5&]%4C7>3iW"(<IcDZh>-:C7RJ`e]S8_Ka^AVPsb<C_Dtgt)%,%YN1pT9,.O;g=c@OQ#8U7uqau^EfN,MsV0rtdCn!=b_B=-]de&c1`-55b4tc'i-p;OlN[lrg4ItZW-Ioojqg-_M1V.DXcr!+A=HBOfi',/MH;TY8\npKO;*qoCXQbVDbe!BfB61&7+,mKsgB1W_)m,`?r]=i7q;a9Ac<XfIrTK:.rYiPaCgC!W+*CEj57C.(7eRBKaK`K$c;]>s1$C>03XgW^$`(ZokV4,=%"fTgF#sua/(;ipj4Gk(Bk8Fim%)$p(.)1%]N-([K_INAW\Q(H7[pp:iUj<qp@#3L0.J8mhQd),R$C8Bg163+SJ7t$/:<>@X8c=`]tll#&e8j>:'r=t!#uZpA,~>endstream
|
| 133 |
-
endobj
|
| 134 |
-
19 0 obj
|
| 135 |
-
<<
|
| 136 |
-
/Filter [ /ASCII85Decode /FlateDecode ] /Length 580
|
| 137 |
-
>>
|
| 138 |
-
stream
|
| 139 |
-
GatUo9lJKG&;KZL(%2TfMXd,p:<El85tF-=Ec520lV4\7hDP0BLr>?eekQgbe/X>tIqWHmcL['/"`cfGE0hU7Y/[LQ!hqrnKihjne2On?B0U"f['Er2&h@;4Yq08Q;'F-7$c0YQPQeh<m:ImprbAc3a1;F)%_A^:qY[CW[d0/l-tmDo$e4'2cVq5)Hp4L=>VF*Sq%0%\(Sa08kC\4<k^O7GL&(X9hl6=*II,%fr98;/b09%8\Disg;%D$CTRI'+Boqo>m',)*=Eh^7VL7").O^DQb'PHFfictml*+5:QAug2q$3E(_qqt;*ZL8d4(k=&/uHE#K+"kM*1BJO[NmllcP)X@-baJm]WXbPmjuS/_S5[M%<kOD*1ZT)%A$dkg3m@E_`.(9>VZZ?fGI)/hJ(te^\riLeTpT7s4:t%U5^5Z5jd4-"O;/YS#Qt,4X^h2]l)Qa)n*#0/b)A.X#Fq'&:5I+?IY)qJM3+Ld?-P_8'fZH@e`O->Sl)8<*&-j]bHht+5'p$X++B'<(8U8C9T]F96H]'pV[$hOZjI^No0WuXQ545<bpNj9<>G\C:Y!3qZD(b+Gg~>endstream
|
| 140 |
-
endobj
|
| 141 |
-
xref
|
| 142 |
-
0 20
|
| 143 |
-
0000000000 65535 f
|
| 144 |
-
0000000073 00000 n
|
| 145 |
-
0000000124 00000 n
|
| 146 |
-
0000000231 00000 n
|
| 147 |
-
0000000343 00000 n
|
| 148 |
-
0000000462 00000 n
|
| 149 |
-
0000000657 00000 n
|
| 150 |
-
0000000852 00000 n
|
| 151 |
-
0000001047 00000 n
|
| 152 |
-
0000001242 00000 n
|
| 153 |
-
0000001437 00000 n
|
| 154 |
-
0000001633 00000 n
|
| 155 |
-
0000001703 00000 n
|
| 156 |
-
0000001987 00000 n
|
| 157 |
-
0000002078 00000 n
|
| 158 |
-
0000003376 00000 n
|
| 159 |
-
0000004573 00000 n
|
| 160 |
-
0000005825 00000 n
|
| 161 |
-
0000007309 00000 n
|
| 162 |
-
0000008184 00000 n
|
| 163 |
-
trailer
|
| 164 |
-
<<
|
| 165 |
-
/ID
|
| 166 |
-
[<1a5d3db62a9cf35878b63f1a28acd618><1a5d3db62a9cf35878b63f1a28acd618>]
|
| 167 |
-
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
| 168 |
-
|
| 169 |
-
/Info 12 0 R
|
| 170 |
-
/Root 11 0 R
|
| 171 |
-
/Size 20
|
| 172 |
-
>>
|
| 173 |
-
startxref
|
| 174 |
-
8855
|
| 175 |
-
%%EOF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data/reports/analysis_1762204265013_20251104_024139.pdf
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
%PDF-1.4
|
| 2 |
+
%���� ReportLab Generated PDF document http://www.reportlab.com
|
| 3 |
+
1 0 obj
|
| 4 |
+
<<
|
| 5 |
+
/F1 2 0 R /F2 3 0 R /F3 4 0 R
|
| 6 |
+
>>
|
| 7 |
+
endobj
|
| 8 |
+
2 0 obj
|
| 9 |
+
<<
|
| 10 |
+
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
| 11 |
+
>>
|
| 12 |
+
endobj
|
| 13 |
+
3 0 obj
|
| 14 |
+
<<
|
| 15 |
+
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
| 16 |
+
>>
|
| 17 |
+
endobj
|
| 18 |
+
4 0 obj
|
| 19 |
+
<<
|
| 20 |
+
/BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
| 21 |
+
>>
|
| 22 |
+
endobj
|
| 23 |
+
5 0 obj
|
| 24 |
+
<<
|
| 25 |
+
/Contents 14 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 26 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 27 |
+
>> /Rotate 0 /Trans <<
|
| 28 |
+
|
| 29 |
+
>>
|
| 30 |
+
/Type /Page
|
| 31 |
+
>>
|
| 32 |
+
endobj
|
| 33 |
+
6 0 obj
|
| 34 |
+
<<
|
| 35 |
+
/Contents 15 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 36 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 37 |
+
>> /Rotate 0 /Trans <<
|
| 38 |
+
|
| 39 |
+
>>
|
| 40 |
+
/Type /Page
|
| 41 |
+
>>
|
| 42 |
+
endobj
|
| 43 |
+
7 0 obj
|
| 44 |
+
<<
|
| 45 |
+
/Contents 16 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 46 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 47 |
+
>> /Rotate 0 /Trans <<
|
| 48 |
+
|
| 49 |
+
>>
|
| 50 |
+
/Type /Page
|
| 51 |
+
>>
|
| 52 |
+
endobj
|
| 53 |
+
8 0 obj
|
| 54 |
+
<<
|
| 55 |
+
/Contents 17 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 56 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 57 |
+
>> /Rotate 0 /Trans <<
|
| 58 |
+
|
| 59 |
+
>>
|
| 60 |
+
/Type /Page
|
| 61 |
+
>>
|
| 62 |
+
endobj
|
| 63 |
+
9 0 obj
|
| 64 |
+
<<
|
| 65 |
+
/Contents 18 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 66 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 67 |
+
>> /Rotate 0 /Trans <<
|
| 68 |
+
|
| 69 |
+
>>
|
| 70 |
+
/Type /Page
|
| 71 |
+
>>
|
| 72 |
+
endobj
|
| 73 |
+
10 0 obj
|
| 74 |
+
<<
|
| 75 |
+
/Contents 19 0 R /MediaBox [ 0 0 612 792 ] /Parent 13 0 R /Resources <<
|
| 76 |
+
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
| 77 |
+
>> /Rotate 0 /Trans <<
|
| 78 |
+
|
| 79 |
+
>>
|
| 80 |
+
/Type /Page
|
| 81 |
+
>>
|
| 82 |
+
endobj
|
| 83 |
+
11 0 obj
|
| 84 |
+
<<
|
| 85 |
+
/PageMode /UseNone /Pages 13 0 R /Type /Catalog
|
| 86 |
+
>>
|
| 87 |
+
endobj
|
| 88 |
+
12 0 obj
|
| 89 |
+
<<
|
| 90 |
+
/Author (\(anonymous\)) /CreationDate (D:20251104024139-05'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20251104024139-05'00') /Producer (ReportLab PDF Library - www.reportlab.com)
|
| 91 |
+
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
| 92 |
+
>>
|
| 93 |
+
endobj
|
| 94 |
+
13 0 obj
|
| 95 |
+
<<
|
| 96 |
+
/Count 6 /Kids [ 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R ] /Type /Pages
|
| 97 |
+
>>
|
| 98 |
+
endobj
|
| 99 |
+
14 0 obj
|
| 100 |
+
<<
|
| 101 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1202
|
| 102 |
+
>>
|
| 103 |
+
stream
|
| 104 |
+
Gatm;gN&fD&:O:SoLl3Se=)YU&E#JpEO*2fmq+BBqU*89njFoZ%VQj.hkuS>_)$V"EeYK:DRrUc[JD(G+S\e)1FiI^-Lgm;$C!hs#F'QTOt4%9:;38qV\oaG.7[s6iG4LpSC.\UR"g3J:iB,@KD)MT<$3Fg7Tr@&>/2Q[Gl'8L_!/h1ON9p$n"l><<`KGBAX`',e\GgC?e,!1,[V^?"(q]dAFqN=:KWI0=]WJ^-A^U:$k[61S:6^!XA^]0J<'Z4*b;OA2%]a+nmqit)@/DpY8>e2ND\;ZGU=\kEo4tn<(,Xl"HU';d=C3@LaMTMKe[IZ7Y^'Oi2h)jOk0D$2O"O++mn+*+U`OR`tK%LB'o&Cq*F>TD"[>:.-GR#/nt]:Dk9ACn/-fd]a`$iTY@+HqEa)&_>ggtW(`fo\97$mM*V6lU\qAIddlb%<a*];&EF]Qe2Ir+9V:TbSZYtqoR8e6#NITnHF8\tb^T!=73!;/n3+@E6:d*t[<fc5IL-/_`'(Df!SXu:s#a[`jZ0uSaud"nAL83lE(D-T*!I)sbQ#q)X]&6b4fkrh?;A<;@0qjG'I%%C]5M65ll*Bs%l.MYhkGdTa+s)tR<h*]1HYq5\drH4YNS-Z&$/:W\)l;oj"k`2K]i)enR>e>N2-,IJ`h1fY:#I;qR1V2j*GS62-Lc43Z!Rec6W,]=&Z'Qp8XU^hB:s`4D]4/32]CuTi5ni@p5._Cq$"XB-Egp`$T\FhIH4kZ>!:KKgZZ^>IG66(\$Rt2"O;sM3129Tp*5JeLn+9MKLPDcaD>>5Wc/B-N:F&?`MBH+<fZKR]Ej0"^GlQ[G7YHW1EV+q<5V,2!QFV/183I@<J)TQ,/GZgFmlM^]]9Dp1>sbAub_lYUIAf*kS+@>O0+%O(Up2F(<.-)G_?-f=4T%Orig-<rSUB@Ol`#lWpB3PJqA93F*tl`mUNDfE/c<F^<hlVnnmpnA7aF@>jlDXRC6+LgtNFc)LR=dA.WONNUXuI\9\a4-JP-W6QSJRXg%Y@B9,0l[5TK4a1/40D+MmL%aGK\Q62LD`j,hWJ'#kG#sN7Htu@c6I=l_?hjTbZJtIGr8W-rUkMChG*Ttf8;WOm-PA2]Z#>[T.FsZGii83^JJ7%;PD-lqE7)dHJ6EgC3gh'M)(p4gET\V8peYDp:snkPl('"8Bbu6fMuG_-<K>>I[?6=.#q%q;hn1LS~>endstream
|
| 105 |
+
endobj
|
| 106 |
+
15 0 obj
|
| 107 |
+
<<
|
| 108 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1110
|
| 109 |
+
>>
|
| 110 |
+
stream
|
| 111 |
+
Gb"/f>u03/'Sc)R/'cD8Z8'[#S1j:IFCPuTJ/6c@2l8GY/58ar0ZpSkj#EkeERIQmd\NjWA"C8]I:=;qBaA,B+mO8."o('1&"a+=$\gT*.&&CXL>K.Uh]Z1f\DUMU'[%=oZ#nEVh8mH)p74elHf7kIF9a#77j:`3Zie3-(qqS<:Z^cmRKPga2L4OmU_G419BWW<TZkG/7dOINF<$=ZhNIo%N6",gYlEg3mguY5qd88%*1ciu^Kp6VGB/V^/\4L;8X`njlS&[b]O;)JGC5)$Qosb$Bo_a5M`8#n?)Z:]CT30Z*[L8M39Kae2u%*hk9A79k9rnTcM7#mic*#Wa_fMnQ?p_[:%;OAo''lm\]!?tJtsuI?M3`8bbFnHcQM8Q.%Ur)>uQ+e+s'Ou/^0Rn4H6[Q@.@@UB-PPf3+<KTE)nACA+dsNS=lbSl3_kuX[=^C)"B?Q\OaV8[b"X=eFi.ngGd_C_1#OrC&l-Q3-_./?r>3DX"#2g(%fBH#1YgWX3!.:lTW6VfJ32J<+uPG]Xb66iulAu(883.pG,=\Xd3/Z+@8bf'JD5taAV=UjAY*idjIsI@b5nDdQ%`Q-c6Q2Q<N`:"CZ+%/PhPgI)]T^r89<"a_FSpVPP=Y)Q[a>!ak'`Y(I]f!gg_!p$B#trNd.R4sYY-=N"4A#R2c9FUYHAn<0Z]qm%Tc<Y^n%.dB)V-lZ/[mYWbH)cb**3[PeGbbdEJ)iIS+C(O1]#c0#Ci`bGBEsbb\,IJKBj#0=`]<mQ:]i_)PCu2]#D/.[*D]*MOBj2pQAYJ#sLaGE%D3JXrORAEk[`6BlT\5X;;9>m_XnNs!C*k4TK\GDFn:@L*S@B2=r'"3FBHGJt?7`d]eg?.)\@08_9/5F9;]5UHNZHo\'OL4?H@Sj\lkY%!,..2\Z&bjRo[Y-7:[V7NXnGS!"K]U>Bm<_p8h7=q&)+6FW9Wa)>\E^0CWDcjEk#LkaQ+3*pf_\8O"W<'Lh@sA#%*%0Ac2;lFi)U1`#kJ/QX]8BDOpU<!&3sn!:pKe[g=a\2SA&J)8L'8Q&5/:o/A.f21Y?[bDNbY&H#D@lKJ=p(<.#D=?O^YKYoVao*@g"P%F*-m=-GGquT%BdG"~>endstream
|
| 112 |
+
endobj
|
| 113 |
+
16 0 obj
|
| 114 |
+
<<
|
| 115 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1161
|
| 116 |
+
>>
|
| 117 |
+
stream
|
| 118 |
+
Gb"/f>BA7S&BE]".JAI`*i:/-bEd#%EmetI/qjFQ<ZO%4p_%,61jjTp>>CA?MB.GhbaDm[dqj4-.ncrAf`\O)Ole_U!</P*P@k"h!4+0u+UVZO)U;ujD^47(,6i-!)J<QtND1uo-B)c*9Pe-:-kP14"BORD9P?&"0o%\S/"+p`s+/i>Qp%nCn'1/N"%a/6Qon&NB9q=ER_Z&<bbRmJk*%^>QMd$bM=AOR'@0`h^Dhfi1d:AGK]77e(IB]pE@^_9YNnMgKS$ue)i'F!gI`2CQTr]7$rM1F(t$t`*6!o\pgsP*;//s?@Z[sTUP,<HqntkW3)0)D3,obT!fWkp-Q^WXf49/s\;MR\8lM??CSgjoERJ\@9lZ,TE6HMWkUZ9#1,=bV;F]UDmt^)gn*d%"8s'ep#RG383D:ABFuI!C_-9+j^CMOq%Q^X.Fk.gY7Pj7E/9*R(k\LiN2DaQ51itW3fOTiN,dF\<0Pol]J%PNh^NX3_;i">/&pn6ZgKE=cqqHg?3_d5RMLq0uAhYi_^92$qY[)o:gR#i:N7?`=ijOG&#rS';Id3m;1EkrVio?Nu>OMT/Me;l"/lZu804f#nm!tu)V]ip<I$um/o<-;@%$YlXN#duhRO(1d<99C8gI#sGchW:r@U?aHEB,[f;-`5b4q"p25.$i^ItGb:S[5`%W59r#<'4]X2<VH]OJSnLm]%\9Y(a0JH4%5e2MTWoQ#-in;rHaWnnK=(T#4Q7IJ;<]SJ'a_C;1R7PYb^Ms'Yn&\qPcN<[j+((B"Z:]GPW*S(oTDTBHK,C"NO%%q6upe?US$1gL)N\;tj?VGb^(][D5&/cqg#/*_@jY$R#j]eZFm[1L$=_haDlPdBSG>d);RYh??H_I4.SJ+5E&D\P"Gp>@MnE,.ZJDm*`_=IHk[3Mm>a!(kUelMgsh"UrfYJns<G%fHI\U@c5XIuS4hfL>78Y`Y:].[tcm)qVL%DceDo'O3T8>&pa!.dL$3f;7V4bIbXIjgK93p"EY:_:;V\r3oRI@c?1$qn74^R'3Oi0\n@])U>.(`iFkWfbn=FHa"`['g+.WWg<UEEthL&6ac/j6RS?4m3S74<V&C$3_o')-4Z/]EMdE5*kjepWm'K'(O;k3Q82Coa>JeR>cB`2XSH<*icb%^0B.-d#G[nT4"$0~>endstream
|
| 119 |
+
endobj
|
| 120 |
+
17 0 obj
|
| 121 |
+
<<
|
| 122 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1455
|
| 123 |
+
>>
|
| 124 |
+
stream
|
| 125 |
+
Gat=+gN)%,&:N/3lq:r$9nVB@X5D\h:".$p@Op]$AhoU,84\$26-?2hrV/!1OJ6PhQ>;OMGe#fGfZRER!QADX5?Bk\cZ=@(.g*e"rZ.@r4ICVK2tP/YKK2akJ<38H6M8I`#BIs+q<`i=WEr@3OEEGm2+N&Ai>_FRjYj.SI$2(tl=EkoZ@M7%=fP0GpLc2[!V;_!iU_U\(.Soi2It,R+L_(#H/EZE]bJJEkuXi_[AqQ"(4'^(I<1:\IC[#c333j^;]D9cGOS1j9BYpZ9W*pP'JIH[7GQPOk"0P:XP4@C&rshL)u]$eZM:S#=fgZr4Js-P,T+MlXid--P5-ksn>B7Oc*;4GqFhkqhV,V+-mV"m-P[6qds[sgF'7$.eFN&HWj[Fc>GP079u"9JPq(@hg?Q9QKbBOmamLZZN>A"'.3,FsU"\qHoS]daQ,s^mAZaL_3^6fpSemA$F>R=NN(,_4NJWc#ppANIRUfV_QIV@*SNjJb.guf5:k-S\D9=US#BkGJZi1eXkV[d/[r*-*@r_pbgF.kJ7gh&Y.XO"^13jq]F;l'ji>AF^Xie[^k=MQM3H4[@Gdj"P&WHEm6S0>'_=ahX,<7K?:7i0E`ul=1H/05.'8Sp^(`Y@WKAZ4fa+X5P`;tUHc`=PMH8d8]%Z;J8c2tW#i7^f&PL0I=mHHHG+7Ea)[o-J=d`?W!>dUdZE\^n,17pQE/m=\4S.$4)Q*<+Y3Z7NOl]6>"rX<b/k:mSX$pBY3q<`j^!.R<X1e)0^K_ZThO/Tp2W.Nb)Lg;%/<$>>/T<;^&Pm)3X0gZM%B6T`)6o7;pSG58W".U1>*dP4'#cb\6R+/EnSZ4WQ5u*282_e\p%4P^=(2$H^Or?QLj'-oVAsm@qHe=_%ol?s1?-?\8_bF1[*ekT^*+L2p&Qgu0H8?$45r]c*/Rq,T.<gl?aPuEMEMemf[B1Rn94[S"Mcqb!9po#b^7_ABBt(mlb2f,;c9Y8HN9%bV7X,aV+fknbmJ0D6dnbp3NL)[`iP8s*AV%9G4@r`=#i!Xc/hAZ"G4m(N,jVk(lXn;l!4j1$,a"q3ncL8:ZHYYp4ZSsN&`A*(E<Q\=)u9*.?16l<]PGr$lD(\Ha8@1b&'"-kKcu2s?LPW$YBSkbhQta@Y6<4jE,g%2o-Xe<_D:*0m&8BA8GGHW3;*f2I^Jh#-Fc%o]"i%XKU#pnB#J=f:!R@aWuBX)_f5R4&a^=C.rG%?Fk[n7mh=$'hjT,HlelN)F&QgPX%V779)G8.8aQ&iN(ERa[loOYXej6Q;'Sq?1d$2'rnVeU9nM3($Z&%sG4@&4gbnRTP8LE^J%#4f*VN!7VO0GUZ_sLN*Y5ar[g,*_&f)!8%h&BW2jQYFS5Kf.i'+c$Y]F!rle.cPcJc@YV2Ht%WnM*`e?D?tOucMF;WT7QaU5A+ahg.O0]f!%1F6b89\/]^LN,-RrfDA4nfNqtQWj~>endstream
|
| 126 |
+
endobj
|
| 127 |
+
18 0 obj
|
| 128 |
+
<<
|
| 129 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 781
|
| 130 |
+
>>
|
| 131 |
+
stream
|
| 132 |
+
GatU1?#Q2d'Re<2\1a+&Uj2k?j1_AC<c.-OO0Q1-OPk5f"B%Qcp##4I9M.K6b8-?#S/fhb[a0(5:qW1o>c[q.,5Cqe!TO_CJOq%/\q/2Tk[h[Pdgn^K&BT_?)W46hTis2S$1g0;TR7`Q&DLooapN8O.U(Z4<ZOuN_Y56/),.*29]t.[/b0D`4[J/ZQM5X;gG0g8+GKF;MYg9Rd306nfCt@7^\5p7*=+Kb0Yr*M@-NZL'ks\RAPcS8GE_@B)LtWL@`N\h8^^%=P,?7B:1<N.onkHQE&+&E=`97ET$2Z(eY1!%+^A6*cJlX@aHpUMnqB*9*iGl,E,f2%@5EsR'rO(igmI/=lUKjejJImBcGM;YMspDH'aUGaF<0@?':6;uDSBbB2]3:@m+RCU!P9k?Gs"m/[!IA*d_sI'nN%5hJ:e8SG1Zmb\pS:j!ianD0f7it#SX(@crg6k-YU)@>oC$b2\ka6BhRHUmqA7@FNF?qAj_$@fF&rE#I8.2fUD;fe"jPo/`e=;=##M[.E>@4m$3aJMo[BmdsAM"eJr^&%8R>3[]Vk,Ui4HPJ=pC&6@/m$A^%H)Rhu]U]A@H%p!bG$3aCS3rT&.gi>TJDmR":d0rd_]r3.Sl(u7PmLXZ0Ir1s$,Lk?IAJ\uHR@_Qk"%U)PZ%`:Sj@CD_`DOjqW39.\p51.JMq:IaKSLgUTnFJ]OHO`6,GZ3[;rn,uC[?"^Fjg0GaNIN[Z_"1>"3PdP5ZaWZ?n[q.tp<U`GZ9nT;Hk-/%4RWpZNe5t&7XkD`XjRp~>endstream
|
| 133 |
+
endobj
|
| 134 |
+
19 0 obj
|
| 135 |
+
<<
|
| 136 |
+
/Filter [ /ASCII85Decode /FlateDecode ] /Length 580
|
| 137 |
+
>>
|
| 138 |
+
stream
|
| 139 |
+
GatUo9lJN8&;KZN/*7%(-,^t[m$8<LJrk9YPhrpo3jZGqM&lL+Lr>?eS88dse30$uq2iXGkBSFb!>uD&4_4lfY(g"""Ge&_4bt:6TY?!CHM(bo:+OLF<+<b'GqR@mHrZ8?gHtC?.a"9r^`HN-,lVPb9#nM'.o*[FW;?EB8^hgX)t_9dpB'XK:a\!P(EO)BY.)B?0q7<kqY@5=gtAKq:\rs:<C)f'W&n`a3/.q9bF_g\:HD'"iJM]VL+Glj4oOr%\q(]"q>a%I]Wi^J9oO:E9:c-S,NkHHVe9G=Vo)i3=?9tdlQ1"Sn4-u\3S:%<4'3/<F0Pk^Qs;W4_c0TO@?6QRmQ>LbFQ2U5aST5ppXZ!"7dO>Lcu@NGN.E8k>48tSZ.oFcS+_C(BVSG?SI'`\kcI1Sp=i@S*1c8ArI:)53geiU#&NIK7<$P!?W7q;4-0Q&]kZ9k%GNjq(:b`q<BE9H#X(+#0BuAJcSqM]_4hpqUM'+91&PLpgudkC<*EmC],%Vu5B==<D#SGMOYed826\$pV)ieMqrrTL9+Q<:_/K*bNT?+[<bp75-#ZQ%@X/Spo`piR+Kk~>endstream
|
| 140 |
+
endobj
|
| 141 |
+
xref
|
| 142 |
+
0 20
|
| 143 |
+
0000000000 65535 f
|
| 144 |
+
0000000073 00000 n
|
| 145 |
+
0000000124 00000 n
|
| 146 |
+
0000000231 00000 n
|
| 147 |
+
0000000343 00000 n
|
| 148 |
+
0000000462 00000 n
|
| 149 |
+
0000000657 00000 n
|
| 150 |
+
0000000852 00000 n
|
| 151 |
+
0000001047 00000 n
|
| 152 |
+
0000001242 00000 n
|
| 153 |
+
0000001437 00000 n
|
| 154 |
+
0000001633 00000 n
|
| 155 |
+
0000001703 00000 n
|
| 156 |
+
0000001987 00000 n
|
| 157 |
+
0000002078 00000 n
|
| 158 |
+
0000003372 00000 n
|
| 159 |
+
0000004574 00000 n
|
| 160 |
+
0000005827 00000 n
|
| 161 |
+
0000007374 00000 n
|
| 162 |
+
0000008246 00000 n
|
| 163 |
+
trailer
|
| 164 |
+
<<
|
| 165 |
+
/ID
|
| 166 |
+
[<c5431da438e3ec00058a42923ba124dc><c5431da438e3ec00058a42923ba124dc>]
|
| 167 |
+
% ReportLab generated PDF document -- digest (http://www.reportlab.com)
|
| 168 |
+
|
| 169 |
+
/Info 12 0 R
|
| 170 |
+
/Root 11 0 R
|
| 171 |
+
/Size 20
|
| 172 |
+
>>
|
| 173 |
+
startxref
|
| 174 |
+
8917
|
| 175 |
+
%%EOF
|
detector/attribution.py
CHANGED
|
@@ -77,7 +77,7 @@ class ModelAttributor:
|
|
| 77 |
- Confidence-weighted aggregation
|
| 78 |
- Explainable reasoning
|
| 79 |
"""
|
| 80 |
-
#
|
| 81 |
METRIC_WEIGHTS = {"perplexity" : 0.25,
|
| 82 |
"structural" : 0.15,
|
| 83 |
"semantic_analysis" : 0.15,
|
|
@@ -86,7 +86,7 @@ class ModelAttributor:
|
|
| 86 |
"multi_perturbation_stability" : 0.10,
|
| 87 |
}
|
| 88 |
|
| 89 |
-
#
|
| 90 |
DOMAIN_MODEL_PREFERENCES = {Domain.GENERAL : [AIModel.GPT_4, AIModel.CLAUDE_3_SONNET, AIModel.GEMINI_PRO, AIModel.GPT_3_5],
|
| 91 |
Domain.ACADEMIC : [AIModel.GPT_4, AIModel.CLAUDE_3_OPUS, AIModel.GEMINI_ULTRA, AIModel.GPT_4_TURBO],
|
| 92 |
Domain.TECHNICAL_DOC : [AIModel.GPT_4_TURBO, AIModel.CLAUDE_3_SONNET, AIModel.LLAMA_3, AIModel.GPT_4],
|
|
@@ -105,7 +105,7 @@ class ModelAttributor:
|
|
| 105 |
Domain.TUTORIAL : [AIModel.GPT_4, AIModel.CLAUDE_3_SONNET, AIModel.GEMINI_PRO, AIModel.GPT_4_TURBO],
|
| 106 |
}
|
| 107 |
|
| 108 |
-
#
|
| 109 |
MODEL_FINGERPRINTS = {AIModel.GPT_3_5 : {"phrases" : ["as an ai language model",
|
| 110 |
"i don't have personal opinions",
|
| 111 |
"it's important to note that",
|
|
@@ -460,13 +460,13 @@ class ModelAttributor:
|
|
| 460 |
domain = domain,
|
| 461 |
)
|
| 462 |
|
| 463 |
-
# Domain-aware prediction
|
| 464 |
predicted_model, confidence = self._make_domain_aware_prediction(combined_scores = combined_scores,
|
| 465 |
domain = domain,
|
| 466 |
domain_preferences = domain_preferences,
|
| 467 |
)
|
| 468 |
|
| 469 |
-
# Reasoning with domain context
|
| 470 |
reasoning = self._generate_detailed_reasoning(predicted_model = predicted_model,
|
| 471 |
confidence = confidence,
|
| 472 |
domain = domain,
|
|
@@ -490,7 +490,7 @@ class ModelAttributor:
|
|
| 490 |
|
| 491 |
def _calculate_fingerprint_scores(self, text: str, domain: Domain) -> Dict[AIModel, float]:
|
| 492 |
"""
|
| 493 |
-
Calculate fingerprint match scores with
|
| 494 |
"""
|
| 495 |
scores = {model: 0.0 for model in AIModel if model not in [AIModel.HUMAN, AIModel.UNKNOWN]}
|
| 496 |
|
|
@@ -812,7 +812,7 @@ class ModelAttributor:
|
|
| 812 |
|
| 813 |
def _make_domain_aware_prediction(self, combined_scores: Dict[str, float], domain: Domain, domain_preferences: List[AIModel]) -> Tuple[AIModel, float]:
|
| 814 |
"""
|
| 815 |
-
Domain aware prediction that considers domain-specific model preferences
|
| 816 |
"""
|
| 817 |
if not combined_scores:
|
| 818 |
return AIModel.UNKNOWN, 0.0
|
|
@@ -825,109 +825,103 @@ class ModelAttributor:
|
|
| 825 |
|
| 826 |
best_model_name, best_score = sorted_models[0]
|
| 827 |
|
| 828 |
-
#
|
| 829 |
-
|
| 830 |
-
if best_score < 0.05: # Changed from 0.08 to 0.05 to be less restrictive
|
| 831 |
return AIModel.UNKNOWN, best_score
|
| 832 |
|
| 833 |
-
# FIXED: Don't override with domain preferences if there's a clear winner
|
| 834 |
-
# Only consider domain preferences if scores are very close
|
| 835 |
-
if len(sorted_models) > 1:
|
| 836 |
-
second_model_name, second_score = sorted_models[1]
|
| 837 |
-
score_difference = best_score - second_score
|
| 838 |
-
|
| 839 |
-
# If scores are very close (within 3%) and second is domain-preferred, consider it
|
| 840 |
-
if score_difference < 0.03:
|
| 841 |
-
try:
|
| 842 |
-
best_model = AIModel(best_model_name)
|
| 843 |
-
second_model = AIModel(second_model_name)
|
| 844 |
-
|
| 845 |
-
# If second model is domain-preferred and first is not, prefer second
|
| 846 |
-
if (second_model in domain_preferences and
|
| 847 |
-
best_model not in domain_preferences):
|
| 848 |
-
best_model_name = second_model_name
|
| 849 |
-
best_score = second_score
|
| 850 |
-
except ValueError:
|
| 851 |
-
pass
|
| 852 |
-
|
| 853 |
try:
|
| 854 |
best_model = AIModel(best_model_name)
|
|
|
|
| 855 |
except ValueError:
|
| 856 |
best_model = AIModel.UNKNOWN
|
| 857 |
|
| 858 |
-
# Calculate confidence
|
| 859 |
-
if len(sorted_models) > 1:
|
| 860 |
second_score = sorted_models[1][1]
|
| 861 |
-
margin
|
| 862 |
-
#
|
| 863 |
-
confidence
|
|
|
|
| 864 |
else:
|
| 865 |
-
confidence = best_score * 0.
|
| 866 |
|
| 867 |
-
#
|
| 868 |
-
|
| 869 |
-
return best_model, confidence
|
| 870 |
|
| 871 |
|
| 872 |
def _generate_detailed_reasoning(self, predicted_model: AIModel, confidence: float, domain: Domain, metric_contributions: Dict[str, float],
|
| 873 |
combined_scores: Dict[str, float]) -> List[str]:
|
| 874 |
"""
|
| 875 |
-
Generate Explainable reasoning -
|
| 876 |
"""
|
| 877 |
reasoning = []
|
| 878 |
|
| 879 |
reasoning.append("**AI Model Attribution Analysis**")
|
| 880 |
reasoning.append("")
|
| 881 |
-
reasoning.append(f"**Domain**: {domain.value.replace('_', ' ').title()}")
|
| 882 |
-
reasoning.append("")
|
| 883 |
|
| 884 |
# Show prediction with confidence
|
| 885 |
if (predicted_model == AIModel.UNKNOWN):
|
| 886 |
reasoning.append("**Most Likely**: Unable to determine with high confidence")
|
| 887 |
-
|
| 888 |
-
reasoning.append("**Top Candidates:**")
|
| 889 |
-
|
| 890 |
else:
|
| 891 |
model_name = predicted_model.value.replace("-", " ").replace("_", " ").title()
|
| 892 |
reasoning.append(f"**Predicted Model**: {model_name}")
|
| 893 |
reasoning.append(f"**Confidence**: {confidence*100:.1f}%")
|
| 894 |
-
reasoning.append("")
|
| 895 |
-
reasoning.append("**Model Probability Distribution:**")
|
| 896 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 897 |
reasoning.append("")
|
| 898 |
|
| 899 |
-
# Show top candidates in proper format
|
| 900 |
if combined_scores:
|
| 901 |
sorted_models = sorted(combined_scores.items(), key = lambda x: x[1], reverse = True)
|
| 902 |
|
| 903 |
for i, (model_name, score) in enumerate(sorted_models[:6]):
|
| 904 |
-
# Skip very low
|
| 905 |
if (score < 0.01):
|
| 906 |
continue
|
| 907 |
-
|
| 908 |
display_name = model_name.replace("-", " ").replace("_", " ").title()
|
| 909 |
percentage = score * 100
|
| 910 |
|
| 911 |
-
#
|
| 912 |
reasoning.append(f"• **{display_name}**: {percentage:.1f}%")
|
| 913 |
|
| 914 |
reasoning.append("")
|
| 915 |
|
| 916 |
-
#
|
| 917 |
reasoning.append("**Analysis Notes:**")
|
| 918 |
-
reasoning.append(f"• Calibrated for {domain.value.replace('_', ' ')} domain")
|
| 919 |
-
|
| 920 |
-
if (domain in [Domain.ACADEMIC, Domain.TECHNICAL_DOC, Domain.AI_ML, Domain.SOFTWARE_DEV, Domain.ENGINEERING, Domain.SCIENCE]):
|
| 921 |
-
reasoning.append("• Higher weight on structural coherence and technical patterns")
|
| 922 |
|
| 923 |
-
|
| 924 |
-
reasoning.append("•
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 931 |
|
| 932 |
return reasoning
|
| 933 |
|
|
|
|
| 77 |
- Confidence-weighted aggregation
|
| 78 |
- Explainable reasoning
|
| 79 |
"""
|
| 80 |
+
# Metric weights from technical specification
|
| 81 |
METRIC_WEIGHTS = {"perplexity" : 0.25,
|
| 82 |
"structural" : 0.15,
|
| 83 |
"semantic_analysis" : 0.15,
|
|
|
|
| 86 |
"multi_perturbation_stability" : 0.10,
|
| 87 |
}
|
| 88 |
|
| 89 |
+
# Domain-aware model patterns for ALL 16 DOMAINS
|
| 90 |
DOMAIN_MODEL_PREFERENCES = {Domain.GENERAL : [AIModel.GPT_4, AIModel.CLAUDE_3_SONNET, AIModel.GEMINI_PRO, AIModel.GPT_3_5],
|
| 91 |
Domain.ACADEMIC : [AIModel.GPT_4, AIModel.CLAUDE_3_OPUS, AIModel.GEMINI_ULTRA, AIModel.GPT_4_TURBO],
|
| 92 |
Domain.TECHNICAL_DOC : [AIModel.GPT_4_TURBO, AIModel.CLAUDE_3_SONNET, AIModel.LLAMA_3, AIModel.GPT_4],
|
|
|
|
| 105 |
Domain.TUTORIAL : [AIModel.GPT_4, AIModel.CLAUDE_3_SONNET, AIModel.GEMINI_PRO, AIModel.GPT_4_TURBO],
|
| 106 |
}
|
| 107 |
|
| 108 |
+
# Model-specific fingerprints with comprehensive patterns
|
| 109 |
MODEL_FINGERPRINTS = {AIModel.GPT_3_5 : {"phrases" : ["as an ai language model",
|
| 110 |
"i don't have personal opinions",
|
| 111 |
"it's important to note that",
|
|
|
|
| 460 |
domain = domain,
|
| 461 |
)
|
| 462 |
|
| 463 |
+
# Domain-aware prediction : Always show the actual highest probability model
|
| 464 |
predicted_model, confidence = self._make_domain_aware_prediction(combined_scores = combined_scores,
|
| 465 |
domain = domain,
|
| 466 |
domain_preferences = domain_preferences,
|
| 467 |
)
|
| 468 |
|
| 469 |
+
# Reasoning with domain context
|
| 470 |
reasoning = self._generate_detailed_reasoning(predicted_model = predicted_model,
|
| 471 |
confidence = confidence,
|
| 472 |
domain = domain,
|
|
|
|
| 490 |
|
| 491 |
def _calculate_fingerprint_scores(self, text: str, domain: Domain) -> Dict[AIModel, float]:
|
| 492 |
"""
|
| 493 |
+
Calculate fingerprint match scores with domain calibration - for all domains
|
| 494 |
"""
|
| 495 |
scores = {model: 0.0 for model in AIModel if model not in [AIModel.HUMAN, AIModel.UNKNOWN]}
|
| 496 |
|
|
|
|
| 812 |
|
| 813 |
def _make_domain_aware_prediction(self, combined_scores: Dict[str, float], domain: Domain, domain_preferences: List[AIModel]) -> Tuple[AIModel, float]:
|
| 814 |
"""
|
| 815 |
+
Domain aware prediction that considers domain-specific model preferences
|
| 816 |
"""
|
| 817 |
if not combined_scores:
|
| 818 |
return AIModel.UNKNOWN, 0.0
|
|
|
|
| 825 |
|
| 826 |
best_model_name, best_score = sorted_models[0]
|
| 827 |
|
| 828 |
+
# Thresholding to show model only if confidence is sufficient
|
| 829 |
+
if (best_score < 0.01):
|
|
|
|
| 830 |
return AIModel.UNKNOWN, best_score
|
| 831 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
try:
|
| 833 |
best_model = AIModel(best_model_name)
|
| 834 |
+
|
| 835 |
except ValueError:
|
| 836 |
best_model = AIModel.UNKNOWN
|
| 837 |
|
| 838 |
+
# Calculate confidence - be more generous
|
| 839 |
+
if (len(sorted_models) > 1):
|
| 840 |
second_score = sorted_models[1][1]
|
| 841 |
+
margin = best_score - second_score
|
| 842 |
+
# More generous confidence calculation
|
| 843 |
+
confidence = min(1.0, best_score * 0.8 + margin * 1.5)
|
| 844 |
+
|
| 845 |
else:
|
| 846 |
+
confidence = best_score * 0.9
|
| 847 |
|
| 848 |
+
# Always return the actual best model, never downgrade to UNKNOWN
|
| 849 |
+
return best_model, max(0.05, confidence)
|
|
|
|
| 850 |
|
| 851 |
|
| 852 |
def _generate_detailed_reasoning(self, predicted_model: AIModel, confidence: float, domain: Domain, metric_contributions: Dict[str, float],
|
| 853 |
combined_scores: Dict[str, float]) -> List[str]:
|
| 854 |
"""
|
| 855 |
+
Generate Explainable reasoning - ENHANCED version
|
| 856 |
"""
|
| 857 |
reasoning = []
|
| 858 |
|
| 859 |
reasoning.append("**AI Model Attribution Analysis**")
|
| 860 |
reasoning.append("")
|
|
|
|
|
|
|
| 861 |
|
| 862 |
# Show prediction with confidence
|
| 863 |
if (predicted_model == AIModel.UNKNOWN):
|
| 864 |
reasoning.append("**Most Likely**: Unable to determine with high confidence")
|
| 865 |
+
|
|
|
|
|
|
|
| 866 |
else:
|
| 867 |
model_name = predicted_model.value.replace("-", " ").replace("_", " ").title()
|
| 868 |
reasoning.append(f"**Predicted Model**: {model_name}")
|
| 869 |
reasoning.append(f"**Confidence**: {confidence*100:.1f}%")
|
|
|
|
|
|
|
| 870 |
|
| 871 |
+
reasoning.append(f"**Domain**: {domain.value.replace('_', ' ').title()}")
|
| 872 |
+
reasoning.append("")
|
| 873 |
+
|
| 874 |
+
# Show model probability distribution
|
| 875 |
+
reasoning.append("**Model Probability Distribution:**")
|
| 876 |
reasoning.append("")
|
| 877 |
|
|
|
|
| 878 |
if combined_scores:
|
| 879 |
sorted_models = sorted(combined_scores.items(), key = lambda x: x[1], reverse = True)
|
| 880 |
|
| 881 |
for i, (model_name, score) in enumerate(sorted_models[:6]):
|
| 882 |
+
# Skip very low probabilities
|
| 883 |
if (score < 0.01):
|
| 884 |
continue
|
| 885 |
+
|
| 886 |
display_name = model_name.replace("-", " ").replace("_", " ").title()
|
| 887 |
percentage = score * 100
|
| 888 |
|
| 889 |
+
# Use proper markdown formatting
|
| 890 |
reasoning.append(f"• **{display_name}**: {percentage:.1f}%")
|
| 891 |
|
| 892 |
reasoning.append("")
|
| 893 |
|
| 894 |
+
# Add analysis insights
|
| 895 |
reasoning.append("**Analysis Notes:**")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 896 |
|
| 897 |
+
if (confidence < 0.3):
|
| 898 |
+
reasoning.append("• Low confidence attribution - text patterns are ambiguous")
|
| 899 |
+
reasoning.append("• May be human-written or from multiple AI sources")
|
| 900 |
+
|
| 901 |
+
else:
|
| 902 |
+
reasoning.append(f"• Calibrated for {domain.value.replace('_', ' ')} domain")
|
| 903 |
+
|
| 904 |
+
# Domain-specific insights
|
| 905 |
+
domain_insights = {Domain.ACADEMIC : "Academic writing patterns analyzed",
|
| 906 |
+
Domain.TECHNICAL_DOC : "Technical coherence and structure weighted",
|
| 907 |
+
Domain.CREATIVE : "Stylistic and linguistic diversity emphasized",
|
| 908 |
+
Domain.SOCIAL_MEDIA : "Casual language and engagement patterns considered",
|
| 909 |
+
Domain.AI_ML : "Technical terminology and analytical patterns emphasized",
|
| 910 |
+
Domain.SOFTWARE_DEV : "Code-like structures and technical precision weighted",
|
| 911 |
+
Domain.ENGINEERING : "Technical specifications and formal language analyzed",
|
| 912 |
+
Domain.SCIENCE : "Scientific terminology and methodological patterns considered",
|
| 913 |
+
Domain.BUSINESS : "Professional communication and strategic language weighted",
|
| 914 |
+
Domain.LEGAL : "Formal language and legal terminology emphasized",
|
| 915 |
+
Domain.MEDICAL : "Medical terminology and clinical language analyzed",
|
| 916 |
+
Domain.JOURNALISM : "News reporting style and factual presentation weighted",
|
| 917 |
+
Domain.MARKETING : "Persuasive language and engagement patterns considered",
|
| 918 |
+
Domain.BLOG_PERSONAL : "Personal voice and conversational style analyzed",
|
| 919 |
+
Domain.TUTORIAL : "Instructional clarity and step-by-step structure weighted",
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
insight = domain_insights.get(domain, "Multiple attribution factors analyzed")
|
| 923 |
+
|
| 924 |
+
reasoning.append(f"• {insight}")
|
| 925 |
|
| 926 |
return reasoning
|
| 927 |
|
detector/highlighter.py
CHANGED
|
@@ -48,14 +48,14 @@ class TextHighlighter:
|
|
| 48 |
- Explainable tooltips
|
| 49 |
- Highlighting metrics calculation
|
| 50 |
"""
|
| 51 |
-
# Color thresholds with mixed content support
|
| 52 |
COLOR_THRESHOLDS = [(0.00, 0.10, "very-high-human", "#dcfce7", "Very likely human-written"),
|
| 53 |
(0.10, 0.25, "high-human", "#bbf7d0", "Likely human-written"),
|
| 54 |
(0.25, 0.40, "medium-human", "#86efac", "Possibly human-written"),
|
| 55 |
(0.40, 0.60, "uncertain", "#fef9c3", "Uncertain"),
|
| 56 |
(0.60, 0.75, "medium-ai", "#fde68a", "Possibly AI-generated"),
|
| 57 |
(0.75, 0.90, "high-ai", "#fed7aa", "Likely AI-generated"),
|
| 58 |
-
(0.90, 1.
|
| 59 |
]
|
| 60 |
|
| 61 |
# Mixed content pattern
|
|
@@ -86,11 +86,23 @@ class TextHighlighter:
|
|
| 86 |
self.text_processor = TextProcessor()
|
| 87 |
self.domain = domain
|
| 88 |
self.domain_thresholds = get_threshold_for_domain(domain)
|
| 89 |
-
self.ensemble = ensemble_classifier or
|
| 90 |
-
fallback_method = "domain_weighted",
|
| 91 |
-
)
|
| 92 |
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
def generate_highlights(self, text: str, metric_results: Dict[str, MetricResult], ensemble_result: Optional[EnsembleResult] = None,
|
| 95 |
enabled_metrics: Optional[Dict[str, bool]] = None, use_sentence_level: bool = True) -> List[HighlightedSentence]:
|
| 96 |
"""
|
|
@@ -112,80 +124,197 @@ class TextHighlighter:
|
|
| 112 |
--------
|
| 113 |
{ list } : List of HighlightedSentence objects
|
| 114 |
"""
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
-
|
| 146 |
-
|
| 147 |
|
| 148 |
-
#
|
| 149 |
-
|
|
|
|
| 150 |
|
| 151 |
-
|
| 152 |
-
|
| 153 |
|
| 154 |
-
#
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
mixed_prob = mixed_prob,
|
| 158 |
-
)
|
| 159 |
|
| 160 |
-
|
| 161 |
-
tooltip = self._generate_ensemble_tooltip(sentence = sentence,
|
| 162 |
-
ai_prob = ai_prob,
|
| 163 |
-
human_prob = human_prob,
|
| 164 |
-
mixed_prob = mixed_prob,
|
| 165 |
-
confidence = confidence,
|
| 166 |
-
confidence_level = confidence_level,
|
| 167 |
-
tooltip_base = tooltip_base,
|
| 168 |
-
breakdown = breakdown,
|
| 169 |
-
is_mixed_content = is_mixed_content,
|
| 170 |
-
)
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
confidence_level = confidence_level,
|
| 178 |
-
color_class = color_class,
|
| 179 |
-
tooltip = tooltip,
|
| 180 |
-
index = idx,
|
| 181 |
-
is_mixed_content = is_mixed_content,
|
| 182 |
-
metric_breakdown = breakdown,
|
| 183 |
-
)
|
| 184 |
-
)
|
| 185 |
-
|
| 186 |
-
return highlighted_sentences
|
| 187 |
|
| 188 |
-
|
| 189 |
def _calculate_sentence_ensemble_probability(self, sentence: str, metric_results: Dict[str, MetricResult], weights: Dict[str, float],
|
| 190 |
ensemble_result: Optional[EnsembleResult] = None) -> Tuple[float, float, float, float, Dict[str, float]]:
|
| 191 |
"""
|
|
@@ -193,10 +322,24 @@ class TextHighlighter:
|
|
| 193 |
"""
|
| 194 |
sentence_length = len(sentence.split())
|
| 195 |
|
| 196 |
-
#
|
| 197 |
if (sentence_length < 3):
|
| 198 |
-
# Return
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
# Calculate sentence-level metric results
|
| 202 |
sentence_metric_results = dict()
|
|
@@ -204,20 +347,27 @@ class TextHighlighter:
|
|
| 204 |
|
| 205 |
for name, doc_result in metric_results.items():
|
| 206 |
if doc_result.error is None:
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
# Use ensemble to combine sentence-level metrics
|
| 223 |
if sentence_metric_results:
|
|
@@ -226,8 +376,11 @@ class TextHighlighter:
|
|
| 226 |
domain = self.domain,
|
| 227 |
)
|
| 228 |
|
| 229 |
-
return (ensemble_sentence_result.ai_probability,
|
| 230 |
-
ensemble_sentence_result.
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
except Exception as e:
|
| 233 |
logger.warning(f"Sentence ensemble failed: {e}")
|
|
@@ -262,12 +415,12 @@ class TextHighlighter:
|
|
| 262 |
return adjusted_prob
|
| 263 |
|
| 264 |
|
| 265 |
-
def _create_sentence_metric_result(self, metric_name: str, ai_prob: float, doc_result: MetricResult) -> MetricResult:
|
| 266 |
"""
|
| 267 |
Create sentence-level MetricResult from document-level result
|
| 268 |
"""
|
| 269 |
-
#
|
| 270 |
-
sentence_confidence = self._calculate_sentence_confidence(doc_result.confidence)
|
| 271 |
|
| 272 |
return MetricResult(metric_name = metric_name,
|
| 273 |
ai_probability = ai_prob,
|
|
@@ -279,12 +432,15 @@ class TextHighlighter:
|
|
| 279 |
)
|
| 280 |
|
| 281 |
|
| 282 |
-
def _calculate_sentence_confidence(self, doc_confidence: float) -> float:
|
| 283 |
"""
|
| 284 |
-
Calculate confidence for sentence-level analysis
|
| 285 |
"""
|
| 286 |
-
|
| 287 |
-
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
|
| 290 |
def _calculate_weighted_probability(self, metric_results: Dict[str, MetricResult], weights: Dict[str, float], breakdown: Dict[str, float]) -> Tuple[float, float, float, float, Dict[str, float]]:
|
|
@@ -306,8 +462,8 @@ class TextHighlighter:
|
|
| 306 |
confidences.append(result.confidence)
|
| 307 |
total_weight += weight
|
| 308 |
|
| 309 |
-
if not weighted_ai_probs or total_weight == 0:
|
| 310 |
-
return 0.5, 0.5, 0.0, 0.5, {}
|
| 311 |
|
| 312 |
ai_prob = sum(weighted_ai_probs) / total_weight
|
| 313 |
human_prob = sum(weighted_human_probs) / total_weight
|
|
@@ -331,84 +487,94 @@ class TextHighlighter:
|
|
| 331 |
else:
|
| 332 |
# Calculate from metrics
|
| 333 |
return self._calculate_weighted_probability(metric_results, weights, {})
|
| 334 |
-
|
| 335 |
|
| 336 |
def _apply_domain_specific_adjustments(self, sentence: str, ai_prob: float, sentence_length: int) -> float:
|
| 337 |
"""
|
| 338 |
-
Apply domain-specific adjustments to AI probability
|
| 339 |
"""
|
|
|
|
|
|
|
| 340 |
sentence_lower = sentence.lower()
|
| 341 |
|
| 342 |
# Technical & AI/ML domains
|
| 343 |
-
if self.domain in [Domain.AI_ML, Domain.SOFTWARE_DEV, Domain.TECHNICAL_DOC, Domain.ENGINEERING, Domain.SCIENCE]:
|
| 344 |
if self._has_technical_terms(sentence_lower):
|
| 345 |
-
|
| 346 |
-
ai_prob *= 1.1
|
| 347 |
|
| 348 |
elif self._has_code_like_patterns(sentence):
|
| 349 |
-
|
| 350 |
|
| 351 |
-
elif sentence_length > 35:
|
| 352 |
-
|
| 353 |
|
| 354 |
# Creative & informal domains
|
| 355 |
-
elif self.domain in [Domain.CREATIVE, Domain.SOCIAL_MEDIA, Domain.BLOG_PERSONAL]:
|
| 356 |
if self._has_informal_language(sentence_lower):
|
| 357 |
-
|
| 358 |
-
ai_prob *= 0.7
|
| 359 |
|
| 360 |
elif self._has_emotional_language(sentence):
|
| 361 |
-
|
| 362 |
|
| 363 |
elif (sentence_length < 10):
|
| 364 |
-
|
| 365 |
|
| 366 |
# Academic & formal domains
|
| 367 |
-
elif self.domain in [Domain.ACADEMIC, Domain.LEGAL, Domain.MEDICAL]:
|
| 368 |
if self._has_citation_patterns(sentence):
|
| 369 |
-
|
| 370 |
-
ai_prob *= 0.8
|
| 371 |
|
| 372 |
elif self._has_technical_terms(sentence_lower):
|
| 373 |
-
|
| 374 |
|
| 375 |
elif (sentence_length > 40):
|
| 376 |
-
|
| 377 |
|
| 378 |
# Business & professional domains
|
| 379 |
-
elif self.domain in [Domain.BUSINESS, Domain.MARKETING, Domain.JOURNALISM]:
|
| 380 |
if self._has_business_jargon(sentence_lower):
|
| 381 |
-
|
| 382 |
-
ai_prob *= 1.05
|
| 383 |
|
| 384 |
elif self._has_ambiguous_phrasing(sentence_lower):
|
| 385 |
-
|
| 386 |
-
ai_prob *= 0.9
|
| 387 |
|
| 388 |
elif (15 <= sentence_length <= 25):
|
| 389 |
-
|
| 390 |
|
| 391 |
# Tutorial & educational domains
|
| 392 |
elif (self.domain == Domain.TUTORIAL):
|
| 393 |
if self._has_instructional_language(sentence_lower):
|
| 394 |
-
|
| 395 |
-
ai_prob *= 0.85
|
| 396 |
|
| 397 |
elif self._has_step_by_step_pattern(sentence):
|
| 398 |
-
|
| 399 |
|
| 400 |
elif self._has_examples(sentence):
|
| 401 |
-
|
| 402 |
|
| 403 |
# General domain - minimal adjustments
|
| 404 |
-
elif self.domain == Domain.GENERAL:
|
| 405 |
if self._has_complex_structure(sentence):
|
| 406 |
-
|
| 407 |
|
| 408 |
elif self._has_repetition(sentence):
|
| 409 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
|
| 411 |
-
return max(0.0, min(1.0,
|
| 412 |
|
| 413 |
|
| 414 |
def _apply_metric_specific_adjustments(self, metric_name: str, sentence: str, base_prob: float, sentence_length: int, thresholds: MetricThresholds) -> float:
|
|
@@ -466,8 +632,12 @@ class TextHighlighter:
|
|
| 466 |
|
| 467 |
def _get_color_for_probability(self, probability: float, is_mixed_content: bool = False, mixed_prob: float = 0.0) -> Tuple[str, str, str]:
|
| 468 |
"""
|
| 469 |
-
Get color class with mixed content support
|
| 470 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
# Check mixed content first
|
| 472 |
if (is_mixed_content and (mixed_prob > self.MIXED_THRESHOLD)):
|
| 473 |
return "mixed-content", "#e9d5ff", f"Mixed AI/Human content ({mixed_prob:.1%} mixed)"
|
|
@@ -477,12 +647,12 @@ class TextHighlighter:
|
|
| 477 |
if (min_thresh <= probability < max_thresh):
|
| 478 |
return color_class, color_hex, tooltip
|
| 479 |
|
| 480 |
-
# Fallback
|
| 481 |
-
return "
|
| 482 |
-
|
| 483 |
|
| 484 |
def _generate_ensemble_tooltip(self, sentence: str, ai_prob: float, human_prob: float, mixed_prob: float, confidence: float, confidence_level: ConfidenceLevel,
|
| 485 |
-
|
| 486 |
"""
|
| 487 |
Generate enhanced tooltip with ENSEMBLE information
|
| 488 |
"""
|
|
@@ -504,7 +674,7 @@ class TextHighlighter:
|
|
| 504 |
for metric, prob in list(breakdown.items())[:4]:
|
| 505 |
tooltip += f"\n• {metric}: {prob:.1%}"
|
| 506 |
|
| 507 |
-
tooltip += f"\n\nEnsemble Method: {self.ensemble
|
| 508 |
|
| 509 |
return tooltip
|
| 510 |
|
|
@@ -619,7 +789,7 @@ class TextHighlighter:
|
|
| 619 |
Analyze sentence complexity (0 = simple, 1 = complex)
|
| 620 |
"""
|
| 621 |
words = sentence.split()
|
| 622 |
-
if len(words) < 5:
|
| 623 |
return 0.2
|
| 624 |
|
| 625 |
complexity_indicators = ['although', 'because', 'while', 'when', 'if', 'since', 'unless', 'until', 'which', 'that', 'who', 'whom', 'whose', 'and', 'but', 'or', 'yet', 'so', 'however', 'therefore', 'moreover', 'furthermore', 'nevertheless', ',', ';', ':', '—']
|
|
@@ -637,7 +807,7 @@ class TextHighlighter:
|
|
| 637 |
|
| 638 |
clause_indicators = [',', ';', 'and', 'but', 'or', 'because', 'although']
|
| 639 |
clause_count = sum(1 for indicator in clause_indicators if indicator in sentence.lower())
|
| 640 |
-
score
|
| 641 |
|
| 642 |
return min(1.0, score)
|
| 643 |
|
|
@@ -671,7 +841,7 @@ class TextHighlighter:
|
|
| 671 |
for sentence in sentences:
|
| 672 |
clean_sentence = sentence.strip()
|
| 673 |
|
| 674 |
-
if (len(clean_sentence) >=
|
| 675 |
filtered_sentences.append(clean_sentence)
|
| 676 |
|
| 677 |
return filtered_sentences
|
|
@@ -1002,7 +1172,7 @@ class TextHighlighter:
|
|
| 1002 |
total_sentences = len(highlighted_sentences)
|
| 1003 |
|
| 1004 |
# Calculate weighted risk score
|
| 1005 |
-
weighted_risk
|
| 1006 |
|
| 1007 |
for sent in highlighted_sentences:
|
| 1008 |
weight = self.RISK_WEIGHTS.get(sent.color_class, 0.4)
|
|
|
|
| 48 |
- Explainable tooltips
|
| 49 |
- Highlighting metrics calculation
|
| 50 |
"""
|
| 51 |
+
# Color thresholds with mixed content support - FIXED: No gaps
|
| 52 |
COLOR_THRESHOLDS = [(0.00, 0.10, "very-high-human", "#dcfce7", "Very likely human-written"),
|
| 53 |
(0.10, 0.25, "high-human", "#bbf7d0", "Likely human-written"),
|
| 54 |
(0.25, 0.40, "medium-human", "#86efac", "Possibly human-written"),
|
| 55 |
(0.40, 0.60, "uncertain", "#fef9c3", "Uncertain"),
|
| 56 |
(0.60, 0.75, "medium-ai", "#fde68a", "Possibly AI-generated"),
|
| 57 |
(0.75, 0.90, "high-ai", "#fed7aa", "Likely AI-generated"),
|
| 58 |
+
(0.90, 1.00, "very-high-ai", "#fecaca", "Very likely AI-generated"),
|
| 59 |
]
|
| 60 |
|
| 61 |
# Mixed content pattern
|
|
|
|
| 86 |
self.text_processor = TextProcessor()
|
| 87 |
self.domain = domain
|
| 88 |
self.domain_thresholds = get_threshold_for_domain(domain)
|
| 89 |
+
self.ensemble = ensemble_classifier or self._create_default_ensemble()
|
|
|
|
|
|
|
| 90 |
|
| 91 |
|
| 92 |
+
def _create_default_ensemble(self) -> EnsembleClassifier:
|
| 93 |
+
"""
|
| 94 |
+
Create default ensemble classifier with proper error handling
|
| 95 |
+
"""
|
| 96 |
+
try:
|
| 97 |
+
return EnsembleClassifier(primary_method = "confidence_calibrated",
|
| 98 |
+
fallback_method = "domain_weighted",
|
| 99 |
+
)
|
| 100 |
+
except Exception as e:
|
| 101 |
+
logger.warning(f"Failed to create default ensemble: {e}. Using fallback mode.")
|
| 102 |
+
# Return a minimal ensemble or raise based on requirements
|
| 103 |
+
return EnsembleClassifier(primary_method = "weighted_average")
|
| 104 |
+
|
| 105 |
+
|
| 106 |
def generate_highlights(self, text: str, metric_results: Dict[str, MetricResult], ensemble_result: Optional[EnsembleResult] = None,
|
| 107 |
enabled_metrics: Optional[Dict[str, bool]] = None, use_sentence_level: bool = True) -> List[HighlightedSentence]:
|
| 108 |
"""
|
|
|
|
| 124 |
--------
|
| 125 |
{ list } : List of HighlightedSentence objects
|
| 126 |
"""
|
| 127 |
+
try:
|
| 128 |
+
# Validate inputs
|
| 129 |
+
if not text or not text.strip():
|
| 130 |
+
return self._handle_empty_text(text, metric_results, ensemble_result)
|
| 131 |
+
|
| 132 |
+
# Get domain-appropriate weights for enabled metrics
|
| 133 |
+
if enabled_metrics is None:
|
| 134 |
+
enabled_metrics = {name: True for name in metric_results.keys()}
|
| 135 |
+
|
| 136 |
+
weights = get_active_metric_weights(self.domain, enabled_metrics)
|
| 137 |
+
|
| 138 |
+
# Split text into sentences with error handling
|
| 139 |
+
sentences = self._split_sentences_with_fallback(text)
|
| 140 |
+
|
| 141 |
+
if not sentences:
|
| 142 |
+
return self._handle_no_sentences(text, metric_results, ensemble_result)
|
| 143 |
+
|
| 144 |
+
# Calculate probabilities for each sentence using ENSEMBLE METHODS
|
| 145 |
+
highlighted_sentences = list()
|
| 146 |
+
|
| 147 |
+
for idx, sentence in enumerate(sentences):
|
| 148 |
+
try:
|
| 149 |
+
if use_sentence_level:
|
| 150 |
+
# Use ENSEMBLE for sentence-level analysis
|
| 151 |
+
ai_prob, human_prob, mixed_prob, confidence, breakdown = self._calculate_sentence_ensemble_probability(sentence = sentence,
|
| 152 |
+
metric_results = metric_results,
|
| 153 |
+
weights = weights,
|
| 154 |
+
ensemble_result = ensemble_result,
|
| 155 |
+
)
|
| 156 |
+
else:
|
| 157 |
+
# Use document-level ensemble probabilities
|
| 158 |
+
ai_prob, human_prob, mixed_prob, confidence, breakdown = self._get_document_ensemble_probability(ensemble_result = ensemble_result,
|
| 159 |
+
metric_results = metric_results,
|
| 160 |
+
weights = weights,
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
# Apply domain-specific adjustments with limits
|
| 164 |
+
ai_prob = self._apply_domain_specific_adjustments(sentence = sentence,
|
| 165 |
+
ai_prob = ai_prob,
|
| 166 |
+
sentence_length = len(sentence.split()),
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
# Determine if this is mixed content
|
| 170 |
+
is_mixed_content = (mixed_prob > self.MIXED_THRESHOLD)
|
| 171 |
+
|
| 172 |
+
# Get confidence level
|
| 173 |
+
confidence_level = get_confidence_level(confidence)
|
| 174 |
+
|
| 175 |
+
# Get color class (consider mixed content)
|
| 176 |
+
color_class, color_hex, tooltip_base = self._get_color_for_probability(probability = ai_prob,
|
| 177 |
+
is_mixed_content = is_mixed_content,
|
| 178 |
+
mixed_prob = mixed_prob,
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
# Generate enhanced tooltip
|
| 182 |
+
tooltip = self._generate_ensemble_tooltip(sentence = sentence,
|
| 183 |
+
ai_prob = ai_prob,
|
| 184 |
+
human_prob = human_prob,
|
| 185 |
+
mixed_prob = mixed_prob,
|
| 186 |
+
confidence = confidence,
|
| 187 |
+
confidence_level = confidence_level,
|
| 188 |
+
tooltip_base = tooltip_base,
|
| 189 |
+
breakdown = breakdown,
|
| 190 |
+
is_mixed_content = is_mixed_content,
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
highlighted_sentences.append(HighlightedSentence(text = sentence,
|
| 194 |
+
ai_probability = ai_prob,
|
| 195 |
+
human_probability = human_prob,
|
| 196 |
+
mixed_probability = mixed_prob,
|
| 197 |
+
confidence = confidence,
|
| 198 |
+
confidence_level = confidence_level,
|
| 199 |
+
color_class = color_class,
|
| 200 |
+
tooltip = tooltip,
|
| 201 |
+
index = idx,
|
| 202 |
+
is_mixed_content = is_mixed_content,
|
| 203 |
+
metric_breakdown = breakdown,
|
| 204 |
+
)
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
except Exception as e:
|
| 208 |
+
logger.warning(f"Failed to process sentence {idx}: {e}")
|
| 209 |
+
# Add fallback sentence
|
| 210 |
+
highlighted_sentences.append(self._create_fallback_sentence(sentence, idx))
|
| 211 |
+
|
| 212 |
+
return highlighted_sentences
|
| 213 |
|
| 214 |
+
except Exception as e:
|
| 215 |
+
logger.error(f"Highlight generation failed: {e}")
|
| 216 |
+
return self._create_error_fallback(text, metric_results)
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _handle_empty_text(self, text: str, metric_results: Dict[str, MetricResult], ensemble_result: Optional[EnsembleResult]) -> List[HighlightedSentence]:
|
| 220 |
+
"""
|
| 221 |
+
Handle empty input text
|
| 222 |
+
"""
|
| 223 |
+
if ensemble_result:
|
| 224 |
+
return [self._create_fallback_sentence(text = "No text content",
|
| 225 |
+
index = 0,
|
| 226 |
+
ai_prob = ensemble_result.ai_probability,
|
| 227 |
+
human_prob = ensemble_result.human_probability,
|
| 228 |
+
)
|
| 229 |
+
]
|
| 230 |
+
|
| 231 |
+
return [self._create_fallback_sentence("No text content", 0)]
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def _handle_no_sentences(self, text: str, metric_results: Dict[str, MetricResult], ensemble_result: Optional[EnsembleResult]) -> List[HighlightedSentence]:
|
| 235 |
+
"""
|
| 236 |
+
Handle case where no sentences could be extracted
|
| 237 |
+
"""
|
| 238 |
+
if (text and (len(text.strip()) > 0)):
|
| 239 |
+
# Treat entire text as one sentence
|
| 240 |
+
return [self._create_fallback_sentence(text.strip(), 0)]
|
| 241 |
+
|
| 242 |
+
return [self._create_fallback_sentence("No processable content", 0)]
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def _create_fallback_sentence(self, text: str, index: int, ai_prob: float = 0.5, human_prob: float = 0.5) -> HighlightedSentence:
|
| 246 |
+
"""
|
| 247 |
+
Create a fallback sentence when processing fails
|
| 248 |
+
"""
|
| 249 |
+
confidence_level = get_confidence_level(0.3)
|
| 250 |
+
color_class, _, tooltip_base = self._get_color_for_probability(probability = ai_prob,
|
| 251 |
+
is_mixed_content = False,
|
| 252 |
+
mixed_prob = 0.0,
|
| 253 |
+
)
|
| 254 |
|
| 255 |
+
return HighlightedSentence(text = text,
|
| 256 |
+
ai_probability = ai_prob,
|
| 257 |
+
human_probability = human_prob,
|
| 258 |
+
mixed_probability = 0.0,
|
| 259 |
+
confidence = 0.3,
|
| 260 |
+
confidence_level = confidence_level,
|
| 261 |
+
color_class = color_class,
|
| 262 |
+
tooltip = f"Fallback: {tooltip_base}\nProcessing failed for this sentence",
|
| 263 |
+
index = index,
|
| 264 |
+
is_mixed_content = False,
|
| 265 |
+
metric_breakdown = {"fallback": ai_prob},
|
| 266 |
+
)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def _create_error_fallback(self, text: str, metric_results: Dict[str, MetricResult]) -> List[HighlightedSentence]:
|
| 270 |
+
"""
|
| 271 |
+
Create fallback when entire processing fails
|
| 272 |
+
"""
|
| 273 |
+
return [HighlightedSentence(text = text[:100] + "..." if len(text) > 100 else text,
|
| 274 |
+
ai_probability = 0.5,
|
| 275 |
+
human_probability = 0.5,
|
| 276 |
+
mixed_probability = 0.0,
|
| 277 |
+
confidence = 0.1,
|
| 278 |
+
confidence_level = get_confidence_level(0.1),
|
| 279 |
+
color_class = "uncertain",
|
| 280 |
+
tooltip = "Error in text processing",
|
| 281 |
+
index = 0,
|
| 282 |
+
is_mixed_content = False,
|
| 283 |
+
metric_breakdown = {"error": 0.5},
|
| 284 |
+
)
|
| 285 |
+
]
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def _split_sentences_with_fallback(self, text: str) -> List[str]:
|
| 289 |
+
"""
|
| 290 |
+
Split text into sentences with comprehensive fallback handling
|
| 291 |
+
"""
|
| 292 |
+
try:
|
| 293 |
+
sentences = self.text_processor.split_sentences(text)
|
| 294 |
+
filtered_sentences = [s.strip() for s in sentences if len(s.strip()) >= 3]
|
| 295 |
|
| 296 |
+
if filtered_sentences:
|
| 297 |
+
return filtered_sentences
|
| 298 |
|
| 299 |
+
# Fallback: split by common sentence endings
|
| 300 |
+
fallback_sentences = re.split(r'[.!?]+', text)
|
| 301 |
+
fallback_sentences = [s.strip() for s in fallback_sentences if len(s.strip()) >= 3]
|
| 302 |
|
| 303 |
+
if fallback_sentences:
|
| 304 |
+
return fallback_sentences
|
| 305 |
|
| 306 |
+
# Ultimate fallback: treat as single sentence if meaningful
|
| 307 |
+
if text.strip():
|
| 308 |
+
return [text.strip()]
|
|
|
|
|
|
|
| 309 |
|
| 310 |
+
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
+
except Exception as e:
|
| 313 |
+
logger.warning(f"Sentence splitting failed, using fallback: {e}")
|
| 314 |
+
# Return text as single sentence
|
| 315 |
+
return [text] if text.strip() else []
|
| 316 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
|
|
|
|
| 318 |
def _calculate_sentence_ensemble_probability(self, sentence: str, metric_results: Dict[str, MetricResult], weights: Dict[str, float],
|
| 319 |
ensemble_result: Optional[EnsembleResult] = None) -> Tuple[float, float, float, float, Dict[str, float]]:
|
| 320 |
"""
|
|
|
|
| 322 |
"""
|
| 323 |
sentence_length = len(sentence.split())
|
| 324 |
|
| 325 |
+
# Handling short sentences - don't force neutral
|
| 326 |
if (sentence_length < 3):
|
| 327 |
+
# Return probabilities with lower confidence for very short sentences
|
| 328 |
+
base_ai_prob = 0.5
|
| 329 |
+
|
| 330 |
+
# Low confidence for very short sentences
|
| 331 |
+
base_confidence = 0.2
|
| 332 |
+
|
| 333 |
+
breakdown = {"short_sentence" : base_ai_prob}
|
| 334 |
+
|
| 335 |
+
# Try to get some signal from available metrics
|
| 336 |
+
for name, result in metric_results.items():
|
| 337 |
+
if ((result.error is None) and (weights.get(name, 0) > 0)):
|
| 338 |
+
base_ai_prob = result.ai_probability
|
| 339 |
+
breakdown[name] = base_ai_prob
|
| 340 |
+
break
|
| 341 |
+
|
| 342 |
+
return base_ai_prob, 1.0 - base_ai_prob, 0.0, base_confidence, breakdown
|
| 343 |
|
| 344 |
# Calculate sentence-level metric results
|
| 345 |
sentence_metric_results = dict()
|
|
|
|
| 347 |
|
| 348 |
for name, doc_result in metric_results.items():
|
| 349 |
if doc_result.error is None:
|
| 350 |
+
try:
|
| 351 |
+
# Compute sentence-level probability for this metric
|
| 352 |
+
sentence_prob = self._compute_sentence_metric(metric_name = name,
|
| 353 |
+
sentence = sentence,
|
| 354 |
+
result = doc_result,
|
| 355 |
+
weight = weights.get(name, 0.0),
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
# Create sentence-level MetricResult
|
| 359 |
+
sentence_metric_results[name] = self._create_sentence_metric_result(metric_name = name,
|
| 360 |
+
ai_prob = sentence_prob,
|
| 361 |
+
doc_result = doc_result,
|
| 362 |
+
sentence_length = sentence_length,
|
| 363 |
+
)
|
| 364 |
+
|
| 365 |
+
breakdown[name] = sentence_prob
|
| 366 |
|
| 367 |
+
except Exception as e:
|
| 368 |
+
logger.warning(f"Metric {name} failed for sentence: {e}")
|
| 369 |
+
# Use document probability as fallback
|
| 370 |
+
breakdown[name] = doc_result.ai_probability
|
| 371 |
|
| 372 |
# Use ensemble to combine sentence-level metrics
|
| 373 |
if sentence_metric_results:
|
|
|
|
| 376 |
domain = self.domain,
|
| 377 |
)
|
| 378 |
|
| 379 |
+
return (ensemble_sentence_result.ai_probability,
|
| 380 |
+
ensemble_sentence_result.human_probability,
|
| 381 |
+
ensemble_sentence_result.mixed_probability,
|
| 382 |
+
ensemble_sentence_result.overall_confidence,
|
| 383 |
+
breakdown)
|
| 384 |
|
| 385 |
except Exception as e:
|
| 386 |
logger.warning(f"Sentence ensemble failed: {e}")
|
|
|
|
| 415 |
return adjusted_prob
|
| 416 |
|
| 417 |
|
| 418 |
+
def _create_sentence_metric_result(self, metric_name: str, ai_prob: float, doc_result: MetricResult, sentence_length: int) -> MetricResult:
|
| 419 |
"""
|
| 420 |
Create sentence-level MetricResult from document-level result
|
| 421 |
"""
|
| 422 |
+
# IMPROVED: Calculate confidence based on sentence characteristics
|
| 423 |
+
sentence_confidence = self._calculate_sentence_confidence(doc_result.confidence, sentence_length)
|
| 424 |
|
| 425 |
return MetricResult(metric_name = metric_name,
|
| 426 |
ai_probability = ai_prob,
|
|
|
|
| 432 |
)
|
| 433 |
|
| 434 |
|
| 435 |
+
def _calculate_sentence_confidence(self, doc_confidence: float, sentence_length: int) -> float:
|
| 436 |
"""
|
| 437 |
+
IMPROVED: Calculate confidence for sentence-level analysis with length consideration
|
| 438 |
"""
|
| 439 |
+
base_reduction = 0.8
|
| 440 |
+
# Scale confidence penalty with sentence length
|
| 441 |
+
length_penalty = max(0.3, min(1.0, sentence_length / 12.0)) # Normalize around 12 words
|
| 442 |
+
|
| 443 |
+
return max(0.1, doc_confidence * base_reduction * length_penalty)
|
| 444 |
|
| 445 |
|
| 446 |
def _calculate_weighted_probability(self, metric_results: Dict[str, MetricResult], weights: Dict[str, float], breakdown: Dict[str, float]) -> Tuple[float, float, float, float, Dict[str, float]]:
|
|
|
|
| 462 |
confidences.append(result.confidence)
|
| 463 |
total_weight += weight
|
| 464 |
|
| 465 |
+
if ((not weighted_ai_probs) or (total_weight == 0)):
|
| 466 |
+
return 0.5, 0.5, 0.0, 0.5, breakdown or {}
|
| 467 |
|
| 468 |
ai_prob = sum(weighted_ai_probs) / total_weight
|
| 469 |
human_prob = sum(weighted_human_probs) / total_weight
|
|
|
|
| 487 |
else:
|
| 488 |
# Calculate from metrics
|
| 489 |
return self._calculate_weighted_probability(metric_results, weights, {})
|
| 490 |
+
|
| 491 |
|
| 492 |
def _apply_domain_specific_adjustments(self, sentence: str, ai_prob: float, sentence_length: int) -> float:
|
| 493 |
"""
|
| 494 |
+
Apply domain-specific adjustments to AI probability with limits
|
| 495 |
"""
|
| 496 |
+
original_prob = ai_prob
|
| 497 |
+
adjustments = list()
|
| 498 |
sentence_lower = sentence.lower()
|
| 499 |
|
| 500 |
# Technical & AI/ML domains
|
| 501 |
+
if (self.domain in [Domain.AI_ML, Domain.SOFTWARE_DEV, Domain.TECHNICAL_DOC, Domain.ENGINEERING, Domain.SCIENCE]):
|
| 502 |
if self._has_technical_terms(sentence_lower):
|
| 503 |
+
adjustments.append(1.1)
|
|
|
|
| 504 |
|
| 505 |
elif self._has_code_like_patterns(sentence):
|
| 506 |
+
adjustments.append(1.15)
|
| 507 |
|
| 508 |
+
elif (sentence_length > 35):
|
| 509 |
+
adjustments.append(1.05)
|
| 510 |
|
| 511 |
# Creative & informal domains
|
| 512 |
+
elif (self.domain in [Domain.CREATIVE, Domain.SOCIAL_MEDIA, Domain.BLOG_PERSONAL]):
|
| 513 |
if self._has_informal_language(sentence_lower):
|
| 514 |
+
adjustments.append(0.7)
|
|
|
|
| 515 |
|
| 516 |
elif self._has_emotional_language(sentence):
|
| 517 |
+
adjustments.append(0.8)
|
| 518 |
|
| 519 |
elif (sentence_length < 10):
|
| 520 |
+
adjustments.append(0.8)
|
| 521 |
|
| 522 |
# Academic & formal domains
|
| 523 |
+
elif (self.domain in [Domain.ACADEMIC, Domain.LEGAL, Domain.MEDICAL]):
|
| 524 |
if self._has_citation_patterns(sentence):
|
| 525 |
+
adjustments.append(0.8)
|
|
|
|
| 526 |
|
| 527 |
elif self._has_technical_terms(sentence_lower):
|
| 528 |
+
adjustments.append(1.1)
|
| 529 |
|
| 530 |
elif (sentence_length > 40):
|
| 531 |
+
adjustments.append(1.1)
|
| 532 |
|
| 533 |
# Business & professional domains
|
| 534 |
+
elif (self.domain in [Domain.BUSINESS, Domain.MARKETING, Domain.JOURNALISM]):
|
| 535 |
if self._has_business_jargon(sentence_lower):
|
| 536 |
+
adjustments.append(1.05)
|
|
|
|
| 537 |
|
| 538 |
elif self._has_ambiguous_phrasing(sentence_lower):
|
| 539 |
+
adjustments.append(0.9)
|
|
|
|
| 540 |
|
| 541 |
elif (15 <= sentence_length <= 25):
|
| 542 |
+
adjustments.append(0.9)
|
| 543 |
|
| 544 |
# Tutorial & educational domains
|
| 545 |
elif (self.domain == Domain.TUTORIAL):
|
| 546 |
if self._has_instructional_language(sentence_lower):
|
| 547 |
+
adjustments.append(0.85)
|
|
|
|
| 548 |
|
| 549 |
elif self._has_step_by_step_pattern(sentence):
|
| 550 |
+
adjustments.append(0.8)
|
| 551 |
|
| 552 |
elif self._has_examples(sentence):
|
| 553 |
+
adjustments.append(0.9)
|
| 554 |
|
| 555 |
# General domain - minimal adjustments
|
| 556 |
+
elif (self.domain == Domain.GENERAL):
|
| 557 |
if self._has_complex_structure(sentence):
|
| 558 |
+
adjustments.append(0.9)
|
| 559 |
|
| 560 |
elif self._has_repetition(sentence):
|
| 561 |
+
adjustments.append(1.1)
|
| 562 |
+
|
| 563 |
+
# Apply adjustments with limits - take strongest 2 adjustments maximum
|
| 564 |
+
if adjustments:
|
| 565 |
+
# Sort by impact (farthest from 1.0)
|
| 566 |
+
adjustments.sort(key = lambda x: abs(x - 1.0), reverse = True)
|
| 567 |
+
# Limit to 2 strongest
|
| 568 |
+
strongest_adjustments = adjustments[:2]
|
| 569 |
+
|
| 570 |
+
for adjustment in strongest_adjustments:
|
| 571 |
+
ai_prob *= adjustment
|
| 572 |
+
|
| 573 |
+
# Ensure probability stays within bounds and doesn't change too drastically : Maximum 30% change from original
|
| 574 |
+
max_change = 0.3
|
| 575 |
+
bounded_prob = max(original_prob - max_change, min(original_prob + max_change, ai_prob))
|
| 576 |
|
| 577 |
+
return max(0.0, min(1.0, bounded_prob))
|
| 578 |
|
| 579 |
|
| 580 |
def _apply_metric_specific_adjustments(self, metric_name: str, sentence: str, base_prob: float, sentence_length: int, thresholds: MetricThresholds) -> float:
|
|
|
|
| 632 |
|
| 633 |
def _get_color_for_probability(self, probability: float, is_mixed_content: bool = False, mixed_prob: float = 0.0) -> Tuple[str, str, str]:
|
| 634 |
"""
|
| 635 |
+
Get color class with mixed content support and no threshold gaps
|
| 636 |
"""
|
| 637 |
+
# Handle probability = 1.0 explicitly
|
| 638 |
+
if (probability >= 1.0):
|
| 639 |
+
return "very-high-ai", "#fecaca", "Very likely AI-generated (100%)"
|
| 640 |
+
|
| 641 |
# Check mixed content first
|
| 642 |
if (is_mixed_content and (mixed_prob > self.MIXED_THRESHOLD)):
|
| 643 |
return "mixed-content", "#e9d5ff", f"Mixed AI/Human content ({mixed_prob:.1%} mixed)"
|
|
|
|
| 647 |
if (min_thresh <= probability < max_thresh):
|
| 648 |
return color_class, color_hex, tooltip
|
| 649 |
|
| 650 |
+
# Fallback for probability = 1.0 (should be caught above, but just in case)
|
| 651 |
+
return "very-high-ai", "#fecaca", "Very likely AI-generated"
|
| 652 |
+
|
| 653 |
|
| 654 |
def _generate_ensemble_tooltip(self, sentence: str, ai_prob: float, human_prob: float, mixed_prob: float, confidence: float, confidence_level: ConfidenceLevel,
|
| 655 |
+
tooltip_base: str, breakdown: Optional[Dict[str, float]] = None, is_mixed_content: bool = False) -> str:
|
| 656 |
"""
|
| 657 |
Generate enhanced tooltip with ENSEMBLE information
|
| 658 |
"""
|
|
|
|
| 674 |
for metric, prob in list(breakdown.items())[:4]:
|
| 675 |
tooltip += f"\n• {metric}: {prob:.1%}"
|
| 676 |
|
| 677 |
+
tooltip += f"\n\nEnsemble Method: {getattr(self.ensemble, 'primary_method', 'fallback')}"
|
| 678 |
|
| 679 |
return tooltip
|
| 680 |
|
|
|
|
| 789 |
Analyze sentence complexity (0 = simple, 1 = complex)
|
| 790 |
"""
|
| 791 |
words = sentence.split()
|
| 792 |
+
if (len(words) < 5):
|
| 793 |
return 0.2
|
| 794 |
|
| 795 |
complexity_indicators = ['although', 'because', 'while', 'when', 'if', 'since', 'unless', 'until', 'which', 'that', 'who', 'whom', 'whose', 'and', 'but', 'or', 'yet', 'so', 'however', 'therefore', 'moreover', 'furthermore', 'nevertheless', ',', ';', ':', '—']
|
|
|
|
| 807 |
|
| 808 |
clause_indicators = [',', ';', 'and', 'but', 'or', 'because', 'although']
|
| 809 |
clause_count = sum(1 for indicator in clause_indicators if indicator in sentence.lower())
|
| 810 |
+
score += min(0.2, clause_count * 0.05)
|
| 811 |
|
| 812 |
return min(1.0, score)
|
| 813 |
|
|
|
|
| 841 |
for sentence in sentences:
|
| 842 |
clean_sentence = sentence.strip()
|
| 843 |
|
| 844 |
+
if (len(clean_sentence) >= 3):
|
| 845 |
filtered_sentences.append(clean_sentence)
|
| 846 |
|
| 847 |
return filtered_sentences
|
|
|
|
| 1172 |
total_sentences = len(highlighted_sentences)
|
| 1173 |
|
| 1174 |
# Calculate weighted risk score
|
| 1175 |
+
weighted_risk = 0.0
|
| 1176 |
|
| 1177 |
for sent in highlighted_sentences:
|
| 1178 |
weight = self.RISK_WEIGHTS.get(sent.color_class, 0.4)
|
logs/application/app_2025-10-29.log
DELETED
|
@@ -1,105 +0,0 @@
|
|
| 1 |
-
{"text": "Centralized logging system initialized\n", "record": {"elapsed": {"repr": "0:00:03.681153", "seconds": 3.681153}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 140, "message": "Centralized logging system initialized", "module": "logger", "name": "utils.logger", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.258826+05:30", "timestamp": 1761742167.258826}}}
|
| 2 |
-
{"text": "Environment: development\n", "record": {"elapsed": {"repr": "0:00:03.681320", "seconds": 3.68132}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 141, "message": "Environment: development", "module": "logger", "name": "utils.logger", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.258993+05:30", "timestamp": 1761742167.258993}}}
|
| 3 |
-
{"text": "Log Level: INFO\n", "record": {"elapsed": {"repr": "0:00:03.681410", "seconds": 3.68141}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 142, "message": "Log Level: INFO", "module": "logger", "name": "utils.logger", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.259083+05:30", "timestamp": 1761742167.259083}}}
|
| 4 |
-
{"text": "Log Directory: /Users/itobuz/projects/text_auth/logs\n", "record": {"elapsed": {"repr": "0:00:03.681487", "seconds": 3.681487}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 143, "message": "Log Directory: /Users/itobuz/projects/text_auth/logs", "module": "logger", "name": "utils.logger", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.259160+05:30", "timestamp": 1761742167.25916}}}
|
| 5 |
-
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:03.681853", "seconds": 3.681853}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 369, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.259526+05:30", "timestamp": 1761742167.259526}}}
|
| 6 |
-
{"text": "TEXT-AUTH API Starting Up...\n", "record": {"elapsed": {"repr": "0:00:03.681957", "seconds": 3.681957}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 370, "message": "TEXT-AUTH API Starting Up...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.259630+05:30", "timestamp": 1761742167.25963}}}
|
| 7 |
-
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:03.682034", "seconds": 3.682034}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 371, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.259707+05:30", "timestamp": 1761742167.259707}}}
|
| 8 |
-
{"text": "Initializing Detection Orchestrator...\n", "record": {"elapsed": {"repr": "0:00:03.682104", "seconds": 3.682104}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 375, "message": "Initializing Detection Orchestrator...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.259777+05:30", "timestamp": 1761742167.259777}}}
|
| 9 |
-
{"text": "TextProcessor initialized with min_length=50, max_length=50000\n", "record": {"elapsed": {"repr": "0:00:03.682177", "seconds": 3.682177}, "exception": null, "extra": {}, "file": {"name": "text_processor.py", "path": "/Users/itobuz/projects/text_auth/processors/text_processor.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 107, "message": "TextProcessor initialized with min_length=50, max_length=50000", "module": "text_processor", "name": "processors.text_processor", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.259850+05:30", "timestamp": 1761742167.25985}}}
|
| 10 |
-
{"text": "ModelManager initialized with device: cpu\n", "record": {"elapsed": {"repr": "0:00:03.682673", "seconds": 3.682673}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 132, "message": "ModelManager initialized with device: cpu", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.260346+05:30", "timestamp": 1761742167.260346}}}
|
| 11 |
-
{"text": "Model cache directory: /Users/itobuz/projects/text_auth/models/cache\n", "record": {"elapsed": {"repr": "0:00:03.682975", "seconds": 3.682975}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 133, "message": "Model cache directory: /Users/itobuz/projects/text_auth/models/cache", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.260648+05:30", "timestamp": 1761742167.260648}}}
|
| 12 |
-
{"text": "LanguageDetector initialized (use_model=True)\n", "record": {"elapsed": {"repr": "0:00:03.683057", "seconds": 3.683057}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/text_auth/processors/language_detector.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 179, "message": "LanguageDetector initialized (use_model=True)", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.260730+05:30", "timestamp": 1761742167.26073}}}
|
| 13 |
-
{"text": "Initialized 6 metrics: ['structural', 'entropy', 'perplexity', 'semantic_analysis', 'linguistic', 'detect_gpt']\n", "record": {"elapsed": {"repr": "0:00:03.683152", "seconds": 3.683152}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "_initialize_metrics", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 190, "message": "Initialized 6 metrics: ['structural', 'entropy', 'perplexity', 'semantic_analysis', 'linguistic', 'detect_gpt']", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.260825+05:30", "timestamp": 1761742167.260825}}}
|
| 14 |
-
{"text": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)\n", "record": {"elapsed": {"repr": "0:00:03.683228", "seconds": 3.683228}, "exception": null, "extra": {}, "file": {"name": "ensemble.py", "path": "/Users/itobuz/projects/text_auth/detector/ensemble.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 91, "message": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)", "module": "ensemble", "name": "detector.ensemble", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.260901+05:30", "timestamp": 1761742167.260901}}}
|
| 15 |
-
{"text": "DetectionOrchestrator initialized (language_detection=True, skip_expensive=False)\n", "record": {"elapsed": {"repr": "0:00:03.683294", "seconds": 3.683294}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 133, "message": "DetectionOrchestrator initialized (language_detection=True, skip_expensive=False)", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.260967+05:30", "timestamp": 1761742167.260967}}}
|
| 16 |
-
{"text": "Initializing detection pipeline...\n", "record": {"elapsed": {"repr": "0:00:03.683357", "seconds": 3.683357}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 203, "message": "Initializing detection pipeline...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.261030+05:30", "timestamp": 1761742167.26103}}}
|
| 17 |
-
{"text": "Initializing domain classifier...\n", "record": {"elapsed": {"repr": "0:00:03.683422", "seconds": 3.683422}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/text_auth/processors/domain_classifier.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 61, "message": "Initializing domain classifier...", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.261095+05:30", "timestamp": 1761742167.261095}}}
|
| 18 |
-
{"text": "Loading model: domain_classifier (cross-encoder/nli-roberta-base)\n", "record": {"elapsed": {"repr": "0:00:03.683492", "seconds": 3.683492}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Loading model: domain_classifier (cross-encoder/nli-roberta-base)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:27.261165+05:30", "timestamp": 1761742167.261165}}}
|
| 19 |
-
{"text": "Added model to cache: domain_classifier\n", "record": {"elapsed": {"repr": "0:00:04.551206", "seconds": 4.551206}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 86, "message": "Added model to cache: domain_classifier", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:28.128879+05:30", "timestamp": 1761742168.128879}}}
|
| 20 |
-
{"text": "Successfully loaded model: domain_classifier\n", "record": {"elapsed": {"repr": "0:00:04.551392", "seconds": 4.551392}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 262, "message": "Successfully loaded model: domain_classifier", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:28.129065+05:30", "timestamp": 1761742168.129065}}}
|
| 21 |
-
{"text": "Loading model: domain_classifier_fallback (microsoft/deberta-v3-small)\n", "record": {"elapsed": {"repr": "0:00:04.551480", "seconds": 4.55148}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Loading model: domain_classifier_fallback (microsoft/deberta-v3-small)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:28.129153+05:30", "timestamp": 1761742168.129153}}}
|
| 22 |
-
{"text": "Added model to cache: domain_classifier_fallback\n", "record": {"elapsed": {"repr": "0:00:05.680966", "seconds": 5.680966}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 86, "message": "Added model to cache: domain_classifier_fallback", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:29.258639+05:30", "timestamp": 1761742169.258639}}}
|
| 23 |
-
{"text": "Successfully loaded model: domain_classifier_fallback\n", "record": {"elapsed": {"repr": "0:00:05.681158", "seconds": 5.681158}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 262, "message": "Successfully loaded model: domain_classifier_fallback", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:29.258831+05:30", "timestamp": 1761742169.258831}}}
|
| 24 |
-
{"text": "Fallback classifier loaded successfully\n", "record": {"elapsed": {"repr": "0:00:05.681248", "seconds": 5.681248}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/text_auth/processors/domain_classifier.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 69, "message": "Fallback classifier loaded successfully", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:29.258921+05:30", "timestamp": 1761742169.258921}}}
|
| 25 |
-
{"text": "Domain classifier initialized successfully\n", "record": {"elapsed": {"repr": "0:00:05.681335", "seconds": 5.681335}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/text_auth/processors/domain_classifier.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 76, "message": "Domain classifier initialized successfully", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:29.259008+05:30", "timestamp": 1761742169.259008}}}
|
| 26 |
-
{"text": "Initializing language detection model...\n", "record": {"elapsed": {"repr": "0:00:05.681407", "seconds": 5.681407}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/text_auth/processors/language_detector.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 195, "message": "Initializing language detection model...", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:29.259080+05:30", "timestamp": 1761742169.25908}}}
|
| 27 |
-
{"text": "Loading pipeline: text-classification with language_detector\n", "record": {"elapsed": {"repr": "0:00:05.681476", "seconds": 5.681476}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_pipeline", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 430, "message": "Loading pipeline: text-classification with language_detector", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:29.259149+05:30", "timestamp": 1761742169.259149}}}
|
| 28 |
-
{"text": "Language detector initialized successfully\n", "record": {"elapsed": {"repr": "0:00:06.694072", "seconds": 6.694072}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/text_auth/processors/language_detector.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 203, "message": "Language detector initialized successfully", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:30.271745+05:30", "timestamp": 1761742170.271745}}}
|
| 29 |
-
{"text": "Initializing entropy metric...\n", "record": {"elapsed": {"repr": "0:00:06.694295", "seconds": 6.694295}, "exception": null, "extra": {}, "file": {"name": "entropy.py", "path": "/Users/itobuz/projects/text_auth/metrics/entropy.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 39, "message": "Initializing entropy metric...", "module": "entropy", "name": "metrics.entropy", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:30.271968+05:30", "timestamp": 1761742170.271968}}}
|
| 30 |
-
{"text": "Loading model: perplexity_gpt2 (gpt2)\n", "record": {"elapsed": {"repr": "0:00:06.694388", "seconds": 6.694388}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Loading model: perplexity_gpt2 (gpt2)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:30.272061+05:30", "timestamp": 1761742170.272061}}}
|
| 31 |
-
{"text": "Added model to cache: perplexity_gpt2\n", "record": {"elapsed": {"repr": "0:00:08.177207", "seconds": 8.177207}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 86, "message": "Added model to cache: perplexity_gpt2", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.754880+05:30", "timestamp": 1761742171.75488}}}
|
| 32 |
-
{"text": "Successfully loaded model: perplexity_gpt2\n", "record": {"elapsed": {"repr": "0:00:08.177413", "seconds": 8.177413}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 262, "message": "Successfully loaded model: perplexity_gpt2", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.755086+05:30", "timestamp": 1761742171.755086}}}
|
| 33 |
-
{"text": "Entropy metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:08.177499", "seconds": 8.177499}, "exception": null, "extra": {}, "file": {"name": "entropy.py", "path": "/Users/itobuz/projects/text_auth/metrics/entropy.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 52, "message": "Entropy metric initialized successfully", "module": "entropy", "name": "metrics.entropy", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.755172+05:30", "timestamp": 1761742171.755172}}}
|
| 34 |
-
{"text": "Initializing perplexity metric...\n", "record": {"elapsed": {"repr": "0:00:08.177585", "seconds": 8.177585}, "exception": null, "extra": {}, "file": {"name": "perplexity.py", "path": "/Users/itobuz/projects/text_auth/metrics/perplexity.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 41, "message": "Initializing perplexity metric...", "module": "perplexity", "name": "metrics.perplexity", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.755258+05:30", "timestamp": 1761742171.755258}}}
|
| 35 |
-
{"text": "Perplexity metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:08.177656", "seconds": 8.177656}, "exception": null, "extra": {}, "file": {"name": "perplexity.py", "path": "/Users/itobuz/projects/text_auth/metrics/perplexity.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 55, "message": "Perplexity metric initialized successfully", "module": "perplexity", "name": "metrics.perplexity", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.755329+05:30", "timestamp": 1761742171.755329}}}
|
| 36 |
-
{"text": "Initializing semantic analysis metric...\n", "record": {"elapsed": {"repr": "0:00:08.177722", "seconds": 8.177722}, "exception": null, "extra": {}, "file": {"name": "semantic_analysis.py", "path": "/Users/itobuz/projects/text_auth/metrics/semantic_analysis.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 41, "message": "Initializing semantic analysis metric...", "module": "semantic_analysis", "name": "metrics.semantic_analysis", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.755395+05:30", "timestamp": 1761742171.755395}}}
|
| 37 |
-
{"text": "Loading model: semantic_primary (sentence-transformers/all-MiniLM-L6-v2)\n", "record": {"elapsed": {"repr": "0:00:08.177789", "seconds": 8.177789}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Loading model: semantic_primary (sentence-transformers/all-MiniLM-L6-v2)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.755462+05:30", "timestamp": 1761742171.755462}}}
|
| 38 |
-
{"text": "Load pretrained SentenceTransformer: sentence-transformers/all-MiniLM-L6-v2\n", "record": {"elapsed": {"repr": "0:00:08.179934", "seconds": 8.179934}, "exception": null, "extra": {}, "file": {"name": "SentenceTransformer.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/sentence_transformers/SentenceTransformer.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 218, "message": "Load pretrained SentenceTransformer: sentence-transformers/all-MiniLM-L6-v2", "module": "SentenceTransformer", "name": "sentence_transformers.SentenceTransformer", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:31.757607+05:30", "timestamp": 1761742171.757607}}}
|
| 39 |
-
{"text": "Added model to cache: semantic_primary\n", "record": {"elapsed": {"repr": "0:00:12.965674", "seconds": 12.965674}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 86, "message": "Added model to cache: semantic_primary", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.543347+05:30", "timestamp": 1761742176.543347}}}
|
| 40 |
-
{"text": "Successfully loaded model: semantic_primary\n", "record": {"elapsed": {"repr": "0:00:12.966306", "seconds": 12.966306}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 262, "message": "Successfully loaded model: semantic_primary", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.543979+05:30", "timestamp": 1761742176.543979}}}
|
| 41 |
-
{"text": "Semantic analysis metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:12.966523", "seconds": 12.966523}, "exception": null, "extra": {}, "file": {"name": "semantic_analysis.py", "path": "/Users/itobuz/projects/text_auth/metrics/semantic_analysis.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 49, "message": "Semantic analysis metric initialized successfully", "module": "semantic_analysis", "name": "metrics.semantic_analysis", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.544196+05:30", "timestamp": 1761742176.544196}}}
|
| 42 |
-
{"text": "Initializing linguistic metric...\n", "record": {"elapsed": {"repr": "0:00:12.966714", "seconds": 12.966714}, "exception": null, "extra": {}, "file": {"name": "linguistic.py", "path": "/Users/itobuz/projects/text_auth/metrics/linguistic.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 39, "message": "Initializing linguistic metric...", "module": "linguistic", "name": "metrics.linguistic", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.544387+05:30", "timestamp": 1761742176.544387}}}
|
| 43 |
-
{"text": "Loading model: linguistic_spacy (en_core_web_sm)\n", "record": {"elapsed": {"repr": "0:00:12.966901", "seconds": 12.966901}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Loading model: linguistic_spacy (en_core_web_sm)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.544574+05:30", "timestamp": 1761742176.544574}}}
|
| 44 |
-
{"text": "Loaded spaCy model: en_core_web_sm\n", "record": {"elapsed": {"repr": "0:00:13.261871", "seconds": 13.261871}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "_load_spacy_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 447, "message": "Loaded spaCy model: en_core_web_sm", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.839544+05:30", "timestamp": 1761742176.839544}}}
|
| 45 |
-
{"text": "Added model to cache: linguistic_spacy\n", "record": {"elapsed": {"repr": "0:00:13.262395", "seconds": 13.262395}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 86, "message": "Added model to cache: linguistic_spacy", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.840068+05:30", "timestamp": 1761742176.840068}}}
|
| 46 |
-
{"text": "Successfully loaded model: linguistic_spacy\n", "record": {"elapsed": {"repr": "0:00:13.262513", "seconds": 13.262513}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 262, "message": "Successfully loaded model: linguistic_spacy", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.840186+05:30", "timestamp": 1761742176.840186}}}
|
| 47 |
-
{"text": "Linguistic metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:13.262600", "seconds": 13.2626}, "exception": null, "extra": {}, "file": {"name": "linguistic.py", "path": "/Users/itobuz/projects/text_auth/metrics/linguistic.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 46, "message": "Linguistic metric initialized successfully", "module": "linguistic", "name": "metrics.linguistic", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.840273+05:30", "timestamp": 1761742176.840273}}}
|
| 48 |
-
{"text": "Initializing DetectGPT metric...\n", "record": {"elapsed": {"repr": "0:00:13.262676", "seconds": 13.262676}, "exception": null, "extra": {}, "file": {"name": "detect_gpt.py", "path": "/Users/itobuz/projects/text_auth/metrics/detect_gpt.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 44, "message": "Initializing DetectGPT metric...", "module": "detect_gpt", "name": "metrics.detect_gpt", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.840349+05:30", "timestamp": 1761742176.840349}}}
|
| 49 |
-
{"text": "Loading model: detectgpt_base (gpt2)\n", "record": {"elapsed": {"repr": "0:00:13.262757", "seconds": 13.262757}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Loading model: detectgpt_base (gpt2)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:36.840430+05:30", "timestamp": 1761742176.84043}}}
|
| 50 |
-
{"text": "Evicted model from cache: domain_classifier\n", "record": {"elapsed": {"repr": "0:00:16.074200", "seconds": 16.0742}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 82, "message": "Evicted model from cache: domain_classifier", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:39.651873+05:30", "timestamp": 1761742179.651873}}}
|
| 51 |
-
{"text": "Added model to cache: detectgpt_base\n", "record": {"elapsed": {"repr": "0:00:16.074401", "seconds": 16.074401}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 86, "message": "Added model to cache: detectgpt_base", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:39.652074+05:30", "timestamp": 1761742179.652074}}}
|
| 52 |
-
{"text": "Successfully loaded model: detectgpt_base\n", "record": {"elapsed": {"repr": "0:00:16.074483", "seconds": 16.074483}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 262, "message": "Successfully loaded model: detectgpt_base", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:39.652156+05:30", "timestamp": 1761742179.652156}}}
|
| 53 |
-
{"text": "Loading model: detectgpt_mask (distilroberta-base)\n", "record": {"elapsed": {"repr": "0:00:16.195286", "seconds": 16.195286}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Loading model: detectgpt_mask (distilroberta-base)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:39.772959+05:30", "timestamp": 1761742179.772959}}}
|
| 54 |
-
{"text": "Evicted model from cache: domain_classifier_fallback\n", "record": {"elapsed": {"repr": "0:00:18.221749", "seconds": 18.221749}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 82, "message": "Evicted model from cache: domain_classifier_fallback", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.799422+05:30", "timestamp": 1761742181.799422}}}
|
| 55 |
-
{"text": "Added model to cache: detectgpt_mask\n", "record": {"elapsed": {"repr": "0:00:18.221942", "seconds": 18.221942}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 86, "message": "Added model to cache: detectgpt_mask", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.799615+05:30", "timestamp": 1761742181.799615}}}
|
| 56 |
-
{"text": "Successfully loaded model: detectgpt_mask\n", "record": {"elapsed": {"repr": "0:00:18.222025", "seconds": 18.222025}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 262, "message": "Successfully loaded model: detectgpt_mask", "module": "model_manager", "name": "models.model_manager", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.799698+05:30", "timestamp": 1761742181.799698}}}
|
| 57 |
-
{"text": "DetectGPT metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:18.331655", "seconds": 18.331655}, "exception": null, "extra": {}, "file": {"name": "detect_gpt.py", "path": "/Users/itobuz/projects/text_auth/metrics/detect_gpt.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 76, "message": "DetectGPT metric initialized successfully", "module": "detect_gpt", "name": "metrics.detect_gpt", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909328+05:30", "timestamp": 1761742181.909328}}}
|
| 58 |
-
{"text": "Detection pipeline initialized: 6/6 metrics ready\n", "record": {"elapsed": {"repr": "0:00:18.331887", "seconds": 18.331887}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 230, "message": "Detection pipeline initialized: 6/6 metrics ready", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909560+05:30", "timestamp": 1761742181.90956}}}
|
| 59 |
-
{"text": "✓ Detection Orchestrator initialized\n", "record": {"elapsed": {"repr": "0:00:18.331973", "seconds": 18.331973}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 383, "message": "✓ Detection Orchestrator initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909646+05:30", "timestamp": 1761742181.909646}}}
|
| 60 |
-
{"text": "Initializing Model Attributor...\n", "record": {"elapsed": {"repr": "0:00:18.332049", "seconds": 18.332049}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 389, "message": "Initializing Model Attributor...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909722+05:30", "timestamp": 1761742181.909722}}}
|
| 61 |
-
{"text": "ModelAttributor initialized with domain-aware calibration\n", "record": {"elapsed": {"repr": "0:00:18.332120", "seconds": 18.33212}, "exception": null, "extra": {}, "file": {"name": "attribution.py", "path": "/Users/itobuz/projects/text_auth/detector/attribution.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 403, "message": "ModelAttributor initialized with domain-aware calibration", "module": "attribution", "name": "detector.attribution", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909793+05:30", "timestamp": 1761742181.909793}}}
|
| 62 |
-
{"text": "Model attribution system initialized with metric ensemble\n", "record": {"elapsed": {"repr": "0:00:18.332185", "seconds": 18.332185}, "exception": null, "extra": {}, "file": {"name": "attribution.py", "path": "/Users/itobuz/projects/text_auth/detector/attribution.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 412, "message": "Model attribution system initialized with metric ensemble", "module": "attribution", "name": "detector.attribution", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909858+05:30", "timestamp": 1761742181.909858}}}
|
| 63 |
-
{"text": "✓ Model Attributor initialized\n", "record": {"elapsed": {"repr": "0:00:18.332255", "seconds": 18.332255}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 395, "message": "✓ Model Attributor initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909928+05:30", "timestamp": 1761742181.909928}}}
|
| 64 |
-
{"text": "Initializing Text Highlighter...\n", "record": {"elapsed": {"repr": "0:00:18.332318", "seconds": 18.332318}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 401, "message": "Initializing Text Highlighter...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.909991+05:30", "timestamp": 1761742181.909991}}}
|
| 65 |
-
{"text": "TextProcessor initialized with min_length=50, max_length=50000\n", "record": {"elapsed": {"repr": "0:00:18.332385", "seconds": 18.332385}, "exception": null, "extra": {}, "file": {"name": "text_processor.py", "path": "/Users/itobuz/projects/text_auth/processors/text_processor.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 107, "message": "TextProcessor initialized with min_length=50, max_length=50000", "module": "text_processor", "name": "processors.text_processor", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.910058+05:30", "timestamp": 1761742181.910058}}}
|
| 66 |
-
{"text": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)\n", "record": {"elapsed": {"repr": "0:00:18.332457", "seconds": 18.332457}, "exception": null, "extra": {}, "file": {"name": "ensemble.py", "path": "/Users/itobuz/projects/text_auth/detector/ensemble.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 91, "message": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)", "module": "ensemble", "name": "detector.ensemble", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.910130+05:30", "timestamp": 1761742181.91013}}}
|
| 67 |
-
{"text": "✓ Text Highlighter initialized\n", "record": {"elapsed": {"repr": "0:00:18.332527", "seconds": 18.332527}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 407, "message": "✓ Text Highlighter initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.910200+05:30", "timestamp": 1761742181.9102}}}
|
| 68 |
-
{"text": "Initializing Report Generator...\n", "record": {"elapsed": {"repr": "0:00:18.332591", "seconds": 18.332591}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 410, "message": "Initializing Report Generator...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.910264+05:30", "timestamp": 1761742181.910264}}}
|
| 69 |
-
{"text": "ReportGenerator initialized (output_dir=/Users/itobuz/projects/text_auth/data/reports)\n", "record": {"elapsed": {"repr": "0:00:18.333106", "seconds": 18.333106}, "exception": null, "extra": {}, "file": {"name": "report_generator.py", "path": "/Users/itobuz/projects/text_auth/reporter/report_generator.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 58, "message": "ReportGenerator initialized (output_dir=/Users/itobuz/projects/text_auth/data/reports)", "module": "report_generator", "name": "reporter.report_generator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.910779+05:30", "timestamp": 1761742181.910779}}}
|
| 70 |
-
{"text": "✓ Report Generator initialized\n", "record": {"elapsed": {"repr": "0:00:18.333235", "seconds": 18.333235}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 416, "message": "✓ Report Generator initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.910908+05:30", "timestamp": 1761742181.910908}}}
|
| 71 |
-
{"text": "Initializing Reasoning Generator...\n", "record": {"elapsed": {"repr": "0:00:18.333322", "seconds": 18.333322}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 419, "message": "Initializing Reasoning Generator...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.910995+05:30", "timestamp": 1761742181.910995}}}
|
| 72 |
-
{"text": "✓ Reasoning Generator initialized\n", "record": {"elapsed": {"repr": "0:00:18.333397", "seconds": 18.333397}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 425, "message": "✓ Reasoning Generator initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911070+05:30", "timestamp": 1761742181.91107}}}
|
| 73 |
-
{"text": "Initializing Document Extractor...\n", "record": {"elapsed": {"repr": "0:00:18.333465", "seconds": 18.333465}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 428, "message": "Initializing Document Extractor...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911138+05:30", "timestamp": 1761742181.911138}}}
|
| 74 |
-
{"text": "DocumentExtractor initialized (max_size=50.0MB)\n", "record": {"elapsed": {"repr": "0:00:18.333538", "seconds": 18.333538}, "exception": null, "extra": {}, "file": {"name": "document_extractor.py", "path": "/Users/itobuz/projects/text_auth/processors/document_extractor.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 124, "message": "DocumentExtractor initialized (max_size=50.0MB)", "module": "document_extractor", "name": "processors.document_extractor", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911211+05:30", "timestamp": 1761742181.911211}}}
|
| 75 |
-
{"text": "✓ Document Extractor initialized\n", "record": {"elapsed": {"repr": "0:00:18.333604", "seconds": 18.333604}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 434, "message": "✓ Document Extractor initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911277+05:30", "timestamp": 1761742181.911277}}}
|
| 76 |
-
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:18.333668", "seconds": 18.333668}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 436, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911341+05:30", "timestamp": 1761742181.911341}}}
|
| 77 |
-
{"text": "TEXT-AUTH API Ready!\n", "record": {"elapsed": {"repr": "0:00:18.333730", "seconds": 18.33373}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 437, "message": "TEXT-AUTH API Ready!", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911403+05:30", "timestamp": 1761742181.911403}}}
|
| 78 |
-
{"text": "Server: 0.0.0.0:8000\n", "record": {"elapsed": {"repr": "0:00:18.333792", "seconds": 18.333792}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 438, "message": "Server: 0.0.0.0:8000", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911465+05:30", "timestamp": 1761742181.911465}}}
|
| 79 |
-
{"text": "Environment: development\n", "record": {"elapsed": {"repr": "0:00:18.333854", "seconds": 18.333854}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 439, "message": "Environment: development", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911527+05:30", "timestamp": 1761742181.911527}}}
|
| 80 |
-
{"text": "Device: cpu\n", "record": {"elapsed": {"repr": "0:00:18.333913", "seconds": 18.333913}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 440, "message": "Device: cpu", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911586+05:30", "timestamp": 1761742181.911586}}}
|
| 81 |
-
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:18.333974", "seconds": 18.333974}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 441, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911647+05:30", "timestamp": 1761742181.911647}}}
|
| 82 |
-
{"text": "Application startup complete.\n", "record": {"elapsed": {"repr": "0:00:18.334210", "seconds": 18.33421}, "exception": null, "extra": {}, "file": {"name": "on.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/lifespan/on.py"}, "function": "startup", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 62, "message": "Application startup complete.", "module": "on", "name": "uvicorn.lifespan.on", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:41.911883+05:30", "timestamp": 1761742181.911883}}}
|
| 83 |
-
{"text": "API Request: GET / -> 200\n", "record": {"elapsed": {"repr": "0:00:26.376190", "seconds": 26.37619}, "exception": null, "extra": {"log_type": "application", "extra": {"http_method": "GET", "path": "/", "status_code": 200, "duration_seconds": 0.0033, "user": null, "ip_address": "127.0.0.1", "timestamp": "2025-10-29T18:19:49.953812"}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/text_auth/utils/logger.py"}, "function": "log_api_request", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 374, "message": "API Request: GET / -> 200", "module": "logger", "name": "utils.logger", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:49.953863+05:30", "timestamp": 1761742189.953863}}}
|
| 84 |
-
{"text": "127.0.0.1:61039 - \"GET / HTTP/1.1\" 200\n", "record": {"elapsed": {"repr": "0:00:26.376935", "seconds": 26.376935}, "exception": null, "extra": {}, "file": {"name": "h11_impl.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py"}, "function": "send", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 473, "message": "127.0.0.1:61039 - \"GET / HTTP/1.1\" 200", "module": "h11_impl", "name": "uvicorn.protocols.http.h11_impl", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:19:49.954608+05:30", "timestamp": 1761742189.954608}}}
|
| 85 |
-
{"text": "[analysis_1761742231503] Analyzing text (6124 chars)\n", "record": {"elapsed": {"repr": "0:01:07.925544", "seconds": 67.925544}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "analyze_text", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 729, "message": "[analysis_1761742231503] Analyzing text (6124 chars)", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:31.503217+05:30", "timestamp": 1761742231.503217}}}
|
| 86 |
-
{"text": "Step 1: Preprocessing text...\n", "record": {"elapsed": {"repr": "0:01:07.925807", "seconds": 67.925807}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 260, "message": "Step 1: Preprocessing text...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:31.503480+05:30", "timestamp": 1761742231.50348}}}
|
| 87 |
-
{"text": "Step 2: Detecting language...\n", "record": {"elapsed": {"repr": "0:01:07.933266", "seconds": 67.933266}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 272, "message": "Step 2: Detecting language...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:31.510939+05:30", "timestamp": 1761742231.510939}}}
|
| 88 |
-
{"text": "Text too long, truncated to 2000 characters for language detection\n", "record": {"elapsed": {"repr": "0:01:07.941615", "seconds": 67.941615}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/text_auth/processors/language_detector.py"}, "function": "_detect_with_model", "level": {"icon": "⚠️", "name": "WARNING", "no": 30}, "line": 304, "message": "Text too long, truncated to 2000 characters for language detection", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:31.519288+05:30", "timestamp": 1761742231.519288}}}
|
| 89 |
-
{"text": "Detected language: en (confidence: 0.98, method: xlm-roberta-model)\n", "record": {"elapsed": {"repr": "0:01:08.145482", "seconds": 68.145482}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/text_auth/processors/language_detector.py"}, "function": "detect", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 288, "message": "Detected language: en (confidence: 0.98, method: xlm-roberta-model)", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:31.723155+05:30", "timestamp": 1761742231.723155}}}
|
| 90 |
-
{"text": "Step 3: Classifying domain...\n", "record": {"elapsed": {"repr": "0:01:08.145741", "seconds": 68.145741}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 291, "message": "Step 3: Classifying domain...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:31.723414+05:30", "timestamp": 1761742231.723414}}}
|
| 91 |
-
{"text": "Primary model classified domain: social_media (confidence: 0.109)\n", "record": {"elapsed": {"repr": "0:01:10.726145", "seconds": 70.726145}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/text_auth/processors/domain_classifier.py"}, "function": "_classify_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Primary model classified domain: social_media (confidence: 0.109)", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:34.303818+05:30", "timestamp": 1761742234.303818}}}
|
| 92 |
-
{"text": "Primary classifier low confidence, trying fallback model...\n", "record": {"elapsed": {"repr": "0:01:10.726378", "seconds": 70.726378}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/text_auth/processors/domain_classifier.py"}, "function": "classify", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 118, "message": "Primary classifier low confidence, trying fallback model...", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:34.304051+05:30", "timestamp": 1761742234.304051}}}
|
| 93 |
-
{"text": "Fallback model classified domain: science (confidence: 0.063)\n", "record": {"elapsed": {"repr": "0:01:13.849320", "seconds": 73.84932}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/text_auth/processors/domain_classifier.py"}, "function": "_classify_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Fallback model classified domain: science (confidence: 0.063)", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:37.426993+05:30", "timestamp": 1761742237.426993}}}
|
| 94 |
-
{"text": "Detected domain: social_media (confidence: 0.11)\n", "record": {"elapsed": {"repr": "0:01:13.849569", "seconds": 73.849569}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 319, "message": "Detected domain: social_media (confidence: 0.11)", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:37.427242+05:30", "timestamp": 1761742237.427242}}}
|
| 95 |
-
{"text": "Step 4: Executing detection metrics calculations...\n", "record": {"elapsed": {"repr": "0:01:13.849687", "seconds": 73.849687}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 322, "message": "Step 4: Executing detection metrics calculations...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:37.427360+05:30", "timestamp": 1761742237.42736}}}
|
| 96 |
-
{"text": "Executed 6 metrics successfully\n", "record": {"elapsed": {"repr": "0:01:20.393725", "seconds": 80.393725}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 362, "message": "Executed 6 metrics successfully", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.971398+05:30", "timestamp": 1761742243.971398}}}
|
| 97 |
-
{"text": "Step 5: Aggregating results with ensemble...\n", "record": {"elapsed": {"repr": "0:01:20.393966", "seconds": 80.393966}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 365, "message": "Step 5: Aggregating results with ensemble...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.971639+05:30", "timestamp": 1761742243.971639}}}
|
| 98 |
-
{"text": "Analysis complete: Human-Written (AI probability: 38.5%, confidence: 0.63) in 12.47s\n", "record": {"elapsed": {"repr": "0:01:20.394253", "seconds": 80.394253}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 394, "message": "Analysis complete: Human-Written (AI probability: 38.5%, confidence: 0.63) in 12.47s", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.971926+05:30", "timestamp": 1761742243.971926}}}
|
| 99 |
-
{"text": "[analysis_1761742231503] Running attribution...\n", "record": {"elapsed": {"repr": "0:01:20.394704", "seconds": 80.394704}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "analyze_text", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 745, "message": "[analysis_1761742231503] Running attribution...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.972377+05:30", "timestamp": 1761742243.972377}}}
|
| 100 |
-
{"text": "[analysis_1761742231503] Generating highlights...\n", "record": {"elapsed": {"repr": "0:01:20.396346", "seconds": 80.396346}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/text_auth/text_auth_app.py"}, "function": "analyze_text", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 763, "message": "[analysis_1761742231503] Generating highlights...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.974019+05:30", "timestamp": 1761742243.974019}}}
|
| 101 |
-
{"text": "Detection completed: analysis_1761742231503 -> Human-Written\n", "record": {"elapsed": {"repr": "0:01:20.406711", "seconds": 80.406711}, "exception": null, "extra": {"log_type": "application", "extra": {"analysis_id": "analysis_1761742231503", "text_length": 6124, "verdict": "Human-Written", "confidence": 0.6342, "domain": "social_media", "processing_time_seconds": 12.4812, "timestamp": "2025-10-29T18:20:43.984376", "enable_attribution": true, "enable_highlighting": true}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/text_auth/utils/logger.py"}, "function": "log_detection_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 419, "message": "Detection completed: analysis_1761742231503 -> Human-Written", "module": "logger", "name": "utils.logger", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.984384+05:30", "timestamp": 1761742243.984384}}}
|
| 102 |
-
{"text": "API Request: POST /api/analyze -> 200\n", "record": {"elapsed": {"repr": "0:01:20.407701", "seconds": 80.407701}, "exception": null, "extra": {"log_type": "application", "extra": {"http_method": "POST", "path": "/api/analyze", "status_code": 200, "duration_seconds": 12.4884, "user": null, "ip_address": "127.0.0.1", "timestamp": "2025-10-29T18:20:43.985367"}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/text_auth/utils/logger.py"}, "function": "log_api_request", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 374, "message": "API Request: POST /api/analyze -> 200", "module": "logger", "name": "utils.logger", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.985374+05:30", "timestamp": 1761742243.985374}}}
|
| 103 |
-
{"text": "127.0.0.1:61041 - \"POST /api/analyze HTTP/1.1\" 200\n", "record": {"elapsed": {"repr": "0:01:20.407866", "seconds": 80.407866}, "exception": null, "extra": {}, "file": {"name": "h11_impl.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py"}, "function": "send", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 473, "message": "127.0.0.1:61041 - \"POST /api/analyze HTTP/1.1\" 200", "module": "h11_impl", "name": "uvicorn.protocols.http.h11_impl", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:20:43.985539+05:30", "timestamp": 1761742243.985539}}}
|
| 104 |
-
{"text": "Shutting down\n", "record": {"elapsed": {"repr": "0:02:43.050189", "seconds": 163.050189}, "exception": null, "extra": {}, "file": {"name": "server.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/server.py"}, "function": "shutdown", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 263, "message": "Shutting down", "module": "server", "name": "uvicorn.server", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:22:06.627862+05:30", "timestamp": 1761742326.627862}}}
|
| 105 |
-
{"text": "Waiting for application shutdown.\n", "record": {"elapsed": {"repr": "0:02:43.152124", "seconds": 163.152124}, "exception": null, "extra": {}, "file": {"name": "on.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/lifespan/on.py"}, "function": "shutdown", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 67, "message": "Waiting for application shutdown.", "module": "on", "name": "uvicorn.lifespan.on", "process": {"id": 66535, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-10-29 18:22:06.729797+05:30", "timestamp": 1761742326.729797}}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logs/application/app_2025-10-31.log
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
logs/application/app_2025-11-03.log
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
logs/application/app_2025-11-04.log
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"text": "Centralized logging system initialized\n", "record": {"elapsed": {"repr": "0:00:04.035484", "seconds": 4.035484}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 140, "message": "Centralized logging system initialized", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252075+05:30", "timestamp": 1762204238.252075}}}
|
| 2 |
+
{"text": "Environment: development\n", "record": {"elapsed": {"repr": "0:00:04.035673", "seconds": 4.035673}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 141, "message": "Environment: development", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252264+05:30", "timestamp": 1762204238.252264}}}
|
| 3 |
+
{"text": "Log Level: INFO\n", "record": {"elapsed": {"repr": "0:00:04.035761", "seconds": 4.035761}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 142, "message": "Log Level: INFO", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252352+05:30", "timestamp": 1762204238.252352}}}
|
| 4 |
+
{"text": "Log Directory: /Users/itobuz/projects/office/text_auth/logs\n", "record": {"elapsed": {"repr": "0:00:04.035835", "seconds": 4.035835}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 143, "message": "Log Directory: /Users/itobuz/projects/office/text_auth/logs", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252426+05:30", "timestamp": 1762204238.252426}}}
|
| 5 |
+
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:04.035907", "seconds": 4.035907}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 369, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252498+05:30", "timestamp": 1762204238.252498}}}
|
| 6 |
+
{"text": "TEXT-AUTH API Starting Up...\n", "record": {"elapsed": {"repr": "0:00:04.035975", "seconds": 4.035975}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 370, "message": "TEXT-AUTH API Starting Up...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252566+05:30", "timestamp": 1762204238.252566}}}
|
| 7 |
+
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:04.036038", "seconds": 4.036038}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 371, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252629+05:30", "timestamp": 1762204238.252629}}}
|
| 8 |
+
{"text": "Initializing Detection Orchestrator...\n", "record": {"elapsed": {"repr": "0:00:04.036106", "seconds": 4.036106}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 375, "message": "Initializing Detection Orchestrator...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252697+05:30", "timestamp": 1762204238.252697}}}
|
| 9 |
+
{"text": "TextProcessor initialized with min_length=50, max_length=500000\n", "record": {"elapsed": {"repr": "0:00:04.036173", "seconds": 4.036173}, "exception": null, "extra": {}, "file": {"name": "text_processor.py", "path": "/Users/itobuz/projects/office/text_auth/processors/text_processor.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 107, "message": "TextProcessor initialized with min_length=50, max_length=500000", "module": "text_processor", "name": "processors.text_processor", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.252764+05:30", "timestamp": 1762204238.252764}}}
|
| 10 |
+
{"text": "ModelManager initialized with device: cpu\n", "record": {"elapsed": {"repr": "0:00:04.036886", "seconds": 4.036886}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 133, "message": "ModelManager initialized with device: cpu", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.253477+05:30", "timestamp": 1762204238.253477}}}
|
| 11 |
+
{"text": "Model cache directory: /Users/itobuz/projects/office/text_auth/models/cache\n", "record": {"elapsed": {"repr": "0:00:04.037057", "seconds": 4.037057}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 134, "message": "Model cache directory: /Users/itobuz/projects/office/text_auth/models/cache", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.253648+05:30", "timestamp": 1762204238.253648}}}
|
| 12 |
+
{"text": "LanguageDetector initialized (use_model=True)\n", "record": {"elapsed": {"repr": "0:00:04.037154", "seconds": 4.037154}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/office/text_auth/processors/language_detector.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 182, "message": "LanguageDetector initialized (use_model=True)", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.253745+05:30", "timestamp": 1762204238.253745}}}
|
| 13 |
+
{"text": "Initialized 6 metrics: ['structural', 'entropy', 'perplexity', 'semantic_analysis', 'linguistic', 'multi_perturbation_stability']\n", "record": {"elapsed": {"repr": "0:00:04.037261", "seconds": 4.037261}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "_initialize_metrics", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 189, "message": "Initialized 6 metrics: ['structural', 'entropy', 'perplexity', 'semantic_analysis', 'linguistic', 'multi_perturbation_stability']", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.253852+05:30", "timestamp": 1762204238.253852}}}
|
| 14 |
+
{"text": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)\n", "record": {"elapsed": {"repr": "0:00:04.037349", "seconds": 4.037349}, "exception": null, "extra": {}, "file": {"name": "ensemble.py", "path": "/Users/itobuz/projects/office/text_auth/detector/ensemble.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 91, "message": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)", "module": "ensemble", "name": "detector.ensemble", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.253940+05:30", "timestamp": 1762204238.25394}}}
|
| 15 |
+
{"text": "DetectionOrchestrator initialized (language_detection=True, skip_expensive=False)\n", "record": {"elapsed": {"repr": "0:00:04.037419", "seconds": 4.037419}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 132, "message": "DetectionOrchestrator initialized (language_detection=True, skip_expensive=False)", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.254010+05:30", "timestamp": 1762204238.25401}}}
|
| 16 |
+
{"text": "Initializing detection pipeline...\n", "record": {"elapsed": {"repr": "0:00:04.037533", "seconds": 4.037533}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 202, "message": "Initializing detection pipeline...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.254124+05:30", "timestamp": 1762204238.254124}}}
|
| 17 |
+
{"text": "Initializing domain classifier...\n", "record": {"elapsed": {"repr": "0:00:04.037656", "seconds": 4.037656}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 61, "message": "Initializing domain classifier...", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.254247+05:30", "timestamp": 1762204238.254247}}}
|
| 18 |
+
{"text": "Loading model: domain_classifier (cross-encoder/nli-roberta-base)\n", "record": {"elapsed": {"repr": "0:00:04.037752", "seconds": 4.037752}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 222, "message": "Loading model: domain_classifier (cross-encoder/nli-roberta-base)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:38.254343+05:30", "timestamp": 1762204238.254343}}}
|
| 19 |
+
{"text": "Added model to cache: domain_classifier\n", "record": {"elapsed": {"repr": "0:00:05.024296", "seconds": 5.024296}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 87, "message": "Added model to cache: domain_classifier", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:39.240887+05:30", "timestamp": 1762204239.240887}}}
|
| 20 |
+
{"text": "Successfully loaded model: domain_classifier\n", "record": {"elapsed": {"repr": "0:00:05.024486", "seconds": 5.024486}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 269, "message": "Successfully loaded model: domain_classifier", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:39.241077+05:30", "timestamp": 1762204239.241077}}}
|
| 21 |
+
{"text": "Loading model: domain_classifier_fallback (microsoft/deberta-v3-small)\n", "record": {"elapsed": {"repr": "0:00:05.024576", "seconds": 5.024576}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 222, "message": "Loading model: domain_classifier_fallback (microsoft/deberta-v3-small)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:39.241167+05:30", "timestamp": 1762204239.241167}}}
|
| 22 |
+
{"text": "Added model to cache: domain_classifier_fallback\n", "record": {"elapsed": {"repr": "0:00:06.617982", "seconds": 6.617982}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 87, "message": "Added model to cache: domain_classifier_fallback", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:40.834573+05:30", "timestamp": 1762204240.834573}}}
|
| 23 |
+
{"text": "Successfully loaded model: domain_classifier_fallback\n", "record": {"elapsed": {"repr": "0:00:06.618167", "seconds": 6.618167}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 269, "message": "Successfully loaded model: domain_classifier_fallback", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:40.834758+05:30", "timestamp": 1762204240.834758}}}
|
| 24 |
+
{"text": "Fallback classifier loaded successfully\n", "record": {"elapsed": {"repr": "0:00:06.618250", "seconds": 6.61825}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 69, "message": "Fallback classifier loaded successfully", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:40.834841+05:30", "timestamp": 1762204240.834841}}}
|
| 25 |
+
{"text": "Domain classifier initialized successfully\n", "record": {"elapsed": {"repr": "0:00:06.618328", "seconds": 6.618328}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 76, "message": "Domain classifier initialized successfully", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:40.834919+05:30", "timestamp": 1762204240.834919}}}
|
| 26 |
+
{"text": "Initializing language detection model...\n", "record": {"elapsed": {"repr": "0:00:06.618494", "seconds": 6.618494}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/office/text_auth/processors/language_detector.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 198, "message": "Initializing language detection model...", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:40.835085+05:30", "timestamp": 1762204240.835085}}}
|
| 27 |
+
{"text": "Loading pipeline: text-classification with language_detector\n", "record": {"elapsed": {"repr": "0:00:06.618570", "seconds": 6.61857}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_pipeline", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 491, "message": "Loading pipeline: text-classification with language_detector", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:40.835161+05:30", "timestamp": 1762204240.835161}}}
|
| 28 |
+
{"text": "Language detector initialized successfully\n", "record": {"elapsed": {"repr": "0:00:07.492609", "seconds": 7.492609}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/office/text_auth/processors/language_detector.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 206, "message": "Language detector initialized successfully", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:41.709200+05:30", "timestamp": 1762204241.7092}}}
|
| 29 |
+
{"text": "Initializing entropy metric...\n", "record": {"elapsed": {"repr": "0:00:07.492845", "seconds": 7.492845}, "exception": null, "extra": {}, "file": {"name": "entropy.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/entropy.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 39, "message": "Initializing entropy metric...", "module": "entropy", "name": "metrics.entropy", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:41.709436+05:30", "timestamp": 1762204241.709436}}}
|
| 30 |
+
{"text": "Loading model: perplexity_gpt2 (gpt2)\n", "record": {"elapsed": {"repr": "0:00:07.492937", "seconds": 7.492937}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 222, "message": "Loading model: perplexity_gpt2 (gpt2)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:41.709528+05:30", "timestamp": 1762204241.709528}}}
|
| 31 |
+
{"text": "Added model to cache: perplexity_gpt2\n", "record": {"elapsed": {"repr": "0:00:09.470203", "seconds": 9.470203}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 87, "message": "Added model to cache: perplexity_gpt2", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.686794+05:30", "timestamp": 1762204243.686794}}}
|
| 32 |
+
{"text": "Successfully loaded model: perplexity_gpt2\n", "record": {"elapsed": {"repr": "0:00:09.470415", "seconds": 9.470415}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 269, "message": "Successfully loaded model: perplexity_gpt2", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.687006+05:30", "timestamp": 1762204243.687006}}}
|
| 33 |
+
{"text": "Entropy metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:09.470506", "seconds": 9.470506}, "exception": null, "extra": {}, "file": {"name": "entropy.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/entropy.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 52, "message": "Entropy metric initialized successfully", "module": "entropy", "name": "metrics.entropy", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.687097+05:30", "timestamp": 1762204243.687097}}}
|
| 34 |
+
{"text": "Initializing perplexity metric...\n", "record": {"elapsed": {"repr": "0:00:09.470584", "seconds": 9.470584}, "exception": null, "extra": {}, "file": {"name": "perplexity.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/perplexity.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 41, "message": "Initializing perplexity metric...", "module": "perplexity", "name": "metrics.perplexity", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.687175+05:30", "timestamp": 1762204243.687175}}}
|
| 35 |
+
{"text": "Perplexity metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:09.470659", "seconds": 9.470659}, "exception": null, "extra": {}, "file": {"name": "perplexity.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/perplexity.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 55, "message": "Perplexity metric initialized successfully", "module": "perplexity", "name": "metrics.perplexity", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.687250+05:30", "timestamp": 1762204243.68725}}}
|
| 36 |
+
{"text": "Initializing semantic analysis metric...\n", "record": {"elapsed": {"repr": "0:00:09.470730", "seconds": 9.47073}, "exception": null, "extra": {}, "file": {"name": "semantic_analysis.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/semantic_analysis.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 41, "message": "Initializing semantic analysis metric...", "module": "semantic_analysis", "name": "metrics.semantic_analysis", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.687321+05:30", "timestamp": 1762204243.687321}}}
|
| 37 |
+
{"text": "Loading model: semantic_primary (sentence-transformers/all-MiniLM-L6-v2)\n", "record": {"elapsed": {"repr": "0:00:09.470798", "seconds": 9.470798}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 222, "message": "Loading model: semantic_primary (sentence-transformers/all-MiniLM-L6-v2)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.687389+05:30", "timestamp": 1762204243.687389}}}
|
| 38 |
+
{"text": "Load pretrained SentenceTransformer: sentence-transformers/all-MiniLM-L6-v2\n", "record": {"elapsed": {"repr": "0:00:09.473093", "seconds": 9.473093}, "exception": null, "extra": {}, "file": {"name": "SentenceTransformer.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/sentence_transformers/SentenceTransformer.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 218, "message": "Load pretrained SentenceTransformer: sentence-transformers/all-MiniLM-L6-v2", "module": "SentenceTransformer", "name": "sentence_transformers.SentenceTransformer", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:43.689684+05:30", "timestamp": 1762204243.689684}}}
|
| 39 |
+
{"text": "Added model to cache: semantic_primary\n", "record": {"elapsed": {"repr": "0:00:14.013444", "seconds": 14.013444}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 87, "message": "Added model to cache: semantic_primary", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.230035+05:30", "timestamp": 1762204248.230035}}}
|
| 40 |
+
{"text": "Successfully loaded model: semantic_primary\n", "record": {"elapsed": {"repr": "0:00:14.014011", "seconds": 14.014011}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 269, "message": "Successfully loaded model: semantic_primary", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.230602+05:30", "timestamp": 1762204248.230602}}}
|
| 41 |
+
{"text": "Semantic analysis metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:14.014265", "seconds": 14.014265}, "exception": null, "extra": {}, "file": {"name": "semantic_analysis.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/semantic_analysis.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 49, "message": "Semantic analysis metric initialized successfully", "module": "semantic_analysis", "name": "metrics.semantic_analysis", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.230856+05:30", "timestamp": 1762204248.230856}}}
|
| 42 |
+
{"text": "Initializing linguistic metric...\n", "record": {"elapsed": {"repr": "0:00:14.014492", "seconds": 14.014492}, "exception": null, "extra": {}, "file": {"name": "linguistic.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/linguistic.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 39, "message": "Initializing linguistic metric...", "module": "linguistic", "name": "metrics.linguistic", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.231083+05:30", "timestamp": 1762204248.231083}}}
|
| 43 |
+
{"text": "Loading model: linguistic_spacy (en_core_web_sm)\n", "record": {"elapsed": {"repr": "0:00:14.014708", "seconds": 14.014708}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 222, "message": "Loading model: linguistic_spacy (en_core_web_sm)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.231299+05:30", "timestamp": 1762204248.231299}}}
|
| 44 |
+
{"text": "Loaded spaCy model: en_core_web_sm\n", "record": {"elapsed": {"repr": "0:00:14.322347", "seconds": 14.322347}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "_load_spacy_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 508, "message": "Loaded spaCy model: en_core_web_sm", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.538938+05:30", "timestamp": 1762204248.538938}}}
|
| 45 |
+
{"text": "Added model to cache: linguistic_spacy\n", "record": {"elapsed": {"repr": "0:00:14.322842", "seconds": 14.322842}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 87, "message": "Added model to cache: linguistic_spacy", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.539433+05:30", "timestamp": 1762204248.539433}}}
|
| 46 |
+
{"text": "Successfully loaded model: linguistic_spacy\n", "record": {"elapsed": {"repr": "0:00:14.322937", "seconds": 14.322937}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 269, "message": "Successfully loaded model: linguistic_spacy", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.539528+05:30", "timestamp": 1762204248.539528}}}
|
| 47 |
+
{"text": "Linguistic metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:14.323014", "seconds": 14.323014}, "exception": null, "extra": {}, "file": {"name": "linguistic.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/linguistic.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 46, "message": "Linguistic metric initialized successfully", "module": "linguistic", "name": "metrics.linguistic", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.539605+05:30", "timestamp": 1762204248.539605}}}
|
| 48 |
+
{"text": "Initializing MultiPerturbationStability metric...\n", "record": {"elapsed": {"repr": "0:00:14.323085", "seconds": 14.323085}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "initialize", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 52, "message": "Initializing MultiPerturbationStability metric...", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.539676+05:30", "timestamp": 1762204248.539676}}}
|
| 49 |
+
{"text": "Loading model: multi_perturbation_base (gpt2)\n", "record": {"elapsed": {"repr": "0:00:14.323156", "seconds": 14.323156}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 222, "message": "Loading model: multi_perturbation_base (gpt2)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:48.539747+05:30", "timestamp": 1762204248.539747}}}
|
| 50 |
+
{"text": "Evicted model from cache: domain_classifier\n", "record": {"elapsed": {"repr": "0:00:16.259391", "seconds": 16.259391}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 83, "message": "Evicted model from cache: domain_classifier", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:50.475982+05:30", "timestamp": 1762204250.475982}}}
|
| 51 |
+
{"text": "Added model to cache: multi_perturbation_base\n", "record": {"elapsed": {"repr": "0:00:16.259602", "seconds": 16.259602}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 87, "message": "Added model to cache: multi_perturbation_base", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:50.476193+05:30", "timestamp": 1762204250.476193}}}
|
| 52 |
+
{"text": "Successfully loaded model: multi_perturbation_base\n", "record": {"elapsed": {"repr": "0:00:16.259685", "seconds": 16.259685}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 269, "message": "Successfully loaded model: multi_perturbation_base", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:50.476276+05:30", "timestamp": 1762204250.476276}}}
|
| 53 |
+
{"text": "✓ GPT-2 model loaded for MultiPerturbationStability\n", "record": {"elapsed": {"repr": "0:00:16.382942", "seconds": 16.382942}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 62, "message": "✓ GPT-2 model loaded for MultiPerturbationStability", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:50.599533+05:30", "timestamp": 1762204250.599533}}}
|
| 54 |
+
{"text": "Loading model: multi_perturbation_mask (distilroberta-base)\n", "record": {"elapsed": {"repr": "0:00:16.383195", "seconds": 16.383195}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 222, "message": "Loading model: multi_perturbation_mask (distilroberta-base)", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:50.599786+05:30", "timestamp": 1762204250.599786}}}
|
| 55 |
+
{"text": "Evicted model from cache: domain_classifier_fallback\n", "record": {"elapsed": {"repr": "0:00:17.880725", "seconds": 17.880725}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 83, "message": "Evicted model from cache: domain_classifier_fallback", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.097316+05:30", "timestamp": 1762204252.097316}}}
|
| 56 |
+
{"text": "Added model to cache: multi_perturbation_mask\n", "record": {"elapsed": {"repr": "0:00:17.880958", "seconds": 17.880958}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "put", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 87, "message": "Added model to cache: multi_perturbation_mask", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.097549+05:30", "timestamp": 1762204252.097549}}}
|
| 57 |
+
{"text": "Successfully loaded model: multi_perturbation_mask\n", "record": {"elapsed": {"repr": "0:00:17.881040", "seconds": 17.88104}, "exception": null, "extra": {}, "file": {"name": "model_manager.py", "path": "/Users/itobuz/projects/office/text_auth/models/model_manager.py"}, "function": "load_model", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 269, "message": "Successfully loaded model: multi_perturbation_mask", "module": "model_manager", "name": "models.model_manager", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.097631+05:30", "timestamp": 1762204252.097631}}}
|
| 58 |
+
{"text": "✓ DistilRoBERTa model loaded for MultiPerturbationStability\n", "record": {"elapsed": {"repr": "0:00:17.975351", "seconds": 17.975351}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 84, "message": "✓ DistilRoBERTa model loaded for MultiPerturbationStability", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.191942+05:30", "timestamp": 1762204252.191942}}}
|
| 59 |
+
{"text": "GPT-2 test - Likelihood: 5.3535\n", "record": {"elapsed": {"repr": "0:00:18.153332", "seconds": 18.153332}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "_verify_model_loading", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 114, "message": "GPT-2 test - Likelihood: 5.3535", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.369923+05:30", "timestamp": 1762204252.369923}}}
|
| 60 |
+
{"text": "DistilRoBERTa mask token: '<mask>'\n", "record": {"elapsed": {"repr": "0:00:18.156517", "seconds": 18.156517}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "_verify_model_loading", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 124, "message": "DistilRoBERTa mask token: '<mask>'", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373108+05:30", "timestamp": 1762204252.373108}}}
|
| 61 |
+
{"text": "DistilRoBERTa tokenization test - Input shape: torch.Size([1, 11])\n", "record": {"elapsed": {"repr": "0:00:18.156860", "seconds": 18.15686}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "_verify_model_loading", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 128, "message": "DistilRoBERTa tokenization test - Input shape: torch.Size([1, 11])", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373451+05:30", "timestamp": 1762204252.373451}}}
|
| 62 |
+
{"text": "MultiPerturbationStability metric initialized successfully\n", "record": {"elapsed": {"repr": "0:00:18.157003", "seconds": 18.157003}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 96, "message": "MultiPerturbationStability metric initialized successfully", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373594+05:30", "timestamp": 1762204252.373594}}}
|
| 63 |
+
{"text": "Detection pipeline initialized: 6/6 metrics ready\n", "record": {"elapsed": {"repr": "0:00:18.157096", "seconds": 18.157096}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 229, "message": "Detection pipeline initialized: 6/6 metrics ready", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373687+05:30", "timestamp": 1762204252.373687}}}
|
| 64 |
+
{"text": "✓ Detection Orchestrator initialized\n", "record": {"elapsed": {"repr": "0:00:18.157174", "seconds": 18.157174}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 383, "message": "✓ Detection Orchestrator initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373765+05:30", "timestamp": 1762204252.373765}}}
|
| 65 |
+
{"text": "Initializing Model Attributor...\n", "record": {"elapsed": {"repr": "0:00:18.157248", "seconds": 18.157248}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 389, "message": "Initializing Model Attributor...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373839+05:30", "timestamp": 1762204252.373839}}}
|
| 66 |
+
{"text": "ModelAttributor initialized with domain-aware calibration\n", "record": {"elapsed": {"repr": "0:00:18.157317", "seconds": 18.157317}, "exception": null, "extra": {}, "file": {"name": "attribution.py", "path": "/Users/itobuz/projects/office/text_auth/detector/attribution.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 403, "message": "ModelAttributor initialized with domain-aware calibration", "module": "attribution", "name": "detector.attribution", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373908+05:30", "timestamp": 1762204252.373908}}}
|
| 67 |
+
{"text": "Model attribution system initialized with metric ensemble\n", "record": {"elapsed": {"repr": "0:00:18.157385", "seconds": 18.157385}, "exception": null, "extra": {}, "file": {"name": "attribution.py", "path": "/Users/itobuz/projects/office/text_auth/detector/attribution.py"}, "function": "initialize", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 412, "message": "Model attribution system initialized with metric ensemble", "module": "attribution", "name": "detector.attribution", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.373976+05:30", "timestamp": 1762204252.373976}}}
|
| 68 |
+
{"text": "✓ Model Attributor initialized\n", "record": {"elapsed": {"repr": "0:00:18.157449", "seconds": 18.157449}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 395, "message": "✓ Model Attributor initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374040+05:30", "timestamp": 1762204252.37404}}}
|
| 69 |
+
{"text": "Initializing Text Highlighter...\n", "record": {"elapsed": {"repr": "0:00:18.157511", "seconds": 18.157511}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 401, "message": "Initializing Text Highlighter...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374102+05:30", "timestamp": 1762204252.374102}}}
|
| 70 |
+
{"text": "TextProcessor initialized with min_length=50, max_length=500000\n", "record": {"elapsed": {"repr": "0:00:18.157582", "seconds": 18.157582}, "exception": null, "extra": {}, "file": {"name": "text_processor.py", "path": "/Users/itobuz/projects/office/text_auth/processors/text_processor.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 107, "message": "TextProcessor initialized with min_length=50, max_length=500000", "module": "text_processor", "name": "processors.text_processor", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374173+05:30", "timestamp": 1762204252.374173}}}
|
| 71 |
+
{"text": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)\n", "record": {"elapsed": {"repr": "0:00:18.157653", "seconds": 18.157653}, "exception": null, "extra": {}, "file": {"name": "ensemble.py", "path": "/Users/itobuz/projects/office/text_auth/detector/ensemble.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 91, "message": "AdvancedEnsembleClassifier initialized (primary=confidence_calibrated, fallback=domain_weighted, ml_ensemble=False)", "module": "ensemble", "name": "detector.ensemble", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374244+05:30", "timestamp": 1762204252.374244}}}
|
| 72 |
+
{"text": "✓ Text Highlighter initialized\n", "record": {"elapsed": {"repr": "0:00:18.157716", "seconds": 18.157716}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 407, "message": "✓ Text Highlighter initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374307+05:30", "timestamp": 1762204252.374307}}}
|
| 73 |
+
{"text": "Initializing Report Generator...\n", "record": {"elapsed": {"repr": "0:00:18.157778", "seconds": 18.157778}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 410, "message": "Initializing Report Generator...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374369+05:30", "timestamp": 1762204252.374369}}}
|
| 74 |
+
{"text": "ReportGenerator initialized (output_dir=/Users/itobuz/projects/office/text_auth/data/reports)\n", "record": {"elapsed": {"repr": "0:00:18.158119", "seconds": 18.158119}, "exception": null, "extra": {}, "file": {"name": "report_generator.py", "path": "/Users/itobuz/projects/office/text_auth/reporter/report_generator.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 58, "message": "ReportGenerator initialized (output_dir=/Users/itobuz/projects/office/text_auth/data/reports)", "module": "report_generator", "name": "reporter.report_generator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374710+05:30", "timestamp": 1762204252.37471}}}
|
| 75 |
+
{"text": "✓ Report Generator initialized\n", "record": {"elapsed": {"repr": "0:00:18.158232", "seconds": 18.158232}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 416, "message": "✓ Report Generator initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374823+05:30", "timestamp": 1762204252.374823}}}
|
| 76 |
+
{"text": "Initializing Reasoning Generator...\n", "record": {"elapsed": {"repr": "0:00:18.158305", "seconds": 18.158305}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 419, "message": "Initializing Reasoning Generator...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374896+05:30", "timestamp": 1762204252.374896}}}
|
| 77 |
+
{"text": "✓ Reasoning Generator initialized\n", "record": {"elapsed": {"repr": "0:00:18.158376", "seconds": 18.158376}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 425, "message": "✓ Reasoning Generator initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.374967+05:30", "timestamp": 1762204252.374967}}}
|
| 78 |
+
{"text": "Initializing Document Extractor...\n", "record": {"elapsed": {"repr": "0:00:18.158440", "seconds": 18.15844}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 428, "message": "Initializing Document Extractor...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375031+05:30", "timestamp": 1762204252.375031}}}
|
| 79 |
+
{"text": "DocumentExtractor initialized (max_size=50.0MB)\n", "record": {"elapsed": {"repr": "0:00:18.158512", "seconds": 18.158512}, "exception": null, "extra": {}, "file": {"name": "document_extractor.py", "path": "/Users/itobuz/projects/office/text_auth/processors/document_extractor.py"}, "function": "__init__", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 124, "message": "DocumentExtractor initialized (max_size=50.0MB)", "module": "document_extractor", "name": "processors.document_extractor", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375103+05:30", "timestamp": 1762204252.375103}}}
|
| 80 |
+
{"text": "✓ Document Extractor initialized\n", "record": {"elapsed": {"repr": "0:00:18.158579", "seconds": 18.158579}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 434, "message": "✓ Document Extractor initialized", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375170+05:30", "timestamp": 1762204252.37517}}}
|
| 81 |
+
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:18.158645", "seconds": 18.158645}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 436, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375236+05:30", "timestamp": 1762204252.375236}}}
|
| 82 |
+
{"text": "TEXT-AUTH API Ready!\n", "record": {"elapsed": {"repr": "0:00:18.158706", "seconds": 18.158706}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 437, "message": "TEXT-AUTH API Ready!", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375297+05:30", "timestamp": 1762204252.375297}}}
|
| 83 |
+
{"text": "Server: 0.0.0.0:8000\n", "record": {"elapsed": {"repr": "0:00:18.158771", "seconds": 18.158771}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 438, "message": "Server: 0.0.0.0:8000", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375362+05:30", "timestamp": 1762204252.375362}}}
|
| 84 |
+
{"text": "Environment: development\n", "record": {"elapsed": {"repr": "0:00:18.158830", "seconds": 18.15883}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 439, "message": "Environment: development", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375421+05:30", "timestamp": 1762204252.375421}}}
|
| 85 |
+
{"text": "Device: cpu\n", "record": {"elapsed": {"repr": "0:00:18.159115", "seconds": 18.159115}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 440, "message": "Device: cpu", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375706+05:30", "timestamp": 1762204252.375706}}}
|
| 86 |
+
{"text": "================================================================================\n", "record": {"elapsed": {"repr": "0:00:18.159189", "seconds": 18.159189}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "startup_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 441, "message": "================================================================================", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.375780+05:30", "timestamp": 1762204252.37578}}}
|
| 87 |
+
{"text": "Application startup complete.\n", "record": {"elapsed": {"repr": "0:00:18.159683", "seconds": 18.159683}, "exception": null, "extra": {}, "file": {"name": "on.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/lifespan/on.py"}, "function": "startup", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 62, "message": "Application startup complete.", "module": "on", "name": "uvicorn.lifespan.on", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:52.376274+05:30", "timestamp": 1762204252.376274}}}
|
| 88 |
+
{"text": "API Request: GET / -> 200\n", "record": {"elapsed": {"repr": "0:00:22.204529", "seconds": 22.204529}, "exception": null, "extra": {"log_type": "application", "extra": {"http_method": "GET", "path": "/", "status_code": 200, "duration_seconds": 0.003, "user": null, "ip_address": "127.0.0.1", "timestamp": "2025-11-04T02:40:56.421053"}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "log_api_request", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 374, "message": "API Request: GET / -> 200", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:56.421120+05:30", "timestamp": 1762204256.42112}}}
|
| 89 |
+
{"text": "127.0.0.1:59452 - \"GET / HTTP/1.1\" 200\n", "record": {"elapsed": {"repr": "0:00:22.205405", "seconds": 22.205405}, "exception": null, "extra": {}, "file": {"name": "h11_impl.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py"}, "function": "send", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 473, "message": "127.0.0.1:59452 - \"GET / HTTP/1.1\" 200", "module": "h11_impl", "name": "uvicorn.protocols.http.h11_impl", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:40:56.421996+05:30", "timestamp": 1762204256.421996}}}
|
| 90 |
+
{"text": "[analysis_1762204265013] Analyzing text (7084 chars)\n", "record": {"elapsed": {"repr": "0:00:30.796571", "seconds": 30.796571}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "analyze_text", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 729, "message": "[analysis_1762204265013] Analyzing text (7084 chars)", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:05.013162+05:30", "timestamp": 1762204265.013162}}}
|
| 91 |
+
{"text": "Step 1: Preprocessing text...\n", "record": {"elapsed": {"repr": "0:00:30.797171", "seconds": 30.797171}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 259, "message": "Step 1: Preprocessing text...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:05.013762+05:30", "timestamp": 1762204265.013762}}}
|
| 92 |
+
{"text": "Step 2: Detecting language...\n", "record": {"elapsed": {"repr": "0:00:30.815050", "seconds": 30.81505}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 271, "message": "Step 2: Detecting language...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:05.031641+05:30", "timestamp": 1762204265.031641}}}
|
| 93 |
+
{"text": "Split text into 16 chunks for language detection\n", "record": {"elapsed": {"repr": "0:00:30.826827", "seconds": 30.826827}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/office/text_auth/processors/language_detector.py"}, "function": "_detect_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 307, "message": "Split text into 16 chunks for language detection", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:05.043418+05:30", "timestamp": 1762204265.043418}}}
|
| 94 |
+
{"text": "Detected language: en (confidence: 0.94, method: xlm-roberta-model)\n", "record": {"elapsed": {"repr": "0:00:31.632558", "seconds": 31.632558}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/office/text_auth/processors/language_detector.py"}, "function": "detect", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 291, "message": "Detected language: en (confidence: 0.94, method: xlm-roberta-model)", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:05.849149+05:30", "timestamp": 1762204265.849149}}}
|
| 95 |
+
{"text": "Step 3: Classifying domain...\n", "record": {"elapsed": {"repr": "0:00:31.632810", "seconds": 31.63281}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 290, "message": "Step 3: Classifying domain...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:05.849401+05:30", "timestamp": 1762204265.849401}}}
|
| 96 |
+
{"text": "Primary model classified domain: general (confidence: 0.093)\n", "record": {"elapsed": {"repr": "0:00:34.005318", "seconds": 34.005318}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "_classify_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Primary model classified domain: general (confidence: 0.093)", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:08.221909+05:30", "timestamp": 1762204268.221909}}}
|
| 97 |
+
{"text": "Primary classifier low confidence, trying fallback model...\n", "record": {"elapsed": {"repr": "0:00:34.005681", "seconds": 34.005681}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "classify", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 118, "message": "Primary classifier low confidence, trying fallback model...", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:08.222272+05:30", "timestamp": 1762204268.222272}}}
|
| 98 |
+
{"text": "Fallback model classified domain: medical (confidence: 0.063)\n", "record": {"elapsed": {"repr": "0:00:36.809233", "seconds": 36.809233}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "_classify_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Fallback model classified domain: medical (confidence: 0.063)", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:11.025824+05:30", "timestamp": 1762204271.025824}}}
|
| 99 |
+
{"text": "Detected domain: general (confidence: 0.09)\n", "record": {"elapsed": {"repr": "0:00:36.809472", "seconds": 36.809472}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 318, "message": "Detected domain: general (confidence: 0.09)", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:11.026063+05:30", "timestamp": 1762204271.026063}}}
|
| 100 |
+
{"text": "Step 4: Executing detection metrics calculations...\n", "record": {"elapsed": {"repr": "0:00:36.809574", "seconds": 36.809574}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 321, "message": "Step 4: Executing detection metrics calculations...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:11.026165+05:30", "timestamp": 1762204271.026165}}}
|
| 101 |
+
{"text": "Valid perturbations: 10/10\n", "record": {"elapsed": {"repr": "0:00:43.955369", "seconds": 43.955369}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "_calculate_stability_features", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 297, "message": "Valid perturbations: 10/10", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:18.171960+05:30", "timestamp": 1762204278.17196}}}
|
| 102 |
+
{"text": "Stability: 0.227, Curvature: 0.186\n", "record": {"elapsed": {"repr": "0:00:43.955711", "seconds": 43.955711}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "_calculate_stability_features", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 312, "message": "Stability: 0.227, Curvature: 0.186", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:18.172302+05:30", "timestamp": 1762204278.172302}}}
|
| 103 |
+
{"text": "Executed 6 metrics successfully\n", "record": {"elapsed": {"repr": "0:00:46.034007", "seconds": 46.034007}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 361, "message": "Executed 6 metrics successfully", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.250598+05:30", "timestamp": 1762204280.250598}}}
|
| 104 |
+
{"text": "Step 5: Aggregating results with ensemble...\n", "record": {"elapsed": {"repr": "0:00:46.034219", "seconds": 46.034219}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 364, "message": "Step 5: Aggregating results with ensemble...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.250810+05:30", "timestamp": 1762204280.25081}}}
|
| 105 |
+
{"text": "Analysis complete: Human-Written (AI probability: 32.8%, confidence: 0.70) in 15.24s\n", "record": {"elapsed": {"repr": "0:00:46.034429", "seconds": 46.034429}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 393, "message": "Analysis complete: Human-Written (AI probability: 32.8%, confidence: 0.70) in 15.24s", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.251020+05:30", "timestamp": 1762204280.25102}}}
|
| 106 |
+
{"text": "[analysis_1762204265013] Running attribution...\n", "record": {"elapsed": {"repr": "0:00:46.034763", "seconds": 46.034763}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "analyze_text", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 745, "message": "[analysis_1762204265013] Running attribution...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.251354+05:30", "timestamp": 1762204280.251354}}}
|
| 107 |
+
{"text": "[analysis_1762204265013] Generating highlights...\n", "record": {"elapsed": {"repr": "0:00:46.036959", "seconds": 46.036959}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "analyze_text", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 763, "message": "[analysis_1762204265013] Generating highlights...", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.253550+05:30", "timestamp": 1762204280.25355}}}
|
| 108 |
+
{"text": "Detection completed: analysis_1762204265013 -> Human-Written\n", "record": {"elapsed": {"repr": "0:00:46.050481", "seconds": 46.050481}, "exception": null, "extra": {"log_type": "application", "extra": {"analysis_id": "analysis_1762204265013", "text_length": 7084, "verdict": "Human-Written", "confidence": 0.7012, "domain": "general", "processing_time_seconds": 15.2539, "timestamp": "2025-11-04T02:41:20.267055", "enable_attribution": true, "enable_highlighting": true}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "log_detection_event", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 419, "message": "Detection completed: analysis_1762204265013 -> Human-Written", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.267072+05:30", "timestamp": 1762204280.267072}}}
|
| 109 |
+
{"text": "API Request: POST /api/analyze -> 200\n", "record": {"elapsed": {"repr": "0:00:46.051774", "seconds": 46.051774}, "exception": null, "extra": {"log_type": "application", "extra": {"http_method": "POST", "path": "/api/analyze", "status_code": 200, "duration_seconds": 15.2629, "user": null, "ip_address": "127.0.0.1", "timestamp": "2025-11-04T02:41:20.268352"}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "log_api_request", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 374, "message": "API Request: POST /api/analyze -> 200", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.268365+05:30", "timestamp": 1762204280.268365}}}
|
| 110 |
+
{"text": "127.0.0.1:59453 - \"POST /api/analyze HTTP/1.1\" 200\n", "record": {"elapsed": {"repr": "0:00:46.052001", "seconds": 46.052001}, "exception": null, "extra": {}, "file": {"name": "h11_impl.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py"}, "function": "send", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 473, "message": "127.0.0.1:59453 - \"POST /api/analyze HTTP/1.1\" 200", "module": "h11_impl", "name": "uvicorn.protocols.http.h11_impl", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:20.268592+05:30", "timestamp": 1762204280.268592}}}
|
| 111 |
+
{"text": "Generating report for analysis_1762204265013\n", "record": {"elapsed": {"repr": "0:00:52.283794", "seconds": 52.283794}, "exception": null, "extra": {}, "file": {"name": "text_auth_app.py", "path": "/Users/itobuz/projects/office/text_auth/text_auth_app.py"}, "function": "generate_report", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 1089, "message": "Generating report for analysis_1762204265013", "module": "text_auth_app", "name": "text_auth_app", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:26.500385+05:30", "timestamp": 1762204286.500385}}}
|
| 112 |
+
{"text": "Step 1: Preprocessing text...\n", "record": {"elapsed": {"repr": "0:00:52.284935", "seconds": 52.284935}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 259, "message": "Step 1: Preprocessing text...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:26.501526+05:30", "timestamp": 1762204286.501526}}}
|
| 113 |
+
{"text": "Step 2: Detecting language...\n", "record": {"elapsed": {"repr": "0:00:52.302325", "seconds": 52.302325}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 271, "message": "Step 2: Detecting language...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:26.518916+05:30", "timestamp": 1762204286.518916}}}
|
| 114 |
+
{"text": "Split text into 16 chunks for language detection\n", "record": {"elapsed": {"repr": "0:00:52.318069", "seconds": 52.318069}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/office/text_auth/processors/language_detector.py"}, "function": "_detect_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 307, "message": "Split text into 16 chunks for language detection", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:26.534660+05:30", "timestamp": 1762204286.53466}}}
|
| 115 |
+
{"text": "Detected language: en (confidence: 0.94, method: xlm-roberta-model)\n", "record": {"elapsed": {"repr": "0:00:53.057609", "seconds": 53.057609}, "exception": null, "extra": {}, "file": {"name": "language_detector.py", "path": "/Users/itobuz/projects/office/text_auth/processors/language_detector.py"}, "function": "detect", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 291, "message": "Detected language: en (confidence: 0.94, method: xlm-roberta-model)", "module": "language_detector", "name": "processors.language_detector", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:27.274200+05:30", "timestamp": 1762204287.2742}}}
|
| 116 |
+
{"text": "Step 3: Classifying domain...\n", "record": {"elapsed": {"repr": "0:00:53.057969", "seconds": 53.057969}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 290, "message": "Step 3: Classifying domain...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:27.274560+05:30", "timestamp": 1762204287.27456}}}
|
| 117 |
+
{"text": "Primary model classified domain: general (confidence: 0.093)\n", "record": {"elapsed": {"repr": "0:00:55.176692", "seconds": 55.176692}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "_classify_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Primary model classified domain: general (confidence: 0.093)", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:29.393283+05:30", "timestamp": 1762204289.393283}}}
|
| 118 |
+
{"text": "Primary classifier low confidence, trying fallback model...\n", "record": {"elapsed": {"repr": "0:00:55.177097", "seconds": 55.177097}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "classify", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 118, "message": "Primary classifier low confidence, trying fallback model...", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:29.393688+05:30", "timestamp": 1762204289.393688}}}
|
| 119 |
+
{"text": "Fallback model classified domain: medical (confidence: 0.063)\n", "record": {"elapsed": {"repr": "0:00:57.615243", "seconds": 57.615243}, "exception": null, "extra": {}, "file": {"name": "domain_classifier.py", "path": "/Users/itobuz/projects/office/text_auth/processors/domain_classifier.py"}, "function": "_classify_with_model", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 221, "message": "Fallback model classified domain: medical (confidence: 0.063)", "module": "domain_classifier", "name": "processors.domain_classifier", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:31.831834+05:30", "timestamp": 1762204291.831834}}}
|
| 120 |
+
{"text": "Detected domain: general (confidence: 0.09)\n", "record": {"elapsed": {"repr": "0:00:57.615484", "seconds": 57.615484}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 318, "message": "Detected domain: general (confidence: 0.09)", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:31.832075+05:30", "timestamp": 1762204291.832075}}}
|
| 121 |
+
{"text": "Step 4: Executing detection metrics calculations...\n", "record": {"elapsed": {"repr": "0:00:57.615590", "seconds": 57.61559}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 321, "message": "Step 4: Executing detection metrics calculations...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:31.832181+05:30", "timestamp": 1762204291.832181}}}
|
| 122 |
+
{"text": "Valid perturbations: 10/10\n", "record": {"elapsed": {"repr": "0:01:03.429344", "seconds": 63.429344}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "_calculate_stability_features", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 297, "message": "Valid perturbations: 10/10", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:37.645935+05:30", "timestamp": 1762204297.645935}}}
|
| 123 |
+
{"text": "Stability: 0.223, Curvature: 0.258\n", "record": {"elapsed": {"repr": "0:01:03.429670", "seconds": 63.42967}, "exception": null, "extra": {}, "file": {"name": "multi_perturbation_stability.py", "path": "/Users/itobuz/projects/office/text_auth/metrics/multi_perturbation_stability.py"}, "function": "_calculate_stability_features", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 312, "message": "Stability: 0.223, Curvature: 0.258", "module": "multi_perturbation_stability", "name": "metrics.multi_perturbation_stability", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:37.646261+05:30", "timestamp": 1762204297.646261}}}
|
| 124 |
+
{"text": "Executed 6 metrics successfully\n", "record": {"elapsed": {"repr": "0:01:04.822565", "seconds": 64.822565}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 361, "message": "Executed 6 metrics successfully", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.039156+05:30", "timestamp": 1762204299.039156}}}
|
| 125 |
+
{"text": "Step 5: Aggregating results with ensemble...\n", "record": {"elapsed": {"repr": "0:01:04.822780", "seconds": 64.82278}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 364, "message": "Step 5: Aggregating results with ensemble...", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.039371+05:30", "timestamp": 1762204299.039371}}}
|
| 126 |
+
{"text": "Analysis complete: Human-Written (AI probability: 32.8%, confidence: 0.70) in 12.54s\n", "record": {"elapsed": {"repr": "0:01:04.822990", "seconds": 64.82299}, "exception": null, "extra": {}, "file": {"name": "orchestrator.py", "path": "/Users/itobuz/projects/office/text_auth/detector/orchestrator.py"}, "function": "analyze", "level": {"icon": "✅", "name": "SUCCESS", "no": 25}, "line": 393, "message": "Analysis complete: Human-Written (AI probability: 32.8%, confidence: 0.70) in 12.54s", "module": "orchestrator", "name": "detector.orchestrator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.039581+05:30", "timestamp": 1762204299.039581}}}
|
| 127 |
+
{"text": "PDF report saved: /Users/itobuz/projects/office/text_auth/data/reports/analysis_1762204265013_20251104_024139.pdf\n", "record": {"elapsed": {"repr": "0:01:04.894839", "seconds": 64.894839}, "exception": null, "extra": {}, "file": {"name": "report_generator.py", "path": "/Users/itobuz/projects/office/text_auth/reporter/report_generator.py"}, "function": "_generate_pdf_report", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 600, "message": "PDF report saved: /Users/itobuz/projects/office/text_auth/data/reports/analysis_1762204265013_20251104_024139.pdf", "module": "report_generator", "name": "reporter.report_generator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.111430+05:30", "timestamp": 1762204299.11143}}}
|
| 128 |
+
{"text": "Generated 1 report(s): ['pdf']\n", "record": {"elapsed": {"repr": "0:01:04.895130", "seconds": 64.89513}, "exception": null, "extra": {}, "file": {"name": "report_generator.py", "path": "/Users/itobuz/projects/office/text_auth/reporter/report_generator.py"}, "function": "generate_complete_report", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 127, "message": "Generated 1 report(s): ['pdf']", "module": "report_generator", "name": "reporter.report_generator", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.111721+05:30", "timestamp": 1762204299.111721}}}
|
| 129 |
+
{"text": "API Request: POST /api/report/generate -> 200\n", "record": {"elapsed": {"repr": "0:01:04.895647", "seconds": 64.895647}, "exception": null, "extra": {"log_type": "application", "extra": {"http_method": "POST", "path": "/api/report/generate", "status_code": 200, "duration_seconds": 12.6151, "user": null, "ip_address": "127.0.0.1", "timestamp": "2025-11-04T02:41:39.112229"}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "log_api_request", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 374, "message": "API Request: POST /api/report/generate -> 200", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.112238+05:30", "timestamp": 1762204299.112238}}}
|
| 130 |
+
{"text": "127.0.0.1:59459 - \"POST /api/report/generate HTTP/1.1\" 200\n", "record": {"elapsed": {"repr": "0:01:04.895835", "seconds": 64.895835}, "exception": null, "extra": {}, "file": {"name": "h11_impl.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py"}, "function": "send", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 473, "message": "127.0.0.1:59459 - \"POST /api/report/generate HTTP/1.1\" 200", "module": "h11_impl", "name": "uvicorn.protocols.http.h11_impl", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.112426+05:30", "timestamp": 1762204299.112426}}}
|
| 131 |
+
{"text": "API Request: GET /api/report/download/analysis_1762204265013_20251104_024139.pdf -> 200\n", "record": {"elapsed": {"repr": "0:01:04.900807", "seconds": 64.900807}, "exception": null, "extra": {"log_type": "application", "extra": {"http_method": "GET", "path": "/api/report/download/analysis_1762204265013_20251104_024139.pdf", "status_code": 200, "duration_seconds": 0.0006, "user": null, "ip_address": "127.0.0.1", "timestamp": "2025-11-04T02:41:39.117391"}}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "log_api_request", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 374, "message": "API Request: GET /api/report/download/analysis_1762204265013_20251104_024139.pdf -> 200", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.117398+05:30", "timestamp": 1762204299.117398}}}
|
| 132 |
+
{"text": "127.0.0.1:59459 - \"GET /api/report/download/analysis_1762204265013_20251104_024139.pdf HTTP/1.1\" 200\n", "record": {"elapsed": {"repr": "0:01:04.900946", "seconds": 64.900946}, "exception": null, "extra": {}, "file": {"name": "h11_impl.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py"}, "function": "send", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 473, "message": "127.0.0.1:59459 - \"GET /api/report/download/analysis_1762204265013_20251104_024139.pdf HTTP/1.1\" 200", "module": "h11_impl", "name": "uvicorn.protocols.http.h11_impl", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:41:39.117537+05:30", "timestamp": 1762204299.117537}}}
|
| 133 |
+
{"text": "Shutting down\n", "record": {"elapsed": {"repr": "0:01:33.413597", "seconds": 93.413597}, "exception": null, "extra": {}, "file": {"name": "server.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/server.py"}, "function": "shutdown", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 263, "message": "Shutting down", "module": "server", "name": "uvicorn.server", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:42:07.630188+05:30", "timestamp": 1762204327.630188}}}
|
| 134 |
+
{"text": "Waiting for application shutdown.\n", "record": {"elapsed": {"repr": "0:01:33.515638", "seconds": 93.515638}, "exception": null, "extra": {}, "file": {"name": "on.py", "path": "/Users/itobuz/anaconda3/lib/python3.10/site-packages/uvicorn/lifespan/on.py"}, "function": "shutdown", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 67, "message": "Waiting for application shutdown.", "module": "on", "name": "uvicorn.lifespan.on", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:42:07.732229+05:30", "timestamp": 1762204327.732229}}}
|
| 135 |
+
{"text": "Logging system cleanup completed\n", "record": {"elapsed": {"repr": "0:01:33.516366", "seconds": 93.516366}, "exception": null, "extra": {}, "file": {"name": "logger.py", "path": "/Users/itobuz/projects/office/text_auth/utils/logger.py"}, "function": "cleanup", "level": {"icon": "ℹ️", "name": "INFO", "no": 20}, "line": 522, "message": "Logging system cleanup completed", "module": "logger", "name": "utils.logger", "process": {"id": 3009, "name": "SpawnProcess-1"}, "thread": {"id": 8707055360, "name": "MainThread"}, "time": {"repr": "2025-11-04 02:42:07.732957+05:30", "timestamp": 1762204327.732957}}}
|
metrics/multi_perturbation_stability.py
CHANGED
|
@@ -59,6 +59,7 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 59 |
self.gpt_model, self.gpt_tokenizer = gpt_result
|
| 60 |
# Move model to appropriate device
|
| 61 |
self.gpt_model.to(self.device)
|
|
|
|
| 62 |
|
| 63 |
else:
|
| 64 |
logger.error("Failed to load GPT-2 model for MultiPerturbationStability")
|
|
@@ -76,9 +77,20 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 76 |
if (self.mask_tokenizer.pad_token is None):
|
| 77 |
self.mask_tokenizer.pad_token = self.mask_tokenizer.eos_token or '[PAD]'
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
else:
|
| 80 |
logger.warning("Failed to load mask model, using GPT-2 only")
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
self.is_initialized = True
|
| 83 |
|
| 84 |
logger.success("MultiPerturbationStability metric initialized successfully")
|
|
@@ -89,12 +101,51 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 89 |
return False
|
| 90 |
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
def compute(self, text: str, **kwargs) -> MetricResult:
|
| 93 |
"""
|
| 94 |
Compute MultiPerturbationStability analysis with FULL DOMAIN THRESHOLD INTEGRATION
|
| 95 |
"""
|
| 96 |
try:
|
| 97 |
-
if ((not text) or (len(text.strip()) <
|
| 98 |
return MetricResult(metric_name = self.name,
|
| 99 |
ai_probability = 0.5,
|
| 100 |
human_probability = 0.5,
|
|
@@ -121,13 +172,16 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 121 |
)
|
| 122 |
|
| 123 |
# Calculate MultiPerturbationStability features
|
| 124 |
-
features = self._calculate_stability_features(text)
|
| 125 |
|
| 126 |
# Calculate raw MultiPerturbationStability score (0-1 scale)
|
| 127 |
-
raw_stability_score, confidence = self._analyze_stability_patterns(features)
|
| 128 |
|
| 129 |
# Apply domain-specific thresholds to convert raw score to probabilities
|
| 130 |
-
ai_prob, human_prob, mixed_prob = self._apply_domain_thresholds(raw_stability_score,
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
# Apply confidence multiplier from domain thresholds
|
| 133 |
confidence *= multi_perturbation_stability_thresholds.confidence_multiplier
|
|
@@ -211,54 +265,75 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 211 |
|
| 212 |
def _calculate_stability_features(self, text: str) -> Dict[str, Any]:
|
| 213 |
"""
|
| 214 |
-
Calculate comprehensive MultiPerturbationStability features
|
| 215 |
"""
|
| 216 |
if not self.gpt_model or not self.gpt_tokenizer:
|
| 217 |
return self._get_default_features()
|
| 218 |
|
| 219 |
try:
|
| 220 |
# Preprocess text for better analysis
|
| 221 |
-
processed_text = self._preprocess_text_for_analysis(text)
|
| 222 |
|
| 223 |
# Calculate original text likelihood
|
| 224 |
-
original_likelihood = self._calculate_likelihood(processed_text)
|
|
|
|
| 225 |
|
| 226 |
# Generate perturbations and calculate perturbed likelihoods
|
| 227 |
-
perturbations = self._generate_perturbations(processed_text,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
perturbed_likelihoods = list()
|
| 229 |
|
| 230 |
-
for perturbed_text in perturbations:
|
| 231 |
if (perturbed_text and (perturbed_text != processed_text)):
|
| 232 |
-
likelihood = self._calculate_likelihood(perturbed_text)
|
| 233 |
|
| 234 |
if (likelihood > 0):
|
| 235 |
perturbed_likelihoods.append(likelihood)
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
# Calculate stability metrics
|
| 238 |
if perturbed_likelihoods:
|
| 239 |
-
stability_score = self._calculate_stability_score(original_likelihood,
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
avg_perturbed_likelihood = np.mean(perturbed_likelihoods)
|
|
|
|
|
|
|
| 243 |
|
| 244 |
else:
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
|
|
|
|
|
|
| 249 |
|
| 250 |
# Calculate likelihood ratio
|
| 251 |
-
likelihood_ratio
|
| 252 |
|
| 253 |
# Chunk-based analysis for whole-text understanding
|
| 254 |
-
chunk_stabilities
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
-
#
|
| 259 |
-
normalized_stability
|
| 260 |
-
normalized_curvature
|
| 261 |
-
normalized_likelihood_ratio
|
| 262 |
|
| 263 |
return {"original_likelihood" : round(original_likelihood, 4),
|
| 264 |
"avg_perturbed_likelihood" : round(avg_perturbed_likelihood, 4),
|
|
@@ -281,59 +356,87 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 281 |
|
| 282 |
def _calculate_likelihood(self, text: str) -> float:
|
| 283 |
"""
|
| 284 |
-
Calculate log-likelihood
|
|
|
|
| 285 |
"""
|
| 286 |
try:
|
| 287 |
# Check text length before tokenization
|
| 288 |
if (len(text.strip()) < 10):
|
| 289 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
|
| 291 |
-
#
|
| 292 |
-
|
|
|
|
| 293 |
|
| 294 |
# Tokenize text with proper settings
|
| 295 |
-
encodings =
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
|
| 303 |
input_ids = encodings.input_ids.to(self.device)
|
| 304 |
attention_mask = encodings.attention_mask.to(self.device)
|
| 305 |
|
| 306 |
# Minimum tokens for meaningful analysis
|
| 307 |
-
if ((input_ids.numel() == 0) or (input_ids.size(1) <
|
| 308 |
-
return
|
| 309 |
|
| 310 |
-
# Calculate
|
| 311 |
with torch.no_grad():
|
| 312 |
-
outputs
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
|
| 317 |
-
|
|
|
|
| 318 |
|
| 319 |
-
# Convert to positive
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
if (abs(log_likelihood) > 100):
|
| 324 |
-
logger.warning(f"Extreme likelihood value detected: {log_likelihood}")
|
| 325 |
-
return 0.0
|
| 326 |
|
| 327 |
-
return
|
| 328 |
|
| 329 |
except Exception as e:
|
| 330 |
logger.warning(f"Likelihood calculation failed: {repr(e)}")
|
| 331 |
-
return
|
| 332 |
|
| 333 |
|
| 334 |
def _generate_perturbations(self, text: str, num_perturbations: int = 5) -> List[str]:
|
| 335 |
"""
|
| 336 |
-
Generate perturbed versions of the text
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
"""
|
| 338 |
perturbations = list()
|
| 339 |
|
|
@@ -383,33 +486,37 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 383 |
logger.debug(f"Word swapping perturbation failed: {e}")
|
| 384 |
continue
|
| 385 |
|
| 386 |
-
# Method 3:
|
| 387 |
if (self.mask_model and self.mask_tokenizer and (len(words) > 4) and len(perturbations) < num_perturbations):
|
| 388 |
|
| 389 |
try:
|
| 390 |
-
roberta_perturbations = self._generate_roberta_masked_perturbations(processed_text,
|
| 391 |
-
words,
|
| 392 |
-
num_perturbations - len(perturbations)
|
|
|
|
| 393 |
perturbations.extend(roberta_perturbations)
|
| 394 |
|
| 395 |
except Exception as e:
|
| 396 |
-
logger.warning(f"
|
| 397 |
|
| 398 |
# Method 4: Synonym replacement as fallback
|
| 399 |
if (len(perturbations) < num_perturbations):
|
| 400 |
try:
|
| 401 |
-
synonym_perturbations = self._generate_synonym_perturbations(processed_text,
|
| 402 |
-
words,
|
| 403 |
-
num_perturbations - len(perturbations)
|
|
|
|
| 404 |
perturbations.extend(synonym_perturbations)
|
| 405 |
|
| 406 |
except Exception as e:
|
| 407 |
-
logger.debug(f"Synonym replacement failed: {e}")
|
| 408 |
|
| 409 |
# Ensure we have at least some perturbations
|
| 410 |
if not perturbations:
|
| 411 |
# Fallback: create simple variations
|
| 412 |
-
fallback_perturbations = self._generate_fallback_perturbations(processed_text,
|
|
|
|
|
|
|
| 413 |
perturbations.extend(fallback_perturbations)
|
| 414 |
|
| 415 |
# Remove duplicates and ensure we don't exceed requested number
|
|
@@ -423,19 +530,23 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 423 |
|
| 424 |
except Exception as e:
|
| 425 |
logger.warning(f"Perturbation generation failed: {repr(e)}")
|
| 426 |
-
# Return at least the original text as fallback
|
| 427 |
-
return [text]
|
| 428 |
|
| 429 |
|
| 430 |
def _generate_roberta_masked_perturbations(self, text: str, words: List[str], max_perturbations: int) -> List[str]:
|
| 431 |
"""
|
| 432 |
-
Generate perturbations using
|
|
|
|
| 433 |
"""
|
| 434 |
perturbations = list()
|
| 435 |
|
| 436 |
try:
|
| 437 |
-
#
|
| 438 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 439 |
|
| 440 |
# Select words to mask (avoid very short words and punctuation)
|
| 441 |
candidate_positions = [i for i, word in enumerate(words) if (len(word) > 3) and word.isalpha() and word.lower() not in ['the', 'and', 'but', 'for', 'with']]
|
|
@@ -448,7 +559,7 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 448 |
|
| 449 |
# Try multiple mask positions
|
| 450 |
attempts = min(max_perturbations * 2, len(candidate_positions))
|
| 451 |
-
positions_to_try = np.random.choice(candidate_positions, min(attempts, len(candidate_positions)), replace=False)
|
| 452 |
|
| 453 |
for pos in positions_to_try:
|
| 454 |
if (len(perturbations) >= max_perturbations):
|
|
@@ -461,15 +572,15 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 461 |
masked_words[pos] = roberta_mask_token
|
| 462 |
masked_text = ' '.join(masked_words)
|
| 463 |
|
| 464 |
-
#
|
| 465 |
if not masked_text.endswith(('.', '!', '?')):
|
| 466 |
masked_text += '.'
|
| 467 |
|
| 468 |
-
# Tokenize with
|
| 469 |
inputs = self.mask_tokenizer(masked_text,
|
| 470 |
return_tensors = "pt",
|
| 471 |
truncation = True,
|
| 472 |
-
max_length = min(128, self.mask_tokenizer.model_max_length),
|
| 473 |
padding = True,
|
| 474 |
)
|
| 475 |
|
|
@@ -508,15 +619,14 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 508 |
|
| 509 |
if (self._is_valid_perturbation(new_text, text)):
|
| 510 |
perturbations.append(new_text)
|
| 511 |
-
# Use first valid prediction
|
| 512 |
-
break
|
| 513 |
|
| 514 |
except Exception as e:
|
| 515 |
-
logger.debug(f"
|
| 516 |
continue
|
| 517 |
|
| 518 |
except Exception as e:
|
| 519 |
-
logger.warning(f"
|
| 520 |
|
| 521 |
return perturbations
|
| 522 |
|
|
@@ -559,7 +669,7 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 559 |
perturbations.append(new_text)
|
| 560 |
|
| 561 |
except Exception as e:
|
| 562 |
-
logger.debug(f"Synonym replacement failed: {e}")
|
| 563 |
|
| 564 |
return perturbations
|
| 565 |
|
|
@@ -585,41 +695,72 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 585 |
perturbations.append(text.capitalize())
|
| 586 |
|
| 587 |
except Exception as e:
|
| 588 |
-
logger.debug(f"Fallback perturbation failed: {e}")
|
| 589 |
|
| 590 |
return [p for p in perturbations if p and p != text][:3]
|
| 591 |
|
| 592 |
|
| 593 |
def _calculate_stability_score(self, original_likelihood: float, perturbed_likelihoods: List[float]) -> float:
|
| 594 |
"""
|
| 595 |
-
Calculate text stability score
|
| 596 |
"""
|
| 597 |
if ((not perturbed_likelihoods) or (original_likelihood <= 0)):
|
| 598 |
-
|
|
|
|
| 599 |
|
| 600 |
-
# Calculate
|
| 601 |
-
|
| 602 |
-
avg_drop = np.mean(likelihood_drops) if likelihood_drops else 0.0
|
| 603 |
|
| 604 |
-
|
| 605 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 606 |
|
| 607 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
|
| 609 |
|
| 610 |
def _calculate_curvature_score(self, original_likelihood: float, perturbed_likelihoods: List[float]) -> float:
|
| 611 |
"""
|
| 612 |
-
Calculate likelihood curvature score :
|
| 613 |
"""
|
| 614 |
if ((not perturbed_likelihoods) or (original_likelihood <= 0)):
|
| 615 |
-
return 0.
|
| 616 |
|
| 617 |
# Calculate variance of likelihood changes
|
| 618 |
likelihood_changes = [abs(original_likelihood - pl) for pl in perturbed_likelihoods]
|
| 619 |
-
change_variance = np.var(likelihood_changes) if len(likelihood_changes) > 1 else 0.0
|
| 620 |
|
| 621 |
-
|
| 622 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
|
| 624 |
return curvature_score
|
| 625 |
|
|
@@ -637,7 +778,7 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 637 |
|
| 638 |
if (len(chunk) > 50):
|
| 639 |
try:
|
| 640 |
-
chunk_likelihood = self._calculate_likelihood(chunk)
|
| 641 |
|
| 642 |
if (chunk_likelihood > 0):
|
| 643 |
# Generate a simple perturbation for this chunk
|
|
@@ -649,11 +790,12 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 649 |
indices_to_keep = np.random.choice(len(chunk_words), len(chunk_words) - delete_count, replace=False)
|
| 650 |
perturbed_chunk = ' '.join([chunk_words[i] for i in sorted(indices_to_keep)])
|
| 651 |
|
| 652 |
-
perturbed_likelihood = self._calculate_likelihood(perturbed_chunk)
|
| 653 |
|
| 654 |
if (perturbed_likelihood > 0):
|
| 655 |
stability = (chunk_likelihood - perturbed_likelihood) / chunk_likelihood
|
| 656 |
stabilities.append(min(1.0, max(0.0, stability)))
|
|
|
|
| 657 |
except Exception:
|
| 658 |
continue
|
| 659 |
|
|
@@ -662,7 +804,7 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 662 |
|
| 663 |
def _analyze_stability_patterns(self, features: Dict[str, Any]) -> tuple:
|
| 664 |
"""
|
| 665 |
-
Analyze MultiPerturbationStability patterns
|
| 666 |
"""
|
| 667 |
# Check feature validity first
|
| 668 |
required_features = ['stability_score', 'curvature_score', 'normalized_likelihood_ratio', 'stability_variance', 'perturbation_variance']
|
|
@@ -675,61 +817,76 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 675 |
|
| 676 |
|
| 677 |
# Initialize ai_indicator list
|
| 678 |
-
ai_indicators
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 679 |
|
| 680 |
# High stability score suggests AI (larger likelihood drops)
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
|
| 687 |
else:
|
| 688 |
-
ai_indicators.append(0.2)
|
| 689 |
|
| 690 |
# High curvature score suggests AI
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
else:
|
| 698 |
-
ai_indicators.append(0.2)
|
| 699 |
|
| 700 |
# High likelihood ratio suggests AI (original much more likely than perturbations)
|
| 701 |
-
|
| 702 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 703 |
|
| 704 |
-
elif (features['normalized_likelihood_ratio'] > 0.6):
|
| 705 |
-
ai_indicators.append(0.6)
|
| 706 |
-
|
| 707 |
else:
|
| 708 |
-
ai_indicators.append(0.3)
|
| 709 |
|
| 710 |
# Low stability variance suggests AI (consistent across chunks)
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
elif (features['stability_variance'] < 0.1):
|
| 715 |
-
ai_indicators.append(0.4)
|
| 716 |
-
|
| 717 |
-
else:
|
| 718 |
-
ai_indicators.append(0.2)
|
| 719 |
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
ai_indicators.append(0.6)
|
| 723 |
-
|
| 724 |
-
elif (features['perturbation_variance'] > 0.05):
|
| 725 |
-
ai_indicators.append(0.4)
|
| 726 |
|
| 727 |
else:
|
| 728 |
-
ai_indicators.append(0.2)
|
| 729 |
|
| 730 |
# Calculate raw score and confidence
|
| 731 |
-
|
| 732 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 733 |
confidence = max(0.1, min(0.9, confidence))
|
| 734 |
|
| 735 |
return raw_score, confidence
|
|
@@ -770,16 +927,16 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 770 |
|
| 771 |
def _get_default_features(self) -> Dict[str, Any]:
|
| 772 |
"""
|
| 773 |
-
Return default features
|
| 774 |
"""
|
| 775 |
return {"original_likelihood" : 2.0,
|
| 776 |
"avg_perturbed_likelihood" : 1.8,
|
| 777 |
"likelihood_ratio" : 1.1,
|
| 778 |
"normalized_likelihood_ratio" : 0.55,
|
| 779 |
-
"stability_score" : 0.
|
| 780 |
-
"curvature_score" : 0.
|
| 781 |
"perturbation_variance" : 0.05,
|
| 782 |
-
"avg_chunk_stability" : 0.
|
| 783 |
"stability_variance" : 0.1,
|
| 784 |
"num_perturbations" : 0,
|
| 785 |
"num_valid_perturbations" : 0,
|
|
@@ -814,14 +971,14 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 814 |
# Normalize whitespace
|
| 815 |
text = ' '.join(text.split())
|
| 816 |
|
| 817 |
-
#
|
| 818 |
if not text.endswith(('.', '!', '?')):
|
| 819 |
text += '.'
|
| 820 |
|
| 821 |
# Truncate to safe length
|
| 822 |
if (len(text) > 1000):
|
| 823 |
sentences = text.split('. ')
|
| 824 |
-
if len(sentences) > 1:
|
| 825 |
# Keep first few sentences
|
| 826 |
text = '. '.join(sentences[:3]) + '.'
|
| 827 |
|
|
@@ -831,50 +988,54 @@ class MultiPerturbationStabilityMetric(BaseMetric):
|
|
| 831 |
return text
|
| 832 |
|
| 833 |
|
| 834 |
-
def _configure_tokenizer_padding(self, tokenizer) -> Any:
|
| 835 |
-
"""
|
| 836 |
-
Configure tokenizer for proper padding
|
| 837 |
-
"""
|
| 838 |
-
if tokenizer.pad_token is None:
|
| 839 |
-
if tokenizer.eos_token is not None:
|
| 840 |
-
tokenizer.pad_token = tokenizer.eos_token
|
| 841 |
-
|
| 842 |
-
else:
|
| 843 |
-
tokenizer.add_special_tokens({'pad_token': '[PAD]'})
|
| 844 |
-
|
| 845 |
-
tokenizer.padding_side = "left"
|
| 846 |
-
|
| 847 |
-
return tokenizer
|
| 848 |
-
|
| 849 |
-
|
| 850 |
def _clean_roberta_token(self, token: str) -> str:
|
| 851 |
"""
|
| 852 |
-
Clean tokens from
|
| 853 |
"""
|
| 854 |
if not token:
|
| 855 |
return ""
|
| 856 |
|
| 857 |
-
# Remove
|
| 858 |
token = token.replace('Ġ', ' ') # RoBERTa space marker
|
| 859 |
token = token.replace('</s>', '')
|
| 860 |
token = token.replace('<s>', '')
|
| 861 |
token = token.replace('<pad>', '')
|
|
|
|
| 862 |
|
| 863 |
-
# Remove leading/trailing whitespace
|
| 864 |
-
token = token.strip(
|
| 865 |
|
| 866 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
|
| 868 |
|
| 869 |
def _is_valid_perturbation(self, perturbed_text: str, original_text: str) -> bool:
|
| 870 |
"""
|
| 871 |
-
Check if a perturbation is valid
|
| 872 |
"""
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
|
| 879 |
|
| 880 |
def cleanup(self):
|
|
|
|
| 59 |
self.gpt_model, self.gpt_tokenizer = gpt_result
|
| 60 |
# Move model to appropriate device
|
| 61 |
self.gpt_model.to(self.device)
|
| 62 |
+
logger.success("✓ GPT-2 model loaded for MultiPerturbationStability")
|
| 63 |
|
| 64 |
else:
|
| 65 |
logger.error("Failed to load GPT-2 model for MultiPerturbationStability")
|
|
|
|
| 77 |
if (self.mask_tokenizer.pad_token is None):
|
| 78 |
self.mask_tokenizer.pad_token = self.mask_tokenizer.eos_token or '[PAD]'
|
| 79 |
|
| 80 |
+
# Ensure tokenizer has mask token
|
| 81 |
+
if not hasattr(self.mask_tokenizer, 'mask_token') or self.mask_tokenizer.mask_token is None:
|
| 82 |
+
self.mask_tokenizer.mask_token = "<mask>"
|
| 83 |
+
|
| 84 |
+
logger.success("✓ DistilRoBERTa model loaded for MultiPerturbationStability")
|
| 85 |
+
|
| 86 |
else:
|
| 87 |
logger.warning("Failed to load mask model, using GPT-2 only")
|
| 88 |
|
| 89 |
+
# Verify model loading
|
| 90 |
+
if not self._verify_model_loading():
|
| 91 |
+
logger.error("Model verification failed")
|
| 92 |
+
return False
|
| 93 |
+
|
| 94 |
self.is_initialized = True
|
| 95 |
|
| 96 |
logger.success("MultiPerturbationStability metric initialized successfully")
|
|
|
|
| 101 |
return False
|
| 102 |
|
| 103 |
|
| 104 |
+
def _verify_model_loading(self) -> bool:
|
| 105 |
+
"""
|
| 106 |
+
Verify that models are properly loaded and working
|
| 107 |
+
"""
|
| 108 |
+
try:
|
| 109 |
+
test_text = "This is a test sentence for model verification."
|
| 110 |
+
|
| 111 |
+
# Test GPT-2 model
|
| 112 |
+
if self.gpt_model and self.gpt_tokenizer:
|
| 113 |
+
gpt_likelihood = self._calculate_likelihood(text = test_text)
|
| 114 |
+
logger.info(f"GPT-2 test - Likelihood: {gpt_likelihood:.4f}")
|
| 115 |
+
|
| 116 |
+
else:
|
| 117 |
+
logger.error("GPT-2 model not loaded")
|
| 118 |
+
return False
|
| 119 |
+
|
| 120 |
+
# Test DistilRoBERTa model if available
|
| 121 |
+
if self.mask_model and self.mask_tokenizer:
|
| 122 |
+
# Test mask token
|
| 123 |
+
if hasattr(self.mask_tokenizer, 'mask_token') and self.mask_tokenizer.mask_token:
|
| 124 |
+
logger.info(f"DistilRoBERTa mask token: '{self.mask_tokenizer.mask_token}'")
|
| 125 |
+
|
| 126 |
+
# Test basic tokenization
|
| 127 |
+
inputs = self.mask_tokenizer(test_text, return_tensors = "pt")
|
| 128 |
+
logger.info(f"DistilRoBERTa tokenization test - Input shape: {inputs['input_ids'].shape}")
|
| 129 |
+
|
| 130 |
+
else:
|
| 131 |
+
logger.warning("DistilRoBERTa mask token not available")
|
| 132 |
+
|
| 133 |
+
else:
|
| 134 |
+
logger.warning("DistilRoBERTa model not loaded")
|
| 135 |
+
|
| 136 |
+
return True
|
| 137 |
+
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"Model verification failed: {e}")
|
| 140 |
+
return False
|
| 141 |
+
|
| 142 |
+
|
| 143 |
def compute(self, text: str, **kwargs) -> MetricResult:
|
| 144 |
"""
|
| 145 |
Compute MultiPerturbationStability analysis with FULL DOMAIN THRESHOLD INTEGRATION
|
| 146 |
"""
|
| 147 |
try:
|
| 148 |
+
if ((not text) or (len(text.strip()) < 50)):
|
| 149 |
return MetricResult(metric_name = self.name,
|
| 150 |
ai_probability = 0.5,
|
| 151 |
human_probability = 0.5,
|
|
|
|
| 172 |
)
|
| 173 |
|
| 174 |
# Calculate MultiPerturbationStability features
|
| 175 |
+
features = self._calculate_stability_features(text = text)
|
| 176 |
|
| 177 |
# Calculate raw MultiPerturbationStability score (0-1 scale)
|
| 178 |
+
raw_stability_score, confidence = self._analyze_stability_patterns(features = features)
|
| 179 |
|
| 180 |
# Apply domain-specific thresholds to convert raw score to probabilities
|
| 181 |
+
ai_prob, human_prob, mixed_prob = self._apply_domain_thresholds(raw_score = raw_stability_score,
|
| 182 |
+
thresholds = multi_perturbation_stability_thresholds,
|
| 183 |
+
features = features,
|
| 184 |
+
)
|
| 185 |
|
| 186 |
# Apply confidence multiplier from domain thresholds
|
| 187 |
confidence *= multi_perturbation_stability_thresholds.confidence_multiplier
|
|
|
|
| 265 |
|
| 266 |
def _calculate_stability_features(self, text: str) -> Dict[str, Any]:
|
| 267 |
"""
|
| 268 |
+
Calculate comprehensive MultiPerturbationStability features with diagnostic logging
|
| 269 |
"""
|
| 270 |
if not self.gpt_model or not self.gpt_tokenizer:
|
| 271 |
return self._get_default_features()
|
| 272 |
|
| 273 |
try:
|
| 274 |
# Preprocess text for better analysis
|
| 275 |
+
processed_text = self._preprocess_text_for_analysis(text = text)
|
| 276 |
|
| 277 |
# Calculate original text likelihood
|
| 278 |
+
original_likelihood = self._calculate_likelihood(text = processed_text)
|
| 279 |
+
logger.debug(f"Original likelihood: {original_likelihood:.4f}")
|
| 280 |
|
| 281 |
# Generate perturbations and calculate perturbed likelihoods
|
| 282 |
+
perturbations = self._generate_perturbations(text = processed_text,
|
| 283 |
+
num_perturbations = 10,
|
| 284 |
+
)
|
| 285 |
+
logger.debug(f"Generated {len(perturbations)} perturbations")
|
| 286 |
+
|
| 287 |
perturbed_likelihoods = list()
|
| 288 |
|
| 289 |
+
for idx, perturbed_text in enumerate(perturbations):
|
| 290 |
if (perturbed_text and (perturbed_text != processed_text)):
|
| 291 |
+
likelihood = self._calculate_likelihood(text = perturbed_text)
|
| 292 |
|
| 293 |
if (likelihood > 0):
|
| 294 |
perturbed_likelihoods.append(likelihood)
|
| 295 |
+
logger.debug(f"Perturbation {idx}: likelihood={likelihood:.4f}")
|
| 296 |
+
|
| 297 |
+
logger.info(f"Valid perturbations: {len(perturbed_likelihoods)}/{len(perturbations)}")
|
| 298 |
|
| 299 |
# Calculate stability metrics
|
| 300 |
if perturbed_likelihoods:
|
| 301 |
+
stability_score = self._calculate_stability_score(original_likelihood = original_likelihood,
|
| 302 |
+
perturbed_likelihoods = perturbed_likelihoods,
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
curvature_score = self._calculate_curvature_score(original_likelihood = original_likelihood,
|
| 306 |
+
perturbed_likelihoods = perturbed_likelihoods,
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
variance_score = np.var(perturbed_likelihoods) if (len(perturbed_likelihoods) > 1) else 0.0
|
| 310 |
avg_perturbed_likelihood = np.mean(perturbed_likelihoods)
|
| 311 |
+
|
| 312 |
+
logger.info(f"Stability: {stability_score:.3f}, Curvature: {curvature_score:.3f}")
|
| 313 |
|
| 314 |
else:
|
| 315 |
+
# Use meaningful defaults when perturbations fail
|
| 316 |
+
stability_score = 0.3 # Assume more human-like when no perturbations work
|
| 317 |
+
curvature_score = 0.3
|
| 318 |
+
variance_score = 0.05
|
| 319 |
+
avg_perturbed_likelihood = original_likelihood * 0.9 # Assume some drop
|
| 320 |
+
logger.warning("No valid perturbations, using fallback values")
|
| 321 |
|
| 322 |
# Calculate likelihood ratio
|
| 323 |
+
likelihood_ratio = original_likelihood / avg_perturbed_likelihood if avg_perturbed_likelihood > 0 else 1.0
|
| 324 |
|
| 325 |
# Chunk-based analysis for whole-text understanding
|
| 326 |
+
chunk_stabilities = self._calculate_chunk_stability(text = processed_text,
|
| 327 |
+
chunk_size = 150,
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
stability_variance = np.var(chunk_stabilities) if chunk_stabilities else 0.1
|
| 331 |
+
avg_chunk_stability = np.mean(chunk_stabilities) if chunk_stabilities else stability_score
|
| 332 |
|
| 333 |
+
# Better normalization to prevent extreme values
|
| 334 |
+
normalized_stability = min(1.0, max(0.0, stability_score))
|
| 335 |
+
normalized_curvature = min(1.0, max(0.0, curvature_score))
|
| 336 |
+
normalized_likelihood_ratio = min(3.0, max(0.33, likelihood_ratio)) / 3.0
|
| 337 |
|
| 338 |
return {"original_likelihood" : round(original_likelihood, 4),
|
| 339 |
"avg_perturbed_likelihood" : round(avg_perturbed_likelihood, 4),
|
|
|
|
| 356 |
|
| 357 |
def _calculate_likelihood(self, text: str) -> float:
|
| 358 |
"""
|
| 359 |
+
Calculate proper log-likelihood using token probabilities
|
| 360 |
+
Inspired by DetectGPT's likelihood calculation approach
|
| 361 |
"""
|
| 362 |
try:
|
| 363 |
# Check text length before tokenization
|
| 364 |
if (len(text.strip()) < 10):
|
| 365 |
+
return 2.0 # Return reasonable baseline
|
| 366 |
+
|
| 367 |
+
if not self.gpt_model or not self.gpt_tokenizer:
|
| 368 |
+
logger.warning("GPT model not available for likelihood calculation")
|
| 369 |
+
return 2.0
|
| 370 |
|
| 371 |
+
# Ensure tokenizer has pad token
|
| 372 |
+
if self.gpt_tokenizer.pad_token is None:
|
| 373 |
+
self.gpt_tokenizer.pad_token = self.gpt_tokenizer.eos_token
|
| 374 |
|
| 375 |
# Tokenize text with proper settings
|
| 376 |
+
encodings = self.gpt_tokenizer(text,
|
| 377 |
+
return_tensors = 'pt',
|
| 378 |
+
truncation = True,
|
| 379 |
+
max_length = 256,
|
| 380 |
+
padding = True,
|
| 381 |
+
return_attention_mask = True,
|
| 382 |
+
)
|
| 383 |
|
| 384 |
input_ids = encodings.input_ids.to(self.device)
|
| 385 |
attention_mask = encodings.attention_mask.to(self.device)
|
| 386 |
|
| 387 |
# Minimum tokens for meaningful analysis
|
| 388 |
+
if ((input_ids.numel() == 0) or (input_ids.size(1) < 3)):
|
| 389 |
+
return 2.0
|
| 390 |
|
| 391 |
+
# Calculate proper log-likelihood using token probabilities
|
| 392 |
with torch.no_grad():
|
| 393 |
+
outputs = self.gpt_model(input_ids,
|
| 394 |
+
attention_mask = attention_mask,
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
logits = outputs.logits
|
| 398 |
+
|
| 399 |
+
# Calculate log probabilities for each token
|
| 400 |
+
log_probs = torch.nn.functional.log_softmax(logits, dim = -1)
|
| 401 |
+
|
| 402 |
+
# Get the log probability of each actual token
|
| 403 |
+
log_likelihood = 0.0
|
| 404 |
+
token_count = 0
|
| 405 |
+
|
| 406 |
+
for i in range(input_ids.size(1) - 1):
|
| 407 |
+
# Only consider non-padding tokens
|
| 408 |
+
if (attention_mask[0, i] == 1):
|
| 409 |
+
token_id = input_ids[0, i + 1] # Next token prediction
|
| 410 |
+
log_prob = log_probs[0, i, token_id]
|
| 411 |
+
log_likelihood += log_prob.item()
|
| 412 |
+
token_count += 1
|
| 413 |
+
|
| 414 |
+
# Normalize by token count to get average log likelihood per token
|
| 415 |
+
if (token_count > 0):
|
| 416 |
+
avg_log_likelihood = log_likelihood / token_count
|
| 417 |
|
| 418 |
+
else:
|
| 419 |
+
avg_log_likelihood = 0.0
|
| 420 |
|
| 421 |
+
# Convert to positive scale and normalize
|
| 422 |
+
# Typical GPT-2 log probabilities range from ~-10 to ~-2
|
| 423 |
+
# Higher normalized value = more likely text
|
| 424 |
+
normalized_likelihood = max(0.5, min(10.0, -avg_log_likelihood))
|
|
|
|
|
|
|
|
|
|
| 425 |
|
| 426 |
+
return normalized_likelihood
|
| 427 |
|
| 428 |
except Exception as e:
|
| 429 |
logger.warning(f"Likelihood calculation failed: {repr(e)}")
|
| 430 |
+
return 2.0 # Return reasonable baseline on error
|
| 431 |
|
| 432 |
|
| 433 |
def _generate_perturbations(self, text: str, num_perturbations: int = 5) -> List[str]:
|
| 434 |
"""
|
| 435 |
+
Generate perturbed versions of the text using multiple techniques:
|
| 436 |
+
1. Word deletion (simple but effective)
|
| 437 |
+
2. Word swapping (preserve meaning)
|
| 438 |
+
3. DistilRoBERTa masked prediction (DetectGPT-inspired, using lighter model than T5)
|
| 439 |
+
4. Synonym replacement (fallback)
|
| 440 |
"""
|
| 441 |
perturbations = list()
|
| 442 |
|
|
|
|
| 486 |
logger.debug(f"Word swapping perturbation failed: {e}")
|
| 487 |
continue
|
| 488 |
|
| 489 |
+
# Method 3: DistilRoBERTa-based masked word replacement (DetectGPT-inspired)
|
| 490 |
if (self.mask_model and self.mask_tokenizer and (len(words) > 4) and len(perturbations) < num_perturbations):
|
| 491 |
|
| 492 |
try:
|
| 493 |
+
roberta_perturbations = self._generate_roberta_masked_perturbations(text = processed_text,
|
| 494 |
+
words = words,
|
| 495 |
+
max_perturbations = num_perturbations - len(perturbations),
|
| 496 |
+
)
|
| 497 |
perturbations.extend(roberta_perturbations)
|
| 498 |
|
| 499 |
except Exception as e:
|
| 500 |
+
logger.warning(f"DistilRoBERTa masked perturbation failed: {repr(e)}")
|
| 501 |
|
| 502 |
# Method 4: Synonym replacement as fallback
|
| 503 |
if (len(perturbations) < num_perturbations):
|
| 504 |
try:
|
| 505 |
+
synonym_perturbations = self._generate_synonym_perturbations(text = processed_text,
|
| 506 |
+
words = words,
|
| 507 |
+
max_perturbations = num_perturbations - len(perturbations),
|
| 508 |
+
)
|
| 509 |
perturbations.extend(synonym_perturbations)
|
| 510 |
|
| 511 |
except Exception as e:
|
| 512 |
+
logger.debug(f"Synonym replacement failed: {repr(e)}")
|
| 513 |
|
| 514 |
# Ensure we have at least some perturbations
|
| 515 |
if not perturbations:
|
| 516 |
# Fallback: create simple variations
|
| 517 |
+
fallback_perturbations = self._generate_fallback_perturbations(text = processed_text,
|
| 518 |
+
words = words,
|
| 519 |
+
)
|
| 520 |
perturbations.extend(fallback_perturbations)
|
| 521 |
|
| 522 |
# Remove duplicates and ensure we don't exceed requested number
|
|
|
|
| 530 |
|
| 531 |
except Exception as e:
|
| 532 |
logger.warning(f"Perturbation generation failed: {repr(e)}")
|
| 533 |
+
return [text] # Return at least the original text as fallback
|
|
|
|
| 534 |
|
| 535 |
|
| 536 |
def _generate_roberta_masked_perturbations(self, text: str, words: List[str], max_perturbations: int) -> List[str]:
|
| 537 |
"""
|
| 538 |
+
Generate perturbations using DistilRoBERTa mask filling
|
| 539 |
+
This is inspired by DetectGPT but uses a lighter model (DistilRoBERTa instead of T5)
|
| 540 |
"""
|
| 541 |
perturbations = list()
|
| 542 |
|
| 543 |
try:
|
| 544 |
+
# Use the proper DistilRoBERTa mask token from tokenizer
|
| 545 |
+
if hasattr(self.mask_tokenizer, 'mask_token') and self.mask_tokenizer.mask_token:
|
| 546 |
+
roberta_mask_token = self.mask_tokenizer.mask_token
|
| 547 |
+
|
| 548 |
+
else:
|
| 549 |
+
roberta_mask_token = "<mask>" # Fallback
|
| 550 |
|
| 551 |
# Select words to mask (avoid very short words and punctuation)
|
| 552 |
candidate_positions = [i for i, word in enumerate(words) if (len(word) > 3) and word.isalpha() and word.lower() not in ['the', 'and', 'but', 'for', 'with']]
|
|
|
|
| 559 |
|
| 560 |
# Try multiple mask positions
|
| 561 |
attempts = min(max_perturbations * 2, len(candidate_positions))
|
| 562 |
+
positions_to_try = np.random.choice(candidate_positions, min(attempts, len(candidate_positions)), replace = False)
|
| 563 |
|
| 564 |
for pos in positions_to_try:
|
| 565 |
if (len(perturbations) >= max_perturbations):
|
|
|
|
| 572 |
masked_words[pos] = roberta_mask_token
|
| 573 |
masked_text = ' '.join(masked_words)
|
| 574 |
|
| 575 |
+
# DistilRoBERTa works better with proper sentence structure
|
| 576 |
if not masked_text.endswith(('.', '!', '?')):
|
| 577 |
masked_text += '.'
|
| 578 |
|
| 579 |
+
# Tokenize with DistilRoBERTa-specific settings
|
| 580 |
inputs = self.mask_tokenizer(masked_text,
|
| 581 |
return_tensors = "pt",
|
| 582 |
truncation = True,
|
| 583 |
+
max_length = min(128, self.mask_tokenizer.model_max_length),
|
| 584 |
padding = True,
|
| 585 |
)
|
| 586 |
|
|
|
|
| 619 |
|
| 620 |
if (self._is_valid_perturbation(new_text, text)):
|
| 621 |
perturbations.append(new_text)
|
| 622 |
+
break # Use first valid prediction
|
|
|
|
| 623 |
|
| 624 |
except Exception as e:
|
| 625 |
+
logger.debug(f"DistilRoBERTa mask filling failed for position {pos}: {e}")
|
| 626 |
continue
|
| 627 |
|
| 628 |
except Exception as e:
|
| 629 |
+
logger.warning(f"DistilRoBERTa masked perturbations failed: {e}")
|
| 630 |
|
| 631 |
return perturbations
|
| 632 |
|
|
|
|
| 669 |
perturbations.append(new_text)
|
| 670 |
|
| 671 |
except Exception as e:
|
| 672 |
+
logger.debug(f"Synonym replacement failed: {repr(e)}")
|
| 673 |
|
| 674 |
return perturbations
|
| 675 |
|
|
|
|
| 695 |
perturbations.append(text.capitalize())
|
| 696 |
|
| 697 |
except Exception as e:
|
| 698 |
+
logger.debug(f"Fallback perturbation failed: {repr(e)}")
|
| 699 |
|
| 700 |
return [p for p in perturbations if p and p != text][:3]
|
| 701 |
|
| 702 |
|
| 703 |
def _calculate_stability_score(self, original_likelihood: float, perturbed_likelihoods: List[float]) -> float:
|
| 704 |
"""
|
| 705 |
+
Calculate text stability score with improved normalization : AI text typically shows higher stability (larger drops) than human text
|
| 706 |
"""
|
| 707 |
if ((not perturbed_likelihoods) or (original_likelihood <= 0)):
|
| 708 |
+
# Assume more human-like when no data
|
| 709 |
+
return 0.3
|
| 710 |
|
| 711 |
+
# Calculate relative likelihood drops
|
| 712 |
+
relative_drops = list()
|
|
|
|
| 713 |
|
| 714 |
+
for pl in perturbed_likelihoods:
|
| 715 |
+
if (pl > 0):
|
| 716 |
+
# Use relative drop to handle scale differences
|
| 717 |
+
relative_drop = (original_likelihood - pl) / original_likelihood
|
| 718 |
+
|
| 719 |
+
# Clamp to [0, 1]
|
| 720 |
+
relative_drops.append(max(0.0, min(1.0, relative_drop)))
|
| 721 |
+
|
| 722 |
+
if not relative_drops:
|
| 723 |
+
return 0.3
|
| 724 |
+
|
| 725 |
+
avg_relative_drop = np.mean(relative_drops)
|
| 726 |
+
|
| 727 |
+
# Normalization based on empirical observations : AI text typically shows 20-60% drops, human text shows 10-30% drops
|
| 728 |
+
if (avg_relative_drop > 0.5):
|
| 729 |
+
# Strong AI indicator
|
| 730 |
+
stability_score = 0.9
|
| 731 |
|
| 732 |
+
elif (avg_relative_drop > 0.3):
|
| 733 |
+
# 0.6 to 0.9
|
| 734 |
+
stability_score = 0.6 + (avg_relative_drop - 0.3) * 1.5
|
| 735 |
+
|
| 736 |
+
elif (avg_relative_drop > 0.15):
|
| 737 |
+
# 0.3 to 0.6
|
| 738 |
+
stability_score = 0.3 + (avg_relative_drop - 0.15) * 2.0
|
| 739 |
+
|
| 740 |
+
else:
|
| 741 |
+
# 0.0 to 0.3
|
| 742 |
+
stability_score = avg_relative_drop * 2.0
|
| 743 |
+
|
| 744 |
+
return min(1.0, max(0.0, stability_score))
|
| 745 |
|
| 746 |
|
| 747 |
def _calculate_curvature_score(self, original_likelihood: float, perturbed_likelihoods: List[float]) -> float:
|
| 748 |
"""
|
| 749 |
+
Calculate likelihood curvature score with better scaling : Measures how "curved" the likelihood surface is around the text
|
| 750 |
"""
|
| 751 |
if ((not perturbed_likelihoods) or (original_likelihood <= 0)):
|
| 752 |
+
return 0.3
|
| 753 |
|
| 754 |
# Calculate variance of likelihood changes
|
| 755 |
likelihood_changes = [abs(original_likelihood - pl) for pl in perturbed_likelihoods]
|
|
|
|
| 756 |
|
| 757 |
+
if (len(likelihood_changes) < 2):
|
| 758 |
+
return 0.3
|
| 759 |
+
|
| 760 |
+
change_variance = np.var(likelihood_changes)
|
| 761 |
+
|
| 762 |
+
# Typical variance for meaningful analysis is around 0.1-0.5 : Adjusted scaling
|
| 763 |
+
curvature_score = min(1.0, change_variance * 3.0)
|
| 764 |
|
| 765 |
return curvature_score
|
| 766 |
|
|
|
|
| 778 |
|
| 779 |
if (len(chunk) > 50):
|
| 780 |
try:
|
| 781 |
+
chunk_likelihood = self._calculate_likelihood(text = chunk)
|
| 782 |
|
| 783 |
if (chunk_likelihood > 0):
|
| 784 |
# Generate a simple perturbation for this chunk
|
|
|
|
| 790 |
indices_to_keep = np.random.choice(len(chunk_words), len(chunk_words) - delete_count, replace=False)
|
| 791 |
perturbed_chunk = ' '.join([chunk_words[i] for i in sorted(indices_to_keep)])
|
| 792 |
|
| 793 |
+
perturbed_likelihood = self._calculate_likelihood(text = perturbed_chunk)
|
| 794 |
|
| 795 |
if (perturbed_likelihood > 0):
|
| 796 |
stability = (chunk_likelihood - perturbed_likelihood) / chunk_likelihood
|
| 797 |
stabilities.append(min(1.0, max(0.0, stability)))
|
| 798 |
+
|
| 799 |
except Exception:
|
| 800 |
continue
|
| 801 |
|
|
|
|
| 804 |
|
| 805 |
def _analyze_stability_patterns(self, features: Dict[str, Any]) -> tuple:
|
| 806 |
"""
|
| 807 |
+
Analyze MultiPerturbationStability patterns with better feature weighting
|
| 808 |
"""
|
| 809 |
# Check feature validity first
|
| 810 |
required_features = ['stability_score', 'curvature_score', 'normalized_likelihood_ratio', 'stability_variance', 'perturbation_variance']
|
|
|
|
| 817 |
|
| 818 |
|
| 819 |
# Initialize ai_indicator list
|
| 820 |
+
ai_indicators = list()
|
| 821 |
+
|
| 822 |
+
# Better weighting based on feature reliability
|
| 823 |
+
stability_weight = 0.3
|
| 824 |
+
curvature_weight = 0.25
|
| 825 |
+
ratio_weight = 0.25
|
| 826 |
+
variance_weight = 0.2
|
| 827 |
|
| 828 |
# High stability score suggests AI (larger likelihood drops)
|
| 829 |
+
stability = features['stability_score']
|
| 830 |
+
if (stability > 0.7):
|
| 831 |
+
ai_indicators.append(0.9 * stability_weight)
|
| 832 |
+
|
| 833 |
+
elif (stability > 0.5):
|
| 834 |
+
ai_indicators.append(0.7 * stability_weight)
|
| 835 |
+
|
| 836 |
+
elif (stability > 0.3):
|
| 837 |
+
ai_indicators.append(0.5 * stability_weight)
|
| 838 |
|
| 839 |
else:
|
| 840 |
+
ai_indicators.append(0.2 * stability_weight)
|
| 841 |
|
| 842 |
# High curvature score suggests AI
|
| 843 |
+
curvature = features['curvature_score']
|
| 844 |
+
if (curvature > 0.7):
|
| 845 |
+
ai_indicators.append(0.8 * curvature_weight)
|
| 846 |
+
|
| 847 |
+
elif (curvature > 0.5):
|
| 848 |
+
ai_indicators.append(0.6 * curvature_weight)
|
| 849 |
+
|
| 850 |
+
elif (curvature > 0.3):
|
| 851 |
+
ai_indicators.append(0.4 * curvature_weight)
|
| 852 |
+
|
| 853 |
else:
|
| 854 |
+
ai_indicators.append(0.2 * curvature_weight)
|
| 855 |
|
| 856 |
# High likelihood ratio suggests AI (original much more likely than perturbations)
|
| 857 |
+
ratio = features['normalized_likelihood_ratio']
|
| 858 |
+
if (ratio > 0.8):
|
| 859 |
+
ai_indicators.append(0.9 * ratio_weight)
|
| 860 |
+
|
| 861 |
+
elif (ratio > 0.6):
|
| 862 |
+
ai_indicators.append(0.7 * ratio_weight)
|
| 863 |
+
|
| 864 |
+
elif (ratio > 0.4):
|
| 865 |
+
ai_indicators.append(0.5 * ratio_weight)
|
| 866 |
|
|
|
|
|
|
|
|
|
|
| 867 |
else:
|
| 868 |
+
ai_indicators.append(0.3 * ratio_weight)
|
| 869 |
|
| 870 |
# Low stability variance suggests AI (consistent across chunks)
|
| 871 |
+
stability_var = features['stability_variance']
|
| 872 |
+
if (stability_var < 0.05):
|
| 873 |
+
ai_indicators.append(0.8 * variance_weight)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 874 |
|
| 875 |
+
elif (stability_var < 0.1):
|
| 876 |
+
ai_indicators.append(0.5 * variance_weight)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 877 |
|
| 878 |
else:
|
| 879 |
+
ai_indicators.append(0.2 * variance_weight)
|
| 880 |
|
| 881 |
# Calculate raw score and confidence
|
| 882 |
+
if ai_indicators:
|
| 883 |
+
raw_score = sum(ai_indicators)
|
| 884 |
+
confidence = 0.5 + (0.5 * (1.0 - (np.std([x / (weights := [stability_weight, curvature_weight, ratio_weight, variance_weight])[i] for i, x in enumerate(ai_indicators)]) if len(ai_indicators) > 1 else 0.5)))
|
| 885 |
+
|
| 886 |
+
else:
|
| 887 |
+
raw_score = 0.5
|
| 888 |
+
confidence = 0.3
|
| 889 |
+
|
| 890 |
confidence = max(0.1, min(0.9, confidence))
|
| 891 |
|
| 892 |
return raw_score, confidence
|
|
|
|
| 927 |
|
| 928 |
def _get_default_features(self) -> Dict[str, Any]:
|
| 929 |
"""
|
| 930 |
+
Return more meaningful default features
|
| 931 |
"""
|
| 932 |
return {"original_likelihood" : 2.0,
|
| 933 |
"avg_perturbed_likelihood" : 1.8,
|
| 934 |
"likelihood_ratio" : 1.1,
|
| 935 |
"normalized_likelihood_ratio" : 0.55,
|
| 936 |
+
"stability_score" : 0.3,
|
| 937 |
+
"curvature_score" : 0.3,
|
| 938 |
"perturbation_variance" : 0.05,
|
| 939 |
+
"avg_chunk_stability" : 0.3,
|
| 940 |
"stability_variance" : 0.1,
|
| 941 |
"num_perturbations" : 0,
|
| 942 |
"num_valid_perturbations" : 0,
|
|
|
|
| 971 |
# Normalize whitespace
|
| 972 |
text = ' '.join(text.split())
|
| 973 |
|
| 974 |
+
# DistilRoBERTa works better with proper punctuation
|
| 975 |
if not text.endswith(('.', '!', '?')):
|
| 976 |
text += '.'
|
| 977 |
|
| 978 |
# Truncate to safe length
|
| 979 |
if (len(text) > 1000):
|
| 980 |
sentences = text.split('. ')
|
| 981 |
+
if (len(sentences) > 1):
|
| 982 |
# Keep first few sentences
|
| 983 |
text = '. '.join(sentences[:3]) + '.'
|
| 984 |
|
|
|
|
| 988 |
return text
|
| 989 |
|
| 990 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 991 |
def _clean_roberta_token(self, token: str) -> str:
|
| 992 |
"""
|
| 993 |
+
Clean tokens from DistilRoBERTa tokenizer
|
| 994 |
"""
|
| 995 |
if not token:
|
| 996 |
return ""
|
| 997 |
|
| 998 |
+
# Remove DistilRoBERTa-specific artifacts
|
| 999 |
token = token.replace('Ġ', ' ') # RoBERTa space marker
|
| 1000 |
token = token.replace('</s>', '')
|
| 1001 |
token = token.replace('<s>', '')
|
| 1002 |
token = token.replace('<pad>', '')
|
| 1003 |
+
token = token.replace('<mask>', '')
|
| 1004 |
|
| 1005 |
+
# Remove leading/trailing whitespace
|
| 1006 |
+
token = token.strip()
|
| 1007 |
|
| 1008 |
+
# Only remove punctuation if token is ONLY punctuation
|
| 1009 |
+
if token and not token.replace('.', '').replace(',', '').replace('!', '').replace('?', '').strip():
|
| 1010 |
+
return ""
|
| 1011 |
+
|
| 1012 |
+
# Keep the token if it has at least 2 alphanumeric characters
|
| 1013 |
+
if sum(c.isalnum() for c in token) >= 2:
|
| 1014 |
+
return token
|
| 1015 |
+
|
| 1016 |
+
return ""
|
| 1017 |
|
| 1018 |
|
| 1019 |
def _is_valid_perturbation(self, perturbed_text: str, original_text: str) -> bool:
|
| 1020 |
"""
|
| 1021 |
+
Check if a perturbation is valid (more lenient validation)
|
| 1022 |
"""
|
| 1023 |
+
if (not perturbed_text or not perturbed_text.strip()):
|
| 1024 |
+
return False
|
| 1025 |
+
|
| 1026 |
+
# Must be different from original
|
| 1027 |
+
if (perturbed_text == original_text):
|
| 1028 |
+
return False
|
| 1029 |
+
|
| 1030 |
+
# Lenient length check
|
| 1031 |
+
if (len(perturbed_text) < len(original_text) * 0.3):
|
| 1032 |
+
return False
|
| 1033 |
+
|
| 1034 |
+
# Must have some actual content
|
| 1035 |
+
if len(perturbed_text.strip()) < 5:
|
| 1036 |
+
return False
|
| 1037 |
+
|
| 1038 |
+
return True
|
| 1039 |
|
| 1040 |
|
| 1041 |
def cleanup(self):
|
models/model_manager.py
CHANGED
|
@@ -21,6 +21,7 @@ from transformers import AutoTokenizer
|
|
| 21 |
from transformers import GPT2LMHeadModel
|
| 22 |
from config.model_config import ModelType
|
| 23 |
from config.model_config import ModelConfig
|
|
|
|
| 24 |
from transformers import AutoModelForMaskedLM
|
| 25 |
from config.model_config import MODEL_REGISTRY
|
| 26 |
from config.model_config import get_model_config
|
|
@@ -237,6 +238,12 @@ class ModelManager:
|
|
| 237 |
elif (model_config.model_type == ModelType.TRANSFORMER):
|
| 238 |
model = self._load_transformer(config = model_config)
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
elif (model_config.model_type == ModelType.RULE_BASED):
|
| 241 |
# Check if it's a spaCy model
|
| 242 |
if model_config.additional_params.get("is_spacy_model", False):
|
|
@@ -288,7 +295,13 @@ class ModelManager:
|
|
| 288 |
logger.info(f"Loading tokenizer for: {model_name}")
|
| 289 |
|
| 290 |
try:
|
| 291 |
-
if (model_config.model_type in [ModelType.GPT,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path = model_config.model_id,
|
| 293 |
cache_dir = str(self.cache_dir),
|
| 294 |
)
|
|
@@ -339,6 +352,54 @@ class ModelManager:
|
|
| 339 |
return (model, tokenizer)
|
| 340 |
|
| 341 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
def _load_classifier(self, config: ModelConfig) -> Any:
|
| 343 |
"""
|
| 344 |
Load classification model (for zero-shot, etc.)
|
|
@@ -483,7 +544,7 @@ class ModelManager:
|
|
| 483 |
logger.info(f"Downloading model: {model_name} ({model_config.model_id})")
|
| 484 |
|
| 485 |
try:
|
| 486 |
-
if model_config.model_type == ModelType.SENTENCE_TRANSFORMER:
|
| 487 |
SentenceTransformer(model_name_or_path = model_config.model_id,
|
| 488 |
cache_folder = str(self.cache_dir),
|
| 489 |
)
|
|
@@ -506,6 +567,24 @@ class ModelManager:
|
|
| 506 |
cache_dir = str(self.cache_dir),
|
| 507 |
)
|
| 508 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
elif (model_config.model_type == ModelType.RULE_BASED):
|
| 510 |
if model_config.additional_params.get("is_spacy_model", False):
|
| 511 |
subprocess.run(["python", "-m", "spacy", "download", model_config.model_id], check = True)
|
|
|
|
| 21 |
from transformers import GPT2LMHeadModel
|
| 22 |
from config.model_config import ModelType
|
| 23 |
from config.model_config import ModelConfig
|
| 24 |
+
from transformers import AutoModelForCausalLM
|
| 25 |
from transformers import AutoModelForMaskedLM
|
| 26 |
from config.model_config import MODEL_REGISTRY
|
| 27 |
from config.model_config import get_model_config
|
|
|
|
| 238 |
elif (model_config.model_type == ModelType.TRANSFORMER):
|
| 239 |
model = self._load_transformer(config = model_config)
|
| 240 |
|
| 241 |
+
elif (model_config.model_type == ModelType.CAUSAL_LM):
|
| 242 |
+
model = self._load_causal_lm(config = model_config)
|
| 243 |
+
|
| 244 |
+
elif (model_config.model_type == ModelType.MASKED_LM):
|
| 245 |
+
model = self._load_masked_lm(config = model_config)
|
| 246 |
+
|
| 247 |
elif (model_config.model_type == ModelType.RULE_BASED):
|
| 248 |
# Check if it's a spaCy model
|
| 249 |
if model_config.additional_params.get("is_spacy_model", False):
|
|
|
|
| 295 |
logger.info(f"Loading tokenizer for: {model_name}")
|
| 296 |
|
| 297 |
try:
|
| 298 |
+
if (model_config.model_type in [ModelType.GPT,
|
| 299 |
+
ModelType.CLASSIFIER,
|
| 300 |
+
ModelType.SEQUENCE_CLASSIFICATION,
|
| 301 |
+
ModelType.TRANSFORMER,
|
| 302 |
+
ModelType.CAUSAL_LM,
|
| 303 |
+
ModelType.MASKED_LM]):
|
| 304 |
+
|
| 305 |
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path = model_config.model_id,
|
| 306 |
cache_dir = str(self.cache_dir),
|
| 307 |
)
|
|
|
|
| 352 |
return (model, tokenizer)
|
| 353 |
|
| 354 |
|
| 355 |
+
def _load_causal_lm(self, config: ModelConfig) -> tuple:
|
| 356 |
+
"""
|
| 357 |
+
Load causal language model (like GPT-2) for text generation
|
| 358 |
+
"""
|
| 359 |
+
model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path = config.model_id,
|
| 360 |
+
cache_dir = str(self.cache_dir),
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path = config.model_id,
|
| 364 |
+
cache_dir = str(self.cache_dir),
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
# Move to device
|
| 368 |
+
model = model.to(self.device)
|
| 369 |
+
|
| 370 |
+
model.eval()
|
| 371 |
+
|
| 372 |
+
# Apply quantization if enabled
|
| 373 |
+
if (settings.USE_QUANTIZATION and config.quantizable):
|
| 374 |
+
model = self._quantize_model(model = model)
|
| 375 |
+
|
| 376 |
+
return (model, tokenizer)
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def _load_masked_lm(self, config: ModelConfig) -> tuple:
|
| 380 |
+
"""
|
| 381 |
+
Load masked language model (like RoBERTa) for fill-mask tasks
|
| 382 |
+
"""
|
| 383 |
+
model = AutoModelForMaskedLM.from_pretrained(pretrained_model_name_or_path = config.model_id,
|
| 384 |
+
cache_dir = str(self.cache_dir),
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
tokenizer = AutoTokenizer.from_pretrained(pretrained_model_name_or_path = config.model_id,
|
| 388 |
+
cache_dir = str(self.cache_dir),
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
# Move to device
|
| 392 |
+
model = model.to(self.device)
|
| 393 |
+
|
| 394 |
+
model.eval()
|
| 395 |
+
|
| 396 |
+
# Apply quantization if enabled
|
| 397 |
+
if (settings.USE_QUANTIZATION and config.quantizable):
|
| 398 |
+
model = self._quantize_model(model = model)
|
| 399 |
+
|
| 400 |
+
return (model, tokenizer)
|
| 401 |
+
|
| 402 |
+
|
| 403 |
def _load_classifier(self, config: ModelConfig) -> Any:
|
| 404 |
"""
|
| 405 |
Load classification model (for zero-shot, etc.)
|
|
|
|
| 544 |
logger.info(f"Downloading model: {model_name} ({model_config.model_id})")
|
| 545 |
|
| 546 |
try:
|
| 547 |
+
if (model_config.model_type == ModelType.SENTENCE_TRANSFORMER):
|
| 548 |
SentenceTransformer(model_name_or_path = model_config.model_id,
|
| 549 |
cache_folder = str(self.cache_dir),
|
| 550 |
)
|
|
|
|
| 567 |
cache_dir = str(self.cache_dir),
|
| 568 |
)
|
| 569 |
|
| 570 |
+
elif (model_config.model_type == ModelType.CAUSAL_LM):
|
| 571 |
+
AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path = model_config.model_id,
|
| 572 |
+
cache_dir = str(self.cache_dir),
|
| 573 |
+
)
|
| 574 |
+
|
| 575 |
+
AutoTokenizer.from_pretrained(pretrained_model_name_or_path = model_config.model_id,
|
| 576 |
+
cache_dir = str(self.cache_dir),
|
| 577 |
+
)
|
| 578 |
+
|
| 579 |
+
elif (model_config.model_type == ModelType.MASKED_LM):
|
| 580 |
+
AutoModelForMaskedLM.from_pretrained(pretrained_model_name_or_path = model_config.model_id,
|
| 581 |
+
cache_dir = str(self.cache_dir),
|
| 582 |
+
)
|
| 583 |
+
|
| 584 |
+
AutoTokenizer.from_pretrained(pretrained_model_name_or_path = model_config.model_id,
|
| 585 |
+
cache_dir = str(self.cache_dir),
|
| 586 |
+
)
|
| 587 |
+
|
| 588 |
elif (model_config.model_type == ModelType.RULE_BASED):
|
| 589 |
if model_config.additional_params.get("is_spacy_model", False):
|
| 590 |
subprocess.run(["python", "-m", "spacy", "download", model_config.model_id], check = True)
|
reporter/report_generator.py
CHANGED
|
@@ -79,6 +79,9 @@ class ReportGenerator:
|
|
| 79 |
--------
|
| 80 |
{ dict } : Dictionary mapping format to filepath
|
| 81 |
"""
|
|
|
|
|
|
|
|
|
|
| 82 |
# Generate detailed reasoning
|
| 83 |
reasoning = self.reasoning_generator.generate(ensemble_result = detection_result.ensemble_result,
|
| 84 |
metric_results = detection_result.metric_results,
|
|
@@ -88,7 +91,7 @@ class ReportGenerator:
|
|
| 88 |
)
|
| 89 |
|
| 90 |
# Extract detailed metrics from ACTUAL detection results
|
| 91 |
-
detailed_metrics = self._extract_detailed_metrics(
|
| 92 |
|
| 93 |
# Timestamp for filenames
|
| 94 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
@@ -97,7 +100,7 @@ class ReportGenerator:
|
|
| 97 |
|
| 98 |
# Generate requested formats
|
| 99 |
if ("json" in formats):
|
| 100 |
-
json_path = self._generate_json_report(
|
| 101 |
reasoning = reasoning,
|
| 102 |
detailed_metrics = detailed_metrics,
|
| 103 |
attribution_result = attribution_result,
|
|
@@ -108,7 +111,7 @@ class ReportGenerator:
|
|
| 108 |
|
| 109 |
if ("pdf" in formats):
|
| 110 |
try:
|
| 111 |
-
pdf_path = self._generate_pdf_report(
|
| 112 |
reasoning = reasoning,
|
| 113 |
detailed_metrics = detailed_metrics,
|
| 114 |
attribution_result = attribution_result,
|
|
@@ -126,26 +129,29 @@ class ReportGenerator:
|
|
| 126 |
return generated_files
|
| 127 |
|
| 128 |
|
| 129 |
-
def _extract_detailed_metrics(self,
|
| 130 |
"""
|
| 131 |
Extract detailed metrics with sub-metrics from ACTUAL detection result
|
| 132 |
"""
|
| 133 |
detailed_metrics = list()
|
| 134 |
-
|
| 135 |
-
|
| 136 |
|
| 137 |
# Get actual metric weights from ensemble
|
| 138 |
-
metric_weights =
|
| 139 |
|
| 140 |
# Extract actual metric data
|
| 141 |
-
for metric_name, metric_result in
|
| 142 |
-
if metric_result
|
|
|
|
|
|
|
|
|
|
| 143 |
continue
|
| 144 |
|
| 145 |
# Get actual probabilities and confidence
|
| 146 |
-
ai_prob = metric_result.ai_probability * 100
|
| 147 |
-
human_prob = metric_result.human_probability * 100
|
| 148 |
-
confidence = metric_result.confidence * 100
|
| 149 |
|
| 150 |
# Determine verdict based on actual probability
|
| 151 |
if (ai_prob >= 60):
|
|
@@ -158,7 +164,9 @@ class ReportGenerator:
|
|
| 158 |
verdict = "MIXED (AI + HUMAN)"
|
| 159 |
|
| 160 |
# Get actual weight or use default
|
| 161 |
-
weight
|
|
|
|
|
|
|
| 162 |
|
| 163 |
# Extract actual detailed metrics from metric result
|
| 164 |
detailed_metrics_data = self._extract_metric_details(metric_name = metric_name,
|
|
@@ -182,22 +190,22 @@ class ReportGenerator:
|
|
| 182 |
return detailed_metrics
|
| 183 |
|
| 184 |
|
| 185 |
-
def _extract_metric_details(self, metric_name: str, metric_result) -> Dict[str, float]:
|
| 186 |
"""
|
| 187 |
Extract detailed sub-metrics from metric result
|
| 188 |
"""
|
| 189 |
details = dict()
|
| 190 |
|
| 191 |
# Try to get details from metric result
|
| 192 |
-
if
|
| 193 |
-
details = metric_result
|
| 194 |
|
| 195 |
# If no details available, provide basic calculated values
|
| 196 |
if not details:
|
| 197 |
-
details = {"ai_probability" : metric_result.ai_probability * 100,
|
| 198 |
-
"human_probability" : metric_result.human_probability * 100,
|
| 199 |
-
"confidence" : metric_result.confidence * 100,
|
| 200 |
-
"score" :
|
| 201 |
}
|
| 202 |
|
| 203 |
return details
|
|
@@ -218,7 +226,7 @@ class ReportGenerator:
|
|
| 218 |
return descriptions.get(metric_name, "Advanced text analysis metric.")
|
| 219 |
|
| 220 |
|
| 221 |
-
def _generate_json_report(self,
|
| 222 |
attribution_result: Optional[AttributionResult], highlighted_sentences: Optional[List] = None, filename: str = None) -> Path:
|
| 223 |
"""
|
| 224 |
Generate JSON format report with detailed metrics
|
|
@@ -251,7 +259,7 @@ class ReportGenerator:
|
|
| 251 |
"index" : sent.index,
|
| 252 |
})
|
| 253 |
|
| 254 |
-
# Attribution data
|
| 255 |
attribution_data = None
|
| 256 |
|
| 257 |
if attribution_result:
|
|
@@ -264,30 +272,32 @@ class ReportGenerator:
|
|
| 264 |
"metric_contributions": attribution_result.metric_contributions,
|
| 265 |
}
|
| 266 |
|
| 267 |
-
# Use ACTUAL detection results
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
report_data = {"report_metadata" : {"generated_at" : datetime.now().isoformat(),
|
| 271 |
"version" : "1.0.0",
|
| 272 |
"format" : "json",
|
| 273 |
"report_id" : filename.replace('.json', ''),
|
| 274 |
},
|
| 275 |
-
"overall_results" : {"final_verdict" :
|
| 276 |
-
"ai_probability" :
|
| 277 |
-
"human_probability" :
|
| 278 |
-
"mixed_probability" :
|
| 279 |
-
"overall_confidence" :
|
| 280 |
-
"uncertainty_score" :
|
| 281 |
-
"consensus_level" :
|
| 282 |
-
"domain" :
|
| 283 |
-
"domain_confidence" :
|
| 284 |
-
"text_length" :
|
| 285 |
-
"sentence_count" :
|
| 286 |
},
|
| 287 |
"ensemble_analysis" : {"method_used" : "confidence_calibrated",
|
| 288 |
-
"metric_weights" :
|
| 289 |
-
"
|
| 290 |
-
"reasoning" : ensemble_result.reasoning,
|
| 291 |
},
|
| 292 |
"detailed_metrics" : metrics_data,
|
| 293 |
"detection_reasoning" : {"summary" : reasoning.summary,
|
|
@@ -303,10 +313,10 @@ class ReportGenerator:
|
|
| 303 |
},
|
| 304 |
"highlighted_text" : highlighted_data,
|
| 305 |
"model_attribution" : attribution_data,
|
| 306 |
-
"performance_metrics" : {"total_processing_time" :
|
| 307 |
-
"metrics_execution_time" :
|
| 308 |
-
"warnings" :
|
| 309 |
-
"errors" :
|
| 310 |
}
|
| 311 |
}
|
| 312 |
|
|
@@ -323,7 +333,7 @@ class ReportGenerator:
|
|
| 323 |
return output_path
|
| 324 |
|
| 325 |
|
| 326 |
-
def _generate_pdf_report(self,
|
| 327 |
attribution_result: Optional[AttributionResult], highlighted_sentences: Optional[List] = None, filename: str = None) -> Path:
|
| 328 |
"""
|
| 329 |
Generate PDF format report with detailed metrics
|
|
@@ -378,8 +388,9 @@ class ReportGenerator:
|
|
| 378 |
spaceAfter = 8,
|
| 379 |
)
|
| 380 |
|
| 381 |
-
# Use detection results
|
| 382 |
-
|
|
|
|
| 383 |
|
| 384 |
# Title and main sections
|
| 385 |
elements.append(Paragraph("AI Text Detection Analysis Report", title_style))
|
|
@@ -388,13 +399,13 @@ class ReportGenerator:
|
|
| 388 |
|
| 389 |
# Verdict section with ensemble metrics
|
| 390 |
elements.append(Paragraph("Detection Summary", heading_style))
|
| 391 |
-
verdict_data = [['Final Verdict:',
|
| 392 |
-
['AI Probability:', f"{
|
| 393 |
-
['Human Probability:', f"{
|
| 394 |
-
['Mixed Probability:', f"{
|
| 395 |
-
['Overall Confidence:', f"{
|
| 396 |
-
['Uncertainty Score:', f"{
|
| 397 |
-
['Consensus Level:', f"{
|
| 398 |
]
|
| 399 |
|
| 400 |
verdict_table = Table(verdict_data, colWidths=[2*inch, 3*inch])
|
|
@@ -410,11 +421,11 @@ class ReportGenerator:
|
|
| 410 |
|
| 411 |
# Content analysis
|
| 412 |
elements.append(Paragraph("Content Analysis", heading_style))
|
| 413 |
-
content_data = [['Content Domain:',
|
| 414 |
-
['Domain Confidence:', f"{
|
| 415 |
-
['Word Count:', str(
|
| 416 |
-
['Sentence Count:', str(
|
| 417 |
-
['Processing Time:', f"{
|
| 418 |
]
|
| 419 |
|
| 420 |
content_table = Table(content_data, colWidths=[2*inch, 3*inch])
|
|
@@ -428,14 +439,16 @@ class ReportGenerator:
|
|
| 428 |
|
| 429 |
# Ensemble Analysis
|
| 430 |
elements.append(Paragraph("Ensemble Analysis", heading_style))
|
| 431 |
-
elements.append(Paragraph(
|
| 432 |
elements.append(Spacer(1, 0.1*inch))
|
| 433 |
|
| 434 |
# Metric weights table
|
| 435 |
-
|
|
|
|
| 436 |
elements.append(Paragraph("Metric Weights", styles['Heading3']))
|
| 437 |
weight_data = [['Metric', 'Weight']]
|
| 438 |
-
for metric,
|
|
|
|
| 439 |
weight_data.append([metric.title(), f"{weight:.1%}"])
|
| 440 |
|
| 441 |
weight_table = Table(weight_data, colWidths=[3*inch, 1*inch])
|
|
@@ -578,8 +591,8 @@ class ReportGenerator:
|
|
| 578 |
|
| 579 |
# Footer
|
| 580 |
elements.append(Spacer(1, 0.3*inch))
|
| 581 |
-
elements.append(Paragraph(f"Generated by AI Text Detector v2.0 | Processing Time: {
|
| 582 |
-
|
| 583 |
|
| 584 |
# Build PDF
|
| 585 |
doc.build(elements)
|
|
|
|
| 79 |
--------
|
| 80 |
{ dict } : Dictionary mapping format to filepath
|
| 81 |
"""
|
| 82 |
+
# Convert DetectionResult to dict for consistent access
|
| 83 |
+
detection_dict = detection_result.to_dict() if hasattr(detection_result, 'to_dict') else detection_result
|
| 84 |
+
|
| 85 |
# Generate detailed reasoning
|
| 86 |
reasoning = self.reasoning_generator.generate(ensemble_result = detection_result.ensemble_result,
|
| 87 |
metric_results = detection_result.metric_results,
|
|
|
|
| 91 |
)
|
| 92 |
|
| 93 |
# Extract detailed metrics from ACTUAL detection results
|
| 94 |
+
detailed_metrics = self._extract_detailed_metrics(detection_dict)
|
| 95 |
|
| 96 |
# Timestamp for filenames
|
| 97 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
| 100 |
|
| 101 |
# Generate requested formats
|
| 102 |
if ("json" in formats):
|
| 103 |
+
json_path = self._generate_json_report(detection_dict = detection_dict,
|
| 104 |
reasoning = reasoning,
|
| 105 |
detailed_metrics = detailed_metrics,
|
| 106 |
attribution_result = attribution_result,
|
|
|
|
| 111 |
|
| 112 |
if ("pdf" in formats):
|
| 113 |
try:
|
| 114 |
+
pdf_path = self._generate_pdf_report(detection_dict = detection_dict,
|
| 115 |
reasoning = reasoning,
|
| 116 |
detailed_metrics = detailed_metrics,
|
| 117 |
attribution_result = attribution_result,
|
|
|
|
| 129 |
return generated_files
|
| 130 |
|
| 131 |
|
| 132 |
+
def _extract_detailed_metrics(self, detection_dict: Dict) -> List[DetailedMetric]:
|
| 133 |
"""
|
| 134 |
Extract detailed metrics with sub-metrics from ACTUAL detection result
|
| 135 |
"""
|
| 136 |
detailed_metrics = list()
|
| 137 |
+
metrics_data = detection_dict.get("metrics", {})
|
| 138 |
+
ensemble_data = detection_dict.get("ensemble", {})
|
| 139 |
|
| 140 |
# Get actual metric weights from ensemble
|
| 141 |
+
metric_weights = ensemble_data.get("metric_contributions", {})
|
| 142 |
|
| 143 |
# Extract actual metric data
|
| 144 |
+
for metric_name, metric_result in metrics_data.items():
|
| 145 |
+
if not isinstance(metric_result, dict):
|
| 146 |
+
continue
|
| 147 |
+
|
| 148 |
+
if metric_result.get("error") is not None:
|
| 149 |
continue
|
| 150 |
|
| 151 |
# Get actual probabilities and confidence
|
| 152 |
+
ai_prob = metric_result.get("ai_probability", 0) * 100
|
| 153 |
+
human_prob = metric_result.get("human_probability", 0) * 100
|
| 154 |
+
confidence = metric_result.get("confidence", 0) * 100
|
| 155 |
|
| 156 |
# Determine verdict based on actual probability
|
| 157 |
if (ai_prob >= 60):
|
|
|
|
| 164 |
verdict = "MIXED (AI + HUMAN)"
|
| 165 |
|
| 166 |
# Get actual weight or use default
|
| 167 |
+
weight = 0.0
|
| 168 |
+
if metric_name in metric_weights:
|
| 169 |
+
weight = metric_weights[metric_name].get("weight", 0.0) * 100
|
| 170 |
|
| 171 |
# Extract actual detailed metrics from metric result
|
| 172 |
detailed_metrics_data = self._extract_metric_details(metric_name = metric_name,
|
|
|
|
| 190 |
return detailed_metrics
|
| 191 |
|
| 192 |
|
| 193 |
+
def _extract_metric_details(self, metric_name: str, metric_result: Dict) -> Dict[str, float]:
|
| 194 |
"""
|
| 195 |
Extract detailed sub-metrics from metric result
|
| 196 |
"""
|
| 197 |
details = dict()
|
| 198 |
|
| 199 |
# Try to get details from metric result
|
| 200 |
+
if metric_result.get("details"):
|
| 201 |
+
details = metric_result["details"].copy()
|
| 202 |
|
| 203 |
# If no details available, provide basic calculated values
|
| 204 |
if not details:
|
| 205 |
+
details = {"ai_probability" : metric_result.get("ai_probability", 0) * 100,
|
| 206 |
+
"human_probability" : metric_result.get("human_probability", 0) * 100,
|
| 207 |
+
"confidence" : metric_result.get("confidence", 0) * 100,
|
| 208 |
+
"score" : metric_result.get("score", 0) * 100,
|
| 209 |
}
|
| 210 |
|
| 211 |
return details
|
|
|
|
| 226 |
return descriptions.get(metric_name, "Advanced text analysis metric.")
|
| 227 |
|
| 228 |
|
| 229 |
+
def _generate_json_report(self, detection_dict: Dict, reasoning: DetailedReasoning, detailed_metrics: List[DetailedMetric],
|
| 230 |
attribution_result: Optional[AttributionResult], highlighted_sentences: Optional[List] = None, filename: str = None) -> Path:
|
| 231 |
"""
|
| 232 |
Generate JSON format report with detailed metrics
|
|
|
|
| 259 |
"index" : sent.index,
|
| 260 |
})
|
| 261 |
|
| 262 |
+
# Attribution data
|
| 263 |
attribution_data = None
|
| 264 |
|
| 265 |
if attribution_result:
|
|
|
|
| 272 |
"metric_contributions": attribution_result.metric_contributions,
|
| 273 |
}
|
| 274 |
|
| 275 |
+
# Use ACTUAL detection results from dictionary
|
| 276 |
+
ensemble_data = detection_dict.get("ensemble", {})
|
| 277 |
+
analysis_data = detection_dict.get("analysis", {})
|
| 278 |
+
metrics_data_dict = detection_dict.get("metrics", {})
|
| 279 |
+
performance_data = detection_dict.get("performance", {})
|
| 280 |
|
| 281 |
report_data = {"report_metadata" : {"generated_at" : datetime.now().isoformat(),
|
| 282 |
"version" : "1.0.0",
|
| 283 |
"format" : "json",
|
| 284 |
"report_id" : filename.replace('.json', ''),
|
| 285 |
},
|
| 286 |
+
"overall_results" : {"final_verdict" : ensemble_data.get("final_verdict", "Unknown"),
|
| 287 |
+
"ai_probability" : ensemble_data.get("ai_probability", 0),
|
| 288 |
+
"human_probability" : ensemble_data.get("human_probability", 0),
|
| 289 |
+
"mixed_probability" : ensemble_data.get("mixed_probability", 0),
|
| 290 |
+
"overall_confidence" : ensemble_data.get("overall_confidence", 0),
|
| 291 |
+
"uncertainty_score" : ensemble_data.get("uncertainty_score", 0),
|
| 292 |
+
"consensus_level" : ensemble_data.get("consensus_level", 0),
|
| 293 |
+
"domain" : analysis_data.get("domain", "general"),
|
| 294 |
+
"domain_confidence" : analysis_data.get("domain_confidence", 0),
|
| 295 |
+
"text_length" : analysis_data.get("text_length", 0),
|
| 296 |
+
"sentence_count" : analysis_data.get("sentence_count", 0),
|
| 297 |
},
|
| 298 |
"ensemble_analysis" : {"method_used" : "confidence_calibrated",
|
| 299 |
+
"metric_weights" : ensemble_data.get("metric_contributions", {}),
|
| 300 |
+
"reasoning" : ensemble_data.get("reasoning", []),
|
|
|
|
| 301 |
},
|
| 302 |
"detailed_metrics" : metrics_data,
|
| 303 |
"detection_reasoning" : {"summary" : reasoning.summary,
|
|
|
|
| 313 |
},
|
| 314 |
"highlighted_text" : highlighted_data,
|
| 315 |
"model_attribution" : attribution_data,
|
| 316 |
+
"performance_metrics" : {"total_processing_time" : performance_data.get("total_time", 0),
|
| 317 |
+
"metrics_execution_time" : performance_data.get("metrics_time", {}),
|
| 318 |
+
"warnings" : detection_dict.get("warnings", []),
|
| 319 |
+
"errors" : detection_dict.get("errors", []),
|
| 320 |
}
|
| 321 |
}
|
| 322 |
|
|
|
|
| 333 |
return output_path
|
| 334 |
|
| 335 |
|
| 336 |
+
def _generate_pdf_report(self, detection_dict: Dict, reasoning: DetailedReasoning, detailed_metrics: List[DetailedMetric],
|
| 337 |
attribution_result: Optional[AttributionResult], highlighted_sentences: Optional[List] = None, filename: str = None) -> Path:
|
| 338 |
"""
|
| 339 |
Generate PDF format report with detailed metrics
|
|
|
|
| 388 |
spaceAfter = 8,
|
| 389 |
)
|
| 390 |
|
| 391 |
+
# Use detection results from dictionary
|
| 392 |
+
ensemble_data = detection_dict.get("ensemble", {})
|
| 393 |
+
analysis_data = detection_dict.get("analysis", {})
|
| 394 |
|
| 395 |
# Title and main sections
|
| 396 |
elements.append(Paragraph("AI Text Detection Analysis Report", title_style))
|
|
|
|
| 399 |
|
| 400 |
# Verdict section with ensemble metrics
|
| 401 |
elements.append(Paragraph("Detection Summary", heading_style))
|
| 402 |
+
verdict_data = [['Final Verdict:', ensemble_data.get("final_verdict", "Unknown")],
|
| 403 |
+
['AI Probability:', f"{ensemble_data.get('ai_probability', 0):.1%}"],
|
| 404 |
+
['Human Probability:', f"{ensemble_data.get('human_probability', 0):.1%}"],
|
| 405 |
+
['Mixed Probability:', f"{ensemble_data.get('mixed_probability', 0):.1%}"],
|
| 406 |
+
['Overall Confidence:', f"{ensemble_data.get('overall_confidence', 0):.1%}"],
|
| 407 |
+
['Uncertainty Score:', f"{ensemble_data.get('uncertainty_score', 0):.1%}"],
|
| 408 |
+
['Consensus Level:', f"{ensemble_data.get('consensus_level', 0):.1%}"],
|
| 409 |
]
|
| 410 |
|
| 411 |
verdict_table = Table(verdict_data, colWidths=[2*inch, 3*inch])
|
|
|
|
| 421 |
|
| 422 |
# Content analysis
|
| 423 |
elements.append(Paragraph("Content Analysis", heading_style))
|
| 424 |
+
content_data = [['Content Domain:', analysis_data.get("domain", "general").title()],
|
| 425 |
+
['Domain Confidence:', f"{analysis_data.get('domain_confidence', 0):.1%}"],
|
| 426 |
+
['Word Count:', str(analysis_data.get("text_length", 0))],
|
| 427 |
+
['Sentence Count:', str(analysis_data.get("sentence_count", 0))],
|
| 428 |
+
['Processing Time:', f"{detection_dict.get('performance', {}).get('total_time', 0):.2f}s"],
|
| 429 |
]
|
| 430 |
|
| 431 |
content_table = Table(content_data, colWidths=[2*inch, 3*inch])
|
|
|
|
| 439 |
|
| 440 |
# Ensemble Analysis
|
| 441 |
elements.append(Paragraph("Ensemble Analysis", heading_style))
|
| 442 |
+
elements.append(Paragraph("Method: Confidence Calibrated Aggregation", styles['Normal']))
|
| 443 |
elements.append(Spacer(1, 0.1*inch))
|
| 444 |
|
| 445 |
# Metric weights table
|
| 446 |
+
metric_contributions = ensemble_data.get("metric_contributions", {})
|
| 447 |
+
if metric_contributions:
|
| 448 |
elements.append(Paragraph("Metric Weights", styles['Heading3']))
|
| 449 |
weight_data = [['Metric', 'Weight']]
|
| 450 |
+
for metric, contribution in metric_contributions.items():
|
| 451 |
+
weight = contribution.get("weight", 0)
|
| 452 |
weight_data.append([metric.title(), f"{weight:.1%}"])
|
| 453 |
|
| 454 |
weight_table = Table(weight_data, colWidths=[3*inch, 1*inch])
|
|
|
|
| 591 |
|
| 592 |
# Footer
|
| 593 |
elements.append(Spacer(1, 0.3*inch))
|
| 594 |
+
elements.append(Paragraph(f"Generated by AI Text Detector v2.0 | Processing Time: {detection_dict.get('performance', {}).get('total_time', 0):.2f}s",
|
| 595 |
+
ParagraphStyle('Footer', parent=styles['Normal'], fontSize=8, textColor=colors.gray)))
|
| 596 |
|
| 597 |
# Build PDF
|
| 598 |
doc.build(elements)
|
requirements.txt
CHANGED
|
@@ -1,51 +1,56 @@
|
|
| 1 |
# Core Framework
|
| 2 |
-
fastapi==0.
|
| 3 |
-
uvicorn
|
| 4 |
-
pydantic==2.
|
| 5 |
-
pydantic-settings==2.
|
| 6 |
-
python-multipart==0.0.
|
| 7 |
-
|
| 8 |
-
# Machine Learning & Transformers
|
| 9 |
-
torch==2.1
|
| 10 |
-
transformers==4.
|
| 11 |
-
sentence-transformers==
|
| 12 |
-
tokenizers==0.
|
| 13 |
-
huggingface-hub==0.
|
| 14 |
|
| 15 |
# NLP Libraries
|
| 16 |
-
spacy==3.
|
| 17 |
-
nltk==3.
|
| 18 |
-
textstat==0.7.
|
| 19 |
|
| 20 |
# Scientific Computing
|
| 21 |
-
numpy==1.
|
| 22 |
-
scipy==1.
|
| 23 |
-
scikit-learn==1.
|
| 24 |
-
pandas==2.
|
| 25 |
|
| 26 |
# Text Processing
|
| 27 |
-
python-docx==1.1.
|
| 28 |
PyPDF2==3.0.1
|
| 29 |
-
pdfplumber==0.
|
| 30 |
-
pymupdf==1.
|
| 31 |
python-magic==0.4.27
|
| 32 |
|
| 33 |
# Language Detection
|
| 34 |
langdetect==1.0.9
|
| 35 |
|
| 36 |
# Visualization & Reporting
|
| 37 |
-
matplotlib==3.8.
|
| 38 |
seaborn==0.13.0
|
| 39 |
-
reportlab==4.
|
| 40 |
|
| 41 |
# Utilities
|
| 42 |
-
python-dotenv==1.0.
|
| 43 |
aiofiles==23.2.1
|
| 44 |
-
httpx==0.
|
| 45 |
-
tenacity==
|
| 46 |
|
| 47 |
# Logging & Monitoring
|
| 48 |
-
loguru==0.7.
|
| 49 |
|
| 50 |
# Caching
|
| 51 |
-
diskcache==5.6.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Core Framework
|
| 2 |
+
fastapi==0.115.6
|
| 3 |
+
uvicorn==0.34.0
|
| 4 |
+
pydantic==2.11.4
|
| 5 |
+
pydantic-settings==2.11.0
|
| 6 |
+
python-multipart==0.0.20
|
| 7 |
+
|
| 8 |
+
# Machine Learning & Transformers
|
| 9 |
+
torch==2.3.1
|
| 10 |
+
transformers==4.48.0
|
| 11 |
+
sentence-transformers==3.3.1
|
| 12 |
+
tokenizers==0.21.0
|
| 13 |
+
huggingface-hub==0.27.0
|
| 14 |
|
| 15 |
# NLP Libraries
|
| 16 |
+
spacy==3.8.3
|
| 17 |
+
nltk==3.9.1
|
| 18 |
+
textstat==0.7.10
|
| 19 |
|
| 20 |
# Scientific Computing
|
| 21 |
+
numpy==1.23.5
|
| 22 |
+
scipy==1.12.0
|
| 23 |
+
scikit-learn==1.6.0
|
| 24 |
+
pandas==2.2.3
|
| 25 |
|
| 26 |
# Text Processing
|
| 27 |
+
python-docx==1.1.2
|
| 28 |
PyPDF2==3.0.1
|
| 29 |
+
pdfplumber==0.11.5
|
| 30 |
+
pymupdf==1.25.5
|
| 31 |
python-magic==0.4.27
|
| 32 |
|
| 33 |
# Language Detection
|
| 34 |
langdetect==1.0.9
|
| 35 |
|
| 36 |
# Visualization & Reporting
|
| 37 |
+
matplotlib==3.8.0
|
| 38 |
seaborn==0.13.0
|
| 39 |
+
reportlab==4.2.2
|
| 40 |
|
| 41 |
# Utilities
|
| 42 |
+
python-dotenv==1.0.1
|
| 43 |
aiofiles==23.2.1
|
| 44 |
+
httpx==0.27.0
|
| 45 |
+
tenacity==9.1.2
|
| 46 |
|
| 47 |
# Logging & Monitoring
|
| 48 |
+
loguru==0.7.3
|
| 49 |
|
| 50 |
# Caching
|
| 51 |
+
diskcache==5.6.3
|
| 52 |
+
|
| 53 |
+
# Additional packages from your working environment
|
| 54 |
+
safetensors==0.4.4
|
| 55 |
+
accelerate==1.2.1
|
| 56 |
+
protobuf==4.25.4
|
ui/static/index.html
CHANGED
|
@@ -273,7 +273,6 @@ body {
|
|
| 273 |
padding: 2rem;
|
| 274 |
border: 1px solid var(--border);
|
| 275 |
backdrop-filter: blur(10px);
|
| 276 |
-
/* Changed from fixed height to use available space */
|
| 277 |
height: 850px;
|
| 278 |
overflow: hidden;
|
| 279 |
display: flex;
|
|
@@ -621,7 +620,7 @@ input[type="checkbox"] {
|
|
| 621 |
color: var(--text-secondary);
|
| 622 |
line-height: 1.7;
|
| 623 |
}
|
| 624 |
-
/*
|
| 625 |
.reasoning-box.enhanced {
|
| 626 |
background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%);
|
| 627 |
border: 1px solid rgba(71, 85, 105, 0.5);
|
|
@@ -703,7 +702,7 @@ input[type="checkbox"] {
|
|
| 703 |
.metric-indicator {
|
| 704 |
display: flex;
|
| 705 |
justify-content: space-between;
|
| 706 |
-
align-items:
|
| 707 |
padding: 0.75rem;
|
| 708 |
margin-bottom: 0.5rem;
|
| 709 |
border-radius: 8px;
|
|
@@ -714,7 +713,7 @@ input[type="checkbox"] {
|
|
| 714 |
transform: translateX(4px);
|
| 715 |
}
|
| 716 |
.metric-name {
|
| 717 |
-
font-weight:
|
| 718 |
color: var(--text-primary);
|
| 719 |
min-width: 140px;
|
| 720 |
}
|
|
@@ -795,6 +794,23 @@ input[type="checkbox"] {
|
|
| 795 |
font-weight: 700;
|
| 796 |
color: var(--primary);
|
| 797 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 798 |
/* Download Actions */
|
| 799 |
.download-actions {
|
| 800 |
display: flex;
|
|
@@ -959,21 +975,16 @@ input[type="checkbox"] {
|
|
| 959 |
}
|
| 960 |
.metrics-carousel-content {
|
| 961 |
flex: 1;
|
| 962 |
-
/* Removed padding and centering to allow content to fill space */
|
| 963 |
padding: 0;
|
| 964 |
-
/* Removed align-items: center; justify-content: center; to let content take natural space */
|
| 965 |
display: flex;
|
| 966 |
align-items: flex-start;
|
| 967 |
justify-content: flex-start;
|
| 968 |
overflow-y: auto;
|
| 969 |
-
/* Added some internal spacing for readability */
|
| 970 |
padding: 1rem;
|
| 971 |
-
/* min-height: 600px; */
|
| 972 |
}
|
| 973 |
.metric-slide {
|
| 974 |
display: none;
|
| 975 |
width: 100%;
|
| 976 |
-
/* Reduced padding to make card tighter */
|
| 977 |
padding: 1rem;
|
| 978 |
}
|
| 979 |
.metric-slide.active {
|
|
@@ -1011,6 +1022,43 @@ input[type="checkbox"] {
|
|
| 1011 |
color: var(--text-secondary);
|
| 1012 |
font-weight: 600;
|
| 1013 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1014 |
/* Responsive */
|
| 1015 |
@media (max-width: 1200px) {
|
| 1016 |
.interface-grid {
|
|
@@ -1222,7 +1270,7 @@ html {
|
|
| 1222 |
id="text-input"
|
| 1223 |
class="text-input"
|
| 1224 |
placeholder="Paste your text here for analysis...
|
| 1225 |
-
The more text you provide (minimum 50 characters), the more accurate the detection will be.
|
| 1226 |
></textarea>
|
| 1227 |
</div>
|
| 1228 |
<div id="upload-tab" class="tab-content">
|
|
@@ -1351,18 +1399,21 @@ const API_BASE = '';
|
|
| 1351 |
let currentAnalysisData = null;
|
| 1352 |
let currentMetricIndex = 0;
|
| 1353 |
let totalMetrics = 0;
|
|
|
|
| 1354 |
// Navigation
|
| 1355 |
function showLanding() {
|
| 1356 |
document.getElementById('landing-page').style.display = 'block';
|
| 1357 |
document.getElementById('analysis-interface').style.display = 'none';
|
| 1358 |
window.scrollTo(0, 0);
|
| 1359 |
}
|
|
|
|
| 1360 |
function showAnalysis() {
|
| 1361 |
document.getElementById('landing-page').style.display = 'none';
|
| 1362 |
document.getElementById('analysis-interface').style.display = 'block';
|
| 1363 |
window.scrollTo(0, 0);
|
| 1364 |
resetAnalysisInterface();
|
| 1365 |
}
|
|
|
|
| 1366 |
// Reset analysis interface
|
| 1367 |
function resetAnalysisInterface() {
|
| 1368 |
// Clear text input
|
|
@@ -1419,6 +1470,7 @@ function resetAnalysisInterface() {
|
|
| 1419 |
currentMetricIndex = 0;
|
| 1420 |
totalMetrics = 0;
|
| 1421 |
}
|
|
|
|
| 1422 |
// Input Tab Switching
|
| 1423 |
document.querySelectorAll('.input-tab').forEach(tab => {
|
| 1424 |
tab.addEventListener('click', () => {
|
|
@@ -1431,6 +1483,7 @@ document.querySelectorAll('.input-tab').forEach(tab => {
|
|
| 1431 |
document.getElementById(`${tabName}-tab`).classList.add('active');
|
| 1432 |
});
|
| 1433 |
});
|
|
|
|
| 1434 |
// Report Tab Switching
|
| 1435 |
document.querySelectorAll('.report-tab').forEach(tab => {
|
| 1436 |
tab.addEventListener('click', () => {
|
|
@@ -1443,24 +1496,30 @@ document.querySelectorAll('.report-tab').forEach(tab => {
|
|
| 1443 |
document.getElementById(`${reportName}-report`).classList.add('active');
|
| 1444 |
});
|
| 1445 |
});
|
|
|
|
| 1446 |
// File Upload Handling
|
| 1447 |
const fileInput = document.getElementById('file-input');
|
| 1448 |
const fileUploadArea = document.getElementById('file-upload-area');
|
| 1449 |
const fileNameDisplay = document.getElementById('file-name-display');
|
|
|
|
| 1450 |
fileUploadArea.addEventListener('click', () => {
|
| 1451 |
fileInput.click();
|
| 1452 |
});
|
|
|
|
| 1453 |
fileInput.addEventListener('change', (e) => {
|
| 1454 |
handleFileSelect(e.target.files[0]);
|
| 1455 |
});
|
|
|
|
| 1456 |
// Drag and Drop
|
| 1457 |
fileUploadArea.addEventListener('dragover', (e) => {
|
| 1458 |
e.preventDefault();
|
| 1459 |
fileUploadArea.classList.add('drag-over');
|
| 1460 |
});
|
|
|
|
| 1461 |
fileUploadArea.addEventListener('dragleave', () => {
|
| 1462 |
fileUploadArea.classList.remove('drag-over');
|
| 1463 |
});
|
|
|
|
| 1464 |
fileUploadArea.addEventListener('drop', (e) => {
|
| 1465 |
e.preventDefault();
|
| 1466 |
fileUploadArea.classList.remove('drag-over');
|
|
@@ -1470,6 +1529,7 @@ fileUploadArea.addEventListener('drop', (e) => {
|
|
| 1470 |
handleFileSelect(file);
|
| 1471 |
}
|
| 1472 |
});
|
|
|
|
| 1473 |
function handleFileSelect(file) {
|
| 1474 |
if (!file) return;
|
| 1475 |
const allowedTypes = ['.txt', '.pdf', '.docx', '.doc', '.md'];
|
|
@@ -1488,16 +1548,19 @@ function handleFileSelect(file) {
|
|
| 1488 |
<span style="color: var(--text-muted);">(${formatFileSize(file.size)})</span>
|
| 1489 |
`;
|
| 1490 |
}
|
|
|
|
| 1491 |
function formatFileSize(bytes) {
|
| 1492 |
if (bytes < 1024) return bytes + ' B';
|
| 1493 |
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
| 1494 |
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
| 1495 |
}
|
|
|
|
| 1496 |
// Analyze Button
|
| 1497 |
document.getElementById('analyze-btn').addEventListener('click', async () => {
|
| 1498 |
const activeTab = document.querySelector('.input-tab.active').dataset.tab;
|
| 1499 |
const textInput = document.getElementById('text-input').value.trim();
|
| 1500 |
const fileInput = document.getElementById('file-input').files[0];
|
|
|
|
| 1501 |
if (activeTab === 'paste' && !textInput) {
|
| 1502 |
alert('Please paste some text to analyze (minimum 50 characters).');
|
| 1503 |
return;
|
|
@@ -1510,21 +1573,26 @@ document.getElementById('analyze-btn').addEventListener('click', async () => {
|
|
| 1510 |
alert('Please select a file to upload.');
|
| 1511 |
return;
|
| 1512 |
}
|
|
|
|
| 1513 |
await performAnalysis(activeTab, textInput, fileInput);
|
| 1514 |
});
|
|
|
|
| 1515 |
// Refresh Button - clears everything and shows empty state
|
| 1516 |
document.getElementById('refresh-btn').addEventListener('click', () => {
|
| 1517 |
resetAnalysisInterface();
|
| 1518 |
});
|
|
|
|
| 1519 |
// Try Next Button - same as refresh but keeps the interface ready
|
| 1520 |
document.getElementById('try-next-btn').addEventListener('click', () => {
|
| 1521 |
resetAnalysisInterface();
|
| 1522 |
});
|
|
|
|
| 1523 |
async function performAnalysis(mode, text, file) {
|
| 1524 |
const analyzeBtn = document.getElementById('analyze-btn');
|
| 1525 |
analyzeBtn.disabled = true;
|
| 1526 |
analyzeBtn.innerHTML = '⏳ Analyzing...';
|
| 1527 |
showLoading();
|
|
|
|
| 1528 |
try {
|
| 1529 |
let response;
|
| 1530 |
if (mode === 'paste') {
|
|
@@ -1542,12 +1610,14 @@ async function performAnalysis(mode, text, file) {
|
|
| 1542 |
analyzeBtn.innerHTML = '🔍 Analyze Text';
|
| 1543 |
}
|
| 1544 |
}
|
|
|
|
| 1545 |
async function analyzeText(text) {
|
| 1546 |
const domain = document.getElementById('domain-select').value || null;
|
| 1547 |
const enableAttribution = document.getElementById('enable-attribution').checked;
|
| 1548 |
const enableHighlighting = document.getElementById('enable-highlighting').checked;
|
| 1549 |
const useSentenceLevel = document.getElementById('use-sentence-level').checked;
|
| 1550 |
const includeMetricsSummary = document.getElementById('include-metrics-summary').checked;
|
|
|
|
| 1551 |
const response = await fetch(`${API_BASE}/api/analyze`, {
|
| 1552 |
method: 'POST',
|
| 1553 |
headers: { 'Content-Type': 'application/json' },
|
|
@@ -1561,17 +1631,20 @@ async function analyzeText(text) {
|
|
| 1561 |
skip_expensive_metrics: false
|
| 1562 |
})
|
| 1563 |
});
|
|
|
|
| 1564 |
if (!response.ok) {
|
| 1565 |
const error = await response.json();
|
| 1566 |
throw new Error(error.error || 'Analysis failed');
|
| 1567 |
}
|
| 1568 |
return await response.json();
|
| 1569 |
}
|
|
|
|
| 1570 |
async function analyzeFile(file) {
|
| 1571 |
const domain = document.getElementById('domain-select').value || null;
|
| 1572 |
const enableAttribution = document.getElementById('enable-attribution').checked;
|
| 1573 |
const useSentenceLevel = document.getElementById('use-sentence-level').checked;
|
| 1574 |
const includeMetricsSummary = document.getElementById('include-metrics-summary').checked;
|
|
|
|
| 1575 |
const formData = new FormData();
|
| 1576 |
formData.append('file', file);
|
| 1577 |
if (domain) formData.append('domain', domain);
|
|
@@ -1579,16 +1652,19 @@ async function analyzeFile(file) {
|
|
| 1579 |
formData.append('use_sentence_level', useSentenceLevel.toString());
|
| 1580 |
formData.append('include_metrics_summary', includeMetricsSummary.toString());
|
| 1581 |
formData.append('skip_expensive_metrics', 'false');
|
|
|
|
| 1582 |
const response = await fetch(`${API_BASE}/api/analyze/file`, {
|
| 1583 |
method: 'POST',
|
| 1584 |
body: formData
|
| 1585 |
});
|
|
|
|
| 1586 |
if (!response.ok) {
|
| 1587 |
const error = await response.json();
|
| 1588 |
throw new Error(error.error || 'File analysis failed');
|
| 1589 |
}
|
| 1590 |
return await response.json();
|
| 1591 |
}
|
|
|
|
| 1592 |
function showLoading() {
|
| 1593 |
document.getElementById('summary-report').innerHTML = `
|
| 1594 |
<div class="loading">
|
|
@@ -1600,6 +1676,7 @@ function showLoading() {
|
|
| 1600 |
</div>
|
| 1601 |
`;
|
| 1602 |
}
|
|
|
|
| 1603 |
function showError(message) {
|
| 1604 |
document.getElementById('summary-report').innerHTML = `
|
| 1605 |
<div class="empty-state">
|
|
@@ -1609,6 +1686,7 @@ function showError(message) {
|
|
| 1609 |
</div>
|
| 1610 |
`;
|
| 1611 |
}
|
|
|
|
| 1612 |
function displayResults(data) {
|
| 1613 |
console.log('Response data:', data);
|
| 1614 |
// Handle different response structures
|
|
@@ -1618,13 +1696,16 @@ function displayResults(data) {
|
|
| 1618 |
console.error('Full response:', data);
|
| 1619 |
return;
|
| 1620 |
}
|
|
|
|
| 1621 |
// Extract data based on your actual API structure
|
| 1622 |
const ensemble = detection.ensemble_result || detection.ensemble;
|
| 1623 |
const prediction = detection.prediction || {};
|
| 1624 |
const metrics = detection.metric_results || detection.metrics;
|
| 1625 |
const analysis = detection.analysis || {};
|
|
|
|
| 1626 |
// Display Summary with enhanced reasoning
|
| 1627 |
displaySummary(ensemble, prediction, analysis, data.attribution, data.reasoning);
|
|
|
|
| 1628 |
// Display Highlighted Text with enhanced features
|
| 1629 |
if (data.highlighted_html) {
|
| 1630 |
displayHighlightedText(data.highlighted_html);
|
|
@@ -1635,6 +1716,7 @@ function displayResults(data) {
|
|
| 1635 |
</div>
|
| 1636 |
`;
|
| 1637 |
}
|
|
|
|
| 1638 |
// Display Metrics with carousel
|
| 1639 |
if (metrics && Object.keys(metrics).length > 0) {
|
| 1640 |
displayMetricsCarousel(metrics, analysis, ensemble);
|
|
@@ -1646,10 +1728,48 @@ function displayResults(data) {
|
|
| 1646 |
`;
|
| 1647 |
}
|
| 1648 |
}
|
|
|
|
| 1649 |
function displaySummary(ensemble, prediction, analysis, attribution, reasoning) {
|
| 1650 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1651 |
const aiProbability = ensemble.ai_probability !== undefined ?
|
| 1652 |
(ensemble.ai_probability * 100).toFixed(0) : '0';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1653 |
const verdict = ensemble.final_verdict || 'Unknown';
|
| 1654 |
const confidence = ensemble.overall_confidence !== undefined ?
|
| 1655 |
(ensemble.overall_confidence * 100).toFixed(1) : '0';
|
|
@@ -1657,81 +1777,289 @@ function displaySummary(ensemble, prediction, analysis, attribution, reasoning)
|
|
| 1657 |
const isAI = verdict.toLowerCase().includes('ai');
|
| 1658 |
const gaugeColor = isAI ? 'var(--danger)' : 'var(--success)';
|
| 1659 |
const gaugeDegree = aiProbability * 3.6;
|
| 1660 |
-
|
| 1661 |
-
|
| 1662 |
-
const confidenceClass = confidenceLevel
|
| 1663 |
-
|
| 1664 |
-
|
| 1665 |
-
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
|
| 1674 |
-
|
| 1675 |
-
|
| 1676 |
-
|
| 1677 |
-
|
| 1678 |
-
|
| 1679 |
-
|
| 1680 |
-
|
| 1681 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1682 |
<div class="attribution-section">
|
| 1683 |
<div class="attribution-title">🤖 AI Model Attribution</div>
|
| 1684 |
-
${
|
| 1685 |
-
|
| 1686 |
-
|
|
|
|
|
|
|
| 1687 |
</div>
|
| 1688 |
`;
|
| 1689 |
}
|
| 1690 |
-
|
| 1691 |
-
|
| 1692 |
-
|
| 1693 |
-
|
| 1694 |
-
|
| 1695 |
-
|
| 1696 |
-
|
| 1697 |
-
|
| 1698 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1699 |
</div>
|
| 1700 |
-
|
| 1701 |
-
|
| 1702 |
-
|
| 1703 |
-
|
| 1704 |
-
|
| 1705 |
-
|
| 1706 |
-
|
| 1707 |
-
|
| 1708 |
-
|
| 1709 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1710 |
</div>
|
| 1711 |
-
|
| 1712 |
-
|
| 1713 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1714 |
</div>
|
| 1715 |
</div>
|
| 1716 |
-
|
| 1717 |
-
|
| 1718 |
-
|
| 1719 |
-
<button class="download-btn" onclick="downloadReport('json')">
|
| 1720 |
-
📄 Download JSON
|
| 1721 |
-
</button>
|
| 1722 |
-
<button class="download-btn" onclick="downloadReport('pdf')">
|
| 1723 |
-
📑 Download PDF Report
|
| 1724 |
-
</button>
|
| 1725 |
</div>
|
| 1726 |
</div>
|
| 1727 |
`;
|
| 1728 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1729 |
function createEnhancedReasoningHTML(ensemble, analysis, reasoning) {
|
| 1730 |
-
// Use
|
| 1731 |
if (reasoning && reasoning.summary) {
|
| 1732 |
-
// Process
|
| 1733 |
-
|
| 1734 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1735 |
return `
|
| 1736 |
<div class="reasoning-box enhanced">
|
| 1737 |
<div class="reasoning-header">
|
|
@@ -1745,23 +2073,41 @@ function createEnhancedReasoningHTML(ensemble, analysis, reasoning) {
|
|
| 1745 |
<div class="verdict-text">${ensemble.final_verdict}</div>
|
| 1746 |
<div class="probability">AI Probability: <span class="probability-value">${(ensemble.ai_probability * 100).toFixed(2)}%</span></div>
|
| 1747 |
</div>
|
| 1748 |
-
<div class="reasoning-
|
| 1749 |
-
${
|
| 1750 |
</div>
|
| 1751 |
-
${
|
| 1752 |
<div class="metrics-breakdown">
|
| 1753 |
-
<div class="breakdown-header"
|
| 1754 |
-
|
| 1755 |
-
|
| 1756 |
-
|
| 1757 |
-
|
| 1758 |
-
|
| 1759 |
-
|
| 1760 |
-
|
| 1761 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1762 |
</div>
|
| 1763 |
-
|
| 1764 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1765 |
}).join('')}
|
| 1766 |
</div>
|
| 1767 |
` : ''}
|
|
@@ -1778,7 +2124,7 @@ function createEnhancedReasoningHTML(ensemble, analysis, reasoning) {
|
|
| 1778 |
return `
|
| 1779 |
<div class="reasoning-box">
|
| 1780 |
<div class="reasoning-title">💡 Detection Reasoning</div>
|
| 1781 |
-
<p class="reasoning-text">
|
| 1782 |
Analysis based on 6-metric ensemble with domain-aware calibration.
|
| 1783 |
The system evaluated linguistic patterns, statistical features, and semantic structures
|
| 1784 |
to determine content authenticity with ${(ensemble.overall_confidence * 100).toFixed(1)}% confidence.
|
|
@@ -1786,6 +2132,58 @@ function createEnhancedReasoningHTML(ensemble, analysis, reasoning) {
|
|
| 1786 |
</div>
|
| 1787 |
`;
|
| 1788 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1789 |
function displayHighlightedText(html) {
|
| 1790 |
document.getElementById('highlighted-report').innerHTML = `
|
| 1791 |
${createDefaultLegend()}
|
|
@@ -1795,6 +2193,7 @@ function displayHighlightedText(html) {
|
|
| 1795 |
${getHighlightStyles()}
|
| 1796 |
`;
|
| 1797 |
}
|
|
|
|
| 1798 |
function createDefaultLegend() {
|
| 1799 |
return `
|
| 1800 |
<div class="highlight-legend">
|
|
@@ -1833,6 +2232,7 @@ function createDefaultLegend() {
|
|
| 1833 |
</div>
|
| 1834 |
`;
|
| 1835 |
}
|
|
|
|
| 1836 |
function getHighlightStyles() {
|
| 1837 |
return `
|
| 1838 |
<style>
|
|
@@ -1888,10 +2288,12 @@ function getHighlightStyles() {
|
|
| 1888 |
</style>
|
| 1889 |
`;
|
| 1890 |
}
|
|
|
|
| 1891 |
function displayMetricsCarousel(metrics, analysis, ensemble) {
|
| 1892 |
const metricOrder = ['structural', 'perplexity', 'entropy', 'semantic_analysis', 'linguistic', 'multi_perturbation_stability'];
|
| 1893 |
const availableMetrics = metricOrder.filter(key => metrics[key]);
|
| 1894 |
totalMetrics = availableMetrics.length;
|
|
|
|
| 1895 |
if (totalMetrics === 0) {
|
| 1896 |
document.getElementById('metrics-report').innerHTML = `
|
| 1897 |
<div class="empty-state">
|
|
@@ -1900,24 +2302,39 @@ function displayMetricsCarousel(metrics, analysis, ensemble) {
|
|
| 1900 |
`;
|
| 1901 |
return;
|
| 1902 |
}
|
|
|
|
| 1903 |
let carouselHTML = `
|
| 1904 |
<div class="metrics-carousel-container">
|
| 1905 |
<div class="metrics-carousel-content">
|
| 1906 |
`;
|
|
|
|
| 1907 |
availableMetrics.forEach((metricKey, index) => {
|
| 1908 |
const metric = metrics[metricKey];
|
| 1909 |
if (!metric) return;
|
|
|
|
| 1910 |
const aiProb = (metric.ai_probability * 100).toFixed(1);
|
| 1911 |
const humanProb = (metric.human_probability * 100).toFixed(1);
|
|
|
|
| 1912 |
const confidence = (metric.confidence * 100).toFixed(1);
|
| 1913 |
const weight = ensemble.metric_contributions && ensemble.metric_contributions[metricKey] ?
|
| 1914 |
-
|
| 1915 |
-
|
| 1916 |
-
|
| 1917 |
-
|
| 1918 |
-
|
| 1919 |
-
|
| 1920 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1921 |
carouselHTML += `
|
| 1922 |
<div class="metric-slide ${index === 0 ? 'active' : ''}" data-metric-index="${index}">
|
| 1923 |
<div class="metric-result-card">
|
|
@@ -1927,22 +2344,32 @@ function displayMetricsCarousel(metrics, analysis, ensemble) {
|
|
| 1927 |
<div class="metric-description">
|
| 1928 |
${getMetricDescription(metricKey)}
|
| 1929 |
</div>
|
| 1930 |
-
|
| 1931 |
-
|
|
|
|
|
|
|
| 1932 |
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">AI</div>
|
| 1933 |
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
|
| 1934 |
<div style="background: var(--danger); height: 100%; width: ${aiProb}%; transition: width 0.5s;"></div>
|
| 1935 |
</div>
|
| 1936 |
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${aiProb}%</div>
|
| 1937 |
</div>
|
| 1938 |
-
<div style="
|
| 1939 |
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Human</div>
|
| 1940 |
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
|
| 1941 |
<div style="background: var(--success); height: 100%; width: ${humanProb}%; transition: width 0.5s;"></div>
|
| 1942 |
</div>
|
| 1943 |
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${humanProb}%</div>
|
| 1944 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1945 |
</div>
|
|
|
|
| 1946 |
<div style="display: flex; justify-content: space-between; align-items: center; margin: 0.75rem 0;">
|
| 1947 |
<span class="metric-verdict ${verdictClass}">${verdictText}</span>
|
| 1948 |
<span style="font-size: 0.85rem; color: var(--text-secondary);">Confidence: ${confidence}% | Weight: ${weight}%</span>
|
|
@@ -1953,6 +2380,7 @@ function displayMetricsCarousel(metrics, analysis, ensemble) {
|
|
| 1953 |
</div>
|
| 1954 |
`;
|
| 1955 |
});
|
|
|
|
| 1956 |
carouselHTML += `
|
| 1957 |
</div>
|
| 1958 |
<div class="metrics-carousel-nav">
|
|
@@ -1962,9 +2390,11 @@ function displayMetricsCarousel(metrics, analysis, ensemble) {
|
|
| 1962 |
</div>
|
| 1963 |
</div>
|
| 1964 |
`;
|
|
|
|
| 1965 |
document.getElementById('metrics-report').innerHTML = carouselHTML;
|
| 1966 |
updateCarouselButtons();
|
| 1967 |
}
|
|
|
|
| 1968 |
function navigateMetrics(direction) {
|
| 1969 |
const newMetricIndex = currentMetricIndex + direction;
|
| 1970 |
if (newMetricIndex >= 0 && newMetricIndex < totalMetrics) {
|
|
@@ -1972,6 +2402,7 @@ function navigateMetrics(direction) {
|
|
| 1972 |
updateMetricCarousel();
|
| 1973 |
}
|
| 1974 |
}
|
|
|
|
| 1975 |
function updateMetricCarousel() {
|
| 1976 |
const slides = document.querySelectorAll('.metric-slide');
|
| 1977 |
slides.forEach((slide, index) => {
|
|
@@ -1988,6 +2419,7 @@ function updateMetricCarousel() {
|
|
| 1988 |
positionElement.textContent = `${currentMetricIndex + 1} / ${totalMetrics}`;
|
| 1989 |
}
|
| 1990 |
}
|
|
|
|
| 1991 |
function updateCarouselButtons() {
|
| 1992 |
const prevBtn = document.querySelector('.prev-btn');
|
| 1993 |
const nextBtn = document.querySelector('.next-btn');
|
|
@@ -1998,8 +2430,10 @@ function updateCarouselButtons() {
|
|
| 1998 |
nextBtn.disabled = currentMetricIndex === totalMetrics - 1;
|
| 1999 |
}
|
| 2000 |
}
|
|
|
|
| 2001 |
function renderMetricDetails(metricName, details) {
|
| 2002 |
if (!details || Object.keys(details).length === 0) return '';
|
|
|
|
| 2003 |
// Key metrics to show for each type
|
| 2004 |
const importantKeys = {
|
| 2005 |
'structural': ['burstiness_score', 'length_uniformity', 'avg_sentence_length', 'std_sentence_length'],
|
|
@@ -2007,29 +2441,47 @@ function renderMetricDetails(metricName, details) {
|
|
| 2007 |
'entropy': ['token_diversity', 'sequence_unpredictability', 'char_entropy'],
|
| 2008 |
'semantic_analysis': ['coherence_score', 'consistency_score', 'repetition_score'],
|
| 2009 |
'linguistic': ['pos_diversity', 'syntactic_complexity', 'grammatical_consistency'],
|
| 2010 |
-
'multi_perturbation_stability': ['stability_score', 'curvature_score', 'likelihood_ratio']
|
| 2011 |
};
|
|
|
|
| 2012 |
const keysToShow = importantKeys[metricName] || Object.keys(details).slice(0, 6);
|
|
|
|
| 2013 |
let detailsHTML = '<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border);">';
|
| 2014 |
detailsHTML += '<div style="font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.75rem;">📈 Detailed Metrics:</div>';
|
| 2015 |
detailsHTML += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; font-size: 0.85rem;">';
|
|
|
|
| 2016 |
keysToShow.forEach(key => {
|
| 2017 |
if (details[key] !== undefined && details[key] !== null) {
|
| 2018 |
-
|
| 2019 |
-
|
| 2020 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2021 |
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
| 2022 |
detailsHTML += `
|
| 2023 |
<div style="background: rgba(15, 23, 42, 0.6); padding: 0.5rem; border-radius: 6px;">
|
| 2024 |
<div style="color: var(--text-muted); font-size: 0.75rem; margin-bottom: 0.25rem;">${label}</div>
|
| 2025 |
-
<div style="color: var(--primary); font-weight: 700;">${
|
| 2026 |
</div>
|
| 2027 |
`;
|
| 2028 |
}
|
| 2029 |
});
|
|
|
|
| 2030 |
detailsHTML += '</div></div>';
|
| 2031 |
return detailsHTML;
|
| 2032 |
}
|
|
|
|
| 2033 |
function getMetricDescription(metricName) {
|
| 2034 |
const descriptions = {
|
| 2035 |
structural: 'Analyzes sentence structure, length patterns, and statistical features.',
|
|
@@ -2041,6 +2493,7 @@ function getMetricDescription(metricName) {
|
|
| 2041 |
};
|
| 2042 |
return descriptions[metricName] || 'Metric analysis complete.';
|
| 2043 |
}
|
|
|
|
| 2044 |
function formatMetricName(name) {
|
| 2045 |
const names = {
|
| 2046 |
structural: 'Structural Analysis',
|
|
@@ -2052,17 +2505,17 @@ function formatMetricName(name) {
|
|
| 2052 |
};
|
| 2053 |
return names[name] || name.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
| 2054 |
}
|
| 2055 |
-
|
| 2056 |
-
return domain.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
| 2057 |
-
}
|
| 2058 |
async function downloadReport(format) {
|
| 2059 |
if (!currentAnalysisData) {
|
| 2060 |
alert('No analysis data available');
|
| 2061 |
return;
|
| 2062 |
}
|
|
|
|
| 2063 |
try {
|
| 2064 |
const analysisId = currentAnalysisData.analysis_id;
|
| 2065 |
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
|
|
| 2066 |
// For JSON, download directly from current data
|
| 2067 |
if (format === 'json') {
|
| 2068 |
const data = {
|
|
@@ -2077,6 +2530,7 @@ async function downloadReport(format) {
|
|
| 2077 |
await downloadBlob(blob, filename);
|
| 2078 |
return;
|
| 2079 |
}
|
|
|
|
| 2080 |
// Get the original text for report generation
|
| 2081 |
const activeTab = document.querySelector('.input-tab.active').dataset.tab;
|
| 2082 |
let textToSend = '';
|
|
@@ -2086,19 +2540,23 @@ async function downloadReport(format) {
|
|
| 2086 |
textToSend = currentAnalysisData.detection_result?.processed_text?.text ||
|
| 2087 |
'Uploaded file content - see analysis for details';
|
| 2088 |
}
|
|
|
|
| 2089 |
// For PDF, request from server
|
| 2090 |
const formData = new FormData();
|
| 2091 |
formData.append('analysis_id', analysisId);
|
| 2092 |
formData.append('text', textToSend);
|
| 2093 |
formData.append('formats', format);
|
| 2094 |
formData.append('include_highlights', document.getElementById('enable-highlighting').checked.toString());
|
|
|
|
| 2095 |
const response = await fetch(`${API_BASE}/api/report/generate`, {
|
| 2096 |
method: 'POST',
|
| 2097 |
body: formData
|
| 2098 |
});
|
|
|
|
| 2099 |
if (!response.ok) {
|
| 2100 |
throw new Error('Report generation failed');
|
| 2101 |
}
|
|
|
|
| 2102 |
const result = await response.json();
|
| 2103 |
if (result.reports && result.reports[format]) {
|
| 2104 |
const filename = result.reports[format];
|
|
@@ -2117,6 +2575,7 @@ async function downloadReport(format) {
|
|
| 2117 |
alert('Failed to download report. Please try again.');
|
| 2118 |
}
|
| 2119 |
}
|
|
|
|
| 2120 |
async function downloadBlob(blob, filename) {
|
| 2121 |
try {
|
| 2122 |
const url = URL.createObjectURL(blob);
|
|
@@ -2136,6 +2595,7 @@ async function downloadBlob(blob, filename) {
|
|
| 2136 |
alert('Download failed. Please try again.');
|
| 2137 |
}
|
| 2138 |
}
|
|
|
|
| 2139 |
function showDownloadSuccess(filename) {
|
| 2140 |
const notification = document.createElement('div');
|
| 2141 |
notification.style.cssText = `
|
|
@@ -2158,6 +2618,7 @@ function showDownloadSuccess(filename) {
|
|
| 2158 |
</div>
|
| 2159 |
`;
|
| 2160 |
document.body.appendChild(notification);
|
|
|
|
| 2161 |
if (!document.querySelector('#download-animation')) {
|
| 2162 |
const style = document.createElement('style');
|
| 2163 |
style.id = 'download-animation';
|
|
@@ -2169,12 +2630,14 @@ function showDownloadSuccess(filename) {
|
|
| 2169 |
`;
|
| 2170 |
document.head.appendChild(style);
|
| 2171 |
}
|
|
|
|
| 2172 |
setTimeout(() => {
|
| 2173 |
if (notification.parentNode) {
|
| 2174 |
notification.parentNode.removeChild(notification);
|
| 2175 |
}
|
| 2176 |
}, 3000);
|
| 2177 |
}
|
|
|
|
| 2178 |
// Smooth scrolling for anchor links
|
| 2179 |
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
| 2180 |
anchor.addEventListener('click', function (e) {
|
|
@@ -2188,6 +2651,7 @@ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
| 2188 |
}
|
| 2189 |
});
|
| 2190 |
});
|
|
|
|
| 2191 |
// Initialize - show landing page by default
|
| 2192 |
showLanding();
|
| 2193 |
</script>
|
|
|
|
| 273 |
padding: 2rem;
|
| 274 |
border: 1px solid var(--border);
|
| 275 |
backdrop-filter: blur(10px);
|
|
|
|
| 276 |
height: 850px;
|
| 277 |
overflow: hidden;
|
| 278 |
display: flex;
|
|
|
|
| 620 |
color: var(--text-secondary);
|
| 621 |
line-height: 1.7;
|
| 622 |
}
|
| 623 |
+
/* Reasoning Styles */
|
| 624 |
.reasoning-box.enhanced {
|
| 625 |
background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%);
|
| 626 |
border: 1px solid rgba(71, 85, 105, 0.5);
|
|
|
|
| 702 |
.metric-indicator {
|
| 703 |
display: flex;
|
| 704 |
justify-content: space-between;
|
| 705 |
+
align-items: left;
|
| 706 |
padding: 0.75rem;
|
| 707 |
margin-bottom: 0.5rem;
|
| 708 |
border-radius: 8px;
|
|
|
|
| 713 |
transform: translateX(4px);
|
| 714 |
}
|
| 715 |
.metric-name {
|
| 716 |
+
font-weight: 400;
|
| 717 |
color: var(--text-primary);
|
| 718 |
min-width: 140px;
|
| 719 |
}
|
|
|
|
| 794 |
font-weight: 700;
|
| 795 |
color: var(--primary);
|
| 796 |
}
|
| 797 |
+
.attribution-confidence {
|
| 798 |
+
margin-top: 0.75rem;
|
| 799 |
+
font-size: 0.85rem;
|
| 800 |
+
color: var(--text-secondary);
|
| 801 |
+
}
|
| 802 |
+
.attribution-uncertain {
|
| 803 |
+
color: var(--text-muted);
|
| 804 |
+
font-style: italic;
|
| 805 |
+
margin-top: 0.5rem;
|
| 806 |
+
font-size: 0.9rem;
|
| 807 |
+
}
|
| 808 |
+
.attribution-reasoning {
|
| 809 |
+
color: var(--text-secondary);
|
| 810 |
+
margin-top: 1rem;
|
| 811 |
+
font-size: 0.9rem;
|
| 812 |
+
line-height: 1.4;
|
| 813 |
+
}
|
| 814 |
/* Download Actions */
|
| 815 |
.download-actions {
|
| 816 |
display: flex;
|
|
|
|
| 975 |
}
|
| 976 |
.metrics-carousel-content {
|
| 977 |
flex: 1;
|
|
|
|
| 978 |
padding: 0;
|
|
|
|
| 979 |
display: flex;
|
| 980 |
align-items: flex-start;
|
| 981 |
justify-content: flex-start;
|
| 982 |
overflow-y: auto;
|
|
|
|
| 983 |
padding: 1rem;
|
|
|
|
| 984 |
}
|
| 985 |
.metric-slide {
|
| 986 |
display: none;
|
| 987 |
width: 100%;
|
|
|
|
| 988 |
padding: 1rem;
|
| 989 |
}
|
| 990 |
.metric-slide.active {
|
|
|
|
| 1022 |
color: var(--text-secondary);
|
| 1023 |
font-weight: 600;
|
| 1024 |
}
|
| 1025 |
+
/* Info Card Text Styles */
|
| 1026 |
+
.verdict-text {
|
| 1027 |
+
font-size: 1.2rem !important;
|
| 1028 |
+
}
|
| 1029 |
+
.domain-text {
|
| 1030 |
+
font-size: 1.1rem !important;
|
| 1031 |
+
}
|
| 1032 |
+
|
| 1033 |
+
.verdict-mixed {
|
| 1034 |
+
background: rgba(168, 85, 247, 0.2);
|
| 1035 |
+
color: #a855f7;
|
| 1036 |
+
border: 1px solid rgba(168, 85, 247, 0.3);
|
| 1037 |
+
}
|
| 1038 |
+
|
| 1039 |
+
/* Reasoning Bullet Points */
|
| 1040 |
+
.reasoning-bullet-points {
|
| 1041 |
+
margin: 1.5rem 0;
|
| 1042 |
+
line-height: 1.6;
|
| 1043 |
+
text-align: left;
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
.bullet-point {
|
| 1047 |
+
margin-bottom: 0.75rem;
|
| 1048 |
+
padding-left: 0.5rem;
|
| 1049 |
+
color: var(--text-secondary);
|
| 1050 |
+
font-size: 0.95rem;
|
| 1051 |
+
text-align: left;
|
| 1052 |
+
}
|
| 1053 |
+
|
| 1054 |
+
.bullet-point:last-child {
|
| 1055 |
+
margin-bottom: 0;
|
| 1056 |
+
}
|
| 1057 |
+
|
| 1058 |
+
.bullet-point strong {
|
| 1059 |
+
color: var(--text-primary);
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
/* Responsive */
|
| 1063 |
@media (max-width: 1200px) {
|
| 1064 |
.interface-grid {
|
|
|
|
| 1270 |
id="text-input"
|
| 1271 |
class="text-input"
|
| 1272 |
placeholder="Paste your text here for analysis...
|
| 1273 |
+
The more text you provide (minimum 50 characters), the more accurate the detection will be."
|
| 1274 |
></textarea>
|
| 1275 |
</div>
|
| 1276 |
<div id="upload-tab" class="tab-content">
|
|
|
|
| 1399 |
let currentAnalysisData = null;
|
| 1400 |
let currentMetricIndex = 0;
|
| 1401 |
let totalMetrics = 0;
|
| 1402 |
+
|
| 1403 |
// Navigation
|
| 1404 |
function showLanding() {
|
| 1405 |
document.getElementById('landing-page').style.display = 'block';
|
| 1406 |
document.getElementById('analysis-interface').style.display = 'none';
|
| 1407 |
window.scrollTo(0, 0);
|
| 1408 |
}
|
| 1409 |
+
|
| 1410 |
function showAnalysis() {
|
| 1411 |
document.getElementById('landing-page').style.display = 'none';
|
| 1412 |
document.getElementById('analysis-interface').style.display = 'block';
|
| 1413 |
window.scrollTo(0, 0);
|
| 1414 |
resetAnalysisInterface();
|
| 1415 |
}
|
| 1416 |
+
|
| 1417 |
// Reset analysis interface
|
| 1418 |
function resetAnalysisInterface() {
|
| 1419 |
// Clear text input
|
|
|
|
| 1470 |
currentMetricIndex = 0;
|
| 1471 |
totalMetrics = 0;
|
| 1472 |
}
|
| 1473 |
+
|
| 1474 |
// Input Tab Switching
|
| 1475 |
document.querySelectorAll('.input-tab').forEach(tab => {
|
| 1476 |
tab.addEventListener('click', () => {
|
|
|
|
| 1483 |
document.getElementById(`${tabName}-tab`).classList.add('active');
|
| 1484 |
});
|
| 1485 |
});
|
| 1486 |
+
|
| 1487 |
// Report Tab Switching
|
| 1488 |
document.querySelectorAll('.report-tab').forEach(tab => {
|
| 1489 |
tab.addEventListener('click', () => {
|
|
|
|
| 1496 |
document.getElementById(`${reportName}-report`).classList.add('active');
|
| 1497 |
});
|
| 1498 |
});
|
| 1499 |
+
|
| 1500 |
// File Upload Handling
|
| 1501 |
const fileInput = document.getElementById('file-input');
|
| 1502 |
const fileUploadArea = document.getElementById('file-upload-area');
|
| 1503 |
const fileNameDisplay = document.getElementById('file-name-display');
|
| 1504 |
+
|
| 1505 |
fileUploadArea.addEventListener('click', () => {
|
| 1506 |
fileInput.click();
|
| 1507 |
});
|
| 1508 |
+
|
| 1509 |
fileInput.addEventListener('change', (e) => {
|
| 1510 |
handleFileSelect(e.target.files[0]);
|
| 1511 |
});
|
| 1512 |
+
|
| 1513 |
// Drag and Drop
|
| 1514 |
fileUploadArea.addEventListener('dragover', (e) => {
|
| 1515 |
e.preventDefault();
|
| 1516 |
fileUploadArea.classList.add('drag-over');
|
| 1517 |
});
|
| 1518 |
+
|
| 1519 |
fileUploadArea.addEventListener('dragleave', () => {
|
| 1520 |
fileUploadArea.classList.remove('drag-over');
|
| 1521 |
});
|
| 1522 |
+
|
| 1523 |
fileUploadArea.addEventListener('drop', (e) => {
|
| 1524 |
e.preventDefault();
|
| 1525 |
fileUploadArea.classList.remove('drag-over');
|
|
|
|
| 1529 |
handleFileSelect(file);
|
| 1530 |
}
|
| 1531 |
});
|
| 1532 |
+
|
| 1533 |
function handleFileSelect(file) {
|
| 1534 |
if (!file) return;
|
| 1535 |
const allowedTypes = ['.txt', '.pdf', '.docx', '.doc', '.md'];
|
|
|
|
| 1548 |
<span style="color: var(--text-muted);">(${formatFileSize(file.size)})</span>
|
| 1549 |
`;
|
| 1550 |
}
|
| 1551 |
+
|
| 1552 |
function formatFileSize(bytes) {
|
| 1553 |
if (bytes < 1024) return bytes + ' B';
|
| 1554 |
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
| 1555 |
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
| 1556 |
}
|
| 1557 |
+
|
| 1558 |
// Analyze Button
|
| 1559 |
document.getElementById('analyze-btn').addEventListener('click', async () => {
|
| 1560 |
const activeTab = document.querySelector('.input-tab.active').dataset.tab;
|
| 1561 |
const textInput = document.getElementById('text-input').value.trim();
|
| 1562 |
const fileInput = document.getElementById('file-input').files[0];
|
| 1563 |
+
|
| 1564 |
if (activeTab === 'paste' && !textInput) {
|
| 1565 |
alert('Please paste some text to analyze (minimum 50 characters).');
|
| 1566 |
return;
|
|
|
|
| 1573 |
alert('Please select a file to upload.');
|
| 1574 |
return;
|
| 1575 |
}
|
| 1576 |
+
|
| 1577 |
await performAnalysis(activeTab, textInput, fileInput);
|
| 1578 |
});
|
| 1579 |
+
|
| 1580 |
// Refresh Button - clears everything and shows empty state
|
| 1581 |
document.getElementById('refresh-btn').addEventListener('click', () => {
|
| 1582 |
resetAnalysisInterface();
|
| 1583 |
});
|
| 1584 |
+
|
| 1585 |
// Try Next Button - same as refresh but keeps the interface ready
|
| 1586 |
document.getElementById('try-next-btn').addEventListener('click', () => {
|
| 1587 |
resetAnalysisInterface();
|
| 1588 |
});
|
| 1589 |
+
|
| 1590 |
async function performAnalysis(mode, text, file) {
|
| 1591 |
const analyzeBtn = document.getElementById('analyze-btn');
|
| 1592 |
analyzeBtn.disabled = true;
|
| 1593 |
analyzeBtn.innerHTML = '⏳ Analyzing...';
|
| 1594 |
showLoading();
|
| 1595 |
+
|
| 1596 |
try {
|
| 1597 |
let response;
|
| 1598 |
if (mode === 'paste') {
|
|
|
|
| 1610 |
analyzeBtn.innerHTML = '🔍 Analyze Text';
|
| 1611 |
}
|
| 1612 |
}
|
| 1613 |
+
|
| 1614 |
async function analyzeText(text) {
|
| 1615 |
const domain = document.getElementById('domain-select').value || null;
|
| 1616 |
const enableAttribution = document.getElementById('enable-attribution').checked;
|
| 1617 |
const enableHighlighting = document.getElementById('enable-highlighting').checked;
|
| 1618 |
const useSentenceLevel = document.getElementById('use-sentence-level').checked;
|
| 1619 |
const includeMetricsSummary = document.getElementById('include-metrics-summary').checked;
|
| 1620 |
+
|
| 1621 |
const response = await fetch(`${API_BASE}/api/analyze`, {
|
| 1622 |
method: 'POST',
|
| 1623 |
headers: { 'Content-Type': 'application/json' },
|
|
|
|
| 1631 |
skip_expensive_metrics: false
|
| 1632 |
})
|
| 1633 |
});
|
| 1634 |
+
|
| 1635 |
if (!response.ok) {
|
| 1636 |
const error = await response.json();
|
| 1637 |
throw new Error(error.error || 'Analysis failed');
|
| 1638 |
}
|
| 1639 |
return await response.json();
|
| 1640 |
}
|
| 1641 |
+
|
| 1642 |
async function analyzeFile(file) {
|
| 1643 |
const domain = document.getElementById('domain-select').value || null;
|
| 1644 |
const enableAttribution = document.getElementById('enable-attribution').checked;
|
| 1645 |
const useSentenceLevel = document.getElementById('use-sentence-level').checked;
|
| 1646 |
const includeMetricsSummary = document.getElementById('include-metrics-summary').checked;
|
| 1647 |
+
|
| 1648 |
const formData = new FormData();
|
| 1649 |
formData.append('file', file);
|
| 1650 |
if (domain) formData.append('domain', domain);
|
|
|
|
| 1652 |
formData.append('use_sentence_level', useSentenceLevel.toString());
|
| 1653 |
formData.append('include_metrics_summary', includeMetricsSummary.toString());
|
| 1654 |
formData.append('skip_expensive_metrics', 'false');
|
| 1655 |
+
|
| 1656 |
const response = await fetch(`${API_BASE}/api/analyze/file`, {
|
| 1657 |
method: 'POST',
|
| 1658 |
body: formData
|
| 1659 |
});
|
| 1660 |
+
|
| 1661 |
if (!response.ok) {
|
| 1662 |
const error = await response.json();
|
| 1663 |
throw new Error(error.error || 'File analysis failed');
|
| 1664 |
}
|
| 1665 |
return await response.json();
|
| 1666 |
}
|
| 1667 |
+
|
| 1668 |
function showLoading() {
|
| 1669 |
document.getElementById('summary-report').innerHTML = `
|
| 1670 |
<div class="loading">
|
|
|
|
| 1676 |
</div>
|
| 1677 |
`;
|
| 1678 |
}
|
| 1679 |
+
|
| 1680 |
function showError(message) {
|
| 1681 |
document.getElementById('summary-report').innerHTML = `
|
| 1682 |
<div class="empty-state">
|
|
|
|
| 1686 |
</div>
|
| 1687 |
`;
|
| 1688 |
}
|
| 1689 |
+
|
| 1690 |
function displayResults(data) {
|
| 1691 |
console.log('Response data:', data);
|
| 1692 |
// Handle different response structures
|
|
|
|
| 1696 |
console.error('Full response:', data);
|
| 1697 |
return;
|
| 1698 |
}
|
| 1699 |
+
|
| 1700 |
// Extract data based on your actual API structure
|
| 1701 |
const ensemble = detection.ensemble_result || detection.ensemble;
|
| 1702 |
const prediction = detection.prediction || {};
|
| 1703 |
const metrics = detection.metric_results || detection.metrics;
|
| 1704 |
const analysis = detection.analysis || {};
|
| 1705 |
+
|
| 1706 |
// Display Summary with enhanced reasoning
|
| 1707 |
displaySummary(ensemble, prediction, analysis, data.attribution, data.reasoning);
|
| 1708 |
+
|
| 1709 |
// Display Highlighted Text with enhanced features
|
| 1710 |
if (data.highlighted_html) {
|
| 1711 |
displayHighlightedText(data.highlighted_html);
|
|
|
|
| 1716 |
</div>
|
| 1717 |
`;
|
| 1718 |
}
|
| 1719 |
+
|
| 1720 |
// Display Metrics with carousel
|
| 1721 |
if (metrics && Object.keys(metrics).length > 0) {
|
| 1722 |
displayMetricsCarousel(metrics, analysis, ensemble);
|
|
|
|
| 1728 |
`;
|
| 1729 |
}
|
| 1730 |
}
|
| 1731 |
+
|
| 1732 |
function displaySummary(ensemble, prediction, analysis, attribution, reasoning) {
|
| 1733 |
+
// Extract and validate data with fallbacks
|
| 1734 |
+
const {
|
| 1735 |
+
aiProbability,
|
| 1736 |
+
humanProbability,
|
| 1737 |
+
mixedProbability,
|
| 1738 |
+
verdict,
|
| 1739 |
+
confidence,
|
| 1740 |
+
domain,
|
| 1741 |
+
isAI,
|
| 1742 |
+
gaugeColor,
|
| 1743 |
+
gaugeDegree,
|
| 1744 |
+
confidenceLevel,
|
| 1745 |
+
confidenceClass
|
| 1746 |
+
} = extractSummaryData(ensemble, analysis);
|
| 1747 |
+
|
| 1748 |
+
// Generate attribution HTML with proper filtering
|
| 1749 |
+
const attributionHTML = generateAttributionHTML(attribution);
|
| 1750 |
+
|
| 1751 |
+
document.getElementById('summary-report').innerHTML = `
|
| 1752 |
+
<div class="result-summary">
|
| 1753 |
+
${createGaugeSection(aiProbability, humanProbability, mixedProbability, gaugeColor, gaugeDegree)}
|
| 1754 |
+
${createInfoGrid(verdict, confidence, confidenceClass, domain, mixedProbability)}
|
| 1755 |
+
${createEnhancedReasoningHTML(ensemble, analysis, reasoning)}
|
| 1756 |
+
${attributionHTML}
|
| 1757 |
+
${createDownloadActions()}
|
| 1758 |
+
</div>
|
| 1759 |
+
`;
|
| 1760 |
+
}
|
| 1761 |
+
|
| 1762 |
+
// Helper function to extract and validate summary data
|
| 1763 |
+
function extractSummaryData(ensemble, analysis) {
|
| 1764 |
const aiProbability = ensemble.ai_probability !== undefined ?
|
| 1765 |
(ensemble.ai_probability * 100).toFixed(0) : '0';
|
| 1766 |
+
|
| 1767 |
+
const humanProbability = ensemble.human_probability !== undefined ?
|
| 1768 |
+
(ensemble.human_probability * 100).toFixed(0) : '0';
|
| 1769 |
+
|
| 1770 |
+
const mixedProbability = ensemble.mixed_probability !== undefined ?
|
| 1771 |
+
(ensemble.mixed_probability * 100).toFixed(0) : '0';
|
| 1772 |
+
|
| 1773 |
const verdict = ensemble.final_verdict || 'Unknown';
|
| 1774 |
const confidence = ensemble.overall_confidence !== undefined ?
|
| 1775 |
(ensemble.overall_confidence * 100).toFixed(1) : '0';
|
|
|
|
| 1777 |
const isAI = verdict.toLowerCase().includes('ai');
|
| 1778 |
const gaugeColor = isAI ? 'var(--danger)' : 'var(--success)';
|
| 1779 |
const gaugeDegree = aiProbability * 3.6;
|
| 1780 |
+
|
| 1781 |
+
const confidenceLevel = getConfidenceLevel(parseFloat(confidence));
|
| 1782 |
+
const confidenceClass = getConfidenceClass(confidenceLevel);
|
| 1783 |
+
|
| 1784 |
+
return {
|
| 1785 |
+
aiProbability,
|
| 1786 |
+
humanProbability,
|
| 1787 |
+
mixedProbability,
|
| 1788 |
+
verdict,
|
| 1789 |
+
confidence,
|
| 1790 |
+
domain,
|
| 1791 |
+
isAI,
|
| 1792 |
+
gaugeColor,
|
| 1793 |
+
gaugeDegree,
|
| 1794 |
+
confidenceLevel,
|
| 1795 |
+
confidenceClass
|
| 1796 |
+
};
|
| 1797 |
+
}
|
| 1798 |
+
|
| 1799 |
+
// Helper function to determine confidence level
|
| 1800 |
+
function getConfidenceLevel(confidence) {
|
| 1801 |
+
if (confidence >= 70) return 'HIGH';
|
| 1802 |
+
if (confidence >= 40) return 'MEDIUM';
|
| 1803 |
+
return 'LOW';
|
| 1804 |
+
}
|
| 1805 |
+
|
| 1806 |
+
// Helper function to get confidence CSS class
|
| 1807 |
+
function getConfidenceClass(confidenceLevel) {
|
| 1808 |
+
const classMap = {
|
| 1809 |
+
'HIGH': 'confidence-high',
|
| 1810 |
+
'MEDIUM': 'confidence-medium',
|
| 1811 |
+
'LOW': 'confidence-low'
|
| 1812 |
+
};
|
| 1813 |
+
return classMap[confidenceLevel] || 'confidence-low';
|
| 1814 |
+
}
|
| 1815 |
+
|
| 1816 |
+
// Helper function to generate attribution HTML with filtering
|
| 1817 |
+
function generateAttributionHTML(attribution) {
|
| 1818 |
+
if (!attribution || !attribution.predicted_model) {
|
| 1819 |
+
return '';
|
| 1820 |
+
}
|
| 1821 |
+
|
| 1822 |
+
const modelName = formatModelName(attribution.predicted_model);
|
| 1823 |
+
const modelConf = attribution.confidence ?
|
| 1824 |
+
(attribution.confidence * 100).toFixed(1) : 'N/A';
|
| 1825 |
+
|
| 1826 |
+
const topModelsHTML = generateTopModelsHTML(attribution.model_probabilities);
|
| 1827 |
+
const reasoningHTML = generateAttributionReasoningHTML(attribution.reasoning);
|
| 1828 |
+
|
| 1829 |
+
// Only show attribution if confidence is meaningful (>30%)
|
| 1830 |
+
if (attribution.confidence > 0.3) {
|
| 1831 |
+
return `
|
| 1832 |
<div class="attribution-section">
|
| 1833 |
<div class="attribution-title">🤖 AI Model Attribution</div>
|
| 1834 |
+
${topModelsHTML}
|
| 1835 |
+
<div class="attribution-confidence">
|
| 1836 |
+
Attribution Confidence: <strong>${modelConf}%</strong>
|
| 1837 |
+
</div>
|
| 1838 |
+
${reasoningHTML}
|
| 1839 |
</div>
|
| 1840 |
`;
|
| 1841 |
}
|
| 1842 |
+
|
| 1843 |
+
return '';
|
| 1844 |
+
}
|
| 1845 |
+
|
| 1846 |
+
// Helper function to generate top models HTML with filtering
|
| 1847 |
+
function generateTopModelsHTML(modelProbabilities) {
|
| 1848 |
+
if (!modelProbabilities) {
|
| 1849 |
+
return '<div class="attribution-uncertain">Model probabilities not available</div>';
|
| 1850 |
+
}
|
| 1851 |
+
|
| 1852 |
+
// Filter and sort models
|
| 1853 |
+
const meaningfulModels = Object.entries(modelProbabilities)
|
| 1854 |
+
.sort((a, b) => b[1] - a[1])
|
| 1855 |
+
.filter(([model, prob]) => prob > 0.15) // Only show models with >15% probability
|
| 1856 |
+
.slice(0, 3); // Show top 3
|
| 1857 |
+
|
| 1858 |
+
if (meaningfulModels.length === 0) {
|
| 1859 |
+
return `
|
| 1860 |
+
<div class="attribution-uncertain">
|
| 1861 |
+
Model attribution uncertain - text patterns don't strongly match any specific AI model
|
| 1862 |
</div>
|
| 1863 |
+
`;
|
| 1864 |
+
}
|
| 1865 |
+
|
| 1866 |
+
return meaningfulModels.map(([model, prob]) =>
|
| 1867 |
+
`<div class="model-match">
|
| 1868 |
+
<span class="model-name">${formatModelName(model)}</span>
|
| 1869 |
+
<span class="model-confidence">${(prob * 100).toFixed(1)}%</span>
|
| 1870 |
+
</div>`
|
| 1871 |
+
).join('');
|
| 1872 |
+
}
|
| 1873 |
+
|
| 1874 |
+
// Helper function to format model names
|
| 1875 |
+
function formatModelName(modelName) {
|
| 1876 |
+
return modelName.replace(/_/g, ' ').replace(/-/g, ' ').toUpperCase();
|
| 1877 |
+
}
|
| 1878 |
+
|
| 1879 |
+
// Helper function to generate attribution reasoning HTML
|
| 1880 |
+
function generateAttributionReasoningHTML(reasoning) {
|
| 1881 |
+
if (!reasoning || !Array.isArray(reasoning) || reasoning.length === 0) {
|
| 1882 |
+
return '';
|
| 1883 |
+
}
|
| 1884 |
+
|
| 1885 |
+
return `
|
| 1886 |
+
<div class="attribution-reasoning">
|
| 1887 |
+
${reasoning[0]}
|
| 1888 |
+
</div>
|
| 1889 |
+
`;
|
| 1890 |
+
}
|
| 1891 |
+
|
| 1892 |
+
// Helper function to create single-progress gauge section
|
| 1893 |
+
function createGaugeSection(aiProbability, humanProbability, mixedProbability, gaugeColor, gaugeDegree) {
|
| 1894 |
+
// Determine which probability is highest
|
| 1895 |
+
let maxValue, maxColor, maxLabel;
|
| 1896 |
+
|
| 1897 |
+
if (aiProbability >= humanProbability && aiProbability >= mixedProbability) {
|
| 1898 |
+
maxValue = aiProbability;
|
| 1899 |
+
maxColor = 'var(--danger)';
|
| 1900 |
+
maxLabel = 'AI Probability';
|
| 1901 |
+
} else if (humanProbability >= aiProbability && humanProbability >= mixedProbability) {
|
| 1902 |
+
maxValue = humanProbability;
|
| 1903 |
+
maxColor = 'var(--success)';
|
| 1904 |
+
maxLabel = 'Human Probability';
|
| 1905 |
+
} else {
|
| 1906 |
+
maxValue = mixedProbability;
|
| 1907 |
+
maxColor = 'var(--primary)';
|
| 1908 |
+
maxLabel = 'Mixed Probability';
|
| 1909 |
+
}
|
| 1910 |
+
|
| 1911 |
+
// Calculate the degree for the progress (maxValue% of 360 degrees)
|
| 1912 |
+
const progressDegree = (maxValue / 100) * 360;
|
| 1913 |
+
|
| 1914 |
+
return `
|
| 1915 |
+
<div class="gauge-container">
|
| 1916 |
+
<div class="single-progress-gauge" style="
|
| 1917 |
+
background: conic-gradient(
|
| 1918 |
+
${maxColor} 0deg,
|
| 1919 |
+
${maxColor} ${progressDegree}deg,
|
| 1920 |
+
rgba(51, 65, 85, 0.3) ${progressDegree}deg,
|
| 1921 |
+
rgba(51, 65, 85, 0.3) 360deg
|
| 1922 |
+
);
|
| 1923 |
+
">
|
| 1924 |
+
<div class="gauge-inner">
|
| 1925 |
+
<div class="gauge-value" style="color: ${maxColor}">${maxValue}%</div>
|
| 1926 |
+
<div class="gauge-label">${maxLabel}</div>
|
| 1927 |
</div>
|
| 1928 |
+
</div>
|
| 1929 |
+
</div>
|
| 1930 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin: 1.5rem 0;">
|
| 1931 |
+
<div style="text-align: center; padding: 1rem; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border: 1px solid rgba(239, 68, 68, 0.3);">
|
| 1932 |
+
<div style="font-size: 0.85rem; color: var(--danger); margin-bottom: 0.25rem; font-weight: 600;">AI</div>
|
| 1933 |
+
<div style="font-size: 1.4rem; font-weight: 700; color: var(--danger);">${aiProbability}%</div>
|
| 1934 |
+
</div>
|
| 1935 |
+
<div style="text-align: center; padding: 1rem; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.3);">
|
| 1936 |
+
<div style="font-size: 0.85rem; color: var(--success); margin-bottom: 0.25rem; font-weight: 600;">Human</div>
|
| 1937 |
+
<div style="font-size: 1.4rem; font-weight: 700; color: var(--success);">${humanProbability}%</div>
|
| 1938 |
+
</div>
|
| 1939 |
+
<div style="text-align: center; padding: 1rem; background: rgba(6, 182, 212, 0.1); border-radius: 8px; border: 1px solid rgba(6, 182, 212, 0.3);">
|
| 1940 |
+
<div style="font-size: 0.85rem; color: var(--primary); margin-bottom: 0.25rem; font-weight: 600;">Mixed</div>
|
| 1941 |
+
<div style="font-size: 1.4rem; font-weight: 700; color: var(--primary);">${mixedProbability}%</div>
|
| 1942 |
+
</div>
|
| 1943 |
+
</div>
|
| 1944 |
+
<style>
|
| 1945 |
+
.single-progress-gauge {
|
| 1946 |
+
width: 220px;
|
| 1947 |
+
height: 220px;
|
| 1948 |
+
margin: 0 auto 2rem;
|
| 1949 |
+
position: relative;
|
| 1950 |
+
border-radius: 50%;
|
| 1951 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
| 1952 |
+
}
|
| 1953 |
+
|
| 1954 |
+
.gauge-inner {
|
| 1955 |
+
position: absolute;
|
| 1956 |
+
width: 170px;
|
| 1957 |
+
height: 170px;
|
| 1958 |
+
background: var(--bg-panel);
|
| 1959 |
+
border-radius: 50%;
|
| 1960 |
+
top: 50%;
|
| 1961 |
+
left: 50%;
|
| 1962 |
+
transform: translate(-50%, -50%);
|
| 1963 |
+
display: flex;
|
| 1964 |
+
flex-direction: column;
|
| 1965 |
+
align-items: center;
|
| 1966 |
+
justify-content: center;
|
| 1967 |
+
}
|
| 1968 |
+
|
| 1969 |
+
.gauge-value {
|
| 1970 |
+
font-size: 3rem;
|
| 1971 |
+
font-weight: 800;
|
| 1972 |
+
}
|
| 1973 |
+
|
| 1974 |
+
.gauge-label {
|
| 1975 |
+
font-size: 0.9rem;
|
| 1976 |
+
color: var(--text-secondary);
|
| 1977 |
+
margin-top: 0.25rem;
|
| 1978 |
+
}
|
| 1979 |
+
</style>
|
| 1980 |
+
`;
|
| 1981 |
+
}
|
| 1982 |
+
|
| 1983 |
+
|
| 1984 |
+
// Helper function to create info grid
|
| 1985 |
+
function createInfoGrid(verdict, confidence, confidenceClass, domain, mixedProbability) {
|
| 1986 |
+
const mixedContentInfo = mixedProbability > 10 ?
|
| 1987 |
+
`<div style="margin-top: 0.5rem; font-size: 0.85rem; color: var(--primary);">
|
| 1988 |
+
🔀 ${mixedProbability}% Mixed Content Detected
|
| 1989 |
+
</div>` : '';
|
| 1990 |
+
|
| 1991 |
+
return `
|
| 1992 |
+
<div class="result-info-grid">
|
| 1993 |
+
<div class="info-card">
|
| 1994 |
+
<div class="info-label">Verdict</div>
|
| 1995 |
+
<div class="info-value verdict-text">${verdict}</div>
|
| 1996 |
+
${mixedContentInfo}
|
| 1997 |
+
</div>
|
| 1998 |
+
<div class="info-card">
|
| 1999 |
+
<div class="info-label">Confidence Level</div>
|
| 2000 |
+
<div class="info-value">
|
| 2001 |
+
<span class="confidence-badge ${confidenceClass}">${confidence}%</span>
|
| 2002 |
</div>
|
| 2003 |
</div>
|
| 2004 |
+
<div class="info-card">
|
| 2005 |
+
<div class="info-label">Content Domain</div>
|
| 2006 |
+
<div class="info-value domain-text">${formatDomainName(domain)}</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2007 |
</div>
|
| 2008 |
</div>
|
| 2009 |
`;
|
| 2010 |
}
|
| 2011 |
+
|
| 2012 |
+
// Helper function to create download actions
|
| 2013 |
+
function createDownloadActions() {
|
| 2014 |
+
return `
|
| 2015 |
+
<div class="download-actions">
|
| 2016 |
+
<button class="download-btn" onclick="downloadReport('json')">
|
| 2017 |
+
📄 Download JSON
|
| 2018 |
+
</button>
|
| 2019 |
+
<button class="download-btn" onclick="downloadReport('pdf')">
|
| 2020 |
+
📑 Download PDF Report
|
| 2021 |
+
</button>
|
| 2022 |
+
</div>
|
| 2023 |
+
`;
|
| 2024 |
+
}
|
| 2025 |
+
|
| 2026 |
+
// Helper function to format domain names
|
| 2027 |
+
function formatDomainName(domain) {
|
| 2028 |
+
return domain.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
| 2029 |
+
}
|
| 2030 |
+
|
| 2031 |
function createEnhancedReasoningHTML(ensemble, analysis, reasoning) {
|
| 2032 |
+
// Use reasoning data if available
|
| 2033 |
if (reasoning && reasoning.summary) {
|
| 2034 |
+
// Process the summary into bullet points
|
| 2035 |
+
const bulletPoints = formatSummaryAsBulletPoints(reasoning.summary, ensemble, analysis);
|
| 2036 |
+
|
| 2037 |
+
// Process key indicators with markdown formatting
|
| 2038 |
+
let processedIndicators = [];
|
| 2039 |
+
if (reasoning.key_indicators && reasoning.key_indicators.length > 0) {
|
| 2040 |
+
processedIndicators = reasoning.key_indicators.map(indicator => {
|
| 2041 |
+
let processedIndicator = indicator;
|
| 2042 |
+
|
| 2043 |
+
// Remove HTML entities
|
| 2044 |
+
processedIndicator = processedIndicator.replace(/*/g, '*')
|
| 2045 |
+
.replace(/*/g, '*');
|
| 2046 |
+
|
| 2047 |
+
// Process bold formatting
|
| 2048 |
+
processedIndicator = processedIndicator.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
| 2049 |
+
.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
|
| 2050 |
+
|
| 2051 |
+
// Clean up remaining asterisks
|
| 2052 |
+
processedIndicator = processedIndicator.replace(/\*\*/g, '')
|
| 2053 |
+
.replace(/\*(?![^<]*>)/g, '');
|
| 2054 |
+
|
| 2055 |
+
// Replace underscores with spaces
|
| 2056 |
+
processedIndicator = processedIndicator.replace(/_/g, ' ');
|
| 2057 |
+
|
| 2058 |
+
|
| 2059 |
+
return processedIndicator;
|
| 2060 |
+
});
|
| 2061 |
+
}
|
| 2062 |
+
|
| 2063 |
return `
|
| 2064 |
<div class="reasoning-box enhanced">
|
| 2065 |
<div class="reasoning-header">
|
|
|
|
| 2073 |
<div class="verdict-text">${ensemble.final_verdict}</div>
|
| 2074 |
<div class="probability">AI Probability: <span class="probability-value">${(ensemble.ai_probability * 100).toFixed(2)}%</span></div>
|
| 2075 |
</div>
|
| 2076 |
+
<div class="reasoning-bullet-points">
|
| 2077 |
+
${bulletPoints}
|
| 2078 |
</div>
|
| 2079 |
+
${processedIndicators.length > 0 ? `
|
| 2080 |
<div class="metrics-breakdown">
|
| 2081 |
+
<div class="breakdown-header" style="text-align: center; font-weight: 700; color: var(--text-secondary); margin-bottom: 1rem;">
|
| 2082 |
+
KEY INDICATORS
|
| 2083 |
+
</div>
|
| 2084 |
+
${processedIndicators.map(indicator => {
|
| 2085 |
+
// Split indicator into metric name and sub-metric details
|
| 2086 |
+
const colonIndex = indicator.indexOf(':');
|
| 2087 |
+
if (colonIndex !== -1) {
|
| 2088 |
+
const metricName = indicator.substring(0, colonIndex).trim();
|
| 2089 |
+
const metricDetails = indicator.substring(colonIndex + 1).trim();
|
| 2090 |
+
|
| 2091 |
+
return `
|
| 2092 |
+
<div style="margin-bottom: 1rem; text-align: left;">
|
| 2093 |
+
<div style="font-weight: 700; color: #fff; text-align: center; margin-bottom: 0.5rem; font-size: 1rem;">
|
| 2094 |
+
${metricName}
|
| 2095 |
+
</div>
|
| 2096 |
+
<div style="color: var(--text-secondary); font-size: 0.9rem; line-height: 1.4; text-align: left;">
|
| 2097 |
+
${metricDetails}
|
| 2098 |
+
</div>
|
| 2099 |
</div>
|
| 2100 |
+
`;
|
| 2101 |
+
} else {
|
| 2102 |
+
// If no colon, treat as general indicator
|
| 2103 |
+
return `
|
| 2104 |
+
<div style="margin-bottom: 1rem; text-align: left;">
|
| 2105 |
+
<div style="color: var(--text-secondary); font-size: 0.9rem; line-height: 1.4;">
|
| 2106 |
+
${indicator}
|
| 2107 |
+
</div>
|
| 2108 |
+
</div>
|
| 2109 |
+
`;
|
| 2110 |
+
}
|
| 2111 |
}).join('')}
|
| 2112 |
</div>
|
| 2113 |
` : ''}
|
|
|
|
| 2124 |
return `
|
| 2125 |
<div class="reasoning-box">
|
| 2126 |
<div class="reasoning-title">💡 Detection Reasoning</div>
|
| 2127 |
+
<p class="reasoning-text" style="text-align: left;">
|
| 2128 |
Analysis based on 6-metric ensemble with domain-aware calibration.
|
| 2129 |
The system evaluated linguistic patterns, statistical features, and semantic structures
|
| 2130 |
to determine content authenticity with ${(ensemble.overall_confidence * 100).toFixed(1)}% confidence.
|
|
|
|
| 2132 |
</div>
|
| 2133 |
`;
|
| 2134 |
}
|
| 2135 |
+
|
| 2136 |
+
// Helper function to format summary as bullet points
|
| 2137 |
+
function formatSummaryAsBulletPoints(summary, ensemble, analysis) {
|
| 2138 |
+
let processedSummary = summary;
|
| 2139 |
+
|
| 2140 |
+
// Remove any existing HTML entities for asterisks first
|
| 2141 |
+
processedSummary = processedSummary.replace(/*/g, '*')
|
| 2142 |
+
.replace(/*/g, '*');
|
| 2143 |
+
|
| 2144 |
+
// Process markdown bold formatting
|
| 2145 |
+
processedSummary = processedSummary.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
| 2146 |
+
.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
|
| 2147 |
+
|
| 2148 |
+
// Final cleanup: remove any remaining standalone asterisks that weren't processed
|
| 2149 |
+
processedSummary = processedSummary.replace(/\*\*/g, '')
|
| 2150 |
+
.replace(/\*(?![^<]*>)/g, '');
|
| 2151 |
+
|
| 2152 |
+
// Split the summary into sentences/phrases for bullet points
|
| 2153 |
+
const sentences = processedSummary.split(/\.\s+/);
|
| 2154 |
+
|
| 2155 |
+
// Create bullet points from key information
|
| 2156 |
+
const bulletPoints = [];
|
| 2157 |
+
|
| 2158 |
+
// Add confidence level as first bullet
|
| 2159 |
+
const confidenceLevel = ensemble.overall_confidence >= 0.7 ? 'High Confidence' :
|
| 2160 |
+
ensemble.overall_confidence >= 0.4 ? 'Medium Confidence' : 'Low Confidence';
|
| 2161 |
+
bulletPoints.push(`<div class="bullet-point">• ${confidenceLevel}</div>`);
|
| 2162 |
+
|
| 2163 |
+
// Add verdict as second bullet
|
| 2164 |
+
bulletPoints.push(`<div class="bullet-point">• ${ensemble.final_verdict}</div>`);
|
| 2165 |
+
|
| 2166 |
+
// Add AI probability as third bullet
|
| 2167 |
+
bulletPoints.push(`<div class="bullet-point">• AI Probability: ${(ensemble.ai_probability * 100).toFixed(2)}%</div>`);
|
| 2168 |
+
|
| 2169 |
+
// Add the main analysis sentences as individual bullets
|
| 2170 |
+
sentences.forEach(sentence => {
|
| 2171 |
+
if (sentence.trim() &&
|
| 2172 |
+
!sentence.includes('confidence') &&
|
| 2173 |
+
!sentence.includes(ensemble.final_verdict) &&
|
| 2174 |
+
!sentence.includes('AI probability')) {
|
| 2175 |
+
// Clean up the sentence and add as bullet
|
| 2176 |
+
let cleanSentence = sentence.trim();
|
| 2177 |
+
if (!cleanSentence.endsWith('.')) {
|
| 2178 |
+
cleanSentence += '.';
|
| 2179 |
+
}
|
| 2180 |
+
bulletPoints.push(`<div class="bullet-point">• ${cleanSentence}</div>`);
|
| 2181 |
+
}
|
| 2182 |
+
});
|
| 2183 |
+
|
| 2184 |
+
return bulletPoints.join('');
|
| 2185 |
+
}
|
| 2186 |
+
|
| 2187 |
function displayHighlightedText(html) {
|
| 2188 |
document.getElementById('highlighted-report').innerHTML = `
|
| 2189 |
${createDefaultLegend()}
|
|
|
|
| 2193 |
${getHighlightStyles()}
|
| 2194 |
`;
|
| 2195 |
}
|
| 2196 |
+
|
| 2197 |
function createDefaultLegend() {
|
| 2198 |
return `
|
| 2199 |
<div class="highlight-legend">
|
|
|
|
| 2232 |
</div>
|
| 2233 |
`;
|
| 2234 |
}
|
| 2235 |
+
|
| 2236 |
function getHighlightStyles() {
|
| 2237 |
return `
|
| 2238 |
<style>
|
|
|
|
| 2288 |
</style>
|
| 2289 |
`;
|
| 2290 |
}
|
| 2291 |
+
|
| 2292 |
function displayMetricsCarousel(metrics, analysis, ensemble) {
|
| 2293 |
const metricOrder = ['structural', 'perplexity', 'entropy', 'semantic_analysis', 'linguistic', 'multi_perturbation_stability'];
|
| 2294 |
const availableMetrics = metricOrder.filter(key => metrics[key]);
|
| 2295 |
totalMetrics = availableMetrics.length;
|
| 2296 |
+
|
| 2297 |
if (totalMetrics === 0) {
|
| 2298 |
document.getElementById('metrics-report').innerHTML = `
|
| 2299 |
<div class="empty-state">
|
|
|
|
| 2302 |
`;
|
| 2303 |
return;
|
| 2304 |
}
|
| 2305 |
+
|
| 2306 |
let carouselHTML = `
|
| 2307 |
<div class="metrics-carousel-container">
|
| 2308 |
<div class="metrics-carousel-content">
|
| 2309 |
`;
|
| 2310 |
+
|
| 2311 |
availableMetrics.forEach((metricKey, index) => {
|
| 2312 |
const metric = metrics[metricKey];
|
| 2313 |
if (!metric) return;
|
| 2314 |
+
|
| 2315 |
const aiProb = (metric.ai_probability * 100).toFixed(1);
|
| 2316 |
const humanProb = (metric.human_probability * 100).toFixed(1);
|
| 2317 |
+
const mixedProb = (metric.mixed_probability * 100).toFixed(1);
|
| 2318 |
const confidence = (metric.confidence * 100).toFixed(1);
|
| 2319 |
const weight = ensemble.metric_contributions && ensemble.metric_contributions[metricKey] ?
|
| 2320 |
+
(ensemble.metric_contributions[metricKey].weight * 100).toFixed(1) : '0.0';
|
| 2321 |
+
|
| 2322 |
+
// Determine verdict based on probabilities
|
| 2323 |
+
let verdictText, verdictClass;
|
| 2324 |
+
if (metric.mixed_probability > 0.3) {
|
| 2325 |
+
verdictText = 'MIXED';
|
| 2326 |
+
verdictClass = 'verdict-mixed';
|
| 2327 |
+
} else if (metric.ai_probability >= 0.6) {
|
| 2328 |
+
verdictText = 'AI';
|
| 2329 |
+
verdictClass = 'verdict-ai';
|
| 2330 |
+
} else if (metric.ai_probability >= 0.4) {
|
| 2331 |
+
verdictText = 'UNCERTAIN';
|
| 2332 |
+
verdictClass = 'verdict-uncertain';
|
| 2333 |
+
} else {
|
| 2334 |
+
verdictText = 'HUMAN';
|
| 2335 |
+
verdictClass = 'verdict-human';
|
| 2336 |
+
}
|
| 2337 |
+
|
| 2338 |
carouselHTML += `
|
| 2339 |
<div class="metric-slide ${index === 0 ? 'active' : ''}" data-metric-index="${index}">
|
| 2340 |
<div class="metric-result-card">
|
|
|
|
| 2344 |
<div class="metric-description">
|
| 2345 |
${getMetricDescription(metricKey)}
|
| 2346 |
</div>
|
| 2347 |
+
|
| 2348 |
+
<!-- Enhanced Probability Display with Mixed -->
|
| 2349 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin: 1rem 0;">
|
| 2350 |
+
<div style="text-align: center;">
|
| 2351 |
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">AI</div>
|
| 2352 |
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
|
| 2353 |
<div style="background: var(--danger); height: 100%; width: ${aiProb}%; transition: width 0.5s;"></div>
|
| 2354 |
</div>
|
| 2355 |
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${aiProb}%</div>
|
| 2356 |
</div>
|
| 2357 |
+
<div style="text-align: center;">
|
| 2358 |
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Human</div>
|
| 2359 |
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
|
| 2360 |
<div style="background: var(--success); height: 100%; width: ${humanProb}%; transition: width 0.5s;"></div>
|
| 2361 |
</div>
|
| 2362 |
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${humanProb}%</div>
|
| 2363 |
</div>
|
| 2364 |
+
<div style="text-align: center;">
|
| 2365 |
+
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Mixed</div>
|
| 2366 |
+
<div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;">
|
| 2367 |
+
<div style="background: var(--primary); height: 100%; width: ${mixedProb}%; transition: width 0.5s;"></div>
|
| 2368 |
+
</div>
|
| 2369 |
+
<div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${mixedProb}%</div>
|
| 2370 |
+
</div>
|
| 2371 |
</div>
|
| 2372 |
+
|
| 2373 |
<div style="display: flex; justify-content: space-between; align-items: center; margin: 0.75rem 0;">
|
| 2374 |
<span class="metric-verdict ${verdictClass}">${verdictText}</span>
|
| 2375 |
<span style="font-size: 0.85rem; color: var(--text-secondary);">Confidence: ${confidence}% | Weight: ${weight}%</span>
|
|
|
|
| 2380 |
</div>
|
| 2381 |
`;
|
| 2382 |
});
|
| 2383 |
+
|
| 2384 |
carouselHTML += `
|
| 2385 |
</div>
|
| 2386 |
<div class="metrics-carousel-nav">
|
|
|
|
| 2390 |
</div>
|
| 2391 |
</div>
|
| 2392 |
`;
|
| 2393 |
+
|
| 2394 |
document.getElementById('metrics-report').innerHTML = carouselHTML;
|
| 2395 |
updateCarouselButtons();
|
| 2396 |
}
|
| 2397 |
+
|
| 2398 |
function navigateMetrics(direction) {
|
| 2399 |
const newMetricIndex = currentMetricIndex + direction;
|
| 2400 |
if (newMetricIndex >= 0 && newMetricIndex < totalMetrics) {
|
|
|
|
| 2402 |
updateMetricCarousel();
|
| 2403 |
}
|
| 2404 |
}
|
| 2405 |
+
|
| 2406 |
function updateMetricCarousel() {
|
| 2407 |
const slides = document.querySelectorAll('.metric-slide');
|
| 2408 |
slides.forEach((slide, index) => {
|
|
|
|
| 2419 |
positionElement.textContent = `${currentMetricIndex + 1} / ${totalMetrics}`;
|
| 2420 |
}
|
| 2421 |
}
|
| 2422 |
+
|
| 2423 |
function updateCarouselButtons() {
|
| 2424 |
const prevBtn = document.querySelector('.prev-btn');
|
| 2425 |
const nextBtn = document.querySelector('.next-btn');
|
|
|
|
| 2430 |
nextBtn.disabled = currentMetricIndex === totalMetrics - 1;
|
| 2431 |
}
|
| 2432 |
}
|
| 2433 |
+
|
| 2434 |
function renderMetricDetails(metricName, details) {
|
| 2435 |
if (!details || Object.keys(details).length === 0) return '';
|
| 2436 |
+
|
| 2437 |
// Key metrics to show for each type
|
| 2438 |
const importantKeys = {
|
| 2439 |
'structural': ['burstiness_score', 'length_uniformity', 'avg_sentence_length', 'std_sentence_length'],
|
|
|
|
| 2441 |
'entropy': ['token_diversity', 'sequence_unpredictability', 'char_entropy'],
|
| 2442 |
'semantic_analysis': ['coherence_score', 'consistency_score', 'repetition_score'],
|
| 2443 |
'linguistic': ['pos_diversity', 'syntactic_complexity', 'grammatical_consistency'],
|
| 2444 |
+
'multi_perturbation_stability': ['stability_score', 'curvature_score', 'likelihood_ratio', 'perturbation_variance', 'mixed_probability']
|
| 2445 |
};
|
| 2446 |
+
|
| 2447 |
const keysToShow = importantKeys[metricName] || Object.keys(details).slice(0, 6);
|
| 2448 |
+
|
| 2449 |
let detailsHTML = '<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border);">';
|
| 2450 |
detailsHTML += '<div style="font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.75rem;">📈 Detailed Metrics:</div>';
|
| 2451 |
detailsHTML += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; font-size: 0.85rem;">';
|
| 2452 |
+
|
| 2453 |
keysToShow.forEach(key => {
|
| 2454 |
if (details[key] !== undefined && details[key] !== null) {
|
| 2455 |
+
let value = details[key];
|
| 2456 |
+
let displayValue;
|
| 2457 |
+
|
| 2458 |
+
// Format values appropriately
|
| 2459 |
+
if (typeof value === 'number') {
|
| 2460 |
+
if (key.includes('score') || key.includes('ratio') || key.includes('probability')) {
|
| 2461 |
+
displayValue = (value * 100).toFixed(2) + '%';
|
| 2462 |
+
} else if (value < 1 && value > 0) {
|
| 2463 |
+
displayValue = value.toFixed(4);
|
| 2464 |
+
} else {
|
| 2465 |
+
displayValue = value.toFixed(2);
|
| 2466 |
+
}
|
| 2467 |
+
} else {
|
| 2468 |
+
displayValue = value;
|
| 2469 |
+
}
|
| 2470 |
+
|
| 2471 |
const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
|
| 2472 |
detailsHTML += `
|
| 2473 |
<div style="background: rgba(15, 23, 42, 0.6); padding: 0.5rem; border-radius: 6px;">
|
| 2474 |
<div style="color: var(--text-muted); font-size: 0.75rem; margin-bottom: 0.25rem;">${label}</div>
|
| 2475 |
+
<div style="color: var(--primary); font-weight: 700;">${displayValue}</div>
|
| 2476 |
</div>
|
| 2477 |
`;
|
| 2478 |
}
|
| 2479 |
});
|
| 2480 |
+
|
| 2481 |
detailsHTML += '</div></div>';
|
| 2482 |
return detailsHTML;
|
| 2483 |
}
|
| 2484 |
+
|
| 2485 |
function getMetricDescription(metricName) {
|
| 2486 |
const descriptions = {
|
| 2487 |
structural: 'Analyzes sentence structure, length patterns, and statistical features.',
|
|
|
|
| 2493 |
};
|
| 2494 |
return descriptions[metricName] || 'Metric analysis complete.';
|
| 2495 |
}
|
| 2496 |
+
|
| 2497 |
function formatMetricName(name) {
|
| 2498 |
const names = {
|
| 2499 |
structural: 'Structural Analysis',
|
|
|
|
| 2505 |
};
|
| 2506 |
return names[name] || name.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
| 2507 |
}
|
| 2508 |
+
|
|
|
|
|
|
|
| 2509 |
async function downloadReport(format) {
|
| 2510 |
if (!currentAnalysisData) {
|
| 2511 |
alert('No analysis data available');
|
| 2512 |
return;
|
| 2513 |
}
|
| 2514 |
+
|
| 2515 |
try {
|
| 2516 |
const analysisId = currentAnalysisData.analysis_id;
|
| 2517 |
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
| 2518 |
+
|
| 2519 |
// For JSON, download directly from current data
|
| 2520 |
if (format === 'json') {
|
| 2521 |
const data = {
|
|
|
|
| 2530 |
await downloadBlob(blob, filename);
|
| 2531 |
return;
|
| 2532 |
}
|
| 2533 |
+
|
| 2534 |
// Get the original text for report generation
|
| 2535 |
const activeTab = document.querySelector('.input-tab.active').dataset.tab;
|
| 2536 |
let textToSend = '';
|
|
|
|
| 2540 |
textToSend = currentAnalysisData.detection_result?.processed_text?.text ||
|
| 2541 |
'Uploaded file content - see analysis for details';
|
| 2542 |
}
|
| 2543 |
+
|
| 2544 |
// For PDF, request from server
|
| 2545 |
const formData = new FormData();
|
| 2546 |
formData.append('analysis_id', analysisId);
|
| 2547 |
formData.append('text', textToSend);
|
| 2548 |
formData.append('formats', format);
|
| 2549 |
formData.append('include_highlights', document.getElementById('enable-highlighting').checked.toString());
|
| 2550 |
+
|
| 2551 |
const response = await fetch(`${API_BASE}/api/report/generate`, {
|
| 2552 |
method: 'POST',
|
| 2553 |
body: formData
|
| 2554 |
});
|
| 2555 |
+
|
| 2556 |
if (!response.ok) {
|
| 2557 |
throw new Error('Report generation failed');
|
| 2558 |
}
|
| 2559 |
+
|
| 2560 |
const result = await response.json();
|
| 2561 |
if (result.reports && result.reports[format]) {
|
| 2562 |
const filename = result.reports[format];
|
|
|
|
| 2575 |
alert('Failed to download report. Please try again.');
|
| 2576 |
}
|
| 2577 |
}
|
| 2578 |
+
|
| 2579 |
async function downloadBlob(blob, filename) {
|
| 2580 |
try {
|
| 2581 |
const url = URL.createObjectURL(blob);
|
|
|
|
| 2595 |
alert('Download failed. Please try again.');
|
| 2596 |
}
|
| 2597 |
}
|
| 2598 |
+
|
| 2599 |
function showDownloadSuccess(filename) {
|
| 2600 |
const notification = document.createElement('div');
|
| 2601 |
notification.style.cssText = `
|
|
|
|
| 2618 |
</div>
|
| 2619 |
`;
|
| 2620 |
document.body.appendChild(notification);
|
| 2621 |
+
|
| 2622 |
if (!document.querySelector('#download-animation')) {
|
| 2623 |
const style = document.createElement('style');
|
| 2624 |
style.id = 'download-animation';
|
|
|
|
| 2630 |
`;
|
| 2631 |
document.head.appendChild(style);
|
| 2632 |
}
|
| 2633 |
+
|
| 2634 |
setTimeout(() => {
|
| 2635 |
if (notification.parentNode) {
|
| 2636 |
notification.parentNode.removeChild(notification);
|
| 2637 |
}
|
| 2638 |
}, 3000);
|
| 2639 |
}
|
| 2640 |
+
|
| 2641 |
// Smooth scrolling for anchor links
|
| 2642 |
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
| 2643 |
anchor.addEventListener('click', function (e) {
|
|
|
|
| 2651 |
}
|
| 2652 |
});
|
| 2653 |
});
|
| 2654 |
+
|
| 2655 |
// Initialize - show landing page by default
|
| 2656 |
showLanding();
|
| 2657 |
</script>
|