satyakimitra commited on
Commit
38d5865
·
1 Parent(s): 5818013

UI updated

Browse files
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.GPTMASK,
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.TRANSFORMER,
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
- # DOCUMENT-ALIGNED: Metric weights from technical specification
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
- # 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,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
- # Enhanced 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,13 +460,13 @@ class ModelAttributor:
460
  domain = domain,
461
  )
462
 
463
- # Domain-aware prediction - FIXED: 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 - FIXED
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 DOMAIN CALIBRATION - FIXED for all domains
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 - FIXED
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
- # FIXED: Only return UNKNOWN if the best score is very low
829
- # Use a more reasonable threshold for attribution
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 based on score dominance
859
- if len(sorted_models) > 1:
860
  second_score = sorted_models[1][1]
861
- margin = best_score - second_score
862
- # Confidence based on both absolute score and margin
863
- confidence = min(1.0, best_score * 0.6 + margin * 2.0)
 
864
  else:
865
- confidence = best_score * 0.7
866
 
867
- # FIXED: Don't downgrade to UNKNOWN based on confidence alone
868
- # If we have a model with reasonable probability, show it even with low confidence
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 - FIXED to show proper formatting
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
- reasoning.append("")
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 probability models
905
  if (score < 0.01):
906
  continue
907
-
908
  display_name = model_name.replace("-", " ").replace("_", " ").title()
909
  percentage = score * 100
910
 
911
- # Single line format: "• Model Name: XX.X%"
912
  reasoning.append(f"• **{display_name}**: {percentage:.1f}%")
913
 
914
  reasoning.append("")
915
 
