Skip to content

Commit

Permalink
Merge pull request #81 from bitovi/adjusted-story-points
Browse files Browse the repository at this point in the history
adds adjusted-story-points service
  • Loading branch information
justinbmeyer authored Aug 1, 2024
2 parents 06c5ef8 + cdb99c5 commit 96bfe74
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"jstat": "^1.9.6",
"oauth": "^0.10.0"
},
"devDependencies": {
Expand Down
57 changes: 57 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { fetchTokenWithAccessCode } = require('./helper')
const cors = require('cors');
const path = require('path');

const stats = require("./stats");
// configurations
dotenv.config()

Expand Down Expand Up @@ -62,6 +63,62 @@ app.get('/access-token', async (req, res) => {
}
});

const requiredFields = {
storyPointsMedian: function(value){
if(value === undefined) {
return {message: "storyPointsMedian is undefined"}
}
if(typeof value !== "number") {
return {message: "storyPointsMedian is not a number"}
}
if(value < 0 ) {
return {message: "storyPointsMedian is negative"}
}
},
storyPointsConfidence: function(value){
if(value === undefined) {
return {message: "storyPointsConfidence is undefined"}
}
if(typeof value !== "number") {
return {message: "storyPointsConfidence is not a number"}
}
if(value < 0 ) {
return {message: "storyPointsConfidence is negative"}
}
if(value > 100) {
return {message: "storyPointsConfidence is greater than 100"}
}
}
}
function validateAdjustedStoryPoints(req, res, next) {
const errors = [];
for(let requiredField in requiredFields) {
let error = requiredFields[requiredField](req.body[requiredField]);
if(error) {
errors.push(error)
}
}
if(errors.length) {
res.status(400).json({errors})
} else {
next();
}
}

app.post('/adjusted-story-points', validateAdjustedStoryPoints, (req, res) => {


const extraStoryPoints = stats.estimateExtraPoints(req.body.storyPointsMedian, req.body.storyPointsConfidence, req.body.riskThreshold || 80)
const adjustedStoryPoints = req.body.storyPointsMedian + extraStoryPoints;
// Respond with a status message
res.status(200).json({
adjustedStoryPoints,
extraStoryPoints,
roundedExtraStoryPoints: Math.round(extraStoryPoints),
roundedAdjustedStoryPoints: Math.round(adjustedStoryPoints)
});
});

// Start server
app.listen(port, () => console.log(`Server is listening on port ${port}!`))

Expand Down
28 changes: 28 additions & 0 deletions server/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const jStat = require("jstat");

function toStandardDeviations({
confidence,
highConfidenceStds = 0,
highConfidence = 100,
lowConfidenceStds = 1.3,
lowConfidence = 10
}){
const slope = -1 * (highConfidenceStds - lowConfidenceStds) / (highConfidence - lowConfidence)
const uncertainty = (100 - confidence);
return (uncertainty * slope);
}


const stats = {
estimateExtraPoints: function (estimate, confidence, uncertaintyWeight) {
var std = toStandardDeviations({confidence});
if(uncertaintyWeight === "average") {
return estimate * jStat.lognormal.mean( 0, std) - estimate;
} else {
return estimate * jStat.lognormal.inv( (uncertaintyWeight / 100) , 0, std) - estimate;
}

}
};

module.exports = stats;

0 comments on commit 96bfe74

Please sign in to comment.