From 0321a1fc4b59a7bf1928e8bbf0fd99b30e3dd70b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20Mal=C3=BD?= Date: Sun, 16 Nov 2014 00:42:17 +0100 Subject: [PATCH] Initial commit --- bin/images/arrow_down.png | Bin 0 -> 938 bytes bin/images/arrow_right.png | Bin 0 -> 915 bytes bin/images/h3o_calc.png | Bin 0 -> 2562 bytes bin/images/h3o_calc_atpr_acid.png | Bin 0 -> 2935 bytes bin/images/h3o_calc_dissoc_acid.png | Bin 0 -> 4221 bytes bin/images/ign_atpr_acid.png | Bin 0 -> 2954 bytes bin/images/ign_atpr_base.png | Bin 0 -> 2721 bytes bin/images/ign_chart_acid.png | Bin 0 -> 42304 bytes bin/images/ign_chart_base.png | Bin 0 -> 42099 bytes bin/images/ign_dissoc_acid.png | Bin 0 -> 2708 bytes bin/images/ign_dissoc_base.png | Bin 0 -> 2388 bytes bin/images/oh_calc.png | Bin 0 -> 2270 bytes bin/images/oh_calc_atpr_base.png | Bin 0 -> 2771 bytes bin/images/oh_calc_dissoc_base.png | Bin 0 -> 3957 bytes bin/images/ph_calc.png | Bin 0 -> 2159 bytes bin/images/poh_calc.png | Bin 0 -> 1980 bytes bin/scripts/expand_collapse.js | 19 + bin/styles/main.css | 249 +++++++++ bin/templates/answer_section.html | 3 + bin/templates/face_acidobazic.html | 47 ++ bin/templates/face_index.html | 3 + bin/templates/footer.html | 9 + bin/templates/header.html | 19 + bin/templates/hints_acidobazic_acid.html | 45 ++ bin/templates/hints_acidobazic_base.html | 44 ++ src/face_generators/face_generator.adb | 130 +++++ src/face_generators/face_generator.ads | 32 ++ src/formatting_helpers.adb | 75 +++ src/formatting_helpers.ads | 17 + src/global_types.adb | 33 ++ src/global_types.ads | 17 + src/handlers/handler_check_answer.adb | 74 +++ src/handlers/handler_check_answer.ads | 5 + src/handlers/handler_default.adb | 32 ++ src/handlers/handler_default.ads | 5 + src/handlers/handler_images.adb | 50 ++ src/handlers/handler_images.ads | 5 + src/handlers/handler_next_problem.adb | 76 +++ src/handlers/handler_next_problem.ads | 5 + src/handlers/handler_start.adb | 78 +++ src/handlers/handler_start.ads | 5 + src/handlers/handler_styles.adb | 21 + src/handlers/handler_styles.ads | 6 + src/handlers/handlers.adb | 27 + src/handlers/handlers.ads | 5 + src/nine_q.adb | 29 + .../problem_generator-acidobazic_suite.adb | 497 ++++++++++++++++++ src/problem_generators/problem_generator.adb | 37 ++ src/problem_generators/problem_generator.ads | 97 ++++ .../problem_generator_syswides.ads | 41 ++ src/problem_manager.adb | 237 +++++++++ src/problem_manager.ads | 46 ++ 52 files changed, 2120 insertions(+) create mode 100644 bin/images/arrow_down.png create mode 100644 bin/images/arrow_right.png create mode 100644 bin/images/h3o_calc.png create mode 100644 bin/images/h3o_calc_atpr_acid.png create mode 100644 bin/images/h3o_calc_dissoc_acid.png create mode 100644 bin/images/ign_atpr_acid.png create mode 100644 bin/images/ign_atpr_base.png create mode 100644 bin/images/ign_chart_acid.png create mode 100644 bin/images/ign_chart_base.png create mode 100644 bin/images/ign_dissoc_acid.png create mode 100644 bin/images/ign_dissoc_base.png create mode 100644 bin/images/oh_calc.png create mode 100644 bin/images/oh_calc_atpr_base.png create mode 100644 bin/images/oh_calc_dissoc_base.png create mode 100644 bin/images/ph_calc.png create mode 100644 bin/images/poh_calc.png create mode 100644 bin/scripts/expand_collapse.js create mode 100644 bin/styles/main.css create mode 100644 bin/templates/answer_section.html create mode 100644 bin/templates/face_acidobazic.html create mode 100644 bin/templates/face_index.html create mode 100644 bin/templates/footer.html create mode 100644 bin/templates/header.html create mode 100644 bin/templates/hints_acidobazic_acid.html create mode 100644 bin/templates/hints_acidobazic_base.html create mode 100644 src/face_generators/face_generator.adb create mode 100644 src/face_generators/face_generator.ads create mode 100644 src/formatting_helpers.adb create mode 100644 src/formatting_helpers.ads create mode 100644 src/global_types.adb create mode 100644 src/global_types.ads create mode 100644 src/handlers/handler_check_answer.adb create mode 100644 src/handlers/handler_check_answer.ads create mode 100644 src/handlers/handler_default.adb create mode 100644 src/handlers/handler_default.ads create mode 100644 src/handlers/handler_images.adb create mode 100644 src/handlers/handler_images.ads create mode 100644 src/handlers/handler_next_problem.adb create mode 100644 src/handlers/handler_next_problem.ads create mode 100644 src/handlers/handler_start.adb create mode 100644 src/handlers/handler_start.ads create mode 100644 src/handlers/handler_styles.adb create mode 100644 src/handlers/handler_styles.ads create mode 100644 src/handlers/handlers.adb create mode 100644 src/handlers/handlers.ads create mode 100644 src/nine_q.adb create mode 100644 src/problem_generators/problem_generator-acidobazic_suite.adb create mode 100644 src/problem_generators/problem_generator.adb create mode 100644 src/problem_generators/problem_generator.ads create mode 100644 src/problem_generators/problem_generator_syswides.ads create mode 100644 src/problem_manager.adb create mode 100644 src/problem_manager.ads diff --git a/bin/images/arrow_down.png b/bin/images/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..9be0360a7b617b3e4dd0ce25edf5d3b7c2a8e378 GIT binary patch literal 938 zcmV;b16BNqP)>&UE12;)T zK~z}7&6n9uQ&AL#zn(&YmO(@%AZP@QLrk1dgDA!eNYJP7F-&{_Uqg+Fcg8y-g2n;I zIDs@OK@mj}1WG7Wrh4-qvOU?m6BoLYru3Y(_xk^T4SNIs*h2c=44eT@0a4%`a0~bV zEUz<05wH{J0`>#*z$2gsnDCvtfNo$CNC1PtMWEgQS*X6SM}SV?q4xCmpHWOaIqn83jfKU)UZ4ZmQlJVUrM6Rh6&vp=l#Vq3QR8pl zDuH(3aDggBfJ54&$^x#^1TbYOHV>M`a)P=!|D1W=X% zK$(vo3#J<5YGZuk2Qh3Hpv1e(KA*nEKVNn9K zSSmcKXRc0}5c~JR^xATn(Bbg{_-)B^ouNXJlxWhkdI$SgVn1su94smf19yP$mX3(% zd^oKsSFUN<;+U2{bkq;o?iOK3J1qezodwN-lPn~eLu%cPz>A2La z8-QP6a|&Lixv-#o_W;*zCtfc9oaSROV4tpM&3g7pvtl4ijr@Q12jmafCx^&MVgLXD M07*qoM6N<$f+63elmGw# literal 0 HcmV?d00001 diff --git a/bin/images/arrow_right.png b/bin/images/arrow_right.png new file mode 100644 index 0000000000000000000000000000000000000000..859848fab636fb2e827fd60058e246eecab1e93c GIT binary patch literal 915 zcmV;E18n?>P)cG}31Ym8iaikV zz*g;Hi}j>QD{ZrFlHK)}&%h9}v%5))I4}%5v-5r4^E{vD`0^hgwOqI15MIIKn8siD z21juc3s^<(c7VKvk8uSZbg>enU*KK5iW!WRz&Cv~a(xGLxF?I(P{$;8-~b-OeeuF| zEC=+K0K6I3PptEwI;L-Teo@g7m6Axk*`}6xI+M^1!4DeIVs{v?5aP^9Jr}0og9*$<(qkt3u-3aSfe21TL zEelQ!P<{hUGDu@Uh*kq~HX!Gc|E+|k*$1Q+$a^Ew*6p)Nu*5TFcl z8D|1=zLYhSS*IV!IJPEjXH(3sX1(Rch-tf$7f<8ch_+J#X;$={iS(R3N!yFK*w{ev z>Lm;d_z~aZw>)dbP_v?#wF2-bjx{z>#tV>30Xdlr+8Pm7d7~EZylWp2?sxQPHdlpx_FVg8+)$-GOKG#m}Xp zO%8asm(qJ7Bi(!q|B<%cTRW7lXLvgf=8Ip5>!*e=+)j!ejo}mcJ$<0pZz?XA&6$X5 z4lhM)Q$x7=MhepL7(SkA*RC4bYNd3X!Ba8(a;anvsuefm+7mH+EUsS~W>ze+U>^?S zjSQXBLl|C;n7+bCiS;iLU3b(%^g7&H8MUYs00*M>u>qZM zG3|IhX>}&^LT97pv;Zi^>V8FJ0Y7B-bOfh1Yj`cd=NT9dmf~MbL#Oa5K1ubuoM*Rf pbGkoix0qgEY)%&|y!8J){sr|D+fzf}FM0p~002ovPDHLkV1j;7pTGbB literal 0 HcmV?d00001 diff --git a/bin/images/h3o_calc.png b/bin/images/h3o_calc.png new file mode 100644 index 0000000000000000000000000000000000000000..aa327b57b722c958d292867adb00e9079023a29f GIT binary patch literal 2562 zcmV+d3jOtoP)4M`&+z zbaP{JX>fEPIW93YowHn6000SjNklU9#gS428v1Z_#&=@gkXrm_#8W1}p@a z^HsTKx;r75KOqST=e;KY0000y`LA~c0001hk$_+X0000O|9V#d05A^DIrvt@%RUU+bbV^I?00S@cAfVK#LKxrIfytBkkb6upq z--N#d0|IRkfHXyd5j)H4umve4M0R=xSr5Hn#bW{1gXw^_2tWc8AL9>G?62o*p{*!m z2-BW(ZVPQdEbW%}-i3#V{^%(bHzEq{1q}?S!^TzB=1Xfn3+1Ym1He_RQo`k!-9A2( z*vbAy-}_lc;&)96af8Pnlj*h&4RMdY^Ki}~MHkTKYED$G`J5^@Sr+C3Fu{9&`Vi6M z`O)*wmXybyt+E_mhFblU-f?4fdz8Lo)J6~Owr}vhP0Gci-cMVrF?Dr5NA;_kH+b-9 zJiWp-K6Zp&$Dh_+I}98e-$zpB(s%UFo$+f2{mX<@V%nmk?KMqcU@BVNprJXRLs@?{ z^9BG7Mz~$0+Rvd~5e;%pr29N%fTsCP=%4UNK`oP*}Q>J1jEonFy79;$jv|p%@!YoaDYl$_!fD& zL{v7Px%C1<5sXA{jqxu2MP8rQ;S~^!por15#UrjaTy+JLJ{S&DALAYUi^4l$=L<%o ziMCiXpJ6%$Gtm1~>eT*4OA{&pH5h7(L?`o^nND%)6by~EDfMptMVK|g=(O@J^0J92 zJ)dDZwd06smg1TpIG>PDPaosd{zXAs#H0r!5@G|pXp1ZqBl#I{9k%&+#e(|JT+Bqz z6q7@&pQB?Y7;orbJj>GxKv&qDQy-g|Nx^vrC?Z%`nh>?~Un17|bx8p<$8&o67+>a< zD|GbfSq-kG2eG7?TVZJdaL#QF#BGu zY7sxlnp&pmoruXk?*0py%W_sxJCW%Y8QV3-? z9WT;6l;)K1j@`$|)%9b9hYD`riPw?BvXhV2_eShXvx|xw6(Fb!P@w$V~cUvx-YFxrS^_re9(Kx^hH#kQ8dRdZBeP+Ku3hz zO=4~8(k|G*w@CCoHpP>W0pZ_zos5hHa_c&%m*3$qT2ldcfu?=gBaX3G{9=4ED`vD? zEMP{xA&0r?kNr8`uw5N{*4kq3hAsY#=^VS!b!%p^)3uwIXsvD*D%A4bn+wJf{EM?8 z}x2%Kre)sR}GcEdL2w^4A()?=DmllGtw|_AV(6d7| zquA2F{5M@{F$f{mRfoqqb=HSDwbYDdkH;kA*1;ht^2a6V4T6fSdcy9XC1Dd zvXf$yM%bnGCNf7^K0VsL>JSbadrcRO2@_CTUAHw2hXp2QY>nn$H0{eNG)7W1$GYfR z7J4_hD=Ak~YZP)BLMX0Dtu|#M7zK)d&nk4|8H(VGj5Uc8Cu*({>5H>sg@E>n806+8-GUX zq>6GbH=2Lm0nC+kDW+^(J%#~dyd z08`Gxs7<9%n4PHH*B6(Y`+dHCYODi2`%+CX-hoIkRyFX+&iL|Pec9`F4KsM7SuEOKh}8n1f?(Pwk~OVfCsBvQ zy;FrPw=vTc>XCo3#A2rAUmT$?d!&T3=GD;?#$kbVNrrB;^es-zgUYf*%^b4Pk`daX zdta^~7+;j^tY$W?fcX(D51a7+5Q~=G_fHNImBlYD98skp%+l(3=n3>uU5AH)^970>9S8B6Y9B`?dB>p_y zEMl9Snk8936n!sI#xQ-k%&Am%2uE5_AQUfhhcPlDsW69?+xb^$ScYZjbirGxZF9qD zgwVR2@VwLYbkx3F!IVlw@7;G|H-U^ZU`P@H|4Wb6D}rk!p_fCgBB0fdqxR)8f-wM% z5KIUU-M|)QP8$<0ANwzlQD%0M$9(=8dItvdlD++CtHU%)8(bX$0K8^uc;#Aj$!h5;rSjP^{dUr3 z#@anrg2`G=;bng!J_9dMq zf#^$jaa+JpS7)K$sk1;UGz}Ib*i|H64|+5_o?kA?1$=Bn|Bz*y`gz^>=NJ(JoRi|D zJ_#Nd28x7^izG2A>1l!+VBkEb%nkjzLZFpcqM{hvT6xqJWUGfWNtAW;SNmh*mX=`O z9Z`2nmF=!B*7&h>y6=(v7TA4q8z!oPG28$jjm#s5YQX(Xhwwr41g(E zs~p^xvd`i3Sh+Ker`8+WHRRS;Gy0_{hp^(c*q6CY&A#qk5_YKuL1aQY(wyMMFzUZ5 z<{6e-dW}fEhfizLdnq%Izuov_lqyHW-b`p1De|=L_kfPm2_DX+BoS-ajLw1yHDCHVY2TI+*j3~}lwOa2im|{% zT(55wSF;i%D-V&0WoC=qtpl>ULPllkt}G_mE$_srjoESaFNRBw-+O}ENuMMgOD@yA zv9z|asvoRxiiM*)Hp%g-gar9y?n&E7dy5k)$J=#1Zd4f(sUcnm+YLoy9nW@f9XeoC zuCd*$vz-CqXYw}WqSJDl=8L*xD4t2Uk7;K5i!eS>kCHOxJBb0lrDN=aLgVD*f#6w!e&O z#u$iF^v;>W+};hWee=B)oXbU79Daum@7~F84S8G4)iB)QY$rF=$A~fCJg*P#8HCGw zSrlfUU0#061ud_j8ZH?U3U+t3bol_4WLET6Bx6Rc1@sa;VAWsBZ~Ei6!sezQ8^HvLFSV`6F*9QqHbC%OboA|E_bx*N?}#PRJxWX z3^D&D!(^Iu`WAI6t$TFFGTl*iM$@gc-x|9^c1U||KX1o*khAdrDoY$+CN&{?eqWO6 zu}X5!Fjf28AXCA|$q*&2hN@DVYgkCqG=uriq2}~Q<>Uiu=LNU&_8vg@JMI|!>TNHm zPii1>`K3OT2t187K%smnNJajAwoLlWyb@HbG*Bhc8m+3o{rJ-1=}JoZp^n!JDwSp%;$%UG(`!*WLE{&pGne{{6ix7YuJ*jWFxe z9VDbvH+%Pv(JeP6u#yYr>`!(0V}jF@irLOe*^FvXT#sAYU-ZjUivt({YZ?8XuRd*;fW=QcV>N6EK2B=g;7NH)p77v%qx!0k+ zm2Lln9)v)~q1GzjgQ5xuK{4BI5b)^ijqclg9fb#Tqf8?8-#50h@d=g}4zFprjj1S2 z8Zy`LJjg}|cDbpWN+ZsQUV(@=p73G^gw)`FHH46xw@U5l{^K5TF?v}1h_5p>KCAvo zU8#B^c543yoA%HBJF{e~cytj9hjaS%Za?fS<696+vVh*soh0y6Q(!x)CJgS}hxeZbfoG~vMI5~yvom$*qZYM` z90S}uJvFsAib1$)g1h zsvllkS_Ql55X`gSzWW9_Fi048KaHVoJiF46&+7Pc`RA(FV&ln*t;Mdm65c!g;mLMd5d060nkeJH|TKt;8S%vU zQ=Z5Dg1nXZ+Fjb)tu&vxFv28U;Tns7?zU3P62Z*gkO z8OS&l!I(nrUsk`OEbz);?l8QD=51_yf-tSUyoaFR9PBNESNrm26$p=TaS-hQ7f4dv zjQ$x%^Mtllo18aPhBqv;)@H=VQY&m`wqR2X{%$$3mh>i6KyE+xqwubo1bgbRgi*b{ zo=AC!r=^yTbXsBSzmAKEh(lIK-kZM?vNiKWrCh1ArWNi~GfUq|cty-m%bAETVTi3H z8VHeRH*!<;H5I*}J>{&l^X?TW5Dxn(9P53jYv|ME^VT z>E`V)AnAsQtp&P|&#ag8Frz^KfdE2>f64_;2b$zQb2*2fBUC{de_+A$f1&-5tJ9+xh!5GIEg~RWsVoPe>`@t=}MBqTFtxB_n31_5xT{ZD0*^{^XfOrAsCv_V<=!%-owft~<% z*X%qD=^um&au4(hK%Li6z6{N&aj*e^Z`1gOzSX1bmC3J3YHpX=nNvZS0ow3O~Tgxy2<=X`ub6 zxuPZEum#JGo&O|kzQdY#t6Kz^L$0v2XCPA{!0u`s8RdA^2hRgmE*r4u)g*nzA%^&w z@@$Qe0Gaj`H&<;%M2w)H7|f(7as$2OI5O$1`@3hSV(^1XpM^hA>I5bL!7Z8{D$C>4t*0YDlXrAlTyCMQrLtu)`AuJJ+f0_PbAj|5&ZSH5Mjb z(EGM}%#EUctWCp!*o$)~u<#768f`g5`5OnF>~(OakDL?6mbKdhlX2d{7GvFItV zLdQ*MBaP&CIZhuOxs9_<|)gr{)p=R+Pjq;&RUEW6Fc@h_|8vEl1_Th(ZN z`?TYsrG>Qj-|qiVIkST(cLppgeFR%KojWk*TEhsF-AqtIthqPm^JKEQ-I^ z1MnN?#U!v{>zw1OT0u==nra!P*RKq`yExh!8^i%g_mR#{PJztWCre;2e;!xe9c20U z0SxiV;m*Jy{?rHGQT3v=fxqwt4=K%KF9sS;lY2OMReO@MeH5nezFqi98d~(Kcld>a z30w_HO^hwCJ54s%V+r@!{4edDxXo_N;M)uRPAWGMm4sjyXZ?Xq#Y!)72}f=JHjdg$&>psN+b6L?+dVA(N)8eIlm%POyd$ao zYQsH~&dGzhTiTu#sKggkS~Z7nA@7))@-LC0PKOJh2oiIZB70sVciI86E%geM!{)lVZpPuUsgZO8t8937$LD5FEmD;<>Wj2H7cD* zjh1xW>KQ>o9NWSbs@dRfvz(+5mB*H=j8wa(Hhl z{ehAx6R)aqDMC5tM%Gly{xKOtFVn_)Z_7*QyBchkY1d%>oI(2Pys=mc@8X%8N6(b_ zdaTl`ZsG*`cXftZ5bGON(j_WwHa}CsV(@xuj>ws%Ri(O{wwZ=3yuw|!PLnT)-wnyT zgWvCW9ZqggBllaXg=>wYezP;ahcK)XLr$WxbP?Hk>ni?PaU(Mo1|^OEZwT+7n@N_-I#bL^<8y zaJ)80yefWqQm0-v`2*|mWOpdJ&Tfw=(m7qI7~iKSMh_3Q`yOI${`D4S$8fX>p<-Sz zs)<~V>g;#j^snfFj|6>MdQg7JL3?ua-cp1@DV!JC8ZX`y;j#Wqxc&rO2z|xFH*{-# zvRaW1+J~je@!=ICT;(Qe>1HC0&rbPa=z%IqowK59c6?6#3nS)a^k@Bx!7*f~(NBb^^f0jm%p;T#av7gP$qt8W|9j_z@)8 ztwxh8A*3*3CRd3}#qy>FjR(J5mfc)|k+W~Yf4 z$0H@3$53v^4w#f8>^yZsz{YL3qvfsLgV8`iya!wAqWW)sTf0aM2UKn=CBI1lHb?AK zdUw8}aO`S+gyIPG07vY$=oV33#CoZ8c-z4i)qL${+19?ZKtlgnu@G96i_@|Z`>Jz= z5l=q46xHoQS{QQJGkoagrBFWim%U>;g%;#6Da1r{dO>CZMCMj~o**4WE4*)UU%Y zq_?DWT+G_1c$IUfGpO&Y=b~3@!fMXmY)x63XZ$d7_Ip-s+{aTB zJUgeyzX(!c)u&f{mnUkLBJh7}XJzPG7AT%&gTl(LzcJz7nd~`+Kp!QO=FsnvlYe`v z_gxNR9+vBL_|ATivEyhO8M!)ehMe_^@v4_yZaWG?H(u)mh&A2V|MDbLImL1(w*wKP z`KFFWS$0ZmQ5nB3@QqY?*d^wWG;D>jp7|k@th45PRKE;q5O(L>$Pcn9taLB0Pt^AN z92EGHZKC{9%fls!|6jTMuww0vbbVrZUBZvtU7G)H_0j$5sNx5^3h_WWzuIE&_wgjeH0{3k; zh{{>IH7I;h<*!M#p1^Ia%rqtMRF1i5ysS8(=ZN?`{TO9noq;n-J82rjx0@DlFG1D6 zrsAg76vXdmA`ku5BbfHH?b-x%ZGci)s^@a^0?xjpXDc(&TI!T7YI*sZc^usAT&nHT zHJ*Xp;!H^Rqpn;b`j%_Ne%0#Xl!9F;0p>7vsf|WI#_(8pIA@pUR;>K9m5cV0TWy z72oMRr29|EtC@9+*o)YYtyxB7RmSRb(q@|Bi7UVB7gR0Yv^6a1*wJ_=nCmjwuGQ+v~NS{PmHUpaQE@t zZL8zkc?KcjLv8o`zDxB9J*WZ7ipD{{Zc;*^qLj+nJ2p=dIFcZywl8pbfRdFpX{c*f zZJjOMOpOJAqf(K+Y_FrZO%(u2@={M{c98LMN|;M*3?BX7UwIm12e@#QQJDw;%Z2pn z-lyXq(N(bfnGMy}&0})|RFeW%%;iOP&{u57)N5BDs>1*Ktb}{j5t~-SUg*P3%&Tij zRtk9<-|Cn_W3-SFQfS5b?E|Xa@RDu_x>{FdL@fTyn8D<0Tg7}XV@>(I zc&6wE!s(*^@HddZ)QoAml&F2pc^|EM=B)a)?ga9e2Aao{+2HblE) z6QvG7W9=w5oN8EbiZjJ&&Dm+xwBR)VtK_!L$}6EdS*8$;6@}v|!iMpD z59!G9O{N_T02f|+Hs}B!wI{*WX2Ivh5c88||BUNy7Tgv|K^V9`)r%R&NWlSC+qYV_ zT~wteo;Y(S_owc1Q1Y7x!92P_J~0Wrok@1=di*gb8$Rc@0m&hUxrZSOC6r-lPJZ<@ zR;)a?2wTUou7_;6Sq?i=zc*gZ=hpVi(yH&*fmOPS(E!?US!=KVrk|hdXJ!ph=b)6g zs}@p;Q=+f|dXk62M6#fcA*b#bTh|MG+vDCRDGsaLY#T#SxZiDzkUi<$kF>A_Gn(*JuV@#-+dv5 zVdNQ9T45a8F@#coS=EFEO7oV6N=4(vHg8eKhC3dy9;a zflq{h@0wjwu@o2WTl+9ta@2*6-1D!O>ETsp5wrj!G8{+m$y$d84f zs-71bvJqtYPT7+$0Q^6(+wLR8^?rH%MXO?B`*HEdiY1*WZqS$=$%IWG{B{S^#I(GU zRG!TQl7(}aG7-`2K(?EncUhAK+QrEW)?luJruOH6`5!#1veyLhY95=UpU^93fJwdE zsx0C$48QFR5i)t1;$7I0xr`B~)Pb=YKMBO1&tn4X+6Y!iyDKLW%zR@IiHCpSF*Yp8 zWqu5uQ-pimQB@@a9BkS`=L=onqgh5XEVVcESio5|b-jhBl5$>pmJC4w`ld$qZgGwy z=)YTPm@_E(00dm-d5Y_#-CDkLcQNV~=@b8>Gl=zMC=9MCGGHtSusP-k(!R)v=?yApfOlYg^Y)Dj I1D7ZN1FbhENB{r; literal 0 HcmV?d00001 diff --git a/bin/images/ign_atpr_acid.png b/bin/images/ign_atpr_acid.png new file mode 100644 index 0000000000000000000000000000000000000000..e74884e0e0359c8e5685e02f6b426dce94cbc840 GIT binary patch literal 2954 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 AxBvhE literal 0 HcmV?d00001 diff --git a/bin/images/ign_atpr_base.png b/bin/images/ign_atpr_base.png new file mode 100644 index 0000000000000000000000000000000000000000..431422b56b195738b6e075e3343c6833ff5d197d GIT binary patch literal 2721 zcmV;S3SRYzP)00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmYGQa=;GQa^SWLnz*0013yMObuGZ)S9NVRB^vM^H>4M`&+z zbaP{JX>fEPIW93YowHn6000UYNkl>$0ON5JsC*^NQvnlZWK|h-4Fj3J4<2 zT2)ld>?oq(XVcBaSZf6UUt=pt3dDoU>XoS0GI|9f@h3*8e?o`dM4ysB_9B| z2mg8(pc1dh-C*0T#g0D z-p6|LXH5hkcmNZj#=?^g|6OF(#8)?#<(&ZLL6hM5II)WjISKuCr>wk`wfHv9D_Upo z;ChHRzz!+|FZQDQvnKp#?fUP~$}1MVHGd-_VYK%pZ=sD2d9l`-^|x1W&TIWG8a?)| z7oPVZ;ta5l3c-uuRyj!(fYZLba0pB&v>Ev%mDxM)QF<+a*-$Z;Hqvc86o8U*GRu*a zU$|Il>L6(5{Q~5p(ZWkzk+`ZaFOp}o2(XJfBX$6J3@>=4j{1mLTs}nQgzs>XU!NfW z95B4#Mab!v_8y|Lgk?Cwi6lNdfP6FvUef&84O(<-lkj|Sxx)1nro;ghp+WF`vV>q` zh{_o&FUf}&@xl4CLN>3z{>H9p0EG-awd`&KZLPI0W0HcbrCr&DrK|wTncBh=i3cyv z3`9P{^TFi?9SBqaoMd#tJLA9exzd-H?Snhv?*jmjVFj}%^*ZQ1g{`Li_qW0+9^P>&&k7CYbUz@*cD64l)PD;wZd*y0OVPzCna`(nex;~`}#2rMhRapxJFZmhJrEO1% z5AGA)g?aEJ{0a5%Tpde~8AmrMYxYXt2wq!C9k=-HCL zgW$o`Yy=`k6}pkEujH~uK1Po0y26JCQxchfSvROIJ z5DO2cB4*JM9ltG4d4Gg{K0x-pf6QY&W=S2=g@p&|aS_+hqPrmC3g_6hw!;}5DV81< z9;oGtF?7&`WZzscp!v`*h~R-b5W2BB<3l~_+Wzwol(C{bFfL8RXJZThp zL<9>Dbm9AOh_+73-cs+r;L$kXDq6KQ|4aWg#?XzOlg$mYK7toSJOGBn{%wir+%s+* z*T{p3a~SFVqGk0Xtt~!Q3`+rZtb>P*d@nkAxFJkOh8LeFVN zzwERNyZ*NHzR$vE(``fa;M%*-FXz~;I=^FC*S3{XmoP*xJ12VZNz1)VtwTY)qjQ@{Cketjkur zaipf0C6$}UZvCa!aqL;`JmaDUdDj$cHy2zp_CZdngM_>f>-!F6-OIP+ojeR$IH#a} zk6a&`YzJrIIXiiUj&X_n|I6tgyIv%p-8|$Vn2=h>!A8ovlk@#!HMAXv_w0uZ&^U}S zR#Uwslc=y&zXn$)`8DTr&Fdy#cBy1VN`^IerjCQ_>^_H_V@cPi(ckIPq<$Qpx0$lY zYN6T8FSC7HSy@X-C-P;P<(6xMmR84WwL1)(XJ7t}yo?2G3J1QBA44%k8n=1#( zmOj5qLJoDAvQE@mc$LNO=cPR>G!2ESdqeIqUB6Fl306QE(2#va*!huFbLsPN&%rfd!L@(t_1^Tcj* z!Ny#-1VBqv2%a3zCA|$={C9!a?OuBz*8(U<%S?-_EQl7@aeJqp;!06T6+0|DxEsR@ z-o{H+{pGKCom$ptm%+Wh&raTAy(Ma&SuU3p3?PBw1y3SYS0ihu#dV4t{k{Bm!O#<| zHy92rKD;Y9ZJ8#1>M2J)KK=1%|P$zgIfBS0$(N-sZ?l30~Rd?8`F~NSI<>>*2LZ#q2Zm_Ws z0Ne;QK??wYBM>|Qm<9w70Hy)K1Au8j@Bm;M5Ig{w1_TcPrXhJ8Uah6hp+C&ag;~iN zc+6BLJ+O&3Oi7C1ZHXFUH!jp2Oo_49_AUbez$Fm00Dx&g@Bm;M5Ig{w1_TcPrUAhN bfNA(2qyF|yl7_`000000NkvXXu0mjf)(;xU literal 0 HcmV?d00001 diff --git a/bin/images/ign_chart_acid.png b/bin/images/ign_chart_acid.png new file mode 100644 index 0000000000000000000000000000000000000000..6494e71fda0e529d6603dfc94b527bd3c494a75c GIT binary patch literal 42304 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 literal 0 HcmV?d00001 diff --git a/bin/images/ign_chart_base.png b/bin/images/ign_chart_base.png new file mode 100644 index 0000000000000000000000000000000000000000..6c377db45891c988dd29977b9de0f25d7a432e71 GIT binary patch literal 42099 zcmd42bx_sc+b+Bj1?iMdNs$g|0Yy4QTDlt~Hw}U!jkGk<(%m5~ozfvC&89of+W32( zd1l`Cod3?u`TCt<&)(~^K5O0U&g;Hb&|3vb3{(Kpr$cegM8X z`tjf|_=999D=7}Sga1o!%8iCVs36kfVk)l5JM(UyPfnUdZqNFY6Dn3$cc!UDrCwt(EAUN z4T@kv_$N_(QF~XyE*g4ByVh)RL<9SCAo;&jW`!{s23C_AHeXds` zME<(z!l>XQA@d<)%gvU*^T7|EOKqS3Gxo8x5WyK9g97vWN=APE3uKY=kIa4U-T&rE z6@x(#t=!M{iFPme${*`xtx0}rQrmHW^6p%+sRBL#axB*vTIJpdyKD2tn$+eTU^JNn=I^X;gBFKjVm#XS6{DLVM`K#c8 ze+UZvw;LpyJ+C!$&V|FXV}c}YrBPSz0wn{JFz%+;@VP|`#q6=Lw9tIodyPwGn{8d% zH!BGeTKkFN&pu}8-+RnZGs$!%DA;N0RDr z|39Pg)$sknd1G>JKfAwD9i7lTR9s|L^9vVmSP%z3r#MGw?ODmcCto<)lxw~qW_Lq^ z|Kg5Dvv(7&YB5LAy>U~ma}k!5Ghv0KV0i8R1#aoSjQ-0nMfpCb)V2$I*I2=(a)ODg zf)PAk+;0Oby%vj6!JrG~@Rz*O=9%SOtX_#dKCLF~-X5?YEn9^*Fb17uC_3hjg<)s*O5HwY%XrJ4T2XTO(dZ;)AaUB2F$bwrrB5C_hH8nA!-e zGbSb`BO^nUs?_c1FZgd~XJ>b|X4bkIJEEOE^tj>8QjmD4c@(d_-Hc$pu90->2HbK) zxm`a^bM3PTgy;0KqaXc5#KInlqLbwdr^!f6OpJ<(qLB=5JYNpM61gsA{n4LcFkv~A z#ZrwyhtVCtTTqz)_H8^Zd^OlT%XzhUBcs`}Y_~=-GQF>{gq`kg&g>Q%HA|$(akV~^ z-bT(?LyMAre=z)@{1pSYv(N4o{6Q-G1JyY}S!rqNrxpB9PkF0&(P|pgW(*ze7;OYz zREimxV4U2$_Q1!TH-B3F#beUaN}yj$rrw{Eib&;4SH}$Wp3vbtr-@>W`0HQ!s8;uCxqg5Zl3rL?akTQxD(Oy#y1{fLNsx;HOq*bz7^!qM0# z#!0Q{&!4cVB|Nz;`_)(iy>A+R>giA?O-xSj++LxJ)#wH&%=>sm(qnwanR_+Hc~dsQ z@uhsZ6agar5PbTM1U_>6lET7d4kHNz1A{kj-gGDKeIK*xxG5MitfZ-9u`>IO{E#lN zhYLO$5tEc1G@#l#v ztbKdU;3{^_5BiFCGpE#|#Dj&CJ9yFKaW z8e-OKP7ZuwGc$gX*{VOzt5aeAt!jqLzQAUFCjc)&9u;rAbd3qTO|&pD6X)zh&=k&D z2v2Rtp(cjes2A>YoUMC>)-dQGZ|nN4pV%VQ)Jj*JGUh~Iq-jdqgm-U?X&sa8KMugp z^(QIgZZHfP+j103DXw$)k5ruBYrC6UbMf}vO@4(@<_OFbJ*d`(hs!{PZ%lY> zO;b5GKF41ayhG)h557@MTjvjcg2D|cn(6PLS!Ey7%E|O zDkw?eurp{k!(GH`o=)}X_+J>F7qyR2PHw~B`4GOiHYHg7BZKH(+o?vuQh3x%4xxgQ zyEU-HVQo#7T%t-UgC!|2tFP3jWy}ubX=$w#qf>g@#F1LN7~Ws`B+vfijb#5Ushme? za`#^{Z!Qp)cf;ggC|3K1k#;E@U&*`pAE-HwmZRrWlYQawX3bP{fp^Wg| z3KREOE4w`^1Mc_dEvtF%synN9=EgaxW+2d6E@Rd(iKFwXbVO^vL%_ zv#WXH;5Vggq~`=-IMBz^TW&`-b|xwqHafhsvzD>ONFmK2sS|Dd)5B=4{bPfede+JF z0d^<3J5_-I$98rH4gbKlOjCwttVYYf6S<5OrT3ShGaQwg{(e>QopEp2s7`Vh{I9B6 z1Z=5>;US&G_`S~;{dQoy%#$N7NAhgexnzeN2q3;ZANNaga-80BkP(Z=i~zwE^}rnhreoZ6g=UOc`S7x?`%{eEXoOMzxV2~=+MkH~N|rUlb$ zU%T#$xh%U(<{_RAC~uvH+8As2%YD0oXR?vubcmAH)z)m7wYa|n7ofPE%9{> z3Nu^9jJA9+&#lmZWNqAs-*nl~kLT3EUO9`KDq-$lHbDaYDeZT+-kE)GC`NJca=`04 z-7o_rZ$*7Dv+r|*K#s5MLL14{GrN((z1|%7&Vyt3LRQIfa8H+GG6Xj`(u8UqEk$dzg{`r%U*$3Aac7{3@X9@Z^y;eb|_wLW`*V zm%yLM(-|zOzzN%YULb#@yUa&?Z~U+Hsc4iwgc_%j>}ynNu-24aFCr;*8? z-Q@VnXl+#hK69)Ym^s~w>;V18MU|3p+b?Dg_aVca-8OaG z7TV?XUsnT4WQeCIQP4X?5FOBQzba8ytwl{9(a}sT@(!?TjAkU=b4N{3bVlWlipFHthP&j%#W$N2VDYg zA5RG-k)GPb>?G3*;Bo81_#%NvF$|;rr7*Wd(J)z;neM$!`+4acGM01ijcY%BbbZPq zSv~Zac3MT{^eb>G*0{zlGA0hKm_vhp0W8sUu`7Q6bm+J{>LWFay362WK0H<6@kbtl zsS~Iw-o&PkT#=;>?Ng9^H)nn%eV-qQCL0DW{rZ}tA^S$L+;CJ-;W(&a^Db90tr^yQ z#mU7LCShc0x#jl=t=f7z1IoCeH}RrP3&E4$wPH_2i{6Z)hkt2{gj=?C= z@XCGm&+H7+_o_1mP{Q6^ovp90w}3SDMbhryBs&FZX*XEYS&n?tX-&(GJj{F;w!_jE z_I#}Qz*21w2e|~5*&Z0Y@5k!hwE%TnJ{)t^6U@;^dh+|L>$^qk{;F1byC*5n&rg#SES=Ik)$8%&32)|kjgI%r_;?siDF0F3ij+ftx$`)_F2+Q{^1Kf`LB52D(&g@Y)bpC*UtjeZg)I? z*H@?KxjTg7#n zG#R(hddAi9($z6t^+&OJ!MTl&md<|lB$D2Bke04_9+RCBQE#%L(v#isoYQ$bUY_Ky z6jRCbnV+r}?Yy_cm4mbBBDgUVOqFTj16Uc-JtB~9?PfSK!}<~vs9qE>OkCX_v{&$4 zb!y!SF?Q@vI~E502tf3)i}c*8oh4&d6gQ^y?99%rX`0)w@lcGG@XyuQ@jp-WS}x{I zI=v~nAf?3YOEub6s-_^{qBXZ($aP3&S^vO-@g4iYNVC^PWHusXcHRJhAT}I@ZGiUHd4{3vYjSzGS=OM=FvBo zrWMhC_wjl~OXalv2%c}RNCE}U4f@Kp6lahQ78hfUD(%I`o!+^db{P04G+j})2gp@m zN9K&=iU_xy5>{od&l0xqWhEu&-1b~Q|OlZz=-0-)0$h3)nl`DO^EztVP%Uib4NX`M|jTj zy?3R3WlyV)XIGhC&En~M)9yEd#}RS7f74WGcUz12{ZR*~0~j)ZM{|piCB*cmlh0i> zlOP*ax$NpS;Ggu=ES$W9&UfFrv2y(0&&+QOSGvZo`0UEP#l1`s$4jEGPxMPFOAeLx ziHw3+NXQ!H!>jU-b|d!3?O%u1X)->2>Ps`JeqZEA^{brB(Ef`?r@7n$b+^}$*}%;& zqbDqW0gK}Pw}}ceu(^}ZIV0qDrn-5Pg1T1dNR`A`ZUVhzI2`D)H|m_-TnrPRkDq6< zV#$j8hcFWgu;NT3A3e*I56-I65%B6XUm<8dG6Bveh9d`ucPemWhn3^_)2yCF3{Vn5 zGQ#(QI0vQ^`a8?(#I3!9KK8?(DV+~(E5jNF#Kf-_t@bR=-|n~;aWGnbUUbsgm=;$# zT*(@@&zU%C^{1>nX4HxZcX11CZW@6g{uF(n81M{X@}`I;=aG>8=*$VOq_T;C)AS$) zl0U69y1#{nXlU0eMU@$4%`WMD?e{EQo>E+Yo>(!;{EQpt146?JhXK4F3Chfbx*}C| zDS>nCVGBm_bHmIO{v|nAEM3J;Qz6gFC-ziTG?o2zLK(8dc@k3MJ?#`o3}~PFyk}L6E<)=h$8ax$V9j#m3;78rg=3H_d~+)B(gZ!6KKKjnVFgQdU`O2I#zET zRaGVYFbSF=J^ON+4pKFfu3hIf@@l)3@zPAHJo{uhSEnaPGcycU1XymV8q!M!W7OEJah2fCA>%z{FJ zXq6s|m0~6E%$eV7%~0pI?QjdFs(Ckkn4=;F?_2YimMAolFWSTQdA2@+9-7rJDT<(W z+@c~&AXy@%6U=>>9K_`El+xT@Wv)AdQcqK}4nSVZ%gZ5pi`6!yIANr`3c1?Yn-3FM z@{dDjck_}*Yds3%`CdpNG`$uJ#Y>89VHL<`ngO=$tb0Mr5zBDM5WyXG`F!AGjmK${;xWw@T>Y49R>s*>zd;!UKKXG;aza0!D+hO zDhPJyu~S-f-)4DQzZR!;9P~)3jhRHIFFLMS!1J7gwB*oQ=w=uV(XR8xjHDo2`mDZd zZl)7|zg3x8-02T&AVp++yS>-DYOSz8X*wjNBqypx$$-cZLwLLSJFOhK(bJ6lLs^RQ zn3?goJ^Bo%sUsIW9OoNc5s`##AMgZ0YZ#rJ9whu6a8ij8yBoi?dCg+s}S<3jRUU7w^YFJYn^| z*~o1!P#GW%zfDn19_sJy_Rllve~~rMUM!cM|2{P z+yCDc|I5;>Q9vv0r=ez7o8I)Gd?!AUJcu3%K{S?{Gv|!>A zsKj?JTuh(2%cH*|f|ts>oazTdnX!`aIp%_FNUIN$P0y{J0f5WPM3#_bST3-2RnORZN`S`~5>Y4_TcL)INXlLma>T5vEUBn6YK& zAhb)sHw4JDgwAgAOb9OtmG(oo*aRX04rd%jRD;8VUI|a#U#VF-2Po8BWxWFd1 z`yOMvNN{SY6ssH?KVsAC!ashZN3&<}5$DVmuXonTq10}^@KUYeX*nFpcHg|qp%AR9 zyIE?d5vq%4qd{b6?~l$6<45!u) zd?}88BLzv7c8#oyMC*omxGGFH)HJkB~6*NoROn*Jwp7)XF`!?AK!Y&8NwRHg~ zhN&!*7inIoNYe{Kr@2XeZ%v;*&+Jh~JhhKX0>!Q|06`uz+(Ra-!bZQABd72)@q+$`*2&R7yMCZj>$D%|6yreNFg14l)P6>5sh>Z}vY}Ml3CjyN-3& zNWaKyI3j(XJ02~Doxn@z$zYUjW|0=N0gFztd zqI}EzhKI)MDkB!Z_m3U%!S0Fp8v6-s4nk+Q=d@L7>1s*vP}3*O`I%zzR+W&k}@VF2rZ;!~lf z?z!m;3i64(((-`M0RSzk2{m@ffAwQA8b~b!$otsLYRT@L{le37Ink=|q}U|^52oa>YfWgh!6FZigaIS+&>Y^MY`%;ch$mOgM`5Gmvj$$ znLpgw8ctzRDO0Yz)nJR_as6zCvbTo9(F@J)#Wg*9-S9?@c2FcUl6(?fy`wSdYsTF& ztWI(&TmZhzo#bEa2gV(iyGy@?!;OCog%XI~p&_0IMTJaiG_CxEh}k0tz6B{EdHFg; z870wlt%0f6S2J3qXaI&Sr-+(n{BiAh@$3(WMe>5ZK7gzc#ktk8Ij&MNvG~GQs|yoU z8Bi>-Gr{56|1gU2@-t>buYEhcf)^|(im%TNN(zsOgN_tD(&r4nt&r5{mEcV}aLl-T z8QRkVEPtd`+?rN=ZGqr2kSL8A)a#F+0yT0LbNtN;CMa-Nu;WD>AENzhziQl(wYOoQ z6iJqSY?#u^fhrA0&3xI7f>r|iBFV=PSxF_PKLP+AAtQ#WLRkbF zF>f@>mw79t5YnrJ4RAp!diD-fW4U*q5)9wv8%_vfierOwU^G2Bm@JlgKC`2%{@#ak zEVooF_8z_i=wy$z`o+5zv(l(^C_x|}=66EAW8R{|$Km(-2Ym5L3n@k8-~tFfd;$%Ot-yICjW$M%de3j>%M8X9P{yE+_$GA%2fOq zc{{_7V5Mads?U6CjE=o$i?6!|l($t8pX0%iH6%<$`hf8u1nBowd~O98A9fLt!SZM7 zUj(*L#i_L!(3UZjC^FP%2T1PlBHyxRhIEvb;m>F0g$wpn_276r2>-jgn>tZzX5G|l ziTdaSba=4+HI_9DkBleLM-tKx+`B(&z{Lb zJ(+#GiI()wlsr^0dI~Oy7Utam%>v~dTPM4e(MBsUxdq(yc(r1;xI7jF zs^DryfI+vm=Vw!mksT&kPNJfwR0jHGqC><|QfkZVj%fh0u5je**f$fepk z7PA!;k}qQz5V+>_7fymud7~H;aOcP!ul{AvZ*rF2?^FsF3FrlK6-CFgn5Op}n z+rdVF@2Q(8jM#oE)p5KUYOVanUT0WT>=hU~)e4~hI5z&=hxgDw=FfQe;CPMLp2b}< zx8dsSJd5sg_gLj8sBjZYSA?VP#*fIk-o(qVorEKPKmjt?z!O_Gwf;b8>ujC6HP4kJ zwkAb=&mR!(!fucLic=XEND;vVOd=f($N?LPKJvLvl6GZJm4*A*!iY1IEE6~gDtDq^ zd>YcsCxSZp(lkg7gJQ;jHRD^#5dsuU7_Vk#V!)PDGctsW%wlj!}0VqgUb=p$qE{~t&VkOTeUgimxO zw8=y>?_c)KnqnZ`w;!qY9vgF50>?K9cYFvN+2>4qDcM!tmji9?=_n^+ zx)@Pez{CTeksUBAeP3lp=W}Ceo*4EgBAD#FsTg`W3n*mQ7e|1>c0Bk18~RT_8z#&C zTbbK>xgf*pWGKud#ywm(vE+sdg)GPybB;qr8fDAN>Zx9*(~&fiF1JbL%!-NJn?FAy zdYtW%a~O(mpBK4jf-qdUlgw1l@*ehu-hTGW zTYdgt?0ile!&|=#*u1j=4$;>FNEC{D3r$x&3~4(`R>;1$`Y}_)Po(i`8eXQ2l=@_; z8}Qusp?vdNvHNi1@tk4<6$*N{fkCu%%mjJ_GChB8*8fdOLqkJJX{0R2V*|A8?exO1HFZV3au7**1TjQ>jV>9mujVp$N{wG1TH9@8F9O}uHZb_k7_0?& zYA-!q6-CH9g%|$VY*|B!gNB&w5*KQH#QnSSnV#LavqAzT&^eHB10LGYWsLnGX?Hb- zPxOt63wvgEc6}g8T}!J0aQ$>ET_8k6YLq?$3_Wn`i{(is8!zMx4uEAs_SuApb@JWf z7E_8fz;UpDST3mTEBD+ks-$fY zg#QmYgDS2R5@??44u5g}7uvX?e7iE~`&hpGNReL5$TT=rXnIf|Q6-I?mo?hfL1*M)q4|FI?ST#Jz`L2NTe9*n}BiOe&P(_ za7iD^q4T+7WRw4S4)=(i8nMJJvpnuCM(YplOd{j?{5#I!5xU8kDwUdqtuEw&Ee(^P zCmi%FS#o@U?sBe<~~smC@LF zLWRrGK@>=tHPw5~nrk^Ohb&hm!!P200`+>+y|#Y)Gp!yMI9l zcm>* z_1a?PzFW_;6Z)9_tt+R1%8_-8AY$eY#0#V~5PRC)#ku*H7%>yE${jzHPbiNHD%Qt@siA&dF+n|b{y zX)^;xpuWw2dVX2Hx2LiQUHt<@)CQOSGI=Hk;DBf{SPRl~t0?>!(;xtzRJap_=R`LC zkO!>MU7O^{R4+iz19%I>e*LROdj}2VT4+tP4DQ5n@kA}4GRuG{(ub{%lzZiGC=9y- zzHJ95h|j(yk8yc#j#j0$gay$^m>BvB^eJLTWf4N&umP6LvRJjveC!<2CaGs*Bl9s;5;V zZ9sL{fliaaFP}tRFWJYc24jbOKmbw`g_)=_m^MYZ3Ke8~#!aTBBY{2<9>Q_Z$I!wY zc?`ZIy4;U%XAqvE!N=-hl$4vOPAi&J7dPh=3yYzY0B-Cg4Vpwr9&Igo~nv%IvQyruP=-SR5XzSbbpw>YBvTbx%mG24D=iO#lJ>Giqu@n z;`xQjXFdr-aln+&#sO%-miaV?suP`g-a|+v=A8@zq^%#@n86krvA8zFTVIw@tHAMr z)4zucTT_6^qj@!z?1L6C2y8mIY%d?>vR`;;(_jg>5O7;36q=L4>k=^^W@4gIiC^Kw z2+)jPs}6lJT3>Te5I}|_{^`&3_7wNLhkjN@dFZRCh3`}f937>`L|L?Pm-7_Av*7y! z{2hp|cl@yJw<8Og5!3aq%@aw-_fNv#E*emAbnMg8KFfK>@k|wPJCGsnDB$GueQB#v zCyQj#nw5%yZVuS0X}qC(1AB{o<%%1HFZ*LdL75qN2H%&U2!{~84Jv|T3cNJ$&&q&BcWa+mYg|qVR&!@Jl4K zpG0Sdpkg)GlspQR8l1hqK@a>$cn#-Jc2!BruFZ!a1cJUnQN4qEw#G(Jq)pxqgaZkz zR@BU5of&Phl#FuWovs?&dnx1DV}*xGJyHateq#@c?W4v-H4@3Gkl^+#{%NV(MpHEi z4k|rYHKCCP<;E*hD!kV+20wMF>K^XoJj8B*AI8&1nH)4&{D+vc5vDbTDLQP#(uOm@ zgmA(bxC(wKBktLk`%9W22Rg$|DDv~jx4F#ILcE}cZXWAYl5uoT-_}*)+GJ7su$XIK zoAOmot8-gSY{Gp-P{897`|Wg)r%%YK2Kj|gu?)@aG{xL9+CY6qaZZ;=y@1cr| zudAQWKA2hd>-3K#q0h70z*kv6JIrYA=0`*kf8V^yidx%Gk_(QP2wr`IKofsYA`%89 zA`C$W?)2tv>Tq=CGSG66_-UdF&E-LsQ5vB!91x)zkg80XR z0UD|2PITYLVfF``k$W;KH4M(z%d1J%Cz}2M;Hgk6wp<{M0H29FK$v zG6ZhKff9fs7CffOQL!X$YV|di8&29WXve1c`s@wIlaM`?&O)%-Kz#UWM|!8t8{OW_ zOmol9@GnT@;_JV8%8105 zZk&7qdGm?Mf~g1l&r7An%$NjR*Dz2ohQm=Rh^_ z?%6-sbU>fP@jfz3z#n8~5`<`L{?98J%=wQXWi%^=CL>mHE1E38-Py#Y_op{Y3ViGj z%~8PO>yl;-CjycZ6*oG;OpgHc4B^}J6*NNDalywB(R1K?=8u+3w_nOASSw<4hb-8q zTS>^UTfS2Id26G0`miT#0uwSH+`a6*+!6F9gn*8L;qDKz$X63hIMwpz=~>=QGhul# zXy{^SxMGB$NMNVRJwSYKQ`H>HndF;;8^V+cFDf7PrNr%cDoT5lft(6;MTM*D)07>Ts z8EFeB2H9G!524LRDS&dV2qFLkF*ttu2dU0yEiyq=l4`lrd@@p6p?U%A{~)E~zGL|r z<%puN6AycJ)mRf`+tUTdA>kUs&fpL|x6^4eP^d|ZgVOM7;j9@tz%zM$5aB~sDg*(X z44~;ULsPlFvHqako5L40?Xz#pB2^;t1P5i6929|8r3-Y_#1Ev)maHWWG07CmYAS3HtX%<8?s0%y~Zjy9RiB*Lc z@k}}F9s9;|YW>;&h;zsg!-ts`22=WWU|XG?of+qAr2}}1LS_1`etv!rQrG)>d#}Ck z5_zmRIXPYS<~E%KuDt7YU3bd5y>E}w^lo56=YRac#TxCJFNQ=Cm-88=S)+jlrL6`X z?X%;=#nNUY9`c@QZex{QuW3l0C@4QhvpIS4I#e_Vzf*;|UODr7xg;#!_O6xZq)R=wtHaQCMfP{`GbwJ+vj-e>jO#acDC^Ic)26%`dh=DRahfN6dM z&fq#k?>YmPix$7NwPnyAFp$h&(by<-JoM^ca^KFg$vjgMG)b9cu?tc~~hGi#@?Pz8{0pIy_*M8jTv-lSJcAxS!;_1|G4G9@sL&X2rBiT5b>?gSJ=c-- z(o`CsL7=Mtlv_JFIT=AIa@$5p$nsuXoUrAU_@hUUy2HuQ`ZfUqE|N8_vFB<9pGN4s z@%3AIKJ*#VwQ>5+m9Ob<-X{fj~Tyg+<;1*^Ylu9<;^#_U%8NO>%p2Ne%%`Yf`?#}!^ zkSv*$6;}oHF+D&pT6%0$9SQ}Ep};F;MPX;4#kO~e-76??ndtv4WeS_! zCaYL<`=D}k86}XQZbDD7E==;PBZ~8xeiBvPCol~J+qsQvnL_NSd% zGXiC-#ROjKz(;^?h|@?)`pH%J3L>*Yvy;ce$%n6SI^kEYu(4bdQV#2^+FZKhp27w_ zOa8&&Tas{0;luO`nm^SAesI|@oNUH>!F3}u;^oZL)YQdmkJBAuE>k&hH9{{O3;2Rx zMEW6<3|{LO$r&sZ|864Sdo$Y$q0kt2zgsLNy|FOgg_|Jji}+>YupYL}!GqTBL8>Ku+o5Gwjgg8jR= zlgF|?$5rjkXcH>X;5WzevC|c1ir}`!I?z-U6qK=C#l=#C*Z)>G2x_q_K9jBWL6H9{ zpxeHiN+%cy`@&#U9`%;K{vCeBf+hsQI+?q?cXUx){x>jZB}p-|vDW44s-Yi4(WCaGU;EHrsEkcA>ce7_n0S0l&J zP1CDMGK$ZKdmP#&Zd%YA7o1AnD&Eu%+Ai{G6bdPvCcU7k{qlOheUS1p?d~G2{R@`j zH}>yr-C&-)X!ixrZY_U1&B$e28SZ=nz+DfEWOf;F(D4}M^=uyoMs|3zxirpWHPN5I z^<||Nz)n{=Nwy?Ht{rBrO;PDdYK+{P+{ZVO^rwQ;r)mD4Pust(wk>s z*5|+mg7cX7#8q^3Zf2}XRMpfb#>dSQA05WO_I-{D21PmI^w=<lC@X>pjPU>PyK)#Bbae?lj7DT+OFz7H05IY0jR%{+dwP>9u&oAN_pZ@992%wM_&O z=VRK{-s0@$rOC_atA&{q3!j@4F7;+b-7Ce`=AHg&iUkJb79*FXmlv^(2R?*5eTj*8 z&S^rUPp9Hzbqm&;9@qcgjX$WNn4+DEzxg9Mi^Tk{A9P&ZcUFYgx-E|t5Sor<)(*6E zHD*b^zP^ibd3IjlvzG*1&XQNfQ>H0Mo*mvKWl7P|BQ;9mVXu@cSYvL|OxmLB>R{08 zv=%QJ*9al@hY2b5Jx73TB{#FY{3JE=Wvq1o5#oWH*x7uu0q^CQwdEAj4CPg4>LErg zpqi`eig@?=RTVWOG?8yQjHYf)ew7TbX7;%15MKOsyK6^(+I_Hsm(vS*mhUoY^+b!J znR>u>0yI>oz+tGX!j#7;L*Ah^J#P=n#r#j*+RJ z6rt(O?r-`}5@}o?1$#LjjsE?cy_xcgw?>`_S9V>8lfOU&vjEL1X{kOUDrcH(+iW6@ zXyR!_u!Ia_nmKKSGET3!V*RKdX;nDITAsj2{fEr}MRi>x_CGIChMVjbqcfqOh(Kq> zn7b4!Htc#$~AE%Fp)B`;F?Z-6Ta}VX@U|pL9@;pIC944lk=nyIZUxhsd>Q zJWUB!Z20xS8?&9w$OhrU3`-b{zshikP*9OK)y6g+pXaw}Gmpqo8R;P#`U}cp8{(@1 zEcH1p0jAE|KE<<}qv1M%v<>zn6aF8HLrLEfU3)g(GoqZOSh-uC+&Xt#T4~+*eI!l# zn`gD_J|Eg-mgIeVu!qrY^+vNtLFQUwWZ#F4*xq2Sa&~|6W^s$>E)g1e9Ss${9Xhm& zo8xr(u$`RWYTa!$6+Ktf}+z|VwTKifw z9S(9mN2Xp}A3Ry%N-lo5^4tI9ILtQMFFNh^++3$mP*(QVMw{@?nedat!Mf1d=H*J2 zppWOu2DfDQ^8(D&8`#h(Ok{Bt?V&JAm-K6)Gljn97i>3ex!RBCR^!2U3DeK+z|GOm ziY@x{Wr>SL!nJG0UYUC3r~zf0gLaBTbxr!_@#`3CAr*vQ!VusDF08Tl)~Ue>VUk~X z7_Hwn915`iYxA{GKKDS*{Q2^Dp1q0$Tc?|58UGeX>Aj_+U^iZ>dZgci@pNq7cx)EWZCH(B`kD68WM1Kbwe}d z6UOl@`1BSzw;1=fUQ4Y+^Q>f6>2HgzWS@*}-H#AK=^9Cz4CsPj-FecJv&E#VFS%?@ z@d}!5fAo)j&>Sf%_s^#ux6xTz#tGZ5^e@NUT`z5nWHw(i%-Of9-`d2DQ45`h%qP{1 z%a#iWJwF(n4FE!m6dKaKo{VFA&hK1*10A9e^2)K9BU+ybCVj;(yZGAWw4xh&cbVW- zGso?EQ7+pem~2b>(!KNCevpOKcHKxEILPtVyBK0eoITzXIK7kuB4SqT*>A{ zwFAit$(FjN%d<7)Tr)baqJkM{YhO}?@ctly>KH25Jh4mP(o7uIS&FXX>h&c|Pw>S} z@Leh_nNLoM?V=O9)^nNfp7#6rH~P3PS>0^Anwu73lbXSk^J{c7?|?DFue@r%ZeajPM*ORB z1Bz#PYHE{E{-G-k9jhM7mt_g(%S$~h8I4P>yAf*EA|IMJ+vL`4+lPAP<)yj3YGIXZ z*xKygkB2zKxqn4|$c9oosSmjfjqfZLF4S!OB(~G~nWEpgulf8ejXL9Eg-bJ_PNr11 zab!`e`2<+Znb}{Rrm8RA2W-`K_1+6Q;0CN0Pm@92KQN49X-GOF3$pqqDSdiexwj_7zwW|XiT3X{8Cnae!KA{ zbI~*R@@$Aa`6b<^6|dgO%ZsftWbe{9s#R?dpb`OV`;l>2clPcBGe_GZ4K6zy&lef& zNOKtD7lx}R)Km5~iwNf#JiJ$Jv9z2soJU4ctsbO_3?_(7Y##NH^8UWLt@C$2S=LK! zIevN3TXsTfNfZ!F?-rvy`(m&dJ5;2=&o3POf~ql;}(-sW`_H?9j(P`ALIqgqc#RMRSXL zdvS09Httms5>jncKGf9kSI48|ZtbL9B&GSNv;WJA@tdWFob!uIk_nsIm}o6_r?*B$ zaX3o)`kO0RCpt>ahfo2tg=sTgzH~+z?NIeh$QNaiMV&@wqm%lOf;gwmiOv#p!89-L z%P*A$x89gK4d*6iAq{6bK1X(vV_5S{Y6LBZeil&nHMQ+#=`3da*ZvvHAx>C`)2{PD*E zSMBpF1oP+~MeXE`F_WatUFd10YS*$E=4%DkIQ}SU{F)+Y(kVvh?=p~9Ps@Em|MboG z78{|{)`{pbUP`@7@!WSUA%V@_Lf z4BS>I(gjpw9ahp`Kc9aZn78I(_jMx{D%w{{6X~V06{7)ZdC8 z7SxvQ2Icr5b19V%c{Q?{t@$6)3CRb)LHD&i@k-8=XXB#l-lgb`Z>aQofUzM5-~zJ_(bJl|M7#tLUk z(L3e4xX47D>FVoK*D)WRvgl2t43Ac|tZOrU{aV|_X8U(@Q7)B`&=WS1KE8a5 z+vvBok%1FF&>vcaEsq=p+-tW3H^vpAxbI$MQ`Wofh*z)q)$^c~=nVNRZ#J1$TzB@L zGjs`GD-A@yuhPe)~5>++U0;1NK6X;-%?2uj9~59*#;cEk&!gmcw@s zajHUfqa9zai`)e}JF?g%Gh2tD%^gB=%Ih;c*LIGVv*!qPU*?UJ{+3m)&lMtj_wr`* z1R5@MFuuUmjfAf|!;lfCRV>8yqmzNsrF7@=id_9msGL^A?eYX=xx3SiQT@tAXfn%o z161L(Xk6El$yh1OpS&UW-0p_jYBRXs>`rC1rah-moZDEGUDN*6tBd@q{QkcKNkNQ! ze6T2u4Mt>eu z1O!A<6i`6AyFqECC8Uw=4nax8%#y0M-7~guk~j&!u{zsY&{qWpvU}u9r>jrZbn8_( zN)s15(RNOz(Y4nW$+FuJ)5dzZ%5r(NDL_pS zPjbITc#y$2Fif|*Tx5D|+X+-`+9sZWC``h*AJapgUesK{qw5#d409Aa-j2n9x%p^| zMCsDc=Jr>G4uq!h@2)+N*uqU>JdV~WGHA8apgI|6QhTP!@GPZgywlxVvpOctyLUfK z>qG5EE~3t>=#+SbC%+qI3pYIWd)(1`ei6&y&%_a{uS#q?4yos(jTKDvXu}45yLUG< z#76J2p7w}?8(rap>_7Wj)p^BTBT0=NVH;dUKYgYzdAo@A)p}|7go~K_tk=7L{h8?Q zZt^-Zr2Da0F zQ5-SRSBdS-kbEuaO5M8X3h&vBR907q!PD9GRm?5tV`UZnuJs;EN_qwr|0xeu)T!chclXF)mFcOJmFTKV z=$WY1-=B=C!L7ZuGmQJu!#kLoarW1GRlJ>diAH>7iyrrYqh=S8N%UOa-WJQayOPq# zK)Z|&5YI}n;aGU%-Uh`Y(-T`mLFNWkPNA4(81`=5LL3S^HUoiy3c7WVhW~IEG+Cce zqMam!qO*=2&QB+VuJ;(f$DI4D{CU1YCkM?Ydal>W?bmy(db0TbZ}~57bVe;5r!ws= z)>qC{fTXT?Ig6g6V#~-@`*i3HOTxvqU$9ct_Ti>Mn-U)Dn47A;A}Y1im!~NoHgFj zV#ym$?KC}DDyw}MLqS0Bjz>YNvZ_W{K;c#Owcgd0fZ23HjJ>r61qrl{L@U*7c`G-K z80ps3R#G@a`Z7Wz=U)|mjI+uA{(SqUgz>@4tn4S^r1-thU-E|7zJ6`b=xS4A#_K~V zu+>2oIiHXa+>?~ue=cC}IMJi^%0xTNupO zuJhPos2ZP~^!fbal6*Xm7qCRUHY0O0Gn>8A6!}_ZmPfm*1H)wf&wHf5Qq&{}^PcD! zK3WgjxYJzk!AzSR*wQokX!B;sfV+vi&vKjaFI#8pwJojiKvL_fkETI%s})vf3W9Ze z$D!I);hk>>tQa0+^m2R__k1j)8OgS+M~=t6@o|R9slzeBZR<4lx8^(u^f6Cifj{(f3*o8**eQ@ zy4#GT{S~by<%3bL)yXsdI=9JC?FtHE!X3iUNLvBQ>s`35vW(YFP3G~GBU{wSXir{C zIPclMbtulAeyCQGA7#9l3bC%M^q&$IJ)fR6$(%2U8-19bz&VjaLtbnrx;(M z;8f_+cRoF%3fuun6Qp`ft;tl{^y!kI|H(CXlx46k2ywGH!rH80mj*<~{d z_;t&oJ1ktgpO+}I4i`>)J{@h@kgu(~wL|(`UfX5=zN@W2C*9+D{PqFu5*_8hKxf>S zYP6oAjM1TiNw0Aa{v18R!b$e2P$a;!Ib3}3P=Cl?G|v{lbV1Ci<$Aownd|JSl~uUJ zeoYznHeEy4lJhB`6{%7yj$e(#V6d455{lO5GRhP)sCl>OJ)b|Gd5Kfk7<3wIV6#Z^ z$fTfC)5$aM`$u^PXZ#uTE%ihhI+JTtBYXv+QC*8J`t^eRmP9_I>^aK|#QOXM6wCR{ ztG_nZeL`?7)Mu!9wb&KND>qI9XvfB#C%YA9GV*)0|4z2Hq(^QdtM|{eIB9FE$20dO z?tkKUhrSTAEJO=J|}p_Zeu4QB5zS*(lN*u+G1g`q)sW z*NHOMzHaNqKmLKYZhbstOl^Obvrr+*e`oHCq5|_{$J6nO)IjsTXh!BQI$yL!Hb*}+ zZQW{D$!`+AeS0b^*_&ZG&ux8zJN|4xO)0N=CF9kdrb(N@@3BExd-A$2*+2cII)a~1 z*B3~djcF~~*1L}#3rBqP6cH+zBmBxxq2pb$ZY`8aS33Q^K==M>>>00DUl7)CRaBk1 zjr@IFX<y%Ss7^*=Nh<0<1S(snTC3ijwZ z^;^J6`HbVkenxA=N73(HwF#$!LM&f94homUUdNU!Pjz^;pRHuXC9Y4W#K&DfRf+lY zUQn^2_VCZ6ki9dgO{3g&W53?pyfS9^=o8Ci7Zs&w%w$aGrUS<{<%QLReVRe_HHS2&>6i))MwnsAlX)s_EZ8df6UbDI_JiFuSfdAsw++D zCi&HWhzfGN7)2E(IK$&hr6b<;n6+GruAI=EkM6MBwOc1sBN+LVYZVhFsnC2T=rCoUi0-9+cI3Ei>VS0?*b&@Ge~zxkWoEsQzd=q zX-8`!GTfU@?G!_^i@e-@;1H`AdRWmaCMEFN>9w)0LO`G=Nw_Qi%wf50z9U9hh@sN- z^3K=TSX2C2aqs>4rD?2+VB2t;6-fyzMOAzKhdD|5(Mj%tdu@Ji?qzgkW;=GwbH$rQ z7q2bcD7)X=B%}UrMd&Si?n+wbM_H2TUs4i+KF{W6^a~!_?fa@~-`Q*1xbyN5u#bA#yklHfM`UsF=4+`B=F^NF4|d7^GhcJf zE1tjBqQk zNOq`4lcLgf z00#AC%5#!j#ix-Y5%oUKRm;Pn5b>O9d=7CIXbp!Y?6LjE#Jw|?Jz4{Dqy>eA>s*!x zx!>McYRVE9D8;a6E!Wxp8Jl@Il-kgZWAMG~2R3)f2XjddsYl4yZNV8!wS52v< z_50gX+kecCIBm(jDiyk`;`yn5t^9CQkhl~sf|GV^?XNygavvh2P$WN`wu_!VcyBtRL$KA5%AzYQkbCN-A$}?{I!b~Sy=L!L(y6; z0OtE?VZYg~uZO7Kbo#?qyrTYw6HjLO=}GZS_OATJ?(&50!}T@M!R~fKECn6CG*%cRO<5= z-Iy5K)iK>U)#Ia$kQmTAt+)1K4bYgQW@_c&HKk~7iMRI~4Kayuk@qhplvxtAs5;u8 zQ6&8=7iOgU-dkM$dS{xMF*@P!VA@_)sU13tmVHyVebKm^H7Rsx&z$J=%SkG1)Wn@m zr8oNF_+so^;{oEY zok%~2?nLdCJct-3L#wv+nqKqMokE{Wm(>rQ#FawsQ8Brv8jZEy3{fb!;^=nlEE8ZB zLr%hL*Hd#dyaLDm5rKfmf$PHR*xA+a_-*@4iOS^Vf|8*cpYnYF@Q2wkTa!nU4W*mc z@DBKHCh2;l{MI`xwKR4|rAILnKYCJPw{0nGGcbLsZ%Z*yAEeBMdA%xw3UnFMUp_YPW516LWRE(nwYX)m?AVc&?tX37i5yP**2Epa zGG=+WmOFTAdpUpMcg85Vp8!H0(l|5e`1Q8`aR{yRu6_D!rFLAl%ag1#vM@uv zu{W#q$yD}~i8RNf)4IDwId0ECt_b>2eH-5p{W43#JmPlNOV34UTJjy!)6;qjw?Wi} zU2res-5b`d}n@=@Hkeb$N zYo_<@q^DtqM*byJ*)Na1`cW;vNlTg{|M@>bwbeMWl` zliw+~Saz3vq9#RbIL4cb?)!#la*Mo&?c@oi+5H?r=9N^@6ZbO>CvU4CKNnZ3jsm=E zP1vqXdcJ?WU(ckFRE>YVN`PhXVb**=_*nP1&W#;*ruU;oO^WwFPCn!oj}{7L9xc-n z@pj(A73Fu?P+5D}BZ}ZN{2zzp<%$CgV?o(IXQz;+kjQD)U7%Z2pi@QJQ5+B;ZEQRT zc}fCMfUwU2JPXpaGVbVwz~ETNL7pTfMh#b(8|>8H4OeQkZS z75zm0l~0<+GJr=xpPKsRl^yte4EYYvN6F!VPMk|jB|n78uob`Z`eeDiqh^$N_cqb0 zs<6M^+CNF|%E2$(ED2c_p)mh;Om%;>@tmq2@V^u%osT!{+}4v1W5sXmF>3c6G}B?q z%VX#t8Z8uc48xB3Vddv~kG#jtlE3zw8CG;7#o3>}=da^M^{Q7FQz&{1+N{Q@D9KOo zXLgeEzj*12jJhoB&#yS~t{?H;d{%z-X@OD5^`qm&vepW-WqW;%<#-$W$0gBc z`p@_s$7Us4+I(7sPL5CUvx@D;Ik;_RWF+{jKtNB7f-ZConM0MKbyX~8yw~Y)bQn%k zXHO5)W5gB9&&KDW99o*pb8MJWs{5Z5X zx$|d_vq0Bpqp2DF-t#bt{*O6*F(oq^`E~Z&61%&JL#3n>=|Qvo#7$Yp{9_zwqlTL$ z%w%L-gs*&H`+$G4Hs<9ul(fuzU-!DY=#nn2Zq71#4d7&OB+y2EeQM4WPVrU9YA^D!Wc}R` zD`SU%4A0gl3>aQ25ldc<3o*~~jRxiw!?u#Vb`ha_bQg` zBZlf~jj;=jN%^p;RBsb|J*F4)f2`#9hAZJ9jl0lccbrx%P-ZE7f@noF2b~DiR)oC zB&GN>Y+NtZVik?Wr(w=AI<=qPVt4d#T)hd(u z98$q9(L_R>p5^|ZY56#=-sPPb7G3Bm4uCcOaBtlQ`g{jo5o#l9931Q7J$t%Qt{I`f zbX|lwBWlDUMbBvMRLFr2HX^YPi#g6cESfa{0p z)Z4PMou5Nnv8UeBQ}_2A^*Y%ZLdUr!^WZ_Q!Vz?w zN7+A@cI^#23b-a(`9yuy|6+msAm2%H3dOxn*;O_as#(n{?3tF9)`JJZ>UoKL4uQp-~f+`V84wqTQtPZTj&i-dTjI+F&E|K@XZ^bd_PO_BFu?UtWs*srx zP$9o2s-O0sT!3t{4A9PYBwg0o! z#%^9s{NcZwj8xu-RE#KFMn+HtX&Y|{s%#E~sc5gP^_TsFto3ayH_B@J=_UZ6d#j_6 zPRUKVxwoe%BSRwSYOSQCBv1X(O{sG9=B><#@3eP^)sjC=$=q=u*(!-Qqh#RCI94lm z=Y5;*`Yz5)BdZ|lJFP+JD=m1OU~TtuKn|iku0Lo$T4>*~O(Ba~k)+#X*3_X2vEWg! zA8pIgrvHuqN$QI>PO+!Y$+G@k*~33yaINf!Q79$e#|D*e_+DQsRT2!eB-talieFVw zVXZ|KzjwXO5;~REJjz!yY2RBeY+%u;{Iok#NK-O=yfdVa%%S}qy(OflXE(%3`+heo zHvPT4t5Zg7IHu1glIGwUU_m+1{SV4DtGu9c5=uIu33I*6vO05_zC2P@DeU1aCG`r> zfs9Px2(%hrqFPvM#Shr{{+Ke;HTuvWql+7-kp72eTd5p9-O0u@3dLRU!@ow#XrMwS zLPDabs3=N7^(dNEU&OMXE>vo($LAFJQ_^d$3Vm_KPMUOT-CN%ZQAd+J*yRvrn(UD| z=pa}ug;zE1s#V*&ITkOyC(z0-hvavFD<3`6N6NrTup6~hR#)4OSC>IQa!Y7Kn5`c> z@wQ^UV|14?UvHHO8x6mSE6ncOf^%DG!W>f>R2wkoIZu0oPciaMnYYt)D_u5{T2`yh zgFDw1D!*l(1{2rL;U{CQ?A(VmDN}m0Bc#5pQu6|gxE*f3{_HA=$`dkj#6%;9tHshFCS6rFL&QhwjX?Ma6a&iR;$jQgT z)2(r}0TT_pd$H#ZTtG2i?V?>~IRHJ{A;(^G@A(DP5{?mwnD(kEd1!x_p<--g3I)?| z51ARaVhhf<*{-)y=GoTy_M%>g+k2Bf`pzBWHEzsjPDi^o=c%2Td9IX}?MkQjg};N_V7t@r2>)LHDp`) zzWO?dx##%!!S2?t9^%oY>lT~i|XnQ(d_dV4#mJp z(@cH2hWdobsNOtM?z%cc#_wnhh3nu5e>dlnC8Cqkcdx{a}gvw$J)UQ#Y0x5{L|EGa-W&#IQr$*@vAkx;#Z4)*PXldjU9 zTB#S|T9!iM18A4%Fe^3N5^7{3C!$wLoN!X3P#8bF?Dfx}*t8xolIDMUwC)4B^)amauCA^MGBQu+qcj9u zmW;iy=*CvDaURM3j*L)U!%qvj)|XtdC`FoYc_YPCrr15pS}Xrs#geJdz5@%!`FIm- zDfSh-d~$a`C_YfhSG=X;=l7vjjrRJAdC>*I+ieS8#TNdQli91n)2&s1&)p_6G+~t3 zmvpylhAQr=xIaky!~Wq`m>MxI2#sk@jIg*w?>5klhT8cD6YAAoy}}YIhOPKDVQe7m zpRxyETy(2=^f+wZbj8cIX$az?vaf=ciG8_a?!H`5E3UsV_raAQo3ZmA+g1GSoZ?Eg z>qBoh#X!+gNC_b49Q(qoP%&2cVvkNOyw%d+a)IJ}HG%9HWk|eIJ=#xNf(b|hA zwJ8+TK{rkMXediYf>0@8Dad2tkChmaqX$<_@Jhc%r{wqWsgbqfN~v?@MgPZFE1(H# zB)y;1%loI}G4ZLa-u{kaTcZbM9XEodGgQ#{bjar`FFuWfQqpJBph#l#3%GmT$kOcj zE58lYXFko|#J8I<3_osw`4qFc2alzQEG%_I25h}~{BA|bMD9u-Uwwv5!-LF>2cLVu z3j4+(ZxmAtv%P<80H=6miYvjVOLA8R_-a28B)pS*NU&H7hCPJ_nWa)t_wCZvuF)Nu zx|bu+z?n@xT?+qB$TMRH5Gt~b`mZ8O=Ay8E`)m4UHhDUCHrXX>lG$qGf`<`Zi^Uc> zl=_cAN~*+&Fa^?kH-78g52!b`R}sYgiSZxmox1OV%35|JIw}_j5oV%6f-no2rsW zk|+ON*RvqwGPDa~%}P)=*w2QT6l}B85AakrYF6aM#*G3Bh7iyV^maF2kMqwDeIg?7 zH~02jmZ+%4%9#K91^kIJ%5a{(JAVv&ibYSzn+;cyvIUNICBoO}S6bS1+@#v|Gi9;> z3ZpW_5ScS~c7@Yo&)zBe8atwi$_>GILfXnpxTrzzlS6s((e)PU=|yB|84L^Rg}krf zKD^D6op>pQhW(D}?EWhidle35_4KQt8;$G81XPpKYOaX}N;^xn;qbh+pyZBQJU>J= z&W~RdGSY)yK}@p4&EI2BgZ1e-% z&9*}9wDk+)rF+OzLooTM@|Or6>wEViEyChGrAaOsdZR0{5YN7jojjdkBflOJL6#ED zCSNLrpO5Uw5JGzKRX$WX3V9##!qS&n^sP@9XI_9lSX1;#SaS85I8|F~6@1f#?QQ-O z7Rz^>jSmBan4NSk#KwaE3Xy1tZj%fk43{b_LnF?)5b1_5sAf!8lj{tr{Tl{T7sM6& zf_%AkTzBK>4&KNvWnJ2cGIZp*Sg_n))>iCZW2`0KE{qIBjSR%Y9vUXOcYS1!CM^U$ zYf4$J)upboyad^eF6lx_g4e{p_#)%s7}ZJ&g;R8noq#z(P+WqFQw> zo?Izdy%TcNcxx}6ARC4gxevo(lW*bT!RAg;hi&U?`D9F-?e%=MEtpqp1zMGUg^NlJ zu>QW6Umx(`N4zSTk^!6{7Wza;j7?$Tkg?bz-+JQZCRxfgPzm!lv2LHfZ#cYLL6rxC z{oTJhv==F#XS{U*_E-P;KY2=*+5&@J?)_e8+_|XsjC88P=#{3Hu z&86g}2XG9wNN@>WM_vjTiv6wwYO!P#Y$%;9mMGwo3m0__4-bQL9TXPU-PtMRICtfj zXIsQw@c^8%V!Z!@EA}sbn)vdBK{j9HiNczY?X~dVayr>Zb!fiT?_NYkSQJs3^dE+? zt-K_3F2Gu<4=yiks)K-UJdceBocAL8Q)u1%cwPtdF^Zxm&HyktIXFIAU-Yl3`wV0H zi#XU5UWdLXi875BMN!W(LF`AoYpu&G(R*SqSud$wBc|Z8Z zQ4x*;m#_7<3agV1KHlD^b8V3(n&sA-RgNZ5$pZ*?0Q)j3{JXh(fa)sX@BPo&BNtMf z5ydQ3-~>f6lmY4$Q%I=xPja3{pfWYB?esb zo!t1&M=l!Zei^U}ij=KI_~I9df`?JfF2$8EWz+@n#IQ@snohPC`zC$Pyn2~I8<>yJ zKwD?tno(sW6Wku7ap02=i@qa)UN*l&%jl0dCo()&PW)1#BAYnxbxxMVeW@h70ucvSB`&kb2ISw zNSRf0`DCTT%fPYmakXz34a{N{@$a}(V}+?gq4rNa8b8Zhr@1m_)P0T_DUKSG7nTSG z;}{~Nq6R7*j3kxS^R)&F^+h{cN=r+@PooQVJUw>T*Vnfi`K)>a;hj_n0e(N-^7~wC za-Ci9Fa;O2bVHJbkM38Y(p<-U-IXsQk1v3S%Hjjp1Hj@V8N`;Bmd?)3C8k|0dbLnb ze6Ra}Sl{!B$p8*2s#(X&ebb)&`opUwhA1&$(+#U0z1Fdp;9=HC#{z#h`<7t!jBOG4 z9LU~}M!5{b+YV<1ds~-^Flp{5P)cZN+vt;J<%6dH61ggnq&8<;dJr6 z0;h)Rg+0tUaKN@)C7>Y$obDeQoi5?BnI;cDE5a~>{&3(i z%*-%TU!8t}@e~CfDgU{WF%aL1-il$|yjwQwJB}cB6HY#i)uYYde@|;vN|J$2-*Z71 zB}|0}nf}4f_g00@A&{8GEgk>Z(^?ly3uok5%npyGQAu-G{7Xi9s;%-F5X0f$NEJKr zy!Br+4*jcAlFr!@GGli6>l$wp@?FW#G0@bV(CCUq7U$p6I0eXP=3>a%?h;gNQ9^Bd zd;a-|d!s2bN6OGJY2;+TlU4!<07XiNHiSD>-D>O*LR}z!fPNg=+Uv-wHR9n?RTjQ{*IVYP*1ACVA8Onq?j{EGvqYrSkbzLh`kX*0E>T~*iucLr4=mANSb{xLG6PMY-gx=m zEM-I%g=$P@$cP*ZIjJ>ZnhKtRh22c*GE__W|j5H z)>?Pt`+^9{@Sm$8LkTSqe#L*axv+|CRg@A<3)&?Jia|>>@R%FGi(-Ja0?&8d;KS_f zU+)`*Qh#!vF?3ci*!jPH^*k~|&e%LwMe=TO!yxuDXMTuzkd+#>+4(QxbyRNTFG~Zccv@o zLR>g%f{)wU+7?gtCRvyB;BT*s9>%b!<;wH&@`hfHF*?0={dyBgjnl8N`DB1drmvyj zOJ01gDzv14QlgEGjo})%!V%5J0(SZImW?xvmMoGzbwR9mO_`W`z%WZmN?f`f|~Sy)(da&iE1F{*!*k(1K__^Z2caddi$!I?bIYg510OaI63eg!n_&QZID zI5 z+ormdlqu-!50@!z?C$PvGo3ttSIi^|oo*`ZCgH!~ib+CMo(G%Yxj^Gbvj}Qo=+WpO zV~qNk>UJ$c+B|tbwqn&kf-F}mB3(I6O1H{Ua`m}`hSe*{rYL6;>i`1pG-FXwBPAi3lj_13^_ z!{ra_{W*R8IghY-MfS#=>YnY@1G0xEmdgyRT=p+sN$>QB9nr48d7$-j#3JQF$m`d& zfPVw=s4y`x0W2=vZe)knaUFPft(4?Mh1Dp*vO=MM<5d*v#65~dQ5zQ`%05NH`^K;#S#^M&CYUK_Gg2ipgvs( zv06h&5PbgpxmNyQZ!i0uJJW|d%NYxR^7r9=FdMj=3qA_U+3+<_XKz4+AHsivMk_1B zB@%OMYqsv|Fc6E2{$N8Fi*7Yr3lQ6oToOhm2*dnZUCr+`9~&D3))<7A%7KF=mj6WH z-jK%fw%up8=-7}EiqpO>l!OgXepbcXpHY`#(m=1e|ZD!kwT4QaqpVbf)kU z-GQyqIh0`XWvB#F-Kwyz96I6L;54PCrNt%X%1$8mf!Y8-xur}U?Ci*NVBhEgXojX0 zaP#UYEO~Lg)%Ne+<^{)<$%ZrddT`$cETB>gGG7H9=VWMUvwoldK%50;_^jpi4PM)^ zSV1@2Ub|xmRe;m?nIt4UJcil=T=fuQRuP}mYD-$%s z1*lO17fy!sedOijoE;n%$Evm<4&t^p#t#l3ObaPwP)8QB222Eaa1alEg-cN|U>QV5 zN0U z)gFety{CPy5NtmO);|eok)JFODY<|%9X)-U0oyJ33l~@a=D^(C9QbECwNKgE*xm%f zt!-bLnqEuIg7bSFA3sP&`CY|?$1?Qcxd}}@rpXSVH!nXw|GZqSCiDh+b4UCiG(>xs zW@Ze%I_4RNnefQ?>{EcNcyasjsXJ_!Xl8990s^6@e;SK7V0%wbPfPT}?u3rK5PQGY z+ZIU+p$+J78-9!bIvJUbNfT@i$m95P_L)(anc8JC3hihz#oflOe@;{V#`}Qpuy$=D&Z?)drcPGGS^Mh(KeH;n;X3n*= za~SbFF(Cm|-Ucb$f~^KQnY5{?J{ga1On4C1cSSJSc{cfB_^Z|270PwZCXT(y`+7*= zEPnY>P^o2B5f!#+ZYKocg1c@*Tt0K2Ln`B)R-zRbsWQP0^{H0@{}aXd=?{IdY~iL3 z*eTv{b9+5>)rw&KB$sVIQF~z5aH5W|H$!D_u|==_Cl|nK=;N?IwX9Lg>naFWg9^;f z&RXTp7soNTTk}j-yL4Fd#2hM%Ef400sHH*|9SLWMDlX%%4VMH2wkKPu(lIdP{=`l> zIoydC_3_TZr|H18tHYc$m5t_|DAaFQS|Ltx{U2vfjw<)PWOT|c?9Bf=TFE%@A~LcM z?gvCJqJG--<$m=1@v&!}p56PbE3RSgJTu@G%Aivo-$8dVgDnbiSjfIpP*H(2g~(v9 z0hJG7H#jrrc(tmov`qq0g6#XH{zVAj7k-ZJu#66bSnbAQRx)%ZuKD>uKSS>lYrD!6 zk)Cl8%?A_NE-+N;B$cYLaV1opm8Td2D)fpfG$ZCiV?nfhKecPXHY%)@d(aiu;GI!&e_}FRrbvLA3N&?YkQhYF1SV zrVXcu%Rt;%aK%B>W*Tbhr|$0X_ZU47dQQ%grjd$%T+t52$IkMx44*nx1{R$M5CMqJ zmZfvNm*Xyz_SI_kYkBd7i$y=;Uo!OP$IFi@HT0dGw~-sN*C!{7j9ODi0Zd6q%rkYM zc*6e^s$|o}?8m=x;{aCF4S1qQnQ}#Aky_t+dcp&nzI+K$a4Y`-pUfQ6*qJ0;GdDka zUU&lCprTXhApQ<`gnn#)t!7>Q!)_VPhk2)d21T(7xh8r~Vr~++Imy#AkdBIq>du`H z-L9BQ@~_b`A;_{9Ypj(^a17Cto5s?-SrBWE39LWa9W~60)g1f^bYg=*k(gR%o9=HO zWzIEma#G+ERN=^|gcb6Fd(5M;F#VbOT8BPrKJw>*t7?W{zkZGMB@%yimHuD=K2V_q za=jpi&rTXDiKVX@4b9VT7?H})vyf;;elNb-0cU8iZD&_kS9^Q0DKYFL8p?-S%TS_Y zoL$E_6Wtur0AHhJNs+S4263D}uUe+LX;dBlx%sX+408|ICqI9Gpoy2FH1z0o>dmH` z{5ztVv2bw6*$w@wH&O}~$sh{AsI0m*^rjgb$wGHx z4dl9;X48kI{S zCKp=4X3+(!3=i~ulr^tnb^%)i{3)a7=hrtl$%USZ-KF^vnEJR>&%8XK*CDt?JFR@Q;1_@^1}V}pg*1|<0Qs3%tp;)jVCWj>#p^^wM5LsLa61uFh9NPxysW)X8Un+G zk#e^mo~O4^8$x+})Y=>uc?w%P@Xv zZEc0Jkf;6G%FxQN^V_$;;NU~J4m96KpAZul*9V&VO?V%zB3HIAbiU8muITRS8iz(@ zkRsKF9);@HNU#!wh}_?7wBcN}Pbs%}R4VnWopXN2aYCnc|1%^@acts)ctDzTxI(gs z7jVyTIPYw4--5IUa42;5zvF*$5J0mVk`7#xy-W*B{EnWQ8o|-@JeN$sa)LRh@Y?Q$ z6+-$_E-j4!ZUgw_g&anO?_ur1xQ0=PdT%YrcU)(T{-yCzyQ1JH%kl9s6Jhz^M1Qae zkXLg&)NsmeHz5Q_07;03F7uG72Tsc|nA~`k%S}%RyK|LYjd$C0O(jvJLe~<3T->Ed;cX;MfC~oXt7a zn^iM7N;Kyj41@)CRS{;v*9#(K6klEHOdRs43re1E)`bDFaI*zy3`LSAp?~@nz!OH{ zvZMW*2=u18EB)O8H3!{g04Ov_>J8WHOMW>B1A}_-1yUIxJVq0*-o7v{G2jsqmk3m6 z`73T4#;?0(rR?+rh(hGynO& ztUGW5C>lSDTazm3`qX_K8B=Cuv;C}eV;?>k+IkCFkl+U0Z2`{=2ICIGVCKTHqeC3@ z>yiS6t_g?#_zQB0NkQNkW!D#aF&`mNP$}g|hC<&1p7khsMgqw9bm)g{2T}rQjXT~~ z>;{4^CRD>yyS`2~978m1NI*;VZDCHLw0yRA%Jc7nSJ-nPzszFiTtvPCEtNcBR)XIQ z>6A&pEGl2X26|BZ*Hi~yUoghzCXo*Ku@yX)`zW1~A=-=?~# z%S-Q5W<4PZoLhD|^2XaT973P z4EE2$LTlu!4I~`}-Uk-`yUy&NYRVIac~<50KFije3m|X>xFqg8wdES|UxAm0V|;*2 zhbSbD`GHbbNl>8-KE68DZI0{^6G}HBnHKg3#KSX#`pq*tW^f}cxaqNX-sZtzyNmzH z6a=OZr4-)iB3#KLN#yn*Gb(*dSx_Nja8v2P-;tbBsqbc_YJ@M-^Y;9_D+t@kR(TbWU~$S{P_zrKNur7s$YK2kM*kc zl9!L{*zI_pp|edgf5HA+El-Ux!(~laCjP3+Cmcj9l?9MW#Tp`d?fn3bDhMbt)~h1m%7}*A4M~A?OI3mSFBzVGxId#rMDTYbBC%|Q`yMHXu<$=; zF`Hp(xkmT$0RS+i_(O67{8(Pak2AlONxyj)$$rbir?;d%Bm+&x{5L=D2@+9(T@5KH z2s(1R2T1}8&KczI)?qCjW6f?uyk6WW5XnD5MkB_50lb@}n+}xl%9sscL!Q9L=GvF; zO-d6VnGtRk^@&l=Jk7y6?L3``a4|uP9X3RBY&@$XDMGoOFBZ^+TyII`(^dYQjn?d? zWOu&|pY7p)HqG!4WeP@iwsJ^t_HDY^jC+!S7wrcmZdl)M^{`0Iq&|%rWN4cs>-zTe z29>(0z`$j~t7SY|sc3qn7GJf4skjr(b90orf?wD^emo(%>qYiJ>7Lu(bi#@EsUzHx zvAo-SGEqaWoHUiIMty!24J$y> zqV`1z+)6*tEn#6;&M@6&({Uw$e>^4*7WBJzpDtLy`2}fA!Nt9(bKN6@>)4giTk|m9 zKPdcVLJO~Kq*5Lej{9Nu>B+J6-5J_qWUEeL(5;a_aJS8V(qx^ner*^L?ss|ux9~K- zU0&+CR0*7zb3?By=CF7Bff9vFtt8nk){DGipx)m}f@`Fej$iceJG<-?Jel&j1zSHd z+d&{}A3blDqyM+;{V1mpRv3}*QnPNu@r$b%@0S#59Nz&=EQYQ+d?8^4Pz+Is$^M_P z26u}xEx7d>7%B1l_d89Upe6}89_R!-cI(G{=Y-|`X!>71{dl4oWoWQ?FMo`TFbKZv zLn60=f=6&@_xAR}H9V_H(0nyCJiM@=00#>T4)QTv(PJg(W2&z&YW_V1xRgs5FFJ2# z(FOk~^{#*PPX(qVo1$?i$xH5IAdE%`w8(Uil;_p0LuY=Ar z*#NWLDnnE0pi~j>KJXhPS1;fBy0bh4-9?@PB>@@9CrcU)j#h?QoN)unec1m;aP}g? z1bcu@GqbV~3Jp?Ift&@09B2zN+XEn|%DhO3h{~ZwHFPJ@P*bbAn`3n@&^W$_7{8g% z&618JZZmL&bd;2a;En+cX=85w`-c_KDt^~QL_`XL7A%7W`1Hs`wK10VaS34>zh99D z*L*hk3B+Ju-ENaV*8RZPSduInNA$atBX77gFd!)ivHfc)0W+se;GL%BS^_>u;Yz;09&=){t`z$!KWLv~J#a{rj!A$3W{ldM$F*lrA6|0CVu2U_*p-CcNoCd!l zMyfmRD|@B)`}gmFCUNW<`v(y>Iz2rdd_!;m#vJ7Lx}8R`V+k5=bKiM%(k8Ly4L1IJ zzC9WZZxy({FcUGcMsS|0M@GiK7G^!Mt`;y1{&ypaRZKZFp8#zKjov|*_#mJTET>89 z0bWTsX}lqcu=vx1cH|_6yQ@lq;Z~`5D5cJMlvQp!emE1t+8(wwS?h`Iz&Q|`(=_*2 ziq&L^3A6P+XQ$AY`;NrJhYw9mGLn<^@85qhn*+?un>W|)NH{rdLB_L|ib@2Ci=5mW zFud!tKf7zHMy{v)Yf>d(DV|uz{n>SEw}myIQc0LS-7e6p8`+KH3=Iv%r2yitRTOEy z8xe8+4kWG6de;UlESL_pS}Q86aeG7vu!wKqx}GT_=gX_#bAdI2{ELA#sW`XtX}Z<# zC%N6Hft9VqlO7ZHOT`J(z%O|>^~)HsAikzbW8)8QT(csH`QYc)kT(p^r94ZktO>e4 zq7VAANB_(p>x-f%QUz4TG2($JZ; z>%sbO1lCNzf_xw~HbY)Qt~8d|#rpltn+z#MXygutD~N8bdv@L|@R=+bw=PN7U&-6S zKN@h|kgCsa$-qiR4WDB*MS?vuBd)x(%$qIgn91aSpX{MqQ|vHMkv>)>i_hFg?>NU! zCuxXbRO=-s3 zyBU^`zs7Lmq!~+$wNd>4@}b-6V?Y4f6g>v!d7>>eEj5)!F==H+38vOq@HwUr9VkBlr*#}CKk7IU&KnR#G8>&jC0p*mOu6W+?X9`5JQzXkvIAJS``2L?Bbciytb{FI|!Z?iy-V z-@Utd`Lc+^5yUpSAj61@%lTk)o+rKw8w1fOGvjw;jTl!ie*q^?psHxVvg|Pv)Kd`> z5(1^m0x4Z^M1_zCKZ{|~t=*PW1%S*z3>7!$?1$iYE%S}6pY3y5^MLKJ{`skU{X=#2VHfWt zMB0{sUrtCwG{c^`i(qeLWCY073bLHS!u8`0U^q;Q9?Q}60qyJL9Q~7!)Db zjFH zYd`|rNQ9M=d}S@&)z5C+xDkBCY9piKw6rv#$R}imzc4l;A5Y}v!=qQED~X-hU1uA0 zCIV`G6X)_!AWyiTx_ArmCF2tlzRC->Joyh>_IO=)H=6UOqhgW!-w$AB^3%)dQ7l$b z(=vSOAvSgvxPsS%%`F`rqVbfrV{yxg6xvtuQSfFW1U>)0;eUJzRwmc~q`7V#254h& z+O-ia_~NMkZa1(Pj)lHQ==9sbm;wR{t6Zkwk?9f5+F5M}Kz-e~p|ot7i~e;0dGT8N zY4-N&#_{Qepfc_3S_##kL{kx@cAw9E>-40){(fd=%uUNdWFrL5gJw%MPf1DX#?`CL zS5Lm;rJNl2XCBiR-5I*6wMfOR?_Ns7M=r;|?o0FcFwuG|ADpAdt zl_cb`YcMhL%$Wy{C7%$S@&s@8TUlA{+4C!zXrx@RVg;J7sFHj4;%inLCt4H0)2dY! zxfaYqG0%(>re`bmo8NqU!Dp=C5GRz@Rr33tJ5}#AK43P&7NxGzKsI^R`t{>j(K7bX zUYmv2i4$x=pMOvAB!+QFQUDyO8ziVZ&+%3@=e zQ@H4?gh;htfd>U2IrVVaS=x!ApX+}*gD#Q1dx-|K2TqTS82a~8tXr>1`MAlPim`5B z9?U+tRb$j~JihF+rQ`-&n$8pe79vv_U4=F~*6n7y)yY%Bt6Y9lwiuLEGWvHfs8x^CKjn^2nwgHEUz*(wXCU~B{Tb05MlaQm|Y5uvmxR}eepm0G;p#A-Q zhItG&s`;tZ7}?Xq-n5EQa#CzSX;P;Hmr#*ix=RCv)ghnQ$x;+q6_Yn}e^ptlrL{}4 zPas%r9GL`+@F<|dl#~?kJ{%qc+t3a?4+6*ey2EpN8XDV~^3h}k1rH8CMAR3-6u{SX zcX!iP4VeQgv#_uLqP~6mc4z0_Tl%gtr>`4IJVv4 zF)P2Q{c2K21Vl?a+&5@b%Khih_fWWiAT=ph2JxSwGc)0v^TOV=g96)~yoG=uA?>Vd zNOd;=n1(%rmj{IVm!r+MLqkbsW@Sycw)u4Aq};h<$utVC1r8q_t-EerqDkO;5OtW+ zfgv-$-ndbUlfi2;`NGq~zg%&c=IuZj0y4Xm;o~1Zcp8k3jrr(b_tHOTYkTG5A(dBf zj-cb}7Z6Gu&*UZ2(yXW`c{pR=J}T}kXz&}T@%dwF&8AHsu`kH|iRLORt9ndjAUG`! z-%X-5rM$cx0%HKw06VqNI}IWi-LFJcx{f>}@XPi+Lk{MSwWiW*s~*)EeWn9`r%GDd zbg9&YbubDrpXAEZ33NVT?YrCB+VFF)Ar>%TbgVRH%{i6^z-NPDg7GC8`)5CVIDu>o z{a3>;OD5HmgYwQydh^6S5^3`7+iLpy`ntM}jg4bi7bePdMmL_0c$;&+wt{#R2e)pn zc_jULc9!5;0BhE`d8p>`h5NGF@bu_>_{gxwIVe@crRCwaZsq3sftt>18B+DkICL*1ThK1vTJ-JRCJwAZigH=Sw9~uG zFYtHJs5`e0P%<+!FaGEXV=dWP^msoMuphb6EL_7_d-<*^h`s`{3$g=YH9UGa8PXLR zZKwnCjM>>)Y>5Y(ry>jY+vDfcJ1prp+^P}SWFqdF zp@nt7#*g-Qiz5S~M)sfv5oS+~qs3gB(%jNAz-=x;^$7I08_Hj-aFK*;tg?i!Y|SQ# zIZ=&S0ba^xL82V;DjgkbYpvPSXo@O< zEqIm#7QN1!W4stF%iXm*5bh9yzuO(3n3zbT(ZILEkGr|Qvk=NEloz?Ux}GEZl)kI3 zykY=N{iD)SY=xKC{VvAzhvfOsHFUidm#WR?ryAuX@Qi}-3JUsTX4LosZ{pPkcki6@ z#TA0xaYtlMWm_jDyy{IAw>vsJ7G@eyiol3-Y@n|XH)*(L&1-Zc6Md)fzqm`J+Va88 zY-E3J_J_6M6nE`fDkoP4%jdgKj`cLw%Q^~?S1ek*G-2aW8j7J2Eu)igs~Waxt!-^6 zu61>EMo~gyyS}C};E_>i!`7a|9+eJjD?rDBfE}l443+KiLytkb>lxOLaA zB~zh7vfHH8sPebvim{1Jo7U_qY!|3uhB> z2Rz0o=B+T9pHPpxMKq`5ZqVKhJ}<%VJeAD|QS$T8Cv+_P^KK*H;^qQFLqlzCAv$*R z%!l@)$VIsK_04JWSSl@z(M!{qe}yjz!(|F4!|Z9G6=%&Dk`U51_+?2~iCm-5>d zDu>3Z_=SY||F(iaM)%(qJH1cgwZ5^;J#b%G4bJi#%WH@cmA`d%92FU~A^xv5M7GS55t z`Foj*Nuf&l`T4k@a;KG*X^_YlIv-b8*RuJ!83@+$A3mhh>CjNA%W)p2J|H z-k~kC6D^QN5mGN$bZTmU@8d8~MJO)@1_sQ!3o|k@a&mleWd;NfXP#~G=_>p!F>x9? zg7~<&y>@mZd`=@n9~IoA;^Iy>tm2Il3Rx3JU>`V1OBaU;j-a5hw!Tbu!8(KmH_&sB zS!7pYVz3vJzB95@Gcy=2R?p}2->uiy_Cj>0Mm9Io2Is#Z^{zn4&^z?wMTMr(y!ALX z-%?xK+Y9)5T3RDTgGt8Fh7kjNM8UWW0}r1qe6mybky^N6$Xw~y%NB)hOT{jCO3TUN zV=rECzC%BS02fJi;%Wz^DiHacJemxN)HhVz%uutRB39l)l-a^f1_siUpy1$;&#i)```Lye4$jos!&ggTImv&bRvP)1(ybTMI#8VLtwsHsK@Py`YX zP>j5aKwZj&%o*IPhgs0LVmKx)?qh zB3-Df^Ys5q8iW}?T38rn+hcC}H6rCvQ-h(u>JwijMslpWX-k}e(c_&ExuIN0-h86k zn*+^PDTHd8_Su95mbmea(+iY|I}6lzB_QQOl>dZ9J4zm5^XA4Q%uuN$VIFjLb_4PW zh4s!zlc1N_2llpW`mI>#AAdh*Q8VFEUxRqn0lL#GWmxlXppx+cy9H-r_@ARAqQj3T W54E;5>W|}-r0tg07THwiU;YN}F}JG# literal 0 HcmV?d00001 diff --git a/bin/images/ign_dissoc_acid.png b/bin/images/ign_dissoc_acid.png new file mode 100644 index 0000000000000000000000000000000000000000..d95de0d46354b41aed62d8d20ac3f4956e72c243 GIT binary patch literal 2708 zcmV;F3TyR=P)00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmYGQa=;GQa^SWLnz*0013yMObuGZ)S9NVRB^vM^H>4M`&+z zbaP{JX>fEPIW93YowHn6000ULNkl?Xsh+5=NU-?=6~(OfHi1V?%}@C@7z@ zRuxqs0Ib0OdK&-$&_#nA}{$9yG=yw z{la4cD8!YJ_9`ji&(ARtZ}R1xci2bdeVby0+ZDN>mux(`k9W>nDHS~j0McnDcuKiYeJ7-2BpnZ3@eFjZ^)azt-aO1($AHh>xFIW6VKGb@y@>h8^1j97ie zPHUYc=q^U!>^U1bGJt!GG7s;PP>m`8sUd74?!6(O2XKQff>%+fx_fwq#ct>MjwCw0Q&%XnfZQci$Ry}X=JaNEb7(Q<$pX*o*k^0@q+8KC$PyIKOQl-dG)=f2l= z;T(Xe=_PnBPs!i8_SBw6vJ)$R^!f;U?-=!k)o32lJ~4k6xF$R&fB_jHhc|Wpu4O0- z0Qcx2cu&jUF;xU$5_)@hQ|Ipj-W_={fPrZ$cn{3qovq^qfPv^eO1qV)X3{Zd)+(GO zYz8PmYr!k!5!IO7n?DV1HyN#+V?5lvW< zmZP-2=ij}}@xgOEdq(l}cHUiy`=+&~f-)>guX%XA=HIy$h(nB4dya4xpdd{IZ-o5a zY=qY5GFSm{k6t;v9_wfA=0C%W1}iuK>d;E?dgt#_%O`#9>?|;TD2vzu6re{AZ^-qt zUjL^86A;8AIloZVWI|bhGE^758S;0Y$mC2(@%Nj!C3=xkQ+Rg(h3JvP>t6rP5#d|n zV)l-8*8*S85qf*rUa|)Oj%g9ncF*7Wm_1@&W&*O!?b+29xnA1ORF*4VUO?<#h#f$A zstR7I$-6c4cUJu5U*7JW^M2lD*&>;_Jb%M($qT6gAOod!T6HFy=L#*$@{VyiT^beg zWD@$>?bua9B<}1f>z{jOlasG2wufzRQl1fjo|G26?AmsIe}PfT6pD$3cMVzIR!GDg z`#zqKKSt;*2p)`1(U4Yat#VtSkJp8MY6(V`@H%T$-7t5PzcAUa(DJHmJ4}wL-ZRR& z?n6r1dLsv(MDIN}`R0au zY*#vnt#v{}#b$n$K%!YpBqN{oAb^_!2ohb90oF zN#H#MYYEvxly%;lbnR zIJE@sk(`^;zZ+;#9tze2p_`jKIn>n9b|-j&y-@**P)m4XrS%cI$MX@Vc3#Q}ArBA1 zgW2%6xMZ|-L6lP4*=Nt71v~-wA-^@(|3J+bq+W}Zx(!%Wu~ zwAKBQqC6Bp0#5laf5*6?HotM0UZ_go{!mHfO3sYZ!sGBfK9PT4q>(+o5d}F$ZCk7L zkKn;$afHtu#T{MvvY9Cjm1ygw&n2aALRxq{dcQ&XcXRZf0~}X{s>fzijcm0)M5*uP z7k@Yf!GmQ82%m@<;OHoP-!E#7Jfpk3uD~_cvh_P}@`ksXCj6!MtfbLqX<|G*xw+SA z0XwqZJLmY)*H_C{V`s8$-gTv)*ZZNP(MD)Jh0lBMt<#skGo#wsp(wH@WZc`Mn=78g0bZL->xuv|P{5IdAi> zAzLI`+3MK)cI@@rEe~9oHvxbEp~3{Z@-&c0Y4ail5tLF|$M#Ru??x;Q=gy2@|LFPD_B`0jvdr2LR8;u8f#D|5yS94`2sa_F{E zH*W!e4ooe0rkbl3v%pwut=wh~W1mt05Rr)m&xn{6vS4Xw^S6)umGQ>Y16Z6X1<%>X zQshtUb`vUn?@wf^ZSW4U*M^7#{A*|lE(?p!bC-!CsOc>BDokdFko&NLfwI|UdmDSKR|9(zcJuzu~UF|$2z4*(#8 zX;xYUyt`^kW!w-n$2+@vUPk}BPgYpnI)wC5uwUo z`kb)230Cm&<^b+7g@-3|0+0Y!C)uRb~{t zYK)e@bGHU8)hEEpAb7uwmcN^RA5?&mm}Su778f@#*{-aRC=~{ujLLrGflKB9YBP(6 zch|#l=kOizGrujh-L&nf{xs^fZ>#{X_Bo z05X|oEv?00Q|rC_&3omF&j{;hbC!>_Tw4tA6s8DiUF;V-^YUp-?4lQ zpgz+Ho~5uyGs4u4w?vkYMPO4M`&+z zbaP{JX>fEPIW93YowHn6000QfNkl0t6B` z{@+@8=(-xQEq5IV0b{Kd02tYScQ*h4ju2D;hDT5V7#=|dV0Z)-fZ-8T0ES0U0T>=Z z1z>ntL1m128e?qYc=m1}`q=n9nf;3){VRO@dhp+vN9wa%E^h^pp(v=VwU1s6xl2$L zZhc8t5m~LZszN#bEUAt$vY#BIGR7>vJ1c(ywOVV9KSbmquaH_q#CLs9R~$Rt=)L1B zL@Lffw+_=P?fOm6ed)t+N>+)kf?A{JJdH7%&&ib2<@$Kc#_K(z=k1!XF_nH+8a`v_^%Gq`_a%IXl(n`v3|uhz z=PV7!bUrGfMq^=APf$gDP&KZE!T{)Tg{&%I7zl@Us)MR^cP*}V&IUxsfT5y-D$Rq+ zT~RB(B9(CzRWOOMpH^Sqo>f(C8+&vgfnoHz>UZlG#f92ONcH;i8e^6d=Vp8g$k05f z!pr(M3cFOzuF!2*q|N(fdoK7f7>5L6jb_YYPs7tHt+P=TO2(0Rr?<5NHe zg6de@l>BC`eFTHxen;coLGWNas>vRJi9q=Zs`xRf*Q+?sOiM2V+0+G<$UCgU z@L7y8{tLWXYuD%t-fWZ7=zP_WNnCNvJqJMrbyNnG$ltbI(bYR2TK-}FOWmZ47reGP ze$?SPW?4oV3Be!2;j7$MZLQhkGWR*E#;8)PwX3!E;SLiW{^vvgB4G~3n5W;_JNU8x z*|XJHWecs_>c@B8b9bK~JjP1qII`M`3b<0xRsR}32&($0F{by1xQ9Bo_1$|3dM;bC z&#%6s0(#r~;*0!M_3|2OSj>%AZwG2?Q3w)LzzN|+kBl##A7Z#AtOjYTpY~H!sS#8_ zrRYM<;W($iz~lb&(($Sb+ZsME{#o^yFk@8s2|4rWD!jkIwHtI?I9_#0TYJ49Dk`WJ zU0rHj#T1|K#)0&i+WLix3hE^8G!SWgV|k--n@hTXGqrU~QBbv7k{9>Wc;Da^0kz@G zB*`ngeHkM`rvHC4wKXX>sJ1?ELTX;qe|AAtcx6E0 z23bKBU8;lvBMp?6N-YsB^9IyR)lPO>6Msa!s?Un6ewaq${;*SXbI_hKW(n>eT*G^7 z<+U-2zdfgDjFhW>7~aMv8`D;V$~|RcH4?YH(!)JwDH)unqQa2eU*@k0%CCBHXWOuA zx4MmIyysR{R8$zsjZ+M8PWIT7TiDWf4_W2~^B|z+c5=bJ!!F=QP^Gpn2SoZ^CES<; zHT6|VWLf2N<=tIzMMY5IYVp=&T)q22M@tejn2x)JznsOFt)a$lf%H*PQEuaE{&z0g z;>^n2lQ>Jv^!}1<4ga{X%nMZWn{UYHVr_>T1>9)*fN3H810x+*F6F8ph7wg+BA@#* zj#^=j7^GWzx3$ap2`UUJ2&~jbvZDr8v6+TjGTU19xg4FFt{;XL?27W<8AXEq=Wc9Q zRlr1Tz9eifYODVPlKWcmP35}?Doh~}SR!uV{l>t0f5Cc;qlLE-HF%G`+WMaNcA`2w z4s2^J2gh`WxXMaIY;ZhpXh+d&mps1p`|H(K*Us#=Mei%GEqDLIad4bH?hLHxwU<_p zb&t34F=AWYAC27iwA8$x+keS8I9eU4z{>gZG<@C7fQ-JNS#4?^RGr(p6rMNx+}09Q z914#+0&A~3Q(8#{&s(DRQ1tvO+Zwd@8MHS(sw-aOUI0)hb;GmmJRM+T3En0*S{q}m zeNBpOqE}n@&MTEuO9(2c7X;SS^RHS5)l3K~U=qI^RLr9SCg8eL(^i9L5mdlD2r2+$ z@-xY29$!0)paRDC)7?H12GfC|`Ahx1r&WxOiV7H1(B=E-kP@My0x$t8DgeVHr~nL4 zbx^rQ$Na)$;W6rl0RR$I29;YG(Tck%(pqc6!##{W