916
- # Domain-specific insights - FIXED: Removed duplicate header
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
- elif (domain in [Domain.CREATIVE, Domain.MARKETING, Domain.SOCIAL_MEDIA, Domain.BLOG_PERSONAL]):
924
- reasoning.append("• Emphasis on linguistic diversity and stylistic variation")
925
-
926
- elif (domain in [Domain.LEGAL, Domain.MEDICAL]):
927
- reasoning.append("• Focus on formal language and specialized terminology")
928
-
929
- elif (domain in [Domain.BUSINESS, Domain.JOURNALISM, Domain.TUTORIAL]):
930
- reasoning.append("• Balanced analysis across multiple attribution factors")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.01, "very-high-ai", "#fecaca", "Very likely AI-generated"),
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 EnsembleClassifier(primary_method = "confidence_calibrated",
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
- # Get domain-appropriate weights for enabled metrics
116
- if enabled_metrics is None:
117
- enabled_metrics = {name: True for name in metric_results.keys()}
118
-
119
- weights = get_active_metric_weights(self.domain, enabled_metrics)
120
-
121
- # Split text into sentences
122
- sentences = self._split_sentences(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- if not sentences:
125
- return []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- # Calculate probabilities for each sentence using ENSEMBLE METHODS
128
- highlighted_sentences = list()
129
-
130
- for idx, sentence in enumerate(sentences):
131
- if use_sentence_level:
132
- # Use ENSEMBLE for sentence-level analysis
133
- ai_prob, human_prob, mixed_prob, confidence, breakdown = self._calculate_sentence_ensemble_probability(sentence = sentence,
134
- metric_results = metric_results,
135
- weights = weights,
136
- ensemble_result = ensemble_result,
137
- )
138
- else:
139
- # Use document-level ensemble probabilities
140
- ai_prob, human_prob, mixed_prob, confidence, breakdown = self._get_document_ensemble_probability(ensemble_result = ensemble_result,
141
- metric_results = metric_results,
142
- weights = weights,
143
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
- # Apply domain-specific adjustments
146
- ai_prob = self._apply_domain_specific_adjustments(sentence, ai_prob, len(sentence.split()))
147
 
148
- # Determine if this is mixed content
149
- is_mixed_content = (mixed_prob > self.MIXED_THRESHOLD)
 
150
 
151
- # Get confidence level
152
- confidence_level = get_confidence_level(confidence)
153
 
154
- # Get color class (consider mixed content)
155
- color_class, color_hex, tooltip_base = self._get_color_for_probability(probability = ai_prob,
156
- is_mixed_content = is_mixed_content,
157
- mixed_prob = mixed_prob,
158
- )
159
 
160
- # Generate enhanced tooltip
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
- highlighted_sentences.append(HighlightedSentence(text = sentence,
173
- ai_probability = ai_prob,
174
- human_probability = human_prob,
175
- mixed_probability = mixed_prob,
176
- confidence = confidence,
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
- # IMPROVED: Better handling of short sentences
197
  if (sentence_length < 3):
198
- # Return neutral probability for very short sentences with low confidence
199
- return 0.5, 0.5, 0.0, 0.3, {"short_sentence": 0.5}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Compute sentence-level probability for this metric
208
- sentence_prob = self._compute_sentence_metric(metric_name = name,
209
- sentence = sentence,
210
- result = doc_result,
211
- weight = weights.get(name, 0.0),
212
- )
213
-
214
- # Create sentence-level MetricResult
215
- sentence_metric_results[name] = self._create_sentence_metric_result(metric_name = name,
216
- ai_prob = sentence_prob,
217
- doc_result = doc_result,
218
- )
 
 
 
 
219
 
220
- breakdown[name] = sentence_prob
 
 
 
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, ensemble_sentence_result.human_probability, ensemble_sentence_result.mixed_probability,
230
- ensemble_sentence_result.overall_confidence, breakdown)
 
 
 
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
- # Adjust confidence based on sentence characteristics
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
- # Sentence-level analysis typically has lower confidence
287
- return max(0.1, doc_confidence * 0.8)
 
 
 
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 - UPDATED FOR ALL DOMAINS
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
- # Technical terms more common in AI
346
- ai_prob *= 1.1
347
 
348
  elif self._has_code_like_patterns(sentence):
349
- ai_prob *= 1.15
350
 
351
- elif sentence_length > 35:
352
- ai_prob *= 1.05
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
- # Informal language more human-like
358
- ai_prob *= 0.7
359
 
360
  elif self._has_emotional_language(sentence):
361
- ai_prob *= 0.8
362
 
363
  elif (sentence_length < 10):
364
- ai_prob *= 0.8
365
 
366
  # Academic & formal domains
367
- elif self.domain in [Domain.ACADEMIC, Domain.LEGAL, Domain.MEDICAL]:
368
  if self._has_citation_patterns(sentence):
369
- # Citations more human-like
370
- ai_prob *= 0.8
371
 
372
  elif self._has_technical_terms(sentence_lower):
373
- ai_prob *= 1.1
374
 
375
  elif (sentence_length > 40):
376
- ai_prob *= 1.1
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
- # Jargon can be AI-like
382
- ai_prob *= 1.05
383
 
384
  elif self._has_ambiguous_phrasing(sentence_lower):
385
- # Ambiguity more human
386
- ai_prob *= 0.9
387
 
388
  elif (15 <= sentence_length <= 25):
389
- ai_prob *= 0.9
390
 
391
  # Tutorial & educational domains
392
  elif (self.domain == Domain.TUTORIAL):
393
  if self._has_instructional_language(sentence_lower):
394
- # Instructional tone more human
395
- ai_prob *= 0.85
396
 
397
  elif self._has_step_by_step_pattern(sentence):
398
- ai_prob *= 0.8
399
 
400
  elif self._has_examples(sentence):
401
- ai_prob *= 0.9
402
 
403
  # General domain - minimal adjustments
404
- elif self.domain == Domain.GENERAL:
405
  if self._has_complex_structure(sentence):
406
- ai_prob *= 0.9
407
 
408
  elif self._has_repetition(sentence):
409
- ai_prob *= 1.1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
 
411
- return max(0.0, min(1.0, ai_prob))
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 "uncertain", "#fef9c3", "Uncertain"
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
- tooltip_base: str, breakdown: Optional[Dict[str, float]] = None, is_mixed_content: bool = False) -> str:
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.primary_method}"
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 += min(0.2, clause_count * 0.05)
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) >= 10):
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 = 0.0
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()) < 100)):
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, multi_perturbation_stability_thresholds, features)
 
 
 
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, num_perturbations = 5)
 
 
 
 
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, perturbed_likelihoods)
240
- curvature_score = self._calculate_curvature_score(original_likelihood, perturbed_likelihoods)
241
- variance_score = np.var(perturbed_likelihoods) if len(perturbed_likelihoods) > 1 else 0.0
 
 
 
 
 
 
242
  avg_perturbed_likelihood = np.mean(perturbed_likelihoods)
 
 
