diff --git a/_data/uploads/invoices/20260505_082127_ina_racun.png b/_data/uploads/invoices/20260505_082127_ina_racun.png new file mode 100644 index 0000000..5fd4e5d Binary files /dev/null and b/_data/uploads/invoices/20260505_082127_ina_racun.png differ diff --git a/_data/uploads/placanja/invoice_16_HUB3_20260505_082323.pdf b/_data/uploads/placanja/invoice_16_HUB3_20260505_082323.pdf new file mode 100644 index 0000000..2b04c21 --- /dev/null +++ b/_data/uploads/placanja/invoice_16_HUB3_20260505_082323.pdf @@ -0,0 +1,299 @@ +%PDF-1.3 +% ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2+0 8 0 R /F3+0 12 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 294 /Length 6909 /Subtype /Image + /Type /XObject /Width 294 +>> +stream +Gb"/c=dHusP/HG]QgQk!V;Adj`7n:/sjcCEk,F-.K?l7Pq[Jph+[OJ>$13VH$Ykr8IO,AZkc?5G;IpqC14YKk36Gh,V<-IdonGrhT]RaX+fY!RAZC>OgJLj:M[dgH8h&$X;7-!A=d+>7aclG4fnn:l.o&,Z)VGL-;5nqVS.D(?<3Y(ArE84Be>K$QnYM`ReHI>H!,E=R4W_kVmT(0@)A]pCYIO@k@Nc>A[^fJS^o"\JdIX`XD)UFSsP_?QeA+rF]s^a$RSOsZ_kEaHeo2kmBpj<,Q87dO&q7(h;mpe>2;C`I>3=\$V9HNWKO8Q3PV&G=%6"okQFV3L.C8Fni>5]lY$n_GGq>LXG.Y)MSepFo(6`N9/Qa&eB9M*=m&&sb33C0\)HI63a3pAd[l_4dGqi'00tE%hK&s7Z-J@e3DL,WJdooGJ@C9Kg/=f@U]8JQaP.R/U!BsCp>$^YK?3hZK9BEXF3iZZ)c)VMtO<#9m0gm;lHh?"F4EXG>>AZ?r<^\MGmST@7!1.Po^d4fhtRh%OACSU"B8SCV.NRDB[)@J-)USg[h2bfe_\;RX6>K\!W"<)1)n%VoKu=UDk,Faa]K@If>^&mEG7(XleE/2&O4(VS+4@3nTr;-$"V3UpQ>XG,bI-2aLhq(8\$aY1e/BIP<89S^$[]W?cnoK'a@Pq`#&=U?\A`/Ela(7(;Pn;CW9\$-Jl^F1V+V4;cb*S/*uZ4_W6lfmlVKf-P&pL^#3Go3He\2X./^R=/$*l_nGm`iGeD^P:LLN-LUHZO,rRn-FB"aH'llgMmpI]rcd6oBIW]H0V-B&Dh#h+n%P:<;XWXT3gVGTrf6m5I5djrgrmTW/mH!e*V[>GDF.",Q\Xi'mkD:Sig'G9(b?iRHs/2SII^CAHV^&LRMX`!g5tR')iLV$+n%VjWn4q^bW*k*m:1"SFP)Sle$"jWT7#2ThG\$Vk/I&!2Inl8`o?5aaLb6^l?2PE1J[ONqcFdm*Hkr_oIK"Y-XZPXpQQPnH*Me!%@I^G_GWSbL1)N,DE+&pLR$Q(l*[a[:V1%;b**c>B's)=[VgCC/EP>u(maaRL6J$P=IH:\?40F%Zl=ar4LsE]jrRQ8l_`:N;EjKF#MUWRRIjt2ed\D+IFjDA9R8!G4aKgM]pQ*J_ru7#ojTtG?AVF;qLIpK]GL1[11go]S0'oO>to%AVO'f8JleH6kU8H;,d#R'LkPk]PeU@QK]ZM2oPcT&-Kkh+Gal=A>3q[?$T@ku_9(H$\n8f;-$DOL;<]13[LU.l5YZ]QWo=g_6k_G:OhP@5VWaeim,HVIOo=k39E(?BaeX6Is"gb1s+IXNXAYo%M@;F&0Fqh5u@3L"2Eu>PhNjR?$C:S#>G@GYPbR$!@Y:.U,GILm]/JmZogM.r^u$qGO(OE,2kI=A3)kKbL>M=YN4M`[pE2h&q4=XVUOS44?_R#9&=u?l]p!EiAWZ*3^$6]e)Ti!jP>$h?HmS(qhef.9QYS>PKK'qH^T*RgOajWh,b"8@A;5_R8fbOfZUqNh\PR*mCm%3#nrldc(so@_HuA1rjH4lC\JR]8sni7DC1Vr'D=3Mo!\f"?G!BX43hPhA5oa)Yfe=UV,)`Is)77;ned\YkW>Z[J4$jj'=#(/4O^#nntMhEHd::FB"Oq_0&6SMat1R.;%,-=.J$I1kY5:85u+@60]9CW]g7jY%rS]I\C+,g&9@7%*G[F=_XePdOoA?VHi3=nL;/dG$?P50Z7<@?`CHV8RD'Jm'Z')gF#i^brsH_H!'5#Dj"28%=9[Qo_(k@:NK)9\A+Q;XVBVTC2N0h-Rc67W5QRKY(Wm_R\NmeMUn?G.:IDm@E;]q$b:*mFTISo<9Ke[o8^(7S"MgY#JrKXF?mH5@3s]7,>i%Zfo60YdsKWgI"!^V/)fn7M)%`FDCen]hQ'uFo6XVB$$XulPZ^=rA&7a.?M-(Ghoil/\@Y5)bS%3#$^R^bWgEPZ?5?+!X(cJ]QI&N)?Erola&6Mbo^X\IetRrMleP6>GIO?.C0dQXBt@=HJ=u:[$6bUX?4n9Ph?tg4A;0Nk[F.1\'s+"/>8o1]OK]bNhstNkiH6qX@3Z#f=RU1G%V+5jl@0Bc?(UK[RMn\;e@V-=MQ/pX@c3Oa+-a]bWd*h)0?l94"mkVm4u#nR;fO/UHisC[<[>]OK\mj+!t\75$I/@9u)Dh0V4Z_S57e?ed#+[bUb\4DW@9Z?DgmIFDK9\5/i8\bB0NetV>=m5t^PVRq4qT1%@Qn#u;`7F002GnTgFGPU5^M6c7"?fbFKSsQkV,j_#go#6"iA]qd%b-MjH05K`BfJd@emZT4LQ.ZVi]\CedDjdolAkp`qXQ?frnQ&"U+2_RI6goVMgfqOh;\'H=#Mr%pVVA@D3]!7JOH3nrq9ObEn/_4]P_;).39A-&+Yd_q(gD]quRhh7BkU/U@_Mqb1J,kUkA$C;J]-)b\kYQa!sF,Z]i[@_JWSbh]k8PV%1fHi'=/YEuZ^UlNu`kuccf^I`[abs0,$Hh9'@3nB%ONn''P=P_<<8U.to5Ju9'oJ.dTeqTKNeViZoh9Wi-GeVEe_q]V21+,-6L6dkMMs4@"cbE%r^-C]]rNG4i]-jeLo(f>l4P#84-VYN!%'-a/p.q5^e`eqVbVds1nnphrjgZ+O0[X&j3K/+[;TaVL)RH6g2*ba''gA+f,h@:c%s2;)HE=-'/DQ.b_;H)t>c26>#c]d?rWp?Ie4Z8,Wj/^[t^/nZI`WFa^.,n?<1[]Mrh3ip"$?&:\L5n1"iTtd`]dLH*KJ4YML0uk/gqn/LGgI=RO9L]i?Fuc@>NU$dS`A@[sm,L#nSIS]&]te[mjAe\Q&p5n]i^ktQ^)O!]Ci2eDDdut@D*9d0<5o4e+9jXmWQ'Gp"Z[HP;fRE6K2SL@X39+Z0@n#*e_?0smPSRDfo>)W\VB5&95&UU(07WqL+IDL.EfqQ/GI7D/GEI;bDWd4HM'1,RpP>-B_afAa+8KH!TQ\W+Uo[[D/^Z!I/nlYP>Xes[LM*"AOYk%F4IF4UJjK[pZ>>;5"'6fb(@HHbHqP$%ra^`_>\!gL$-LNE._ogHoogO'oP/j<[DDeh&:#&l\Q_.f^FJR"bA?Ri?8:P0LUgWm/:-a8cKDCRHM'$t40r!^3RM:'eglZM]=%8+X0Q'Fbgt)+'iRkLR(N%6U??F]I/^.I7B`'.SRq6^@L3pa?>L]V\`[P?&mG1:)s5[l[pE5(HEhlLjdqeB95*_1DO=MOh0NY;lc&S&P^?^P/>9H^]4##/GO(O5Xr#j5;XSg5Y_:Rkm"TfYgoXWn]3]:q9=a4s_%KrKYT)`TF15IYm!>pb/ENI)O596T@Yo-"_n*/nb:@jL@:clb=EhrHZ.mH*,>h3X=EepuGpTXFXN]6)lY^u&G'dA-,g(rRlcsB!YN1t&Q.``Wh(:;"DD,db6oiY'h:[eLQ+XE,10Z]=]B.#am:ED^-mq'W41hc1P#iE"B'Y1o\%EQi]rX((j[=lHB/c,Xo#ehlCu)>`QIdCq<>iE^=@2-HT'IJ#1<^CA2#0:'t2`GY+EDpeH!0QqfJK[qYsF'j?RhJ\%OgP[Ab?Ep!sl;`oh:u(9r6<-`]W?dCNg5^BPh?sUMd;n1@]Tanl'H^[RH[h!/?brZkA.;'N60;0:2_l-\Vq7Kelc%Z:[Fl_VOMSS3k/m%T3kas??8(I?Y0@HuL(*4`1/E(ceEM*goQi7dGAVLo2InKpQ?fe5Ue\*TUXtF7^MC/CZ6=jGZ-8Ef-Zsak)80A#rEYm5Opunnh,W/!4ltNaPO;-?["Q1$>KkfhI>a\;DkBUFVL2Ra?\L(DmEE:5:V!s\UcVL=/`EnM(:/0rhkI%(gLns*.A)pq=(."i40rkQ;ss$/l->Mh9DGQl`N5W`\*,:8p6g#8b,6Pm?jSEdK\!1qS_fA!as,MXEh>"f@::Ih?;9_WDQHnTabH#VHuTT'/"uWN;Q"_K]s:0eSD<:cCKO[FX]L]I]c6:&7C(n55$qR4_FeLTRagTX=Ka9=%4CoGiYiS56L30t:@H2KAW^dg\^+u*SF?.Kl_Rq=.k=EAbIV,MchFa99IfeLrotd$')(ng]W<'I9ZLU[=pUpLj,kM`Co(9Sr)j^;I0.W+FT!6t$-1#2T$*=uT,ag%SZ#3<@A%)V7fZVJO=tOnh1/I=Jh41pnD@;c7Q^K/,>3qD_T%qAt3Gjc3(+`lI1J;DhfHnX%\W:lSg)@RT=MeBVQI'BO99OR@+7&'r20N3.KY>S\U"*V=,kL(\f$.oE==l(8k&/!n9X+*[HM-&r_Ct&>gt>S;mM9WV"=m9O=3mE`r*l8>SLnF8DS\5)Yk%F4p"W]e0BS\XfhuGM(2Ukk.o3?Mc^cB[@\1]4/S4'^_)=uko(78$gf_KIk*b#X;J,>4Cr6B+:TmjGSL+2CT&#s>b`cg9_H(5`Y^MnjC!$i7O4AY6oAggRYZ9?pC`he^RPMe'@s+Ef4LrHWCfRnJgP:'FVTc:8=PY>\:TcDY>Eu>,p0h:*.5S%fjOIe4?gYi'0Tm27X>P0d/M&PjGtWq#?qo=Th9;'.H!#trAIH])>2I\9/QbsFC,b`08KQGl;VAqXZBhtA=:<]E^"U^n8Wnk:QrLMu]WC3H*S,g"AZp85:QjdI/LH`7/2(da98Bo_:"cT'b`a;t=Goa^$d6^shU5ga=B"p.1@sUJ\QgbRbN3cT>V;Adj`7n:/sjcCEk,F-QcnK2\QgbRbPqYD%g]ImS,~>endstream +endobj +4 0 obj +<< +/Contents 16 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 15 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.57da11f962247e427dae4e77fa661a6c 3 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +5 0 obj +<< +/Filter [ /FlateDecode ] /Length 677 +>> +stream +x}jPṟBÖR}Kri ^hJ}IJA޾{yPJt7ӫ~s_M~wW~Rfz:ܝW0nod>0>wo.N׻kx/wO뷓q]MWT?tgŢ[ׇاyyt_^u?,Wu\u2?;[tYLjYr|y,Evizִ4M[t4}/l?6}K/4K/4K/4K/4gNMlsdӟ#Ȧ?G69ϑMlsdӟ#Ȧ?G69&s_~_~_~_~_~_~_~_~_W~_W~_W~_W~_W~_W~_W~_W~7 ~7 ~7 ~7 ~7 ~7 ~7 ~7 ~w~w~w~w~w~w~w~w????????_6&{]D8:u} "endstream +endobj +6 0 obj +<< +/Filter [ /FlateDecode ] /Length 18175 /Length1 35128 +>> +stream +x \U8~Μ3spyQDE$g>@ +\⡙fe#33H2SninmE0̽<ڶ1̙s~wD!dFA'M^JKjJ(Bx O`/*S}%7 ǜ~nx>4[RknP2+qÛp=*@j\˪|%7E꒫kŇžeZSRc a鵾 '4k뼵K6w!dxa~ Oa-;  +!"qGŸQGq"TSAyvRzuja1.\@FMH +k-C#"2 HAF,ȊlȎP0 +AD( P8@( +E6šxQOzDAI/JF)(Ci?@4]2`4 Eh8L4DY(Qh4EcXơ|4+P!D4 MFSTh:fYr6V4q\Zly#.s5q|)@p?߂},/F ho) +ke)|5?_|9jc&_Ŀ/BS(f8hga⎠:Bs1(W8h 99b˹oQ }1[n$Gb9ҟĐpN{:XB?-yZ@Atٹ"n:w}1B7|a6։) z;(`.C_"b~r ρ)ƈAh5c(AdZz+ (O $72lm+^E@OD'FIs5y<5oE͠6&Bu SajDR?cOM[0E݋{egfg SI6bM7mfK5][͗ [ (f?qO`{>%VG)G[!і-X;[ΣzvV[m|etf{3*Lw@UpO =`hL +9&:*2"<,dY-f"K$vp(}虴Ca61$`DzMK%'KC{2GjM'oOkǣshjKucڹwRڷk@IF-ACgX0{hHcT>L9a*)tuتA!I+!fQ)Hze }" JC4涣CBmke?no}uΞ[Ph((##Ǔd`ơd>`pWz74;BD[ _zx'O~We/{៪9Q<@{ 8d8<w{=-d!#'OY#AF\##D"Q'=r"Sqk{=.>`Z&2|W>2,YI1r`{>5rv&3\*X&|Fq8N  +Ҝ@v|\O`]C7zt޸Q~{߾wiw$}ǎѣs-[/8q`D}/YR_XE>=Q*jfU$KI}S>VQ/`xNM0!;&K솨[rBoGS{Da + 6Mlm-G^:ǔAW{Ә|7` t%4C3hvwmM%%`4s˲UcA]reLu%9\n*ccSƦNӸi4^ӓNIV*9bCRi,,-I6* .cxBoc1=25*5:=Í1&'sӍW&́!UF ;zcu%A>vR)S\ٟB8:4)z7i е\ ӱ80ayp*$}WM  +)~? wXz04Π0)DS'F PHvV.^w/p9}k '^*"qmVqyxt-o][/|ȟ{r$;8M\3CT]2@PՍ9ɝO׋Vmĺ+bvR{79spG}߯v)|+u&9ੈ=#"s Ni8`{Ouv3xK?A̗ţ4+̔;QS_5aDz[{;z $3G*8ʢ@x |2sMӇ"8ɽRP5N[|m8l߲~p޹fϞ6[?׸4q/xpz߿8xYTs6 >LCu*~zwLmmgtB=HA A2w\O=2}[Cuj l=GMWy/R> 4l|8NԦp箿w7g||1[Ez,6& +vWjѝ/֖h[!18Ap Ҁ[!<[+ (G^?o!R[t4/& 4M=I9p:N %ͭ#Vz ~]8NN~ZI^0$ް6K +l;.pRFN|g?G ??=f@: N!HjZ~(.bP,I +8^tYѶLG߮]HE8?J?h bٲ[Jv`Ő@ jEs=œ_1ʟۯp'9ξjc>GY[o ~ǡ*` H#^kTyˑԠ{O\@^fzE' ;q|.@\MiC_ +bn5N;/ӿHHﯯvz4:S# + z=iSΛ6qw\|!6vem%5Ok{-k|kI"ݹ⚅˗kF[ F]>6ϽaCfka}93 6̹~{R%oyljЎR* <% +(Q(I@h ]" rG”eR,EKRiܠܦlPuN) )R1޲jsQhy +O&/Ŧ_7 +V&&Vy-ZX%_C/ KGm6wpLzWNal#|NY.%jKڷᵇMB8ڇssԮ 9ɭ; V4**փDXLErXo ha38]$o-Eݜ`H(W؟Vك =Je[]9sWn[ۯFA8,w^?D[ȹz{f(,aZc77eX 7GFGbh R|'r6w͊ew5MֻWfߏ +.f߾9rS|99LA*Q9څ?4  ׌ߗ{CA-WDv`֒URx\}|wLYG.lx!0 䆱5qS9M֢M+a粩?i{tX Iڽڶ6mp4ig w-D7p!qS⟓~GXaB:Zghmv""K cIxX`+b>hqq˹\ cM3s#=ft(;\f#ƨ>|X'yŝAhi}К0usQ=cD`+5%o;Zu$Iy7=O +FZm^Z>0i qVVkex=XbQ?Ӵ`܉fl}y;@aE]ȁ)4`g?G~Pow^sM-'YeeU:?cVf{ΝȶӰDZlvUDUu Ɯ$PdJ@)QLbfiqyjȔJ& $w0-?~~Vhx-YΫvac#\EY̽Mw$/.lbv$ٓEΰPTNYiZ8È-%" =&F;+24o T.!,fLDX+<<""K `ڍ,C52Z$9 :IySo'}Yg[ę޶zF{uOo4Yv*4$Z30'D$(Xt,e8KIBS&CBq CIFQx4g cx +Y!A*z|_rGVx=c( RhT{y nN(+p\L0H*DF%yWF2SETWm2҃39i0Q@{`l[Qa v0\诤aLnPP` 8, FH(Rn 7%pHK%)24>MHS R<n%9h![ci42(Bi<8C>jH_aJMyv|)VyzuKIH,19?Xp9f+c~AR9W*@(Nܸ7IǃI2^)T?9!+[2.mc\.{*AF@J {-$i!܊ +*K vQrpM$Cn!Idgs{l:?j;E.kĶAJAɿWt(1~-=kTbG694N_Hj#j_)nxwww{pYڿO q<gVPd@oq})HF뷖=\ +]%=4N<f縕b9 +^1% _ Upy.l*/qO8N=7k+r|OO!"b=-cD}]`yîy-rC5&J̲8,C ЦVs_!y) aƩbg3bk V66b>z ֑323! 48e1+1fWtS$ð/u!+F+£\8⸷n7lwwum-FNQ3쀼`&<&VɱjCaoȈ OI6X [lwtR.%.<{Y@ -Z4fOXѾ~qm.hƍݰQSZ;lO͘Y +B<0>[*PSa ^o; 6+b)uqҥƧGV)dMm;S<KK}O망A1MAίM }*~yGP~Q3-3b?&UM4'1_׾ 7S-\HsSk\:b[W(oAWh'9Qa !UӚ'9(3[D2 ;G+Qã(X%W'T$((mϘqgKB8㰄iw-(_~#~8 m _0oۆw=@qxˬZ>f3@66sیwlMZ 7MWio5p Qt%?K,h\$맴(M;FZ l/pl& 3x:ݫ5}; +bE.̙C \K` =Kr@|E<'1mq.a 2kXK4%}ʵ.QXn:eaGH} }tQCH n'"Z^wь$[5`j8yCXeŪJZRfܶ+{޽Hm|/s!o &<FD A^o, +98opYQh 1č>jn9ӽ +Ͳ. 3rH3eYVdd4V!>nYd9YI6&͉j4D 16 6(ccLYMvOާ333-`,E1'JiKw^y^_o*/[2ԾZ}-wrnno+}r/RSI6۽'> b7?kG4ks ,՞p Y&;-mBd9d) +6rܥN{ _"V^G1WLtBi0 qhJc㫴O߫9Į]ƒt=q_tp#/f# !oA"f(mp>t|&ӳE,yt0_~_{n+PkxJDܱK~f04Zp>"zz V<խ"J2Lʹe]Fɿ >`Q(` 8=Q܇$A2T)`|tx3,"FZ_%'ӤGg}1p(v9(m_J7>a4(߄wQ}q^_@h}qO]H VEioEUҹt!UŊ*e(77`B`$D C'RmJt<" I[Fqz9q44L'IWEƩYr-ņbL)66r|P+jBono2nZaWte+(?*<,=*o5n7H'M/f*x;NJft%Wpǎ}`د-ѵھH6ͯ +ψzr~صYk y|J1 ؽD!k?8  nDY%)r2TIB2]Mzr|YF+].r^ o$Mnt;D7|J|@>?A_o r(&oERc} +" PRYX~7e +VqLkuj{Vz:tHy"P>BpK(ߡz3e 5G +/:1^mڍmi7røxmk?=Hk F!+XL,ݐzY H Knb%B"/#Ќ??&1ՖNEˢ^ ֽmⶎVe"aArbBed8Аaɰfl<᳅lqonno1b[ K浖mvx?7MʳƧO[^^3fy6wpmcfʵQ `Y1,|ѭpVeEZ7Dd[}pڏgr \a!7paA5lG`#،=Ib1,҃j, +KH qɛ6'_ھjتh0n92 +j,Ar4j8rGL"ps!Huz|͙h +*п eEQt>0(A%/W9l؝U/{<{i5WnzS PIŞwd"T"]@ofCHW 8 U6ശ4؍bPFu'sP2I줻 si>ߴwtzp;'~#N5>\NBXh{@+"L($Hp4Ft!x LBOee:MCtsw[a gN1{.դGI"!q:cYq3CB173ovZs [7uߥYAO1s@} tp7(h؊q 5OA8QP579n4` OAؗ>Gga;fRD/B8/wnޅst\kxE_8DxZ)4p`Joab vE8SZ(p&>S(GS}h h +kB;t_($h?%| E c(CDm‚݂yz=a+@ m\?^Jld( HOo O,cC^ûM&W +{/yƛۍgL#L3MgC7--ZakSPXࠢAkv "l9xw;op,aaS\kkkx1⯑FFM: |]L}6/|™8 [m ·6; [6En- w-"+-!;w6ҿoumA y߶#~ȗσt i#'~搄? RoH +mEsh0LT1oېsHmGҐHCh!ChMQQ/TᜆRaCE#OyQ FIp7@dhe*TT]y1X=1Y`0\S)%0_1 Zsa$=Jo e#JE*@c- p+ +}0{ { 4WrNEګ4QM%:{:[R֔&UUj!UzueCJWQGT,ܒIjiEIoZRU+kUj0NF`=O(@LF~l٭`dQ1DA[W_QӒ9p)ؾ¤\}5bJ"R`+Ø0au v/W$za hhR@7&J徺9/6uү% iaq>~Tu^fuђ8&9:^0O¬4Ai`O)Vߒzu9i_]錝<Ǽ$mڻ6=z0U:΁;%-yx8z&|ݥ|w5L]u)*]8UV$`mKCUGQb-yp㗘V)^~~iNFl(<M<=˅{*qd\?yɞq'CB&2X:B8RSkz5,:6Masd xhSn舑pg"\(DP}>7q"9kwrٌU!Jg.GObm9WȠSQH(]ѻ\&0~f2ul 9\%aKBh$cSYgFLEIL,6:12mwBIRǃRy"Ll2~n@wF1oqc"/afC܎;>2ZGy^׉.hfT&]w*&3;%?³N\t Lfze2YO谑fOаN0ѯ;0߀ߡ +]YLNC ʆV9 ~{5vf]Τ.k&{QoE:%=fuunZaVz.z;wkYo#+<ב,`O;czvΣ3؟1W u-/PV,,`fBkh5P%eRCW1yR4LíCuY'O([ER> +m0@y0 e +kxtN@_u K [=WR.YR gՃg]puz +*,RRgu%[ՕKJ%JgJO 3u%uηNe]޺ו~,xuI_ZuIAݫKngKOpW*tʤWLkdIU&?VeR*o2)Lx)vvjGʏ֎:k@ڑ/Ԏ~ +v?Q~XQ~Aŧk淬(5/(]*>?Uw-*4 ?ACahqө^uʷ 1Y_%VԫյoZ^V3`_5_uFQ:g+Qu:>SÏ~E3W+%jC]In+x+7tj sͩ+ғv xZRPX%D-P W] i +\KXZR_+,2_icS^YBE!_y`\"ä[[+k,20e@X/A6 \ZXF1YPPkld+tVzOIRj)H}ER9): rޕZEghA(P17^6̧s WF *ՔUR:+J+eZP_^KR۩3Js +)FNy/Iڰ[^%HuZ]UWRE+jՃ-)+c묣ZRx5V)t2o}VaВRROGx& +R J. ?&G'4@jZEJN/mSFR :cո;s(ql@2y~{KPA'}ynQKjkJfWyvLJP*JԊzu]6֔DUaT}UԪبJ*=VkKJk| +UMM PVSFg9 s&gfԂrԸ pN-=~b += +3󋦪s$5{JAa B5w\A^n671+7:/RrТlTn +l\vp9"7/hj[0B5S-,,91/P-XX0~B690Kl _00w$T7̬qcT6H.TYd`ٓ 3E +3Ѿ;ǏVrO,R2Ge)#2s%Y2GQrn:9PQمyIꄂ쑹|-Yzy ݑ'd_1n@Il + d1\ +h|aQ*s'd'Dr +TsL~R2~Ћ'P4ҭ/hWե~]#sLbZ;PQ5`=քŢ:6 Ie"z{SWS|ԙ,g!ڧ< +&QԊX/%U0nam]% YPWD-iup?L1 +N +,Aǿ[_ Qrja2aRYSW08*4s2_⫛* +˸~us?Ƀ=RItA/̃A~'_ b%΄E5ȕ\Io˕`Uҙ+0WR WR~,WR~tɕot 98*]R%l[LJO)L?eRyʤ\2$eR.2JʤeN7f!p\XA=}B{2)ؑwݗ,dc%#zF`vD Kȅˉ6|DZ5ݷoGoZJ#5!_jB#GsE>HBZ<(§i||SX#pqV#g4F@#4~+9^pFNlNww[q7˛n/䝷Cwc6rFe-#3s+y&okވ')Dx'ӑ O!H9 GBȫ<#W^! ye3nF^,#wلfIs9`P+y³4r`]8N?c3L3VDɞ^< O?hdFvkdW(yEqx+ dv,!id{OFil +٢6[4B6{MMd# M6iC+y0$w +7S#5rFnmU#nnrFn iwN#\dB,ȢVrM+Y + 42+il[ICo%uKU% $RJ[IU+HEIH#s4RFeHB<|lE(5 +))v%I1 2K!352C#zF!\ip5-LȔV2Y#saF&j(L!WVr.w|;MƆy2v]BZ1vk![ɨa䄐V5"dYH FL&V[Vr j9P3RFk$#\Ad`z0M$?@1 BȀe|4?i&/uO#?u+I1`7iз$9B`ҧ.# v!!T;M$Vk%^ !1 +&Q.!M"BDq7a&᮱BI]cIFB 9[9$IFpm׈X-6LxXfxbn%4bҌNb\+fxxY#F E5"(D|+!eQ,`;Af>Am?Qmendstream +endobj +7 0 obj +<< +/Ascent 759.7656 /CapHeight 759.7656 /Descent -240.2344 /Flags 262148 /FontBBox [ -1069.336 -415.0391 1975.098 1175.293 ] /FontFile2 6 0 R + /FontName /AAAAAA+DejaVuSans-Bold /ItalicAngle 0 /MissingWidth 600.0977 /StemV 165 /Type /FontDescriptor +>> +endobj +8 0 obj +<< +/BaseFont /AAAAAA+DejaVuSans-Bold /FirstChar 0 /FontDescriptor 7 0 R /LastChar 127 /Name /F2+0 /Subtype /TrueType + /ToUnicode 5 0 R /Type /Font /Widths [ 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 348.1445 456.0547 520.9961 837.8906 695.8008 1001.953 872.0703 306.1523 + 457.0312 457.0312 522.9492 837.8906 379.8828 415.0391 379.8828 365.2344 695.8008 695.8008 + 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 695.8008 399.9023 399.9023 + 837.8906 837.8906 837.8906 580.0781 1000 773.9258 762.207 733.8867 830.0781 683.1055 + 683.1055 820.8008 836.9141 372.0703 372.0703 774.9023 637.207 995.1172 836.9141 850.0977 + 732.9102 850.0977 770.0195 720.2148 682.1289 812.0117 773.9258 1103.027 770.9961 724.1211 + 725.0977 457.0312 365.2344 457.0312 837.8906 500 500 674.8047 715.8203 592.7734 + 715.8203 678.2227 435.0586 715.8203 711.9141 342.7734 342.7734 665.0391 342.7734 1041.992 + 711.9141 687.0117 715.8203 715.8203 493.1641 595.2148 478.0273 711.9141 651.8555 923.8281 + 645.0195 651.8555 582.0312 711.9141 365.2344 711.9141 837.8906 600.0977 ] +>> +endobj +9 0 obj +<< +/Filter [ /FlateDecode ] /Length 697 +>> +stream +xmMkQb-]il PK:ʨ|gI.mu&?<ӻC;7ӯaf7C>.:7iOlv{i9z>&y3V.sv|[_ݏ˪Oo'/&WWw&E/>u>74>o\6ty> +stream +x |T8~̼'3ٗ@ !a L!Aф,$I*e+ Q)"R "m-`KZEEHsɂhv}>Ɨ{ϹF"DQNz“sp,k}-UH&ܫUfgtGON1tQu++),Fh~Gͬs%/u=0aw/FwY8Jt7Bc)3K*>}UeMh2BY{jp|Bb‚?DN7ڙPa( Gm娂8)bFSu⍺wi0lB"QB:#`Y ّ 'r!_Q +DA(P"P$B(Ţn(uG=P Wqg%tZ6th8?#t#;qN% -' _NB Nyv8R 7/-=Th2=, ,=zB(?V4pqJ'1Jxo'Cx):6@q5ZJsBТdR +X'FT? ڠ !x.0r.*HN]$8/HkP3:Iowӳx)FF5썬T3A' }&WE0~2(*E-ـAx]!nnPP%틦|C=:8RkIxhn :TEzV'%+$:xI=oUl:e/k\3I{i~|gI޶Ip1ր:TXL:S.:f1wA@ȀRRzo%ȨstJt+Ҟl=~1V%| *E>cP*_ +h^%$Ap{ D@s/xmM4 9CA7#Av\D킡NcmFH_Pw5`& ~>V=,48(,fAU@_Rlo1>tNT%ufl1vclt8zdptX>$4PhhFC fGvY'#L(^qiJH\[ߣ)vmjUb@XK( ح`u [o[+h 462d?^c:kg)fSgh1{4?b5 )}:a F881q 7ށdycrih#@뛌k\MMzn5u'Ac) v%ŢXgttJHdH4ڐ'M>ݙ4Tn/HsMk4t[ 7HjNڛ2M@?Y":k&Pp<#/op|:(M:d*Z/Ĥ?6aK[CݤJ_/O>X~{5*@BI vAz 'r]d?G>d+lZ׼lRqA_ԁ'eN-d4}lЎ6 &NJ7v[V;&$;Ŕ U0fa;5|98{f;x،u61'/B0ju@BE" v#9 6 M<=5lPvM'`d'75jڠ\<~Pu+6 Y\C} 6Ny3kpTcJeF@~+ +dJp7:TţAoJģ[jFzzQ`D.vQlךY"9Ff.0z_O,\(.9ƒ'ǧ`؝4Aty⬠;@עYR]`MPmT$pIВhGa4f<;'' ,+Lv_jF&}Γs杚)v^޵klzץ {wҧ/߾*DȼܽG7+>.saܬ\-r=샨3 8FSgAclr9,7UyHl*GѽH{܃Y8۔måϣ˰i =&(PI%X>FbtDkedT ;ܑB^o l9m+̤-6m 2 jkŝ%cAlf@H3;G50'dg\G.; ]?)*RO_J#w7u룹]VzWRlt *F+Qh;GBzj}ٚM+"V6EB}ixXP48P՜o=ߡBnqt ' x\)0|b%%Ran)<ɗl]er8!OZ},>Pԋ8e>Lz{CdnKT)X+wTSP:t ""h'P~CPbp&nGy\M fBYp!@1`)(q݋J:;葶Nb-Y\߰1OٷE|pa%pxs{hz (Y&4aͶվu>(""FկZ{4#AGz4Lq񙃂:$(Yӕ% ~1-z >y]}Z(sѨ0Wyc>ƾmM~A!f(ȡ(|QLO4,&}xt<6yům'K5̓)Z-' n(AZan8~LtF3w4Q'%%IU%K;bİ)^ޒ"//okp-S7I&j +;3}ww>C7u -ѣ{[]W:5nwɧGz$;@Z" +8>Ly4X5ohfe%d w7Ylg7Y+D1h"_$b)R?pbx0L߮7~x?i5>mL=w<]3;Ymꙇ-/b螦mkm7mXj)UTV]+s*zO > s쑛QG +QP#D=x~]_1%בz 0ZIJũC=Q=ΓF-$6 |zBMMOl2I( 8,PcĚ}>aq ^0L}_=8:LQ ńk?vuHd&Ǖ0.5 -7 ElA~*KN=.}af瞞 -kK!WZrĮwÏvx p,j : :}A?ȒN!(ŭ 17Ѡ&QWe~U(A>BGe.- x(uz8ԏ: UVT/ckiz{<W F7D{iqƻ&+-% \`^ֶ byHܗ9 S`{8ƛom"W{0.{`5YaxqWYLۜ(jFq(1]L__r8F1k&Q/xP<02f9\`n4o1s63'|Z_Qk<_퐱|s!)ɅgH5-ӹԱ(u,٧>$X /&v'>d8L?L#lq)Y.6גH YO=~B?WhJn!]tKn4 bnXeZIWKFFUݧ3S7?n4n`g>Ku3ڂhu[2 +2mTmQ0>5`: f$ "H4C|N`] ) `Ћ>Ԯ=;Qb dC#T !A(e)X"vf|gN͌UY( A"S#ǐ\N!usEdn iLt,\&0u0c#pO/=_UWO镫=İV$Y2;sݡ:;!HbTQ:,y|PVJM+ŵ*$wb?2@7ԕR"Ar@iΐ&IR .JR(m18\FU/Ml _!- ?eg:IXiTc.*Wb5wsja^?9r5UVc3#c8ڪަnf0C">|@?!6YOY8rj^KS ^s0Pם\"K[רmx:~axM}SLh n`PxxƹmĊMfͦ4k3c5Y! , +JyQΨN ׉u>DIw#m ϷƫU,p!c"gjz7/}P0z<*Kv6Ä'M +&ɼ(Ck>)裌v-fv8n N[;rTpEHt R?!EhCc]>Ơ%Ƴ#WOF$6YVb>X5`N|#.HC l" {:2lB@\͢"):EiE &i_!YL3-#kpU!z'l=JnB-!gn(0EҧdF":-77q !`8I_HAr#EVrx!6_HG-ÜΡU-Pu??|qb#)mfǴ9 Y=M6r8M@̪l$I[;oov»ݠ:]ZJ6JoQ4Po3&PH:Zm@\_iM}=y@kz-to4k6 +'INۃVaDOԕC uT=j[I|]2/zn^%5~wY O)`H^.ւ5 1WѝxpEɳ5Ϊmb+5#a* ¾Rʟ%k,n\5Zt[@1v O:h+aFW70wS:W?&[/@kg"1$141,QI Oq܊;DV. imkP—E<{)6ۡ @) + +R. +[, +Vv j/w)-۽r>v '-ya]"ɥ֜ٶxWiK=ıpe^bc[Yzxw=hVY[ cI9ie^[8V^x tQhs(0$&M#%CI뷚:=)zVt_Խ Vg#s[GU +}l[m6mkQիou_OwoZ[sϾrν~ܹ>o+ +)[8h!59`bo11Y7~n4Бv e;}Pr=ML{ֶ-_ 96l%|.{itX/k[Y־w[.R  +5KjgDV4l$:dykGMO6Q6c qSk\0mϱث@No=S_]G]ΑMn!/72DkF뽅M=ѵy{ 6s@d/%qb} Z_CfK +~N\8X5FcVshi]}Rn#]E^ս^@VdEJL$kCvl-]nM"z;d,ZO5fKyI4$<҅Da")D25(%pMV^ʱ9v"y):x˼ ɔU@T3|^z/zR[nJҔ4.Ȓ^8X]׸`<8tyOnb*K6?5{M˻hAy/pEn?#tV/tXoW {ܑCBBBByqiyeum}9Rn.[k~ik.az/o|>ۤ^P?g>ۉxV! oll!G)? n*)]R;2 2_h#ɻ:ov:-UAHy:Eo/~݀w=-pGo&5|`KK{ӶS/^ݢ?;N` a,o!nz9>[|Vi9NJaNZ+w؏жHxcb'xOYn|GYr޵Ÿ^-uqAzNH:ե.+9e9`9l E140Hdd2*HQ'-[X[/MfIDBFh B!6!nhܝq =w)fwXd_z)ЕY;&X1uУ۾WfVdUɓ'oJ Ct떛t߰bӁͿuLN5iݞ}gA7L"t'\K9,W$%360B}J,3qbyJL_l<_]6O? nV_nlk^qIA#r,6Sag Әk){WUsW>N@aW--Þ{u[|lo+ܲ六dݥEOrG^sXːjeʄұ]r3m_B!v?L'UOd+ѳ k4PA'(/H +zVʠqVO'yR.Og7+sBxIU[{݉DPޅwQg38 mﵽ[Qd4UƍܗA|tY-v+&3b' dM3 6da]Q֪ +V+=gۍ}uڌ (~x~IB^2Pg#}rel,Ők`iF]&D?it-XW1N-GLVe_($~~K5ўM-=fHG3,V=݊o%hhV8oɷKq))-| +taqiyeudhhjjzu% KтiPȚn6ZZ\yY=k\;aSSG5xŭh-U1CF h[4HC}+k~"i,TB"SƠ.LuV"h=fZ0R ~0B}.mBNqG >OG Bdч80LLyA[r'6x^8&8Rw$}J,3{Wy*|_˕!q-4޵!˻rr}/4D +Rvv/xG7Kq2я yd:F lp + (Kl F ~^%u(*nE $sNO?BJWo䯇W+_0rW_wn-vNPfzv;|96k6O7S AȢ~[-_wpB NJݢ5P QJ`0D *GE?K-݂d* C{* t ?7ydBоX&8`pJ8CKplf00Ĺ&.@o088[F zΎġmZ tb#<ڄ_Ϣ} 34h20Zp>G/]O|{ß n6J`ע")%rv;G!֎Fnpy$# Gl@ eɨCc~\E^z^#jWb;)G:-]o6 9U=yF_Z1\^pKulQ=K>۝vgsǮ{\:} |6ߟ p<e`Z༠AeAdB=ͰIFRxiZw\!鿁EE|~/(&^}'y1z $z!COZ끞kHZB2Y#;]6jؾѮ@!\uw-]F=GQ_n.̯j39g  ,ÛɡuXS9ǩ+5RzG4=Uɥ_Wy,Vztܣicq>vL5^%5݋%\ro1?pO>[AhLfx,[;#0մ[M1P{{)r] +ok[m FXs`VǡhR*RY:7}tΩLz<1g?1:k Ndv0N#0jGR9R4A&Tax!,tso•yT.62X5Lgv;f]#o;4XޱJp8קL۹!yNm;gh3 h^x$o;vOْ::sͰc-fه滵9Q笷ZXӞT<=3v*O<\c|XXK+ yF 7?BߙVx2_z2F_-{>ٰ](7e_]Ks|n5:x8f c +:a^y-#ƔָUu$wݘyAMA$zPLNoWAYEՕԕԩQa$w|B]Ii]L]Iz$Ա+u[׺E.is-_.ɨkuՍLuI*8]e}7W*|Cc2ʤǪL?QeRmU& jNڑ|SjGwjGv$oD{kG^'T|:WigU|;giS +MwQGA㰻^V5٭}ҭDZ2rv\/Gll륌1F)YUY][RVWTRKfy6y괍tJ  xr'wޏ0ry\V,K,,T,kkZuaY 8V=$^T ++*U%5Сrj-pXP2-+򩨨rf4g j:pYšʢBO.,YRQ[X)-B 8IuIUueq]Q S\O-a8]:ăf3LfזU23=5VغhȉWf0e 5eƈgc&TV+5% h]zȿahb5fb}Ci]u X;W*5JM%E r(#Q3Ps\Y%M8JPQY bў2TuhN)+1CZVR؅ +ЋjefeuMVjV@4Y83E+Q Sha5U7ZfԔOhLl:1 -, 5Gb e3pxxU̘wRsS]Wļ-adrG \I54Fh66 $鱗%`I jȀdVey;b%sjbª*0©3J v.*e5 OuhwRWQAU#QCR P؊aUa]Ӏ0ÊJ?T]((eHJWFdg*GNLdWree ODxebF +dPR1YI9Ǐ)cs23YFְ 3F*i/+;W @syW qFmjZFfFn~<"#7 `rT%'u\nư 㔜 rǧ6+#k8%}l:e9*7:x9w\+,H&KGff*isǥemwFfeMGdO)in@ʰԌԱ#9AX3vȬqa1.}X.o NdrtegOuoWLrES&_2ɞI)|cʤIiʤ3)7vt6C;uOʎNv${#dGrHIّ|H9S.Ҟߛ(D#pGO'4n4Ƚ|g0#Ί^/ZϺ7 fUPjNIMF7B_SU'6^I߬_[W*_ZK{S/Tzq|p*J?H?M$Gyhx~<ëJ蟝uJ:3?hzHzj$= JDVT[F'ѷox(}3זׂ髾J_Q*}IGTJ_P*=TzNGUsbJ=0E|9"Sh_ϬTJ))OF铻tw^;TJtnUYǓch1m&n~$nV#&qS1}xM|8n 2}Hי*]gMЩi]"FXWW) GSh[?ާU+{Tȼ7XnW8r#m Ŵ8UM/Ut]\%vXTPX@J,.q~4ҹ*cMtLTZ{\WWiJ+UZ.Nr-T%*-ViJp -J0)*MU?II2 NLy*#OH.:t:GU9FҬ61Kcm4Scΰ}hFY̰Qf:R#utJⰫ49:U:TCnqCV<,v_Af:P)*)J__FiPlIbJ{ibQL4# ўD{t{q{4snbTMcbFiJ#Ua@g*4* BiUt P1N:PJ*Q8TjZiԶZEfhV Z|QTfzT*@\R'=)QR܂݇{!a/5endstream +endobj +11 0 obj +<< +/Ascent 759.7656 /CapHeight 759.7656 /Descent -240.2344 /Flags 4 /FontBBox [ -1020.508 -462.8906 1793.457 1232.422 ] /FontFile2 10 0 R + /FontName /AAAAAA+DejaVuSans /ItalicAngle 0 /MissingWidth 600.0977 /StemV 87 /Type /FontDescriptor +>> +endobj +12 0 obj +<< +/BaseFont /AAAAAA+DejaVuSans /FirstChar 0 /FontDescriptor 11 0 R /LastChar 127 /Name /F3+0 /Subtype /TrueType + /ToUnicode 9 0 R /Type /Font /Widths [ 600.0977 685.0586 589.8438 549.8047 634.7656 698.2422 549.8047 1000 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 600.0977 + 600.0977 600.0977 317.8711 400.8789 459.9609 837.8906 636.2305 950.1953 779.7852 274.9023 + 390.1367 390.1367 500 837.8906 317.8711 360.8398 317.8711 336.9141 636.2305 636.2305 + 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 636.2305 336.9141 336.9141 + 837.8906 837.8906 837.8906 530.7617 1000 684.082 686.0352 698.2422 770.0195 631.8359 + 575.1953 774.9023 751.9531 294.9219 294.9219 655.7617 557.1289 862.793 748.0469 787.1094 + 603.0273 787.1094 694.8242 634.7656 610.8398 731.9336 684.082 988.7695 685.0586 610.8398 + 685.0586 390.1367 336.9141 390.1367 837.8906 500 500 612.793 634.7656 549.8047 + 634.7656 615.2344 352.0508 634.7656 633.7891 277.832 277.832 579.1016 277.832 974.1211 + 633.7891 611.8164 634.7656 634.7656 411.1328 520.9961 392.0898 633.7891 591.7969 817.8711 + 591.7969 591.7969 524.9023 636.2305 336.9141 636.2305 837.8906 600.0977 ] +>> +endobj +13 0 obj +<< +/PageMode /UseNone /Pages 15 0 R /Type /Catalog +>> +endobj +14 0 obj +<< +/Author (anonymous) /CreationDate (D:20260505082323+02'00') /Creator (anonymous) /Keywords () /ModDate (D:20260505082323+02'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (unspecified) /Title (untitled) /Trapped /False +>> +endobj +15 0 obj +<< +/Count 1 /Kids [ 4 0 R ] /Type /Pages +>> +endobj +16 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1375 +>> +stream +GauI7>>sfX'SYH?(&QC"k1K=oCYH7jW#QO3+^.g/01"V,f/OA5Wql=o'2W.t0p.&"K+Q/i0<;_Z?3LD.#Nj?VF+@9sbu4`Z&3^(DVBEO)G1^1NJI,^^5l+!'5)&;1Q!bN$EF9f"ShUN0S0Z$koM:a1)!5BR#Ns^@6ODBC90_cdNrOtP^5(+gVkF<1IR'YlT$?bqG)^qCs9M\PVtED[QW.Ya6aCDor=,Cg[/m>4X(HTF9$`beW+)7&;oMgp7/P:GQLGaUe[dSl92UV,hMbC"fEEG%7r)GeMX23-`=l:qk&$W5gncD0:[9rc/c-T;H+ccCROFnIhZTP7W[#nMI7-K^gm?Ss!ZcUJFNk9FF)erl?YMr4`?`E5ia?PSL4BR@nW_J&N:o*[[Bj--gVD52e\`S.X1`K\ie'$R-lX[&DdsMdq[cWSM9I!1,`Q0'(pm/,R_@H#H%E3cqCn:7D,-kJ159-:b`gG![RfO7MAB.eF*fU"7T$ML)E-@7dkYh7_L=:Sq7#8H`#%YPj4&UgM_DFD)=lCNpITb+P(.KMp#L_7T&C\8(MVJeD+D]:)SX8=,*cc30]&BgM\M[Qe@WjSOubgm'>($`63hQGhu8\`,,jfXD9@ItQD'#d5,0F)pE!K@($0hpmOGRL:5-M5]slLC*\pnKEYk\Y%Fn(IE+c/H!cG'g;?==6C-g".'Nj1O;[Z3t$VoF3V3VJ8m8HXmlIMr2Jp?&";T68ke0;D4ejs^DWN07UYsf>AqC)1-8$;DUBaZG?hpW)3c/[_sokdKSQ5M9L`?[X!V_r6Z3_f$N4<)30,(:Ftendstream +endobj +xref +0 17 +0000000000 65535 f +0000000061 00000 n +0000000117 00000 n +0000000224 00000 n +0000007324 00000 n +0000007592 00000 n +0000008344 00000 n +0000026611 00000 n +0000026879 00000 n +0000028230 00000 n +0000029002 00000 n +0000048189 00000 n +0000048448 00000 n +0000049782 00000 n +0000049852 00000 n +0000050114 00000 n +0000050174 00000 n +trailer +<< +/ID +[] +% ReportLab generated PDF document -- digest (opensource) + +/Info 14 0 R +/Root 13 0 R +/Size 17 +>> +startxref +51641 +%%EOF diff --git a/_data/uploads/placanja/putni_nalog_4_HUB3_20260505_082323.pdf b/_data/uploads/placanja/putni_nalog_4_HUB3_20260505_082323.pdf new file mode 100644 index 0000000..6884d4d --- /dev/null +++ b/_data/uploads/placanja/putni_nalog_4_HUB3_20260505_082323.pdf @@ -0,0 +1,97 @@ +%PDF-1.3 +% ReportLab Generated PDF document (opensource) +1 0 obj +<< +/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 5 0 R +>> +endobj +2 0 obj +<< +/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font +>> +endobj +3 0 obj +<< +/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font +>> +endobj +4 0 obj +<< +/BaseFont /ZapfDingbats /Name /F3 /Subtype /Type1 /Type /Font +>> +endobj +5 0 obj +<< +/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font +>> +endobj +6 0 obj +<< +/BitsPerComponent 8 /ColorSpace /DeviceRGB /Filter [ /ASCII85Decode /FlateDecode ] /Height 294 /Length 6814 /Subtype /Image + /Type /XObject /Width 294 +>> +stream +Gb"/b>F+S/P/Lu4S*jFp4l&k#Le,ZWHo'e/q`k+k>V;Adj`7n:/sjcCEk,F-QcnK2\QgbRbN3cT>V;Adj`7n*q7fp3_Xj7#$$3e#Go.2Jf$knqoX#D#C:C1-0\7hALGA=.9eCB[:M[eLG+2YAF1lU>(_\RRe0]lJB8'/.qd,1lIo(@5eFWKf0h/!l#GnR>3ERSA:^Mrh4H-KLD4m\a>FEaHN.JpHb_=siqpA+^X7XYmJ^99jHO`RVd2#t?GFm>H0tT7-KLD4m\a>FEaHN.JpHb_=si3+8=gma3Z=.LW7eJ9]sj_jas1-aO/GAf+b>]l5!='MmkQFk\jb5?&Xhn?R&F4Ek!?Z!XM=RK3C/+^(VI.uc4buH+tJ$fI'A%>gWXN]!KnD0Rl2-6=or7)Q6O6(9PZ8+Z!^is]Lem*?\n5igr?<2:9.[Q6:ri4Nfb_u#2(7(<;h7C`TA=UE7[0H53]Wm1PATbJbEX^=V9F6C@825]k?T`VNRM&b02%`+C;QG=`m22&ef'i0o4!!H(@:@WQI*KhB2'cAQ?(I?Zkb3Re`-2(kPjKM*NQfKh"3Y0"j:D`q#'Hgs#A%0]hW@ZnlRJ-s*F)tcHri->KT[Ddort<;=T)4R)FKFnrEms?](+bNsiq'A:ro4D-X1P4`oO8bCYbq0>IFIB:4H.`mgc+uPr(WB)o.WpTESoP1o/BDCc,2NC`I2gbbhk8Q2\4rU(AH(%1Omm7h`QkH>22(2c2:;"J+NSLXMo3A>KT`k2uh]^2^.e(Cghb@gb3+1pH3HQ9ust">?Kc>pIj0;4\sjW'/Hc7(=kFhr=X:'cP-'/KZrbfL%S/]^P(G#o>H&9+nN9]@=?V[04FVL4E97QaLp@8ld!3e]@tMnlZF,0m^p.Zm]8hjQC4k;gG"sAfBY7?h043?gf]O^/2LKT[p^mbY1IA]]'o?]\?crF=D#!2DSo_N>d)VDFPlICEG$klYfppCgkN@%\R)0gl+Ynfim(^a@@>de\IEV*EkP4XdXAUV`1=:qNTM/1L>RI)GqZ4PQ%rp_m^^Q-:@:i?g`"`YbKP98/Z_&c1NUlR[WHP.RcN&(Y#o;ebA(JI@@$-&PB',].D!C$;1s)k.?*VVTQdrsq__O%B>\6VVpVqf-T9/A&H"2!C.IXEJ?1$q<]QWm#=:=GSeNKhC]7tI9X-a2SSFZ(df%$ouc>,?LK=.r?=P\&@]5(MM]/hsUh)l+s_Ko^7cRJ=c@9r9cjLf[-0N\_KNR^1i]Xro@[B#M_b2ol@SsF#0e^U!8kLO%FmT)YIfm8k">&6.DjJo18;9l;[@,7rG-'#q]`S9b(sDh)pn%VTqRi[g%J-[h;leesdZ1@_6n4=BEc)SJQC5FS>ig&Y=IqH`T'55JmcmK1)([Rj1V(Zm=Kjo+bWO:p-/@%=-o4t;p1A?1e$3;+hO&S_P=rEd1JqbgbjqjZ1SV/S6a-3c,ti_GY!ZfUL@&=J"!sfjY^1YLE2R/M(JaMnM-qMreZa>7"F?l`/V^kFU.2Djk"C2bdkAcb%M2b*/la\SARo\`\(eS#09T9iKa)oBnE%o&Qh(,3PQH@;hT@\Mm5oMpK#]@*4\6ArE1+:5?MsHKF!?Q'qLPWs/*(]i^l^X4*HC?u)u*Q88&@I(S7!I4R&&MriX*3fQ]=[K_(g.5NWs\*H31a7<]Q=J"?Le!RLMCToa`1@nt&]AAa"/2,S4jk7=.NCaMd#('i/1G?bWlTNqAnm]:7T&$HrR'^7:YW%k'o4!*ZhNN@=0@l'CftBG+8IMs2HXeK\08jcCSPd/'$*C8`)ht2ZgF&>cfq#6RnSCk5q]qkXQ$^EQP]Q`_nhdtb2(Y>9l$&aF['"Ff(m]2[T3Rt"GIE7FZRu.DPD%LemL@'3cL$h=e*PB2Y^!UQhgL[7]#q]p=N\1&Ed=,0fbuk^Wu(BMjfu$F>:jkV/2PIZ?!1rFYf1W%2Y1$QRB75b*L<,TZ*(/c/D+FB%@_VoXNX_'S.<`dnlSuE)A[.ogkO'9(05%J78AVLApLI.CCLZ[=Dl#g*XaL)eKUp/1:'*oQ*l>M>:d6l1QR6TfkQYu@@:7>lcNC@(TZ0UpL8%X:RY%_1,H#Um#=H:(Mha#fkXZ7dWC)+R:0Z=D_,qB=OSVA?G"@DVA$&T$nF,\A!-)rRPnmRg/X_$SR?#@=IIh]Md.pdZq7UUY4ACQ"=l:Gb08^EjEe).3M,Wf_!9I_b&RD!':g*o@?]gl%'*J$.`_@#9QY%j>\:E?o.&@Ml0Ka-)`Cp)KZlTG0UJ3pe%Eir@QM+-fnR`e/0T)\--Tq4q_YKs]pR)_6FL=/"\ZqlEsZoUffmZ]>VDt\RuhL>Cr$`>GF">uY^/1a1n-CM:#7REm^_br2&KAPgg[fQc)/`lP;eGjH`h^5%>NCW[oi&mZY?btj:*?sT'MtPK421//XCb4Cte55H,I_6kITj-?s#"$MriePRl5UfhCs!<\aI!c(flRM@_"GBVTiG22iO8RY'Y$qL6ZCQBoO0oL6C6<;ce=#Z>@C]V>=Kl_kb[^%XRTed@2K^?.>?L=E4r3Kh2"OiHYbi2HVG3W'c5)_X[>tLg($,Po*QsB&Z'9;k>:l;dP;Q+iLV7C[IH+csjkS-^Ml-&UNRRT0DjjDX\6)Kd\$OVZWB=>D[!gSrB%&[kQ;c)rCpdq+:Y%LB+82SboSndjP]Joj/XjhG2,;n]]kKBQj'k%1gs/rYVXe8We+R3gXJT(^J*U4oMnNieR9WHo/M(#d**0qkUGGcm]kQVs>YSTX/6M$SYSTX/6M%?-7s7R=;tEMX^8Q8bU_:snO@_\8Y00?CCMefeLT79H0&3`hWSi3acl%%X^NN_HKCSNY?VIT)r3(mH#gU"26;I3Y?Y#gQ#"3\UV"Jj:Tg%lL6^-NQ#"OTE\/,HFLRXuan;sjYS(-QE_Vs'Xp*jFmPuU#=c:c&aa%%&XMrg2$Yr+qR41):AY/8;>U/ECos,?CD_.t!=_=Pd,aWe^:NMMcQds8_N4lL#2Ir5DEg'bjaZds,EaDd"CGGP&_V1)_B$*hgR4%kgf>r5?]r]3*)[gFKQC00\a^q(1dp+-\AbE>E@rEp"%9[;?Hs1-_7C,'kRd4PHam&0'kEJ2\5>!_?o85!'VR^#6St+*/HVXD.pS0PY2QrK'CRXR#jiQcR]'#;BIm6O]@Io@*Z_k9>i,Y'Wq>oCo":'/m%]12E?EA`_%um(2AE5CYmi1L]m4.C5n^?<3YfcE;Lhq=F?U0X"aUVUmHO\&^G7gm]m=Q'ltT1OEVWm:(L%>Z8hZfj\$N\Ml=N2jW&Jf!i^X[G76'G*SJB1GaAfH0o'hMd03(^"HjrRCP74ZT"3%0%RMQ27;5//U!DIHS[I)@sRHNI>_?/bIuS<=4q?210_n%M]=9V0(VK!\QbK(KY+9gYjUk8]jWL5Y_9R2'4pY%keaZRba([E-/sjt]_sRCea;9";L5"maI:fGiBc#>:H-4Af_YRCQOiXb=-RG]MS3FMEj15-bS+RMDIp6hm]O]GH;31sbS+Qb__PH0:>&Ejo`M_e=?jgo\d6AZ!@#>J3eda)\Y&m_`^a:#)kp'4n&Y10_FegW+?DB^WcOH;31sbS+Qb__PH0:>&Ejo`M@q"IZs(\;"mfeWdn_XG"+nS*mnVcbGq4I(S5KXJQEgAL5Q;fQ?#qI/_YakM,[$?o;LL0.UIK_l!c'3o@+7"KNu(H!(Gp)`HMr1S9.SdJ7t!-Wj(\bf`VGgT]qf'!eReFI8D[a!0\a.H`UrP]I-K\M\PQR$X)Y,3Nhmk;K]3EsW_p20O,0Q^,T8kADa32RJ]i#oU92G'01=Q,b*3DCOa,[$5V<)7_VJFdSM\a!0\a.H`UrP]I-K\M\PQR$X)Y,3Nhmk;K]3Es[&)G&QYJ=3VKoGK+NE$^U;8E^#\?eileKl`/WIim-3DDSZrL>L$lEd+YCs@pLrr9\^Mth(;aGB?U*E&mG/hFf_WUMhsN*\%;f:phVs_8O@3bVN,fLCb]pBluojG$(ig41J)9Ie'tY*ROolSN8FCs">+'T(>40Wcer/cpmQ$ZHQ+=H!Q$Kj.!R9;1d`Hj8?4:J9EL".]_L8C)DOsd`+[1Bml.6h+\DIHuHb,o+:jI##M/S=CfY&_Z\Qa0gt=M8`j=?8GhH#egGqlm,ko$aH%()BrSg_uJRN_,ht@b*a0.]<2d.D(:feLS+`V3>DP-b#JN&%ft55@)EL@:AiOX^UJTR,kN=3sE&^mOFRVnm^[E/ZaB%*MGIA%*_q\D3?*D@Z9RE7\m2R$B\pe]_jCZ*&-8C`esu\[-t$?B`@0bW2k=B'YG!Q#gU8YWU.#-"JTr]pOi1K&pG//>73OC\l&!/?FLu-^QjFK&pG//>73OC\l&!/?FLu-^QjFK&pG//>73OC\l&!/?FLu-^QjFK&pG//>73OC\l&!/?FLu-^QjFK&pG//>73OC\l&!/?FLu-^QjFK&pG//>73OC\l&!/?FLuVtjp@p+;Z;jkdYUf^36!2nS80m)$I?;mtJNS?h7qaE^pH26X)f3LY4E0=H7'g%&V5]WB)@=&uLEhgP`q8KQG,[nfr5\X5gl7BWWODWS-jjC'W\q_XS-Mchl@jI09?MY,UN#jft36@4cNs1mOPg=Ys@DRTf,[*fET2efqS!;n9H+7C,FcWTA$ZZ'aT\/TU_FQ(*\71gB%0>4.a#j`7n:/sjcCEk,F-QcnK2\QgbRbN3cT>V;Adj`7n:/q9)b#M`#Je,~>endstream +endobj +7 0 obj +<< +/Contents 11 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 10 0 R /Resources << +/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /XObject << +/FormXob.890ab0d0d01d65a31588b0bfaa0b92c9 6 0 R +>> +>> /Rotate 0 /Trans << + +>> + /Type /Page +>> +endobj +8 0 obj +<< +/PageMode /UseNone /Pages 10 0 R /Type /Catalog +>> +endobj +9 0 obj +<< +/Author (anonymous) /CreationDate (D:20260505082323+02'00') /Creator (anonymous) /Keywords () /ModDate (D:20260505082323+02'00') /Producer (ReportLab PDF Library - \(opensource\)) + /Subject (unspecified) /Title (untitled) /Trapped /False +>> +endobj +10 0 obj +<< +/Count 1 /Kids [ 7 0 R ] /Type /Pages +>> +endobj +11 0 obj +<< +/Filter [ /ASCII85Decode /FlateDecode ] /Length 1406 +>> +stream +Gb!$G>>O!E'Z],&.?:bgZt^t0*tN9"":1+-F\K3#nm[5;+W%I3[*jF"4h`2HU"P.>Zl@%/N1r,CF66a,1'e`c)Bj$=j?11;]bM[45sIX9FN)Cae:YlcO]W$s4**9g8'reAQ3Qr?-VE[j(o(/q,7D4hng('(&[Sq7XMSImEP:$+H5`2Z$oD9)q2SfDEQ`E5K-1X\Li7NOVdS)j:G?YN$+#'8%)Q!e\:/?9qm4#%]fWOqum4bf:GL$2b7/=A0Dr.AYD=L`8s%P(1U0(_;@r+2I1^hJV7q2UJR/.Bu;U_-qqD;A-QBI`:sY"g'a/b/g:bonUTDbPcHW;*uU?H6%+>7sGoCZuD7.L\->]hH(Q(d.)lU="%:b]/L%4#VmiliQ]K>?F=bb@$U(f,`@MkX2qa;96&<8VF#pSKr@&r4ft#K=GHG.%fVQFjfj8UF89XN9XrN_k05lu9B2tfp/d/lK0.bL@/Z.jW65m?fJ+p8,&Ag^McT]XXHjUD/_%`Vok[_9trh+\tmEI/OID85++5.j@<#qH_Jg&l>eOEm0ik0f]BLtW8g2o]rlTuT4%h5s[)CqWq-Q]H_(!3(BMs*^PV^tb9Z,!\DdlSX2CcC"C).LKjuoLVMACfCYC;&pi]'I!*mh"\iY7lAQ0LN?(NUG0A._OM0;c!m9?A+&?8]e.[9!gG8aXJn1nu,$n"IF^^m8D_BiKH0J/d*H2<9M"=,m-%Bs3tX^JfK!C5L?Tq3BZl=bT_G\gd0W,8CnZPkU2.EMCfljYX)'J*Zr;qoJ4B:Z&g1pnmrpQhU"n4I2"=DU"=`R*MW![#?*)ONjp$6h:!D)]h%.BlXaKe!kM_-3[Q(43GcC%Q[,#D+ZgINe1GJT_/hdpp-t~>endstream +endobj +xref +0 12 +0000000000 65535 f +0000000061 00000 n +0000000122 00000 n +0000000229 00000 n +0000000341 00000 n +0000000424 00000 n +0000000501 00000 n +0000007506 00000 n +0000007774 00000 n +0000007843 00000 n +0000008104 00000 n +0000008164 00000 n +trailer +<< +/ID +[<810caeac168f23cd3bade25196ed4788><810caeac168f23cd3bade25196ed4788>] +% ReportLab generated PDF document -- digest (opensource) + +/Info 9 0 R +/Root 8 0 R +/Size 12 +>> +startxref +9662 +%%EOF diff --git a/routers/debug_router.py b/routers/debug_router.py new file mode 100644 index 0000000..aaea6e6 --- /dev/null +++ b/routers/debug_router.py @@ -0,0 +1,170 @@ +"""Debug observability dashboard endpoint.""" +import json, os, time +from pathlib import Path +from fastapi import APIRouter, Query +from fastapi.responses import JSONResponse, HTMLResponse, PlainTextResponse +from typing import Optional + +router = APIRouter(prefix="/api/debug", tags=["debug"]) + +LOGDIR = Path("/var/log/pgz-sport-debug") + +@router.get("/health") +def debug_health(): + """Quick service status.""" + import subprocess + services = ['pgz-sport', 'pgz-debug-tail', 'pgz-auto-triage', 'nginx', 'redis-server'] + status = {} + for s in services: + try: + r = subprocess.run(['systemctl', 'is-active', s], capture_output=True, text=True, timeout=2) + status[s] = r.stdout.strip() + except Exception as e: + status[s] = f"error:{e}" + + # DB + db_status = "unknown" + try: + import psycopg2 + with psycopg2.connect("host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password=R1net2026!SecureDB#v7", connect_timeout=2) as conn: + with conn.cursor() as cur: + cur.execute("SELECT 1") + db_status = "ok" + except Exception as e: + db_status = f"error:{e}" + + # Recent errors count + err_count = 0 + if (LOGDIR / "errors.jsonl").exists(): + with open(LOGDIR / "errors.jsonl") as f: + err_count = sum(1 for _ in f) + + return { + "ts": time.time(), + "services": status, + "db": db_status, + "total_errors_logged": err_count, + "log_dir": str(LOGDIR), + } + +@router.get("/errors") +def recent_errors(limit: int = Query(100, ge=1, le=1000)): + """Last N errors from errors.jsonl.""" + f = LOGDIR / "errors.jsonl" + if not f.exists(): + return {"errors": [], "note": "errors.jsonl not yet created"} + lines = f.read_text(errors='ignore').strip().split('\n')[-limit:] + parsed = [] + for line in lines: + try: + parsed.append(json.loads(line)) + except: + continue + return {"errors": parsed, "count": len(parsed)} + +@router.get("/decisions") +def triage_decisions(limit: int = Query(50, ge=1, le=500)): + """Last N auto-triage decisions.""" + f = LOGDIR / "triage_decisions.jsonl" + if not f.exists(): + return {"decisions": [], "note": "no decisions yet"} + lines = f.read_text(errors='ignore').strip().split('\n')[-limit:] + parsed = [] + for line in lines: + try: + parsed.append(json.loads(line)) + except: + continue + return {"decisions": parsed, "count": len(parsed)} + +@router.get("/stream") +def stream_tail(lines: int = Query(200, ge=10, le=2000)): + """Last N lines of full stream.jsonl.""" + f = LOGDIR / "stream.jsonl" + if not f.exists(): + return {"stream": []} + raw = f.read_text(errors='ignore').strip().split('\n')[-lines:] + parsed = [] + for line in raw: + try: + parsed.append(json.loads(line)) + except: + continue + return {"stream": parsed} + +@router.get("/dashboard", response_class=HTMLResponse) +def dashboard(): + """Live HTML dashboard.""" + return """ +PGŽ Debug Live + + +

🩺 PGŽ Sport · Live Debug Dashboard refresh: 5s

+
+
+

Service Health

+
loading…
+
+
+

Auto-Triage Decisions

+
loading…
+
+
+

Recent Errors (live)

+
loading…
+
+
+ +""" diff --git a/routers/enrich_router.py b/routers/enrich_router.py index 64152dc..5ed7882 100644 --- a/routers/enrich_router.py +++ b/routers/enrich_router.py @@ -1322,8 +1322,17 @@ def _apply_to_db(kind: str, eid: int, fields: dict, sources: list, user_email: O params.append(json.dumps(meta_in, ensure_ascii=False, default=str)) params.append(eid) - cur.execute(f"UPDATE {table} SET {', '.join(sets)} WHERE id=%s RETURNING *", params) - after = dict(cur.fetchone()) + try: + cur.execute(f"UPDATE {table} SET {', '.join(sets)} WHERE id=%s RETURNING *", params) + after = dict(cur.fetchone()) + except psycopg2.errors.UniqueViolation as _uve: + # Race condition — fetch existing row instead + conn.rollback() + cur.execute(f"SELECT * FROM {table} WHERE id=%s", (eid,)) + row = cur.fetchone() + after = dict(row) if row else {} + import logging as _lg + _lg.getLogger("enrich").info(f"UniqueViolation race avoided table={table} id={eid}") cur.execute( """INSERT INTO pgz_sport.enrichment_log diff --git a/scrapers/wiki_hr_categories_fixed.json b/scrapers/wiki_hr_categories_fixed.json new file mode 100644 index 0000000..39f684f --- /dev/null +++ b/scrapers/wiki_hr_categories_fixed.json @@ -0,0 +1,31 @@ +[ + "Gradovi_u_Hrvatskoj", + "Hrvatski_otoci", + "Planine_u_Hrvatskoj", + "Rijeke_u_Hrvatskoj", + "Primorsko-goranska_županija", + "Naselja_u_Primorsko-goranskoj_županiji", + "Hrvatski_političari", + "Hrvatski_športaši", + "Hrvatski_glazbenici", + "Hrvatski_književnici", + "Hrvatski_glumci", + "Hrvatska_povijest", + "Hrvatska_kuhinja", + "Hrvatska_kultura", + "Domovinski_rat", + "Gospodarstvo_Hrvatske", + "Hrvatski_nogometni_klubovi", + "Hrvatski_košarkaški_klubovi", + "Hrvatski_rukometni_klubovi", + "Hrvatski_odbojkaški_klubovi", + "Hrvatske_političke_stranke", + "Rijeka", + "Krk", + "Cres", + "Lošinj", + "Rab", + "Pag", + "Učka", + "HNK_Rijeka" +] \ No newline at end of file diff --git a/scrapers/wiki_hr_scraper.py b/scrapers/wiki_hr_scraper.py index 5811c6f..5de8823 100644 --- a/scrapers/wiki_hr_scraper.py +++ b/scrapers/wiki_hr_scraper.py @@ -18,40 +18,35 @@ API = "https://hr.wikipedia.org/w/api.php" # Kategorije — širok HR knowledge bazu CATEGORIES = [ - "Hrvatski_gradovi", - "Hrvatske_općine", + "Gradovi_u_Hrvatskoj", "Hrvatski_otoci", - "Hrvatske_planine", - "Hrvatske_rijeke", + "Planine_u_Hrvatskoj", + "Rijeke_u_Hrvatskoj", "Primorsko-goranska_županija", "Naselja_u_Primorsko-goranskoj_županiji", "Hrvatski_političari", - "Hrvatski_sportaši", + "Hrvatski_športaši", "Hrvatski_glazbenici", - "Hrvatski_pisci", + "Hrvatski_književnici", "Hrvatski_glumci", "Hrvatska_povijest", - "Hrvatska_arhitektura", "Hrvatska_kuhinja", "Hrvatska_kultura", - "Hrvatska_znanost", "Domovinski_rat", - "Hrvatska_ekonomija", - "Hrvatski_klubovi", + "Gospodarstvo_Hrvatske", "Hrvatski_nogometni_klubovi", "Hrvatski_košarkaški_klubovi", + "Hrvatski_rukometni_klubovi", + "Hrvatski_odbojkaški_klubovi", "Hrvatske_političke_stranke", - "Predsjednici_Hrvatske", - "Premijeri_Hrvatske", "Rijeka", - "Kvarner", "Krk", "Cres", "Lošinj", "Rab", "Pag", "Učka", - "Risnjak", + "HNK_Rijeka" ] def api_get(params): diff --git a/scripts/auto_triage.py b/scripts/auto_triage.py new file mode 100755 index 0000000..a7adfcb --- /dev/null +++ b/scripts/auto_triage.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +""" +auto_triage.py — Active error monitor za pgz-sport stack. + +Tail-a /var/log/pgz-sport-debug/errors.jsonl, klasificira greške, +i automatski dispatcha tasks na CC agente kad detektira pattern. + +Patterns: +- Recurring 5xx → CC4 (backend) +- 401/403 spike → CC2 (auth) +- 4xx na specifičnoj stranici → CC3 (frontend route) +- DB connection error → CC4 + telegram urgent +- ImportError/AttributeError u pgz-sport → CC4 dispatch + restart attempt +""" +import json, os, re, time, subprocess, sys +from collections import defaultdict, deque +from pathlib import Path +from datetime import datetime + +LOG_FILE = Path("/var/log/pgz-sport-debug/errors.jsonl") +TRIAGE_LOG = Path("/var/log/pgz-sport-debug/triage.log") +TRIAGE_DECISIONS = Path("/var/log/pgz-sport-debug/triage_decisions.jsonl") +TG_TOKEN = "8535797835:AAFItT-92jzZ9NWFafLxn0dLa1_n2s-JE5Y" +TG_CHAT = "7969491558" + +# Rate limit: ne više od X telegram poruka po 5 min +RATE_WIN = 300 # seconds +RATE_MAX = 6 +recent_alerts = deque() + +# Pattern counts (sliding window) +PATTERN_WIN = 60 # 60s window +recent_patterns = defaultdict(deque) + +def log(msg): + ts = datetime.now().isoformat(timespec='seconds') + with open(TRIAGE_LOG, "a") as f: + f.write(f"[{ts}] {msg}\n") + print(f"[{ts}] {msg}", flush=True) + +def telegram(text): + now = time.time() + while recent_alerts and now - recent_alerts[0] > RATE_WIN: + recent_alerts.popleft() + if len(recent_alerts) >= RATE_MAX: + log(f"RATE LIMITED telegram: {text[:80]}") + return False + recent_alerts.append(now) + try: + subprocess.run([ + "curl", "-s", "-X", "POST", + f"https://api.telegram.org/bot{TG_TOKEN}/sendMessage", + "-d", f"chat_id={TG_CHAT}", + "--data-urlencode", f"text={text}" + ], timeout=10, capture_output=True) + return True + except Exception as e: + log(f"telegram fail: {e}") + return False + +def dispatch_to_cc(session, msg): + """Pošalji task na CC tmux session.""" + try: + subprocess.run(["tmux", "send-keys", "-t", f"{session}:0", msg], check=False, capture_output=True) + time.sleep(1) + subprocess.run(["tmux", "send-keys", "-t", f"{session}:0", "Enter"], check=False, capture_output=True) + log(f"dispatched to {session}: {msg[:80]}") + record_decision({"action": "dispatch", "target": session, "msg": msg[:200]}) + return True + except Exception as e: + log(f"dispatch fail to {session}: {e}") + return False + +def record_decision(obj): + obj["ts"] = datetime.now().isoformat(timespec='seconds') + with open(TRIAGE_DECISIONS, "a") as f: + f.write(json.dumps(obj) + "\n") + +def pattern_count(key, since=None): + """Count of pattern occurences within sliding window.""" + if since is None: since = time.time() - PATTERN_WIN + dq = recent_patterns[key] + while dq and dq[0] < since: + dq.popleft() + return len(dq) + +def add_pattern(key): + recent_patterns[key].append(time.time()) + +def classify(line): + try: + ev = json.loads(line) + except: + return None + + msg = ev.get("msg", "") or "" + src = ev.get("src", "") + code = ev.get("code", "") + path = ev.get("path", "") + method = ev.get("method", "") + + # ─── Pattern A: HTTP 5xx + if code and code.startswith("5"): + key = f"5xx:{path[:100]}" + add_pattern(key) + n = pattern_count(key) + if n >= 3: + telegram(f"⚠️ 5xx spike: {method} {path} → {code} (×{n}/60s)") + dispatch_to_cc("cc4", f"5xx detected: {method} {path} {code} occurring {n}x in 60s. Investigate /opt/pgz-sport/routers/ for the route handler. Check DB connection, log traceback. Run smoke test. Fix + restart pgz-sport + verify resolved.") + recent_patterns[key].clear() # reset after dispatch + return ("5xx_spike", n, path) + + # ─── Pattern B: 401/403 spike (auth issue) + if code in ("401", "403"): + key = f"auth:{path[:80]}" + add_pattern(key) + n = pattern_count(key) + if n >= 5: + telegram(f"🔒 Auth spike: {code} on {path} (×{n}/60s)") + dispatch_to_cc("cc2", f"Auth spike: {code} on {path} ×{n} times in 60s. Check JWT middleware in pgz_sport_api.py + auth/auth_v2.py. Verify role-based access control. Smoke test 3 demo accounts.") + recent_patterns[key].clear() + return ("auth_spike", n, path) + + # ─── Pattern C: 4xx on consumer endpoints (frontend bug) + if code and code.startswith("4") and code not in ("401", "403"): + if path.startswith("/sport/api/"): + key = f"4xx_api:{path[:80]}" + add_pattern(key) + n = pattern_count(key) + if n >= 5: + telegram(f"⚠️ 4xx API: {path} ×{n}/60s") + dispatch_to_cc("cc3", f"Frontend bug: {path} returning {code} ×{n}x. Frontend may call wrong URL or send bad payload. Check static/*.html for fetch/api() calls to {path}. Verify request shape matches backend schema.") + recent_patterns[key].clear() + return ("4xx_api", n, path) + + # ─── Pattern D: ImportError / AttributeError / SyntaxError u backendu + crit_patterns = [ + (r"ImportError|ModuleNotFoundError", "import_error"), + (r"AttributeError", "attribute_error"), + (r"SyntaxError", "syntax_error"), + (r"OperationalError.*could not connect", "db_connect_error"), + (r"asyncpg|psycopg2.*OperationalError", "db_pool_error"), + (r"FATAL|CRITICAL", "fatal"), + ] + for pat, kind in crit_patterns: + if re.search(pat, msg, re.I): + telegram(f"🚨 {kind.upper()}: {msg[:200]}") + target = "cc4" if "db" not in kind else "cc4" + dispatch_to_cc(target, f"CRITICAL {kind} detected u pgz-sport: {msg[:300]}. Identify file:line, fix, py_compile, restart, verify. If db_connect_error, check Server B (10.10.0.2:6432) connectivity.") + return (kind, 1, msg[:80]) + + # ─── Pattern E: Empty page detection + if code == "200" and "size_download" in str(ev) and ev.get("size", 0) < 100: + key = f"empty:{path}" + add_pattern(key) + if pattern_count(key) >= 2: + telegram(f"📄 Empty page: {path}") + dispatch_to_cc("cc3", f"Empty page detected: {path} returning <100 bytes. Check static/{path.split('/')[-1]} or backend response.") + recent_patterns[key].clear() + + return None + +def follow(path): + """Tail -F equivalent.""" + while not path.exists(): + time.sleep(1) + + f = open(path, "r") + f.seek(0, 2) # EOF + while True: + line = f.readline() + if not line: + time.sleep(0.5) + continue + result = classify(line) + if result: + log(f"PATTERN {result[0]} ×{result[1]}: {result[2]}") + +if __name__ == "__main__": + log("auto_triage starting") + log(f"watching {LOG_FILE}") + try: + follow(LOG_FILE) + except KeyboardInterrupt: + log("shutdown") diff --git a/scripts/debug_tail.sh b/scripts/debug_tail.sh new file mode 100755 index 0000000..b882238 --- /dev/null +++ b/scripts/debug_tail.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Tail journalctl + nginx errors → strukturirani JSONL stream +LOGDIR=/var/log/pgz-sport-debug +mkdir -p $LOGDIR + +# Tail journalctl +journalctl -u pgz-sport -f -n 0 --output=cat 2>/dev/null | while read line; do + ts=$(date -Iseconds) + level="INFO" + + # Klasifikacija + if echo "$line" | grep -qE "ERROR|Exception|Traceback|CRITICAL|FATAL"; then level="ERROR"; fi + if echo "$line" | grep -qE "WARNING|WARN"; then level="WARN"; fi + if echo "$line" | grep -qE "DEBUG"; then level="DEBUG"; fi + + # JSON-escape + safe=$(echo "$line" | python3 -c "import json,sys; print(json.dumps(sys.stdin.read().strip()))") + echo "{\"ts\":\"$ts\",\"src\":\"pgz-sport\",\"level\":\"$level\",\"msg\":$safe}" >> $LOGDIR/stream.jsonl +done & +JPID=$! +echo $JPID > $LOGDIR/journalctl_tail.pid + +# Tail nginx error log +tail -F /var/log/nginx/sport.error.log 2>/dev/null | while read line; do + ts=$(date -Iseconds) + safe=$(echo "$line" | python3 -c "import json,sys; print(json.dumps(sys.stdin.read().strip()))") + echo "{\"ts\":\"$ts\",\"src\":\"nginx\",\"level\":\"ERROR\",\"msg\":$safe}" >> $LOGDIR/stream.jsonl +done & +NPID=$! +echo $NPID > $LOGDIR/nginx_tail.pid + +# Tail nginx access log za 4xx/5xx +tail -F /var/log/nginx/sport.access.log 2>/dev/null | while read line; do + # parse: status code je 9. polje (combined log format) + code=$(echo "$line" | awk '{print $9}') + if [[ "$code" =~ ^[45][0-9][0-9]$ ]]; then + ts=$(date -Iseconds) + method=$(echo "$line" | awk '{print $6}' | tr -d '"') + path=$(echo "$line" | awk '{print $7}') + safe=$(echo "$line" | python3 -c "import json,sys; print(json.dumps(sys.stdin.read().strip()))") + echo "{\"ts\":\"$ts\",\"src\":\"nginx-access\",\"level\":\"WARN\",\"code\":\"$code\",\"method\":\"$method\",\"path\":\"$path\",\"raw\":$safe}" >> $LOGDIR/stream.jsonl + + # ACTIVE ALERTING: ako je 5xx ili 401-403, log do error feed + if [[ "$code" =~ ^5 ]] || [[ "$code" == "401" ]] || [[ "$code" == "403" ]]; then + echo "{\"ts\":\"$ts\",\"src\":\"nginx-access\",\"level\":\"ERROR\",\"code\":\"$code\",\"method\":\"$method\",\"path\":\"$path\"}" >> $LOGDIR/errors.jsonl + fi + fi +done & +APID=$! +echo $APID > $LOGDIR/access_tail.pid + +# Drop ERROR-level u zaseban error file (agenti gledaju ovaj) +tail -F $LOGDIR/stream.jsonl 2>/dev/null | grep -E "\"level\":\"(ERROR|CRITICAL|FATAL)\"" >> $LOGDIR/errors.jsonl & +EPID=$! +echo $EPID > $LOGDIR/error_filter.pid + +echo "Debug tail running. PIDs: journalctl=$JPID nginx=$NPID access=$APID error_filter=$EPID" +echo " stream.jsonl + errors.jsonl in $LOGDIR" +wait