SprintGenerator.java
package de.slothsoft.sprintsim.generation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import de.slothsoft.sprintsim.Member;
import de.slothsoft.sprintsim.Sprint;
import de.slothsoft.sprintsim.Task;
import de.slothsoft.sprintsim.config.SprintConfig;
import de.slothsoft.sprintsim.config.TaskConfig;
import de.slothsoft.sprintsim.config.TaskCreator;
public class SprintGenerator {
/** An array of the member's estimations - a <code>double[]</code>. */
public static final String TASK_DATA_MEMBER_ESTIMATIONS = "memberEstimations"; //$NON-NLS-1$
/** The final estimation of a task - a <code>double</code>. */
public static final String TASK_DATA_COLLECTED_ESTIMATION = "collectedEstimation"; //$NON-NLS-1$
private Member[] members;
private Long seed;
private Random rnd = new Random();
private TaskConfig taskConfig = new TaskConfig();
private SprintConfig sprintConfig = SprintConfig.createDefault();
public SprintGenerator(Member... members) {
this.members = Objects.requireNonNull(members);
}
public SprintPlanning generate() {
final double targetWorkHours = Arrays.stream(this.members).mapToDouble(Member::getWorkHoursPerDay).sum()
* this.sprintConfig.getLengthInDays();
double entireWorkHours = 0;
final List<Task> tasks = new ArrayList<>();
while (entireWorkHours < targetWorkHours) {
final Task task = createTask();
final double[] hoursArray = estimateTask(task);
final double hours = collectEstimations(hoursArray);
task.addUserData(TASK_DATA_MEMBER_ESTIMATIONS, hoursArray);
task.addUserData(TASK_DATA_COLLECTED_ESTIMATION, Double.valueOf(hours));
tasks.add(task);
entireWorkHours += hours;
}
return new SprintPlanning(new Sprint(tasks.toArray(new Task[tasks.size()])), entireWorkHours,
Math.max(0, entireWorkHours - targetWorkHours));
}
Task createTask() {
final double probabilitySum = Arrays.stream(this.sprintConfig.getTaskCreators())
.mapToDouble(TaskCreator::getProbability).sum();
final double taskType = this.rnd.nextDouble() * probabilitySum;
double currentProbability = 0;
for (final TaskCreator taskCreator : this.sprintConfig.getTaskCreators()) {
currentProbability += taskCreator.getProbability();
if (taskType <= currentProbability) return taskCreator.getConstructor().get();
}
// cannot really happen except if no task creators are given
return new Task();
}
double[] estimateTask(Task task) {
final double[] estimations = new double[this.members.length];
for (int i = 0; i < estimations.length; i++) {
estimations[i] = estimateTaskFromMember(task, this.members[i]);
}
return estimations;
}
double estimateTaskFromMember(Task task, Member member) {
final double baseHours = this.taskConfig.getHours(task.getComplexity())
* member.getWorkPerformance().getMultiplicator(task);
double deviation = member.getEstimationDeviation() * this.rnd.nextGaussian() * 0.7;
deviation = Math.max(-member.getEstimationDeviation(), Math.min(member.getEstimationDeviation(), deviation));
return baseHours + deviation * baseHours;
}
// TODO: allow other operations besides average (max and median)
private static double collectEstimations(double[] estimations) {
return Arrays.stream(estimations).average().getAsDouble();
}
public Member[] getMembers() {
return this.members;
}
public SprintGenerator members(Member... newMembers) {
setMembers(newMembers);
return this;
}
public void setMembers(Member... members) {
this.members = Objects.requireNonNull(members);
}
public TaskConfig getTaskConfig() {
return this.taskConfig;
}
public SprintGenerator taskConfig(TaskConfig newTaskConfig) {
setTaskConfig(newTaskConfig);
return this;
}
public void setTaskConfig(TaskConfig taskConfig) {
this.taskConfig = Objects.requireNonNull(taskConfig);
}
public SprintConfig getSprintConfig() {
return this.sprintConfig;
}
public SprintGenerator sprintConfig(SprintConfig newSprintConfig) {
setSprintConfig(newSprintConfig);
return this;
}
public void setSprintConfig(SprintConfig sprintConfig) {
this.sprintConfig = Objects.requireNonNull(sprintConfig);
}
public Long getSeed() {
return this.seed;
}
public SprintGenerator seed(Long newSeed) {
setSeed(newSeed);
return this;
}
public void setSeed(Long seed) {
this.seed = seed;
this.rnd = seed == null ? new Random() : new Random(seed.longValue());
}
}