243
 
244
  else:
245
- stability_score = 0.5
246
- curvature_score = 0.5
247
- variance_score = 0.1
248
- avg_perturbed_likelihood = original_likelihood
 
 
249
 
250
  # Calculate likelihood ratio
251
- likelihood_ratio = original_likelihood / avg_perturbed_likelihood if avg_perturbed_likelihood > 0 else 1.0
252
 
253
  # Chunk-based analysis for whole-text understanding
254
- chunk_stabilities = self._calculate_chunk_stability(processed_text, chunk_size=150)
255
- stability_variance = np.var(chunk_stabilities) if chunk_stabilities else 0.0
256
- avg_chunk_stability = np.mean(chunk_stabilities) if chunk_stabilities else stability_score
 
 
 
257
 
258
- # Normalize scores to 0-1 range
259
- normalized_stability = min(1.0, max(0.0, stability_score))
260
- normalized_curvature = min(1.0, max(0.0, curvature_score))
261
- normalized_likelihood_ratio = min(2.0, likelihood_ratio) / 2.0 # Normalize to 0-1
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 of text using GPT-2 with robust error handling
 
285
  """
286
  try:
287
  # Check text length before tokenization
288
  if (len(text.strip()) < 10):
289
- return 0.0
 
 
 
 
290
 
291
- # Configure tokenizer for proper padding
292
- tokenizer = self._configure_tokenizer_padding(self.gpt_tokenizer)
 
293
 
294
  # Tokenize text with proper settings
295
- encodings = tokenizer(text,
296
- return_tensors = 'pt',
297
- truncation = True,
298
- max_length = 512,
299
- padding = True,
300
- return_attention_mask = True,
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) < 5)):
308
- return 0.0
309
 
310
- # Calculate negative log likelihood
311
  with torch.no_grad():
312
- outputs = self.gpt_model(input_ids,
313
- attention_mask = attention_mask,
314
- labels = input_ids,
315
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
- loss = outputs.loss
 
318
 
319
- # Convert to positive log likelihood (higher = more likely)
320
- log_likelihood = -loss.item()
321
-
322
- # Reasonable range check (typical values are between -10 and 10)
323
- if (abs(log_likelihood) > 100):
324
- logger.warning(f"Extreme likelihood value detected: {log_likelihood}")
325
- return 0.0
326
 
327
- return log_likelihood
328
 
329
  except Exception as e:
330
  logger.warning(f"Likelihood calculation failed: {repr(e)}")
331
- return 0.0
332
 
333
 
334
  def _generate_perturbations(self, text: str, num_perturbations: int = 5) -> List[str]:
335
  """
336
- Generate perturbed versions of the text with robust error handling
 
 
 
 
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: RoBERTa-specific masked word replacement
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"RoBERTa masked perturbation failed: {repr(e)}")
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, words)
 
 
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 RoBERTa mask filling
 
433
  """
434
  perturbations = list()
435
 
436
  try:
437
- # RoBERTa uses <mask> token
438
- roberta_mask_token = "<mask>"
 
 
 
 
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
- # RoBERTa works better with proper sentence structure
465
  if not masked_text.endswith(('.', '!', '?')):
466
  masked_text += '.'
467
 
468
- # Tokenize with RoBERTa-specific settings
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), # Conservative 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"RoBERTa mask filling failed for position {pos}: {e}")
516
  continue
517
 
518
  except Exception as e:
519
- logger.warning(f"RoBERTa masked perturbations failed: {e}")
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 under perturbations : AI text tends to be less stable (larger likelihood drops)
596
  """
