From 7fd96b4696f0e7f4171197931f008be1dcbd7f69 Mon Sep 17 00:00:00 2001
From: Eilert Tunheim <emtunhei@stud.ntnu.no>
Date: Wed, 20 Apr 2022 10:20:12 +0200
Subject: [PATCH] Added confidence interval and reset the x-axis back to index
 instead of hours

---
 Bachelor_application.iml                      |   1 +
 pom.xml                                       |   7 ++
 .../GUI/LineChartFunctionality.java           | 119 ++++++++----------
 .../GUI/LineChartFunctionality.class          | Bin 5589 -> 7894 bytes
 4 files changed, 60 insertions(+), 67 deletions(-)

diff --git a/Bachelor_application.iml b/Bachelor_application.iml
index bcd84db..cde0e4e 100644
--- a/Bachelor_application.iml
+++ b/Bachelor_application.iml
@@ -52,5 +52,6 @@
     <orderEntry type="library" name="Maven: com.google.cloud:google-cloud-storage:2.4.0" level="project" />
     <orderEntry type="library" name="Maven: com.google.apis:google-api-services-storage:v1-rev20211201-1.32.1" level="project" />
     <orderEntry type="library" name="Maven: com.google.auto.value:auto-value-annotations:1.9" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" />
   </component>
 </module>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3126ade..014584e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,6 +29,13 @@
             <groupId>com.google.cloud</groupId>
             <artifactId>google-cloud-storage</artifactId>
         </dependency>
+        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-math3 -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math3</artifactId>
+            <version>3.6.1</version>
+        </dependency>
+
     </dependencies>
 
     <properties>
