001    package org.hackystat.projectbrowser.page.projectportfolio.detailspanel.chart;
002    
003    import java.io.Serializable;
004    import java.util.List;
005    import org.apache.wicket.markup.html.panel.Panel;
006    import org.hackystat.projectbrowser.page.projectportfolio.jaxb.Measures.Measure;
007    import org.hackystat.projectbrowser.page.projectportfolio.jaxb.Measures.Measure.
008            ParticipationParameters;
009    
010    /**
011     * Classify stream into 3 categories: GOOD, AVERAGE and POOR, 
012     * according to the participation of members.
013     * 
014     * @author Shaoxuan Zhang
015     *
016     */
017    public class StreamParticipationClassifier implements Serializable, StreamClassifier {
018    
019      /** Support serialization. */
020      private static final long serialVersionUID = 8335959874933854182L;
021      /** Name of this classifier. */
022      public static final String name = "Participation";
023      /** The expected percentage of members that participated. */
024      private double memberPercentage;
025      /** The lowest value with which a member is consider as participating. */
026      private double thresholdValue;
027      /** The expected percentage of time that has events happened. */
028      private double frequencyPercentage;
029      /** If the condition scale with granularity. */
030      private boolean scaleWithGranularity;
031    
032      /**
033       * @param memberPercentage the {@link memberPercentage} to set.
034       * @param thresholdValue the {@link thresholdValue} to set.
035       * @param frequencyPercentage the {@link frequencyPercentage} to set.
036       * @param scaleWithGranularity the scaleWithGranularity to set.
037       */
038      public StreamParticipationClassifier(final double memberPercentage, final double thresholdValue, 
039          final double frequencyPercentage, boolean scaleWithGranularity) {
040        this.memberPercentage = memberPercentage;
041        this.thresholdValue = thresholdValue;
042        this.frequencyPercentage = frequencyPercentage;
043        this.scaleWithGranularity = scaleWithGranularity;
044      }
045      
046      /**
047       * Parse the given MiniBarChart and produce a StreamCategory result.
048       * If there are more than {@link memberPercentage} members who contribute value higher 
049       * than {@link thresholdValue} in more than {@link frequencyPercentage} of time, the stream will
050       * be consider as GOOD.
051       * 
052       * If there are not enough active members to get a GOOD, but the merged stream satisfies 
053       * the GOOD criteria except member percentage, the stream will be considered as AVERAGE.
054       * 
055       * Otherwise, it will consider as POOR.
056       * @param chart the input chart
057       * @return StreamCategory enumeration. 
058       */
059      public PortfolioCategory getStreamCategory(MiniBarChart chart) {
060        double thresholdValue = this.thresholdValue;
061        if (this.scaleWithGranularity) {
062          if ("Week".equals(chart.granularity)) {
063            thresholdValue *= 7;
064          }
065          else if ("Month".equals(chart.granularity)) {
066            thresholdValue *= 30;
067          }
068        }
069        int activeMember = 0;
070        for (List<Double> stream : chart.streams) {
071          if (isTheStreamGood(stream, thresholdValue)) {
072            activeMember++;
073          }
074        }
075        if (activeMember > this.memberPercentage * chart.streams.size() / 100) {
076          return PortfolioCategory.GOOD;
077        }
078        if (isTheStreamGood(chart.streamData, thresholdValue)) {
079          return PortfolioCategory.AVERAGE;
080        }
081        return PortfolioCategory.POOR;
082      }
083    
084      /**
085       * Check if the stream contains more than {@link frequencyPercentage} values that are greater
086       * than {@link thresholdValue}.
087       * @param stream the given stream
088       * @param thresholdValue the filter threshold.
089       * @return true or false.
090       */
091      private boolean isTheStreamGood(List<Double> stream, double thresholdValue) {
092        int goodValueCount = 0;
093        for (Double value : stream) {
094          if (value >= thresholdValue) {
095            goodValueCount++;
096          }
097        }
098        if (goodValueCount >= this.frequencyPercentage / 100 * stream.size() ) {
099          return true;
100        }
101        return false;
102      }
103    
104      /**
105       * @param memberPercentage the memberPercentage to set
106       */
107      public void setMemberPercentage(double memberPercentage) {
108        this.memberPercentage = memberPercentage;
109      }
110    
111      /**
112       * @return the memberPercentage
113       */
114      public double getMemberPercentage() {
115        return memberPercentage;
116      }
117    
118      /**
119       * @param thresholdValue the thresholdValue to set
120       */
121      public void setThresholdValue(double thresholdValue) {
122        this.thresholdValue = thresholdValue;
123      }
124    
125      /**
126       * @return the thresholdValue
127       */
128      public double getThresholdValue() {
129        return thresholdValue;
130      }
131    
132      /**
133       * @param frequencyPercentage the frequencyPercentage to set
134       */
135      public void setFrequencyPercentage(double frequencyPercentage) {
136        this.frequencyPercentage = frequencyPercentage;
137      }
138    
139      /**
140       * @return the frequencyPercentage
141       */
142      public double getFrequencyPercentage() {
143        return frequencyPercentage;
144      }
145    
146      /**
147       * Return the panel for users to configure this stream trend classifer.
148       * User can customize higher, lower thresholds and if higher is better.
149       * @param id The Wicket component id.
150       * @return a Panel
151       */
152      public Panel getConfigurationPanel(String id) {
153        return new StreamParticipationClassifierConfigurationPanel(id, this);
154      }
155    
156      /**
157       * Return the category of the given value.
158       * If the value < 0, NA will be return. Otherwise, always return OTHER.
159       * @param value the given value.
160       * @return a {@link PortfolioCategory} result
161       */
162      public PortfolioCategory getValueCategory(double value) {
163        if (value < 0) {
164          return PortfolioCategory.NA;
165        }
166        return PortfolioCategory.OTHER;
167      }
168    
169      /**
170       * @return the name of this classifier.
171       */
172      public String getName() {
173        return name;
174      }
175    
176      /**
177       * Save classifier's setting into the given {@link Measure} instance.
178       * @param measure the given {@link Measure} instance
179       */
180      public void saveSetting(Measure measure) {
181        ParticipationParameters param = new ParticipationParameters();
182        param.setFrequencyPercentage(frequencyPercentage);
183        param.setMemberPercentage(memberPercentage);
184        param.setThresoldValue(thresholdValue);
185        param.setScaleWithGranularity(this.scaleWithGranularity);
186        measure.setParticipationParameters(param);
187      }
188    }