use of teamdash.wbs.WBSNode in project processdash by dtuma.
the class ProjectChangeListFactory method addNodeChange.
private void addNodeChange(TreeNodeChange<Integer, WBSNodeContent> tnc, Map<Integer, ProjectWbsNodeChange> nodeChanges, WBSModel wbs, Object changeType) {
Integer parentID = tnc.getParentID();
Integer nodeID = tnc.getNodeID();
WBSNode parent = wbs.getNodeMap().get(parentID);
WBSNode node = wbs.getNodeMap().get(nodeID);
if (node == null || parent == null)
// shouldn't happen
return;
// determine the effective author of this change. If it's different
// than the current author, use a different ID to store the change in
// the result map (so changes by different people don't get merged)
String effAuthor = getAuthorOfNodeChange(node, changeType, author);
int targetID = parentID;
if (!effAuthor.equals(author))
targetID |= effAuthor.hashCode() << 16;
ProjectWbsNodeChange result = nodeChanges.get(targetID);
if (result != null) {
result.addChild(node, changeType);
} else {
result = new ProjectWbsNodeChange(parent, node, changeType, indivTimeAttrs, memberZeroAttrs, teamMemberNames, effAuthor, timestamp, wbsNodeComparator);
nodeChanges.put(targetID, result);
}
}
use of teamdash.wbs.WBSNode in project processdash by dtuma.
the class TeamTimeColumn method multiplyValuesUnder.
@Override
protected void multiplyValuesUnder(WBSNode topNode, double newTopDownValue, double oldTopDownValue, double ratio) {
// our goal with this method is to scale the team time across subtasks
// while still observing minimum time constraints.
// find a list of the tasks under this node that need multiplying.
// see if any of those nodes have minimum times set.
Map<WBSNode, Double> weights = new HashMap();
Map<WBSNode, Double> minTimes = new HashMap();
double totalWeight = 0;
for (WBSNode child : wbsModel.getDescendants(topNode)) {
// do not make any changes to nodes that are hidden.
if (child.isHidden())
continue;
// get the top-down time estimate for this node.
double filt = getFilteredAmount(child);
double val = child.getNumericAttribute(topDownAttrName) - filt;
// look for a minimum time setting on this node.
double minTime = WorkflowMinTimeColumn.getMinTimeAt(child);
if (minTime > 0) {
minTimes.put(child, minTime);
// members would be very complex. Don't attempt it for now.
if (filt > 0) {
minTimes.clear();
break;
}
// if the current node's time is the result of a previous "min
// time" adjustment, allocate the node a weight based on the
// time that was originally replaced by the minimum.
double replacedTime = WorkflowMinTimeColumn.getReplacedTimeAt(child);
if (!Double.isNaN(replacedTime))
val = replacedTime;
}
// only scale nodes that have top-down values or minimum times
if (!(val > 0 || minTime > 0))
continue;
// store the weight of this node in our map.
weights.put(child, val);
totalWeight += val;
// if we previously saw a parent of this node, its top-down time
// must have been a top-down-bottom-up mismatch. remove it from our
// data structures and don't try to scale it.
WBSNode parent = wbsModel.getParent(child);
Double parentWeight = weights.remove(parent);
if (parentWeight != null)
totalWeight -= parentWeight;
}
// if we didn't find any min times, fall back to standard scaling logic.
if (minTimes.isEmpty()) {
super.multiplyValuesUnder(topNode, newTopDownValue, oldTopDownValue, ratio);
return;
}
// we need to scale values, while respecting min times. Calculate the
// weight-based time allocation for each node, and see if any of the
// nodes will need a min time adjustment.
Map<WBSNode, Double> minTimesToUse = new HashMap();
Map<WBSNode, Double> fixedTimeWgtOverrides = new HashMap();
double timeToSpread = newTopDownValue;
double weightToSpread = totalWeight;
while (true) {
boolean madeChangeToMinTimesDuringThisPass = false;
for (Entry<WBSNode, Double> e : weights.entrySet()) {
WBSNode leaf = e.getKey();
if (minTimesToUse.containsKey(leaf))
continue;
Double leafMinTime = minTimes.get(leaf);
if (leafMinTime == null)
continue;
double leafWeight = e.getValue();
if (leafWeight == 0)
// track which leaves have a fixed time (e.g. zero %)
fixedTimeWgtOverrides.put(leaf, null);
double leafTime = timeToSpread * leafWeight / weightToSpread;
if (leafTime < leafMinTime) {
minTimesToUse.put(leaf, leafMinTime);
timeToSpread -= leafMinTime;
weightToSpread -= leafWeight;
madeChangeToMinTimesDuringThisPass = true;
}
}
if (madeChangeToMinTimesDuringThisPass == false)
break;
}
// (and unexpectedly) flip down to zero.
if (timeToSpread <= 0 || weightToSpread <= 0) {
timeToSpread = newTopDownValue;
weightToSpread = totalWeight;
for (Entry<WBSNode, Double> e : fixedTimeWgtOverrides.entrySet()) {
Double fixedTime = minTimes.remove(e.getKey());
if (fixedTime == null)
fixedTime = 1.0;
double fixedWeight = fixedTime * totalWeight;
weightToSpread += fixedWeight;
e.setValue(fixedWeight);
}
minTimesToUse.clear();
}
// Subdivide the time over the leaf tasks, based on what we've found.
for (Entry<WBSNode, Double> e : weights.entrySet()) {
WBSNode leaf = e.getKey();
Double weightOverride = fixedTimeWgtOverrides.get(leaf);
double leafWeight = weightOverride != null ? weightOverride : e.getValue();
double leafTime = timeToSpread * leafWeight / weightToSpread;
Double leafMinTime = minTimesToUse.get(leaf);
if (leafMinTime != null) {
WorkflowMinTimeColumn.storeReplacedTimeAt(leaf, leafTime);
leafTime = leafMinTime;
} else if (weightOverride != null) {
WorkflowMinTimeColumn.storeReplacedTimeAt(leaf, 0);
} else {
WorkflowMinTimeColumn.storeReplacedTimeAt(leaf, Double.NaN);
}
userChangingValue(leaf, leafTime);
leaf.setNumericAttribute(topDownAttrName, leafTime + getFilteredAmount(leaf));
}
}
use of teamdash.wbs.WBSNode in project processdash by dtuma.
the class TopDownBottomUpColumn method filterChildren.
protected int filterChildren(WBSNode[] children) {
int len = children.length;
if (pruner == null)
return len;
int left = 0;
int right = len - 1;
while (true) {
// find the leftmost child that needs to be pruned.
while (left < len && !shouldPrune(children[left])) left++;
if (left >= right)
break;
// find the rightmost child that doesn't need pruning.
while (right > left && shouldPrune(children[right])) right--;
if (left < right) {
WBSNode temp = children[left];
children[left] = children[right];
children[right] = temp;
left++;
right--;
}
}
return left;
}
use of teamdash.wbs.WBSNode in project processdash by dtuma.
the class TopDownBottomUpColumn method maybeMultiplyValues.
protected void maybeMultiplyValues(WBSNode node, double newValue, double oldValue) {
if (topDownBottomUpMismatch(node))
return;
double ratio = newValue / oldValue;
if (!Double.isNaN(ratio) && !Double.isInfinite(ratio) && ratio != 0) {
multiplyValuesUnder(node, newValue, oldValue, ratio);
} else {
WBSNode delegate = getSingleLeafForNode(node, oldValue != 0);
if (delegate != null) {
// We have found a single leaf where the change should be made.
// Go ahead and set its bottom up value. (This will eventually
// be set by the recalculation logic, but this line allows the
// getValueAt to return as a non-error value in the meantime.)
double f = getFilteredAmount(delegate);
delegate.setNumericAttribute(bottomUpAttrName, newValue + f);
if (delegate != node) {
// if the delegate is different from the target node, make
// the change on the delegate. (If they are the same, these
// lines are unnecessary because they will be performed by
// the setValueAt logic after this method returns.)
userChangingValue(delegate, newValue);
delegate.setNumericAttribute(topDownAttrName, newValue + f);
}
}
}
}
use of teamdash.wbs.WBSNode in project processdash by dtuma.
the class TeamActualTimeColumn method recalculate.
/** Recalculate data for a single node in the WBS.
*
* With a single pass over the WBS, this method calculates actual time
* for each team member and for the entire team; actual earned value,
* completion date, percent complete, and percent spent.
*
* @param node the node to recalculate
* @param actualTime a result array, having one entry for each member of the
* team project. This method should calculate (for each team member)
* the total actual time for this node and all children, and store the
* resulting value in corresponding field of this array.
* @param earnedValue a single-entry result array. This method should
* calculate the team earned value (for this node and all children,
* in hours), and return the result in the single field of this array.
* @param completionDate a single-entry result array. This method should
* calculate the actual completion date of this node and all children,
* and return the result in the single field of this array.
*/
private void recalculate(WBSNode node, double[] actualTime, TimeCalculator[] timeCalc, double[] earnedValue, long[] completionDate) {
// get the list of children underneath this node
WBSNode[] children = wbsModel.getChildren(node);
boolean isLeaf = (children.length == 0);
// load the actual node time for each individual into our working array
for (int i = 0; i < teamSize; i++) actualTime[i] = nanToZero(node.getNumericAttribute(nodeTimeAttrs[i]));
earnedValue[0] = 0;
completionDate[0] = COMPL_DATE_NA;
if (isLeaf) {
int milestone = MilestoneColumn.getMilestoneID(node);
// accumulate EV and completion date information for this leaf
for (int i = 0; i < teamSize; i++) {
// decide whether data from this team member should be included
// in team sums
boolean rollupMember = rollupEveryone || matchesTeamFilter[i];
// retrieve the planned time for one team member.
double memberPlanTime = nanToZero(node.getNumericAttribute(planTimeAttrs[i]));
boolean assignedWithZero = (node.getAttribute(assignedWithZeroAttrs[i]) != null);
if (memberPlanTime > 0 || assignedWithZero) {
// if this team member is assigned to this leaf task, get
// their actual completion date for the task.
Date memberCompletionDate = (Date) node.getAttribute(completionDateAttrs[i]);
// keep track of the max completion date so far.
if (rollupMember)
completionDate[0] = mergeCompletionDate(completionDate[0], memberCompletionDate);
// team has earned the value associated with the task.
if (memberCompletionDate != null) {
if (rollupMember)
earnedValue[0] += memberPlanTime;
timeCalc[i].addCompletedTask(memberPlanTime, actualTime[i], milestone);
} else {
// See if subtask data is present for this task
List<ActualSubtaskData> subtaskData = (List) node.getAttribute(subtaskDataAttrs[i]);
if (subtaskData != null && !subtaskData.isEmpty()) {
// if subtask data is present, handle it.
// if the time estimate in the personal dashboard is
// out of sync with the WBS, the subtask times will
// add up to a different value than memberPlanTime.
// calculate this ratio so we can adjust EV.
double subtaskPlanTotal = 0;
for (ActualSubtaskData subtask : subtaskData) subtaskPlanTotal += subtask.getPlanTime();
double ratio = memberPlanTime / subtaskPlanTotal;
if (Double.isInfinite(ratio) || Double.isNaN(ratio))
ratio = 0;
// record each subtask as an independent task.
for (ActualSubtaskData subtask : subtaskData) {
if (subtask.getCompletionDate() != null) {
// this subtask was completed
if (rollupMember)
earnedValue[0] += subtask.getPlanTime() * ratio;
timeCalc[i].addCompletedTask(subtask.getPlanTime(), subtask.getActualTime(), milestone);
} else {
// this subtask is remaining
timeCalc[i].addRemainingTask(subtask.getPlanTime(), subtask.getActualTime(), milestone);
}
}
} else {
// there is no subtask data for this node. Just
// record a plain remaining task.
timeCalc[i].addRemainingTask(memberPlanTime, actualTime[i], milestone);
}
}
}
}
} else {
double[] childTime = new double[teamSize];
double[] childEarnedValue = new double[1];
long[] childCompletionDate = new long[1];
for (int i = 0; i < children.length; i++) {
// ask our child to compute its time data
recalculate(children[i], childTime, timeCalc, childEarnedValue, childCompletionDate);
// if the child isn't hidden, add its values to our totals
if (!children[i].isHidden()) {
// accumulate time from that child into our total
for (int j = 0; j < teamSize; j++) actualTime[j] += childTime[j];
// accumulate EV related data from our children
earnedValue[0] += childEarnedValue[0];
completionDate[0] = Math.max(completionDate[0], childCompletionDate[0]);
}
}
}
double totalActualTime = 0;
for (int i = 0; i < teamSize; i++) {
// add up the actual time for all included team members
if (rollupEveryone || matchesTeamFilter[i])
totalActualTime += actualTime[i];
// also store the total time per individual for this node
node.setNumericAttribute(actTimeAttrs[i], actualTime[i]);
}
// store the actual time for the entire team for this node.
node.setNumericAttribute(ACT_TIME_ATTR_NAME, totalActualTime);
// retrieve the total plan time for this node from the TeamTimeColumn.
double totalPlanTime = nanToZero(NumericDataValue.parse(dataModel.getValueAt(node, teamPlanTimeColumnNum)));
// calculate and store the percent spent
double percentSpent = totalActualTime / totalPlanTime;
node.setNumericAttribute(PercentSpentColumn.RESULT_ATTR, percentSpent);
// calculate and store the completion date and percent complete
Date cd = null;
double percentComplete = earnedValue[0] / totalPlanTime;
if (completionDate[0] != COMPL_DATE_NA && completionDate[0] != INCOMPLETE) {
cd = new Date(completionDate[0]);
percentComplete = 1.0;
}
node.setAttribute(TeamCompletionDateColumn.ATTR_NAME, cd);
node.setNumericAttribute(PercentCompleteColumn.RESULT_ATTR, percentComplete);
}
Aggregations