diff --git a/src/main/java/com/application/GUI/LineChartFunctionality.java b/src/main/java/com/application/GUI/LineChartFunctionality.java
index f4c249b..29d61ae 100644
--- a/src/main/java/com/application/GUI/LineChartFunctionality.java
+++ b/src/main/java/com/application/GUI/LineChartFunctionality.java
@@ -5,9 +5,11 @@ import javafx.scene.chart.LineChart;
 import javafx.scene.chart.NumberAxis;
 import javafx.scene.chart.XYChart;
 
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Map;
+import org.apache.commons.math3.distribution.TDistribution;
+import org.apache.commons.math3.exception.MathIllegalArgumentException;
+import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
+
+import java.util.*;
 
 public class LineChartFunctionality {
 
@@ -15,17 +17,18 @@ public class LineChartFunctionality {
     private static CategoryAxis xAxis;
     private static NumberAxis yAxis;
 
+    private static final double CONFIDENCE_INTERVAL = 0.90;
+
 
     public LineChartFunctionality() {
         xAxis = new CategoryAxis();
         yAxis = new NumberAxis();
         lineChart = new LineChart<>(xAxis, yAxis);
-        xAxis.setLabel("Hours");
+        xAxis.setLabel("Data Points");
         xAxis.setAnimated(false);
         yAxis.setLabel("Kwh");
         yAxis.setAnimated(false);
         lineChart.setTitle("Drying Processes");
-        lineChart.setCreateSymbols(false);
     }
 
     public static void setLineChart(LineChart<String, Number> lineChart) {
@@ -44,99 +47,81 @@ public class LineChartFunctionality {
         lineChart.getData().clear();
     }
 
-    private static double findDifference(String start_date, String end_date) {
-        // Defining a simple date format
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    private static Map<Integer, ArrayList<Double>> statistics(Map<Integer, ArrayList<Double>> multiMap){
 
-        try{
-            // try to convert the string to Date datatype
-            Date dateStart = dateFormat.parse(start_date);
-            Date dateEnd = dateFormat.parse(end_date);
+        System.out.println("\n\nMultimap: \n");
+        for (Map.Entry<Integer, ArrayList<Double>> entry : multiMap.entrySet()) {
+            System.out.printf("\nIndex: \t%s\t\t\tkWh: \t%s\n", entry.getKey(), entry.getValue());
 
-            // Finds the difference in millis
-            double differenceMillis = dateEnd.getTime() - dateStart.getTime();
+            if(entry.getValue().size()>1){
+                SummaryStatistics stats = new SummaryStatistics();
+                for (double val : entry.getValue()) {
+                    stats.addValue(val);
+                }
 
-           // Finds the difference in minutes
-            return (differenceMillis / (1000 * 60 )) % 60;
+                // Calculate 95% confidence interval
+                double ci = calcMeanCI(stats, CONFIDENCE_INTERVAL);
+                System.out.println(String.format("Mean: %f", stats.getMean()));
+                double lower = stats.getMean() - ci;
+                double upper = stats.getMean() + ci;
+                System.out.println(String.format("Confidence Interval 95%%: %f, %f", lower, upper));
 
-        } catch (Exception e) {
-            System.out.println(e.getMessage());
+                // Deletes entries if they are out of bounds with the confidence interval
+                entry.getValue().removeIf(value -> Double.compare(value, lower) < 0 || Double.compare(value, upper) > 0);
+            }
+        }
+        return multiMap;
+    }
+
+    private static double calcMeanCI(SummaryStatistics stats, double level) {
+        try {
+            // Create T Distribution with N-1 degrees of freedom
+            TDistribution tDist = new TDistribution(stats.getN() - 1);
+            // Calculate critical value
+            double critVal = tDist.inverseCumulativeProbability(1.0 - (1 - level) / 2);
+            // Calculate confidence interval
+            return critVal * stats.getStandardDeviation() / Math.sqrt(stats.getN());
+        } catch (MathIllegalArgumentException e) {
+            return Double.NaN;
         }
-        return 0;
     }
 
 
     public static LineChart<String, Number> loadSingleSeries(Map<Integer, Map<String, Number>> userInput) throws Exception {
         clearLineChart();
 
-        //Map<Integer, Map<String, Number>> kWh = userInput;
-        //Map<Integer, Map<String, Number>> kWh = DB.setInputParameters();
-        //System.out.println(kWh.size());
+        Map<Integer, ArrayList<Double>> multiMap = new HashMap<>();
 
         for (Map.Entry<Integer, Map<String, Number>> entryKwh : userInput.entrySet()) {
             Map data = entryKwh.getValue();
             //System.out.println(data.size());
             XYChart.Series<String, Number> newSeries = new XYChart.Series<String, Number>();
-            long minutes = 0;
-            long hours;
-            String previouseDate = "";
+            int index = 0;
             for (Object entryData : data.entrySet()) {
                 //System.out.println("data: \t"+entryData);
                 String entryString = entryData.toString();
                 String[] arr = entryString.split("=");
-                String currentDate = arr[0];
-                int kwhValue = Integer.parseInt(arr[1]);
-
-
-                //System.out.printf("previouse date: \t%s\n",previouseDate);
-                //System.out.printf("Current date: \t\t%s\n",currentDate);
-                //System.out.printf("is prev empty?: \t%s\n",previouseDate.isEmpty());
-
-
-                minutes += findDifference(previouseDate, currentDate);
-
-                hours = minutes/60;
-                System.out.println(hours);
-                previouseDate = currentDate;
+                String date = arr[0];
+                Double kwhValue = Double.parseDouble(arr[1]);
 
                 //System.out.printf("Date: \t%s\t\t\tkWh: \t%s\n",date,kwhValue);
-                //System.out.printf("Hours: \t\t%s\n",hours);
+
+                // Checks if the index already got an arraylist, if not one is created
+                multiMap.computeIfAbsent(index, k -> new ArrayList<Double>());
+                multiMap.get(index).add(kwhValue);
 
 
                 // Connect the data to a series
-                newSeries.getData().add(new XYChart.Data<String, Number>(String.valueOf(hours), kwhValue));
+                newSeries.getData().add(new XYChart.Data<String, Number>(String.valueOf(index), kwhValue));
+                index += 1;
             }
             updateLineChart(newSeries);
         }
-        return getLineChart();
-    }
-
-    public static LineChart<String, Number> loadMultipleSeries(Map<Integer, Map<String, Number>> userInput) throws Exception {
-
-        //Map<Integer, Map<String, Number>> kWh = DB.setInputParameters();
-        //System.out.println(kWh.size());
 
-        for (Map.Entry<Integer, Map<String, Number>> entryKwh : userInput.entrySet()) {
-            Map data = entryKwh.getValue();
-            //System.out.println(data.size());
+        Map<Integer, ArrayList<Double>> confidenceIntervalData = statistics(multiMap);
 
-            XYChart.Series<String, Number> newSeries = new XYChart.Series<String, Number>();
-            for (Object entryData : data.entrySet()) {
-                //System.out.println("data: \t"+entryData);
-                String entryString = entryData.toString();
-                String[] arr = entryString.split("=");
-                String date = arr[0];
-                int kwhValue = Integer.parseInt(arr[1]);
+        System.out.println(confidenceIntervalData);
 
-                //System.out.printf("Date: \t%s\t\t\tkWh: \t%s\n",date,kwhValue);
-
-
-                // Connect the data to a series
-                newSeries.getData().add(new XYChart.Data<String, Number>(date, kwhValue));
-
-            }
-            updateLineChart(newSeries);
-        }
         return getLineChart();
     }
 }
diff --git a/target/classes/com/application/GUI/LineChartFunctionality.class b/target/classes/com/application/GUI/LineChartFunctionality.class
index 8427e84169ee5bbc8f1c49de41a658e1d8cf6745..a7c6a67ecc6871b6999f49cf884d5f375c5ac204 100644
GIT binary patch
literal 7894
zcmX^0Z`VEs1_l$x0xkx5hTZH8d$<^w7<O|qXfy0(XV}NZzzX5)XJ<IT#lXRE5F~Sm
zgW)hp*%1(Nl!M_INZAn(ag>AMI7rzE5OI>5;S|GZ5OIc+;Vi>B4u<nw3>O$Kax=Iv
zT;gW9%y5O9;VQ#55OJNI;RXl8O?HM`+zhe|w?V`mklMTK4EMMglo{@`Gd$p8P-WN+
z;#^^8c*w<|!SDzq@|d0B37h?!Gazu5i$R;=DF?$dki>J4k{4VIFBx8OFuWFGU|{$N
z()@{?;WJ433m1bK!&f#31_%K0zJbKQb20p2_{q-ji>>|#OiG*KH#@^0E(Rx10R3fW
z_|L`Q3UZ1(BLfE`BNxL<MkX!>4Mt`zMixd^AqGZ9PHqMlMlKM+4YKnzNc~B6Mjnt-
zJ}!npMt&{^ZAJkQA;`sWfl&y=5(X*V4KhfCk%2QOGcVOSBeAH2kwMNUE3qswtwO&z
zIW;d;KN%#d?*o&!W@O+D&P>lsEGaEYWn^%{tJnrCtDlpYm#!aNQk0pOZjB`3SDKrY
zT4Zf!&B(x7;aHJb%*db!mPWJNIk6-)J-?_Dqy(fE%wS}Y$EFryCRC-cv%jC4r;Dqf
zvunJkUx;f^n4=FP1EUKg1Fv&_UU5lcUP)MDPH8G51Dj1|US^3MBLlOBW*8#_i*tTT
zDkB3w$kz}PLlTp6QW+V9eDaeMbHWmfGC_Q(AWKOG$n82l$@#hZi3J5YnaPPInfZD8
z?xCKDfO9L&O9qK1=46&sS~D{66sMMe%!h`$q6Rkaz!g}7oF#^8du2f?)CKPNH4_;P
zAS-y%QCy{<p^4vFj0|oBln`k-BLjbFK}uptD%{JA3~a%vMVYC^j0`-Ud3mWt&N+$2
z#UQR4R_{hcf`d{8q6r*eY{gIml(G2&O#>r?Ct+1Y`^JX|Hxg+WC|>xIb5awFkiv$m
zxFoS8v$!NPxtNhb0b-ecX-Q^|zHefIH6+#$d`1T5vcw!l23C-+VnzmApZub9{ltRA
z<cw7P<ow*+{JdiQ+{BU$V||bc{gl+=<f6=ilFYJH{ovBv+{B{FV7PtOj0{Z4nT!mq
zIr-(OMT`urr3D3GIyJAPsFIO^)fGaEf`b$82o<ofH6sH@ZfQ<Qrf*^aD2IW{8n_Z`
zMuuQ)nves^Gp{5yJ+%m0kbr&cSX7i)>62Mpf~?LZzceW))!GhRzWAb=imZ+-^LT?x
z5|gui6AQo@keyMKo#6&M!vjW!IcPCKp-pJ<NTF$r3|z^HImy1MiFwYRj0}z%6r}<e
zO&3N6))JS@;u1y%Q`|<SWEPhcWhRw^3P1f2ka8H$nvsD$xhNA<2s1J;rZO@(;x;9<
zB004HY=m!ONrq=mPHK8$j$=`JX>Mv>i7QMJR6XRRmZj#fGyGy?5Xi|-ObO1+OV3G#
z6bg(C`e^Y3%_(R#4m_V(Gcs_4Q#GXa0#yLu1dCjwF)}bG78Nlv2uDK_8LAu$s7zyI
z;K(k|fYvoaNXnpzl#ziGY>P``Ng}Ep{z+M>$tBi|3?Oq785ub9Qp;g}Va?1-Nv&XH
zVDw~U-~j9QF3(_O;4CdpE%MAOC@o=R5KGR_OUq12%}Y)NrSqb)#2knn@eoIVvyX3L
z0VHn{NM62)1;{EOK?2Q(U{6D;N@$jgM704~C&(#iS(p^F*%?5MB1Q%-c(fO@Gm0@X
z%twn`Dq0E2hqzJ+vPF18g^@urCowlEB~b-EU8)!|GH5|v4-QFK2tn+Hl#lRI&YF>d
zF`JP=7!trJ>KPe?p;my3aY*%`V!+5CuHgb{sX^Le&}63>#mK-~25A9t1?QI*C8xS&
zf|@zf@C<;`YS9CweIABXhBQV7c@P^dK#&`1JPdIR@r(?zSd>AM38;<6!;rv`$jBgr
zMG@QuJPfIf;yesd4AG1XEG~&9sXUAljFLQzQjF4!49wsZFW^#CnVFZa5Kxq#oC<0p
z@G#1Nl*ls5u`|l^Fe)%A@-Qkfq_Hz9^DwF~sxmV0a&h^BDxuuO0xJbBc1ATGMs<)5
z4Mt6NMlBvjZAKk-Mo}I{T}C|~Mtw#DMh0P!8zB(^&o7J&;#{7fJZPoBsanj*$(bFV
z0cLVBG6;axgChr$hj|zc84Y+CjX;DkqX~+M@TlcsG-WhnWU!{7c1E!p645-2=8P6R
zjFya6j0{@DH&S^RCNNB7WZ(eR7gh?YX*`V9AY*J8ZP^*^co^*&9T*uDoZ$tG0w~df
zN*D!8Q&m-v1|0>}G)4vir~Leq;*z4o0^ih<jQo^hel|u&0Y*iBHby4_MrR&I7e-ee
zMmI)xMh0V?{>EL2@GyEXdV&&(6%V5qqc;zu52FP;qc0DmAEP%TgFQ|YiK|d}7^*?3
zT@W0b;L_3~u{Z<VcHu>c`6d>yGx{?!D1wC0G6iZYlZP>YF_4jg(UylXh%uOlF@!Oc
zoiU7u(S|WxfH8ufjWJSyVKWb76k{|GV+>;~4`Uo-1oq4ZGZ#{xgK|b9$e4IgP$cj$
z6f>0ZFw`*Af)aKj4?_V%ArE5`V=^Oy5Xg;?gzQ<8T9jCl530Msg5cB&FH{*BOd&-Q
znukCc7gFkDlK?eVIY3>T#H7?5Mg~!c5s+dBS~h}|a2KbRIOb*MCYGe8Ffy=cM1cfB
z`a&{GKqU}+dTI&8XR3()MRI;lPAa$)RjluyRGeB=2I_@@8h+M@u*a<kR8gm-pt%W>
zIjzA(A-Ii=l7E6Li%U{-85x-KLA5EgL7SPcA5fH;R}x%Ol$w}p4a>=>!i)^;1t3K^
zd9cbkIJJb4K@jR2NCX6@f~t0;galT@kqM6%F%;#n_yV<?G7^jZQY%WpJ=Q2j29`W9
zM+BxG9F<VNF*2~Fr<Qo9Rx&bhq^E*fTi`A)*m-G;3^rJz2)QW(H3eD~q52ZkFe%Qg
zN(DQ^laYZt1>EX}wse>^KutuB#FP}M0E>nTD4s!)3o29?8Q8(KkAE5{Qe2=mfSnF0
zxU5kl13ZGj$iSACUzD3z!pLArlFPy7LX#jDV+vy`XcPo&Gg3tgwHK5eSV~esEn6iG
z7nsYy`9BTXkJk?<N=*Uv^ix4ynxfR){IXQfG)4w!M;;QvSQLYePERfI1BaOxBZEw4
zURi2UajJ7^ZfQ;;sLK^ll%JHClnELr0ObvELt7+0wIsMCF)t;tD8(hUEE7D!z{tRd
z$he?(F(U&@aUp0BM+zj4mOr5-1|tJUNj@YT7#UC#3`7*vh%PS3$t+=HP{$I&n#hR*
zu7kTEv8Xr|5`3W8L<&==8c<q*l%wF70M!=hpl+j>h9`2&L&dEb8KB*KNZEm!3c)5o
zg+S4m&d9(D8hNQ?WRQnt0o2%nHDs(A8Tga)a|=pKQa#fglZsRGN*EcOk-Z5u7UBUM
zh9El_s-BU7IUSV4MNz#2^*mZoLiIrGK=uqMd!Uq=P?aEYc96RhL8ET0pfu^p&X~c&
zn8}#K!?2BEI}gJqhRuu&N}za$gji-?S$=k^z7M#`>6==Tn3kAak`L;jbLS#>?2LJg
z3~WC6`Profj0|Ux0|IIQq}KLG%uC5hEmnc3gp~gfd!Rl<@(YT_kjjEoWaY$5LafBE
z7daxJCOaqQ<OFAyq*}8x<})&=Vgv{_|1dJh;aA6?$e_(2&%nUI!oUS;i!v}W#4s>0
zFfqh3aDiI73=9lhpvEi%1A`Pp5<@ZrBSQ*UUWb8;fsrAVA&r5NA)SGNVJ-s;10w?i
zgTK~x2FBeCOp)6em^U-9>}Ft%+|IzZnSp&b14ra029`|>?AsYQH#2Z;Vqg*C-p0VQ
ziGhWA8w2kq26iF7Z4CUI7}%M&F$ip8;1Uwt#vrs2Y*_{a1A`U=7Xt%>I0FlV1Op#~
zB!dKl6oUqXG=l+y41+m?EQ39R9D^H!JcA#D0z)PP0|O7lISg40*$fN}oD4Y(xeQET
zr@AsQg5_AXHZyR6EMR5;SpXV^XJAlfU|~=}GJq8vDhv#Ha3et?u?!5*aLQ-k1T{jS
zM$3Q=W&nAUYXbuV10&dEHn6BVR7Vj5C#V$z)#1m$1eWF7#K5(UL0D@ig9sBtIJ&E}
z8F(3VkX*&bP|i@nfb1%eNG04=RScY<1|BqSG{816fWjJN$4&-OMh1`#5Z6Kct<S*C
zV8FlyYFa^D#(0;39USC;HZh0^iEm?&(Avo$$;hyiL5h*VM|&rOG$X@Ih6ma^8Dtn4
z4lv02f|SXz?q!gVWY*cqpuorw9Lb`ylR=S@A$S{uQaFTpfI)d1g9^(Mh9$chR3o=D
zsQKxDm4L<7tym;kpax5WSYVSiwlQd0vg%5(Ze!3|{{Nw_2!qZJ20cp_kbU|rVEc5p
zF&OM%Fbv+oV6=@v0>qPGNw;K!Fp@1fKuV1vPLp5*DU4*1-~iDO4R9w&ut{+2VlZJ~
z*v4QQzWl$e7AOu`z;WorAOM=+Vc=jeWl(1@V{l`zU<hNdWQb$1VrXTsX6R<HVd!J9
zXPD05z_5YAkzpr;6T=|}XNHFiZVcZUJQ#j4c!J~4gMpo)m4S_+jv<+Wl_8E{CPO_#
z0|PU|bcO_mMuubt7KWV+b_`7n$qZ}^hZsy5ni-NAI2hC!E-<t(v@tL-FfjaKXlLkv
z#=RH=6I8AfF4x7tz|hUWz#z-O`iDV;ouTI+LpVD_FFQjYJ44Se2F0HY8NV2qpoy`c
zfeX|df+mzz4D1YSAaCwwFpJdM#$e8s+O?g*!cUi<Up-xf!7@L68-ukaiv)|-HU^t)
zkb<rq47T~}{X`h-*Mk$89)muE9Rnmoi7~K(3IqmzhCl{6hF}JLh7bk=hEQ-Ym@u$1
z@G~edOk$YKz|0`WAjmKU?0EwQ7KW(|(-@dRt^fxOX!gj2VLG_z0eOp!fia(*VFm*y
z!%T)*49uV$&E&wq%D~IO!0=!PgX3-nr^xLL&OS(~)mI0as5a}sQmdaXG}*aWvFv7W
zjoi-QX2r^E#s*4tutaXfF3AqnSOj9pFfv$iNOEjraJS-=<P`GQ#^7niCCSBXWW_DX
z#bUIB!D|PDp%ss|DBBJO?_CT&3=BINe3==-Ap$!Y{FoRHFepp%Y-8{bmt@_>5U`se
zFcPFVNRnqeLog)gx3DoT|KBCax`QEP`F}6%9SotMjHR=UA#4XjI3hWQF^DoSFhnyj
zGsH4*GsH7!F(fdUG9)qBF(flMGNdphF{CkMGNdyUF=Q}QFk~{cFyt{zWyoh($xzI2
zh@phxC_^d335GI;vkc`77Z@rTt};|J++nB%$4wOj4?`wH48v@OISiZ(MGV0Va~b9_
za4}Rc_%h6An8U!$(8A!zuz+D90}r@LSj4axoGoTBEMe$iU}3Oh=x12UP{F{;;K<O!
zu#90j0~>=D!(xUN3~k_eeZa61oPa?ymY}4}$Z(Z`n_(402Llthm<6Q`h#8d(tWce+
z;X2naFfc%Lb}_(ob}=x4le`53%U=d@c80a=3_brD%o#YqOb`#80oJlJtYc(n=wV>^
z#Zd8|ft`T~BG16UupV3mGBRv{=8KI2T%d*rG+!7&3u%ts49bz)86rUOzyvN9AVqr~
zINTsL4hO>)hOG<?0$iZh3sipw0}D7W$VswnW6)IF!4Nr{fgjA)*ufAvkAa<KIRgtb
z$Y+q812NSNY^nm-=W^g80hAZyz(oS6Adq9&!LX2l1?+P+24)Fn2Sx^FhMnLVa~DG?
z0|SE+0}F#I!#{=+21y241{a1_Hco~>hJTE_43dmljJb^243Z3Q7#J8p#mHL*W`=hR
S?-@QYyk%f!WMgD!<NyF|QgF}!

literal 5589
zcmX^0Z`VEs1_l#GPA&##hH2~!)43R!7^ZPDI5Es%XPC*wzzX5aVrQ7m#lXQZ2P89>
zgJB*>*?bVOfXyM20SrLAg&<-P2g72J&iNo>0SChpkj|wbVi|~7&dsobVI_!I#m=yr
zgJBI9gEYfh5V4MnVLihJHix%9U<274{J=B=JHtj!hD{8cxfoO!ws0|QW!T2W;KZ<<
zn?avp2RFk`hF#nYyBYR?h`sC#`?wi&8TPX?9AIZS#KmC1FpY!ZFvxR9I2ev{F&tw!
z&c$$o;Uvg_Q>+l<8Eimio(3si&dzWKr05(-(RnTg8-@!Yl@~$8C3c3(><m{J88~w?
z^HQBN5{pV08RUGj63Y_PD)ft!Q}a^wlR={TJ}`M}Mh4E{%=Em(lG377Mg|wWifzEM
z`Z<Yt>H5JXMVWc&)<`0LrMXF|Mb>uKj0~(5jun~3j0}okX*9c?6H8Ll^NT7$N<eDC
z3`PcdY-%B9LRGTaWaed-*fBCNYiNcsGO#%3r=&76@PqsUQ5TY!l#|NHAmo#uoR|}q
zSd<CkLj_q%GC(fU@k!3l)lV!a$jMAjEXmBz({~T`L<EOhX<jl&G%+W$q|%y^fu}gN
z1Y|xmEEP4dITEhG8ssc7RNE^HQlTzz$FG^lzy(>slaAsl1r1I7&SGS6BcOyx%NZH?
zOAAsGOH$!pW@KOsPA$qzEoNlk@yyFhEppCDEG`Cd)v$UuA`%>wDiBTJ0AnkL8la5L
z7ibz789WKABHA}TM7WVi!$9%Emz<NDScDWdd}*0^DK435X{kl2dC94a4C)XIab+QB
zrqXm_WZ(n^b8rbb3k!g~tY2D^nWOKLSdwbZ$iNPgcFjv+WDrP!o9~;MlLJcRj4q4}
zjH!$aq7Z%H{OVefoLT@%+SZH=T*V-B<3UD%ato+@KyeWx14n9J3RIp8q|7b9C^xZ$
zkwFGxVo7R6iGFZqZb42e$UP8QYeoj%;F84TY~REJaFS<dxXRA3nw?=IBZELrequ^+
zW?p(uDkSC^8T6q+0rr(|Vu3ZmM9iI<S5j072_r@Z=ER~RMh4+%h>O5}gh*L4GH@rC
z78RxDm4F<_$iR_Zo)MOqQ<}=i!05@yzzH_SC9xzC)${&IS*gh-pb{w~zqF{Bk%7^R
zk%1)ztd1XI3*4=Y44iqX<uI?X=Vs=WmVgpHZ$VLNS!RA|F*sbn;aE~s>0O?|$iV6f
zay>j!U`DBcg+aw|X>n?iXI?>R2_r*1s&x=G$O+LiuOu}+wFp}L!puTe0Sya?LEzAV
zl#$kU;1W6#B7sdOg=VueTmx5a``8%{F*0z$(_Jwm!+f+TM|LlTw&G7b$QI!V7)Aym
zP&)N3%_+$&K%`Vq8c1bi;0n$!ElN&x%LLUi((qi4Qf26Y@)8e&4}&iwgFJ|hr|#on
z@L=#{WRS(83=&nKYLAD(i@}?bK?aK=xC=n`UFTtNVQ^(+VD$i{T^@!T3^#cgZZX{E
zVYtI^mxtjV!(B!OW^g(ba4D+H%u81YD9TSx1r^mi4EI5bAAl4;WO&5R@R*0;3Byw!
zhG!sONP%qD$C}|88H6h<D=T$<eRWe(6g)hva&xVUi$Sir$;0rR;RQRxOCE+-46hj(
zgu#Y@Qz1M(urs{jVR*~%j)&nr!v}VTk30;YK+Z^F@a19n%<zSs;VTcrH-_)*4A*!V
zelYyxVfe-H8)gzL*+Mb~55pgj6ff8km=rt1Uq%K+kPuoFpjIf13=BLB{}?_nGBDb*
zGhF3i_|M3|&dA8a$i&FZ!;s97!o%>1kp+8X!c2k`B`hF{hmn<$4Mec>F!(TX@GwL%
z#PTqNGej^lXc6D=U}O*gIUG_ZLy9F7AxIKtWDo+Y0{hsrB(*59B)^D}!4#6o(0mO_
zOOVn6n*^xx;s7;u5|dJM7#Tz%MnLR`SO%`Cxr<Xv9P=`B6H8K47#UbJqCon1i&INN
zGLy4?QuESFGC;yEAYp!(Fvv*v!~#YJkOd)`C7|3RP@G!gT$Gwvk{Vo@o0OkZ%*eo=
zo(d|i6B!v)5p9*^{G6OraD%K^-#@81wWti#qVvft1{Kq=$i=M)R7s?yp!o+9p4OUC
zj0~*c`kIk}wIH#mIF*q>1)IN-3QACQV$I0FhX{6%2*m9nnYpRpmZ=wNR8<z2q~<a*
zFz18Hb!dg0nXeyEl$lo&TvC*pm<y_q(^E@)Q;Ule(^DB4M4<jh34U10N7c&6z+M0{
zC?^kA)dZ)OFfs_jOaLd1;8aj(A>|IR8jehOiV;Im4ogX(Mn*<rv0rLM3D{pzj0`M!
zU=FGSq2bBMz>%I>0<C7jg$7zegXR`S29A<^NSb0~U@b1l$t=O%EYieUvkQX59$b<@
zYYavP4si5&=9QquG{hEbO;1pw^a3SH_A*d2z&{P7&<j#!poOxSh9|N()DCMjCqn%U
zNg80sKt(je7!(<t7@Qdx7?>HjKt(bGBZC_Q0|OI-I|CP}z-M4!-~yHD3=9k$42+<f
zf`O3%)QddCz{0@Dz`&59wVi=+Hv?1Tb_V9n3@p1DSR=PHux(~w-_5`gxru>g69fBp
z2F}e4T$>nJgt)gc@N8mWVcy2TyNQ8Cgn@4xg8+;nxQT&XNN5{_@FoU!=4}ijn;5u+
zM7J@BZDQbJ-o_xl5$q~|1_lN*23`gR22lnU1~CRc25|-n1_=fY21y131}O$}25ANd
z1{nrV23dwc204ZZ26=`A1_g$621SMd22kM%b~8gDLl6T411Cc;LkI&C*gviej9~w;
zYHeoV0$Ik)0J03!{bpcLVPIiUMKXXD98?Spp>QKXBJK<f(7+30-~^STP@`qQ1~Y)d
zf@=c<0|O)2WHzv<22@8R11G5Ph3fEQU;@kXZDQcs#vq}!lR=V+AspRRIt;uFx=60#
zV~AsjXFzrpNF)L7sze4(h9m}GXas72ZD0UJ2*{3|3{s2?AR8dAgZSHkft$gQfeTcz
zKy^K2U}0cqU|_J>%^)2qB(t4C)>lVs8-tvmj`lVNIV%=PmTe63y4x7!Q@bo#C0Iom
z6!JwFl#18;ZDLT>-NvA{jX|AnJp&^H7lR&yK7$GaB$SmHI2afhOc}TsEExnCtQf=@
zY#CG-92oQ&oEQukoEfYbT*3bIU|?quV321>Wk_RS21k85LmC4Mg9-yTLk2@811p08
z0~bRULpB35$c+p+47uPD2^)qyhFq|%Mhy831q@6Kh2SK__@6<aftj752#ksu7}*(0
z7&sY98Oj)#L8*d?hk=zrfPsPG#0~}xt(^>-j0`&&v=|wDw0AOSGcwF%Sg*a4L5Gpy
z0E4ct&Q1nB5KG^0H-kasb_PQ$7EW_kAtNhJU4%v}E=ew^)*KK^hmpaGTatSlgRvEl
zB#)5EHU?8GUP)eNBP%{hUKXPr3}$BhVys<~oRWMy7|fIV%~{1*MHnn{&Dl3GsEV;~
zW3ZCsv*H8=y7g`bn@BPC9SpXj{5u%zwlmm663z1e6C_!7FgPs#@3(`&5tRH`z{xL&
zL4<*U!Ha>JA%H=NA&^0bA(+9GA%ww#A&kL>A)FzGA(A1PA&Mb~A)29xA%>xvA%&rn
zA&p@oLpsAuh75+e4EYQ@844KoFcdNzWGG@d!cYv3qACVZJ0y&uoFSKiharc-m!X28
zl7W|@h{2tqiXoSQkD;2uj-i^NhJl|Uh9Q=rmZ6S;n_(h@HbXr_0|PsQ4#QN2MusK^
zW(HG+9)@Oycm@^*3x-aH7KR1}Rt6h}R)$uFTn0`CC5HJ7Z44a@OrY3d=wxVuCVU0B
z&IAS)sH!fws%{1baMYVHu>4`*XJ_d7&tS>G#m>;n&d|fo&<AGpvoJ7#(?bsf!!L%S
z{|xM)bkWPez%YS<0bJ^-Kr`(`22O@au(ZNBmw}amn}LC0Cbopqj5nbmrw~?2R%i-w
z2eCjYgiVqSltS1g*&!)}Lz08p$cj^v1Cm0x;PEfXDawT!`CFlpUxgC+kRr62L7t(8
zL6f1L!HA)OA&8-gA(Ek)A&H@dA%mfnp^%}Qp^KrHp_ieLVG=_>!&HU|;0TUk;ADto
zFa$?1J3|tK1~`H_7%~`?7$$?GrjS7r9Kl=+K@1k)2xeo@WXOa@s}Vy2G+N~uis4Z>
z6D<lMsz6Z)Q3Z)YkSbUd>Vcw=2ONb)3~b<-Lqs7v132=0@kSmK!xV6(GL<2Qfq_Aa
eftkU8;UGgagCv74gFZt$8z+Me!$F3#43YrHI<CY3

-- 
GitLab