597
  if ((not perturbed_likelihoods) or (original_likelihood <= 0)):
598
- return 0.5
 
599
 
600
- # Calculate average likelihood drop
601
- likelihood_drops = [(original_likelihood - pl) / original_likelihood for pl in perturbed_likelihoods]
602
- avg_drop = np.mean(likelihood_drops) if likelihood_drops else 0.0
603
 
604
- # Higher drop = less stable = more AI-like : Normalize to 0-1 scale (assume max drop of 50%)
605
- stability_score = min(1.0, avg_drop / 0.5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
 
607
- return stability_score
 
 
 
 
 
 
 
 
 
 
 
 
608
 
609
 
610
  def _calculate_curvature_score(self, original_likelihood: float, perturbed_likelihoods: List[float]) -> float:
611
  """
612
- Calculate likelihood curvature score : AI text often has different curvature properties
613
  """
614
  if ((not perturbed_likelihoods) or (original_likelihood <= 0)):
615
- return 0.5
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
- # Higher variance = more curvature = potentially more AI-like : Normalize based on typical variance ranges
622
- curvature_score = min(1.0, change_variance * 10.0) # Adjust scaling factor as needed
 
 
 
 
 
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 to determine RAW MultiPerturbationStability score (0-1 scale) : Higher score = more AI-like
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 = list()
 
 
 
 
 
 
679
 
680
  # High stability score suggests AI (larger likelihood drops)
681
- if (features['stability_score'] > 0.6):
682
- ai_indicators.append(0.8)
683
-
684
- elif (features['stability_score'] > 0.3):
685
- ai_indicators.append(0.5)
 
 
 
 
686
 
687
  else:
688
- ai_indicators.append(0.2)
689
 
690
  # High curvature score suggests AI
691
- if (features['curvature_score'] > 0.7):
692
- ai_indicators.append(0.7)
693
-
694
- elif (features['curvature_score'] > 0.4):
695
- ai_indicators.append(0.4)
696
-
 
 
 
 
697
  else:
698
- ai_indicators.append(0.2)
699
 
700
  # High likelihood ratio suggests AI (original much more likely than perturbations)
701
- if (features['normalized_likelihood_ratio'] > 0.8):
702
- ai_indicators.append(0.9)
 
 
 
 
 
 
 
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
- if (features['stability_variance'] < 0.05):
712
- ai_indicators.append(0.7)
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
- # High perturbation variance suggests AI
721
- if (features['perturbation_variance'] > 0.1):
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
- raw_score = np.mean(ai_indicators) if ai_indicators else 0.5
732
- confidence = 1.0 - (np.std(ai_indicators) / 0.5) if ai_indicators else 0.5
 
 
 
 
 
 
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 when analysis is not possible
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.5,
780
- "curvature_score" : 0.5,
781
  "perturbation_variance" : 0.05,
782
- "avg_chunk_stability" : 0.5,
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
- # RoBERTa works better with proper punctuation
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 RoBERTa tokenizer
853
  """
854
  if not token:
855
  return ""
856
 
857
- # Remove RoBERTa-specific artifacts
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 and punctuation
864
- token = token.strip(' .,!?;:"\'')
865
 
866
- return token
 
 
 
 
 
 
 
 
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
- # Not too short
874
- return (perturbed_text and
875
- len(perturbed_text.strip()) > 10 and
876
- perturbed_text != original_text and
877
- len(perturbed_text) > len(original_text) * 0.5)
 
 
 
 
 
 
 
 
 
 
 
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, ModelType.CLASSIFIER, ModelType.SEQUENCE_CLASSIFICATION, ModelType.TRANSFORMER]):
 
 
 
 
 
 
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(detection_result)
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(detection_result = detection_result,
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(detection_result = detection_result,
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, detection_result: DetectionResult) -> List[DetailedMetric]:
130
  """
131
  Extract detailed metrics with sub-metrics from ACTUAL detection result
132
  """
133
  detailed_metrics = list()
134
- metric_results = detection_result.metric_results
135
- ensemble_result = detection_result.ensemble_result
136
 
137
  # Get actual metric weights from ensemble
138
- metric_weights = getattr(ensemble_result, 'metric_weights', {})
139
 
140
  # Extract actual metric data
141
- for metric_name, metric_result in metric_results.items():
142
- if metric_result.error is not None:
 
 
 
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 = metric_weights.get(metric_name, 0.0) * 100
 
 
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 ((hasattr(metric_result, 'details')) and metric_result.details):
193
- details = metric_result.details.copy()
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" : getattr(metric_result, 'score', 0.0) * 100,
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, detection_result: DetectionResult, reasoning: DetailedReasoning, detailed_metrics: List[DetailedMetric],
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 - use attribution_result
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 with ensemble integration
268
- ensemble_result = detection_result.ensemble_result
 
 
 
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" : ensemble_result.final_verdict,
276
- "ai_probability" : round(ensemble_result.ai_probability, 4),
277
- "human_probability" : round(ensemble_result.human_probability, 4),
278
- "mixed_probability" : round(ensemble_result.mixed_probability, 4),
279
- "overall_confidence" : round(ensemble_result.overall_confidence, 4),
280
- "uncertainty_score" : round(ensemble_result.uncertainty_score, 4),
281
- "consensus_level" : round(ensemble_result.consensus_level, 4),
282
- "domain" : detection_result.domain_prediction.primary_domain.value,
283
- "domain_confidence" : round(detection_result.domain_prediction.confidence, 4),
284
- "text_length" : detection_result.processed_text.word_count,
285
- "sentence_count" : detection_result.processed_text.sentence_count,
286
  },
287
  "ensemble_analysis" : {"method_used" : "confidence_calibrated",
288
- "metric_weights" : {name: round(weight, 4) for name, weight in ensemble_result.metric_weights.items()},
289
- "weighted_scores" : {name: round(score, 4) for name, score in ensemble_result.weighted_scores.items()},
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" : round(detection_result.processing_time, 3),
307
- "metrics_execution_time" : {name: round(time, 3) for name, time in detection_result.metrics_execution_time.items()},
308
- "warnings" : detection_result.warnings,
309
- "errors" : detection_result.errors,
310
  }
311
  }
312
 
@@ -323,7 +333,7 @@ class ReportGenerator:
323
  return output_path
324
 
325
 
326
- def _generate_pdf_report(self, detection_result: DetectionResult, reasoning: DetailedReasoning, detailed_metrics: List[DetailedMetric],
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 with ensemble integration
382
- ensemble_result = detection_result.ensemble_result
 
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:', ensemble_result.final_verdict],
392
- ['AI Probability:', f"{ensemble_result.ai_probability:.1%}"],
393
- ['Human Probability:', f"{ensemble_result.human_probability:.1%}"],
394
- ['Mixed Probability:', f"{ensemble_result.mixed_probability:.1%}"],
395
- ['Overall Confidence:', f"{ensemble_result.overall_confidence:.1%}"],
396
- ['Uncertainty Score:', f"{ensemble_result.uncertainty_score:.1%}"],
397
- ['Consensus Level:', f"{ensemble_result.consensus_level:.1%}"],
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:', detection_result.domain_prediction.primary_domain.value.title()],
414
- ['Domain Confidence:', f"{detection_result.domain_prediction.confidence:.1%}"],
415
- ['Word Count:', str(detection_result.processed_text.word_count)],
416
- ['Sentence Count:', str(detection_result.processed_text.sentence_count)],
417
- ['Processing Time:', f"{detection_result.processing_time:.2f}s"],
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(f"Method: Confidence Calibrated Aggregation", styles['Normal']))
432
  elements.append(Spacer(1, 0.1*inch))
433
 
434
  # Metric weights table
435
- if hasattr(ensemble_result, 'metric_weights') and ensemble_result.metric_weights:
 
436
  elements.append(Paragraph("Metric Weights", styles['Heading3']))
437
  weight_data = [['Metric', 'Weight']]
438
- for metric, weight in ensemble_result.metric_weights.items():
 
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: {detection_result.processing_time:.2f}s",
582
- ParagraphStyle('Footer', parent=styles['Normal'], fontSize=8, textColor=colors.gray)))
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.104.1
3
- uvicorn[standard]==0.24.0
4
- pydantic==2.5.0
5
- pydantic-settings==2.1.0
6
- python-multipart==0.0.6
7
-
8
- # Machine Learning & Transformers
9
- torch==2.1.0
10
- transformers==4.35.2
11
- sentence-transformers==2.3.1
12
- tokenizers==0.15.0
13
- huggingface-hub==0.20.3
14
 
15
  # NLP Libraries
16
- spacy==3.7.2
17
- nltk==3.8.1
18
- textstat==0.7.3
19
 
20
  # Scientific Computing
21
- numpy==1.24.3
22
- scipy==1.11.4
23
- scikit-learn==1.3.2
24
- pandas==2.1.3
25
 
26
  # Text Processing
27
- python-docx==1.1.0
28
  PyPDF2==3.0.1
29
- pdfplumber==0.10.3
30
- pymupdf==1.23.8
31
  python-magic==0.4.27
32
 
33
  # Language Detection
34
  langdetect==1.0.9
35
 
36
  # Visualization & Reporting
37
- matplotlib==3.8.2
38
  seaborn==0.13.0
39
- reportlab==4.0.7
40
 
41
  # Utilities
42
- python-dotenv==1.0.0
43
  aiofiles==23.2.1
44
- httpx==0.25.2
45
- tenacity==8.2.3
46
 
47
  # Logging & Monitoring
48
- loguru==0.7.2
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
- /* Enhanced Reasoning Styles */
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: center;
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: 600;
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. Our system analyzes linguistic patterns, statistical features, and semantic structures to determine authenticity."
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
- // Use ensemble values from your actual API response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- const confidenceLevel = parseFloat(confidence) >= 70 ? 'HIGH' :
1661
- parseFloat(confidence) >= 40 ? 'MEDIUM' : 'LOW';
1662
- const confidenceClass = confidenceLevel === 'HIGH' ? 'confidence-high' :
1663
- confidenceLevel === 'MEDIUM' ? 'confidence-medium' : 'confidence-low';
1664
- let attributionHTML = '';
1665
- if (attribution && attribution.predicted_model) {
1666
- const modelName = attribution.predicted_model.replace(/_/g, ' ').replace(/-/g, ' ').toUpperCase();
1667
- const modelConf = attribution.confidence ?
1668
- (attribution.confidence * 100).toFixed(1) : 'N/A';
1669
- let topModels = '';
1670
- if (attribution.model_probabilities) {
1671
- const sorted = Object.entries(attribution.model_probabilities)
1672
- .sort((a, b) => b[1] - a[1])
1673
- .slice(0, 3);
1674
- topModels = sorted.map(([model, prob]) =>
1675
- `<div class="model-match" style="margin-top: 0.5rem;">
1676
- <span class="model-name">${model.replace(/_/g, ' ').replace(/-/g, ' ').toUpperCase()}</span>
1677
- <span class="model-confidence">${(prob * 100).toFixed(1)}%</span>
1678
- </div>`
1679
- ).join('');
1680
- }
1681
- attributionHTML = `
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1682
  <div class="attribution-section">
1683
  <div class="attribution-title">🤖 AI Model Attribution</div>
1684
- ${topModels}
1685
- ${attribution.reasoning && attribution.reasoning.length > 0 ?
1686
- `<p style="color: var(--text-secondary); margin-top: 1rem; font-size: 0.9rem;">${attribution.reasoning[0]}</p>` : ''}
 
 
1687
  </div>
1688
  `;
1689
  }
1690
- document.getElementById('summary-report').innerHTML = `
1691
- <div class="result-summary">
1692
- <div class="gauge-container">
1693
- <div class="gauge-circle" style="--gauge-color: ${gaugeColor}; --gauge-degree: ${gaugeDegree}deg;">
1694
- <div class="gauge-inner">
1695
- <div class="gauge-value">${aiProbability}%</div>
1696
- <div class="gauge-label">AI Probability</div>
1697
- </div>
1698
- </div>
 
 
 
 
 
 
 
 
 
 
 
1699
  </div>
1700
- <div class="result-info-grid">
1701
- <div class="info-card">
1702
- <div class="info-label">Verdict</div>
1703
- <div class="info-value" style="font-size: 1.2rem;">${verdict}</div>
1704
- </div>
1705
- <div class="info-card">
1706
- <div class="info-label">Confidence Level</div>
1707
- <div class="info-value">
1708
- <span class="confidence-badge ${confidenceClass}">${confidence}%</span>
1709
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1710
  </div>
1711
- <div class="info-card">
1712
- <div class="info-label">Content Domain</div>
1713
- <div class="info-value" style="font-size: 1.1rem;">${formatDomainName(domain)}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1714
  </div>
1715
  </div>
1716
- ${createEnhancedReasoningHTML(ensemble, analysis, reasoning)}
1717
- ${attributionHTML}
1718
- <div class="download-actions">
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 actual reasoning data if available
1731
  if (reasoning && reasoning.summary) {
1732
- // Process markdown-style *text* to <strong> tags
1733
- let processedSummary = reasoning.summary;
1734
- processedSummary = processedSummary.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-text-content">
1749
- ${processedSummary}
1750
  </div>
1751
- ${reasoning.key_indicators && reasoning.key_indicators.length > 0 ? `
1752
  <div class="metrics-breakdown">
1753
- <div class="breakdown-header">Key Indicators</div>
1754
- ${reasoning.key_indicators.map(indicator => {
1755
- let processedIndicator = indicator;
1756
- processedIndicator = processedIndicator.replace(/\*([^*]+)\*/g, '<strong>$1</strong>');
1757
- return `
1758
- <div class="metric-indicator">
1759
- <div class="metric-name">${processedIndicator.split(':')[0]}</div>
1760
- <div class="metric-details">
1761
- <span class="reasoning-text-content">${processedIndicator.split(':')[1]}</span>
 
 
 
 
 
 
 
 
 
1762
  </div>
1763
- </div>
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
- (ensemble.metric_contributions[metricKey].weight * 100).toFixed(1) : '0.0';
1915
- const color = metric.ai_probability >= 0.6 ? 'var(--danger)' :
1916
- metric.ai_probability >= 0.4 ? 'var(--warning)' : 'var(--success)';
1917
- const verdictText = metric.ai_probability >= 0.6 ? 'AI' :
1918
- metric.ai_probability >= 0.4 ? 'UNCERTAIN' : 'HUMAN';
1919
- const verdictClass = verdictText === 'AI' ? 'verdict-ai' :
1920
- verdictText === 'UNCERTAIN' ? 'verdict-uncertain' : 'verdict-human';
 
 
 
 
 
 
 
 
 
 
 
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
- <div style="display: flex; gap: 1rem; margin: 1rem 0;">
1931
- <div style="flex: 1;">
 
 
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="flex: 1;">
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
- const value = typeof details[key] === 'number' ?
2019
- (details[key] < 1 && details[key] > 0 ? (details[key] * 100).toFixed(2) + '%' : details[key].toFixed(2)) :
2020
- details[key];
 
 
 
 
 
 
 
 
 
 
 
 
 
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;">${value}</div>
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
- function formatDomainName(domain) {
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(/&ast;/g, '*')
2045
+ .replace(/&#42;/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(/&ast;/g, '*')
2142
+ .replace(/&#42;/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>