├── .gitignore ├── .ipynb_checkpoints ├── Backlog_health_score-checkpoint.ipynb └── How to improve your velocity estimation with machine learning methods?-checkpoint.ipynb ├── Backlog health score-Extended version for blog.html ├── Backlog health score-Extended version for blog.ipynb ├── Backlog health score.html ├── Backlog_health_score.html ├── Backlog_health_score.nbconvert.ipynb ├── Bugs analysis.ipynb ├── Bugs classification by description.ipynb ├── Burndown chart for multiple versions.ipynb ├── Category reporting.ipynb ├── Changes in epic.ipynb ├── EMC Burndown chart.ipynb ├── EMC Scrum Board - Agile Board - JIRA.pdf ├── Epic costs estimation - extended version for blog.html ├── Epic costs estimation - extended version for blog.ipynb ├── Epic costs estimation.ipynb ├── How to improve your velocity estimation with machine learning methods?.ipynb ├── IC Apps Dashboard.ipynb ├── Prediction intervals in Agile forecasting.ipynb ├── R1 EMC estimation.ipynb ├── README.md ├── StableVelocity.csv ├── Story SP equals subtasks sum.ipynb ├── Untitled.ipynb ├── _config.yml ├── finishedItems.csv ├── images ├── boardId.png ├── rules_score.png └── scoring_chart_example.png ├── sprints.csv ├── sprintsData.csv └── timeInStatus.csv /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | .idea/JiraVersionsEstimator.iml 5 | 6 | .idea/misc.xml 7 | 8 | .idea/modules.xml 9 | 10 | .idea/workspace.xml 11 | 12 | .ipynb_checkpoints/JiraVersionsEstimator-checkpoint.ipynb 13 | 14 | notebook.tex 15 | 16 | output_2_1.png 17 | 18 | output_7_0.png 19 | 20 | .ipynb_checkpoints/Epic costs estimation-checkpoint.ipynb 21 | 22 | .ipynb_checkpoints/Changes in epic-checkpoint.ipynb 23 | 24 | .idea/vcs.xml 25 | 26 | .ipynb_checkpoints/Burndown chart for multiple versions-checkpoint.ipynb 27 | 28 | .ipynb_checkpoints/Epic costs estimation - extended version for blog-checkpoint.ipynb 29 | 30 | output_14_1.png 31 | 32 | .ipynb_checkpoints/SprintEstimation-checkpoint.ipynb 33 | 34 | SprintEstimation.ipynb 35 | 36 | .ipynb_checkpoints/Backlog health score-checkpoint.ipynb 37 | 38 | .ipynb_checkpoints/Burndown chart for multiple versions-Copy1-checkpoint.ipynb 39 | 40 | .ipynb_checkpoints/Backlog health score-Extended version for blog-checkpoint.ipynb 41 | 42 | .ipynb_checkpoints/Category reporting-checkpoint.ipynb 43 | 44 | .ipynb_checkpoints/Bugs analysis-checkpoint.ipynb 45 | 46 | .ipynb_checkpoints/Bugs classification by description-checkpoint.ipynb 47 | 48 | .ipynb_checkpoints/EMC Burndown chart-checkpoint.ipynb 49 | 50 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 51 | 52 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 53 | 54 | .ipynb_checkpoints/Prediction intervals in Agile forecasting-checkpoint.ipynb 55 | 56 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 57 | 58 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 59 | 60 | .ipynb_checkpoints/R1 EMC estimation-checkpoint.ipynb 61 | 62 | .ipynb_checkpoints/Story SP equals subtasks sum-checkpoint.ipynb 63 | 64 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 65 | 66 | .ipynb_checkpoints/Untitled-checkpoint.ipynb 67 | 68 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 69 | 70 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 71 | 72 | .ipynb_checkpoints/IC Apps Dashboard-checkpoint.ipynb 73 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/Backlog_health_score-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Quantified backlog state = Sum(Rule \\* Weight \\* 10)\n", 8 | "\n", 9 | "This will give the team clear information about backlog state score in a number from 1-10 (1 - worst, 10 - best )\n", 10 | "Weights need to sum up to 100%\n", 11 | "\n" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from jira import JIRA\n", 21 | "import matplotlib.pyplot as plt\n", 22 | "import pandas as pd\n", 23 | "import re\n", 24 | "from numpy import nan\n", 25 | "from IPython.core.interactiveshell import InteractiveShell\n", 26 | "InteractiveShell.ast_node_interactivity = \"all\"" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "\n", 36 | "\n", 37 | "#to show all rules descriptions\n", 38 | "pd.set_option('max_colwidth',200)\n", 39 | "\n", 40 | "rules = pd.DataFrame()\n", 41 | "rules['rule'] = ''\n", 42 | "rules['weight'] = 0\n", 43 | "rules['value'] = 0\n", 44 | "\n", 45 | "rules['rule'] = ['In next 2 sprints there are items in Backlog state that SP sum is equal to estimated velocity (+-20%) and status is not in presprint - Y/N',\n", 46 | " 'Planned next 3 versions (all items estimated and in Backlog or started state (not in presprint) - [% of planned sprints]',\n", 47 | " '% of Must, Urgent, Should items that are estimated regardless of status',\n", 48 | " '% of key milestone items estimated and in Backlog status']\n", 49 | "rules['weight'] = [0.4, 0.25, 0.25, 0.1]\n", 50 | "\n", 51 | "rules" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "Rules sum should be 1" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "sum(rules.weight)" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "metadata": {}, 73 | "source": [ 74 | "
\n", 75 | "Set up nextSprint, currentVersion, nextVersion values.\n", 76 | "\n", 77 | "They can be loaded automatically from Jira too.\n", 78 | "
" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "#velocity used to check if sprints are planned correctly\n", 88 | "estimatedVelocity = 20\n", 89 | "\n", 90 | "nextVersions = ['1.12', '1.13', '1.14', '1.15', '1.16']\n", 91 | "milestones = ['Frimley MVP']" 92 | ] 93 | }, 94 | { 95 | "cell_type": "code", 96 | "execution_count": null, 97 | "metadata": {}, 98 | "outputs": [], 99 | "source": [ 100 | "jira_url = 'https://kainos-evolve.atlassian.net'\n", 101 | "jira = JIRA(jira_url)" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": null, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [ 110 | "#download next 2 sprints names\n", 111 | "from jira.client import GreenHopper\n", 112 | "options = {'server': jira_url}\n", 113 | "gh = GreenHopper(options)\n", 114 | "sprintsRaw = gh.sprints(285)\n", 115 | "\n", 116 | "sprints = pd.DataFrame()\n", 117 | "sprints['name'] = ''\n", 118 | "sprints['state'] = ''\n", 119 | "\n", 120 | "for sprint in sprintsRaw:\n", 121 | " sprints = sprints.append(\n", 122 | " {\n", 123 | " 'name': sprint.name,\n", 124 | " 'state': sprint.state\n", 125 | " }, ignore_index=True)\n", 126 | "sprints = sprints.loc[(sprints['state'] == 'FUTURE')]\n", 127 | "sprints.sort_values(\"name\", inplace=True)\n", 128 | "\n", 129 | "nextSprints = sprints.head(2)['name'].tolist()\n", 130 | "nextSprints" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "#rule 0\n", 140 | "#In next 2 sprints there are items in Backlog state that SP sum is equal to estimated velocity (+-20%), \n", 141 | "#there are no items assigned to sprint that are not in presprint states - Y/N\n", 142 | "#bugs don't have to be estimated\n", 143 | "\n", 144 | "jql = 'sprint in (\"{}\") and status in (Backlog, \"Ready to test\") and type not in subTaskIssueTypes()'.format('\", \"'.join(nextSprints))\n", 145 | "jql\n", 146 | "\n", 147 | "issuesRaw = jira.search_issues(jql)\n", 148 | "\n", 149 | "issues = pd.DataFrame()\n", 150 | "issues['key'] = ''\n", 151 | "issues['sprint'] = ''\n", 152 | "issues['SP'] = ''\n", 153 | "issues['type'] = ''\n", 154 | "issues['status'] = ''\n", 155 | "issues['summary'] = ''\n", 156 | "\n", 157 | "for issue in issuesRaw:\n", 158 | " for rawSprint in issue.fields.customfield_10007:\n", 159 | " #unfortunately sprint information is encoded and regex is needed\n", 160 | " matches = re.search('name=(.*?),', rawSprint)\n", 161 | " currentSprint = matches.group(1)\n", 162 | " if(currentSprint in nextSprints):\n", 163 | " issues = issues.append(\n", 164 | " {\n", 165 | " 'key': issue.key,\n", 166 | " 'type': issue.fields.issuetype.name,\n", 167 | " 'status': issue.fields.status.name,\n", 168 | " 'SP' : issue.fields.customfield_10005,\n", 169 | " 'summary': issue.fields.summary,\n", 170 | " 'sprint' : currentSprint\n", 171 | " }, ignore_index=True)\n", 172 | "\n", 173 | "\n", 174 | "issues.fillna(value=nan, inplace=True) \n", 175 | "\n", 176 | "#bugs don't have to be estimated\n", 177 | "issues.loc[(issues['type'] == 'Bug') & (issues['SP'].isnull()), ['SP']] = 0\n", 178 | " \n", 179 | "\n", 180 | "issues\n", 181 | "#issues.fillna(0, inplace=True)\n", 182 | "\n", 183 | "\n", 184 | "sprints = issues.groupby(['sprint']).agg({'SP':'sum'})\n", 185 | "sprints\n", 186 | "\n", 187 | "sprints = sprints.loc[(sprints['SP'] >= estimatedVelocity - estimatedVelocity * 0.2) &\n", 188 | " (sprints['SP'] <= estimatedVelocity + estimatedVelocity * 0.2)]\n", 189 | "sprints\n", 190 | "# % of estimated sprints\n", 191 | "rule_value = len(sprints) / len(nextSprints)\n", 192 | "rule_value\n", 193 | "rules.at[0, 'value'] = rule_value" 194 | ] 195 | }, 196 | { 197 | "cell_type": "code", 198 | "execution_count": null, 199 | "metadata": {}, 200 | "outputs": [], 201 | "source": [ 202 | "#rule 1 Planned next 3 versions (all items estimated and in Backlog or started state (not in presprint) - Y/N\n", 203 | "jql = 'fixVersion in (\"{}\") and type not in subTaskIssueTypes()'.format('\", \"'.join(nextVersions))\n", 204 | "jql\n", 205 | "\n", 206 | "issuesRaw = jira.search_issues(jql)\n", 207 | "\n", 208 | "issues = pd.DataFrame()\n", 209 | "issues['key'] = ''\n", 210 | "issues['version'] = ''\n", 211 | "issues['SP'] = ''\n", 212 | "issues['type'] = ''\n", 213 | "issues['status'] = ''\n", 214 | "issues['summary'] = ''\n", 215 | "\n", 216 | "#add issues to dataframe\n", 217 | "for issue in issuesRaw:\n", 218 | " #issue may have many versions - in this approach, one version per issue is recommended\n", 219 | " for fixVersion in issue.fields.fixVersions:\n", 220 | " if(fixVersion.name in nextVersions):\n", 221 | " issues = issues.append(\n", 222 | " {'version': fixVersion.name, \n", 223 | " 'key': issue.key,\n", 224 | " 'type': issue.fields.issuetype.name,\n", 225 | " 'status': issue.fields.status.name,\n", 226 | " 'SP': issue.fields.customfield_10005,\n", 227 | " 'summary': issue.fields.summary,\n", 228 | " 'team' : str(issue.fields.customfield_14200),\n", 229 | " }, ignore_index=True)\n", 230 | " \n", 231 | "issues = issues.loc[~(issues['status'].isin(['Completed', 'Rejected']))]\n", 232 | "\n", 233 | "#indicate not estimated issues, bugs don't have to be estimated\n", 234 | "issues['isEstimated'] = False\n", 235 | "issues.loc[(issues['type'].isin(['Bug', 'Epic'])), ['isEstimated']] = True\n", 236 | "issues.loc[(issues['type'] != 'Bug') & ~(issues['SP'].isnull()), ['isEstimated']] = True\n", 237 | "\n", 238 | "#indicate items in presprint\n", 239 | "issues['inPresprint'] = False\n", 240 | "issues.loc[(issues['status'].isin(['Awaiting Prioritisation', 'PO Refinement', 'UX Refinement', 'QA Refinement', 'Tech Refinement', \n", 241 | " 'Tech Refinement', 'Estimation'])), ['inPresprint']] = True\n", 242 | "issues.sort_values(\"version\", inplace=True)\n", 243 | "\n", 244 | "issues = issues.loc[(issues['inPresprint'] == True) | (issues['isEstimated'] == False)]\n", 245 | "issues\n", 246 | "\n", 247 | "rule_value = len(issues) == 0\n", 248 | "rules.at[1, 'value'] = int(rule_value)\n" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [ 257 | "#rule 2\n", 258 | "# % of Must, Urgent, Should items that are estimated regardless of status\n", 259 | "# bugs don't have to be estimated\n", 260 | "jql = 'project = VXT and priority in (Must,Urgent,Should) and type != Bug and type not in subTaskIssueTypes()'\n", 261 | "jql\n", 262 | "\n", 263 | "issuesRaw = jira.search_issues(jql)\n", 264 | "\n", 265 | "issues = pd.DataFrame()\n", 266 | "issues['key'] = ''\n", 267 | "issues['version'] = ''\n", 268 | "issues['SP'] = ''\n", 269 | "issues['type'] = ''\n", 270 | "issues['status'] = ''\n", 271 | "issues['summary'] = ''\n", 272 | "issues['priority'] = ''\n", 273 | "\n", 274 | "#add issues to dataframe\n", 275 | "for issue in issuesRaw:\n", 276 | " #issue may have many versions - in this approach, one version per issue is recommended\n", 277 | " for fixVersion in issue.fields.fixVersions:\n", 278 | " if(fixVersion.name in nextVersions):\n", 279 | " issues = issues.append(\n", 280 | " {'version': fixVersion.name, \n", 281 | " 'key': issue.key,\n", 282 | " 'type': issue.fields.issuetype.name,\n", 283 | " 'status': issue.fields.status.name,\n", 284 | " 'SP': issue.fields.customfield_10005,\n", 285 | " 'summary': issue.fields.summary,\n", 286 | " 'priority' : str(issue.fields.priority.name),\n", 287 | " }, ignore_index=True)\n", 288 | "issues\n", 289 | "\n", 290 | "rule_value = round(len(issues.loc[~(issues['SP'].isnull())]) / len(issues), 2)\n", 291 | "rules.at[2, 'value'] = rule_value" 292 | ] 293 | }, 294 | { 295 | "cell_type": "code", 296 | "execution_count": null, 297 | "metadata": {}, 298 | "outputs": [], 299 | "source": [ 300 | "# % of key milestone items estimated and in Backlog status\n", 301 | "jql = 'project = VXT and type != Epic and status not in (Rejected, Completed) and type not in subTaskIssueTypes() and fixVersion in (\"' + '\", \"'.join(milestones) + '\")'\n", 302 | "jql\n", 303 | "\n", 304 | "issuesRaw = jira.search_issues(jql)\n", 305 | "\n", 306 | "issues = pd.DataFrame()\n", 307 | "issues['key'] = ''\n", 308 | "issues['version'] = ''\n", 309 | "issues['SP'] = ''\n", 310 | "issues['type'] = ''\n", 311 | "issues['status'] = ''\n", 312 | "issues['summary'] = ''\n", 313 | "\n", 314 | "#add issues to dataframe\n", 315 | "for issue in issuesRaw:\n", 316 | " #issue may have many versions - in this approach, one version per issue is recommended\n", 317 | " for fixVersion in issue.fields.fixVersions:\n", 318 | " if(fixVersion.name in milestones):\n", 319 | " issues = issues.append(\n", 320 | " {'version': fixVersion.name, \n", 321 | " 'key': issue.key,\n", 322 | " 'type': issue.fields.issuetype.name,\n", 323 | " 'status': issue.fields.status.name,\n", 324 | " 'SP': issue.fields.customfield_10005,\n", 325 | " 'summary': issue.fields.summary,\n", 326 | " }, ignore_index=True)\n", 327 | " \n", 328 | "issues['isEstimated'] = False\n", 329 | "issues.loc[(issues['type'] == 'Bug'), ['isEstimated']] = True\n", 330 | "issues.loc[(issues['type'] != 'Bug') & ~(issues['SP'].isnull()), ['isEstimated']] = True\n", 331 | "issues\n", 332 | "\n", 333 | "rule_value = round(len(issues.loc[(issues['isEstimated'] == True)]) / len(issues), 2)\n", 334 | "rule_value\n", 335 | "rules.at[3, 'value'] = rule_value" 336 | ] 337 | }, 338 | { 339 | "cell_type": "code", 340 | "execution_count": null, 341 | "metadata": {}, 342 | "outputs": [], 343 | "source": [ 344 | "rules['score'] = rules.weight * rules.value * 10\n", 345 | "rules" 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": null, 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [ 354 | "score = round(sum(rules['score']), 2)\n", 355 | "\n", 356 | "#refresh stored data\n", 357 | "%store -r\n", 358 | "\n", 359 | "#if data frame for scores is not loaded create it\n", 360 | "if not 'scores' in globals():\n", 361 | " scores = pd.DataFrame()\n", 362 | " scores['date'] = ''\n", 363 | " scores['score'] = 0\n", 364 | "\n", 365 | "#append latest score\n", 366 | "scores = scores.append(\n", 367 | " {\n", 368 | " 'date': pd.to_datetime('now'),\n", 369 | " 'score': score,\n", 370 | " }, ignore_index=True)\n", 371 | "\n", 372 | "#store scores for later use\n", 373 | "%store scores\n", 374 | "\n", 375 | "print(\"Backlog score {} / 10\".format(score))\n", 376 | "\n", 377 | "_ = plt.plot(scores['date'], scores['score'], \"-r.\")\n", 378 | "\n", 379 | "_ = plt.xticks(rotation='vertical')\n", 380 | "_ = plt.ylabel('Backlog score')\n", 381 | "_ = plt.xlabel('Time')\n", 382 | "axes = plt.gca()\n", 383 | "axes.set_ylim([0,10])\n", 384 | "\n" 385 | ] 386 | } 387 | ], 388 | "metadata": { 389 | "kernelspec": { 390 | "display_name": "Python 2", 391 | "language": "python", 392 | "name": "python2" 393 | }, 394 | "language_info": { 395 | "codemirror_mode": { 396 | "name": "ipython", 397 | "version": 2 398 | }, 399 | "file_extension": ".py", 400 | "mimetype": "text/x-python", 401 | "name": "python", 402 | "nbconvert_exporter": "python", 403 | "pygments_lexer": "ipython2", 404 | "version": "2.7.11" 405 | } 406 | }, 407 | "nbformat": 4, 408 | "nbformat_minor": 2 409 | } 410 | -------------------------------------------------------------------------------- /Bugs analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from jira import JIRA\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "import pandas as pd\n", 12 | "import re\n", 13 | "from numpy import nan\n", 14 | "from IPython.core.interactiveshell import InteractiveShell\n", 15 | "InteractiveShell.ast_node_interactivity = \"all\"" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "jira_url = 'https://kainos-evolve.atlassian.net'\n", 25 | "jira = JIRA(jira_url)" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [] 34 | } 35 | ], 36 | "metadata": { 37 | "kernelspec": { 38 | "display_name": "Python 3", 39 | "language": "python", 40 | "name": "python3" 41 | }, 42 | "language_info": { 43 | "codemirror_mode": { 44 | "name": "ipython", 45 | "version": 3 46 | }, 47 | "file_extension": ".py", 48 | "mimetype": "text/x-python", 49 | "name": "python", 50 | "nbconvert_exporter": "python", 51 | "pygments_lexer": "ipython3", 52 | "version": "3.5.1" 53 | } 54 | }, 55 | "nbformat": 4, 56 | "nbformat_minor": 2 57 | } 58 | -------------------------------------------------------------------------------- /Bugs classification by description.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "ename": "SyntaxError", 10 | "evalue": "invalid syntax (, line 33)", 11 | "output_type": "error", 12 | "traceback": [ 13 | "\u001b[0;36m File \u001b[0;32m\"\"\u001b[0;36m, line \u001b[0;32m33\u001b[0m\n\u001b[0;31m print(\"\\nCluster %d:\" % i, end='')\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "import pandas as pd\n", 19 | "from sklearn.feature_extraction.text import TfidfVectorizer\n", 20 | "from sklearn.pipeline import Pipeline\n", 21 | "from sklearn.cluster import KMeans\n", 22 | "from time import time\n", 23 | "from sklearn import metrics\n", 24 | "\n", 25 | "issues = pd.read_pickle('/Users/robertk/PycharmProjects/EvolveAnalysis/lastYearBugs.pickle')\n", 26 | "issues = issues.replace({'does': ''}, regex=True)\n", 27 | "#issues = issues['summary'].str.replace('does' , '')\n", 28 | "\n", 29 | "\n", 30 | "vectorizer = TfidfVectorizer(stop_words='english', min_df = 10)\n", 31 | "\n", 32 | "X = vectorizer.fit_transform(issues.summary)\n", 33 | "\n", 34 | "\n", 35 | "n_clust = 50\n", 36 | "km = KMeans(n_clusters=n_clust, init='k-means++', max_iter=100, n_init=10,\n", 37 | " verbose=True)\n", 38 | "\n", 39 | "print(\"Clustering sparse data with %s\" % km)\n", 40 | "t0 = time()\n", 41 | "km.fit(X)\n", 42 | "print(\"done in %0.3fs\" % (time() - t0))\n", 43 | "print()\n", 44 | "\n", 45 | "order_centroids = km.cluster_centers_.argsort()[:, ::-1]\n", 46 | "\n", 47 | "terms = vectorizer.get_feature_names()\n", 48 | "\n", 49 | "for i in range(n_clust):\n", 50 | " print(\"\\nCluster %d:\" % i, end='')\n", 51 | " for ind in order_centroids[i, :10]:\n", 52 | " print(' %s' % terms[ind], end='')\n", 53 | "print('\\n')\n", 54 | "se = pd.Series(km.labels_.tolist())\n", 55 | "\n", 56 | "\n", 57 | "issues['cluster'] = se.values\n", 58 | "issues.reset_index()\n", 59 | "print(issues.columns)\n", 60 | "\n", 61 | "\n", 62 | "\n", 63 | "#aggrIssues = aggrIssues.reset_index()\n", 64 | "\n", 65 | "#print(aggrIssues.loc[aggrIssues[1].idxmax()])\n", 66 | "#label4Issues = issues.loc[(issues.label == 4)]\n", 67 | "#print(label4Issues)\n", 68 | "\n", 69 | "\n", 70 | "\n", 71 | "\n", 72 | "\n" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 1, 78 | "metadata": {}, 79 | "outputs": [ 80 | { 81 | "ename": "NameError", 82 | "evalue": "name 'issues' is not defined", 83 | "output_type": "error", 84 | "traceback": [ 85 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 86 | "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", 87 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0maggrIssues\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0missues\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgroupby\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'cluster'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0magg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0;34m'cluster'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m'count'\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mas_index\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;31m#aggrIssues = aggrIssues.reset_index()\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m#aggrIssues.columns = ['label', 'count']\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;32mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0maggrIssues\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 88 | "\u001b[0;31mNameError\u001b[0m: name 'issues' is not defined" 89 | ] 90 | } 91 | ], 92 | "source": [ 93 | "aggrIssues = issues.groupby(['cluster']).agg({'cluster':'count'}, as_index=False)\n", 94 | "#aggrIssues = aggrIssues.reset_index()\n", 95 | "#aggrIssues.columns = ['label', 'count']\n", 96 | "print(aggrIssues)" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [] 105 | } 106 | ], 107 | "metadata": { 108 | "kernelspec": { 109 | "display_name": "Python 2", 110 | "language": "python", 111 | "name": "python2" 112 | }, 113 | "language_info": { 114 | "codemirror_mode": { 115 | "name": "ipython", 116 | "version": 2 117 | }, 118 | "file_extension": ".py", 119 | "mimetype": "text/x-python", 120 | "name": "python", 121 | "nbconvert_exporter": "python", 122 | "pygments_lexer": "ipython2", 123 | "version": "2.7.11" 124 | } 125 | }, 126 | "nbformat": 4, 127 | "nbformat_minor": 2 128 | } 129 | -------------------------------------------------------------------------------- /Category reporting.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Reporting days spent on development activity category in given month using Story Points" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 244, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from jira import JIRA\n", 17 | "import matplotlib.pyplot as plt\n", 18 | "import pandas as pd\n", 19 | "import numpy as np\n", 20 | "from dateutil.rrule import rrule, MONTHLY\n", 21 | "from datetime import datetime\n", 22 | "import calendar\n", 23 | "import datetime\n", 24 | "\n", 25 | "from IPython.core.interactiveshell import InteractiveShell\n", 26 | "InteractiveShell.ast_node_interactivity = \"all\"\n", 27 | "\n", 28 | "jira_url = 'https://kainos-evolve.atlassian.net'\n", 29 | "jira = JIRA(jira_url)" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": 245, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "issues = pd.DataFrame()\n", 39 | "issues['key'] = ''\n", 40 | "issues['category'] = ''\n", 41 | "issues['type'] = ''\n", 42 | "issues['team'] = ''\n", 43 | "issues['SP'] = ''\n", 44 | "issues['bugEffort'] = ''\n", 45 | "issues['status'] = ''\n", 46 | "issues['summary'] = ''\n", 47 | "issues['month'] = ''" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 246, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "year = 2018" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 247, 62 | "metadata": { 63 | "scrolled": false 64 | }, 65 | "outputs": [ 66 | { 67 | "data": { 68 | "text/plain": [ 69 | "'project = vxt and status = Completed AND status changed to Completed during (2018-1-1, 2018-1-31) AND type in (bug, \"External Bug\", story)'" 70 | ] 71 | }, 72 | "execution_count": 247, 73 | "metadata": {}, 74 | "output_type": "execute_result" 75 | }, 76 | { 77 | "data": { 78 | "text/plain": [ 79 | "'project = vxt and status = Completed AND status changed to Completed during (2018-2-1, 2018-2-28) AND type in (bug, \"External Bug\", story)'" 80 | ] 81 | }, 82 | "execution_count": 247, 83 | "metadata": {}, 84 | "output_type": "execute_result" 85 | } 86 | ], 87 | "source": [ 88 | "# download data for each month of a year defined previously\n", 89 | "for i in range(1,13):\n", 90 | " lastDay = calendar.monthrange(year,i)[1]\n", 91 | " #don't query Jira for months in the future\n", 92 | " now = datetime.datetime.now()\n", 93 | " if i > now.month: break\n", 94 | " \n", 95 | " jql = 'project = vxt and status = Completed AND status changed to Completed during ({}-{}-1, {}-{}-{}) \\\n", 96 | " AND type in (bug, \"External Bug\", story)'.format(year, i, year, i, lastDay)\n", 97 | "\n", 98 | " issuesRaw = jira.search_issues(jql, maxResults = False)\n", 99 | " jql\n", 100 | " for issue in issuesRaw:\n", 101 | "\n", 102 | " try:\n", 103 | " bugEffort = issue.fields.customfield_14703\n", 104 | " except AttributeError:\n", 105 | " bugEffort = np.nan\n", 106 | "\n", 107 | " try:\n", 108 | " team = str(issue.fields.customfield_14200.value)\n", 109 | " except AttributeError:\n", 110 | " team = np.nan\n", 111 | "\n", 112 | " #category is stored in custom field\n", 113 | " try:\n", 114 | " category = str(issue.fields.customfield_14709.value)\n", 115 | " except AttributeError:\n", 116 | " category = 'Not set'\n", 117 | "\n", 118 | " issues = issues.append(\n", 119 | " {\n", 120 | " 'key': issue.key,\n", 121 | " 'type': issue.fields.issuetype.name,\n", 122 | " 'status': issue.fields.status.name,\n", 123 | " 'category': issue.fields.customfield_14709,\n", 124 | " 'bugEffort': bugEffort,\n", 125 | " 'SP' : issue.fields.customfield_10005,\n", 126 | " 'summary': issue.fields.summary,\n", 127 | " 'team': team,\n", 128 | " 'month': i\n", 129 | " }, ignore_index=True)\n", 130 | "\n" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 248, 136 | "metadata": { 137 | "scrolled": false 138 | }, 139 | "outputs": [], 140 | "source": [ 141 | "#use bug effort if SP is empty for bug type issues\n", 142 | "issues.loc[(issues['type'] == 'Bug') & (issues['SP'].isnull()), [\"SP\"]] = issues.bugEffort\n", 143 | "\n", 144 | "#filter teams\n", 145 | "issues = issues.loc[(issues['team'].isin([\"Gdansk Team 1\", \"Gdansk Team 2\", \"Belfast Team\"]))]\n" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": 249, 151 | "metadata": {}, 152 | "outputs": [ 153 | { 154 | "data": { 155 | "text/plain": [ 156 | "'not estimated issues or issues with zero SP'" 157 | ] 158 | }, 159 | "execution_count": 249, 160 | "metadata": {}, 161 | "output_type": "execute_result" 162 | }, 163 | { 164 | "data": { 165 | "text/html": [ 166 | "
\n", 167 | "\n", 180 | "\n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | "
keycategorytypeteamSPbugEffortstatussummarymonth
34VXT-3810IC Platform - NFRsStoryGdansk Team 10.0NaNCompletedReimplementation of password reset endpoint1
40VXT-3716IC Platform - Core ServicesStoryGdansk Team 10.0NaNCompletedCluster testing of Keycloak1
43VXT-3685Care Pathway Framework - ControlsStoryGdansk Team 20.0NaNCompletedupdate doc and ui tests1
63VXT-2593IC Platform - NFRsStoryGdansk Team 10.0NaNCompletedHTML5 CORS Misconfiguration1
64VXT-2435IC Platform - Core ServicesStoryGdansk Team 10.0NaNCompletedImplement consul-template for BPM service1
65VXT-2428IC Platform - Core ServicesStoryGdansk Team 10.0NaNCompletedImplement consul-template for Integration-webs...1
66VXT-2427IC Platform - Core ServicesStoryGdansk Team 10.0NaNCompletedImplement consul-template for Integration-engi...1
\n", 282 | "
" 283 | ], 284 | "text/plain": [ 285 | " key category type team SP \\\n", 286 | "34 VXT-3810 IC Platform - NFRs Story Gdansk Team 1 0.0 \n", 287 | "40 VXT-3716 IC Platform - Core Services Story Gdansk Team 1 0.0 \n", 288 | "43 VXT-3685 Care Pathway Framework - Controls Story Gdansk Team 2 0.0 \n", 289 | "63 VXT-2593 IC Platform - NFRs Story Gdansk Team 1 0.0 \n", 290 | "64 VXT-2435 IC Platform - Core Services Story Gdansk Team 1 0.0 \n", 291 | "65 VXT-2428 IC Platform - Core Services Story Gdansk Team 1 0.0 \n", 292 | "66 VXT-2427 IC Platform - Core Services Story Gdansk Team 1 0.0 \n", 293 | "\n", 294 | " bugEffort status summary \\\n", 295 | "34 NaN Completed Reimplementation of password reset endpoint \n", 296 | "40 NaN Completed Cluster testing of Keycloak \n", 297 | "43 NaN Completed update doc and ui tests \n", 298 | "63 NaN Completed HTML5 CORS Misconfiguration \n", 299 | "64 NaN Completed Implement consul-template for BPM service \n", 300 | "65 NaN Completed Implement consul-template for Integration-webs... \n", 301 | "66 NaN Completed Implement consul-template for Integration-engi... \n", 302 | "\n", 303 | " month \n", 304 | "34 1 \n", 305 | "40 1 \n", 306 | "43 1 \n", 307 | "63 1 \n", 308 | "64 1 \n", 309 | "65 1 \n", 310 | "66 1 " 311 | ] 312 | }, 313 | "execution_count": 249, 314 | "metadata": {}, 315 | "output_type": "execute_result" 316 | } 317 | ], 318 | "source": [ 319 | "issues['SP']=issues['SP'].fillna(0)\n", 320 | "\n", 321 | "'not estimated issues or issues with zero SP'\n", 322 | "notEstimated = issues.loc[(issues['type'] == 'Story') & (issues['SP'] == 0)]\n", 323 | "notEstimated\n", 324 | "\n", 325 | "\n", 326 | "#issues['category']=issues['category'].fillna('Not set')\n", 327 | "issues['category'] = issues.category.astype(str)\n", 328 | "\n" 329 | ] 330 | }, 331 | { 332 | "cell_type": "code", 333 | "execution_count": 250, 334 | "metadata": {}, 335 | "outputs": [ 336 | { 337 | "data": { 338 | "text/html": [ 339 | "
\n", 340 | "\n", 353 | "\n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | "
monthcategorySPSPPercDays
01Care Pathway Framework - Apps2.00.044.04
11Care Pathway Framework - Controls24.50.4949.49
21IC Platform - Core Services3.00.066.06
31IC Platform - NFRs15.00.3030.30
41Integration & Connector Framework5.00.1010.10
52Care Pathway Framework - Apps3.00.087.69
62Care Pathway Framework - Controls14.50.3737.18
72IC Platform - Core Services16.00.4141.03
82IC Platform - NFRs2.00.055.13
92Integration & Connector Framework3.50.098.97
\n", 447 | "
" 448 | ], 449 | "text/plain": [ 450 | " month category SP SPPerc Days\n", 451 | "0 1 Care Pathway Framework - Apps 2.0 0.04 4.04\n", 452 | "1 1 Care Pathway Framework - Controls 24.5 0.49 49.49\n", 453 | "2 1 IC Platform - Core Services 3.0 0.06 6.06\n", 454 | "3 1 IC Platform - NFRs 15.0 0.30 30.30\n", 455 | "4 1 Integration & Connector Framework 5.0 0.10 10.10\n", 456 | "5 2 Care Pathway Framework - Apps 3.0 0.08 7.69\n", 457 | "6 2 Care Pathway Framework - Controls 14.5 0.37 37.18\n", 458 | "7 2 IC Platform - Core Services 16.0 0.41 41.03\n", 459 | "8 2 IC Platform - NFRs 2.0 0.05 5.13\n", 460 | "9 2 Integration & Connector Framework 3.5 0.09 8.97" 461 | ] 462 | }, 463 | "execution_count": 250, 464 | "metadata": {}, 465 | "output_type": "execute_result" 466 | } 467 | ], 468 | "source": [ 469 | "#group by by month and category and sum SP\n", 470 | "issuesByCategory = issues.groupby(['month', 'category']).agg({'SP':'sum'})\n", 471 | "\n", 472 | "#add column with SP % per month\n", 473 | "issuesByCategory['SPPerc'] = issuesByCategory.groupby(level=[0]).apply(lambda x: x / float(x.sum()))\n", 474 | "#reindexing needed to relate month to each row \n", 475 | "issuesByCategory = issuesByCategory.reset_index()\n", 476 | "\n", 477 | "#days from Kimble used for calculations\n", 478 | "monthDevDays = [100, 100]\n", 479 | "\n", 480 | "#first add days from monthDevDays for specified month\n", 481 | "issuesByCategory['Days'] = issuesByCategory['month'].apply(lambda x: monthDevDays[x-1])\n", 482 | "#then multiply by % of month spent on category\n", 483 | "issuesByCategory['Days'] = issuesByCategory['Days'] * issuesByCategory['SPPerc']\n", 484 | "#round values\n", 485 | "issuesByCategory = issuesByCategory.round({'SPPerc': 2, 'Days':2})\n", 486 | "\n", 487 | "#\n", 488 | "issuesByCategory\n" 489 | ] 490 | } 491 | ], 492 | "metadata": { 493 | "kernelspec": { 494 | "display_name": "Python 3", 495 | "language": "python", 496 | "name": "python3" 497 | }, 498 | "language_info": { 499 | "codemirror_mode": { 500 | "name": "ipython", 501 | "version": 3 502 | }, 503 | "file_extension": ".py", 504 | "mimetype": "text/x-python", 505 | "name": "python", 506 | "nbconvert_exporter": "python", 507 | "pygments_lexer": "ipython3", 508 | "version": "3.5.1" 509 | } 510 | }, 511 | "nbformat": 4, 512 | "nbformat_minor": 2 513 | } 514 | -------------------------------------------------------------------------------- /Changes in epic.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Lists changes in issues linked to specified epics" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Specify epics" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "epic = ['VXT-3080', 'VXT-3952']" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import pandas as pd\n", 33 | "from jira import JIRA\n", 34 | "\n", 35 | "#store credentials in ~/.rcnet file\n", 36 | "jira = JIRA('https://kainos-evolve.atlassian.net')\n", 37 | "jql = '\"Epic Link\" in (' + \", \".join(epic) + ')'\n", 38 | "\n", 39 | "print(jql)\n", 40 | "epicIssues = jira.search_issues(jql)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": { 47 | "scrolled": false 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "from pandas.tseries.offsets import BDay\n", 52 | "\n", 53 | "changes = pd.DataFrame()\n", 54 | "changes['epic'] = ''\n", 55 | "changes['key'] = ''\n", 56 | "changes['summary'] = ''\n", 57 | "changes['updateDate'] = ''\n", 58 | "changes['field'] = ''\n", 59 | "changes['oldValue'] = ''\n", 60 | "changes['newValue'] = ''\n", 61 | "changes['author'] = ''\n", 62 | "\n", 63 | "for issue in epicIssues:\n", 64 | " issue = jira.issue(issue.key, expand='changelog')\n", 65 | " changelog = issue.changelog\n", 66 | " for history in changelog.histories:\n", 67 | " for item in history.items:\n", 68 | " try:\n", 69 | " author = history.author.name\n", 70 | " except AttributeError:\n", 71 | " author = 'NotFound'\n", 72 | " \n", 73 | " changes = changes.append(\n", 74 | " {'key': issue.key,\n", 75 | " 'updateDate': history.created,\n", 76 | " 'author': author,\n", 77 | " 'field': item.field,\n", 78 | " 'oldValue': item.fromString,\n", 79 | " 'newValue': item.toString,\n", 80 | " 'summary': issue.fields.summary,\n", 81 | " 'epic': issue.fields.customfield_10008\n", 82 | " }, ignore_index=True)\n", 83 | "\n", 84 | "\n", 85 | "#sort values\n", 86 | "changes.sort_values([\"key\", \"updateDate\",], inplace=True)\n", 87 | "\n", 88 | "#show only items from last 5 business days\n", 89 | "changes = changes.loc[(pd.to_datetime(changes['updateDate']) > (pd.to_datetime('today') - BDay(5)))]\n", 90 | "\n", 91 | "#don't show changes in following fields\n", 92 | "changes = changes.loc[~(changes['field'].isin(['status', 'assignee', 'Sprint', 'reporter']))]\n", 93 | "changes" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": {}, 100 | "outputs": [], 101 | "source": [ 102 | "\n", 103 | "\n" 104 | ] 105 | } 106 | ], 107 | "metadata": { 108 | "kernelspec": { 109 | "display_name": "Python 3", 110 | "language": "python", 111 | "name": "python3" 112 | }, 113 | "language_info": { 114 | "codemirror_mode": { 115 | "name": "ipython", 116 | "version": 3 117 | }, 118 | "file_extension": ".py", 119 | "mimetype": "text/x-python", 120 | "name": "python", 121 | "nbconvert_exporter": "python", 122 | "pygments_lexer": "ipython3", 123 | "version": "3.5.1" 124 | } 125 | }, 126 | "nbformat": 4, 127 | "nbformat_minor": 2 128 | } 129 | -------------------------------------------------------------------------------- /EMC Scrum Board - Agile Board - JIRA.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robert-krasinski/JiraAndPythonForManagers/9623a054636231e7d97cac5e244a08bb8ffe4dc2/EMC Scrum Board - Agile Board - JIRA.pdf -------------------------------------------------------------------------------- /Epic costs estimation - extended version for blog.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Epic remaining cost estimation based on team velocity" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Tools\n", 15 | "This post was entirely created using Jupyter notebook - if you don't know it I'd highly recommend to install it on your machine: http://jupyter.readthedocs.io/en/latest/install.html Then you could reproduce this example with your Jira data.\n", 16 | "\n", 17 | "This and other ideas on how to use Python with Jira data are available on my GitHub: https://github.com/robert-krasinski/JiraAndPythonForManagers\n", 18 | "\n", 19 | "## Rationale\n", 20 | "Feature cost estimation is very important information in development process. It should be always present during feature prioritization excercises. Using the estimated cost and projected profits should give you information which stories are most profitable and affect priorities for coming sprints.\n", 21 | "\n", 22 | "In this approach I'm not taking into consideration costs of not implementing particular features. In some situations those will be much higher than development costs. For example not fixing security bug may cause data leak and legal costs or not improving performance of the service can increase infrastructure costs.\n", 23 | "\n", 24 | "## Calculations\n", 25 | "\n", 26 | "I'd like to show you how quickly estimate cost for all open (not Completed, Rejected) epics in the project. There are few assumptions that are made to ensure that calculations will be possible:\n", 27 | "* epics are divided into stories that are linked to epics in Jira\n", 28 | "* stories are estimated in SP\n", 29 | "* we know the maximum, minimum and average velocity of the team. I'll show later how to obtain it from Jira report.\n", 30 | "* we know what's the sprint development cost for entire team\n", 31 | "* project is developed by one development team.\n", 32 | "\n", 33 | "The model estimates only not completed stories that are assigned to epics in Jira. \n", 34 | "In this approach bugs are not estimated in sprints and they're affecting calculations only trough team velocity. More bugs - velocity is lower and the cost of the epic will be higher and vice versa.\n", 35 | "\n", 36 | "I marked green sections that should be modified by when executing on different data source (e.g. Jira url, velocity)." 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "
\n", 44 | "Set Jira url\n", 45 | "
" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 2, 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "jira_url = 'https://kainos-evolve.atlassian.net'" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "metadata": {}, 60 | "source": [ 61 | "Code below loads all open epics from Jira using Python API. Credentials are stored in ~/.rcnet file. " 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 3, 67 | "metadata": {}, 68 | "outputs": [ 69 | { 70 | "name": "stderr", 71 | "output_type": "stream", 72 | "text": [ 73 | "WARNING:root:HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',)) while doing GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo [{'headers': {'X-Atlassian-Token': 'no-check', 'Cache-Control': 'no-cache', 'Content-Type': 'application/json', 'Accept': 'application/json,*.*;q=0.9', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate'}, 'params': None}]\n", 74 | "WARNING:root:Got ConnectionError [HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))] errno:None on GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo\n", 75 | "{'request': , 'response': None}\\{'request': , 'response': None}\n", 76 | "WARNING:root:Got recoverable error from GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo, will retry [1/3] in 10.972779475555285s. Err: HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))\n", 77 | "WARNING:root:HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',)) while doing GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo [{'headers': {'X-Atlassian-Token': 'no-check', 'Cache-Control': 'no-cache', 'Content-Type': 'application/json', 'Accept': 'application/json,*.*;q=0.9', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate'}, 'params': None}]\n", 78 | "WARNING:root:Got ConnectionError [HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))] errno:None on GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo\n", 79 | "{'request': , 'response': None}\\{'request': , 'response': None}\n", 80 | "WARNING:root:Got recoverable error from GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo, will retry [2/3] in 25.585144564476696s. Err: HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))\n", 81 | "WARNING:root:HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',)) while doing GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo [{'headers': {'X-Atlassian-Token': 'no-check', 'Cache-Control': 'no-cache', 'Content-Type': 'application/json', 'Accept': 'application/json,*.*;q=0.9', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate'}, 'params': None}]\n", 82 | "WARNING:root:Got ConnectionError [HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))] errno:None on GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo\n", 83 | "{'request': , 'response': None}\\{'request': , 'response': None}\n", 84 | "WARNING:root:Got recoverable error from GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo, will retry [3/3] in 12.716604228056386s. Err: HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))\n", 85 | "WARNING:root:HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',)) while doing GET https://kainos-evolve.atlassian.net/rest/api/2/serverInfo [{'headers': {'X-Atlassian-Token': 'no-check', 'Cache-Control': 'no-cache', 'Content-Type': 'application/json', 'Accept': 'application/json,*.*;q=0.9', 'Connection': 'keep-alive', 'User-Agent': 'python-requests/2.18.4', 'Accept-Encoding': 'gzip, deflate'}, 'params': None}]\n" 86 | ] 87 | }, 88 | { 89 | "ename": "ConnectionError", 90 | "evalue": "HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))", 91 | "output_type": "error", 92 | "traceback": [ 93 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 94 | "\u001b[0;31mPermissionError\u001b[0m Traceback (most recent call last)", 95 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/connection.py\u001b[0m in \u001b[0;36m_new_conn\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 140\u001b[0m conn = connection.create_connection(\n\u001b[0;32m--> 141\u001b[0;31m (self.host, self.port), self.timeout, **extra_kw)\n\u001b[0m\u001b[1;32m 142\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 96 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/util/connection.py\u001b[0m in \u001b[0;36mcreate_connection\u001b[0;34m(address, timeout, source_address, socket_options)\u001b[0m\n\u001b[1;32m 82\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0merr\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 83\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 84\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 97 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/util/connection.py\u001b[0m in \u001b[0;36mcreate_connection\u001b[0;34m(address, timeout, source_address, socket_options)\u001b[0m\n\u001b[1;32m 72\u001b[0m \u001b[0msock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbind\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msource_address\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 73\u001b[0;31m \u001b[0msock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msa\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 74\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msock\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 98 | "\u001b[0;31mPermissionError\u001b[0m: [Errno 1] Operation not permitted", 99 | "\nDuring handling of the above exception, another exception occurred:\n", 100 | "\u001b[0;31mNewConnectionError\u001b[0m Traceback (most recent call last)", 101 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36murlopen\u001b[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001b[0m\n\u001b[1;32m 600\u001b[0m \u001b[0mbody\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mbody\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 601\u001b[0;31m chunked=chunked)\n\u001b[0m\u001b[1;32m 602\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 102 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36m_make_request\u001b[0;34m(self, conn, method, url, timeout, chunked, **httplib_request_kw)\u001b[0m\n\u001b[1;32m 345\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 346\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_validate_conn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 347\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mSocketTimeout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mBaseSSLError\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 103 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36m_validate_conn\u001b[0;34m(self, conn)\u001b[0m\n\u001b[1;32m 849\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mconn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'sock'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# AppEngine might not have `.sock`\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 850\u001b[0;31m \u001b[0mconn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconnect\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 851\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 104 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/connection.py\u001b[0m in \u001b[0;36mconnect\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 283\u001b[0m \u001b[0;31m# Add certificate verification\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 284\u001b[0;31m \u001b[0mconn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_new_conn\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 285\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 105 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/connection.py\u001b[0m in \u001b[0;36m_new_conn\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 149\u001b[0m raise NewConnectionError(\n\u001b[0;32m--> 150\u001b[0;31m self, \"Failed to establish a new connection: %s\" % e)\n\u001b[0m\u001b[1;32m 151\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 106 | "\u001b[0;31mNewConnectionError\u001b[0m: : Failed to establish a new connection: [Errno 1] Operation not permitted", 107 | "\nDuring handling of the above exception, another exception occurred:\n", 108 | "\u001b[0;31mMaxRetryError\u001b[0m Traceback (most recent call last)", 109 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/requests/adapters.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[1;32m 439\u001b[0m \u001b[0mretries\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmax_retries\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 440\u001b[0;31m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 441\u001b[0m )\n", 110 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/connectionpool.py\u001b[0m in \u001b[0;36murlopen\u001b[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001b[0m\n\u001b[1;32m 638\u001b[0m retries = retries.increment(method, url, error=e, _pool=self,\n\u001b[0;32m--> 639\u001b[0;31m _stacktrace=sys.exc_info()[2])\n\u001b[0m\u001b[1;32m 640\u001b[0m \u001b[0mretries\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 111 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/urllib3/util/retry.py\u001b[0m in \u001b[0;36mincrement\u001b[0;34m(self, method, url, response, error, _pool, _stacktrace)\u001b[0m\n\u001b[1;32m 387\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mnew_retry\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mis_exhausted\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 388\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mMaxRetryError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_pool\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0merror\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mResponseError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcause\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 389\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", 112 | "\u001b[0;31mMaxRetryError\u001b[0m: HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))", 113 | "\nDuring handling of the above exception, another exception occurred:\n", 114 | "\u001b[0;31mConnectionError\u001b[0m Traceback (most recent call last)", 115 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mpandas\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mpd\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mjira\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mJIRA\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mjira\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mJIRA\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mjira_url\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;31m#load all open epics\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 116 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/jira/client.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, server, options, basic_auth, oauth, jwt, kerberos, kerberos_options, validate, get_server_info, async, logging, max_retries, proxies, timeout)\u001b[0m\n\u001b[1;32m 344\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mget_server_info\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 345\u001b[0m \u001b[0;31m# We need version in order to know what API calls are available or not\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 346\u001b[0;31m \u001b[0msi\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mserver_info\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 347\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 348\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_version\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msi\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'versionNumbers'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 117 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/jira/client.py\u001b[0m in \u001b[0;36mserver_info\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 1941\u001b[0m \u001b[0;34m\"\"\"Get a dict of server information for this JIRA instance.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1942\u001b[0m \u001b[0mretry\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1943\u001b[0;31m \u001b[0mj\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_json\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'serverInfo'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1944\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mretry\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1945\u001b[0m \u001b[0mlogging\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwarning\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Bug https://jira.atlassian.com/browse/JRA-59676 trying again...\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 118 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/jira/client.py\u001b[0m in \u001b[0;36m_get_json\u001b[0;34m(self, path, params, base)\u001b[0m\n\u001b[1;32m 2353\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_get_json\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpath\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbase\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mJIRA_BASE_URL\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2354\u001b[0m \u001b[0murl\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_url\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbase\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 2355\u001b[0;31m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_session\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2356\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2357\u001b[0m \u001b[0mr_json\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mjson_loads\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 119 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/jira/resilientsession.py\u001b[0m in \u001b[0;36mget\u001b[0;34m(self, url, **kwargs)\u001b[0m\n\u001b[1;32m 148\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 149\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mget\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 150\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__verb\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'GET'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 151\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 152\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mpost\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 120 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/jira/resilientsession.py\u001b[0m in \u001b[0;36m__verb\u001b[0;34m(self, verb, url, retry_data, **kwargs)\u001b[0m\n\u001b[1;32m 143\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 144\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mexception\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 145\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mexception\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 146\u001b[0m \u001b[0mraise_on_error\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mresponse\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mverb\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mverb\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 147\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresponse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 121 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/jira/resilientsession.py\u001b[0m in \u001b[0;36m__verb\u001b[0;34m(self, verb, url, retry_data, **kwargs)\u001b[0m\n\u001b[1;32m 122\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 123\u001b[0m \u001b[0mmethod\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msuper\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mResilientSession\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mverb\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlower\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 124\u001b[0;31m \u001b[0mresponse\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmethod\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimeout\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimeout\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 125\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mresponse\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstatus_code\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m200\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 126\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresponse\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 122 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/requests/sessions.py\u001b[0m in \u001b[0;36mget\u001b[0;34m(self, url, **kwargs)\u001b[0m\n\u001b[1;32m 519\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 520\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msetdefault\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'allow_redirects'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 521\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'GET'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 522\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 523\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0moptions\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0murl\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 123 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/requests/sessions.py\u001b[0m in \u001b[0;36mrequest\u001b[0;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[0m\n\u001b[1;32m 506\u001b[0m }\n\u001b[1;32m 507\u001b[0m \u001b[0msend_kwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msettings\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 508\u001b[0;31m \u001b[0mresp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mprep\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0msend_kwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 509\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 510\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mresp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 124 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/requests/sessions.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, request, **kwargs)\u001b[0m\n\u001b[1;32m 616\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 617\u001b[0m \u001b[0;31m# Send the request\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 618\u001b[0;31m \u001b[0mr\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0madapter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 619\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 620\u001b[0m \u001b[0;31m# Total elapsed time of the request (approximately)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 125 | "\u001b[0;32m/usr/local/lib/python3.5/site-packages/requests/adapters.py\u001b[0m in \u001b[0;36msend\u001b[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001b[0m\n\u001b[1;32m 506\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mSSLError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrequest\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 507\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 508\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mConnectionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0me\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrequest\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mrequest\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 509\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 510\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mClosedPoolError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 126 | "\u001b[0;31mConnectionError\u001b[0m: HTTPSConnectionPool(host='kainos-evolve.atlassian.net', port=443): Max retries exceeded with url: /rest/api/2/serverInfo (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 1] Operation not permitted',))" 127 | ] 128 | } 129 | ], 130 | "source": [ 131 | "import pandas as pd\n", 132 | "from jira import JIRA\n", 133 | "jira = JIRA(jira_url)\n", 134 | "\n", 135 | "#load all open epics \n", 136 | "jql = 'project=VXT and type=epic and status not in (Completed, Rejected)'\n", 137 | "\n", 138 | "epicsRaw = jira.search_issues(jql)\n", 139 | "\n", 140 | "epics = pd.DataFrame()\n", 141 | "epics['version'] = ''\n", 142 | "epics['key'] = ''\n", 143 | "epics['type'] = ''\n", 144 | "epics['status'] = ''\n", 145 | "epics['summary'] = ''\n", 146 | "\n", 147 | "#add epics to dataframe\n", 148 | "for issue in epicsRaw:\n", 149 | " #issue may have many versions - in this approach, one version per issue is recommended\n", 150 | " for fixVersion in issue.fields.fixVersions:\n", 151 | " epics = epics.append(\n", 152 | " {'version': fixVersion.name, \n", 153 | " 'key': issue.key,\n", 154 | " 'type': issue.fields.issuetype.name,\n", 155 | " 'status': issue.fields.status.name,\n", 156 | " 'summary': issue.fields.summary,\n", 157 | " }, ignore_index=True)\n", 158 | " \n", 159 | "epics" 160 | ] 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "metadata": {}, 165 | "source": [ 166 | "When we have all epics it's time to find all related issues." 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "epicKeys = epics['key'].tolist()\n", 176 | "jql = '\"Epic Link\" in (' + \", \".join(epicKeys) + ')'\n", 177 | "jql" 178 | ] 179 | }, 180 | { 181 | "cell_type": "markdown", 182 | "metadata": {}, 183 | "source": [ 184 | "All issues related to epics are loaded from Jira in one batch and added to Pandas dataframe." 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "metadata": {}, 191 | "outputs": [], 192 | "source": [ 193 | "issuesRaw = jira.search_issues(jql)\n", 194 | "\n", 195 | "issues = pd.DataFrame()\n", 196 | "\n", 197 | "issues['epic'] = ''\n", 198 | "issues['key'] = ''\n", 199 | "issues['type'] = ''\n", 200 | "issues['status'] = ''\n", 201 | "issues['SP'] = 0\n", 202 | "issues['summary'] = ''\n", 203 | "\n", 204 | "for issue in issuesRaw:\n", 205 | " issues = issues.append(\n", 206 | " {\n", 207 | " 'key': issue.key,\n", 208 | " 'type': issue.fields.issuetype.name,\n", 209 | " 'status': issue.fields.status.name,\n", 210 | " 'SP': issue.fields.customfield_10005,\n", 211 | " 'summary': issue.fields.summary,\n", 212 | " 'team' : str(issue.fields.customfield_14200),\n", 213 | " 'epic': issue.fields.customfield_10008\n", 214 | " }, ignore_index=True)\n", 215 | "\n" 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "Stories that are rejected or completed should not be included in calculations." 223 | ] 224 | }, 225 | { 226 | "cell_type": "code", 227 | "execution_count": null, 228 | "metadata": {}, 229 | "outputs": [], 230 | "source": [ 231 | "#only open issues are calculated\n", 232 | "issues = issues.loc[~(issues['status'].isin(['Completed', 'Rejected']))]\n", 233 | "issues.sort_values([\"epic\", 'type', 'status'], inplace=True)" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "metadata": {}, 239 | "source": [ 240 | "Cost can be calculated only for epics that are estimated in story points in 100%. To check the estimation factor we need to count estimated and not estimated stories.\n", 241 | "\n", 242 | "In this approach bugs are not estimated - their affect the velocity directly. Fixing bugs in the sprint means that a team is not delivering the features - the velocity is lower. If you're planning reducing tech debt in near future you may like to reduce minimum velocity to reflect that in cost estimations." 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": null, 248 | "metadata": {}, 249 | "outputs": [], 250 | "source": [ 251 | "#bugs are not required to be estimated\n", 252 | "issues['emptySP'] = ((issues.type == 'Story') & issues.SP.isnull())\n", 253 | "issues['notEmptySP'] = (~issues.emptySP)\n", 254 | "\n", 255 | "issues" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": null, 261 | "metadata": {}, 262 | "outputs": [], 263 | "source": [ 264 | "import numpy as np\n", 265 | "#in python we can treat True as 1 and False as 0 so simple sum suffice to calculate count of \n", 266 | "#estimated and not estimated issues in each epic\n", 267 | "aggrIssues = issues.groupby(['epic']).agg({'emptySP':'sum','notEmptySP':'sum', 'SP': 'sum'})\n", 268 | "aggrIssues['estimatedPerc'] = np.ceil(aggrIssues.notEmptySP / (aggrIssues.notEmptySP + aggrIssues.emptySP) * 100)\n", 269 | "aggrIssues = aggrIssues.reset_index()\n", 270 | "\n", 271 | "aggrIssues" 272 | ] 273 | }, 274 | { 275 | "cell_type": "markdown", 276 | "metadata": {}, 277 | "source": [ 278 | "
\n", 279 | "Set maximum, average and minimum estimated velocity of the team and team sprint costs\n", 280 | "
" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": null, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "minVelocity = 10\n", 290 | "avgVelocity = 15\n", 291 | "maxVelocity = 20\n", 292 | "#assuming that a team have 4 developers and each one salary is 1000 / week and we have 2W sprints\n", 293 | "sprintCosts = 4 * 1000 * 2" 294 | ] 295 | }, 296 | { 297 | "cell_type": "markdown", 298 | "metadata": {}, 299 | "source": [ 300 | "Below are estimated cost calculations. Information that we can't estimate epic because insufficient data in related stories might also be useful." 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": null, 306 | "metadata": {}, 307 | "outputs": [], 308 | "source": [ 309 | "del aggrIssues['emptySP']\n", 310 | "del aggrIssues['notEmptySP']\n", 311 | "\n", 312 | "#if the epic's stories are not estimated\n", 313 | "aggrIssues['minCost'] = 'Data not sufficient to estimate'\n", 314 | "aggrIssues['avgCost'] = 'Data not sufficient to estimate'\n", 315 | "aggrIssues['maxCost'] = 'Data not sufficient to estimate'\n", 316 | "\n", 317 | "#only calculate costs for fully estimated epics\n", 318 | "aggrIssues.loc[((aggrIssues['estimatedPerc'] == 100)), ['minCost']] = np.ceil(aggrIssues.SP / maxVelocity * sprintCosts)\n", 319 | "aggrIssues.loc[((aggrIssues['estimatedPerc'] == 100)), ['avgCost']] = np.ceil(aggrIssues.SP / avgVelocity * sprintCosts)\n", 320 | "aggrIssues.loc[((aggrIssues['estimatedPerc'] == 100)), ['maxCost']] = np.ceil(aggrIssues.SP / minVelocity * sprintCosts)\n", 321 | "\n", 322 | "#aggrIssues" 323 | ] 324 | }, 325 | { 326 | "cell_type": "markdown", 327 | "metadata": {}, 328 | "source": [ 329 | "For presentation purposes it's worth to add descriptions to epics." 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": null, 335 | "metadata": {}, 336 | "outputs": [], 337 | "source": [ 338 | "#load epic descriptions from Jira\n", 339 | "epicNames = []\n", 340 | "for epicKey in aggrIssues['epic']:\n", 341 | " epic = jira.issue(epicKey)\n", 342 | " epicNames.append(epic.fields.summary)\n", 343 | " \n", 344 | "epicNames = pd.Series(epicNames)\n", 345 | "aggrIssues['summary'] = epicNames.values\n", 346 | "\n", 347 | "#change column order\n", 348 | "cols = ['epic', 'summary', 'SP', 'estimatedPerc', 'minCost', 'avgCost', 'maxCost']\n", 349 | "aggrIssues = aggrIssues[cols]\n", 350 | "\n", 351 | "aggrIssues" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": {}, 357 | "source": [ 358 | "Above table with cost estimations. If you're interested in more ideas on how to use data you already have in Jira for making smart decisions in your project please visit: https://github.com/robert-krasinski/JiraAndPythonForManagers" 359 | ] 360 | } 361 | ], 362 | "metadata": { 363 | "kernelspec": { 364 | "display_name": "Python 3", 365 | "language": "python", 366 | "name": "python3" 367 | }, 368 | "language_info": { 369 | "codemirror_mode": { 370 | "name": "ipython", 371 | "version": 3 372 | }, 373 | "file_extension": ".py", 374 | "mimetype": "text/x-python", 375 | "name": "python", 376 | "nbconvert_exporter": "python", 377 | "pygments_lexer": "ipython3", 378 | "version": "3.5.1" 379 | } 380 | }, 381 | "nbformat": 4, 382 | "nbformat_minor": 2 383 | } 384 | -------------------------------------------------------------------------------- /Epic costs estimation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Epic remaining cost estimation based on team velocity" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "Cost estimation for all open (not Completed, Rejected) epics in the project. The model estimates only not completed stories that are assigned to epics in Jira. \n", 15 | "In this approach bugs are not estimated in sprints and they're affecting calculations only trough team velocity. More bugs - velocity is lower and the cost of the epic will be higher and vice versa." 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 26, 21 | "metadata": {}, 22 | "outputs": [ 23 | { 24 | "data": { 25 | "text/html": [ 26 | "
\n", 27 | "\n", 40 | "\n", 41 | " \n", 42 | " \n", 43 | " \n", 44 | " \n", 45 | " \n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | "
versionkeytypestatussummary
0Frimley MVPVXT-3952EpicTech. ScopingPatient Manager Performance Improvements
11.14VXT-3952EpicTech. ScopingPatient Manager Performance Improvements
2GCCG PHASE 1VXT-3773EpicIdeaAdditional MVP requirements post release 1.10
3GCCG PHASE 1VXT-3690EpicIdeaDistributed Patient Record - Phase 1 Improvements
4ITH POST GSMVXT-3577EpicAwaiting PrioritisationForm Renderer Refactoring - Improvements
5GCCG PHASE 1VXT-3564EpicAwaiting PrioritisationAudit Reports - GCCG Phase 1 [NEW REPORTING]
6GCCG PHASE 1VXT-3546EpicIdeaIntegration engine improvements for GCCG Phase 1
7GCCG PHASE 1VXT-3530EpicIdeaBuild connectors required for Post-MVP, Phase 1
8GCCG PHASE 1VXT-3527EpicIdeaUser account enhancements - GCCG Phase 1
9GCCG PHASE 1VXT-3524EpicIdeaUX improvements - Post MVP, Phase 1
10ePCR MVPVXT-3507EpicAwaiting PrioritisationCustom Controls for ePCR MVP (SECAmb)
11ePCR MVPVXT-3506EpicAwaiting PrioritisationCustom Controls for ePCR MVP (ePCR)
12ePCR MVPVXT-3497EpicIdeaOffline operation - gaps
13ePCR MVPVXT-3485EpicAwaiting Prioritisationcustom controls epcr MVP bugs
14ePCR MVPVXT-3484EpicIdeaiOS epcr MVP bugs
15ePCR MVPVXT-3376EpicIdeaePCR template productisation
16ePCR MVPVXT-3351EpicAwaiting PrioritisationDual connectivity to UK pod - Internet/N3
17ePCR MVPVXT-3337EpicAwaiting PrioritisationCustom task for sending emails to the given lo...
18ePCR Phase 1VXT-3292EpicAwaiting PrioritisationReporting for ePCR - Additional Reports
19ePCR MVPVXT-3264EpicAwaiting PrioritisationSetup connectivity for integration points for ...
20ePCR MVPVXT-3261EpicAwaiting PrioritisationSetting up new ePCR tenants
21ePCR MVPVXT-3260EpicAwaiting PrioritisationNew pod in Australia region
\n", 230 | "
" 231 | ], 232 | "text/plain": [ 233 | " version key type status \\\n", 234 | "0 Frimley MVP VXT-3952 Epic Tech. Scoping \n", 235 | "1 1.14 VXT-3952 Epic Tech. Scoping \n", 236 | "2 GCCG PHASE 1 VXT-3773 Epic Idea \n", 237 | "3 GCCG PHASE 1 VXT-3690 Epic Idea \n", 238 | "4 ITH POST GSM VXT-3577 Epic Awaiting Prioritisation \n", 239 | "5 GCCG PHASE 1 VXT-3564 Epic Awaiting Prioritisation \n", 240 | "6 GCCG PHASE 1 VXT-3546 Epic Idea \n", 241 | "7 GCCG PHASE 1 VXT-3530 Epic Idea \n", 242 | "8 GCCG PHASE 1 VXT-3527 Epic Idea \n", 243 | "9 GCCG PHASE 1 VXT-3524 Epic Idea \n", 244 | "10 ePCR MVP VXT-3507 Epic Awaiting Prioritisation \n", 245 | "11 ePCR MVP VXT-3506 Epic Awaiting Prioritisation \n", 246 | "12 ePCR MVP VXT-3497 Epic Idea \n", 247 | "13 ePCR MVP VXT-3485 Epic Awaiting Prioritisation \n", 248 | "14 ePCR MVP VXT-3484 Epic Idea \n", 249 | "15 ePCR MVP VXT-3376 Epic Idea \n", 250 | "16 ePCR MVP VXT-3351 Epic Awaiting Prioritisation \n", 251 | "17 ePCR MVP VXT-3337 Epic Awaiting Prioritisation \n", 252 | "18 ePCR Phase 1 VXT-3292 Epic Awaiting Prioritisation \n", 253 | "19 ePCR MVP VXT-3264 Epic Awaiting Prioritisation \n", 254 | "20 ePCR MVP VXT-3261 Epic Awaiting Prioritisation \n", 255 | "21 ePCR MVP VXT-3260 Epic Awaiting Prioritisation \n", 256 | "\n", 257 | " summary \n", 258 | "0 Patient Manager Performance Improvements \n", 259 | "1 Patient Manager Performance Improvements \n", 260 | "2 Additional MVP requirements post release 1.10 \n", 261 | "3 Distributed Patient Record - Phase 1 Improvements \n", 262 | "4 Form Renderer Refactoring - Improvements \n", 263 | "5 Audit Reports - GCCG Phase 1 [NEW REPORTING] \n", 264 | "6 Integration engine improvements for GCCG Phase 1 \n", 265 | "7 Build connectors required for Post-MVP, Phase 1 \n", 266 | "8 User account enhancements - GCCG Phase 1 \n", 267 | "9 UX improvements - Post MVP, Phase 1 \n", 268 | "10 Custom Controls for ePCR MVP (SECAmb) \n", 269 | "11 Custom Controls for ePCR MVP (ePCR) \n", 270 | "12 Offline operation - gaps \n", 271 | "13 custom controls epcr MVP bugs \n", 272 | "14 iOS epcr MVP bugs \n", 273 | "15 ePCR template productisation \n", 274 | "16 Dual connectivity to UK pod - Internet/N3 \n", 275 | "17 Custom task for sending emails to the given lo... \n", 276 | "18 Reporting for ePCR - Additional Reports \n", 277 | "19 Setup connectivity for integration points for ... \n", 278 | "20 Setting up new ePCR tenants \n", 279 | "21 New pod in Australia region " 280 | ] 281 | }, 282 | "execution_count": 26, 283 | "metadata": {}, 284 | "output_type": "execute_result" 285 | } 286 | ], 287 | "source": [ 288 | "import pandas as pd\n", 289 | "from jira import JIRA\n", 290 | "\n", 291 | "jira = JIRA('https://kainos-evolve.atlassian.net')\n", 292 | "\n", 293 | "#load all open epics \n", 294 | "jql = 'project=VXT and type=epic and status not in (Completed, Rejected)'\n", 295 | "\n", 296 | "epicsRaw = jira.search_issues(jql)\n", 297 | "\n", 298 | "epics = pd.DataFrame()\n", 299 | "epics['version'] = ''\n", 300 | "epics['key'] = ''\n", 301 | "epics['type'] = ''\n", 302 | "epics['status'] = ''\n", 303 | "epics['summary'] = ''\n", 304 | "\n", 305 | "#add epics to dataframe\n", 306 | "for issue in epicsRaw:\n", 307 | " #issue may have many versions - in this approach, one version per issue is recommended\n", 308 | " for fixVersion in issue.fields.fixVersions:\n", 309 | " epics = epics.append(\n", 310 | " {'version': fixVersion.name, \n", 311 | " 'key': issue.key,\n", 312 | " 'type': issue.fields.issuetype.name,\n", 313 | " 'status': issue.fields.status.name,\n", 314 | " 'summary': issue.fields.summary,\n", 315 | " }, ignore_index=True)\n", 316 | " \n", 317 | "epics" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "Find all issues related to those epics" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": 27, 330 | "metadata": {}, 331 | "outputs": [ 332 | { 333 | "data": { 334 | "text/plain": [ 335 | "'\"Epic Link\" in (VXT-3952, VXT-3952, VXT-3773, VXT-3690, VXT-3577, VXT-3564, VXT-3546, VXT-3530, VXT-3527, VXT-3524, VXT-3507, VXT-3506, VXT-3497, VXT-3485, VXT-3484, VXT-3376, VXT-3351, VXT-3337, VXT-3292, VXT-3264, VXT-3261, VXT-3260)'" 336 | ] 337 | }, 338 | "execution_count": 27, 339 | "metadata": {}, 340 | "output_type": "execute_result" 341 | } 342 | ], 343 | "source": [ 344 | "epicKeys = epics['key'].tolist()\n", 345 | "jql = '\"Epic Link\" in (' + \", \".join(epicKeys) + ')'\n", 346 | "jql" 347 | ] 348 | }, 349 | { 350 | "cell_type": "code", 351 | "execution_count": 28, 352 | "metadata": {}, 353 | "outputs": [ 354 | { 355 | "data": { 356 | "text/html": [ 357 | "
\n", 358 | "\n", 371 | "\n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | " \n", 513 | " \n", 514 | " \n", 515 | " \n", 516 | " \n", 517 | " \n", 518 | " \n", 519 | " \n", 520 | " \n", 521 | " \n", 522 | " \n", 523 | " \n", 524 | " \n", 525 | " \n", 526 | " \n", 527 | " \n", 528 | " \n", 529 | " \n", 530 | " \n", 531 | " \n", 532 | " \n", 533 | " \n", 534 | " \n", 535 | " \n", 536 | " \n", 537 | " \n", 538 | " \n", 539 | " \n", 540 | " \n", 541 | " \n", 542 | " \n", 543 | " \n", 544 | " \n", 545 | " \n", 546 | " \n", 547 | " \n", 548 | " \n", 549 | " \n", 550 | " \n", 551 | " \n", 552 | " \n", 553 | " \n", 554 | " \n", 555 | " \n", 556 | " \n", 557 | " \n", 558 | " \n", 559 | " \n", 560 | " \n", 561 | " \n", 562 | " \n", 563 | " \n", 564 | " \n", 565 | " \n", 566 | " \n", 567 | " \n", 568 | " \n", 569 | " \n", 570 | " \n", 571 | " \n", 572 | " \n", 573 | " \n", 574 | " \n", 575 | " \n", 576 | " \n", 577 | " \n", 578 | " \n", 579 | " \n", 580 | " \n", 581 | " \n", 582 | " \n", 583 | " \n", 584 | " \n", 585 | " \n", 586 | " \n", 587 | " \n", 588 | " \n", 589 | " \n", 590 | " \n", 591 | " \n", 592 | " \n", 593 | " \n", 594 | " \n", 595 | " \n", 596 | " \n", 597 | " \n", 598 | " \n", 599 | " \n", 600 | " \n", 601 | " \n", 602 | " \n", 603 | " \n", 604 | " \n", 605 | " \n", 606 | " \n", 607 | " \n", 608 | " \n", 609 | " \n", 610 | " \n", 611 | " \n", 612 | " \n", 613 | " \n", 614 | " \n", 615 | " \n", 616 | " \n", 617 | " \n", 618 | " \n", 619 | " \n", 620 | " \n", 621 | " \n", 622 | " \n", 623 | " \n", 624 | " \n", 625 | " \n", 626 | " \n", 627 | " \n", 628 | " \n", 629 | " \n", 630 | " \n", 631 | " \n", 632 | " \n", 633 | " \n", 634 | " \n", 635 | " \n", 636 | " \n", 637 | " \n", 638 | " \n", 639 | " \n", 640 | " \n", 641 | " \n", 642 | " \n", 643 | " \n", 644 | " \n", 645 | " \n", 646 | " \n", 647 | " \n", 648 | " \n", 649 | " \n", 650 | " \n", 651 | " \n", 652 | " \n", 653 | " \n", 654 | " \n", 655 | " \n", 656 | " \n", 657 | " \n", 658 | " \n", 659 | " \n", 660 | " \n", 661 | " \n", 662 | " \n", 663 | " \n", 664 | " \n", 665 | " \n", 666 | " \n", 667 | " \n", 668 | " \n", 669 | " \n", 670 | " \n", 671 | " \n", 672 | " \n", 673 | " \n", 674 | " \n", 675 | " \n", 676 | " \n", 677 | " \n", 678 | " \n", 679 | " \n", 680 | " \n", 681 | " \n", 682 | " \n", 683 | " \n", 684 | " \n", 685 | " \n", 686 | " \n", 687 | " \n", 688 | " \n", 689 | " \n", 690 | " \n", 691 | " \n", 692 | " \n", 693 | " \n", 694 | " \n", 695 | " \n", 696 | " \n", 697 | " \n", 698 | " \n", 699 | " \n", 700 | " \n", 701 | " \n", 702 | " \n", 703 | " \n", 704 | " \n", 705 | " \n", 706 | " \n", 707 | " \n", 708 | " \n", 709 | " \n", 710 | " \n", 711 | " \n", 712 | " \n", 713 | " \n", 714 | " \n", 715 | " \n", 716 | " \n", 717 | " \n", 718 | " \n", 719 | " \n", 720 | " \n", 721 | " \n", 722 | " \n", 723 | " \n", 724 | " \n", 725 | " \n", 726 | " \n", 727 | " \n", 728 | " \n", 729 | " \n", 730 | " \n", 731 | " \n", 732 | " \n", 733 | " \n", 734 | " \n", 735 | " \n", 736 | " \n", 737 | " \n", 738 | " \n", 739 | " \n", 740 | " \n", 741 | " \n", 742 | " \n", 743 | " \n", 744 | " \n", 745 | " \n", 746 | " \n", 747 | " \n", 748 | " \n", 749 | " \n", 750 | " \n", 751 | " \n", 752 | " \n", 753 | " \n", 754 | " \n", 755 | " \n", 756 | " \n", 757 | " \n", 758 | " \n", 759 | " \n", 760 | " \n", 761 | " \n", 762 | " \n", 763 | " \n", 764 | " \n", 765 | " \n", 766 | " \n", 767 | " \n", 768 | " \n", 769 | " \n", 770 | " \n", 771 | " \n", 772 | " \n", 773 | " \n", 774 | " \n", 775 | " \n", 776 | " \n", 777 | " \n", 778 | " \n", 779 | " \n", 780 | " \n", 781 | " \n", 782 | " \n", 783 | " \n", 784 | " \n", 785 | " \n", 786 | " \n", 787 | " \n", 788 | " \n", 789 | " \n", 790 | " \n", 791 | " \n", 792 | " \n", 793 | " \n", 794 | " \n", 795 | " \n", 796 | "
epickeytypestatusSPsummaryteamemptySPnotEmptySP
41VXT-3337VXT-3252StoryTech Refinement3.0Task to get email from FHIR location and enabl...Custom ControlsFalseTrue
25VXT-3351VXT-3494StoryAwaiting Prioritisation3.0Provision dual connectivity to UK podOps TeamFalseTrue
35VXT-3351VXT-3356StoryAwaiting Prioritisation8.0Security testing dual connectivity to UK podOps TeamFalseTrue
36VXT-3351VXT-3354StoryAwaiting PrioritisationNaNCorrect issues found due to dual connectivity ...Platform TeamTrueFalse
37VXT-3351VXT-3352StoryAwaiting PrioritisationNaNTest dual connectivity changes to UK podPlatform TeamTrueFalse
33VXT-3376VXT-3378StoryTech RefinementNaNDesign ePCR templateNoneTrueFalse
34VXT-3376VXT-3377StoryTech RefinementNaNCreate generic ePCR form wireframesNoneTrueFalse
16VXT-3484VXT-3589StoryIdeaNaNMove form redirect functionality into form ren...Web TeamTrueFalse
44VXT-3506VXT-3233StoryAwaiting Prioritisation30.0Placeholder for additional custom controlsNoneFalseTrue
46VXT-3506VXT-3205StoryAwaiting Prioritisation1.0Online awareness for postcode lookup / address...NoneFalseTrue
47VXT-3506VXT-3164StoryPO Refinement5.0Address/Postcode lookup controlCustom ControlsFalseTrue
15VXT-3524VXT-3608StoryIdeaNaNBad State Panel ColourNoneTrueFalse
18VXT-3524VXT-3574StoryIdeaNaNStandalone Input MaskWeb TeamTrueFalse
38VXT-3527VXT-3325StoryIdeaNaNDisable a users account after a period of inac...Platform TeamTrueFalse
39VXT-3546VXT-3307StoryIdea5.0Implement Scenario - Check Patient Medical Dat...Integration TeamFalseTrue
48VXT-3546VXT-3146StoryIdea4.0Updates of staging DB with automatic migrationsIntegration TeamFalseTrue
14VXT-3546VXT-3638TaskIn Development2.0Change endpoints to point to internal domains ...Integration TeamFalseTrue
5VXT-3564VXT-3735BugIdeaNaNSpine user details missing from gateway audit ...Platform TeamFalseTrue
10VXT-3564VXT-3705BugIdeaNaNSpine user details missing from preferences au...Platform TeamFalseTrue
12VXT-3564VXT-3696BugIdeaNaNSpine user details missing from login audit entryPlatform TeamFalseTrue
3VXT-3564VXT-3819StoryIdeaNaNPatient Access audit report: Allow user to def...NoneTrueFalse
6VXT-3564VXT-3731StoryIdea1.0Audit Report UI - Display patient name and ide...Web TeamFalseTrue
11VXT-3564VXT-3700StoryIdeaNaNAudit access to audit reportsPlatform TeamTrueFalse
7VXT-3690VXT-3726StoryIdeaNaNImprovements to spine-sensitive patients handlingIntegration TeamTrueFalse
28VXT-3690VXT-3448TaskBacklogNaNConfigurable set of search paramsIntegration TeamFalseTrue
13VXT-3690VXT-3691TaskIdeaNaNAsynchronous purgeGdansk Team 1FalseTrue
19VXT-3690VXT-3572TaskIdeaNaNMake data extract validity configurableIntegration TeamFalseTrue
4VXT-3773VXT-3774StoryIdeaNaNChanges to JUYI brandingNoneTrueFalse
26VXT-3773VXT-3470StoryIdeaNaNEnable sorting on six columns for which FHIR s...NoneTrueFalse
40VXT-3952VXT-3281BugBacklogNaNForm properties trigger function wrongly inter...Gdansk Team 1FalseTrue
0VXT-3952VXT-4109StoryBacklog3.0Encounter Screen - Rework of search filter opt...Gdansk Team 1FalseTrue
1VXT-3952VXT-4100StoryBacklog3.0Encounter Screen - Rework of default form task...Gdansk Team 1FalseTrue
17VXT-3952VXT-3587StoryBacklogNaNTask listing endpoints expose sensitive clinic...Gdansk Team 1TrueFalse
42VXT-3952VXT-3245StoryTech Refinement6.5New patient manager endpointGdansk Team 1FalseTrue
\n", 797 | "
" 798 | ], 799 | "text/plain": [ 800 | " epic key type status SP \\\n", 801 | "41 VXT-3337 VXT-3252 Story Tech Refinement 3.0 \n", 802 | "25 VXT-3351 VXT-3494 Story Awaiting Prioritisation 3.0 \n", 803 | "35 VXT-3351 VXT-3356 Story Awaiting Prioritisation 8.0 \n", 804 | "36 VXT-3351 VXT-3354 Story Awaiting Prioritisation NaN \n", 805 | "37 VXT-3351 VXT-3352 Story Awaiting Prioritisation NaN \n", 806 | "33 VXT-3376 VXT-3378 Story Tech Refinement NaN \n", 807 | "34 VXT-3376 VXT-3377 Story Tech Refinement NaN \n", 808 | "16 VXT-3484 VXT-3589 Story Idea NaN \n", 809 | "44 VXT-3506 VXT-3233 Story Awaiting Prioritisation 30.0 \n", 810 | "46 VXT-3506 VXT-3205 Story Awaiting Prioritisation 1.0 \n", 811 | "47 VXT-3506 VXT-3164 Story PO Refinement 5.0 \n", 812 | "15 VXT-3524 VXT-3608 Story Idea NaN \n", 813 | "18 VXT-3524 VXT-3574 Story Idea NaN \n", 814 | "38 VXT-3527 VXT-3325 Story Idea NaN \n", 815 | "39 VXT-3546 VXT-3307 Story Idea 5.0 \n", 816 | "48 VXT-3546 VXT-3146 Story Idea 4.0 \n", 817 | "14 VXT-3546 VXT-3638 Task In Development 2.0 \n", 818 | "5 VXT-3564 VXT-3735 Bug Idea NaN \n", 819 | "10 VXT-3564 VXT-3705 Bug Idea NaN \n", 820 | "12 VXT-3564 VXT-3696 Bug Idea NaN \n", 821 | "3 VXT-3564 VXT-3819 Story Idea NaN \n", 822 | "6 VXT-3564 VXT-3731 Story Idea 1.0 \n", 823 | "11 VXT-3564 VXT-3700 Story Idea NaN \n", 824 | "7 VXT-3690 VXT-3726 Story Idea NaN \n", 825 | "28 VXT-3690 VXT-3448 Task Backlog NaN \n", 826 | "13 VXT-3690 VXT-3691 Task Idea NaN \n", 827 | "19 VXT-3690 VXT-3572 Task Idea NaN \n", 828 | "4 VXT-3773 VXT-3774 Story Idea NaN \n", 829 | "26 VXT-3773 VXT-3470 Story Idea NaN \n", 830 | "40 VXT-3952 VXT-3281 Bug Backlog NaN \n", 831 | "0 VXT-3952 VXT-4109 Story Backlog 3.0 \n", 832 | "1 VXT-3952 VXT-4100 Story Backlog 3.0 \n", 833 | "17 VXT-3952 VXT-3587 Story Backlog NaN \n", 834 | "42 VXT-3952 VXT-3245 Story Tech Refinement 6.5 \n", 835 | "\n", 836 | " summary team \\\n", 837 | "41 Task to get email from FHIR location and enabl... Custom Controls \n", 838 | "25 Provision dual connectivity to UK pod Ops Team \n", 839 | "35 Security testing dual connectivity to UK pod Ops Team \n", 840 | "36 Correct issues found due to dual connectivity ... Platform Team \n", 841 | "37 Test dual connectivity changes to UK pod Platform Team \n", 842 | "33 Design ePCR template None \n", 843 | "34 Create generic ePCR form wireframes None \n", 844 | "16 Move form redirect functionality into form ren... Web Team \n", 845 | "44 Placeholder for additional custom controls None \n", 846 | "46 Online awareness for postcode lookup / address... None \n", 847 | "47 Address/Postcode lookup control Custom Controls \n", 848 | "15 Bad State Panel Colour None \n", 849 | "18 Standalone Input Mask Web Team \n", 850 | "38 Disable a users account after a period of inac... Platform Team \n", 851 | "39 Implement Scenario - Check Patient Medical Dat... Integration Team \n", 852 | "48 Updates of staging DB with automatic migrations Integration Team \n", 853 | "14 Change endpoints to point to internal domains ... Integration Team \n", 854 | "5 Spine user details missing from gateway audit ... Platform Team \n", 855 | "10 Spine user details missing from preferences au... Platform Team \n", 856 | "12 Spine user details missing from login audit entry Platform Team \n", 857 | "3 Patient Access audit report: Allow user to def... None \n", 858 | "6 Audit Report UI - Display patient name and ide... Web Team \n", 859 | "11 Audit access to audit reports Platform Team \n", 860 | "7 Improvements to spine-sensitive patients handling Integration Team \n", 861 | "28 Configurable set of search params Integration Team \n", 862 | "13 Asynchronous purge Gdansk Team 1 \n", 863 | "19 Make data extract validity configurable Integration Team \n", 864 | "4 Changes to JUYI branding None \n", 865 | "26 Enable sorting on six columns for which FHIR s... None \n", 866 | "40 Form properties trigger function wrongly inter... Gdansk Team 1 \n", 867 | "0 Encounter Screen - Rework of search filter opt... Gdansk Team 1 \n", 868 | "1 Encounter Screen - Rework of default form task... Gdansk Team 1 \n", 869 | "17 Task listing endpoints expose sensitive clinic... Gdansk Team 1 \n", 870 | "42 New patient manager endpoint Gdansk Team 1 \n", 871 | "\n", 872 | " emptySP notEmptySP \n", 873 | "41 False True \n", 874 | "25 False True \n", 875 | "35 False True \n", 876 | "36 True False \n", 877 | "37 True False \n", 878 | "33 True False \n", 879 | "34 True False \n", 880 | "16 True False \n", 881 | "44 False True \n", 882 | "46 False True \n", 883 | "47 False True \n", 884 | "15 True False \n", 885 | "18 True False \n", 886 | "38 True False \n", 887 | "39 False True \n", 888 | "48 False True \n", 889 | "14 False True \n", 890 | "5 False True \n", 891 | "10 False True \n", 892 | "12 False True \n", 893 | "3 True False \n", 894 | "6 False True \n", 895 | "11 True False \n", 896 | "7 True False \n", 897 | "28 False True \n", 898 | "13 False True \n", 899 | "19 False True \n", 900 | "4 True False \n", 901 | "26 True False \n", 902 | "40 False True \n", 903 | "0 False True \n", 904 | "1 False True \n", 905 | "17 True False \n", 906 | "42 False True " 907 | ] 908 | }, 909 | "execution_count": 28, 910 | "metadata": {}, 911 | "output_type": "execute_result" 912 | } 913 | ], 914 | "source": [ 915 | "issuesRaw = jira.search_issues(jql)\n", 916 | "\n", 917 | "issues = pd.DataFrame()\n", 918 | "\n", 919 | "issues['epic'] = ''\n", 920 | "issues['key'] = ''\n", 921 | "issues['type'] = ''\n", 922 | "issues['status'] = ''\n", 923 | "issues['SP'] = 0\n", 924 | "issues['summary'] = ''\n", 925 | "\n", 926 | "for issue in issuesRaw:\n", 927 | " issues = issues.append(\n", 928 | " {\n", 929 | " 'key': issue.key,\n", 930 | " 'type': issue.fields.issuetype.name,\n", 931 | " 'status': issue.fields.status.name,\n", 932 | " 'SP': issue.fields.customfield_10005,\n", 933 | " 'summary': issue.fields.summary,\n", 934 | " 'team' : str(issue.fields.customfield_14200),\n", 935 | " 'epic': issue.fields.customfield_10008\n", 936 | " }, ignore_index=True)\n", 937 | "\n", 938 | "#only open issues are calculated\n", 939 | "issues = issues.loc[~(issues['status'].isin(['Completed', 'Rejected']))]\n", 940 | "issues.sort_values([\"epic\", 'type', 'status'], inplace=True)\n", 941 | "\n", 942 | "#bugs are not required to be estimated\n", 943 | "issues['emptySP'] = ((issues.type == 'Story') & issues.SP.isnull())\n", 944 | "issues['notEmptySP'] = (~issues.emptySP)\n", 945 | "\n", 946 | "issues" 947 | ] 948 | }, 949 | { 950 | "cell_type": "code", 951 | "execution_count": 29, 952 | "metadata": {}, 953 | "outputs": [ 954 | { 955 | "data": { 956 | "text/html": [ 957 | "
\n", 958 | "\n", 971 | "\n", 972 | " \n", 973 | " \n", 974 | " \n", 975 | " \n", 976 | " \n", 977 | " \n", 978 | " \n", 979 | " \n", 980 | " \n", 981 | " \n", 982 | " \n", 983 | " \n", 984 | " \n", 985 | " \n", 986 | " \n", 987 | " \n", 988 | " \n", 989 | " \n", 990 | " \n", 991 | " \n", 992 | " \n", 993 | " \n", 994 | " \n", 995 | " \n", 996 | " \n", 997 | " \n", 998 | " \n", 999 | " \n", 1000 | " \n", 1001 | " \n", 1002 | " \n", 1003 | " \n", 1004 | " \n", 1005 | " \n", 1006 | " \n", 1007 | " \n", 1008 | " \n", 1009 | " \n", 1010 | " \n", 1011 | " \n", 1012 | " \n", 1013 | " \n", 1014 | " \n", 1015 | " \n", 1016 | " \n", 1017 | " \n", 1018 | " \n", 1019 | " \n", 1020 | " \n", 1021 | " \n", 1022 | " \n", 1023 | " \n", 1024 | " \n", 1025 | " \n", 1026 | " \n", 1027 | " \n", 1028 | " \n", 1029 | " \n", 1030 | " \n", 1031 | " \n", 1032 | " \n", 1033 | " \n", 1034 | " \n", 1035 | " \n", 1036 | " \n", 1037 | " \n", 1038 | " \n", 1039 | " \n", 1040 | " \n", 1041 | " \n", 1042 | " \n", 1043 | " \n", 1044 | " \n", 1045 | " \n", 1046 | " \n", 1047 | " \n", 1048 | " \n", 1049 | " \n", 1050 | " \n", 1051 | " \n", 1052 | " \n", 1053 | " \n", 1054 | " \n", 1055 | " \n", 1056 | " \n", 1057 | " \n", 1058 | " \n", 1059 | " \n", 1060 | " \n", 1061 | " \n", 1062 | " \n", 1063 | " \n", 1064 | " \n", 1065 | " \n", 1066 | " \n", 1067 | " \n", 1068 | " \n", 1069 | " \n", 1070 | " \n", 1071 | " \n", 1072 | " \n", 1073 | " \n", 1074 | " \n", 1075 | " \n", 1076 | " \n", 1077 | " \n", 1078 | " \n", 1079 | " \n", 1080 | "
epicnotEmptySPemptySPSPestimatedPerc
0VXT-33371.00.03.0100.0
1VXT-33512.02.011.050.0
2VXT-33760.02.00.00.0
3VXT-34840.01.00.00.0
4VXT-35063.00.036.0100.0
5VXT-35240.02.00.00.0
6VXT-35270.01.00.00.0
7VXT-35463.00.011.0100.0
8VXT-35644.02.01.067.0
9VXT-36903.01.00.075.0
10VXT-37730.02.00.00.0
11VXT-39524.01.012.580.0
\n", 1081 | "
" 1082 | ], 1083 | "text/plain": [ 1084 | " epic notEmptySP emptySP SP estimatedPerc\n", 1085 | "0 VXT-3337 1.0 0.0 3.0 100.0\n", 1086 | "1 VXT-3351 2.0 2.0 11.0 50.0\n", 1087 | "2 VXT-3376 0.0 2.0 0.0 0.0\n", 1088 | "3 VXT-3484 0.0 1.0 0.0 0.0\n", 1089 | "4 VXT-3506 3.0 0.0 36.0 100.0\n", 1090 | "5 VXT-3524 0.0 2.0 0.0 0.0\n", 1091 | "6 VXT-3527 0.0 1.0 0.0 0.0\n", 1092 | "7 VXT-3546 3.0 0.0 11.0 100.0\n", 1093 | "8 VXT-3564 4.0 2.0 1.0 67.0\n", 1094 | "9 VXT-3690 3.0 1.0 0.0 75.0\n", 1095 | "10 VXT-3773 0.0 2.0 0.0 0.0\n", 1096 | "11 VXT-3952 4.0 1.0 12.5 80.0" 1097 | ] 1098 | }, 1099 | "execution_count": 29, 1100 | "metadata": {}, 1101 | "output_type": "execute_result" 1102 | } 1103 | ], 1104 | "source": [ 1105 | "import numpy as np\n", 1106 | "#in python we can treat True as 1 and False as 0 so simple sum suffice to calculate count of \n", 1107 | "#estimated and not estimated issues in each epic\n", 1108 | "aggrIssues = issues.groupby(['epic']).agg({'emptySP':'sum','notEmptySP':'sum', 'SP': 'sum'})\n", 1109 | "aggrIssues['estimatedPerc'] = np.ceil(aggrIssues.notEmptySP / (aggrIssues.notEmptySP + aggrIssues.emptySP) * 100)\n", 1110 | "aggrIssues = aggrIssues.reset_index()\n", 1111 | "\n", 1112 | "aggrIssues" 1113 | ] 1114 | }, 1115 | { 1116 | "cell_type": "markdown", 1117 | "metadata": {}, 1118 | "source": [ 1119 | "
\n", 1120 | "Set max & min estimated velocity of the team and team sprint costs\n", 1121 | "
" 1122 | ] 1123 | }, 1124 | { 1125 | "cell_type": "code", 1126 | "execution_count": 30, 1127 | "metadata": {}, 1128 | "outputs": [], 1129 | "source": [ 1130 | "minVelocity = 10\n", 1131 | "avgVelocity = 15\n", 1132 | "maxVelocity = 20\n", 1133 | "#assuming that a team have 4 developers and each one salary is 1000 / week and we have 2W sprints\n", 1134 | "sprintCosts = 4 * 1000 * 2" 1135 | ] 1136 | }, 1137 | { 1138 | "cell_type": "code", 1139 | "execution_count": 31, 1140 | "metadata": {}, 1141 | "outputs": [], 1142 | "source": [ 1143 | "del aggrIssues['emptySP']\n", 1144 | "del aggrIssues['notEmptySP']\n", 1145 | "\n", 1146 | "#if the epic's stories are not estimated\n", 1147 | "aggrIssues['minCost'] = 'Data not sufficient to estimate'\n", 1148 | "aggrIssues['avgCost'] = 'Data not sufficient to estimate'\n", 1149 | "aggrIssues['maxCost'] = 'Data not sufficient to estimate'\n", 1150 | "\n", 1151 | "#only calculate costs for fully estimated epics\n", 1152 | "aggrIssues.loc[((aggrIssues['estimatedPerc'] == 100)), ['minCost']] = np.ceil(aggrIssues.SP / maxVelocity * sprintCosts)\n", 1153 | "aggrIssues.loc[((aggrIssues['estimatedPerc'] == 100)), ['avgCost']] = np.ceil(aggrIssues.SP / avgVelocity * sprintCosts)\n", 1154 | "aggrIssues.loc[((aggrIssues['estimatedPerc'] == 100)), ['maxCost']] = np.ceil(aggrIssues.SP / minVelocity * sprintCosts)\n", 1155 | "\n", 1156 | "#aggrIssues" 1157 | ] 1158 | }, 1159 | { 1160 | "cell_type": "code", 1161 | "execution_count": 32, 1162 | "metadata": {}, 1163 | "outputs": [], 1164 | "source": [ 1165 | "#load epic descriptions from Jira\n", 1166 | "epicNames = []\n", 1167 | "for epicKey in aggrIssues['epic']:\n", 1168 | " epic = jira.issue(epicKey)\n", 1169 | " epicNames.append(epic.fields.summary)\n", 1170 | " \n", 1171 | "epicNames = pd.Series(epicNames)\n", 1172 | "aggrIssues['summary'] = epicNames.values\n", 1173 | "\n", 1174 | "\n", 1175 | "\n", 1176 | " " 1177 | ] 1178 | }, 1179 | { 1180 | "cell_type": "code", 1181 | "execution_count": 35, 1182 | "metadata": {}, 1183 | "outputs": [ 1184 | { 1185 | "data": { 1186 | "text/html": [ 1187 | "
\n", 1188 | "\n", 1201 | "\n", 1202 | " \n", 1203 | " \n", 1204 | " \n", 1205 | " \n", 1206 | " \n", 1207 | " \n", 1208 | " \n", 1209 | " \n", 1210 | " \n", 1211 | " \n", 1212 | " \n", 1213 | " \n", 1214 | " \n", 1215 | " \n", 1216 | " \n", 1217 | " \n", 1218 | " \n", 1219 | " \n", 1220 | " \n", 1221 | " \n", 1222 | " \n", 1223 | " \n", 1224 | " \n", 1225 | " \n", 1226 | " \n", 1227 | " \n", 1228 | " \n", 1229 | " \n", 1230 | " \n", 1231 | " \n", 1232 | " \n", 1233 | " \n", 1234 | " \n", 1235 | " \n", 1236 | " \n", 1237 | " \n", 1238 | " \n", 1239 | " \n", 1240 | " \n", 1241 | " \n", 1242 | " \n", 1243 | " \n", 1244 | " \n", 1245 | " \n", 1246 | " \n", 1247 | " \n", 1248 | " \n", 1249 | " \n", 1250 | " \n", 1251 | " \n", 1252 | " \n", 1253 | " \n", 1254 | " \n", 1255 | " \n", 1256 | " \n", 1257 | " \n", 1258 | " \n", 1259 | " \n", 1260 | " \n", 1261 | " \n", 1262 | " \n", 1263 | " \n", 1264 | " \n", 1265 | " \n", 1266 | " \n", 1267 | " \n", 1268 | " \n", 1269 | " \n", 1270 | " \n", 1271 | " \n", 1272 | " \n", 1273 | " \n", 1274 | " \n", 1275 | " \n", 1276 | " \n", 1277 | " \n", 1278 | " \n", 1279 | " \n", 1280 | " \n", 1281 | " \n", 1282 | " \n", 1283 | " \n", 1284 | " \n", 1285 | " \n", 1286 | " \n", 1287 | " \n", 1288 | " \n", 1289 | " \n", 1290 | " \n", 1291 | " \n", 1292 | " \n", 1293 | " \n", 1294 | " \n", 1295 | " \n", 1296 | " \n", 1297 | " \n", 1298 | " \n", 1299 | " \n", 1300 | " \n", 1301 | " \n", 1302 | " \n", 1303 | " \n", 1304 | " \n", 1305 | " \n", 1306 | " \n", 1307 | " \n", 1308 | " \n", 1309 | " \n", 1310 | " \n", 1311 | " \n", 1312 | " \n", 1313 | " \n", 1314 | " \n", 1315 | " \n", 1316 | " \n", 1317 | " \n", 1318 | " \n", 1319 | " \n", 1320 | " \n", 1321 | " \n", 1322 | " \n", 1323 | " \n", 1324 | " \n", 1325 | " \n", 1326 | " \n", 1327 | " \n", 1328 | " \n", 1329 | " \n", 1330 | " \n", 1331 | " \n", 1332 | " \n", 1333 | " \n", 1334 | " \n", 1335 | " \n", 1336 | "
epicsummarySPestimatedPercminCostavgCostmaxCost
0VXT-3337Custom task for sending emails to the given lo...3.0100.0120016002400
1VXT-3351Dual connectivity to UK pod - Internet/N311.050.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
2VXT-3376ePCR template productisation0.00.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
3VXT-3484iOS epcr MVP bugs0.00.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
4VXT-3506Custom Controls for ePCR MVP (ePCR)36.0100.0144001920028800
5VXT-3524UX improvements - Post MVP, Phase 10.00.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
6VXT-3527User account enhancements - GCCG Phase 10.00.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
7VXT-3546Integration engine improvements for GCCG Phase 111.0100.0440058678800
8VXT-3564Audit Reports - GCCG Phase 1 [NEW REPORTING]1.067.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
9VXT-3690Distributed Patient Record - Phase 1 Improvements0.075.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
10VXT-3773Additional MVP requirements post release 1.100.00.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
11VXT-3952Patient Manager Performance Improvements12.580.0Data not sufficient to estimateData not sufficient to estimateData not sufficient to estimate
\n", 1337 | "
" 1338 | ], 1339 | "text/plain": [ 1340 | " epic summary SP \\\n", 1341 | "0 VXT-3337 Custom task for sending emails to the given lo... 3.0 \n", 1342 | "1 VXT-3351 Dual connectivity to UK pod - Internet/N3 11.0 \n", 1343 | "2 VXT-3376 ePCR template productisation 0.0 \n", 1344 | "3 VXT-3484 iOS epcr MVP bugs 0.0 \n", 1345 | "4 VXT-3506 Custom Controls for ePCR MVP (ePCR) 36.0 \n", 1346 | "5 VXT-3524 UX improvements - Post MVP, Phase 1 0.0 \n", 1347 | "6 VXT-3527 User account enhancements - GCCG Phase 1 0.0 \n", 1348 | "7 VXT-3546 Integration engine improvements for GCCG Phase 1 11.0 \n", 1349 | "8 VXT-3564 Audit Reports - GCCG Phase 1 [NEW REPORTING] 1.0 \n", 1350 | "9 VXT-3690 Distributed Patient Record - Phase 1 Improvements 0.0 \n", 1351 | "10 VXT-3773 Additional MVP requirements post release 1.10 0.0 \n", 1352 | "11 VXT-3952 Patient Manager Performance Improvements 12.5 \n", 1353 | "\n", 1354 | " estimatedPerc minCost \\\n", 1355 | "0 100.0 1200 \n", 1356 | "1 50.0 Data not sufficient to estimate \n", 1357 | "2 0.0 Data not sufficient to estimate \n", 1358 | "3 0.0 Data not sufficient to estimate \n", 1359 | "4 100.0 14400 \n", 1360 | "5 0.0 Data not sufficient to estimate \n", 1361 | "6 0.0 Data not sufficient to estimate \n", 1362 | "7 100.0 4400 \n", 1363 | "8 67.0 Data not sufficient to estimate \n", 1364 | "9 75.0 Data not sufficient to estimate \n", 1365 | "10 0.0 Data not sufficient to estimate \n", 1366 | "11 80.0 Data not sufficient to estimate \n", 1367 | "\n", 1368 | " avgCost maxCost \n", 1369 | "0 1600 2400 \n", 1370 | "1 Data not sufficient to estimate Data not sufficient to estimate \n", 1371 | "2 Data not sufficient to estimate Data not sufficient to estimate \n", 1372 | "3 Data not sufficient to estimate Data not sufficient to estimate \n", 1373 | "4 19200 28800 \n", 1374 | "5 Data not sufficient to estimate Data not sufficient to estimate \n", 1375 | "6 Data not sufficient to estimate Data not sufficient to estimate \n", 1376 | "7 5867 8800 \n", 1377 | "8 Data not sufficient to estimate Data not sufficient to estimate \n", 1378 | "9 Data not sufficient to estimate Data not sufficient to estimate \n", 1379 | "10 Data not sufficient to estimate Data not sufficient to estimate \n", 1380 | "11 Data not sufficient to estimate Data not sufficient to estimate " 1381 | ] 1382 | }, 1383 | "execution_count": 35, 1384 | "metadata": {}, 1385 | "output_type": "execute_result" 1386 | } 1387 | ], 1388 | "source": [ 1389 | "#change column order\n", 1390 | "cols = ['epic', 'summary', 'SP', 'estimatedPerc', 'minCost', 'avgCost', 'maxCost']\n", 1391 | "aggrIssues = aggrIssues[cols]\n", 1392 | "\n", 1393 | "\n", 1394 | "aggrIssues" 1395 | ] 1396 | }, 1397 | { 1398 | "cell_type": "code", 1399 | "execution_count": null, 1400 | "metadata": {}, 1401 | "outputs": [], 1402 | "source": [] 1403 | } 1404 | ], 1405 | "metadata": { 1406 | "kernelspec": { 1407 | "display_name": "Python 3", 1408 | "language": "python", 1409 | "name": "python3" 1410 | }, 1411 | "language_info": { 1412 | "codemirror_mode": { 1413 | "name": "ipython", 1414 | "version": 3 1415 | }, 1416 | "file_extension": ".py", 1417 | "mimetype": "text/x-python", 1418 | "name": "python", 1419 | "nbconvert_exporter": "python", 1420 | "pygments_lexer": "ipython3", 1421 | "version": "3.5.1" 1422 | } 1423 | }, 1424 | "nbformat": 4, 1425 | "nbformat_minor": 2 1426 | } 1427 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jira And Python For Managers 2 | Ideas on how to use Jira with Python 3 | 4 | ![Screenshot](burndown_chart.png) 5 | 6 | # Install Jupyter notebook 7 | http://jupyter.readthedocs.io/en/latest/install.html 8 | 9 | # Clone repository on your machine 10 | https://help.github.com/articles/cloning-a-repository/ 11 | 12 | # Set up Jira credentials 13 | https://gist.github.com/robert-krasinski/c677a470a11050355617ca1f0822a37b 14 | 15 | # Run 16 | Execute: 'jupyter notebook' command from cloned repository folder 17 | 18 | 19 | # Examples: 20 | * [How to improve your velocity estimation with machine learning methods?](https://robert-krasinski.github.io/JiraAndPythonForManagers/How%20to%20improve%20your%20velocity%20estimation%20with%20machine%20learning%20methods_.html) 21 | * [Prediction intervals in Agile forecasting¶](https://robert-krasinski.github.io/JiraAndPythonForManagers/Prediction%20intervals%20in%20Agile%20forecasting.html) 22 | * [Backlog health score](https://robert-krasinski.github.io/JiraAndPythonForManagers/Backlog%20health%20score-Extended%20version%20for%20blog.html) 23 | * [Epic development cost estimation](https://robert-krasinski.github.io/JiraAndPythonForManagers/Epic%20costs%20estimation%20-%20extended%20version%20for%20blog.html) 24 | -------------------------------------------------------------------------------- /StableVelocity.csv: -------------------------------------------------------------------------------- 1 | sprintNo;velocity 2 | 1;19 3 | 2;20 4 | 3;18 5 | 4;19 6 | 5;20 7 | 6;19 8 | 7;21 9 | 8;22 10 | 9;19 11 | 10;20 -------------------------------------------------------------------------------- /Story SP equals subtasks sum.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from jira import JIRA\n", 10 | "import matplotlib.pyplot as plt\n", 11 | "import pandas as pd\n", 12 | "from IPython.core.interactiveshell import InteractiveShell\n", 13 | "InteractiveShell.ast_node_interactivity = \"all\"" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": null, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "jira_url = 'https://kainos-evolve.atlassian.net'\n", 23 | "jira = JIRA(jira_url)\n", 24 | "\n" 25 | ] 26 | } 27 | ], 28 | "metadata": { 29 | "kernelspec": { 30 | "display_name": "Python 3", 31 | "language": "python", 32 | "name": "python3" 33 | }, 34 | "language_info": { 35 | "codemirror_mode": { 36 | "name": "ipython", 37 | "version": 3 38 | }, 39 | "file_extension": ".py", 40 | "mimetype": "text/x-python", 41 | "name": "python", 42 | "nbconvert_exporter": "python", 43 | "pygments_lexer": "ipython3", 44 | "version": "3.5.1" 45 | } 46 | }, 47 | "nbformat": 4, 48 | "nbformat_minor": 2 49 | } 50 | -------------------------------------------------------------------------------- /Untitled.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Easy way to improve your scrum team velocity forecast\n", 8 | "\n", 9 | "## Scrum book running average definition\n", 10 | "\n" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": {}, 16 | "source": [ 17 | "## Examples" 18 | ] 19 | }, 20 | { 21 | "cell_type": "markdown", 22 | "metadata": {}, 23 | "source": [ 24 | "### Stable velocity" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 308, 30 | "metadata": { 31 | "ExecuteTime": { 32 | "end_time": "2018-10-31T13:29:40.346883Z", 33 | "start_time": "2018-10-31T13:29:40.341263Z" 34 | } 35 | }, 36 | "outputs": [], 37 | "source": [ 38 | "import pandas as pd\n", 39 | "import numpy as np\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "from scipy.stats import norm" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": 309, 47 | "metadata": { 48 | "ExecuteTime": { 49 | "end_time": "2018-10-31T13:29:40.370775Z", 50 | "start_time": "2018-10-31T13:29:40.350986Z" 51 | } 52 | }, 53 | "outputs": [], 54 | "source": [ 55 | "stable = pd.read_csv('StableVelocity.csv', delimiter=';')\n", 56 | "\n", 57 | "stable['velocityAvg'] = round(stable['velocity'].shift().rolling(3, min_periods=1).mean(), 2)\n", 58 | "stable['error'] = round(stable['velocity'] - stable['velocityAvg'], 2)\n", 59 | "# rollAvg['estVelocityWBugs'] = round(rollAvg['capacity'] * rollAvg['ratioMean3'] + 2.64, 3) \n", 60 | "# rollAvg['error'] = rollAvg['estVelocityWBugs'] - rollAvg['velocityWBugs']\n", 61 | "#stable\n", 62 | "\n" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": 310, 68 | "metadata": { 69 | "ExecuteTime": { 70 | "end_time": "2018-10-31T13:29:40.635320Z", 71 | "start_time": "2018-10-31T13:29:40.374694Z" 72 | } 73 | }, 74 | "outputs": [ 75 | { 76 | "data": { 77 | "image/png": "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L998800D//A1V3e6opq/LEohpG1Lno3v16hjDA4z3/jelnnizE1wzRjNq2E4SHh4OMOGDQPgl19+4aqrrqImHfDGG29k8+bNVjeMMWPG0LZtWwD69etHZmYmBQUFjBw58sxS59OmTatzsl5hYSG33HILe/fuRUSorKwEzOtMjRs3jjlz5rBo0aIz90y++uorVqxYceasoKysjEOHDgEwduzYM++nlGL27Nls3rwZDw8PsrKyyMvL44cffuDaa6/F19cXX19f4uPjAfNy7lu2bGHatGlnaisv18Mim6PX1u7iQEEpn9w5FH9f70YdI9ivBeHtWpGYeZy7uMTGFWq1Na+GcZEzAaO0bl33TNXGaNHit+u7np6eDbqH8MwzzzBq1CiWL1/OwYMHueqqqwDo3Lkz7dq1IzU1lc8//5x3330XMDeCpUuX0rt377OOs3Xr1rN+Tx9//DH5+fkkJSXh7e1NREQEZWVl9dZhMpkICAggOTm53m009/eDJW711uERTV6iPCY8kM17ClBKWX3DXGs4w+5hiEhXEdkoIukislNEHrC8Ps3y3CQi9S54JSIHRSRNRJJFJLG+7VzNkCFD+PbbbykoKKC6uppPP/2UkSNHNumYl156Kd9++y0nTpygqqqKpUuX1rldYWEhnTubhysuWLDgrO/NmDGD119/ncLCQqKiogC45ppr+Pvf/07NApXbt2+v97gdOnTA29ubjRs3kpmZCcCIESNISEigrKyMkpISVq5cCYC/vz/dunVj8eLFgLkxpaSkNOnPQHMtRWWVPLo4hUvat+bx8X2afLyY8EAKSso5dFxn2RvJyJveVcAjSql+wDDgPhHpB+wApgKbrTjGKKXUQGtXUnQFISEhzJ07l1GjRhEdHU1MTAzXXnttk47ZuXNnZs+ezZAhQxgxYgQRERFnLlvV9thjj/Hkk08yaNCg885MbrjhBj777DOmT59+5rVnnnmGyspKoqKi6N+/P88880yd73/jjTeSmJjIgAED+O9//0ufPuYfAJdeeimTJ08mKiqKCRMmMGDAgDN1ffzxx7z//vtER0fTv3//s26Ua+7vhYR0covK+Ov0aFr6eDb5eLHh5sujenitwZRSdvkCvgTG1nq+CYi9wPYHgfYNeY+YmBh1rvT09PNec0fFxcVKKaUqKytVXFycWrZsmYMrMqupq7S0VMXExKik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\n", 78 | "text/plain": [ 79 | "" 80 | ] 81 | }, 82 | "metadata": {}, 83 | "output_type": "display_data" 84 | } 85 | ], 86 | "source": [ 87 | "_ = plt.xticks( stable.index.values, stable.sprintNo ) # location, labels\n", 88 | "_ = plt.plot( stable['velocity'])\n", 89 | "_ = plt.plot( stable['velocityAvg'])\n", 90 | "\n", 91 | "_ = plt.xlabel('Sprint')\n", 92 | "_ = plt.ylabel('velocity')\n", 93 | "_ = plt.legend(['velocity', 'rolling average'])\n", 94 | "_ = plt.show()" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": {}, 100 | "source": [ 101 | "### Growing velocity" 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": null, 107 | "metadata": {}, 108 | "outputs": [], 109 | "source": [] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "## Residuals analysis and prediction intervals\n", 116 | "## Improvement on examples" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 311, 122 | "metadata": { 123 | "ExecuteTime": { 124 | "end_time": "2018-10-31T13:29:40.929231Z", 125 | "start_time": "2018-10-31T13:29:40.638729Z" 126 | } 127 | }, 128 | "outputs": [ 129 | { 130 | "data": { 131 | "image/png": "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\n", 132 | "text/plain": [ 133 | "" 134 | ] 135 | }, 136 | "metadata": {}, 137 | "output_type": "display_data" 138 | } 139 | ], 140 | "source": [ 141 | "\n", 142 | "errors = stable.error.fillna(0)\n", 143 | "_ = plt.hist(errors, bins=5, density=True, alpha=0.6, color='g')\n", 144 | "\n", 145 | "# # Fit a normal distribution to the data:\n", 146 | "mu, std = norm.fit(errors)\n", 147 | "\n", 148 | "\n", 149 | "# Plot the PDF.\n", 150 | "xmin, xmax = plt.xlim()\n", 151 | "x = np.linspace(xmin, xmax, 100)\n", 152 | "p = norm.pdf(x, mu, std)\n", 153 | "plt.plot(x, p, 'k', linewidth=2)\n", 154 | "title = \"Residue rollAvg Fit results: mu = %.2f, std = %.2f\" % (mu, std)\n", 155 | "plt.title(title)\n", 156 | "\n", 157 | "_ = plt.show()" 158 | ] 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "metadata": {}, 163 | "source": [ 164 | "# Sources\n", 165 | "* http://scrumbook.org/value-stream/running-average-velocity.html\n", 166 | "* https://otexts.org/fpp2/residuals.html" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [] 175 | } 176 | ], 177 | "metadata": { 178 | "kernelspec": { 179 | "display_name": "Python 3", 180 | "language": "python", 181 | "name": "python3" 182 | }, 183 | "language_info": { 184 | "codemirror_mode": { 185 | "name": "ipython", 186 | "version": 3 187 | }, 188 | "file_extension": ".py", 189 | "mimetype": "text/x-python", 190 | "name": "python", 191 | "nbconvert_exporter": "python", 192 | "pygments_lexer": "ipython3", 193 | "version": "3.5.1" 194 | } 195 | }, 196 | "nbformat": 4, 197 | "nbformat_minor": 2 198 | } 199 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /finishedItems.csv: -------------------------------------------------------------------------------- 1 | ;devTeam;key;lastMove;observationDate;sp;type 2 | 0;Team M;TUH-1478;12/05/2020;2020-05-22 15:04:30.182284;1.0;Bug 3 | 1;Team S;TUH-1444;18/05/2020;2020-05-22 15:04:30.182284;3.0;Task 4 | 2;Team S;TUH-1443;06/05/2020;2020-05-22 15:04:30.182284;2.0;Task 5 | 3;Team S;TUH-1441;06/05/2020;2020-05-22 15:04:30.182284;1.0;Bug 6 | 4;Team O;TUH-1440;23/04/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 7 | 5;Team S;TUH-1437;30/04/2020;2020-05-22 15:04:30.182284;2.0;Bug 8 | 6;Team O;TUH-1425;20/04/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 9 | 7;Team M;TUH-1423;08/05/2020;2020-05-22 15:04:30.182284;3.0;Task 10 | 8;Team O;TUH-1420;07/05/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 11 | 9;Team M;TUH-1401;18/05/2020;2020-05-22 15:04:30.182284;3.0;Task 12 | 10;Team M;TUH-1398;05/05/2020;2020-05-22 15:04:30.182284;2.0;Story 13 | 11;Team M;TUH-1397;14/05/2020;2020-05-22 15:04:30.182284;2.0;Story 14 | 12;Team O;TUH-1396;22/04/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 15 | 13;Team O;TUH-1395;09/04/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 16 | 14;Team M;TUH-1389;22/04/2020;2020-05-22 15:04:30.182284;3.0;Task 17 | 15;Team M;TUH-1388;30/04/2020;2020-05-22 15:04:30.182284;1.0;Task 18 | 16;Team O;TUH-1381;16/04/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 19 | 17;Team M;TUH-1371;14/05/2020;2020-05-22 15:04:30.182284;5.0;Story 20 | 18;Team S;TUH-1366;11/05/2020;2020-05-22 15:04:30.182284;2.0;Story 21 | 19;Team S;TUH-1365;07/05/2020;2020-05-22 15:04:30.182284;2.0;Story 22 | 20;Team M;TUH-1362;30/03/2020;2020-05-22 15:04:30.182284;1.0;Task 23 | 21;Team O;TUH-1361;23/04/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 24 | 22;Team O;TUH-1360;08/04/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 25 | 23;Team M;TUH-1348;30/04/2020;2020-05-22 15:04:30.182284;3.0;Story 26 | 24;Team O;TUH-1343;21/05/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 27 | 25;Team O;TUH-1341;19/05/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 28 | 26;Team O;TUH-1340;29/04/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 29 | 27;Team O;TUH-1339;06/05/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 30 | 28;Team O;TUH-1338;07/04/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 31 | 29;Team S;TUH-1337;23/04/2020;2020-05-22 15:04:30.182284;4.0;Story 32 | 30;Team O;TUH-1330;21/05/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 33 | 31;Team M;TUH-1325;19/03/2020;2020-05-22 15:04:30.182284;5.0;Task 34 | 32;Team S;TUH-1317;06/05/2020;2020-05-22 15:04:30.182284;2.0;Task 35 | 33;Team S;TUH-1316;06/05/2020;2020-05-22 15:04:30.182284;2.0;Task 36 | 34;Team M;TUH-1304;06/03/2020;2020-05-22 15:04:30.182284;2.0;Task 37 | 35;Team O;TUH-1302;03/04/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 38 | 36;Team S;TUH-1300;12/03/2020;2020-05-22 15:04:30.182284;1.0;Bug 39 | 37;Team S;TUH-1299;11/03/2020;2020-05-22 15:04:30.182284;2.0;Task 40 | 38;Team S;TUH-1298;11/03/2020;2020-05-22 15:04:30.182284;2.0;Story 41 | 39;Team O;TUH-1296;05/03/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 42 | 40;Team O;TUH-1292;20/05/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 43 | 41;Team M;TUH-1290;11/03/2020;2020-05-22 15:04:30.182284;3.0;Bug 44 | 42;Team M;TUH-1286;14/05/2020;2020-05-22 15:04:30.182284;2.0;Story 45 | 43;Team S;TUH-1284;25/02/2020;2020-05-22 15:04:30.182284;2.0;Task 46 | 44;Team S;TUH-1282;16/04/2020;2020-05-22 15:04:30.182284;3.0;Task 47 | 45;Team S;TUH-1281;25/02/2020;2020-05-22 15:04:30.182284;1.0;Task 48 | 46;Team S;TUH-1280;04/03/2020;2020-05-22 15:04:30.182284;5.0;Task 49 | 47;Team M;TUH-1277;27/02/2020;2020-05-22 15:04:30.182284;2.0;Task 50 | 48;Team O;TUH-1275;12/02/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 51 | 49;Team S;TUH-1272;07/04/2020;2020-05-22 15:04:30.182284;2.0;Story 52 | 50;Team S;TUH-1268;23/04/2020;2020-05-22 15:04:30.182284;5.0;Story 53 | 51;Team S;TUH-1266;26/02/2020;2020-05-22 15:04:30.182284;2.0;Task 54 | 52;Team S;TUH-1264;20/05/2020;2020-05-22 15:04:30.182284;2.0;Story 55 | 53;Team S;TUH-1263;23/04/2020;2020-05-22 15:04:30.182284;1.0;Story 56 | 54;Team S;TUH-1262;20/04/2020;2020-05-22 15:04:30.182284;1.0;Story 57 | 55;Team S;TUH-1260;23/04/2020;2020-05-22 15:04:30.182284;3.0;Story 58 | 56;Team M;TUH-1259;30/03/2020;2020-05-22 15:04:30.182284;1.0;Story 59 | 57;Team M;TUH-1258;11/03/2020;2020-05-22 15:04:30.182284;0.5;Story 60 | 58;Team S;TUH-1255;11/03/2020;2020-05-22 15:04:30.182284;3.0;Task 61 | 59;Team S;TUH-1254;26/02/2020;2020-05-22 15:04:30.182284;3.0;Task 62 | 60;Team O;TUH-1252;08/04/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 63 | 61;Team O;TUH-1250;25/03/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 64 | 62;Team O;TUH-1245;17/02/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 65 | 63;Team M;TUH-1244;27/02/2020;2020-05-22 15:04:30.182284;3.0;Task 66 | 64;Team S;TUH-1243;06/04/2020;2020-05-22 15:04:30.182284;1.0;Story 67 | 65;Team O;TUH-1241;09/03/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 68 | 66;Team M;TUH-1240;12/02/2020;2020-05-22 15:04:30.182284;3.0;Task 69 | 67;Team S;TUH-1238;30/04/2020;2020-05-22 15:04:30.182284;3.0;Story 70 | 68;Team S;TUH-1237;30/04/2020;2020-05-22 15:04:30.182284;2.0;Story 71 | 69;Team S;TUH-1236;07/05/2020;2020-05-22 15:04:30.182284;2.0;Story 72 | 70;Team S;TUH-1235;20/04/2020;2020-05-22 15:04:30.182284;1.0;Story 73 | 71;Team S;TUH-1234;23/04/2020;2020-05-22 15:04:30.182284;2.0;Story 74 | 72;Team S;TUH-1233;11/05/2020;2020-05-22 15:04:30.182284;2.0;Story 75 | 73;Team S;TUH-1232;13/02/2020;2020-05-22 15:04:30.182284;2.0;Task 76 | 74;Team O;TUH-1224;05/02/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 77 | 75;Team O;TUH-1223;05/02/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 78 | 76;Team O;TUH-1222;26/02/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 79 | 77;Team O;TUH-1218;20/04/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 80 | 78;Team O;TUH-1197;11/03/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 81 | 79;Team M;TUH-1194;22/01/2020;2020-05-22 15:04:30.182284;5.0;Task 82 | 80;Team M;TUH-1192;12/02/2020;2020-05-22 15:04:30.182284;5.0;Task 83 | 81;Team O;TUH-1190;10/02/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 84 | 82;Team O;TUH-1189;26/02/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 85 | 83;Team O;TUH-1186;25/03/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 86 | 84;Team O;TUH-1185;09/03/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 87 | 85;Team O;TUH-1184;29/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 88 | 86;Team O;TUH-1182;26/02/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 89 | 87;Team O;TUH-1181;13/02/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 90 | 88;Team S;TUH-1180;15/01/2020;2020-05-22 15:04:30.182284;3.0;Task 91 | 89;Team O;TUH-1179;17/12/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 92 | 90;Team O;TUH-1177;09/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 93 | 91;Team O;TUH-1176;12/02/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 94 | 92;Team O;TUH-1175;29/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 95 | 93;Team O;TUH-1174;11/03/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 96 | 94;Team O;TUH-1170;13/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 97 | 95;Team O;TUH-1169;27/01/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 98 | 96;Team O;TUH-1168;16/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 99 | 97;Team S;TUH-1166;26/02/2020;2020-05-22 15:04:30.182284;3.0;Story 100 | 98;Team S;TUH-1165;28/01/2020;2020-05-22 15:04:30.182284;3.0;Story 101 | 99;Team S;TUH-1164;18/12/2019;2020-05-22 15:04:30.182284;2.0;Story 102 | 100;Team S;TUH-1160;18/12/2019;2020-05-22 15:04:30.182284;1.0;Story 103 | 101;Team S;TUH-1159;22/01/2020;2020-05-22 15:04:30.182284;1.0;Story 104 | 102;Team O;TUH-1156;17/12/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 105 | 103;Team O;TUH-1154;16/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 106 | 104;Team M;TUH-1149;18/12/2019;2020-05-22 15:04:30.182284;2.0;Story 107 | 105;Team S;TUH-1148;13/02/2020;2020-05-22 15:04:30.182284;3.0;Story 108 | 106;Team O;TUH-1147;13/12/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 109 | 107;Team O;TUH-1145;29/11/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 110 | 108;Team O;TUH-1144;05/12/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 111 | 109;Team M;TUH-1142;15/01/2020;2020-05-22 15:04:30.182284;5.0;Story 112 | 110;Team O;TUH-1138;20/02/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 113 | 111;Team O;TUH-1137;18/12/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 114 | 112;Team O;TUH-1136;29/01/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 115 | 113;Team S;TUH-1135;13/12/2019;2020-05-22 15:04:30.182284;1.0;Story 116 | 114;Team S;TUH-1134;15/01/2020;2020-05-22 15:04:30.182284;1.0;Story 117 | 115;Team S;TUH-1133;12/12/2019;2020-05-22 15:04:30.182284;2.0;Story 118 | 116;Team S;TUH-1132;12/12/2019;2020-05-22 15:04:30.182284;2.0;Story 119 | 117;Team S;TUH-1131;09/04/2020;2020-05-22 15:04:30.182284;3.0;Task 120 | 118;Team S;TUH-1127;10/03/2020;2020-05-22 15:04:30.182284;0.0;Story 121 | 119;Team S;TUH-1126;05/02/2020;2020-05-22 15:04:30.182284;5.0;Story 122 | 120;Team M;TUH-1124;04/12/2019;2020-05-22 15:04:30.182284;3.0;Story 123 | 121;Team S;TUH-1121;11/03/2020;2020-05-22 15:04:30.182284;3.0;Story 124 | 122;Team S;TUH-1120;15/01/2020;2020-05-22 15:04:30.182284;2.0;Story 125 | 123;Team S;TUH-1119;13/02/2020;2020-05-22 15:04:30.182284;2.0;Story 126 | 124;Team S;TUH-1118;28/01/2020;2020-05-22 15:04:30.182284;3.0;Story 127 | 125;Team S;TUH-1117;20/11/2019;2020-05-22 15:04:30.182284;2.0;Story 128 | 126;Team S;TUH-1116;19/11/2019;2020-05-22 15:04:30.182284;2.0;Story 129 | 127;Team O;TUH-1113;04/12/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 130 | 128;Team O;TUH-1112;20/11/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 131 | 129;Team S;TUH-1111;20/11/2019;2020-05-22 15:04:30.182284;2.0;Story 132 | 130;Team S;TUH-1110;14/11/2019;2020-05-22 15:04:30.182284;1.0;Story 133 | 131;Team S;TUH-1109;28/01/2020;2020-05-22 15:04:30.182284;5.0;Story 134 | 132;Team S;TUH-1108;19/11/2019;2020-05-22 15:04:30.182284;3.0;Story 135 | 133;Team M;TUH-1105;18/12/2019;2020-05-22 15:04:30.182284;2.0;Story 136 | 134;Team S;TUH-1104;19/11/2019;2020-05-22 15:04:30.182284;1.0;Story 137 | 135;Team O;TUH-1103;04/12/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 138 | 136;Team O;TUH-1102;18/11/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 139 | 137;Team O;TUH-1101;14/11/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 140 | 138;Team S;TUH-1100;18/11/2019;2020-05-22 15:04:30.182284;1.0;Bug 141 | 139;Team O;TUH-1099;19/11/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 142 | 140;Team O;TUH-1098;30/01/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 143 | 141;Team S;TUH-1096;01/04/2020;2020-05-22 15:04:30.182284;3.0;Story 144 | 142;Team M;TUH-1094;20/11/2019;2020-05-22 15:04:30.182284;5.0;Story 145 | 143;Team S;TUH-1090;01/04/2020;2020-05-22 15:04:30.182284;2.0;Story 146 | 144;Team S;TUH-1089;28/01/2020;2020-05-22 15:04:30.182284;3.0;Story 147 | 145;Team M;TUH-1085;18/12/2019;2020-05-22 15:04:30.182284;3.0;Story 148 | 146;Team M;TUH-1084;05/12/2019;2020-05-22 15:04:30.182284;2.0;Story 149 | 147;Team O;TUH-1081;06/11/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 150 | 148;Team O;TUH-1080;04/12/2019;2020-05-22 15:04:30.182284;5.0;Ops Task 151 | 149;Team O;TUH-1078;06/11/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 152 | 150;Team O;TUH-1076;06/11/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 153 | 151;Team S;TUH-1075;05/11/2019;2020-05-22 15:04:30.182284;2.0;Bug 154 | 152;Team S;TUH-1074;04/11/2019;2020-05-22 15:04:30.182284;2.0;Story 155 | 153;Team S;TUH-1073;16/01/2020;2020-05-22 15:04:30.182284;1.0;Story 156 | 154;Team S;TUH-1072;05/12/2019;2020-05-22 15:04:30.182284;2.0;Story 157 | 155;Team O;TUH-1071;23/10/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 158 | 156;Team O;TUH-1070;09/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 159 | 157;Team S;TUH-1067;23/10/2019;2020-05-22 15:04:30.182284;1.0;Story 160 | 158;Team M;TUH-1064;04/02/2020;2020-05-22 15:04:30.182284;5.0;Story 161 | 159;Team S;TUH-1063;14/11/2019;2020-05-22 15:04:30.182284;2.0;Story 162 | 160;Team S;TUH-1062;13/11/2019;2020-05-22 15:04:30.182284;2.0;Story 163 | 161;Team O;TUH-1058;15/10/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 164 | 162;Team M;TUH-1054;14/10/2019;2020-05-22 15:04:30.182284;2.0;Story 165 | 163;Team O;TUH-1052;15/10/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 166 | 164;Team S;TUH-1048;06/11/2019;2020-05-22 15:04:30.182284;2.0;Story 167 | 165;Team S;TUH-1047;06/11/2019;2020-05-22 15:04:30.182284;1.0;Story 168 | 166;Team M;TUH-1046;22/10/2019;2020-05-22 15:04:30.182284;2.0;Task 169 | 167;Team O;TUH-1044;08/10/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 170 | 168;Team S;TUH-1043;06/11/2019;2020-05-22 15:04:30.182284;5.0;Story 171 | 169;Team O;TUH-1042;21/10/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 172 | 170;Team S;TUH-1041;13/12/2019;2020-05-22 15:04:30.182284;2.0;Story 173 | 171;Team S;TUH-1035;22/10/2019;2020-05-22 15:04:30.182284;2.0;Story 174 | 172;Team M;TUH-1033;09/10/2019;2020-05-22 15:04:30.182284;2.0;Story 175 | 173;Team S;TUH-1032;22/10/2019;2020-05-22 15:04:30.182284;5.0;Story 176 | 174;Team S;TUH-1031;09/10/2019;2020-05-22 15:04:30.182284;5.0;Story 177 | 175;Team O;TUH-1026;11/03/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 178 | 176;Team O;TUH-1025;18/09/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 179 | 177;Team O;TUH-1021;29/01/2020;2020-05-22 15:04:30.182284;;Ops Task 180 | 178;Team O;TUH-1019;17/09/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 181 | 179;Team M;TUH-1018;18/11/2019;2020-05-22 15:04:30.182284;1.0;Story 182 | 180;Team M;TUH-1017;22/10/2019;2020-05-22 15:04:30.182284;2.0;Story 183 | 181;Team M;TUH-1016;25/09/2019;2020-05-22 15:04:30.182284;2.0;Story 184 | 182;Team M;TUH-1013;14/01/2020;2020-05-22 15:04:30.182284;3.0;Task 185 | 183;Team O;TUH-1012;15/10/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 186 | 184;Team S;TUH-1010;18/12/2019;2020-05-22 15:04:30.182284;3.0;Story 187 | 185;Team M;TUH-1009;08/11/2019;2020-05-22 15:04:30.182284;3.0;Story 188 | 186;Team M;TUH-1008;09/10/2019;2020-05-22 15:04:30.182284;3.0;Story 189 | 187;Team M;TUH-1007;09/10/2019;2020-05-22 15:04:30.182284;5.0;Story 190 | 188;Team S;TUH-1006;24/09/2019;2020-05-22 15:04:30.182284;3.0;Story 191 | 189;Team S;TUH-1005;24/09/2019;2020-05-22 15:04:30.182284;3.0;Story 192 | 190;Team M;TUH-1003;09/10/2019;2020-05-22 15:04:30.182284;3.0;Story 193 | 191;Team S;TUH-1001;18/12/2019;2020-05-22 15:04:30.182284;3.0;Story 194 | 192;Team O;TUH-1000;12/11/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 195 | 193;Team O;TUH-999;25/03/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 196 | 194;Team O;TUH-997;25/09/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 197 | 195;Team O;TUH-996;11/09/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 198 | 196;Team S;TUH-994;10/09/2019;2020-05-22 15:04:30.182284;1.0;Story 199 | 197;Team M;TUH-993;23/10/2019;2020-05-22 15:04:30.182284;2.0;Story 200 | 198;Team O;TUH-992;18/09/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 201 | 199;Team S;TUH-991;26/08/2019;2020-05-22 15:04:30.182284;0.5;Story 202 | 200;Team S;TUH-990;06/11/2019;2020-05-22 15:04:30.182284;2.0;Story 203 | 201;Team S;TUH-989;06/11/2019;2020-05-22 15:04:30.182284;2.0;Story 204 | 202;Team S;TUH-988;23/10/2019;2020-05-22 15:04:30.182284;3.0;Story 205 | 203;Team S;TUH-987;23/10/2019;2020-05-22 15:04:30.182284;3.0;Story 206 | 204;Team O;TUH-977;28/08/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 207 | 205;Team O;TUH-975;22/04/2020;2020-05-22 15:04:30.182284;5.0;Ops Task 208 | 206;Team S;TUH-970;22/08/2019;2020-05-22 15:04:30.182284;2.0;Story 209 | 207;Team O;TUH-969;07/10/2019;2020-05-22 15:04:30.182284;5.0;Ops Task 210 | 208;Team O;TUH-968;16/01/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 211 | 209;Team O;TUH-967;14/08/2019;2020-05-22 15:04:30.182284;1.0;Bug 212 | 210;Team O;TUH-962;09/08/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 213 | 211;Team M;TUH-957;25/09/2019;2020-05-22 15:04:30.182284;3.0;Story 214 | 212;Team M;TUH-955;19/09/2019;2020-05-22 15:04:30.182284;3.0;Story 215 | 213;Team O;TUH-954;14/08/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 216 | 214;Team S;TUH-943;05/12/2019;2020-05-22 15:04:30.182284;1.0;Story 217 | 215;Team S;TUH-920;22/04/2020;2020-05-22 15:04:30.182284;3.0;Story 218 | 216;Team S;TUH-919;20/05/2020;2020-05-22 15:04:30.182284;2.0;Story 219 | 217;Team S;TUH-917;06/04/2020;2020-05-22 15:04:30.182284;3.0;Story 220 | 218;Team M;TUH-910;11/03/2020;2020-05-22 15:04:30.182284;2.0;Story 221 | 219;Team M;TUH-907;23/04/2020;2020-05-22 15:04:30.182284;3.0;Story 222 | 220;Team M;TUH-906;11/03/2020;2020-05-22 15:04:30.182284;1.0;Story 223 | 221;Team M;TUH-905;07/05/2020;2020-05-22 15:04:30.182284;2.0;Story 224 | 222;Team M;TUH-904;24/04/2020;2020-05-22 15:04:30.182284;2.0;Story 225 | 223;Team M;TUH-903;23/04/2020;2020-05-22 15:04:30.182284;5.0;Story 226 | 224;Team M;TUH-902;24/04/2020;2020-05-22 15:04:30.182284;1.0;Story 227 | 225;Team M;TUH-901;23/04/2020;2020-05-22 15:04:30.182284;5.0;Story 228 | 226;Team M;TUH-900;23/04/2020;2020-05-22 15:04:30.182284;5.0;Story 229 | 227;Team M;TUH-899;26/03/2020;2020-05-22 15:04:30.182284;5.0;Story 230 | 228;Team M;TUH-897;18/05/2020;2020-05-22 15:04:30.182284;2.0;Story 231 | 229;Team M;TUH-896;19/03/2020;2020-05-22 15:04:30.182284;3.0;Story 232 | 230;Team M;TUH-895;07/05/2020;2020-05-22 15:04:30.182284;1.0;Story 233 | 231;Team M;TUH-890;20/11/2019;2020-05-22 15:04:30.182284;3.0;Story 234 | 232;Team S;TUH-881;15/01/2020;2020-05-22 15:04:30.182284;1.0;Story 235 | 233;Team M;TUH-878;29/10/2019;2020-05-22 15:04:30.182284;3.0;Story 236 | 234;Team M;TUH-873;31/10/2019;2020-05-22 15:04:30.182284;1.0;Story 237 | 235;Team M;TUH-872;04/11/2019;2020-05-22 15:04:30.182284;5.0;Story 238 | 236;Team M;TUH-871;14/11/2019;2020-05-22 15:04:30.182284;3.0;Story 239 | 237;Team M;TUH-867;05/12/2019;2020-05-22 15:04:30.182284;3.0;Story 240 | 238;Team M;TUH-864;18/11/2019;2020-05-22 15:04:30.182284;2.0;Story 241 | 239;Team S;TUH-859;13/02/2020;2020-05-22 15:04:30.182284;3.0;Story 242 | 240;Team M;TUH-858;23/10/2019;2020-05-22 15:04:30.182284;5.0;Story 243 | 241;Team M;TUH-855;05/12/2019;2020-05-22 15:04:30.182284;3.0;Story 244 | 242;Team M;TUH-854;05/11/2019;2020-05-22 15:04:30.182284;3.0;Story 245 | 243;Team O;TUH-674;01/08/2019;2020-05-22 15:04:30.182284;0.5;Ops Task 246 | 244;Team O;TUH-673;02/08/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 247 | 245;Team O;TUH-672;01/08/2019;2020-05-22 15:04:30.182284;0.5;Bug 248 | 246;Team O;TUH-671;01/10/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 249 | 247;Team S;TUH-670;05/12/2019;2020-05-22 15:04:30.182284;2.0;Story 250 | 248;Team O;TUH-662;20/12/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 251 | 249;Team O;TUH-661;07/08/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 252 | 250;Team S;TUH-654;13/08/2019;2020-05-22 15:04:30.182284;3.0;Story 253 | 251;Team O;TUH-650;09/03/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 254 | 252;Team O;TUH-647;14/08/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 255 | 253;Team S;TUH-646;31/07/2019;2020-05-22 15:04:30.182284;5.0;Story 256 | 254;Team O;TUH-636;18/10/2019;2020-05-22 15:04:30.182284;5.0;Ops Task 257 | 255;Team O;TUH-634;12/07/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 258 | 256;Team O;TUH-633;31/07/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 259 | 257;Team O;TUH-632;15/07/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 260 | 258;Team O;TUH-630;15/07/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 261 | 259;Team O;TUH-629;17/07/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 262 | 260;Team O;TUH-628;28/08/2019;2020-05-22 15:04:30.182284;5.0;Ops Task 263 | 261;Team O;TUH-627;03/07/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 264 | 262;Team O;TUH-626;01/10/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 265 | 263;Team O;TUH-625;18/09/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 266 | 264;Team O;TUH-624;11/09/2019;2020-05-22 15:04:30.182284;5.0;Ops Task 267 | 265;Team O;TUH-623;11/09/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 268 | 266;Team O;TUH-622;07/10/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 269 | 267;Team O;TUH-621;09/10/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 270 | 268;Team O;TUH-620;11/09/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 271 | 269;Team O;TUH-619;10/09/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 272 | 270;Team O;TUH-618;01/10/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 273 | 271;Team O;TUH-617;19/09/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 274 | 272;Team O;TUH-616;18/09/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 275 | 273;Team O;TUH-615;08/10/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 276 | 274;Team O;TUH-614;08/10/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 277 | 275;Team O;TUH-609;03/07/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 278 | 276;Team O;TUH-608;17/07/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 279 | 277;Team O;TUH-607;31/07/2019;2020-05-22 15:04:30.182284;5.0;Ops Task 280 | 278;Team O;TUH-606;03/07/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 281 | 279;Team O;TUH-603;20/03/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 282 | 280;Team O;TUH-601;09/03/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 283 | 281;Team O;TUH-597;10/03/2020;2020-05-22 15:04:30.182284;;Ops Task 284 | 282;Team O;TUH-596;10/03/2020;2020-05-22 15:04:30.182284;;Ops Task 285 | 283;Team O;TUH-594;20/11/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 286 | 284;Team O;TUH-591;20/03/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 287 | 285;Team O;TUH-590;11/09/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 288 | 286;Team O;TUH-589;23/10/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 289 | 287;Team O;TUH-587;17/07/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 290 | 288;Team O;TUH-586;31/07/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 291 | 289;Team O;TUH-585;21/06/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 292 | 290;Team O;TUH-584;21/10/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 293 | 291;Team O;TUH-583;29/11/2019;2020-05-22 15:04:30.182284;5.0;Ops Task 294 | 292;Team M;TUH-580;25/09/2019;2020-05-22 15:04:30.182284;3.0;Story 295 | 293;Team O;TUH-578;28/08/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 296 | 294;Team O;TUH-577;09/08/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 297 | 295;Team O;TUH-576;19/06/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 298 | 296;Team O;TUH-575;17/06/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 299 | 297;Team O;TUH-574;17/06/2019;2020-05-22 15:04:30.182284;0.5;Ops Task 300 | 298;Team O;TUH-573;01/07/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 301 | 299;Team O;TUH-560;20/05/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 302 | 300;Team O;TUH-554;08/04/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 303 | 301;Team O;TUH-553;08/04/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 304 | 302;Team S;TUH-550;24/09/2019;2020-05-22 15:04:30.182284;5.0;Story 305 | 303;Team M;TUH-549;11/09/2019;2020-05-22 15:04:30.182284;2.0;Story 306 | 304;Team M;TUH-532;18/12/2019;2020-05-22 15:04:30.182284;3.0;Story 307 | 305;Team M;TUH-416;27/02/2020;2020-05-22 15:04:30.182284;2.0;Task 308 | 306;Team M;TUH-414;12/02/2020;2020-05-22 15:04:30.182284;5.0;Task 309 | 307;Team O;TUH-331;17/12/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 310 | 308;Team S;TUH-329;04/12/2019;2020-05-22 15:04:30.182284;3.0;Story 311 | 309;Team S;TUH-320;04/12/2019;2020-05-22 15:04:30.182284;2.0;Story 312 | 310;Team S;TUH-319;04/12/2019;2020-05-22 15:04:30.182284;3.0;Story 313 | 311;Team S;TUH-318;04/12/2019;2020-05-22 15:04:30.182284;1.0;Story 314 | 312;Team S;TUH-316;02/12/2019;2020-05-22 15:04:30.182284;5.0;Story 315 | 313;Team S;TUH-312;20/11/2019;2020-05-22 15:04:30.182284;1.0;Story 316 | 314;Team M;TUH-295;24/09/2019;2020-05-22 15:04:30.182284;1.0;Story 317 | 315;Team M;TUH-292;08/10/2019;2020-05-22 15:04:30.182284;2.0;Story 318 | 316;Team M;TUH-284;25/09/2019;2020-05-22 15:04:30.182284;5.0;Story 319 | 317;Team O;TUH-282;22/04/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 320 | 318;Team O;TUH-278;29/04/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 321 | 319;Team O;TUH-274;07/05/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 322 | 320;Team O;TUH-273;06/05/2020;2020-05-22 15:04:30.182284;3.0;Ops Task 323 | 321;Team O;TUH-270;06/05/2020;2020-05-22 15:04:30.182284;1.0;Ops Task 324 | 322;Team O;TUH-269;07/05/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 325 | 323;Team O;TUH-267;09/03/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 326 | 324;Team S;TUH-259;23/10/2019;2020-05-22 15:04:30.182284;3.0;Story 327 | 325;Team S;TUH-257;23/10/2019;2020-05-22 15:04:30.182284;2.0;Story 328 | 326;Team S;TUH-256;14/11/2019;2020-05-22 15:04:30.182284;3.0;Story 329 | 327;Team S;TUH-255;23/10/2019;2020-05-22 15:04:30.182284;5.0;Story 330 | 328;Team S;TUH-225;28/08/2019;2020-05-22 15:04:30.182284;2.0;Story 331 | 329;Team S;TUH-224;28/08/2019;2020-05-22 15:04:30.182284;2.0;Story 332 | 330;Team S;TUH-222;10/09/2019;2020-05-22 15:04:30.182284;2.0;Story 333 | 331;Team S;TUH-221;24/09/2019;2020-05-22 15:04:30.182284;2.0;Story 334 | 332;Team S;TUH-220;24/09/2019;2020-05-22 15:04:30.182284;2.0;Story 335 | 333;Team S;TUH-219;10/09/2019;2020-05-22 15:04:30.182284;1.0;Story 336 | 334;Team S;TUH-218;24/09/2019;2020-05-22 15:04:30.182284;2.0;Story 337 | 335;Team S;TUH-217;10/09/2019;2020-05-22 15:04:30.182284;2.0;Story 338 | 336;Team S;TUH-216;10/09/2019;2020-05-22 15:04:30.182284;2.0;Story 339 | 337;Team S;TUH-214;10/09/2019;2020-05-22 15:04:30.182284;2.0;Story 340 | 338;Team S;TUH-213;28/08/2019;2020-05-22 15:04:30.182284;1.0;Story 341 | 339;Team S;TUH-212;12/08/2019;2020-05-22 15:04:30.182284;1.0;Story 342 | 340;Team S;TUH-211;14/08/2019;2020-05-22 15:04:30.182284;2.0;Story 343 | 341;Team S;TUH-208;14/08/2019;2020-05-22 15:04:30.182284;2.0;Story 344 | 342;Team S;TUH-207;12/08/2019;2020-05-22 15:04:30.182284;2.0;Story 345 | 343;Team S;TUH-206;12/08/2019;2020-05-22 15:04:30.182284;2.0;Story 346 | 344;Team S;TUH-205;12/08/2019;2020-05-22 15:04:30.182284;2.0;Story 347 | 345;Team S;TUH-204;31/07/2019;2020-05-22 15:04:30.182284;2.0;Story 348 | 346;Team S;TUH-203;31/07/2019;2020-05-22 15:04:30.182284;2.0;Story 349 | 347;Team S;TUH-202;31/07/2019;2020-05-22 15:04:30.182284;2.0;Story 350 | 348;Team S;TUH-195;24/09/2019;2020-05-22 15:04:30.182284;2.0;Story 351 | 349;Team S;TUH-194;14/08/2019;2020-05-22 15:04:30.182284;3.0;Story 352 | 350;Team S;TUH-193;16/07/2019;2020-05-22 15:04:30.182284;3.0;Story 353 | 351;Team S;TUH-192;28/08/2019;2020-05-22 15:04:30.182284;3.0;Story 354 | 352;Team S;TUH-191;26/07/2019;2020-05-22 15:04:30.182284;2.0;Story 355 | 353;Team S;TUH-190;26/07/2019;2020-05-22 15:04:30.182284;1.0;Story 356 | 354;Team S;TUH-189;26/07/2019;2020-05-22 15:04:30.182284;2.0;Story 357 | 355;Team S;TUH-188;17/07/2019;2020-05-22 15:04:30.182284;2.0;Story 358 | 356;Team S;TUH-122;02/07/2019;2020-05-22 15:04:30.182284;0.0;Story 359 | 357;Team S;TUH-120;02/07/2019;2020-05-22 15:04:30.182284;0.0;Story 360 | 358;Team S;TUH-119;02/07/2019;2020-05-22 15:04:30.182284;0.0;Story 361 | 359;Team S;TUH-118;02/07/2019;2020-05-22 15:04:30.182284;0.0;Story 362 | 360;Team S;TUH-54;17/07/2019;2020-05-22 15:04:30.182284;5.0;Story 363 | 361;Team O;TUH-52;20/05/2020;2020-05-22 15:04:30.182284;2.0;Ops Task 364 | 362;Team O;TUH-49;07/06/2019;2020-05-22 15:04:30.182284;0.5;Ops Task 365 | 363;Team S;TUH-47;02/08/2019;2020-05-22 15:04:30.182284;3.0;Story 366 | 364;Team O;TUH-45;19/06/2019;2020-05-22 15:04:30.182284;3.0;Ops Task 367 | 365;Team O;TUH-44;03/07/2019;2020-05-22 15:04:30.182284;4.0;Ops Task 368 | 366;Team S;TUH-43;29/07/2019;2020-05-22 15:04:30.182284;3.0;Story 369 | 367;Team S;TUH-42;17/07/2019;2020-05-22 15:04:30.182284;3.0;Story 370 | 368;Team S;TUH-41;03/07/2019;2020-05-22 15:04:30.182284;2.0;Story 371 | 369;Team S;TUH-40;03/07/2019;2020-05-22 15:04:30.182284;2.0;Story 372 | 370;Team M;TUH-38;03/09/2019;2020-05-22 15:04:30.182284;2.0;Story 373 | 371;Team S;TUH-36;26/06/2019;2020-05-22 15:04:30.182284;3.0;Story 374 | 372;Team S;TUH-35;19/06/2019;2020-05-22 15:04:30.182284;0.5;Story 375 | 373;Team S;TUH-34;19/06/2019;2020-05-22 15:04:30.182284;2.0;Story 376 | 374;Team S;TUH-32;03/07/2019;2020-05-22 15:04:30.182284;2.0;Story 377 | 375;Team S;TUH-31;16/07/2019;2020-05-22 15:04:30.182284;3.0;Story 378 | 376;Team S;TUH-29;19/06/2019;2020-05-22 15:04:30.182284;5.0;Story 379 | 377;Team S;TUH-26;03/07/2019;2020-05-22 15:04:30.182284;1.0;Story 380 | 378;Team S;TUH-25;09/07/2019;2020-05-22 15:04:30.182284;5.0;Story 381 | 379;Team S;TUH-24;03/07/2019;2020-05-22 15:04:30.182284;2.0;Story 382 | 380;Team S;TUH-22;03/07/2019;2020-05-22 15:04:30.182284;5.0;Story 383 | 381;Team S;TUH-21;02/07/2019;2020-05-22 15:04:30.182284;3.0;Story 384 | 382;Team S;TUH-20;02/07/2019;2020-05-22 15:04:30.182284;1.0;Story 385 | 383;Team O;TUH-14;19/06/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 386 | 384;Team O;TUH-13;03/07/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 387 | 385;Team O;TUH-11;14/06/2019;2020-05-22 15:04:30.182284;2.0;Ops Task 388 | 386;Team O;TUH-5;07/06/2019;2020-05-22 15:04:30.182284;1.0;Ops Task 389 | 387;Team O;TUH-4;07/06/2019;2020-05-22 15:04:30.182284;0.5;Ops Task 390 | 388;Team O;TUH-3;07/06/2019;2020-05-22 15:04:30.182284;0.5;Ops Task -------------------------------------------------------------------------------- /images/boardId.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robert-krasinski/JiraAndPythonForManagers/9623a054636231e7d97cac5e244a08bb8ffe4dc2/images/boardId.png -------------------------------------------------------------------------------- /images/rules_score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robert-krasinski/JiraAndPythonForManagers/9623a054636231e7d97cac5e244a08bb8ffe4dc2/images/rules_score.png -------------------------------------------------------------------------------- /images/scoring_chart_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robert-krasinski/JiraAndPythonForManagers/9623a054636231e7d97cac5e244a08bb8ffe4dc2/images/scoring_chart_example.png -------------------------------------------------------------------------------- /sprints.csv: -------------------------------------------------------------------------------- 1 | sprintNo;velocity 2 | 0;12 3 | 1;14 4 | 2;10 5 | 3;14 6 | 4;16.0 7 | 5;9.5 8 | 6;11.0 9 | 7;8.5 10 | 8;11 11 | 9;15.0 12 | 10;15.0 13 | 11;11 14 | 12;16 15 | 13;10.5 16 | 14;14.0 17 | 15;15.0 18 | 16;20.0 19 | 17;10.5 20 | 18;17.5 21 | 19;14.5 22 | 20; 23 | 21; 24 | 22; -------------------------------------------------------------------------------- /sprintsData.csv: -------------------------------------------------------------------------------- 1 | sprintNo;velocity;capacity 2 | 0;34.0;60.0 3 | 1;36.5;58.5 4 | 2;34.0;53.0 5 | 3;23.0;50.0 6 | 4;16.0;56.5 7 | 5;9.5;35.5 8 | 6;11.0;56.5 9 | 7;8.5;25.0 10 | 8;24.0;59.0 11 | 9;15.0;42.5 12 | 10;15.0;48.5 13 | 11;2.5;52.0 14 | 12;20.5;60.0 15 | 13;10.5;50.5 16 | 14;14.0;46.5 17 | 15;15.0;52.5 18 | 16;20.0;55.5 19 | 17;10.5;53.0 20 | 18;17.5;45.0 21 | 19;14.5;54.5 --------------------------------------------------------------------------------