107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
|
import { Task, TaskCategory, TaskID, getTaskWeightedMean, TaskCategoryID, getTaskStandardDeviation } from './task';
|
||
|
import { Params, defaults, getTaskCategoryCost } from "./params";
|
||
|
import { Estimation } from '../hooks/useProjectEstimations';
|
||
|
|
||
|
export type ProjectID = string;
|
||
|
|
||
|
export interface Project {
|
||
|
id: ProjectID
|
||
|
label: string
|
||
|
description: string
|
||
|
tasks: Tasks
|
||
|
params: Params
|
||
|
}
|
||
|
|
||
|
export interface Tasks {
|
||
|
[id: string]: Task
|
||
|
}
|
||
|
|
||
|
export function newProject(): Project {
|
||
|
return {
|
||
|
id: "",
|
||
|
label: "",
|
||
|
description: "",
|
||
|
tasks: {},
|
||
|
params: {
|
||
|
...defaults
|
||
|
},
|
||
|
};
|
||
|
}
|
||
|
|
||
|
export function getProjectWeightedMean(p : Project): number {
|
||
|
return Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
||
|
sum += getTaskWeightedMean(t);
|
||
|
return sum;
|
||
|
}, 0);
|
||
|
}
|
||
|
|
||
|
export function getTaskCategoryWeightedMean(taskCategoryId: TaskCategoryID, p : Project): number {
|
||
|
return Object.values(p.tasks).filter(t => t.category === taskCategoryId).reduce((sum: number, t: Task) => {
|
||
|
sum += getTaskWeightedMean(t);
|
||
|
return sum;
|
||
|
}, 0);
|
||
|
}
|
||
|
|
||
|
export function getProjectStandardDeviation(p : Project): number {
|
||
|
return Math.sqrt(Object.values(p.tasks).reduce((sum: number, t: Task) => {
|
||
|
sum += Math.pow(getTaskStandardDeviation(t), 2);
|
||
|
return sum;
|
||
|
}, 0));
|
||
|
}
|
||
|
|
||
|
export interface MeanRepartition {
|
||
|
[id: string]: number
|
||
|
}
|
||
|
|
||
|
export function getTaskCategoriesMeanRepartition(project: Project): MeanRepartition {
|
||
|
let projectMean = getProjectWeightedMean(project);
|
||
|
|
||
|
const repartition: MeanRepartition = {};
|
||
|
|
||
|
Object.values(project.params.taskCategories).forEach(tc => {
|
||
|
repartition[tc.id] = getTaskCategoryWeightedMean(tc.id, project) / projectMean;
|
||
|
if (Number.isNaN(repartition[tc.id])) repartition[tc.id] = 0;
|
||
|
});
|
||
|
|
||
|
return repartition;
|
||
|
}
|
||
|
|
||
|
export interface MinMaxCost {
|
||
|
max: Cost
|
||
|
min: Cost
|
||
|
}
|
||
|
|
||
|
export interface Cost {
|
||
|
totalCost: number
|
||
|
totalTime: number
|
||
|
details: { [taskCategoryId: string]: { time: number, cost: number } }
|
||
|
}
|
||
|
|
||
|
export function getMinMaxCosts(project: Project, estimation: Estimation): MinMaxCost {
|
||
|
const max: Cost = {totalCost: 0, totalTime: 0, details: {}};
|
||
|
const min: Cost = {totalCost: 0, totalTime: 0, details: {}};
|
||
|
|
||
|
const repartition = getTaskCategoriesMeanRepartition(project);
|
||
|
|
||
|
Object.values(project.params.taskCategories).forEach(tc => {
|
||
|
const cost = getTaskCategoryCost(tc);
|
||
|
|
||
|
const maxTime = Math.round((estimation.e + estimation.sd) * repartition[tc.id]);
|
||
|
max.details[tc.id] = {
|
||
|
time: maxTime,
|
||
|
cost: Math.ceil(maxTime) * cost,
|
||
|
};
|
||
|
max.totalTime += max.details[tc.id].time;
|
||
|
max.totalCost += max.details[tc.id].cost;
|
||
|
|
||
|
const minTime = Math.round((estimation.e - estimation.sd) * repartition[tc.id]);
|
||
|
min.details[tc.id] = {
|
||
|
time: minTime,
|
||
|
cost: Math.ceil(minTime) * cost,
|
||
|
};
|
||
|
min.totalTime += min.details[tc.id].time;
|
||
|
min.totalCost += min.details[tc.id].cost;
|
||
|
});
|
||
|
|
||
|
return { max, min };
|
||
|
}
|