diff --git a/resources/reviewstats.csv b/resources/reviewstats.csv new file mode 100644 index 0000000..d51f61e --- /dev/null +++ b/resources/reviewstats.csv @@ -0,0 +1,313 @@ +20110930-12h00 6 +20111007-12h00 6 +20111014-12h00 8 +20111021-12h00 5 +20111028-12h00 10 +20111104-12h00 8 +20111111-12h00 3 +20111118-12h00 7 +20111125-12h00 7 +20111202-12h00 7 +20111209-12h00 3 +20111216-12h00 7 +20111223-12h00 7 +20111230-12h00 7 +20120106-12h00 8 +20120113-12h00 6 +20120120-12h00 7 +20120217-12h00 8 +20120302-12h00 10 +20120309-12h00 8 +20120316-12h00 3 +20120323-12h00 6 +20120330-12h00 6 +20120406-12h00 7 +20120413-12h00 7 +20120420-12h00 5 +20120504-12h00 8 +20120511-12h00 6 +20120518-12h00 4 +20120525-12h00 7 +20120601-12h00 3 +20120608-12h00 6 +20120615-12h00 7 +20120622-12h00 9 +20120629-12h00 4 +20120706-12h00 6 +20120720-12h00 8 +20120727-12h00 5 +20120803-12h00 6 +20120810-12h00 6 +20120817-12h00 6 +20120824-12h00 8 +20120831-12h00 10 +20120907-12h00 12 +20120914-12h00 8 +20120921-12h00 4 +20120928-12h00 8 +20121012-12h00 6 +20121019-12h00 7 +20121026-12h00 7 +20121102-12h00 8 +20121109-12h00 7 +20121116-12h00 8 +20121123-12h00 5 +20121130-12h00 13 +20121207-12h00 10 +20121214-12h00 14 +20121221-12h00 10 +20121228-12h00 4 +20130104-12h00 7 +20130111-12h00 6 +20130118-12h00 10 +20130125-12h00 10 +20130201-12h00 8 +20130208-12h00 11 +20130215-12h00 6 +20130222-12h00 7 +20130301-12h00 8 +20130308-12h00 4 +20130315-12h00 6 +20130322-12h00 4 +20130329-12h00 7 +20130405-12h00 10 +20130412-12h00 8 +20130419-12h00 6 +20130426-12h00 5 +20130503-12h00 8 +20130510-12h00 3 +20130517-12h00 8 +20130524-12h00 9 +20130531-12h00 7 +20130607-12h00 8 +20130614-12h00 9 +20130628-12h00 9 +20130705-12h00 9 +20130712-12h00 7 +20130719-12h00 11 +20130726-12h00 7 +20130802-12h00 11 +20130809-12h00 10 +20130816-12h00 6 +20130823-12h00 6 +20130830-12h00 6 +20130906-12h00 11 +20130913-12h00 8 +20130920-12h00 6 +20130927-12h00 6 +20131004-12h00 3 +20131011-12h00 5 +20131018-12h00 5 +20131025-12h00 3 +20131101-12h00 6 +20131108-12h00 4 +20131115-12h00 3 +20131122-12h00 6 +20131129-12h00 2 +20131206-12h00 6 +20131213-12h00 5 +20131220-12h00 2 +20131227-12h00 5 +20140103-12h00 4 +20140110-12h00 5 +20140117-12h00 8 +20140124-12h00 6 +20140131-12h00 7 +20140207-12h00 5 +20140214-12h00 6 +20140221-12h00 6 +20140228-12h00 8 +20140307-12h00 5 +20140314-12h00 6 +20140321-12h00 5 +20140328-12h00 6 +20140404-12h00 6 +20140411-12h00 5 +20140418-12h00 6 +20140425-12h00 5 +20140502-12h00 5 +20140509-12h00 6 +20140516-12h00 4 +20140523-12h00 2 +20140613-12h00 6 +20140620-12h00 4 +20140627-12h00 5 +20140704-12h00 6 +20140711-12h00 7 +20140718-12h00 7 +20140725-12h00 3 +20140801-12h00 6 +20140808-12h00 5 +20140822-12h00 4 +20140829-12h00 7 +20140905-12h00 6 +20140912-12h00 5 +20140919-12h00 5 +20140926-12h00 3 +20141010-12h00 5 +20141017-12h00 7 +20141024-12h00 6 +20141031-12h00 6 +20141107-12h00 3 +20141114-12h00 7 +20141121-12h00 7 +20141128-12h00 6 +20141205-12h00 7 +20141212-12h00 7 +20141219-12h00 6 +20150109-12h00 4 +20150116-12h00 1 +20150123-12h00 4 +20150130-12h00 6 +20150206-12h00 4 +20150213-12h00 2 +20150220-12h00 7 +20150227-12h00 4 +20150306-12h00 11 +20150313-12h00 10 +20150320-12h00 8 +20150327-12h00 5 +20150403-12h00 6 +20150410-12h00 8 +20150417-12h00 4 +20150424-12h00 3 +20150507-12h00 5 +20150515-12h00 8 +20150522-12h00 7 +20150529-12h00 5 +20150605-12h00 10 +20150612-12h00 6 +20150619-12h00 3 +20150626-12h00 5 +20150703-12h00 5 +20150710-12h00 5 +20150717-12h00 6 +20150724-12h00 7 +20150731-12h00 5 +20150807-12h00 4 +20150814-12h00 4 +20150828-12h00 5 +20150904-12h00 5 +20150918-12h00 6 +20150925-12h00 5 +20151002-12h00 7 +20151009-12h00 5 +20151016-12h00 8 +20151023-12h00 6 +20151030-12h00 8 +20151106-12h00 5 +20151113-12h00 9 +20151120-12h00 9 +20151127-12h00 7 +20151204-12h00 10 +20151211-12h00 7 +20151218-12h00 10 +20160108-12h00 8 +20160219-12h00 6 +20160226-12h00 9 +20160318-12h00 6 +20160401-12h00 6 +20160408-12h00 8 +20160415-12h00 6 +20160422-12h00 7 +20160429-12h00 7 +20160506-12h00 7 +20160513-12h00 6 +20160520-12h00 6 +20160527-12h00 8 +20160603-12h00 3 +20160610-12h00 4 +20160617-12h00 7 +20160624-12h00 6 +20160701-12h00 6 +20160708-12h00 7 +20160715-12h00 4 +20160722-12h00 6 +20160805-12h00 4 +20160812-12h00 3 +20160819-12h00 4 +20160826-12h00 4 +20160902-12h00 6 +20160909-12h00 7 +20160916-12h00 7 +20160923-12h00 7 +20160930-12h00 4 +20161007-12h00 6 +20161021-12h00 7 +20161028-12h00 6 +20161104-12h00 7 +20161110-12h00 7 +20161118-12h00 7 +20161125-12h00 7 +20161202-12h00 4 +20161209-12h00 7 +20161216-12h00 9 +20161223-12h00 5 +20170106-12h00 7 +20170113-12h00 4 +20170120-12h00 8 +20170127-12h00 7 +20170203-12h00 6 +20170210-12h00 6 +20170217-12h00 8 +20170224-12h00 8 +20170303-12h00 7 +20170310-12h00 7 +20170317-12h00 4 +20170324-12h00 6 +20170331-12h00 5 +20170407-12h00 4 +20170414-12h00 4 +20170421-12h00 9 +20170428-12h00 7 +20170505-12h00 6 +20170512-12h00 7 +20170519-12h00 6 +20170526-12h00 5 +20170602-12h00 8 +20170609-12h00 10 +20170616-12h00 9 +20170623-12h00 7 +20170630-12h00 5 +20170707-12h00 12 +20170713-12h00 4 +20170721-12h00 6 +20170727-12h00 6 +20170818-12h00 5 +20170825-12h00 3 +20170901-12h00 6 +20170908-12h00 5 +20170915-12h00 7 +20170922-12h00 5 +20170929-12h00 10 +20171006-12h00 9 +20171013-12h00 9 +20171020-12h00 7 +20171027-12h00 8 +20171103-12h00 10 +20171110-12h00 10 +20171117-12h00 8 +20171124-12h00 8 +20171201-12h00 9 +20171208-12h00 10 +20171215-12h00 11 +20171222-12h00 7 +20180105-12h00 7 +20180112-12h00 10 +20180118-12h00 1 +20180119-12h00 12 +20180126-12h00 10 +20180202-12h00 9 +20180209-12h00 8 17 +20180216-12h00 8 16 +20180223-12h00 5 14 +20180302-12h00 6 19 +20180309-12h00 11 20 +20180316-12h00 9 19 +20180323-12h00 9 17 +20180330-12h00 10 19 +20180406-12h00 9 17 +20180413-12h00 11 15 +20180420-12h00 6 17 +20180427-12h00 7 17 +20180504-12h00 7 17 diff --git a/src/org/april/hebdobot/model/Hebdobot.java b/src/org/april/hebdobot/model/Hebdobot.java index f2a786b..9b202f3 100644 --- a/src/org/april/hebdobot/model/Hebdobot.java +++ b/src/org/april/hebdobot/model/Hebdobot.java @@ -40,6 +40,10 @@ import org.april.hebdobot.model.review.IndividualTopic; import org.april.hebdobot.model.review.Message; import org.april.hebdobot.model.review.Review; import org.april.hebdobot.model.review.Topic; +import org.april.hebdobot.model.stats.ReviewData; +import org.april.hebdobot.model.stats.ReviewDatas; +import org.april.hebdobot.model.stats.ReviewDatasFile; +import org.april.hebdobot.model.stats.ReviewStatsReporter; import org.april.hebdobot.pastebin.PastebinClient; import org.april.hebdobot.pastebin.PastebinSettings; import org.april.hebdobot.pastebin.Private; @@ -364,6 +368,36 @@ public class Hebdobot extends PircBot String participants = StringUtils.join(this.review.getParticipants(), " "); sendMessage("% " + participants + ", pensez à noter votre bénévalo : http://www.april.org/my?action=benevalo"); + // Display statistics. This feature has to not break + // Hebdobot. + try + { + File reviewDataFile = new File(this.reviewDirectory, "reviewstats.csv"); + if (reviewDataFile.exists()) + { + ReviewDatas datas = ReviewDatasFile.load(reviewDataFile); + datas.clean(); + sendMessage("% " + ReviewStatsReporter.reportNewMaxUserCount(datas, this.review.getParticipants().size())); + ReviewData currentReview = new ReviewData(LocalDateTime.now(), this.review.getParticipants().size(), + (int) this.review.getDurationInMinutes()); + datas.add(currentReview); + sendMessage("% " + ReviewStatsReporter.reportUserCount(datas, currentReview.getUserCount())); + sendMessage("% " + ReviewStatsReporter.reportDuration(datas, currentReview.getUserCount())); + + ReviewDatasFile.append(reviewDataFile, currentReview); + } + else + { + logger.warn("Statistic file not found [{}]", reviewDataFile.getAbsolutePath()); + sendMessage("% Fichier de statistiques absent."); + } + } + catch (Exception exception) + { + logger.warn("Exception during statistics work.", exception); + sendMessage("% Impossible d'afficher des statistiques."); + } + sendMessage("% Fin de la revue hebdomadaire"); sendMessage(this.review.getOwner(), "Revue finie."); diff --git a/src/org/april/hebdobot/model/stats/IntegerBoard.java b/src/org/april/hebdobot/model/stats/IntegerBoard.java new file mode 100644 index 0000000..542ee0c --- /dev/null +++ b/src/org/april/hebdobot/model/stats/IntegerBoard.java @@ -0,0 +1,301 @@ +/** + * Copyright (C) 2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import fr.devinsy.util.strings.StringList; + +/** + * The Class Integers. + */ +public class IntegerBoard implements Iterable +{ + private Map integers; + private boolean isUptodate; + private IntegerStats board; + + /** + * Instantiates a new distribution. + */ + public IntegerBoard() + { + this.integers = new HashMap(30); + this.isUptodate = false; + this.board = new IntegerStats(this.integers.size()); + } + + /** + * Adds the. + * + * @param value + * the value + * @return true, if successful + */ + public void add(final Integer value) + { + if (value != null) + { + IntegerStat stat = this.integers.get(value); + if (stat == null) + { + stat = new IntegerStat(value); + this.integers.put(value, stat); + } + + stat.inc(); + } + } + + /** + * Gets the. + * + * @param value + * the value + * @return the integer stat + */ + public IntegerStat get(final int value) + { + IntegerStat result; + + result = this.integers.get(value); + + // + return result; + } + + /** + * Gets the average. + * + * @return the average + */ + public double getAverage() + { + double result; + + int numerator = 0; + int denumerator = 0; + for (IntegerStat stat : this.integers.values()) + { + numerator += stat.getCount() * stat.getValue(); + denumerator += stat.getCount(); + } + result = (numerator * 1. / denumerator); + + // + return result; + } + + /** + * Gets the count sum. + * + * @return the count sum + */ + public int getCountSum() + { + int result; + + result = 0; + for (IntegerStat stat : this.integers.values()) + { + result += stat.getCount(); + } + + // + return result; + } + + public int getMaxValue() + { + int result; + + if (isEmpty()) + { + result = 0; + } + else + { + result = Integer.MIN_VALUE; + for (IntegerStat stat : this.integers.values()) + { + if (stat.getValue() > result) + { + result = stat.getValue(); + } + } + } + + // + return result; + } + + /** + * Gets the min. + * + * @return the min + */ + public int getMinValue() + { + int result; + + if (isEmpty()) + { + result = 0; + } + else + { + result = Integer.MAX_VALUE; + for (IntegerStat stat : this.integers.values()) + { + if (stat.getValue() < result) + { + result = stat.getValue(); + } + } + } + + // + return result; + } + + /** + * Gets the index of. + * + * @param value + * the value + * @return the index of + */ + public Integer getPositionOf(final int search) + { + Integer result; + + update(); + + int index = 0; + boolean ended = false; + result = null; + while (!ended) + { + if (index < this.board.size()) + { + int value = this.board.get(index).getValue(); + + if (value == search) + { + result = index + 1; + ended = true; + } + else + { + index += 1; + } + } + else + { + ended = true; + result = null; + } + } + + // + return result; + } + + /** + * Checks if is empty. + * + * @return true, if is empty + */ + public boolean isEmpty() + { + boolean result; + + if (this.integers.size() == 0) + { + result = true; + } + else + { + result = false; + } + + // + return result; + } + + /** + * Iterator. + * + * @return the iterator + */ + @Override + public Iterator iterator() + { + Iterator result; + + update(); + result = this.board.iterator(); + + // + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + String result; + + update(); + + StringList buffer = new StringList(); + for (IntegerStat stat : this.board) + { + buffer.append(String.format("%d (%d)", stat.getValue(), stat.getCount())); + } + result = buffer.toStringWithBracket(); + + // + return result; + } + + /** + * Update. + */ + public void update() + { + if (!this.isUptodate) + { + this.board.clear(); + for (IntegerStat stat : this.integers.values()) + { + this.board.add(stat); + } + this.board.sortByValue(); + Collections.reverse(this.board); + + this.isUptodate = false; + } + } +} diff --git a/src/org/april/hebdobot/model/stats/IntegerStat.java b/src/org/april/hebdobot/model/stats/IntegerStat.java new file mode 100644 index 0000000..38d810a --- /dev/null +++ b/src/org/april/hebdobot/model/stats/IntegerStat.java @@ -0,0 +1,99 @@ +/** + * Copyright (C) 2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +/** + * The Class Stat. + */ +public class IntegerStat +{ + private int value; + private int count; + + /** + * Instantiates a new distribution pair. + * + * @param value + * the value + */ + public IntegerStat(final int value) + { + this(value, 0); + } + + /** + * Instantiates a new distribution pair. + * + * @param value + * the value + * @param count + * the count + */ + public IntegerStat(final int value, final int count) + { + this.value = value; + this.count = count; + } + + /** + * Decrease count. + */ + public void dec() + { + this.count -= 1; + } + + /** + * Gets the count. + * + * @return the count + */ + public int getCount() + { + int result; + + result = this.count; + + // + return result; + } + + /** + * Gets the value. + * + * @return the value + */ + public int getValue() + { + int result; + + result = this.value; + + // + return result; + } + + /** + * Increments count. + */ + public void inc() + { + this.count += 1; + } +} diff --git a/src/org/april/hebdobot/model/stats/IntegerStatComparator.java b/src/org/april/hebdobot/model/stats/IntegerStatComparator.java new file mode 100644 index 0000000..09d2c0f --- /dev/null +++ b/src/org/april/hebdobot/model/stats/IntegerStatComparator.java @@ -0,0 +1,217 @@ +/** + * Copyright (C) 2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.util.Comparator; + +/** + * The Class Stat. + */ +public class IntegerStatComparator implements Comparator +{ + public enum Sorting + { + VALUE, + COUNT + } + + private Sorting sorting; + + /** + * Instantiates a new review data comparator. + * + * @param sorting + * the sorting + */ + public IntegerStatComparator(final Sorting sorting) + { + this.sorting = sorting; + } + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(final IntegerStat alpha, final IntegerStat bravo) + { + int result; + + result = compare(alpha, bravo, this.sorting); + + // + return result; + } + + /** + * Compare. + * + * @param alpha + * the alpha + * @param bravo + * the bravo + * @return the int + */ + public static int compare(final Integer alpha, final Integer bravo) + { + int result; + + // + if ((alpha == null) && (bravo == null)) + { + result = 0; + } + else if (alpha == null) + { + result = -1; + } + else if (bravo == null) + { + result = +1; + } + else + { + result = alpha.compareTo(bravo); + } + + // + return result; + } + + /** + * Compare. + * + * @param alpha + * the alpha + * @param bravo + * the bravo + * @param sorting + * the sorting + * @return the int + */ + public static int compare(final IntegerStat alpha, final IntegerStat bravo, final Sorting sorting) + { + int result; + + if (sorting == null) + { + result = 0; + } + else + { + switch (sorting) + { + case VALUE: + result = compare(getValue(alpha), getValue(bravo)); + break; + + case COUNT: + result = compare(getCount(alpha), getCount(bravo)); + break; + default: + result = 0; + } + } + + // + return result; + } + + /** + * Compare. + * + * @param alpha + * the alpha + * @param bravo + * the bravo + * @return the int + */ + public static int compare(final Long alpha, final Long bravo) + { + int result; + + // + if ((alpha == null) && (bravo == null)) + { + result = 0; + } + else if (alpha == null) + { + result = -1; + } + else if (bravo == null) + { + result = +1; + } + else + { + result = alpha.compareTo(bravo); + } + + // + return result; + } + + /** + * Gets the user count. + * + * @param source + * the source + * @return the user count + */ + public static Integer getCount(final IntegerStat source) + { + Integer result; + + if (source == null) + { + result = null; + } + else + { + result = source.getCount(); + } + + // + return result; + } + + /** + * Gets the duration. + * + * @param source + * the source + * @return the duration + */ + public static Integer getValue(final IntegerStat source) + { + Integer result; + + if (source == null) + { + result = null; + } + else + { + result = source.getValue(); + } + + // + return result; + } +} diff --git a/src/org/april/hebdobot/model/stats/IntegerStats.java b/src/org/april/hebdobot/model/stats/IntegerStats.java new file mode 100644 index 0000000..a4bd80a --- /dev/null +++ b/src/org/april/hebdobot/model/stats/IntegerStats.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2017-2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.util.ArrayList; +import java.util.Collections; + +import org.april.hebdobot.model.stats.IntegerStatComparator.Sorting; + +/** + * The Class Stats. + */ +public class IntegerStats extends ArrayList +{ + private static final long serialVersionUID = 2632624619156256161L; + + /** + * Instantiates a new integer stats. + */ + public IntegerStats() + { + super(); + } + + /** + * Instantiates a new integer stats. + * + * @param initialCapacity + * the initial capacity + */ + public IntegerStats(final int initialCapacity) + { + super(initialCapacity); + } + + /** + * Reverse. + */ + public void reverse() + { + Collections.reverse(this); + } + + /** + * Sort by user count. + */ + public void sortByCount() + { + Collections.sort(this, new IntegerStatComparator(Sorting.COUNT)); + } + + /** + * Sort by duration. + */ + public void sortByValue() + { + Collections.sort(this, new IntegerStatComparator(Sorting.VALUE)); + } +} diff --git a/src/org/april/hebdobot/model/stats/ReviewData.java b/src/org/april/hebdobot/model/stats/ReviewData.java new file mode 100644 index 0000000..eaf8912 --- /dev/null +++ b/src/org/april/hebdobot/model/stats/ReviewData.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2017-2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.time.LocalDateTime; + +/** + * The Class ReviewStat. + */ +public class ReviewData +{ + private LocalDateTime date; + private int userCount; + private Integer duration; + + /** + * Instantiates a new stat. + * + * @param date + * the date + * @param userCount + * the user count + * @param duration + * the duration + */ + public ReviewData(final LocalDateTime date, final int userCount, final Integer duration) + { + this.date = date; + this.userCount = userCount; + this.duration = duration; + } + + public LocalDateTime getDate() + { + return this.date; + } + + public Integer getDuration() + { + return this.duration; + } + + public int getUserCount() + { + return this.userCount; + } + + public void setDate(final LocalDateTime date) + { + this.date = date; + } + + public void setDuration(final Integer duration) + { + this.duration = duration; + } + + public void setUserCount(final int userCount) + { + this.userCount = userCount; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + String result; + + result = String.format("%s\t%d\t%d", this.date.toString(), this.userCount, this.duration); + + // + return result; + } +} diff --git a/src/org/april/hebdobot/model/stats/ReviewDataComparator.java b/src/org/april/hebdobot/model/stats/ReviewDataComparator.java new file mode 100644 index 0000000..65566b1 --- /dev/null +++ b/src/org/april/hebdobot/model/stats/ReviewDataComparator.java @@ -0,0 +1,273 @@ +/** + * Copyright (C) 2017-2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.time.LocalDateTime; +import java.util.Comparator; + +/** + * The Class Stat. + */ +public class ReviewDataComparator implements Comparator +{ + public enum Sorting + { + DATE, + USERCOUNT, + DURATION + } + + private Sorting sorting; + + /** + * Instantiates a new review data comparator. + * + * @param sorting + * the sorting + */ + public ReviewDataComparator(final Sorting sorting) + { + this.sorting = sorting; + } + + /* (non-Javadoc) + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(final ReviewData alpha, final ReviewData bravo) + { + int result; + + result = compare(alpha, bravo, this.sorting); + + // + return result; + } + + /** + * Compare. + * + * @param alpha + * the alpha + * @param bravo + * the bravo + * @return the int + */ + public static int compare(final Integer alpha, final Integer bravo) + { + int result; + + // + if ((alpha == null) && (bravo == null)) + { + result = 0; + } + else if (alpha == null) + { + result = -1; + } + else if (bravo == null) + { + result = +1; + } + else + { + result = alpha.compareTo(bravo); + } + + // + return result; + } + + public static int compare(final LocalDateTime alpha, final LocalDateTime bravo) + { + int result; + + // + if ((alpha == null) && (bravo == null)) + { + result = 0; + } + else if (alpha == null) + { + result = -1; + } + else if (bravo == null) + { + result = +1; + } + else + { + result = alpha.compareTo(bravo); + } + + // + return result; + } + + /** + * Compare. + * + * @param alpha + * the alpha + * @param bravo + * the bravo + * @return the int + */ + public static int compare(final Long alpha, final Long bravo) + { + int result; + + // + if ((alpha == null) && (bravo == null)) + { + result = 0; + } + else if (alpha == null) + { + result = -1; + } + else if (bravo == null) + { + result = +1; + } + else + { + result = alpha.compareTo(bravo); + } + + // + return result; + } + + /** + * Compare. + * + * @param alpha + * the alpha + * @param bravo + * the bravo + * @param sorting + * the sorting + * @return the int + */ + public static int compare(final ReviewData alpha, final ReviewData bravo, final Sorting sorting) + { + int result; + + if (sorting == null) + { + result = 0; + } + else + { + switch (sorting) + { + case DATE: + result = compare(getDate(alpha), getDate(bravo)); + break; + + case USERCOUNT: + result = compare(getUserCount(alpha), getUserCount(bravo)); + break; + + case DURATION: + result = compare(getDuration(alpha), getDuration(bravo)); + break; + default: + result = 0; + } + } + + // + return result; + } + + /** + * Gets the date. + * + * @param source + * the source + * @return the date + */ + public static LocalDateTime getDate(final ReviewData source) + { + LocalDateTime result; + + if (source == null) + { + result = null; + } + else + { + result = source.getDate(); + } + + // + return result; + } + + /** + * Gets the duration. + * + * @param source + * the source + * @return the duration + */ + public static Integer getDuration(final ReviewData source) + { + Integer result; + + if (source == null) + { + result = null; + } + else + { + result = source.getDuration(); + } + + // + return result; + } + + /** + * Gets the user count. + * + * @param source + * the source + * @return the user count + */ + public static Integer getUserCount(final ReviewData source) + { + Integer result; + + if (source == null) + { + result = null; + } + else + { + result = source.getUserCount(); + } + + // + return result; + } +} diff --git a/src/org/april/hebdobot/model/stats/ReviewDatas.java b/src/org/april/hebdobot/model/stats/ReviewDatas.java new file mode 100644 index 0000000..c06f63d --- /dev/null +++ b/src/org/april/hebdobot/model/stats/ReviewDatas.java @@ -0,0 +1,186 @@ +/** + * Copyright (C) 2017-2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; + +import org.april.hebdobot.model.stats.ReviewDataComparator.Sorting; + +/** + * The Class Stats. + */ +public class ReviewDatas extends ArrayList +{ + private static final long serialVersionUID = 326880908257489774L; + + /** + * Instantiates a new ReviewDatas. + */ + public ReviewDatas() + { + super(); + } + + /** + * Clean. + * + * @return the int + */ + public int clean() + { + int result; + + result = 0; + Iterator iterator = iterator(); + while (iterator.hasNext()) + { + if (iterator.next().getUserCount() < 2) + { + iterator.remove(); + result += 1; + } + } + + // + return result; + } + + /** + * Gets the last by max user count. + * + * @return the last by max user count + */ + public ReviewData getLastByMaxUserCount() + { + ReviewData result; + + if (isEmpty()) + { + result = null; + } + else + { + int max = Integer.MIN_VALUE; + result = null; + for (ReviewData data : this) + { + if (data.getUserCount() > max) + { + max = data.getUserCount(); + result = data; + } + } + } + + // + return result; + } + + /** + * Gets the max user count. + * + * @return the max user count + */ + public int getMaxUserCount() + { + int result; + + if (isEmpty()) + { + result = 0; + } + else + { + result = Integer.MIN_VALUE; + for (ReviewData data : this) + { + if (data.getUserCount() > result) + { + result = data.getUserCount(); + } + } + } + + // + return result; + } + + /** + * Gets the min user count. + * + * @return the min user count + */ + public int getMinUserCount() + { + int result; + + if (isEmpty()) + { + result = 0; + } + else + { + result = Integer.MAX_VALUE; + for (ReviewData data : this) + { + if (data.getUserCount() < result) + { + result = data.getUserCount(); + } + } + } + + // + return result; + } + + /** + * Reverse. + */ + public void reverse() + { + Collections.reverse(this); + } + + /** + * Sort by date. + */ + public void sortByDate() + { + Collections.sort(this, new ReviewDataComparator(Sorting.DATE)); + } + + /** + * Sort by duration. + */ + public void sortByDuration() + { + Collections.sort(this, new ReviewDataComparator(Sorting.DURATION)); + } + + /** + * Sort by user count. + */ + public void sortByUserCount() + { + Collections.sort(this, new ReviewDataComparator(Sorting.USERCOUNT)); + } +} diff --git a/src/org/april/hebdobot/model/stats/ReviewDatasFile.java b/src/org/april/hebdobot/model/stats/ReviewDatasFile.java new file mode 100644 index 0000000..530d073 --- /dev/null +++ b/src/org/april/hebdobot/model/stats/ReviewDatasFile.java @@ -0,0 +1,210 @@ +/** + * Copyright (C) 2017-2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.time.LocalDateTime; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.april.hebdobot.HebdobotException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Class StatsFile. + */ +public class ReviewDatasFile +{ + private static final Logger logger = LoggerFactory.getLogger(ReviewDatasFile.class); + public static final String DEFAULT_CHARSET_NAME = "UTF-8"; + + /** + * Instantiates a new stats file. + */ + private ReviewDatasFile() + { + } + + /** + * Append. + * + * @param file + * the target + * @param stat + * the stat + * @throws HebdobotException + */ + public static void append(final File file, final ReviewData stat) throws HebdobotException + { + RandomAccessFile out = null; + try + { + out = new RandomAccessFile(file, "rw"); + out.seek(out.length()); + + out.writeUTF(String.format("%s\t%d\t%d\n", stat.getDate(), stat.getUserCount(), stat.getDuration())); + } + catch (FileNotFoundException exception) + { + logger.error("File Not Found: " + exception.getMessage(), exception); + throw new HebdobotException("File Not Found appending file [" + file + "]"); + + } + catch (IOException exception) + { + logger.error("IO error: " + exception.getMessage(), exception); + throw new HebdobotException("IO Error appending file [" + file + "]"); + } + finally + { + IOUtils.closeQuietly(out); + } + } + + /** + * Load. + * + * @param source + * the source + * @return the stats + * @throws HebdobotException + */ + public static ReviewDatas load(final File source) throws HebdobotException + { + ReviewDatas result; + + // + if (source == null) + { + logger.error("Attempt to read null file."); + throw new IllegalArgumentException("Null parameter."); + } + else + { + BufferedReader in = null; + try + { + in = new BufferedReader(new InputStreamReader(new FileInputStream(source), DEFAULT_CHARSET_NAME)); + result = read(in); + } + catch (UnsupportedEncodingException exception) + { + logger.error("Encoding error reading file: " + exception.getMessage(), exception); + throw new HebdobotException("Encodding error reading file [" + source + "]"); + } + catch (FileNotFoundException exception) + { + logger.error("File Not Found: " + exception.getMessage(), exception); + throw new HebdobotException("File Not Found opening file:[" + source + "]"); + } + catch (IOException exception) + { + logger.error("IO error: " + exception.getMessage(), exception); + throw new HebdobotException("IO Error opening file: [" + source + "]"); + } + finally + { + IOUtils.closeQuietly(in); + } + } + + // + return result; + } + + /** + * Read. + * + * @param source + * the source + * @return the stats + * @throws IOException + */ + public static ReviewDatas read(final BufferedReader in) throws IOException + { + ReviewDatas result; + + result = new ReviewDatas(); + + boolean ended = false; + while (!ended) + { + String line = in.readLine(); + + if (line == null) + { + ended = true; + } + else if ((StringUtils.isNotBlank(line)) && (!line.startsWith("#"))) + { + // System.out.println("line=" + line); + try + { + + String[] tokens = line.split("[ \\s]+"); + + if ((tokens[0] == null) || (!tokens[0].matches("\\d{8}-\\d{2}h(\\d{2})+"))) + { + logger.warn("Bad line in review stat file: [{}]", line); + } + else + { + int year = Integer.parseInt(tokens[0].substring(0, 4)); + int month = Integer.parseInt(tokens[0].substring(4, 6)); + int day = Integer.parseInt(tokens[0].substring(6, 8)); + int hour = Integer.parseInt(tokens[0].substring(9, 11)); + int minute = Integer.parseInt(tokens[0].substring(12, 14)); + LocalDateTime date = LocalDateTime.of(year, month, day, hour, minute); + + int userCount = Integer.parseInt(tokens[1]); + + Integer duration; + if (tokens.length == 2) + { + duration = null; + } + else + { + duration = Integer.parseInt(tokens[2]); + } + + ReviewData stat = new ReviewData(date, userCount, duration); + + result.add(stat); + } + } + catch (Exception exception) + { + logger.warn("Decode failed for line: [{}]", line, exception); + } + } + } + + // + return result; + } +} diff --git a/src/org/april/hebdobot/model/stats/ReviewStatsReporter.java b/src/org/april/hebdobot/model/stats/ReviewStatsReporter.java new file mode 100644 index 0000000..3646179 --- /dev/null +++ b/src/org/april/hebdobot/model/stats/ReviewStatsReporter.java @@ -0,0 +1,168 @@ +/** + * Copyright (C) 2017-2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +/** + * The Class ReviewStatReporter. + */ +public class ReviewStatsReporter +{ + /** + * Instantiates a new review stat reporter. + */ + private ReviewStatsReporter() + { + } + + /** + * Percent. + * + * @param a + * the a + * @param b + * the b + * @return the double + */ + private static double percent(final double a, final double b) + { + double result; + + if (b == 0.) + { + result = 0.; + } + else + { + result = Math.round(10000. * (a / b)) / 100.; + } + + // + return result; + } + + /** + * Percent. + * + * @param a + * the a + * @param b + * the b + * @return the double + */ + private static double percent(final int a, final int b) + { + double result; + + result = percent(new Double(a).doubleValue(), new Double(b).doubleValue()); + + // + return result; + } + + /** + * Report duration. + * + * @param datas + * the datas + * @param currentDuration + * the current duration + * @return the string + */ + public static String reportDuration(final ReviewDatas datas, final int currentDuration) + { + String result; + + IntegerBoard board = new IntegerBoard(); + for (ReviewData data : datas) + { + board.add(data.getDuration()); + } + System.out.println("Duration board: " + board.toString()); + + IntegerStat stat = board.get(currentDuration); + int total = board.getCountSum(); + result = String.format( + "Statistiques sur la durée de la revue (%d mn) : position %d (min=%d mn,moy=%.1f mn,max=%d mn), fréquence %d/%d (%.0f %%)", + stat.getValue(), board.getPositionOf(stat.getValue()), board.getMinValue(), board.getAverage(), board.getMaxValue(), stat.getCount(), + total, percent(stat.getCount(), total)); + + // + return result; + } + + /** + * Report new max. + * + * @param datas + * the datas + * @param currentUserCount + * the current user count + * @return the string + */ + public static String reportNewMaxUserCount(final ReviewDatas datas, final int currentUserCount) + { + String result; + + datas.sortByDate(); + ReviewData last = datas.getLastByMaxUserCount(); + if (currentUserCount < last.getUserCount()) + { + result = "Pas de nouveau record de participation."; + } + else + { + result = "Nouveau record de participation."; + } + + result = String.format("%s Dernier record %d, le %s.", result, last.getUserCount(), last.getDate().toString()); + + // + return result; + } + + /** + * Report user count. + * + * @param datas + * the datas + * @param currentUserCount + * the current user count + * @return the string + */ + public static String reportUserCount(final ReviewDatas datas, final int currentUserCount) + { + String result; + + IntegerBoard board = new IntegerBoard(); + for (ReviewData data : datas) + { + board.add(data.getUserCount()); + } + System.out.println("User count board: " + board.toString()); + + IntegerStat stat = board.get(currentUserCount); + int total = board.getCountSum(); + result = String.format("Statistiques sur le nombre de participants (%d) : position %d (min=%d,moy=%.1f,max=%d), fréquence %d/%d (%.0f %%)", + stat.getValue(), board.getPositionOf(stat.getValue()), board.getMinValue(), board.getAverage(), board.getMaxValue(), stat.getCount(), + total, percent(stat.getCount(), total)); + + // + return result; + } +} diff --git a/src/org/april/hebdobot/model/stats/SimpleIntegerBoard.java b/src/org/april/hebdobot/model/stats/SimpleIntegerBoard.java new file mode 100644 index 0000000..445dc03 --- /dev/null +++ b/src/org/april/hebdobot/model/stats/SimpleIntegerBoard.java @@ -0,0 +1,169 @@ +/** + * Copyright (C) 2018 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.model.stats; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import fr.devinsy.util.strings.StringList; + +/** + * The Class Integers. + */ +public class SimpleIntegerBoard implements Iterable +{ + private Set integers; + private boolean isUptodate; + private List board; + + /** + * Instantiates a new distribution. + */ + public SimpleIntegerBoard() + { + this.integers = new HashSet(30); + this.isUptodate = false; + this.board = new ArrayList(30); + } + + /** + * Adds the. + * + * @param value + * the value + * @return true, if successful + */ + public boolean add(final Integer value) + { + boolean result; + + if (value == null) + { + result = false; + } + else + { + result = this.integers.add(value); + } + + // + return result; + } + + /** + * Gets the index of. + * + * @param value + * the value + * @return the index of + */ + public Integer getPositionOf(final int search) + { + Integer result; + + update(); + + int index = 0; + boolean ended = false; + result = null; + while (!ended) + { + if (index < this.board.size()) + { + int value = this.board.get(index); + + if (value == search) + { + result = index + 1; + ended = true; + } + else + { + index += 1; + } + } + else + { + ended = true; + result = null; + } + } + + // + return result; + } + + /** + * Iterator. + * + * @return the iterator + */ + @Override + public Iterator iterator() + { + Iterator result; + + update(); + result = this.board.iterator(); + + // + return result; + } + + @Override + public String toString() + { + String result; + + update(); + + StringList buffer = new StringList(); + for (Integer value : this.board) + { + buffer.append(value); + } + result = buffer.toStringWithBracket(); + + // + return result; + } + + /** + * Update. + */ + public void update() + { + if (!this.isUptodate) + { + this.board.clear(); + for (Integer value : this.integers) + { + this.board.add(value); + } + Collections.sort(this.board); + Collections.reverse(this.board); + + this.isUptodate = false; + } + } +} diff --git a/test/org/april/hebdobot/reviewstats/ReviewStatsTest.java b/test/org/april/hebdobot/reviewstats/ReviewStatsTest.java new file mode 100644 index 0000000..9b98fce --- /dev/null +++ b/test/org/april/hebdobot/reviewstats/ReviewStatsTest.java @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2011-2013,2017 Nicolas Vinot + * Copyright (C) 2017 Christian Pierre MOMON + * + * This file is part of (April) Hebdobot. + * + * Hebdobot is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Hebdobot is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Hebdobot. If not, see + */ +package org.april.hebdobot.reviewstats; + +import java.io.File; +import java.time.LocalDateTime; + +import org.apache.log4j.BasicConfigurator; +import org.april.hebdobot.model.stats.ReviewData; +import org.april.hebdobot.model.stats.ReviewDatas; +import org.april.hebdobot.model.stats.ReviewDatasFile; +import org.april.hebdobot.model.stats.ReviewStatsReporter; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Class BotMock. + */ +public class ReviewStatsTest +{ + private static final Logger logger = LoggerFactory.getLogger(ReviewStatsTest.class); + + @Before + public void init() + { + BasicConfigurator.configure(); + logger.info("Basic log configuration done."); + } + + /** + * Test load. + * + * @throws Exception + * the exception + */ + @Test + public void testLoad() throws Exception + { + System.out.println("================"); + ReviewDatas datas = ReviewDatasFile.load(new File("resources/reviewstats.csv")); + + for (ReviewData data : datas) + { + System.out.println(data.toString()); + } + } + + /** + * Test report duration. + * + * @throws Exception + * the exception + */ + @Test + public void testReportDuration() throws Exception + { + System.out.println("================"); + ReviewDatas datas = ReviewDatasFile.load(new File("resources/reviewstats.csv")); + datas.clean(); + System.out.println("File loaded."); + ReviewData currentReview = new ReviewData(LocalDateTime.now(), 12, 17); + datas.add(currentReview); + System.out.println(ReviewStatsReporter.reportDuration(datas, currentReview.getDuration())); + } + + /** + * Test report new max user count. + * + * @throws Exception + * the exception + */ + @Test + public void testReportNewMaxUserCount() throws Exception + { + System.out.println("================"); + ReviewDatas datas = ReviewDatasFile.load(new File("resources/reviewstats.csv")); + datas.clean(); + System.out.println("File loaded."); + ReviewData currentReview = new ReviewData(LocalDateTime.now(), 12, 17); + System.out.println(ReviewStatsReporter.reportNewMaxUserCount(datas, currentReview.getUserCount())); + } + + /** + * Test report user count. + * + * @throws Exception + * the exception + */ + @Test + public void testReportUserCount() throws Exception + { + System.out.println("================"); + ReviewDatas datas = ReviewDatasFile.load(new File("resources/reviewstats.csv")); + datas.clean(); + System.out.println("File loaded."); + ReviewData currentReview = new ReviewData(LocalDateTime.now(), 12, 17); + datas.add(currentReview); + System.out.println(ReviewStatsReporter.reportUserCount(datas, currentReview.getUserCount())); + } +}