3~Af+{DZoT6ab7~{%4=w1R1s0gYkq>9!YI+{#p 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 literal 0 HcmV?d00001 diff --git a/bin/images/oh_calc_atpr_base.png b/bin/images/oh_calc_atpr_base.png new file mode 100644 index 0000000000000000000000000000000000000000..0b61071cbde9bed0a3ca50e86e25829c450549e7 GIT binary patch literal 2771 zcmai0i8s{UAO2ciq)G3*Qno=O$t15mCQP!7ow1H3HN(V!n^H@z{2Bo>+DT_JfXJGFS0E(?AlkLhqhba~b!ReuKR`=a; zK^UwXfV41?KziKw$N9TrJ^gSJ>T(LoFRPu<0KjWxsH<%qoVEVav3w5pr(R;oD;LF{ zd#RsOGDL6r_2;!leM&1$H=D}X#8%4ix|RvBw4=rkg91-l`YL=E$AX&|vOXJH#;*a1OS>z3TuzJqrkxq-i%ps6dX9;Cb+G3<17HQ+*|?BaZ|gD z?NKKHSWD7jiJbmgB#JWR2kvr$FhgvM-M_GC?}5pOY`_FZHeb~HEgOQB5)Ja*=JsC*Jhr%YdFh(AeL@usg16o2L6)5_i>Vt^GC4udQ0&oBA1?pS@(dJIc=+-Lv(~~iJ z_MP7}U-=$a6k_gM=FePl$emTA?#oBDso@*7?FCSqFZ;2y-3_Y0QkRC&2!0bYFn(xu z);d|mGa#+hT?FT8^V2@+RE{`g$m^k7A^V8DnnqSiTuKY;B6DjTv&n7I_uMD^*N(l?b5MXfz?jk!}Q*PWwQS8e|aJmXS z4DXdUdDl0o-X=d8!JR*ZeQo!d>&wle`6*6$rJX!ZTf)a#GTegD=AlEtEq zSt1uGKEEFQCBc6%%x4UP5K}f&l_C$-C5Y}Yq@l=8`ZaR;BR0z%acXte@UXu0og{dw*8DN|LgVp<`4CBU(0btMUc9^v>?r}s?N~q zD#tUD2mT%B3Y~W>8(?*&j9K@_=Fx_rX`2z1x- zMCE~FFw?skY5dF+d}#8bI*G8j00zOl7~W-AAKgzm?+W8FshXWiaW_Xmgyg=6Law+7 zlFm2Jpm*6fVKW6xmG>3nq0!0-W}xJG>i+z&&}wjvv6lIxQaB1*5w85)XLT(znWVrp;E6G67ksOCCoTn*J_|u-#r+)~4Nh zgy=rh9rw^9x#wzH6VM_%c~FP@wZ4)|GM-F+9|rD2+HvD6RlnCY*@R~s(^IvLd3fQk z>z7d98!Ok3xKlKmzxzGW2zg%KI(gJHRvA_oSI~4WeL4MebKu2DUrGAgroGrfz3@~Q z+3KdhF!NM_GUfQe2kggU%$tg1!danA?3Fsb^QfKwI zQ=M84v01jSX~(yv^k)PPh-dhq*Nz5?2JO8WFV4C|H1-hv9Qv$#6`wYoEBHx}NdAT$ zJ8V`O-Qpp0R#k@?zlQxB$~`*vxM66lBQ-d>cx~>v<9q*DH=Enn>Fe%K6qA#(zm$tD zgajgBhDxPR`x|7vs;^#!T=S@uBJYdxV;(<`3LlVHHiu~GEEpsQuKqS7?s_4ybk&R% z(^mVf7_+NZ%uEQG;HctTMrIESMDTX!xN~Rq_7%!_i3&pS9UuG+eeNPxJlaNjB}l!D zmw~0{>a>~CMexy22gcju6O6BN&KA;A962wtI}F?$90Dcvw?ym3sTQb~>xP>$bPnVQ zr!W%UneB=_H`Ol0@};u~2r}yc`&a#!S8c#&hwnF>YYShz3J;1`yiGzehs7qn;+3qu z-d-1kc*dN^47B>!Z`(R~Eb*7Wf=pICUh^O}TN9EqRRyO@G>t$lk4E;Iy0~SLU)e{Hx88dM!Mox33}<8fR%P zumaW&KtEQIzCYb;1|81=fy|5hO`q3dIwDB3`;HBzmTMZT)uiFdIf_;B<(MVWn*a8< zme7eq%ccQW*4=TsJrVzuW{(N)gVR1hM+7;|p`Y7Q6Q9;shbf=``=gQ&O+5UoP)Yqv z6>JHW?Urt*Q9M4bm&=93*3dZQmIfb4LDN<+Q`@68&ecX`suM1z^gYdqPqE~CKANw0gT1et!2JKYmb>s{Nt$s}Eup5Z45?gy} zaYvJE>%ixci7XRO@2t(65ASbm%5^u}CW1ht`C-hbInjngKiuev_6Mj8vsm*dUi~T5 zbURV!KQmwWU=kN7|1}F0BywG(JdvD01rY??(Eq2TJYnP06Oa{?l={al4WmWlqp;Z@yco`fXivf zYm{~@>=rcEeX}9USeagCATc2Y>L~}Z-njTf!whiWkHv?-8WJotFB#?KmeYs-k?zc? zr}YlIbX&~rWB{zH+p4iZiz$#?~@gXf@Q!93l??r!1u1%W8AUp1fh0*?%y zI(Nf9W#@rtEVUPx-(;e1Sg%qR%F@tZJJHxIn z+b`_X1T+-7@LlIes1%cnw}@@SAwKQPfJYC3{=ojRPRv=y(^srE-z3hP`ZzUZh^_PF zaZohH<3hI{0a3Ojm_TzERW?tu$~Io9qzKn8nPeNXXPKeC zkzJN1gsDt(?b%{1`91#ly`I-|&hvcE`FuX_^FGV#yi@ILk#H_iE&u@FR+d+8001it z{0`!Pg5@x4EIU|1@1T%ZfRodww5uWuv~c1ror3^Cz5Dcq$a`XDL8ExE`ORSafV;tA zw*$QZq@9fv(mNn1ILIT=2Olh@tD>e^)aZH>0C-oeu9!H47p~2%WmdoX1F&n;s{1$g z!-!dAT+=V5f!o^|$pi>ym%cICrs?Y@m7-xpjY{pmBdR3D6+ewd*lH5Sa zk}go1Uru#zIuwm>`49XN?SjsFnl;SJ^0PcUFD(+Uup?R{=nnvy%1MHHqiS&bRn%+_8QC#{FMbmp3>@Y3Yn5MG;`NR?of!5DiTK^@lKCOezCP-ryc3sFT_Kox@Ar? znd9p-G$ni9KpzT-b-xD*@<^L|JL;L>QMrbP_p`WFJFPpSE4=!A(i4*p)I%TQj#JxB zubzm<0LCF62tKBbJYpeFcW~%uLF=r#%W&^NujEzPk6Mm zhD?lZ9%nv9^q#%kBpQUZ(v@!~{oQbIE%b;bQD}TCxcQ=gNOC3{46iY5iYk!E>}TyC z97vy1fDJ5XV0x%Ui-^%bd3Yy<9;(%Ftoxw`$7Nh-Im?pet$xmmPjRXf%7gb;{_YVO zIC)PH9DXp^ZmKJg`PIVuVrKL2hQRoauJFaGA-nP#ve*f=k_)oS zT*M{a_$Zeh(_Hha_)-_z(!bJ{i7* zuaGjw)GNawaDNvx0}6%h32P!av6Q#XtckOqgqBr7l(Ev!7d&I=c~WM-?X8eo40%+)A+KEHz>E~=ifb~8Qx7tP0bBGQ|fOd zxa3TcR=zzPT{>CkTGuT6FDHZ;vh=92@Ez)kw^&rau~O$a%}}5q(zd&g)Z# z2NbsFd~`kvhtQ>*&4#?o!eVdn!eI>{6A~7E#K$!cnSn3vcdcYmEHGSV@;j#WW~5Um zMV{Jv?Q^!}Bu#f)_c6Vz?y@@ZhawVE9uw1a?dEUVjoMb2Afw@oDg7Il1+5~LQEq)^ zk?_X~)$hpJRQtBWe7^M+*awzG^Deag-p320l{7%be--*vc8#dr_Cun%F(Xr|qX)f#jL>ZmuCz9#5*&5sR9STGb2XaVD|<6Q)dM^S}J{ z8fj1&?uTVbtn`rJ9!FQj*>;e&H#eP`oYKL1v1D%X?=N}-S|nx@7W6+I`QIs6(7QEQ z)<>ajU|a~~)}!BCUd_a2w_-PHjNgC$^MU?VIVHb0mbrYF|M@hH9$c;; zb-w?MIzEE=?IL138IN8WATC?UUV|>g{A#)6G6K?l`7_hY=XEpR(bL=k1x{bZI43V* z>I_b|u!1o7c5tk$4Ud^H2!uBrD>WdU;m^CaFf-EwC#pPEhpqLKAIYW-V>gGlKL)z_ zG@YXDm|D3oQkmr0mMen@kFt$xU2My_7(X}vArZgC`u&yKTy4&j(P2Sp=ci4MuW~wh z+nmmCBn#%e277|ta%EGi6fYd++tn7Y;{7nx{|z^KBXCeB|{FU+w4PJYtQH6{C6#-m{iVgJ0T1lG-j`L&>2A?{9zy6V6bTWv*6&ObsZB=iiq_09 zY`sE0leVlCF|~?Lb#lz1Ybdy4Bw|4N^Gcz$C1GWt0;8xk=bq{;itVMu9VC_H$rhW9 z)12?d`3z^0=mt^nNaftA+%@?+u^M`NU`1rZa%Z2gWdGSt6d`my()J!ZY;&Pu!o2ZN zcO*qbye)n}dj{FFc5UAMFi)2JmXedNVQ7hYTD)nvDyji`ZErr67ZA8x+cU+=3=sjm-t zGbT2-Af410ij@2@tj}Oh5*(x8BM#`k$6hM*-JAAw-^CMc?H|WBA@s+)<%g}|wC@wD z!>gTf!v=N8;tBJf9-+n$jckwAa&4aV*AcO$+Za{iLMAO> z-sc!txm0u<5%#t{a5ggYi_wW@gTGQrO3}nDJ1mG1ceO7|hQfTT=3kT&Hi9qC-RPWN zaQfAeF&o}^OJ{uit9Zouc^^zJMqRCT>8ZxqX4{BlpVnGAp^TWlpvVH6)KxZ4?i4QK zoW<=szSC)bT{do70zMESKl`^zVkl&9gJ{V&Vo zJ?veT_QMD?_I7^c4J?kaliAbr;i!8`VD(N~27ERrpqu!Hsf~d1`p0e9{eP#XaMQpG z<0^SGZs-nWZ9@w(q#mu#<3Ke8q?5RRiKQv0#(wzQ=1Bn1WNZf*FW)eLMe)j2l@gy@ z0l=3rVnU+by~vN4#d82K_H2+1cA*P;Z&@Wb006qbstAfDQM#bREoFn+08nb~WNkT* zv%H>utbcdJi7=iQ&#y;BrJi&0kf$BesZ$dq%yMmUoppfcbWvl&bg^yB35&kX?Y0RI#2U^OIQA%IeF zD$TuIhGIGHeUiWgPxc;kBoovUnsjSj*yxka58$EVf?Y%s1Zy!hO4IYo+wcP%e}*A1 z0K9Z7V@M@6bircWpOA+FxOnD4q5)%jkhz`lKk0o1$%BTXoIGSMc4+G1ukXsYixl_pR7BCO zpK>}MC>kvkq1j7$s&Jrt*i}V`@=FkeAzGjS zaF@do*wcqx%lCj+7NEx=vk22MDt&W`TMD$CX6Va1%tY(~pWl~-@LFJWhPt9}^wzKd zPh8qLW^SUAX|%$ozaXm{BG?T|@pEJQA5hqaKi2;~gFXXUtyyWmm$s=0GKz{^*oKPl z;Y%NW4w>6&1>((i`uh-OCWqP2AP=*i@Bn@jIAGOc6ar`}_3m$#e!>BhBb03eAUREPjz gaJo|XO*y`v+|f)A|M%RhXwV2)ncH3=nYt(ZAD2*IcK`qY literal 0 HcmV?d00001 diff --git a/bin/images/ph_calc.png b/bin/images/ph_calc.png new file mode 100644 index 0000000000000000000000000000000000000000..576d0f1a23d6eee5ac587e6c2661b6f8643e7a74 GIT binary patch literal 2159 zcmZ8ic|6mP8~-?xYq^=wS5s~xzQr6Fb4wALv(1$w8QU<|q;mZnO=Pi6UowS_P!Y?q zn50C`_=#W4-GMK1{?_-;@9TLz@8@~FpXc>l|2&zOoozwF$Atj^0NLBYT>*e6jq95V z3UJY%rew)A0{)J+aNzK_JYrVf=Q2WQJ5LM%oNfQDJnDg76I@aWYmLBOi44ZZqN0NU z+sjVMwjq%iEG8g26pd9j*3~zpH25F@;3&r)4s(kmuTqu=AFGR|u|}rKl8Eudl{~R{ z6_5j9{ATNEJuTQKqVSGl3bN)oKeu+=f&4z#036;l9sp<;0{yL&9Tk?aa<;=l)n?3+ zQkQkV6SsZ!)KK0}-N(}7=()yx>>QZkTC_x`gb#O*{Lz|hPx?yC$9r&iX?~sVh6nj7 zAl*LUkPPyATg@E5nIvgySdy%6&frA1d;pT*Fq8v* zTkBFck_9R?;6Et7IH6r91LCW+8*BU%0*IA4u#YF~>`k2dRFto;NGL6SMwPA6>;5qZ zLzQ%w@E}hW<7#j_Y_A0HIw80l+@SVNBWa61T};ys0z(RtTh%Os0K+m%Hsq@+oyzWa zgE+uJiPJmBB1yJlA+--2Tq(Q!NZz4EL}1dpfMxMUCzS7dJ+#}LIS)x(2ijdazqnYT zCu%l8=B5c4E+J+qkG{*-n07ycYzcAh#%h{jUZa)*kgxHfO3M!|2h)uFGn|-NIyqnb z=iND9hUUyW64A||17=t}(o(I}Z)L^QoK1c5T@fDvr9tQhyhIdIh4VVO2X>Hz|6$T9CQ<)4$tuSQZ<{zE1si)Zuv>SP)fc0v6T zd%wWKxD|%8RFlQoZglfPC*J$2^%HY*(>8=){=T-3rhz#nG8?bMOo=V9Ww`ukvcA~b z8@yKBlUikhpJ=+-v~D{r=Vi&(U8soF{N(P+LJyDI(Qesa2-aFYe8XGR^2J*f^usmo zJ-Oz4H%0MP45|LJd32jZO;x~!Q;=p(|KM`0&AlM--q8&84}vGvi_=ZH7p+aYsTtI1 z3-Wg6*CyT-kih9!;j}Xv9fKwJv14M^x|jAoNT;2S&7OG?lfH3#R+!j1sYXg?E0%Z) zpfDsD+bOX@5mGc5rdRz_s5md6E~@gcSz3OwpOH9?N?94iR9}5OkFY;eA!x#Kz-Qod zOJ$9BVAx^(b008oHbsz!ZN52+J}HLz_q16b%cSUtTTdcx{+)$gPgb)lmyX(+?A%(? zDJ{p{h@GztzWq~^R>XIp^eaS@967<6hystUFC4=DAi zE3LbPhRJ#$y0DGpC)8sT4X4tKWc01RNg>6V(nZc|S3GKG31dzt7D!~orqz{33pWJ3 zwqx6J(e}+YFCwS5rc2;5z5>vk^lsKy!So3=S2awf8mBKV%@HIfkcDJ*TPnYqC&Cv-P;r`mXI&KqsXno8K<2DJ6^2nNja;p34 z2j^Q|F`G#$5w4R0#Dco8atk*S)V?DD@PzP2CfK@jsHLX<1i4Y?g|t2(MO1 zo7}?Adnly6gcRQQTLuGhAlQb_ES^>tb@zvLTJaZ*Mm>ntzApzLWEL_3d;XM=8BcM2 z0I-P`52_$elVafh;9w2^Bke2Ox#3#AfA>KxSDQC(FFTeofNS47|x(`zzT|3=&V*lwpfRy7pNpy5zqn!u>u*J;OydF`6D__<|1rp?Q} z7F9^P{N~d*nOF&{-tbTl1{k0lF^x#~ySDu8++!#~Eb7VDx8M)iO{WBaN4apJ`C_KH ze}Zzg_v0gL_He&`Z8RUyXC1f>?yq)rnRcoJ|8QiW7V4j?0n_@fE<9y-W3LP!Wc>4j g+j-Cb`yc=Bf}BWUF2O{`nHvG@t)1ZvOJwT*0oA4VEC2ui literal 0 HcmV?d00001 diff --git a/bin/images/poh_calc.png b/bin/images/poh_calc.png new file mode 100644 index 0000000000000000000000000000000000000000..e9211719494e5c4786fdb35cfcce4313673e7fe6 GIT binary patch literal 1980 zcmV;t2SfOYP)4M`&+z zbaP{JX>fEPIW93YowHn6000LwNklZLXv$5J2H&wy2BLMe=^M+(sJY8$r)W z$fWNmqCizqKp~Y<0ssJ@HvipS0001ZCMXyH0B8aV1^@t>fPw)4fF__|005u~C>Q_$ zXaWib0IDIS+@zGsk+!?b26=euGira!zt)8VfLF2ye#1Pt>TZd-acTu)SVg{<-Gval zQi{e6-H}o*!fKmJsr9DfbmfWf`Br&j*Ja9RbUGKAlycF-aPh6^T`47X5<;xvQ`w6U zLJ!%aG;ZM8zTy8>`l*bJJP5Zi5k~vm#HS6K2_^!0Q^r&ynB(J_mF%>V4hv$p?%AZ2 z-g_d687iKx@jP#}?z_yx^IRAgyS`8?6N0>{fKqDsbbJtXq)iC1B;LQ7_g5^JnXgJM zmywT|hr0WFlj^27)vfLmcbB8^9gsJb>iG7`>{VGUD!rCsu=1(aAW35+6pZt_#qK|q zQkKxQ%a6P%uVbau*eXmU@}?cbDHzAz zfoAK<3zrhMs3?>RKu3@_Z5d9%h|dI)rg*3tpq5oJg+CMken8$dWGDsW_y9v%^t1Xy z0pJVrrv3X4Gs)j`sM>%j{Gl)}rjTiDW!uj^^~;{mZVB?HkGx45!8k$}xWW=FuInQF zk2CFdp3)x*b7A%)>+eMoiamW)_tVX|ZWCu0(()j;F7l=W(F#U4EcK1QvkR-i8LKgN zb&k`cLnk;Z)nQG{WGPMK4~0266o8I>wH|$GHPWg7rA!_hO2f=s_fuiyY4&%sFU};G zogi=e5v5>sg?K7qIj+$Say2!qwcg$LlA+i*!!WZy6vpT1Yr2iCK5O;pi$UYf%G+Fy z`_oU4JpVQbTK#>u{+$`*O{aH`or77#MS%`$>na%Bs)zZW4M)AKq8885)@Bi{{h={NSnF^=v~r1_a5jxMacu}j)x9IXM}fR4$|ISeg3%w!(co(p z_@^QoY1bCd$yzsmhd-22#Ml(J2|bEgJRX7*DQ%%N#L0jlZ#oh)2XpBj6C(Y?-Soo6 zmcZ=(P#BtK7A6ZyZUK2C;#_1ug|S||c40>zuGY$f{V$pHoJ;Cux!Z!Iusyge=e364 z{-Gdm8WL16H3~Dpj@ch;BJ;hbU61}Cx>@71x`#sLPJA!gXFAB6gWR3`MLT^oKEuuf9MmKaYQi!hnq|< zRle$vx2!gL3dox?$-yow%)fMd!Nxmwr}Kxx>-lSIt_T2TBn}a=4+qX5Z+>Y$g1LGA z#kSN+Dtl^wsI`f&C#^%E!!N#ewFx66jQDKM{JEF<$_9C3k)vQr=3jn>i;fj8RorQ= zQ|c;e?+T(pFvfI`*@@a&&^c=}V9EU}Ai_r@t4T{N@YA{krq8O^#So z@`i*p9Ked#Cq$XhE85Bb5|B5YNDkL;{0tV>zCIAR&m_CT9j>4jL;flKp)fR6_P&JL zdZ{pd0?3<=D5av4_&iDtMc6a%@e;H@}^N8r@F{u0#({tJxZgiZ>VtRvqD&hBUM`kQ_JET!5<1^ zWA%Yx2!6{obLsQ9{?B#T*yZ74PXl?=2YG0uxQow|W2-n;rI;T!N4Z$fIE57#fsSKz zGP9%A^nV7!KDeG2k&pdOY-)Q54xhYl9@1Ioj?ntP_T@a=B~Y#$!4uu5bh*^$5VPSy z-lUUM$C*RQ%^D(ZM$*5thl@wPr{|e@I-XZ;2q>)KX-o=ytJs9g>>(+k{=Id6(Fd~I z{6&7Qyo2rh>KS*}LE?8o-ek0+m}{d)FjrQUsXbN22`-SkL z)xwGcKp&2Nx*Nu$c@~T>WC{n5K@mxzVgTS-ctXu*D-`QgOq17dP@EOd()79;Edc;R zI1b*@;AWc?D+c8sF$$*nk;wq?DxwY*gsI3M(K3Po0Du;-(Gmavn(#jhdv>|{TmG#8 O0000l2 literal 0 HcmV?d00001 diff --git a/bin/scripts/expand_collapse.js b/bin/scripts/expand_collapse.js new file mode 100644 index 0000000..d4153c6 --- /dev/null +++ b/bin/scripts/expand_collapse.js @@ -0,0 +1,19 @@ + + + diff --git a/bin/styles/main.css b/bin/styles/main.css new file mode 100644 index 0000000..2c0ffb7 --- /dev/null +++ b/bin/styles/main.css @@ -0,0 +1,249 @@ +* { + margin: 0; + padding: 1px; +} + +.answer_kind_good { + color: #2a7c20; + font-size: 14pt; +} + +.answer_kind_bad { + color: #fa0d0d; + font-size: 14pt; +} + +.assignment_hint { + display: none; +} + +.assignment_text { + font-size: 14pt; + margin-left: 32px; + margin-right: 32px; +} + +.backgrounded_block { + background-color: #d4e5f4; + + /* CSS3 only */ + border-radius: 10px; + /* Pre CSS3 Mozilla */ + -moz-border-radius: 10px; + /* Pre CSS3 Webkit */ + -webkit-border-radius: 10px; + + margin-bottom: 24px; + padding-top: 16px; + + width: 100%; +} + +.caption_v1 { + font-size: 16pt; + margin-left: 32px; + margin-right: 32px; + margin-bottom: 16px; +} + +.emphasis { + font-style: italic; +} + +.exponent { + font-size: 75%; + vertical-align: super; +} + +.key_info { + font-weight: bold; +} + +.problem_form { + margin-left: 32px; + margin-right: 32px; + margin-bottom: 32px; + padding-right: 0px; + padding-top: 16px; +} + + div.form_line { + padding-top: 8px; + } + + .form_label_ac { + display: inline-block; + width: 128px; + } + + .form_label_ac_param { + display: inline-block; + width: 320px; + } + + .form_input_ac { + display: inline-block; + width: 128px; + margin-right: 0px; + padding-right: 0px; + } + +.subscript { + font-size: 75%; + vertical-align: sub; +} + +a.back_link { + color: black; + text-decoration: none; +} + +a.main_navlink { + text-align: center; + background-color: #d4e5f4; + color: black; + padding: 8px 8px 8px; + margin-top: 32px; + font-size: 22pt; + font-weight: bold; + text-decoration: none; + width: 420px; + + /* CSS3 only */ + border-radius: 4px; + /* Pre CSS3 Mozilla */ + -moz-border-radius: 4px; + /* Pre CSS3 Webkit */ + -webkit-border-radius: 4px; +} + +a:hover.main_navlink { + background-color: #58acf4; +} + +a:visited.main_navlink { + color: black; +} + +a.footer_link { + color: black; + font-weight: normal; + text-decoration: none; +} + +a:visited.footer_link { + color: black; + font-weight: normal; + text-decoration: none; +} + +div.hint_block { + margin-left: 32px; + margin-right: 32px; + padding-top: 16px; + padding-bottom: 16px; +} + +div.hint_caption { + font-size: 14pt; + margin-left: 16px; + margin-bottom: 16px; +} + +img.expand_section { + width: 14pt; + height: 14pt; + padding: 3px; + border: 2px solid black; + + /* CSS3 only */ + border-radius: 4px; + /* Pre CSS3 Mozilla */ + -moz-border-radius: 4px; + /* Pre CSS3 Webkit */ + -webkit-border-radius: 4px; +} + +img.math_formula { + height: 32px; + width: auto; + display: block; + + margin-bottom: 16px; + margin-left: 32px; +} + +img.math_formula_multiline { + height: 58px; + width: auto; + display: block; + + margin-bottom: 32px; + margin-left: 32px; +} + + +span.expand_section { + float: right; + margin-right: 32px; +} + +#container { + margin: 0 auto 0; + width: 1000px; + min-height: 100vh; + position: relative; +} + +#content { + margin-top: 32px; + width: 100%; +} + +#core_info { + display: table; + margin: 16px auto; +} + +#footer { + background-color: #58acf4; + height: 48px; + position: absolute; + bottom: 0; + width: 100%; + margin-top: 32px; + + /* CSS3 only */ + border-top-left-radius: 10px; + border-top-right-radius: 10px; + /* Pre CSS3 Mozilla */ + -moz-border-radius-topleft: 10px; + -moz-border-radius-topright: 10px; +} + +#ignore_chart_ac { + width: 872px; + height: auto; + + margin-bottom: 16px; + margin-left: 32px; +} + +#page_caption { + background-color: #58acf4; + + /* CSS3 only */ + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + /* Pre CSS3 Mozilla */ + -moz-border-radius-bottomleft: 10px; + -moz-border-radius-bottomright: 10px; + + height: 96px; +} + +#page_caption_text { + margin-top: 32px; + margin-left: 32px; + font-size: 24pt; +} diff --git a/bin/templates/answer_section.html b/bin/templates/answer_section.html new file mode 100644 index 0000000..16a8cfd --- /dev/null +++ b/bin/templates/answer_section.html @@ -0,0 +1,3 @@ +
+
@_ANSWER_MESSAGE_@
+
diff --git a/bin/templates/face_acidobazic.html b/bin/templates/face_acidobazic.html new file mode 100644 index 0000000..59001ca --- /dev/null +++ b/bin/templates/face_acidobazic.html @@ -0,0 +1,47 @@ + +
+
Zadání:
+
Vypočítejte pH @_SUBSTANCE_@ jejíž @_PKX_@ = @_PKX_VALUE_INT_@,@_PKX_VALUE_DEC_@ a koncentrace c = @_CONCENTRATION_INT_@,@_CONCENTRATION_DEC_@ . 10@_CONCENTRATION_EXP_@ mol/L.
+
+
+ + +
+
+ + +
+
+ +
+
+
+ + @_ANSWER_SECTION_@ + +
+ + Zobraz sekci + +
Nápověda:
+
+ @_HINTS_SECTION_@ +
+
+ +
+
Parametry příkladů:
+
+
+ + +
+
+ +
+
+
diff --git a/bin/templates/face_index.html b/bin/templates/face_index.html new file mode 100644 index 0000000..5f29ae9 --- /dev/null +++ b/bin/templates/face_index.html @@ -0,0 +1,3 @@ +
pH jednosytné kyseliny/báze + + diff --git a/bin/templates/footer.html b/bin/templates/footer.html new file mode 100644 index 0000000..f12d5e8 --- /dev/null +++ b/bin/templates/footer.html @@ -0,0 +1,9 @@ + +
+ + + + + diff --git a/bin/templates/header.html b/bin/templates/header.html new file mode 100644 index 0000000..a5583d3 --- /dev/null +++ b/bin/templates/header.html @@ -0,0 +1,19 @@ + + + + + + + @_META_EXPIRE_NOW_@ + @_META_NO_CACHE_@ + + TGen_Prototype + + + +
+ +
diff --git a/bin/templates/hints_acidobazic_acid.html b/bin/templates/hints_acidobazic_acid.html new file mode 100644 index 0000000..a707c6d --- /dev/null +++ b/bin/templates/hints_acidobazic_acid.html @@ -0,0 +1,45 @@ +
+
+ Definice pH: +
+ Definice pH - vztah +
+
+
+ Základní vztah pro koncentraci kationů H3O, není-li uvažován vliv autoprotolýzy nebo úbytku disociací: +
+ Základní vztah pro koncentraci [H3O+] +
+ +
+
+ V některých případech je nutné do základního vztahu pro výpočet koncentrace H3O přidat korekce na vliv autoprotolýzy či úbytku disociací. Existují vztahy, z nichž můžeme určit, kdy je nutné který vliv uvažovat. +
+
+ Je-li splněna tato rovnice, lze zanedbat vliv autoprotolýzy: +
+ Ověření zanedbání autoprotolýzy - vztah +
+ Je-li splněna tato rovnice, lze zanedbat vliv úbytku disociací: +
+ Ověření zanedbání autoprotolýzy - vztah +
+ +
+
+ Vztah pro koncentraci H3O , je-li uvažována autoprotolýza: +
+ Vztah pro koncentraci H3O+ s autoprotolýzou +
+ Vztah pro koncentraci H3O , je-li uvažován úbytek disociací: +
+ Vztah pro koncentraci H3O+ s úbyktem disociací +
+ +
+
+ Grafické znázornění oblastí, kde je třeba uvažovat autoprotolýzu a úbytek disociací: +
+ +
+ diff --git a/bin/templates/hints_acidobazic_base.html b/bin/templates/hints_acidobazic_base.html new file mode 100644 index 0000000..98d695d --- /dev/null +++ b/bin/templates/hints_acidobazic_base.html @@ -0,0 +1,44 @@ +
+
+ Definice pOH: +
+ Definice pOH - vztah +
+
+
+ Základní vztah pro koncentraci aniontů -OH, není-li uvažován vliv autoprotolýzy nebo úbytku disociací: +
+ Základní vztah pro koncentraci -OH +
+ +
+
+ V některých případech je nutné do základního vztahu pro výpočet koncentrace -OH, přidat korekce na vliv autoprotolýzy či úbytku disociací. Existují vztahy, z nichž můžeme určit, kdy je nutné který vliv uvažovat. +
+
+ Je-li splněna tato rovnice, lze zanedbat vliv autoprotolýzy: +
+ Ověření zanedbání autoprotolýzy - vztah +
+ Je-li splněna tato rovnice, lze zanedbat vliv úbytku disociací: +
+ Ověření zanedbání autoprotolýzy - vztah +
+ +
+
+ Vztah pro koncentraci -OH, je-li uvažována autoprotolýza: +
+ Vztah pro koncentraci -OH s autoprotolýzou +
+ Vztah pro koncentraci -OH, je-li uvažován úbytek disociací: +
+ Vztah pro koncentraci -OH s úbyktem disociací +
+ +
+
+ Grafické znázornění oblastí, kde je třeba uvažovat autoprotolýzu a úbytek disociací: +
+ +
diff --git a/src/face_generators/face_generator.adb b/src/face_generators/face_generator.adb new file mode 100644 index 0000000..6790ad8 --- /dev/null +++ b/src/face_generators/face_generator.adb @@ -0,0 +1,130 @@ +with AWS.Templates; + +package body Face_Generator is + + function Generate_Index_Face(HTML: out HTML_Code) return Boolean is + use AWS.Templates; + Temp: HTML_Code; + Translations_Hdr: Translate_Set; + begin + Insert(Translations_Hdr, Assoc(HEADER_CAPTION_KEY, "...")); + Temp := Parse(Filename => "templates/header.html", Translations => Translations_Hdr, Cached => True); + Append_HTML(Source => HTML, New_Item => Temp); + + Temp := Parse(Filename => "templates/face_index.html", Cached => True); + Append_HTML(Source => HTML, New_Item => Temp); + + Temp := Parse(Filename => "templates/footer.html", Cached => True); + Append_HTML(Source => HTML, New_Item => Temp); + return True; + end Generate_Index_Face; + + function Generate_Face(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map; Parameters: + in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean is + begin + return Generate_Face_With_Answer(Assignment => Assignment, Parameters => Parameters, HTML => HTML, + Answer_Message => To_UB_Text(""), Answer_Code => Problem_Generator_Syswides.Invalid_Answer); + end Generate_Face; + + function Generate_Face_With_Answer(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map; + Answer_Message: in UB_Text; + Answer_Code: in Problem_Generator_Syswides.Answer_RetCode; + Parameters: in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean is + use Problem_Generator_Syswides; + use Problem_Generator_Syswides.Assignment_Info; + + begin + if Assignment.Find(PROBLEM_TYPE_KEY) = Assignment_Info.No_Element then + return False; + end if; + + declare + Problem_Type_Str: constant String := Assignment.Element(PROBLEM_TYPE_KEY); + begin + if Problem_Type_Str = Problem_Type'Image(Acidobazic) then + return Generate_Face_Acidobazic(Assignment, Answer_Message, Answer_Code, Parameters, HTML); + else + return False; + end if; + end; + end Generate_Face_With_Answer; + + -- BEGIN: Private functions + function Generate_Face_Acidobazic(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map; + Answer_Message: in UB_Text; + Answer_Code: in Problem_Generator_Syswides.Answer_RetCode; + Parameters: in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean is + use AWS.Templates; + use Problem_Generator_Syswides; + use Problem_Generator_Syswides.Assignment_Info; + use Problem_Generator_Syswides.Parameters_Info; + + Translations_Answer: Translate_Set; + Translations_Hdr: Translate_Set; + Translations: Translate_Set; + Temp: HTML_Code; + begin + Insert(Translations_Hdr, Assoc(HEADER_CAPTION_KEY, "< " & Acidobazic_Suite.PROBLEM_NAME_READABLE)); + Insert(Translations_Hdr, Assoc(META_EXPIRE_NOW_KEY, META_EXPIRE_NOW_TEXT)); + Insert(Translations_Hdr, Assoc(META_NO_CACHE_KEY, META_NO_CACHE_TEXT)); + HTML := Parse(Filename => "templates/header.html", Translations => Translations_Hdr); + -- Add JavaScripts + Temp := Parse(Filename => "scripts/expand_collapse.js", Cached => True); + Append_HTML(Source => HTML, New_Item => Temp); + + Insert(Translations, Assoc(Acidobazic_Suite.CONCENTRATION_INT_KEY, Assignment.Element(Acidobazic_Suite.CONCENTRATION_INT_KEY))); + Insert(Translations, Assoc(Acidobazic_Suite.CONCENTRATION_DEC_KEY, Assignment.Element(Acidobazic_Suite.CONCENTRATION_DEC_KEY))); + Insert(Translations, Assoc(Acidobazic_Suite.CONCENTRATION_EXP_KEY, Assignment.Element(Acidobazic_Suite.CONCENTRATION_EXP_KEY))); + Insert(Translations, Assoc(Acidobazic_Suite.PKX_KEY, Assignment.Element(Acidobazic_Suite.PKX_KEY))); + Insert(Translations, Assoc(Acidobazic_Suite.PKX_VALUE_INT_KEY, Assignment.Element(Acidobazic_Suite.PKX_VALUE_INT_KEY))); + Insert(Translations, Assoc(Acidobazic_Suite.PKX_VALUE_DEC_KEY, Assignment.Element(Acidobazic_Suite.PKX_VALUE_DEC_KEY))); + Insert(Translations, Assoc(Acidobazic_Suite.SUBSTANCE_KEY, Assignment.Element(Acidobazic_Suite.SUBSTANCE_KEY))); + Insert(Translations, Assoc("ANSWER_PH", Acidobazic_Suite.ANSWER_PH_KEY)); + Insert(Translations, Assoc("ANSWER_SIMPLIFICATION", Acidobazic_Suite.ANSWER_SIMPLIFICATION_KEY)); + Insert(Translations, Assoc("ANSWER_OPTION_SIMPL_ATPR", Acidobazic_Suite.Simplification'Image(Acidobazic_Suite.Autoprotolysis))); + Insert(Translations, Assoc("ANSWER_OPTION_SIMPL_BOTH", Acidobazic_Suite.Simplification'Image(Acidobazic_Suite.Both))); + Insert(Translations, Assoc("ANSWER_OPTION_SIMPL_DISSOC", Acidobazic_Suite.Simplification'Image(Acidobazic_Suite.Dissociation))); + Insert(Translations, Assoc("PARAMETER_NO_BOTH_SIMPLIFICATIONS", Acidobazic_Suite.PARAMETER_NO_BOTH_SIMPLIFICATIONS_KEY)); + if Parameters.Find(Acidobazic_Suite.PARAMETER_NO_BOTH_SIMPLIFICATIONS_KEY) = Parameters_Info.No_Element then + Insert(Translations, Assoc("PARAMETER_NO_BOTH_SIMPLIFICATIONS_CHECKED", "")); + else + Insert(Translations, Assoc("PARAMETER_NO_BOTH_SIMPLIFICATIONS_CHECKED", "checked")); + end if; + + case Answer_Code is + when Correct_Answer => + Insert(Translations_Answer, Assoc(ANSWER_KIND_KEY, ANSWER_KIND_GOOD)); + Insert(Translations_Answer, Assoc(ANSWER_MESSAGE_KEY, UB_Text_To_Fixed_String(Answer_Message))); + Temp := Parse(Filename => "templates/answer_section.html", Translations => Translations_Answer); + Insert(Translations, Assoc(ANSWER_SECTION_KEY, HTML_To_Fixed_String(Temp))); + when Wrong_Answer | Malformed_Answer => + Insert(Translations_Answer, Assoc(ANSWER_KIND_KEY, ANSWER_KIND_BAD)); + Insert(Translations_Answer, Assoc(ANSWER_MESSAGE_KEY, UB_Text_To_Fixed_String(Answer_Message))); + Temp := Parse(Filename => "templates/answer_section.html", Translations => Translations_Answer); + Insert(Translations, Assoc(ANSWER_SECTION_KEY, HTML_To_Fixed_String(Temp))); + when others => + Insert(Translations, Assoc(ANSWER_SECTION_KEY, "")); + end case; + + -- Generate hints + if Assignment.Element(Acidobazic_Suite.PKX_KEY) = Acidobazic_Suite.PKX_PKA_TEXT then + Temp := Parse(Filename => "templates/hints_acidobazic_acid.html", Cached => True); + Insert(Translations, Assoc(HINTS_SECTION_KEY, Temp)); + elsif Assignment.Element(Acidobazic_Suite.PKX_KEY) = Acidobazic_Suite.PKX_PKB_TEXT then + Temp := Parse(Filename => "templates/hints_acidobazic_base.html", Cached => True); + Insert(Translations, Assoc(HINTS_SECTION_KEY, Temp)); + end if; + + Temp := Parse(Filename => "templates/face_acidobazic.html", Translations => Translations); + Append_HTML(Source => HTML, New_Item => Temp); + + + Temp := Parse(Filename => "templates/footer.html"); + Append_HTML(Source => HTML, New_Item => Temp); + + return True; + end Generate_Face_Acidobazic; +end Face_Generator; diff --git a/src/face_generators/face_generator.ads b/src/face_generators/face_generator.ads new file mode 100644 index 0000000..3a26435 --- /dev/null +++ b/src/face_generators/face_generator.ads @@ -0,0 +1,32 @@ +with Global_Types; +with Problem_Generator_Syswides; + +use Global_Types; +package Face_Generator is + function Generate_Index_Face(HTML: out HTML_Code) return Boolean; + + function Generate_Face(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map; + Parameters: in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean; + + function Generate_Face_With_Answer(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map; + Answer_Message: in UB_Text; + Answer_Code: in Problem_Generator_Syswides.Answer_RetCode; + Parameters: in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean; + +private + function Generate_Face_Acidobazic(Assignment: in Problem_Generator_Syswides.Assignment_Info.Map; + Answer_Message: in UB_Text; + Answer_Code: in Problem_Generator_Syswides.Answer_RetCode; + Parameters: in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean; + + HEADER_CAPTION_KEY: constant String := "HEADER_CAPTION"; + HINTS_SECTION_KEY: constant String := "HINTS_SECTION"; + META_EXPIRE_NOW_KEY: constant String := "META_EXPIRE_NOW"; + META_EXPIRE_NOW_TEXT: constant String := ""; + META_NO_CACHE_KEY: constant String := "META_NO_CACHE"; + META_NO_CACHE_TEXT: constant String := ""; + +end Face_Generator; diff --git a/src/formatting_helpers.adb b/src/formatting_helpers.adb new file mode 100644 index 0000000..313032f --- /dev/null +++ b/src/formatting_helpers.adb @@ -0,0 +1,75 @@ +with Ada.Numerics.Generic_Elementary_Functions; +with Ada.Strings.Fixed; +--with Ada.Text_IO; + +package body Formatting_Helpers is + + function Round_To_Valid_Nums(Num: in FH_Float; Decimals: in FH_Float) return FH_Float is + package FHEF is new Ada.Numerics.Generic_Elementary_Functions(FH_Float); + use FHEF; + + F_Log_Num: constant FH_Float := FH_Float'Floor(FHEF.Log(Base => 10.0, X => Num)); + F_Log_Dec: constant FH_Float := FH_Float'Floor(FHEF.Log(Base => 10.0, X => Decimals)); + Shift: FH_Float; + TNum: FH_Float; + begin + Shift := 10.0 ** (F_Log_Num - F_Log_Dec); + TNum := FH_Float'Rounding(Num / Shift) * Shift; + + --Ada.Text_IO.Put_Line("FLN: " & FH_Float'Image(F_Log_Num) & " FLD: " & FH_Float'Image(F_Log_Dec) & " S: " & FH_Float'Image(Shift) & " Res: " & FH_Float'Image(TNum)); + return TNum; + end Round_To_Valid_Nums; + + procedure Split_Integer_Decimal_Strs(Num: in FH_Float; Decimals: in FH_Float; Integer_Part: out UB_Text; Decimal_Part: out UB_Text) is + I: Integer; + D: Integer; + begin + Split_Integer_Decimal_Ints(Num, Decimals, I, D); + + Integer_Part := To_UB_Text(Integer'Image(I)); + Get_Decimal_Part_Str(Decimal_Part, D, Decimals); + + --Ada.Text_IO.Put_Line("I: " & Integer'Image(I) & " D: " & Integer'Image(D) & " Num: " & FH_Float'Image(Num)); + end Split_Integer_Decimal_Strs; + + procedure Split_Integer_Decimal_Exponent_Strs(Num: in FH_Float; Decimals: in FH_Float; Integer_Part: out UB_Text; Decimal_Part: out UB_Text; + Exponent_Part: out UB_Text) is + package FHEF is new Ada.Numerics.Generic_Elementary_Functions(FH_Float); + use FHEF; + + FE: FH_Float; + TNum: FH_Float; + begin + --Ada.Text_IO.Put_Line("Num: " & FH_Float'Image(Num)); + FE := FH_Float'Floor(FHEF.Log(Base => 10.0, X => Num)); + --Ada.Text_IO.Put_Line("Exp: " & FH_Float'Image(FE)); + TNum := Num / (10.0 ** FE); + + Split_Integer_Decimal_Strs(TNum, Decimals, Integer_Part, Decimal_Part); + Append_UB_Text(Exponent_Part, To_UB_Text(Integer'Image(Integer(FE)))); + + end Split_Integer_Decimal_Exponent_Strs; + + -- BEGIN: Private functions + procedure Get_Decimal_Part_Str(Decimal_Part: out UB_Text; D: in Integer; Decimals: in FH_Float) is + Pos: Integer := Integer(Decimals) / 10; + begin + while Pos > 1 loop + if D < Pos then + Append_UB_Text(Decimal_Part, To_UB_Text("0")); + else + exit; + end if; + Pos := Pos / 10; + end loop; + + Append_UB_Text(Decimal_Part, To_UB_Text(Ada.Strings.Fixed.Trim(Integer'Image(D), Ada.Strings.Left))); + end Get_Decimal_Part_Str; + + procedure Split_Integer_Decimal_Ints(Num: in FH_Float; Decimals: in FH_Float; I: out Integer; D: out Integer) is + begin + I := Integer(FH_Float'Floor(Num)); + D := Integer(FH_Float'Rounding((Num - FH_Float(I)) * Decimals)); + end Split_Integer_Decimal_Ints; + +end Formatting_Helpers; diff --git a/src/formatting_helpers.ads b/src/formatting_helpers.ads new file mode 100644 index 0000000..ba0128d --- /dev/null +++ b/src/formatting_helpers.ads @@ -0,0 +1,17 @@ +with Global_Types; + +use Global_Types; +generic +type FH_Float is digits <>; +package Formatting_Helpers is + + function Round_To_Valid_Nums(Num: in FH_Float; Decimals: FH_Float) return FH_Float; + procedure Split_Integer_Decimal_Strs(Num: in FH_Float; Decimals: in FH_Float; Integer_Part: out UB_Text; Decimal_Part: out UB_Text); + procedure Split_Integer_Decimal_Exponent_Strs(Num: in FH_Float; Decimals: in FH_Float; Integer_Part: out UB_Text; Decimal_Part: out UB_Text; + Exponent_Part: out UB_Text); + +private + procedure Get_Decimal_Part_Str(Decimal_Part: out UB_Text; D: Integer; Decimals: in FH_Float); + procedure Split_Integer_Decimal_Ints(Num: in FH_Float; Decimals: in FH_Float; I: out Integer; D: out Integer); + +end Formatting_Helpers; diff --git a/src/global_types.adb b/src/global_types.adb new file mode 100644 index 0000000..ab6ccba --- /dev/null +++ b/src/global_types.adb @@ -0,0 +1,33 @@ +package body Global_Types is + + procedure Append_HTML(Source: in out HTML_Code; New_Item: in HTML_Code) is + begin + Ada.Strings.Unbounded.Append(Source => Source, New_Item => New_Item); + end Append_HTML; + + procedure Append_UB_Text(Source: in out UB_Text; New_Item: in UB_Text) is + begin + Ada.Strings.Unbounded.Append(Source => Source, New_Item => New_Item); + end Append_UB_Text; + + function HTML_To_Fixed_String(HTML: in HTML_Code) return String is + begin + return Ada.Strings.Unbounded.To_String(HTML); + end HTML_To_Fixed_String; + + function To_HTML_Code(S: in String) return HTML_Code is + begin + return Ada.Strings.Unbounded.To_Unbounded_String(S); + end To_HTML_Code; + + function To_UB_Text(S: in String) return UB_Text is + begin + return Ada.Strings.Unbounded.To_Unbounded_String(S); + end To_UB_Text; + + function UB_Text_To_Fixed_String(Text: in UB_Text) return String is + begin + return Ada.Strings.Unbounded.To_String(Text); + end UB_Text_To_Fixed_String; + +end Global_Types; diff --git a/src/global_types.ads b/src/global_types.ads new file mode 100644 index 0000000..6d18c20 --- /dev/null +++ b/src/global_types.ads @@ -0,0 +1,17 @@ +with Ada.Containers; +with Ada.Strings.Unbounded; + +package Global_Types is + + subtype Unique_ID is Ada.Containers.Count_Type; + subtype HTML_Code is Ada.Strings.Unbounded.Unbounded_String; + subtype UB_Text is Ada.Strings.Unbounded.Unbounded_String; + + procedure Append_HTML(Source: in out HTML_Code; New_Item: in HTML_Code); + procedure Append_UB_Text(Source: in out UB_Text; New_Item: in UB_Text); + function HTML_To_Fixed_String(HTML: in HTML_Code) return String; + function To_HTML_Code(S: in String) return HTML_Code; + function To_UB_Text(S: in String) return UB_Text; + function UB_Text_To_Fixed_String(Text: in UB_Text) return String; + +end Global_Types; diff --git a/src/handlers/handler_check_answer.adb b/src/handlers/handler_check_answer.adb new file mode 100644 index 0000000..af41bb8 --- /dev/null +++ b/src/handlers/handler_check_answer.adb @@ -0,0 +1,74 @@ +with AWS.Containers.Tables; +with AWS.Messages; +with AWS.MIME; +with AWS.Parameters; +with AWS.Response; +with AWS.Session; +with AWS.Status; + +with Global_Types; +with Problem_Manager; +with Problem_Generator_Syswides; + +with Ada.Text_IO; + +use Global_Types; +package body Handler_Check_Answer is + + function Handle(Request: AWS.Status.Data) return AWS.Response.Data is + SID: constant AWS.Session.ID := AWS.Status.Session(Request); + Req_Type: constant AWS.Status.Request_Method := AWS.Status.Method(Request); + + Raw_UID: constant String := AWS.Session.Get(SID, "UID"); + UID: Unique_ID; + HTML: HTML_Code; + begin + Ada.Text_IO.Put_Line("Handling /check_answer"); + + case Req_Type is + when AWS.Status.POST => + declare + use Problem_Generator_Syswides; + use Problem_Generator_Syswides.Answer_Info; + + POST_Data: constant AWS.Parameters.List := AWS.Status.Parameters(Request); + Answer: Answer_Info.Map; + Success: Boolean; + begin + -- Get UID + Success := Problem_Manager.Get_UID(Raw_UID, UID); + if Success = False then + -- UID could not have been registered + -- TODO: Print some sensible error message, for now just redirect to index + Ada.Text_IO.Put_Line("UID has not been registered: " & Raw_UID); + return AWS.Response.URL(Location => "/"); + end if; + + for Idx in 1 .. POST_Data.Count loop + declare + C: Answer_Info.Cursor; + E: constant AWS.Containers.Tables.Element := POST_Data.Get(Idx); + Success: Boolean; + begin + Answer.Insert(E.Name, E.Value, C, Success); + -- TODO: Handle error + end; + end loop; + + Success := Problem_Manager.Display_Checked_Answer(UID, Answer, HTML); + return AWS.Response.Build(Content_Type => AWS.MIME.Text_HTML, + Message_Body => HTML_To_Fixed_String(HTML), + Status_Code => AWS.Messages.S200); + end; + when others => + Ada.Text_IO.Put_Line("Invalid request"); + return AWS.Response.URL(Location => "/"); + end case; + end Handle; + + function Callback return AWS.Dispatchers.Callback.Handler is + begin + return AWS.Dispatchers.Callback.Create(Handle'Access); + end Callback; + +end Handler_Check_Answer; diff --git a/src/handlers/handler_check_answer.ads b/src/handlers/handler_check_answer.ads new file mode 100644 index 0000000..8daf92f --- /dev/null +++ b/src/handlers/handler_check_answer.ads @@ -0,0 +1,5 @@ +with AWS.Dispatchers.Callback; + +package Handler_Check_Answer is + function Callback return AWS.Dispatchers.Callback.Handler; +end Handler_Check_Answer; diff --git a/src/handlers/handler_default.adb b/src/handlers/handler_default.adb new file mode 100644 index 0000000..f89fb29 --- /dev/null +++ b/src/handlers/handler_default.adb @@ -0,0 +1,32 @@ +with AWS.Messages; +with AWS.MIME; +with AWS.Response; +with AWS.Status; +with Face_Generator; +with Global_Types; + +use Global_Types; +package body Handler_Default is + + function Handle(Request: AWS.Status.Data) return AWS.Response.Data is + HTML: HTML_Code; + Success: Boolean; + begin + Success := Face_Generator.Generate_Index_Face(HTML); + if Success = False then + return AWS.Response.Build(Content_Type => AWS.MIME.Text_HTML, + Message_Body => "Internal server error occured, we're sorry about that...", + Status_Code => AWS.Messages.S503); + else + return AWS.Response.Build(Content_Type => AWS.MIME.Text_HTML, + Message_Body => HTML_To_Fixed_String(HTML), + Status_Code => AWS.Messages.S200); + end if; + end Handle; + + function Callback return AWS.Dispatchers.Callback.Handler is + begin + return AWS.Dispatchers.Callback.Create(Handle'Access); + end Callback; + +end Handler_Default; diff --git a/src/handlers/handler_default.ads b/src/handlers/handler_default.ads new file mode 100644 index 0000000..edecf1f --- /dev/null +++ b/src/handlers/handler_default.ads @@ -0,0 +1,5 @@ +with AWS.Dispatchers.Callback; + +package Handler_Default is + function Callback return AWS.Dispatchers.Callback.Handler; +end Handler_Default; \ No newline at end of file diff --git a/src/handlers/handler_images.adb b/src/handlers/handler_images.adb new file mode 100644 index 0000000..575f545 --- /dev/null +++ b/src/handlers/handler_images.adb @@ -0,0 +1,50 @@ +with Ada.Strings.Fixed; +with AWS.Messages; +with AWS.MIME; +with AWS.Response; +with AWS.Status; +with Ada.Text_IO; + +package body Handler_Images is + + function File_Exists(Path: in String) return Boolean is + File: Ada.Text_IO.File_Type; + begin + begin + Ada.Text_IO.Open(File => File, Mode => Ada.Text_IO.In_File, Name => Path); + Ada.Text_IO.Close(File); + return True; + exception + when Ada.Text_IO.Name_Error => + return False; + end; + end File_Exists; + + function Handle(Request: AWS.Status.Data) return AWS.Response.Data is + use Ada.Strings.Fixed; + + URI: constant String := AWS.Status.URI(Request); + Idx: Positive; + begin + Idx := Index(Source => URI, Pattern => "/", From => URI'Last, Going => Ada.Strings.Backward); + declare + Image_Path: constant String := "images/" & URI(Idx + 1 .. URI'Last); + begin + if File_Exists(Image_Path) = False then + return AWS.Response.Build(Content_Type => AWS.MIME.Text_HTML, + Message_Body => "", + Status_Code => AWS.Messages.S404); + else + return AWS.Response.File(Content_Type => AWS.MIME.Image_Png, + Filename => Image_Path, + Status_Code => AWS.Messages.S200); + end if; + end; + end Handle; + + function Callback return AWS.Dispatchers.Callback.Handler is + begin + return AWS.Dispatchers.Callback.Create(Handle'Access); + end Callback; + +end Handler_Images; diff --git a/src/handlers/handler_images.ads b/src/handlers/handler_images.ads new file mode 100644 index 0000000..c2b542c --- /dev/null +++ b/src/handlers/handler_images.ads @@ -0,0 +1,5 @@ +with AWS.Dispatchers.Callback; + +package Handler_Images is + function Callback return AWS.Dispatchers.Callback.Handler; +end Handler_Images; diff --git a/src/handlers/handler_next_problem.adb b/src/handlers/handler_next_problem.adb new file mode 100644 index 0000000..18675dc --- /dev/null +++ b/src/handlers/handler_next_problem.adb @@ -0,0 +1,76 @@ +with AWS.Containers.Tables; +with AWS.Messages; +with AWS.MIME; +with AWS.Parameters; +with AWS.Response; +with AWS.Session; +with AWS.Status; + +with Global_Types; +with Problem_Generator_Syswides; +with Problem_Manager; + +use Global_Types; + +package body Handler_Next_Problem is + + function Handle(Request: AWS.Status.Data) return AWS.Response.Data is + SID: constant AWS.Session.ID := AWS.Status.Session(Request); + + HTML: HTML_Code; + Raw_UID: constant String := AWS.Session.Get(SID, "UID"); + Req_Method : constant AWS.Status.Request_Method := AWS.Status.Method(Request); + UID: Unique_ID; + begin + case Req_Method is + when AWS.Status.POST => + declare + use Problem_Generator_Syswides; + use Problem_Generator_Syswides.Parameters_Info; + + Problem_Parameters: Parameters_Info.Map; + POST_Data: constant AWS.Parameters.List := AWS.Status.Parameters(Request); + Success: Boolean; + begin + -- Get UID + Success := Problem_Manager.Get_UID(Raw_UID, UID); + if Success = False then + -- UID could not have been registered + -- TODO: Print some sensible error message, for now just redirect to index + return AWS.Response.URL(Location => "/"); + end if; + + -- UID OK, read problem parameters + for Idx in 1 .. POST_Data.Count loop + declare + C: Parameters_Info.Cursor; + E: constant AWS.Containers.Tables.Element := POST_Data.Get(Idx); + Success: Boolean; + begin + Problem_Parameters.Insert(E.Name, E.Value, C, Success); + -- TODO: Handle error + end; + end loop; + + -- Display new problem + Success := Problem_Manager.Display_Next_Assignment(UID, Problem_Parameters, HTML); + if Success = False then + -- TODO: Handle error in a less reckless manner + return AWS.Response.URL(Location => "/"); + end if; + + return AWS.Response.Build(Content_Type => AWS.MIME.Text_HTML, + Message_Body => HTML_To_Fixed_String(HTML), + Status_Code => AWS.Messages.S200); + end; + when others => + return AWS.Response.URL(Location => "/"); + end case; + end Handle; + + function Callback return AWS.Dispatchers.Callback.Handler is + begin + return AWS.Dispatchers.Callback.Create(Handle'Access); + end Callback; + +end Handler_Next_Problem; diff --git a/src/handlers/handler_next_problem.ads b/src/handlers/handler_next_problem.ads new file mode 100644 index 0000000..77c930f --- /dev/null +++ b/src/handlers/handler_next_problem.ads @@ -0,0 +1,5 @@ +with AWS.Dispatchers.Callback; + +package Handler_Next_Problem is + function Callback return AWS.Dispatchers.Callback.Handler; +end Handler_Next_Problem; \ No newline at end of file diff --git a/src/handlers/handler_start.adb b/src/handlers/handler_start.adb new file mode 100644 index 0000000..d4d9def --- /dev/null +++ b/src/handlers/handler_start.adb @@ -0,0 +1,78 @@ +with AWS.Messages; +with AWS.MIME; +with AWS.Response; +with AWS.Session; +with AWS.Status; +with Global_Types; +with Problem_Manager; + +--with Ada.Text_IO; + +use Global_Types; +package body Handler_Start is + + function Handle(Request: AWS.Status.Data) return AWS.Response.Data is + SID: constant AWS.Session.ID := AWS.Status.Session(Request); + Req_Type: constant AWS.Status.Request_Method := AWS.Status.Method(Request); + + Raw_UID: constant String := AWS.Session.Get(SID, "UID"); + HTML: HTML_Code; + begin + --Ada.Text_IO.Put_Line("Handling /start"); + + case Req_Type is + when AWS.Status.GET => + declare + Raw_Problem_Category: constant String := AWS.Status.Parameter(Request, "problem_category"); + P_Cat: Problem_Manager.Problem_Category; + UID: Unique_ID; + Success: Boolean; + begin + if Raw_Problem_Category = Problem_Manager.Problem_Category'Image(Problem_Manager.Acidobazic) then + P_Cat := Problem_Manager.Acidobazic; + else + return AWS.Response.URL(Location => "/"); + end if; + + -- Register new UID if necessary and create a first problem + Success := Problem_Manager.Get_UID(Raw_UID, UID); + if Success = False then + -- UID stored within this session is not valid, register a new one + Success := Problem_Manager.Register_UID(UID); + if Success = False then + -- UID could not have been registered + -- TODO: Print some sensible error message, for now just redirect to index + return AWS.Response.URL(Location => "/"); + end if; + -- Save the new UID + AWS.Session.Set(SID, "UID", Unique_ID'Image(UID)); + AWS.Session.Set_Callback(Problem_Manager.Session_Expired'Access); + end if; + + -- We're all set, create a new problem + Success := Problem_Manager.Prepare_Problem(UID, P_Cat); + if Success = False then + -- Something went wrong when generating the problem + -- TODO: Print some sensible error message, for now just redirect to index + return AWS.Response.URL(Location => "/"); + end if; + + Success := Problem_Manager.Display_New_Assignment(UID, HTML); + if Success = False then + HTML := To_HTML_Code("Cannot display assignment"); + end if; + return AWS.Response.Build(Content_Type => AWS.MIME.Text_HTML, + Message_Body => HTML_To_Fixed_String(HTML), + Status_Code => AWS.Messages.S200); + end; + when others => + return AWS.Response.URL(Location => "/"); + end case; + end Handle; + + function Callback return AWS.Dispatchers.Callback.Handler is + begin + return AWS.Dispatchers.Callback.Create(Handle'Access); + end Callback; + +end Handler_Start; diff --git a/src/handlers/handler_start.ads b/src/handlers/handler_start.ads new file mode 100644 index 0000000..4a672ea --- /dev/null +++ b/src/handlers/handler_start.ads @@ -0,0 +1,5 @@ +with AWS.Dispatchers.Callback; + +package Handler_Start is + function Callback return AWS.Dispatchers.Callback.Handler; +end Handler_Start; \ No newline at end of file diff --git a/src/handlers/handler_styles.adb b/src/handlers/handler_styles.adb new file mode 100644 index 0000000..54523df --- /dev/null +++ b/src/handlers/handler_styles.adb @@ -0,0 +1,21 @@ +with AWS.Messages; +with AWS.MIME; +with AWS.Templates; +with AWS.Response; +with AWS.Status; + +package body Handler_Styles is + + function Main_Handle(Request: AWS.Status.Data) return AWS.Response.Data is + begin + return AWS.Response.Build(Content_Type => AWS.MIME.Text_CSS, + Message_Body => AWS.Templates.Parse(Filename => "styles/main.css", Cached => True), + Status_Code => AWS.Messages.S200); + end Main_Handle; + + function Main_Callback return AWS.Dispatchers.Callback.Handler is + begin + return AWS.Dispatchers.Callback.Create(Main_Handle'Access); + end Main_Callback; + +end Handler_Styles; diff --git a/src/handlers/handler_styles.ads b/src/handlers/handler_styles.ads new file mode 100644 index 0000000..9f3b28a --- /dev/null +++ b/src/handlers/handler_styles.ads @@ -0,0 +1,6 @@ +with AWS.Dispatchers.Callback; + +package Handler_Styles is + function Main_Callback return AWS.Dispatchers.Callback.Handler; +end Handler_Styles; + diff --git a/src/handlers/handlers.adb b/src/handlers/handlers.adb new file mode 100644 index 0000000..3bf75c0 --- /dev/null +++ b/src/handlers/handlers.adb @@ -0,0 +1,27 @@ +with Handler_Check_Answer; +with Handler_Default; +with Handler_Images; +with Handler_Next_Problem; +with Handler_Start; +with Handler_Styles; + +package body Handlers is + function Get_Dispatchers return AWS.Services.Dispatchers.URI.Handler is + Handler: AWS.Services.Dispatchers.URI.Handler; + begin + Handler.Register_Default_Callback(Action => Handler_Default.Callback); + Handler.Register(URI => "/check_answer", + Action => Handler_Check_Answer.Callback); + Handler.Register(URI => "/images", + Action => Handler_Images.Callback, + Prefix => True); + Handler.Register(URI => "/next_problem", + Action => Handler_Next_Problem.Callback); + Handler.Register(URI => "/start", + Action => Handler_Start.Callback); + Handler.Register(URI => "/main_stylesheet", + Action => Handler_Styles.Main_Callback); + + return Handler; + end Get_Dispatchers; +end Handlers; diff --git a/src/handlers/handlers.ads b/src/handlers/handlers.ads new file mode 100644 index 0000000..e0b9679 --- /dev/null +++ b/src/handlers/handlers.ads @@ -0,0 +1,5 @@ +with AWS.Services.Dispatchers.URI; + +package Handlers is + function Get_Dispatchers return AWS.Services.Dispatchers.URI.Handler; +end Handlers; \ No newline at end of file diff --git a/src/nine_q.adb b/src/nine_q.adb new file mode 100644 index 0000000..a13a970 --- /dev/null +++ b/src/nine_q.adb @@ -0,0 +1,29 @@ +with Ada.Text_IO; + +with AWS.Config; +with AWS.Config.Set; +with AWS.Server; + +with Handlers; + +procedure Nine_Q is + Server_Config: AWS.Config.Object; + Web_Server: AWS.Server.HTTP; +begin + Server_Config := AWS.Config.Get_Current; + AWS.Config.Set.Session(Server_Config, True); + AWS.Config.Set.Server_Host(Server_Config, "localhost"); + AWS.Config.Set.Server_Port(Server_Config, 18400); + + AWS.Config.Set.Session_Lifetime(1800.0); + AWS.Config.Set.Session_Cleanup_Interval(900.0); + + Ada.Text_IO.Put_Line("Starting server..."); + AWS.Server.Start(Web_Server => Web_Server, + Dispatcher => Handlers.Get_Dispatchers, + Config => Server_Config); + + AWS.Server.Wait(AWS.Server.Q_Key_Pressed); + AWS.Server.Shutdown(Web_Server); + +end Nine_Q; diff --git a/src/problem_generators/problem_generator-acidobazic_suite.adb b/src/problem_generators/problem_generator-acidobazic_suite.adb new file mode 100644 index 0000000..8dba22e --- /dev/null +++ b/src/problem_generators/problem_generator-acidobazic_suite.adb @@ -0,0 +1,497 @@ +with Ada.Numerics.Discrete_Random; +with Ada.Numerics.Float_Random; +with Ada.Numerics.Generic_Elementary_Functions; +with Ada.Strings.Fixed; +--with Ada.Text_IO; +with Formatting_Helpers; + +separate(Problem_Generator) + +package body Acidobazic_Suite is + -- BEGIN: Inherited functions + function Create return access Acidobazic_Problem is + Parameters: Acidobazic_Parameters; + Problem: access Acidobazic_Problem; + begin + Problem := new Acidobazic_Problem; + + Parameters := (No_Both_Simplifications => False); + Problem.Parameters := Parameters; + + return Problem; + end Create; + + function Check_Answer(Problem: in out Acidobazic_Problem; Answer: in Answer_Info.Map; + Message: out UB_Text) return Answer_RetCode is + package FH is new Formatting_Helpers(pH_Float); + use Answer_Info; + + pH: pH_Float; + pH_Answered: pH_Float; + begin + Problem.Lock_State; + + pH := Calculate_Solution(Problem); + -- Verify answer data + if Answer.Find(ANSWER_PH_KEY) = Answer_Info.No_Element then + return Malformed_Answer; + end if; + if Answer.Find(ANSWER_SIMPLIFICATION_KEY) = Answer_Info.No_Element then + Problem.Unlock_State; + return Malformed_Answer; + end if; + + declare + pH_Answered_S: String := Answer.Element(ANSWER_PH_KEY); + Idx: Natural; + begin + -- Replace "," with "." as decimal seaprator + Idx := Ada.Strings.Fixed.Index(Source => pH_Answered_S, Pattern => ",", From => 1); + if Idx > 0 then + Ada.Strings.Fixed.Replace_Slice(Source => pH_Answered_S, Low => Idx, High => Idx, By => "."); + end if; + pH_Answered := pH_Float'Value(pH_Answered_S); + exception + when Constraint_Error => + Message := To_UB_Text("Nesprávně zadaná hodnota pH"); + Problem.Unlock_State; + return Malformed_Answer; + end; + + -- Check correctness of simplification + declare + Simplification_Str: constant String := Answer.Element(ANSWER_SIMPLIFICATION_KEY); + begin + Ada.Text_IO.Put_Line("SS : " & Simplification_Str & " SI : " & Simplification'Image(Problem.Simpl)); + if Simplification_Str /= Simplification'Image(Problem.Simpl) then + Message := To_UB_Text("Nesprávné zanedbání"); + Problem.Unlock_State; + return Wrong_Answer; + end if; + end; + + -- Check correctness of the result + pH := FH.Round_To_Valid_Nums(pH, Decimals); + pH_Answered := FH.Round_To_Valid_Nums(pH_Answered, Decimals); + + if pH_Answered - (Precision * 5.0) < pH and pH_Answered + (Precision * 5.0) > pH then + Message := To_UB_Text("Správná odpověď"); + Problem.Unlock_State; + return Correct_Answer; + else + declare + package FH is new Formatting_Helpers(pH_Float); + + Int_S: UB_Text; + Dec_S: UB_Text; + begin + FH.Split_Integer_Decimal_Strs(pH, Decimals, Int_S, Dec_S); + Message := To_UB_Text("Nesprávná odpověď. (pH vypočtené programem = "); + Append_UB_Text(Source => Message, New_Item => Int_S); + Append_UB_Text(Source => Message, New_Item => To_UB_Text(",")); + Append_UB_Text(Source => Message, New_Item => Dec_S); + Append_UB_Text(Source => Message, New_Item => To_UB_Text(")")); + Problem.Unlock_State; + return Wrong_Answer; + end; + end if; + end Check_Answer; + + function Get_Assignment(Problem: in out Acidobazic_Problem; Assignment: in out Assignment_Info.Map) return Boolean is + C: Assignment_Info.Cursor; + Success: Boolean; + pKx: pH_Float; + begin + Problem.Lock_State; + + Assignment.Insert(PROBLEM_TYPE_KEY, PROBLEM_TYPE_ACIDOBAZIC, C, Success); + if Success = False then + Problem.Unlock_State; + return False; + end if; + case Problem.Subst_Type is + when Acid => + Assignment.Insert(SUBSTANCE_KEY, "kyseliny", C, Success); + Assignment.Insert(PKX_KEY, PKX_PKA_TEXT, C, Success); + when Base => + Assignment.Insert(SUBSTANCE_KEY, "báze", C, Success); + Assignment.Insert(PKX_KEY, PKX_PKB_TEXT, C, Success); + end case; + + pKx := X_To_pX(Problem.Kx); + -- Print pKx in nn.nnn format + declare + package FH is new Formatting_Helpers(pH_Float); + + Int_S: UB_Text; + Dec_S: UB_Text; + begin + FH.Split_Integer_Decimal_Strs(pKx, Decimals, Int_S, Dec_S); + Assignment.Insert(PKX_VALUE_INT_KEY, UB_Text_To_Fixed_String(Int_S), C, Success); + Assignment.Insert(PKX_VALUE_DEC_KEY, UB_Text_To_Fixed_String(Dec_S), C, Success); + if Success = False then + Problem.Unlock_State; + return False; + end if; + end; + + -- Print concentration in N.nnn 10^n format + declare + package FH is new Formatting_Helpers(pH_Float); + + Int_S: UB_Text; + Dec_S: UB_Text; + Exp_S: UB_Text; + begin + FH.Split_Integer_Decimal_Exponent_Strs(Problem.cX, Decimals, Int_S, Dec_S, Exp_S); + Assignment.Insert(CONCENTRATION_INT_KEY, UB_Text_To_Fixed_String(Int_S), C, Success); + Assignment.Insert(CONCENTRATION_DEC_KEY, UB_Text_To_Fixed_String(Dec_S), C, Success); + Assignment.Insert(CONCENTRATION_EXP_KEY, UB_Text_To_Fixed_String(Exp_S), C, Success); + if Success = False then + Problem.Unlock_State; + return False; + end if; + end; + + Problem.Unlock_State; + return True; + end Get_Assignment; + + function Get_Parameters(Problem: in out Acidobazic_Problem; Parameters: out Parameters_Info.Map) return Boolean is + C: Parameters_Info.Cursor; + Success: Boolean; + begin + Problem.Lock_State; + if Problem.Parameters.No_Both_Simplifications then + Parameters.Insert(PARAMETER_NO_BOTH_SIMPLIFICATIONS_KEY, "True", C, Success); + Problem.Unlock_State; + return Success; + end if; + Problem.Unlock_State; + return True; + end Get_Parameters; + + procedure New_Problem(Problem: in out Acidobazic_Problem) is + + package Random_Substance_Type_Gen is new Ada.Numerics.Discrete_Random(Result_Subtype => Substance_Type); + package Random_Dissoc_Type_Gen is new Ada.Numerics.Discrete_Random(Result_Subtype => Dissociation_Constant_Type); + + DCT_G: Random_Dissoc_Type_Gen.Generator; + ST_G: Random_Substance_Type_Gen.Generator; + + cX_Min: pH_Float; + cX_Max: pH_Float; + begin + Problem.Lock_State; + -- Dissociation constant type (pKa or pKb) + Random_Dissoc_Type_Gen.Reset(Gen => DCT_G); + Problem.DCT := Random_Dissoc_Type_Gen.Random(Gen => DCT_G); + + -- Substance type (acid or base) + Random_Substance_Type_Gen.Reset(Gen => ST_G); + Problem.Subst_Type := Random_Substance_Type_Gen.Random(Gen => ST_G); + -- What simplification to use + if (Problem.Parameters.No_Both_Simplifications = False) then + declare + package Random_Simplification_Gen is new Ada.Numerics.Discrete_Random(Result_Subtype => Simplification); + SIM_G: Random_Simplification_Gen.Generator; + begin + Random_Simplification_Gen.Reset(Gen => SIM_G); + Problem.Simpl := Random_Simplification_Gen.Random(Gen => SIM_G); + + -- Random dissociation constant + Problem.Kx := Random_Kx; + end; + else + declare + subtype Enforced_Simplification is Simplification range Autoprotolysis .. Dissociation; + package Random_Simplification_Gen is new Ada.Numerics.Discrete_Random(Result_Subtype => Enforced_Simplification); + SIM_G: Random_Simplification_Gen.Generator; + begin + Random_Simplification_Gen.Reset(Gen => SIM_G); + Problem.Simpl := Random_Simplification_Gen.Random(Gen => SIM_G); + + Problem.Kx := Random_Kx_Enforced(Problem.Simpl); + -- Generate dissociation constant that fits the enforced simplification mode + end; + end if; + + Calculate_Concentration_Limits(cX_Min, cX_Max, Problem.Kx, Problem.Simpl); + Problem.cX := Random_cX(cX_Min, cX_Max); + Problem.Unlock_State; + end New_Problem; + + function Set_Parameters(Problem: in out Acidobazic_Problem; Parameters: in Parameters_Info.Map) return Boolean is + use Parameters_Info; + begin + Problem.Lock_State; + if Parameters.Find(PARAMETER_NO_BOTH_SIMPLIFICATIONS_KEY) = Parameters_Info.No_Element then + Problem.Parameters.No_Both_Simplifications := False; + else + Problem.Parameters.No_Both_Simplifications := True; + end if; + + Problem.Unlock_State; + return True; + end Set_Parameters; + -- END: Inherited functions + + -- BEGIN: Private functions + function Autoprotolysis_Limit(Kx: in pH_Float) return pH_Float is + Ratio: constant pH_Float := 2.0E-13; + begin + return Ratio / Kx; + end Autoprotolysis_Limit; + + function Dissociation_Limit(Kx: in pH_Float) return pH_Float is + Ratio_Squared: constant pH_Float := 0.0025; + begin + return Kx / Ratio_Squared; + end Dissociation_Limit; + + procedure Calculate_Concentration_Limits(Min: out pH_Float; Max: out pH_Float; Kx: in pH_Float; S: in Simplification) is + begin + case S is + -- We are ignoring autoprotolysis but taking dissociation into account + when Autoprotolysis => + Min := Autoprotolysis_Limit(Kx); + Max := Dissociation_Limit(Kx); + -- We are ignoring dissociation but taking autoprotolysis into account + when Dissociation => + Min := Dissociation_Limit(Kx); + Max := Autoprotolysis_Limit(Kx); + when Both => + declare + ATPR: constant pH_Float := Autoprotolysis_Limit(Kx); + Dissoc: constant pH_Float := Dissociation_Limit(Kx); + begin + Min := pH_Float'Max(ATPR, Dissoc); + Max := Min + MAX_CONCENTRATION_DIFF; + end; + end case; + + -- Apply hard limit on minimum concentration, approximation we use does not work reliably below this limit + if Min < CONCENTRATION_HARD_MIN_LIMIT then + Min := CONCENTRATION_HARD_MIN_LIMIT; + else + Min := Correct_Up(Min); + end if; + Max := Correct_Down(Max); + --Ada.Text_IO.Put_Line("Min: " & pH_Float'Image(Min) & " Max: " & pH_Float'Image(Max) & " Simpl: " & Simplification'Image(S)); + end Calculate_Concentration_Limits; + + function Calculate_Solution(Problem: in Acidobazic_Problem) return pH_Float is + package MEF is new Ada.Numerics.Generic_Elementary_Functions(pH_Float); + use MEF; + + package pH_Float_IO is new Ada.Text_IO.Float_IO(pH_Float); + use pH_Float_IO; + use Ada.Text_IO; + begin + -- DEBUG Dump + --Put("* Kx = "); Put(Problem.Kx); + --Put(" | cX = "); Put(Problem.cX); + --case Problem.Subst_Type is + -- when Acid => + -- Put_Line(" | ACID"); + -- when Base => + -- Put_Line(" | BASE"); + -- end case; + -- END DEBUG Dump + + case Problem.Simpl is + -- We are ignoring everything + when Both => + declare + pH: constant pH_Float := X_To_pX(Sqrt(Problem.Kx * Problem.cX)); + begin + --Ada.Text_IO.Put_Line("Both simplifications"); + case Problem.Subst_Type is + when Acid => + return pH; + when Base => + return 14.0 - pH; + end case; + end; + -- We are ignoring autoprotolysis and taking dissociation into account + when Autoprotolysis => + declare + D: pH_Float; + X_1: pH_Float; + X_2: pH_Float; + pH: pH_Float; + begin + --Ada.Text_IO.Put_Line("Taking only dissociation into account"); + -- Solve the quadratic equation + D := (Problem.Kx ** 2.0) + (4.0 * Problem.Kx * Problem.cX); + if D < 0.0 then + raise Constraint_Error; + end if; + D := MEF.Sqrt(D); + X_1 := (-Problem.Kx + D) / 2.0; + X_2 := (-Problem.Kx - D) / 2.0; + pH := X_To_pX(pH_Float'Max(X_1, X_2)); + if Problem.Subst_Type = Base then + return 14.0 - pH; + else + return pH; + end if; + end; + -- We are ignoring dissociation and taking autoprotolysis into account + when Dissociation => + declare + pH: constant pH_Float := X_To_pX(Sqrt(Problem.Kx * Problem.cX + K_W)); + begin + --Ada.Text_IO.Put_Line("Taking only autoprotolysis into account"); + case Problem.Subst_Type is + when Acid => + return pH; + when Base => + return 14.0 - pH; + end case; + end; + end case; + + end Calculate_Solution; + + function Correct_Down(Num: in pH_Float) return pH_Float is + package MEF is new Ada.Numerics.Generic_Elementary_Functions(pH_Float); + use MEF; + + F: constant pH_Float := Correction_Exponent(Num); + begin + return Num - 10.0 ** F; + end Correct_Down; + + function Correct_Up(Num: in pH_Float) return pH_Float is + package MEF is new Ada.Numerics.Generic_Elementary_Functions(pH_Float); + use MEF; + + F: constant pH_Float := Correction_Exponent(Num); + begin + return Num + 10.0 ** F; + end Correct_Up; + + function Correction_Exponent(Num: in pH_Float) return pH_Float is + package MEF is new Ada.Numerics.Generic_Elementary_Functions(pH_Float); + use MEF; + + F_Log_Num: constant pH_Float := pH_Float'Floor(Log(Base => 10.0, X => Num)); + F_Log_Dec: constant pH_Float := pH_Float'Floor(Log(Base => 10.0, X => Decimals)); + begin + return F_Log_Num - F_Log_Dec; + end Correction_Exponent; + + function pX_To_X(pX: in pH_Float) return pH_Float is + package MEF is new Ada.Numerics.Generic_Elementary_Functions(pH_Float); + use MEF; + begin + return 10.0 ** (-pX); + end pX_To_X; + + function Random_cX(Min: in pH_Float; Max: in pH_Float) return pH_Float is + package FH is new Formatting_Helpers(pH_Float); + package MEF is new Ada.Numerics.Generic_Elementary_Functions(pH_Float); + use MEF; + use Ada.Numerics.Float_Random; + + Seed: Generator; + Rand: Float; + cX_Rand: pH_Float; + cX: pH_Float; + Log_Min: constant pH_Float := Log(Base => 10.0, X => Min); + Log_Max: constant pH_Float := Log(Base => 10.0, X => Max); + Concentration_Scale: constant pH_Float := Log_Max - Log_Min; + begin + Reset(Gen => Seed); + Rand := Random(Gen => Seed); + cX_Rand := pH_Float(Rand); + + cX := (Concentration_Scale * cX_Rand) + Log_Min; + cX := 10.0 ** cX; + return FH.Round_To_Valid_Nums(cX, Decimals); + end Random_cX; + + function Random_Kx return pH_Float is + package FH is new Formatting_Helpers(pH_Float); + use Ada.Numerics.Float_Random; + + type pH_Region is (Acidic, Basic); + package pH_Region_Random is new Ada.Numerics.Discrete_Random(Result_Subtype => pH_Region); + + pH_Seed: pH_Region_Random.Generator; + Seed: Generator; + Region: pH_Region; + Rand: Float; + pH_Rand: pH_Float; + pKx: pH_Float; + + Acid_Scale: constant pH_Float := Acidic_Max_pH - Acidic_Min_pH; + Base_Scale: constant pH_Float := Basic_Max_pH - Basic_Min_pH; + + begin + pH_Region_Random.Reset(Gen => pH_Seed); + Region := pH_Region_Random.Random(Gen => pH_Seed); + + Reset(Seed); + Rand := Random(Gen => Seed); + pH_Rand := pH_Float(Rand); + + -- There is no need to normalize the scale because the generator already returns + -- a normalized range + case Region is + when Acidic => + pKx := (Acid_Scale * pH_Rand) + Acidic_Min_pH; + when Basic => + pKx := (Base_Scale * pH_Rand) + Basic_Min_pH; + end case; + + pKx := FH.Round_To_Valid_Nums(pKx, Decimals); + return pX_To_X(pKx); + end Random_Kx; + + function Random_Kx_Enforced(S: in Simplification) return pH_Float is + package FH is new Formatting_Helpers(pH_Float); + use Ada.Numerics.Float_Random; + + Seed: Generator; + Rand: Float; + pH_Rand: pH_Float; + pKx: pH_Float; + begin + Reset(Seed); + Rand := Random(Seed); + pH_Rand := pH_Float(Rand); + + case S is + -- Ignore autoprotolysis, enforce dissociation + when Autoprotolysis => + declare + Scale: constant pH_Float := Acidic_Max_pH - Acidic_Min_pH; + begin + -- pKa and pKb between <2.0; 7.5> can overpower autoprotolysis and might require dissociation + pKx := (Scale * pH_Rand) + Acidic_Min_pH; + end; + -- Ignore dissociation, enforce autoprotolysis + when Dissociation => + -- pKa and pKb between <7.6; 12> do not require dissociation but might require autoprotolysis + declare + Scale: constant pH_Float := Basic_Max_pH - Basic_Min_pH; + begin + pKx := (Scale * pH_Rand) + Basic_Min_pH; + end; + when others => + raise Constraint_Error; + end case; + + pKx := FH.Round_To_Valid_Nums(pKx, Decimals); + return pX_To_X(pKx); + end Random_Kx_Enforced; + + function X_To_pX(X: in pH_Float) return pH_Float is + package MEF is new Ada.Numerics.Generic_Elementary_Functions(pH_Float); + begin + return MEF.Log(Base => 10.0, X => X) * (-1.0); + end X_To_pX; + +end Acidobazic_Suite; diff --git a/src/problem_generators/problem_generator.adb b/src/problem_generators/problem_generator.adb new file mode 100644 index 0000000..16d1960 --- /dev/null +++ b/src/problem_generators/problem_generator.adb @@ -0,0 +1,37 @@ +with Ada.Text_IO; + +package body Problem_Generator is + + protected body Problem_Mutex is + entry Lock when Locked = False is + begin + Locked := True; + end Lock; + + procedure Unlock is + begin + Locked := False; + end Unlock; + end Problem_Mutex; + + function Get_Problem(P_Type: in Problem_Type) return access Chem_Problem'Class is + begin + case P_Type is + when Acidobazic => + return Acidobazic_Suite.Create; + end case; + end Get_Problem; + + procedure Lock_State(Problem: in out Chem_Problem) is + begin + Problem.Mutex.Lock; + end Lock_State; + + procedure Unlock_State(Problem: in out Chem_Problem) is + begin + Problem.Mutex.Unlock; + end Unlock_State; + + package body Acidobazic_Suite is separate; + +end Problem_Generator; diff --git a/src/problem_generators/problem_generator.ads b/src/problem_generators/problem_generator.ads new file mode 100644 index 0000000..c98e408 --- /dev/null +++ b/src/problem_generators/problem_generator.ads @@ -0,0 +1,97 @@ +with Global_Types; +with Problem_Generator_Syswides; + +use Global_Types; +use Problem_Generator_Syswides; +package Problem_Generator is + + protected type Problem_Mutex is + entry Lock; + procedure Unlock; + + private + Locked: Boolean := False; + end Problem_Mutex; + + type Chem_Problem is abstract tagged limited private; + + function Create return access Chem_Problem is abstract; + function Check_Answer(Problem: in out Chem_Problem; Answer: in Answer_Info.Map; Message: out UB_Text) return Answer_RetCode is abstract; + function Get_Assignment(Problem: in out Chem_Problem; Assignment: in out Assignment_Info.Map) return Boolean is abstract; + function Get_Parameters(Problem: in out Chem_Problem; Parameters: out Parameters_Info.Map) return Boolean is abstract; + procedure New_Problem(Problem: in out Chem_Problem) is abstract; + procedure Lock_State(Problem: in out Chem_Problem); + function Set_Parameters(Problem: in out Chem_Problem; Parameters: in Parameters_Info.Map) return Boolean is abstract; + procedure Unlock_State(Problem: in out Chem_Problem); + + function Get_Problem(P_Type: in Problem_Type) return access Chem_Problem'Class; + +private + type Chem_Problem is abstract tagged limited + record + Mutex: Problem_Mutex; + end record; + + package Acidobazic_Suite is + use Problem_Generator_Syswides.Acidobazic_Suite; + + type Acidobazic_Problem is new Problem_Generator.Chem_Problem with private; + -- Constructor + function Create return access Acidobazic_Problem; + -- Inherited + function Check_Answer(Problem: in out Acidobazic_Problem; Answer: in Answer_Info.Map; Message: out UB_Text) return Answer_RetCode; + procedure New_Problem(Problem: in out Acidobazic_Problem); + function Get_Assignment(Problem: in out Acidobazic_Problem; Assignment: in out Assignment_Info.Map) return Boolean; + function Get_Parameters(Problem: in out Acidobazic_Problem; Parameters: out Parameters_Info.Map) return Boolean; + function Set_Parameters(Problem: in out Acidobazic_Problem; Parameters: in Parameters_Info.Map) return Boolean; + + private + type pH_Float is digits 15; + -- Present dissociation constant as pKa or pKb? + type Dissociation_Constant_Type is (pKa, pKb); + -- Is the substance acidic or basic? + type Substance_Type is (Acid, Base); + + type Acidobazic_Parameters is + record + No_Both_Simplifications: Boolean; + end record; + + function Autoprotolysis_Limit(Kx: in pH_Float) return pH_Float; + procedure Calculate_Concentration_Limits(Min: out pH_Float; Max: out pH_Float; Kx: in pH_Float; S: in Simplification); + function Calculate_Solution(Problem: in Acidobazic_Problem) return pH_Float; + function Correct_Down(Num: in pH_Float) return pH_Float; + function Correct_Up(Num: in pH_Float) return pH_Float; + function Correction_Exponent(Num: in pH_Float) return pH_Float; + function Dissociation_Limit(Kx: in pH_Float) return pH_Float; + function pX_To_X(pX: in pH_Float) return pH_Float; + function Random_cX(Min: in pH_Float; Max: in pH_Float) return pH_Float; + function Random_Kx return pH_Float; + function Random_Kx_Enforced(S: in Simplification) return pH_Float; + function X_To_pX(X: in pH_Float) return pH_Float; + + type Acidobazic_Problem is new Problem_Generator.Chem_Problem with + record + Answer: pH_Float; + cX: pH_Float; + DCT: Dissociation_Constant_Type; + Kx: pH_Float; + Parameters: Acidobazic_Parameters; + Simpl: Simplification; + Subst_Type: Substance_Type; + end record; + + Acidic_Min_pH: constant pH_Float := 1.75; + Acidic_Max_pH: constant pH_Float := 5.75; + Basic_Min_pH: constant pH_Float := 9.5; + Basic_Max_pH: constant pH_Float := 12.5; + K_W: constant pH_Float := 1.0E-14; + -- Maximum concentration can be only 1.5 mol/dm3 higher than minimum concentration + MAX_CONCENTRATION_DIFF: constant pH_Float := 0.75; + CONCENTRATION_HARD_MIN_LIMIT: constant pH_Float := 1.0E-6; + Decimals: constant pH_Float := 1.0E3; + Precision: constant pH_Float := 1.0E-3; + + end Acidobazic_Suite; + +end Problem_Generator; diff --git a/src/problem_generators/problem_generator_syswides.ads b/src/problem_generators/problem_generator_syswides.ads new file mode 100644 index 0000000..86da247 --- /dev/null +++ b/src/problem_generators/problem_generator_syswides.ads @@ -0,0 +1,41 @@ +with Ada.Containers.Indefinite_Ordered_Maps; + +package Problem_Generator_Syswides is + + type Answer_RetCode is (Invalid_Answer, No_Answer, Correct_Answer, Wrong_Answer, Malformed_Answer); + type Problem_Type is (Acidobazic); + package Answer_Info is new Ada.Containers.Indefinite_Ordered_Maps(String, String); + package Assignment_Info is new Ada.Containers.Indefinite_Ordered_Maps(String, String); + package Parameters_Info is new Ada.Containers.Indefinite_Ordered_Maps(String, String); + + ANSWER_KIND_KEY: constant String := "ANSWER_KIND"; + ANSWER_KIND_GOOD: constant String := "answer_kind_good"; + ANSWER_KIND_BAD: constant String := "answer_kind_bad"; + ANSWER_MESSAGE_KEY: constant String := "ANSWER_MESSAGE"; + ANSWER_SECTION_KEY: constant String := "ANSWER_SECTION"; + PROBLEM_TYPE_KEY: constant String := "PROBLEM_TYPE"; + PROBLEM_TYPE_ACIDOBAZIC: constant String := Problem_Type'Image(Acidobazic); + + package Acidobazic_Suite is + -- What effect to ignore in calculations? + type Simplification is (Autoprotolysis, Dissociation, Both); + + PROBLEM_NAME_READABLE: constant String := "pH jednosytné kyseliny/báze"; + CONCENTRATION_INT_KEY: constant String := "CONCENTRATION_INT"; + CONCENTRATION_DEC_KEY: constant String := "CONCENTRATION_DEC"; + CONCENTRATION_EXP_KEY: constant String := "CONCENTRATION_EXP"; + PKX_KEY: constant String := "PKX"; + PKX_PKA_TEXT: constant String := "pKa"; + PKX_PKB_TEXT: constant String := "pKb"; + PKX_VALUE_DEC_KEY: constant String := "PKX_VALUE_DEC"; + PKX_VALUE_INT_KEY: constant String := "PKX_VALUE_INT"; + SIMPLIFICATION_KEY: constant String := "SIMPLIFICATION"; + SUBSTANCE_KEY: constant String := "SUBSTANCE"; + -- + ANSWER_PH_KEY: constant String := "ANSWER_PH"; + ANSWER_SIMPLIFICATION_KEY: constant String := "ANSWER_SIMPLIFICATION"; + -- + PARAMETER_NO_BOTH_SIMPLIFICATIONS_KEY: constant String := "PARAMETER_NO_BOTH_SIMPLIFICATIONS"; + end Acidobazic_Suite; + +end Problem_Generator_Syswides; diff --git a/src/problem_manager.adb b/src/problem_manager.adb new file mode 100644 index 0000000..d7bb1fd --- /dev/null +++ b/src/problem_manager.adb @@ -0,0 +1,237 @@ +with Ada.Text_IO; +with Face_Generator; + +package body Problem_Manager is + + function Display_Checked_Answer(UID: in Unique_ID; Answer: in Problem_Generator_Syswides.Answer_Info.Map; HTML: out HTML_Code) return Boolean is + Answer_Message: UB_Text; + ARC: Problem_Generator_Syswides.Answer_RetCode; + Assignment: Problem_Generator_Syswides.Assignment_Info.Map; + Parameters: Problem_Generator_Syswides.Parameters_Info.Map; + Success: Boolean; + USD: User_Session_Data_Access; + begin + USD := Active_Sessions.Get_Session_Data(UID, Success); + if Success = False then + return False; + end if; + + Success := USD.Problem.Get_Parameters(Parameters); + if Success = False then + -- TODO: Handle error in a better way + return False; + end if; + Success := USD.Problem.Get_Assignment(Assignment); + if Success = False then + -- TODO: Handle error in a better way + return False; + end if; + + ARC := USD.Problem.Check_Answer(Answer, Answer_Message); + return Face_Generator.Generate_Face_With_Answer(Assignment => Assignment, Answer_Message => Answer_Message, + Answer_Code => ARC, HTML => HTML, + Parameters => Parameters); + end Display_Checked_Answer; + + function Display_New_Assignment(UID: in Unique_ID; HTML: out HTML_Code) return Boolean is + Assignment: Problem_Generator_Syswides.Assignment_Info.Map; + Parameters: Problem_Generator_Syswides.Parameters_Info.Map; + USD: User_Session_Data_Access; + Success: Boolean; + begin + USD := Active_Sessions.Get_Session_Data(UID, Success); + if Success = False then + return False; + end if; + + -- Get default problem parameters + Success := USD.Problem.Get_Parameters(Parameters); + if Success = False then + return False; + end if; + -- Get assignment + Success := USD.Problem.Get_Assignment(Assignment); + if Success = False then + return False; + end if; + + return Face_Generator.Generate_Face(Assignment => Assignment, HTML => HTML, Parameters => Parameters); + end Display_New_Assignment; + + function Display_Next_Assignment(UID: in Unique_ID; Problem_Parameters: in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean is + Assignment: Problem_Generator_Syswides.Assignment_Info.Map; + Success: Boolean; + USD: User_Session_Data_Access; + begin + USD := Active_Sessions.Get_Session_Data(UID, Success); + if Success = False then + return False; + end if; + + -- Set parameters before creating a new problem + Success := USD.Problem.Set_Parameters(Problem_Parameters); + if Success = False then + -- TODO: Handle error + return False; + end if; + + -- Create new problem and display it + USD.Problem.New_Problem; + Success := USD.Problem.Get_Assignment(Assignment); + if Success = False then + -- TODO: Handle error in a better way + return False; + end if; + + return Face_Generator.Generate_Face(Assignment => Assignment, HTML => HTML, Parameters => Problem_Parameters); + end Display_Next_Assignment; + + function Get_UID(Raw_UID: in String; UID: out Unique_ID) return Boolean is + begin + begin + UID := Unique_ID'Value(Raw_UID); + return Active_Sessions.Contains(UID); + exception + when Constraint_Error => + return False; + end; + end Get_UID; + + function Prepare_Problem(UID: in Unique_ID; P_Cat: in Problem_Category) return Boolean is + New_USD: User_Session_Data_Access; + Success: Boolean; + begin + New_USD := new User_Session_Data; + New_USD.P_Cat := P_Cat; + + case P_Cat is + when Acidobazic => + New_USD.Problem := Problem_Generator.Get_Problem(Problem_Generator_Syswides.Acidobazic); + when others => + return False; + end case; + + New_USD.Problem.New_Problem; + + Active_Sessions.Set_Session_Data(UID, New_USD, Success); + return Success; + end Prepare_Problem; + + function Register_UID(UID: out Unique_ID) return Boolean is + Success: Boolean; + begin + -- Raw UID is empty, assuming a new session + Active_Sessions.Register_UID(UID, Success); + return Success; + end Register_UID; + + procedure Session_Expired(SID: in AWS.Session.ID) is + Raw_UID: constant String := AWS.Session.Get(SID, "UID"); + UID: Unique_ID; + Success: Boolean; + begin + Success := Get_UID(Raw_UID, UID); + if Success = False then + Ada.Text_IO.Put_Line("Session expired: No such session"); + return; + end if; + + Active_Sessions.Remove_Session(UID); + Ada.Text_IO.Put_Line("Session expired: Session " & Raw_UID & " data deleted"); + end Session_Expired; + + -- BEGIN: Private functions + protected body Active_Sessions is + + function Contains(UID: in Unique_ID) return Boolean is + use Session_Keeping; + begin + if Sessions.Find(UID) = Session_Keeping.No_Element then + return False; + else + return True; + end if; + end Contains; + + procedure Check_Free_And_Register(UID: in Unique_ID; Success: out Boolean; Stop: out Boolean) is + use Session_Keeping; + + C: Session_Keeping.Cursor; + begin + Success := False; + if Sessions.Find(UID) = Session_Keeping.No_Element then + -- We have a free slot + Sessions.Insert(UID, null, C, Success); + if Success then + Last_UID := UID; + -- Registration successful + Stop := True; + return; + else + -- Registration failed + Stop := True; + return; + end if; + end if; + -- Slot occupied, keep looking + Stop := False; + end Check_Free_And_Register; + + function Get_Session_Data(UID: in Unique_ID; Success: out Boolean) return User_Session_Data_Access is + use Session_Keeping; + begin + if Sessions.Find(UID) = Session_Keeping.No_Element then + Success := False; + return null; + else + Success := True; + return Sessions.Element(UID); + end if; + end Get_Session_Data; + + procedure Set_Session_Data(UID: in Unique_ID; USD: in User_Session_Data_Access; Success: out Boolean) is + use Session_Keeping; + begin + if Sessions.Find(UID) = Session_Keeping.No_Element then + Success := False; + else + Sessions.Replace(UID, USD); + Success := True; + end if; + end Set_Session_Data; + + procedure Register_UID(UID: out Unique_ID; Success: out Boolean) is + Stop: Boolean; + begin + -- Look for an available UID slot + for Idx in Last_UID .. Unique_ID'Last loop + Check_Free_And_Register(Idx, Success, Stop); + if Stop then + UID := Idx; + return; + end if; + end loop; + + -- We found no free slot above, search the area below Last_UID + for Idx in Unique_ID'First .. Last_UID loop + Check_Free_And_Register(Idx, Success, Stop); + if Stop then + UID := Idx; + return; + end if; + end loop; + + -- There are no free slots available + Success := False; + end Register_UID; + + procedure Remove_Session(UID: in Unique_ID) is + use Session_Keeping; + begin + Sessions.Delete(UID); + end Remove_Session; + + end Active_Sessions; + +end Problem_Manager; diff --git a/src/problem_manager.ads b/src/problem_manager.ads new file mode 100644 index 0000000..f717374 --- /dev/null +++ b/src/problem_manager.ads @@ -0,0 +1,46 @@ +with Global_Types; +with Problem_Generator; +with Problem_Generator_Syswides; +with Ada.Containers.Ordered_Maps; +with AWS.Session; + +use Global_Types; +package Problem_Manager is + + type Problem_Category is (Invalid, Acidobazic); + + function Display_Checked_Answer(UID: in Unique_ID; Answer: in Problem_Generator_Syswides.Answer_Info.Map; HTML: out HTML_Code) return Boolean; + function Display_New_Assignment(UID: in Unique_ID; HTML: out HTML_Code) return Boolean; + function Display_Next_Assignment(UID: in Unique_ID; + Problem_Parameters: in Problem_Generator_Syswides.Parameters_Info.Map; + HTML: out HTML_Code) return Boolean; + function Get_UID(Raw_UID: in String; UID: out Unique_ID) return Boolean; + function Prepare_Problem(UID: in Unique_ID; P_Cat: in Problem_Category) return Boolean; + function Register_UID(UID: out Unique_ID) return Boolean; + procedure Session_Expired(SID: AWS.Session.ID); + +private + type User_Session_Data is + record + P_Cat: Problem_Category; + Problem: access Problem_Generator.Chem_Problem'Class; + end record; + type User_Session_Data_Access is access all User_Session_Data; + + package Session_Keeping is new Ada.Containers.Ordered_Maps(Key_Type => Unique_ID, Element_Type => User_Session_Data_Access); + + protected Active_Sessions is + function Contains(UID: in Unique_ID) return Boolean; + function Get_Session_Data(UID: in Unique_ID; Success: out Boolean) return User_Session_Data_Access; + procedure Set_Session_Data(UID: in Unique_ID; USD: in User_Session_Data_Access; Success: out Boolean); + procedure Register_UID(UID: out Unique_ID; Success: out Boolean); + procedure Remove_Session(UID: in Unique_ID); + + private + procedure Check_Free_And_Register(UID: in Unique_ID; Success: out Boolean; Stop: out Boolean); + + Sessions: Session_Keeping.Map; + Last_UID: Unique_ID := Unique_ID'First; + end Active_Sessions; + +end Problem_Manager; -- 2.43.5