commit 4a77ce736bf43dea88d88e4f82abe70352b7d9ae
Author: Ondřej Váně <onry.vane@gmail.com>
Date:   Tue Apr 13 15:57:14 2021 +0200

    Long or non existant feedback loops

diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/LongOrNonExistentFeedbackLoopsDetectorImpl.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/LongOrNonExistentFeedbackLoopsDetectorImpl.java
index 18ed504..3e799bd 100644
--- a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/LongOrNonExistentFeedbackLoopsDetectorImpl.java
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/detecting/detectors/LongOrNonExistentFeedbackLoopsDetectorImpl.java
@@ -9,7 +9,9 @@ import cz.zcu.fav.kiv.antipatterndetectionapp.utils.Utils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.HashMap;
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -31,7 +33,7 @@ public class LongOrNonExistentFeedbackLoopsDetectorImpl extends AntiPatternDetec
     /**
      * Settings
      */
-    private final float DIVISION_LONG_OR_NON_FEEDBACK_ITERATIONS = (float) 1/3;
+    private final float DIVISION_LONG_OR_NON_FEEDBACK_ITERATIONS = (float) 1 / 3;
 
     /* Settings */
     private final int MINIMUM_NUMBER_OF_WIKI_PAGES = 1;
@@ -49,15 +51,15 @@ public class LongOrNonExistentFeedbackLoopsDetectorImpl extends AntiPatternDetec
 
     /**
      * Postup detekce:
-     *  A)
-     *      1) najít všechny aktivity, které by mohli indikovat schůzku se zákazníkem
-     *      2) udělat join na iterace
-     *      3) v ideálním případě by měla mít každá iterace jeden nalezený issue
-     *      4) pokud nebude nalezen žádný iisue, tak to znamená že tým nezaznamenává schůzky do úkolů => je nutné ještě prověřit wiki stránky
-     *      5) zkusit nalézt všechny wiki stránky, které by mohly souviset s demem
-     *      6) dle datumu úpravy udělat join na iterace (jedna wiki více schůzek)
-     *      7) v ideálním případě by měla mít opět každá iterace jeden záznam
-     *      8) dle prahových hodnot vyhodnotit výsledky
+     * A)
+     * 1) najít všechny aktivity, které by mohli představovat zákaznické demo (název bude obsahovat substring)
+     * 2) zjistit průměrnou délku iterací
+     * 3) zjistit počet iterací
+     * 4) nejprve porovnat s počtem iterací => iterace a nalezené aktivity by se měly ideálně rovnat (mohou být menší i větší ale né o moc menší)
+     * 5) u každých dvou po sobě jdoucích aktivitách udělat rozdíl datumů a porovnat s průměrnou délkou iterace => rozdíl by se neměl moc lišit od průěrné délky iterace
+     * 6) pokud u bodu 4) dojde k detekci máleho počtu nalezených aktivit (tým nedělá aktivity na schůzky a může zaznamenávat pouze do wiki)
+     * 7) najít všechny wiki stránky a udělat join kdy se měnily (může být použita jedná stránka pro více schůzek) s příslušným názvem
+     * 8) udělat group podle dne
      *
      * @param project            analyzovaný project
      * @param databaseConnection databázové připojení
@@ -66,39 +68,97 @@ public class LongOrNonExistentFeedbackLoopsDetectorImpl extends AntiPatternDetec
      */
     @Override
     public QueryResultItem analyze(Project project, DatabaseConnection databaseConnection, List<String> queries) {
-        Long totalNumberIterations = 0L;
-        Map<String, Integer> iterationsResults = new HashMap<>();
+        long totalNumberIterations = 0;
+        int averageIterationLength = 0;
+        int numberOfIterationsWitchContainsAtLeastOneActivityForFeedback = 0;
+        List<Date> feedbackActivityEndDates = new ArrayList<>();
+        Date projectStartDate = null;
+        Date projectEndDate = null;
 
         List<List<Map<String, Object>>> resultSets = databaseConnection.executeQueriesWithMultipleResults(project, queries);
         for (int i = 0; i < resultSets.size(); i++) {
             List<Map<String, Object>> rs = resultSets.get(i);
 
-            if (i == 0) {
-                totalNumberIterations = (Long) rs.get(0).get("numberOfIterations");
-            }
+            switch (i) {
+                case 0:
+                    totalNumberIterations = (long) rs.get(0).get("numberOfIterations");
+                    break;
+                case 1:
+                    averageIterationLength = ((BigDecimal) rs.get(0).get("averageIterationLength")).intValue();
+                    break;
+                case 2:
+                    if (rs.size() != 0) {
+                        numberOfIterationsWitchContainsAtLeastOneActivityForFeedback = ((Long) rs.get(0).get("totalCountOfIterationsWithFeedbackActivity")).intValue();
+                    }
+                    break;
+                case 3:
+                    Date activityEndDate;
+                    for (Map<String, Object> map : rs) {
+                        activityEndDate = (Date) map.get("endDate");
+                        feedbackActivityEndDates.add(activityEndDate);
+                    }
+                    break;
+                case 4:
+                    projectStartDate = (Date) rs.get(0).get("startDate");
+                    break;
+                case 5:
+                    projectEndDate = (Date) rs.get(0).get("endDate");
+                    break;
+                default:
 
-            if (i == 1) {
-                String iterationName;
-                for (Map<String, Object> map : rs) {
-                    iterationName = (String) map.get("iterationName");
-                    iterationsResults.put(iterationName, 1);
-                }
             }
         }
-        int minFeedbackLimit =  totalNumberIterations.intValue() - Math.round(totalNumberIterations * DIVISION_LONG_OR_NON_FEEDBACK_ITERATIONS);
 
-        List<ResultDetail> resultDetails = Utils.createResultDetailsList(
-                new ResultDetail("Project id", project.getId().toString()));
+        double halfNumberOfIterations = totalNumberIterations / 2.0;
+
+        // pokud je počet iterací, které obsahují alespoň jednu aktivitu s feedbackem, tak je to ideální případ
+        if (totalNumberIterations <= numberOfIterationsWitchContainsAtLeastOneActivityForFeedback) {
+            List<ResultDetail> resultDetails = Utils.createResultDetailsList(
+                    new ResultDetail("Number of iterations", Long.toString(totalNumberIterations)),
+                    new ResultDetail("Number of iterations with feedback loops", Integer.toString(numberOfIterationsWitchContainsAtLeastOneActivityForFeedback)),
+                    new ResultDetail("Conclusion", "In each iteration is at least one activity that represents feedback loop"));
+
+
+            return new QueryResultItem(this.antiPattern, false, resultDetails);
+
+            // pokud alespoň v polovině iteracích došlo ke kontaktu se zákazníkem => zkontrolovat rozestupy
+        } else if (feedbackActivityEndDates.size() > halfNumberOfIterations) {
+
+            Date firstDate = projectStartDate;
+            Date secondDate = null;
+
+            for (Date feedbackActivityDate : feedbackActivityEndDates) {
+                secondDate = feedbackActivityDate;
+                long daysBetween = Utils.daysBetween(firstDate, secondDate);
+                firstDate = secondDate;
 
-        if ((totalNumberIterations.intValue() != iterationsResults.size())) {
-            if (totalNumberIterations - iterationsResults.size() > minFeedbackLimit) {
-                return new QueryResultItem(this.antiPattern, true , resultDetails);
-            } else {
-                return new QueryResultItem(this.antiPattern, false , resultDetails);
+                if (daysBetween >= 2 * averageIterationLength) {
+                    List<ResultDetail> resultDetails = Utils.createResultDetailsList(
+                            new ResultDetail("Days between", Long.toString(daysBetween)),
+                            new ResultDetail("Average iteration length", Integer.toString(averageIterationLength)),
+                            new ResultDetail("Conclusion", "Customer feedback loop is too long"));
 
+
+                    return new QueryResultItem(this.antiPattern, true, resultDetails);
+                }
             }
+
+            // rozestupy feedbacků jsou ok
+            List<ResultDetail> resultDetails = Utils.createResultDetailsList(
+                    new ResultDetail("Average iteration length", Integer.toString(averageIterationLength)),
+                    new ResultDetail("Conclusion", "Customer feedback has been detected and there is not too much gap between them"));
+
+
+            return new QueryResultItem(this.antiPattern, false, resultDetails);
+
+            // bylo nalezeno příliš málo aktivit => zkusit se podívat ve wiki stránkách
         } else {
-            return new QueryResultItem(this.antiPattern, false , resultDetails);
+            // TODO udělat analýzu WIKI stránek
         }
+
+        List<ResultDetail> resultDetails = Utils.createResultDetailsList(
+                new ResultDetail("Project id", project.getId().toString()));
+
+        return new QueryResultItem(this.antiPattern, true, resultDetails);
     }
 }
diff --git a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/utils/Utils.java b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/utils/Utils.java
index 10e1f6a..a04a63b 100644
--- a/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/utils/Utils.java
+++ b/src/main/java/cz/zcu/fav/kiv/antipatterndetectionapp/utils/Utils.java
@@ -11,7 +11,6 @@ import java.time.LocalDate;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedList;
 import java.util.List;
 
 public class Utils {
diff --git a/src/main/resources/queries/long_or_non_existent_feedback_loops.sql b/src/main/resources/queries/long_or_non_existent_feedback_loops.sql
index 84980fc..51bbc2e 100644
--- a/src/main/resources/queries/long_or_non_existent_feedback_loops.sql
+++ b/src/main/resources/queries/long_or_non_existent_feedback_loops.sql
@@ -20,7 +20,14 @@ Detection: How to choose what is the optimal spacing between feedbacks? In ASWI,
 set @projectId = ?;
 /* Number of iterations for given project */
 select COUNT(id) as 'numberOfIterations' from iteration where superProjectId = @projectId;
-/* Select all iteration with detected retrospective activities */
-select  iterationName as 'iterationName', count(name) as 'numberOfIssues' from workunitview where projectId = @projectId and (name like "%schůz%zákazník%" OR name like "%předvedení%zákazník%" OR name LIKE "%zákazn%demo%" OR name like "%schůz%zadavat%" OR name like "%inform%schůz%") group by iterationName;
-/* Look up for wiki pages */
-
+/* Average iteration length */
+select avg(abs(dateDiff(iteration.endDate, iteration.startDate))) as 'averageIterationLength' from iteration where superProjectId = @projectId;
+/* Select number of iterations which contains at least one feedback activity */
+select count(*) over () as 'totalCountOfIterationsWithFeedbackActivity' from workunitview as wuv where wuv.projectId = @projectId and (wuv.name like "%schůz%zákazník%" OR wuv.name like "%předvedení%zákazník%" OR wuv.name LIKE "%zákazn%demo%" OR wuv.name like "%schůz%zadavat%" OR wuv.name like "%inform%schůz%") group by wuv.iterationName order by wuv.activityEndDate;
+/* Select all activities for feedback loop with last modified date as end date */
+select wuv.id, wuv.iterationName, wuv.name, cast(max(fieldchangeview.created) as date) as 'endDate' from workunitview as wuv inner join fieldchangeview on wuv.id = fieldchangeview.itemId where wuv.projectId = @projectId and (wuv.name like "%schůz%zákazník%" OR wuv.name like "%předvedení%zákazník%" OR wuv.name LIKE "%zákazn%demo%" OR wuv.name like "%schůz%zadavat%" OR wuv.name like "%inform%schůz%" OR wuv.name like "%zákazn%" OR wuv.name like "%zadavatel%") GROUP by id order by fieldchangeview.created;
+/* Get project start date */
+select startDate as 'projectStartDate' from iteration where superProjectId = @projectId order by startDate limit 1;
+/* Get project end date */
+select endDate as 'projectEndDate' from iteration where superProjectId = @projectId order by endDate desc limit 1;
+/* Select all wiki pages that can be notes about feedback loop */
