├── .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 | " key | \n",
185 | " category | \n",
186 | " type | \n",
187 | " team | \n",
188 | " SP | \n",
189 | " bugEffort | \n",
190 | " status | \n",
191 | " summary | \n",
192 | " month | \n",
193 | "
\n",
194 | " \n",
195 | " \n",
196 | " \n",
197 | " 34 | \n",
198 | " VXT-3810 | \n",
199 | " IC Platform - NFRs | \n",
200 | " Story | \n",
201 | " Gdansk Team 1 | \n",
202 | " 0.0 | \n",
203 | " NaN | \n",
204 | " Completed | \n",
205 | " Reimplementation of password reset endpoint | \n",
206 | " 1 | \n",
207 | "
\n",
208 | " \n",
209 | " 40 | \n",
210 | " VXT-3716 | \n",
211 | " IC Platform - Core Services | \n",
212 | " Story | \n",
213 | " Gdansk Team 1 | \n",
214 | " 0.0 | \n",
215 | " NaN | \n",
216 | " Completed | \n",
217 | " Cluster testing of Keycloak | \n",
218 | " 1 | \n",
219 | "
\n",
220 | " \n",
221 | " 43 | \n",
222 | " VXT-3685 | \n",
223 | " Care Pathway Framework - Controls | \n",
224 | " Story | \n",
225 | " Gdansk Team 2 | \n",
226 | " 0.0 | \n",
227 | " NaN | \n",
228 | " Completed | \n",
229 | " update doc and ui tests | \n",
230 | " 1 | \n",
231 | "
\n",
232 | " \n",
233 | " 63 | \n",
234 | " VXT-2593 | \n",
235 | " IC Platform - NFRs | \n",
236 | " Story | \n",
237 | " Gdansk Team 1 | \n",
238 | " 0.0 | \n",
239 | " NaN | \n",
240 | " Completed | \n",
241 | " HTML5 CORS Misconfiguration | \n",
242 | " 1 | \n",
243 | "
\n",
244 | " \n",
245 | " 64 | \n",
246 | " VXT-2435 | \n",
247 | " IC Platform - Core Services | \n",
248 | " Story | \n",
249 | " Gdansk Team 1 | \n",
250 | " 0.0 | \n",
251 | " NaN | \n",
252 | " Completed | \n",
253 | " Implement consul-template for BPM service | \n",
254 | " 1 | \n",
255 | "
\n",
256 | " \n",
257 | " 65 | \n",
258 | " VXT-2428 | \n",
259 | " IC Platform - Core Services | \n",
260 | " Story | \n",
261 | " Gdansk Team 1 | \n",
262 | " 0.0 | \n",
263 | " NaN | \n",
264 | " Completed | \n",
265 | " Implement consul-template for Integration-webs... | \n",
266 | " 1 | \n",
267 | "
\n",
268 | " \n",
269 | " 66 | \n",
270 | " VXT-2427 | \n",
271 | " IC Platform - Core Services | \n",
272 | " Story | \n",
273 | " Gdansk Team 1 | \n",
274 | " 0.0 | \n",
275 | " NaN | \n",
276 | " Completed | \n",
277 | " Implement consul-template for Integration-engi... | \n",
278 | " 1 | \n",
279 | "
\n",
280 | " \n",
281 | "
\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 | " month | \n",
358 | " category | \n",
359 | " SP | \n",
360 | " SPPerc | \n",
361 | " Days | \n",
362 | "
\n",
363 | " \n",
364 | " \n",
365 | " \n",
366 | " 0 | \n",
367 | " 1 | \n",
368 | " Care Pathway Framework - Apps | \n",
369 | " 2.0 | \n",
370 | " 0.04 | \n",
371 | " 4.04 | \n",
372 | "
\n",
373 | " \n",
374 | " 1 | \n",
375 | " 1 | \n",
376 | " Care Pathway Framework - Controls | \n",
377 | " 24.5 | \n",
378 | " 0.49 | \n",
379 | " 49.49 | \n",
380 | "
\n",
381 | " \n",
382 | " 2 | \n",
383 | " 1 | \n",
384 | " IC Platform - Core Services | \n",
385 | " 3.0 | \n",
386 | " 0.06 | \n",
387 | " 6.06 | \n",
388 | "
\n",
389 | " \n",
390 | " 3 | \n",
391 | " 1 | \n",
392 | " IC Platform - NFRs | \n",
393 | " 15.0 | \n",
394 | " 0.30 | \n",
395 | " 30.30 | \n",
396 | "
\n",
397 | " \n",
398 | " 4 | \n",
399 | " 1 | \n",
400 | " Integration & Connector Framework | \n",
401 | " 5.0 | \n",
402 | " 0.10 | \n",
403 | " 10.10 | \n",
404 | "
\n",
405 | " \n",
406 | " 5 | \n",
407 | " 2 | \n",
408 | " Care Pathway Framework - Apps | \n",
409 | " 3.0 | \n",
410 | " 0.08 | \n",
411 | " 7.69 | \n",
412 | "
\n",
413 | " \n",
414 | " 6 | \n",
415 | " 2 | \n",
416 | " Care Pathway Framework - Controls | \n",
417 | " 14.5 | \n",
418 | " 0.37 | \n",
419 | " 37.18 | \n",
420 | "
\n",
421 | " \n",
422 | " 7 | \n",
423 | " 2 | \n",
424 | " IC Platform - Core Services | \n",
425 | " 16.0 | \n",
426 | " 0.41 | \n",
427 | " 41.03 | \n",
428 | "
\n",
429 | " \n",
430 | " 8 | \n",
431 | " 2 | \n",
432 | " IC Platform - NFRs | \n",
433 | " 2.0 | \n",
434 | " 0.05 | \n",
435 | " 5.13 | \n",
436 | "
\n",
437 | " \n",
438 | " 9 | \n",
439 | " 2 | \n",
440 | " Integration & Connector Framework | \n",
441 | " 3.5 | \n",
442 | " 0.09 | \n",
443 | " 8.97 | \n",
444 | "
\n",
445 | " \n",
446 | "
\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 | " version | \n",
45 | " key | \n",
46 | " type | \n",
47 | " status | \n",
48 | " summary | \n",
49 | "
\n",
50 | " \n",
51 | " \n",
52 | " \n",
53 | " 0 | \n",
54 | " Frimley MVP | \n",
55 | " VXT-3952 | \n",
56 | " Epic | \n",
57 | " Tech. Scoping | \n",
58 | " Patient Manager Performance Improvements | \n",
59 | "
\n",
60 | " \n",
61 | " 1 | \n",
62 | " 1.14 | \n",
63 | " VXT-3952 | \n",
64 | " Epic | \n",
65 | " Tech. Scoping | \n",
66 | " Patient Manager Performance Improvements | \n",
67 | "
\n",
68 | " \n",
69 | " 2 | \n",
70 | " GCCG PHASE 1 | \n",
71 | " VXT-3773 | \n",
72 | " Epic | \n",
73 | " Idea | \n",
74 | " Additional MVP requirements post release 1.10 | \n",
75 | "
\n",
76 | " \n",
77 | " 3 | \n",
78 | " GCCG PHASE 1 | \n",
79 | " VXT-3690 | \n",
80 | " Epic | \n",
81 | " Idea | \n",
82 | " Distributed Patient Record - Phase 1 Improvements | \n",
83 | "
\n",
84 | " \n",
85 | " 4 | \n",
86 | " ITH POST GSM | \n",
87 | " VXT-3577 | \n",
88 | " Epic | \n",
89 | " Awaiting Prioritisation | \n",
90 | " Form Renderer Refactoring - Improvements | \n",
91 | "
\n",
92 | " \n",
93 | " 5 | \n",
94 | " GCCG PHASE 1 | \n",
95 | " VXT-3564 | \n",
96 | " Epic | \n",
97 | " Awaiting Prioritisation | \n",
98 | " Audit Reports - GCCG Phase 1 [NEW REPORTING] | \n",
99 | "
\n",
100 | " \n",
101 | " 6 | \n",
102 | " GCCG PHASE 1 | \n",
103 | " VXT-3546 | \n",
104 | " Epic | \n",
105 | " Idea | \n",
106 | " Integration engine improvements for GCCG Phase 1 | \n",
107 | "
\n",
108 | " \n",
109 | " 7 | \n",
110 | " GCCG PHASE 1 | \n",
111 | " VXT-3530 | \n",
112 | " Epic | \n",
113 | " Idea | \n",
114 | " Build connectors required for Post-MVP, Phase 1 | \n",
115 | "
\n",
116 | " \n",
117 | " 8 | \n",
118 | " GCCG PHASE 1 | \n",
119 | " VXT-3527 | \n",
120 | " Epic | \n",
121 | " Idea | \n",
122 | " User account enhancements - GCCG Phase 1 | \n",
123 | "
\n",
124 | " \n",
125 | " 9 | \n",
126 | " GCCG PHASE 1 | \n",
127 | " VXT-3524 | \n",
128 | " Epic | \n",
129 | " Idea | \n",
130 | " UX improvements - Post MVP, Phase 1 | \n",
131 | "
\n",
132 | " \n",
133 | " 10 | \n",
134 | " ePCR MVP | \n",
135 | " VXT-3507 | \n",
136 | " Epic | \n",
137 | " Awaiting Prioritisation | \n",
138 | " Custom Controls for ePCR MVP (SECAmb) | \n",
139 | "
\n",
140 | " \n",
141 | " 11 | \n",
142 | " ePCR MVP | \n",
143 | " VXT-3506 | \n",
144 | " Epic | \n",
145 | " Awaiting Prioritisation | \n",
146 | " Custom Controls for ePCR MVP (ePCR) | \n",
147 | "
\n",
148 | " \n",
149 | " 12 | \n",
150 | " ePCR MVP | \n",
151 | " VXT-3497 | \n",
152 | " Epic | \n",
153 | " Idea | \n",
154 | " Offline operation - gaps | \n",
155 | "
\n",
156 | " \n",
157 | " 13 | \n",
158 | " ePCR MVP | \n",
159 | " VXT-3485 | \n",
160 | " Epic | \n",
161 | " Awaiting Prioritisation | \n",
162 | " custom controls epcr MVP bugs | \n",
163 | "
\n",
164 | " \n",
165 | " 14 | \n",
166 | " ePCR MVP | \n",
167 | " VXT-3484 | \n",
168 | " Epic | \n",
169 | " Idea | \n",
170 | " iOS epcr MVP bugs | \n",
171 | "
\n",
172 | " \n",
173 | " 15 | \n",
174 | " ePCR MVP | \n",
175 | " VXT-3376 | \n",
176 | " Epic | \n",
177 | " Idea | \n",
178 | " ePCR template productisation | \n",
179 | "
\n",
180 | " \n",
181 | " 16 | \n",
182 | " ePCR MVP | \n",
183 | " VXT-3351 | \n",
184 | " Epic | \n",
185 | " Awaiting Prioritisation | \n",
186 | " Dual connectivity to UK pod - Internet/N3 | \n",
187 | "
\n",
188 | " \n",
189 | " 17 | \n",
190 | " ePCR MVP | \n",
191 | " VXT-3337 | \n",
192 | " Epic | \n",
193 | " Awaiting Prioritisation | \n",
194 | " Custom task for sending emails to the given lo... | \n",
195 | "
\n",
196 | " \n",
197 | " 18 | \n",
198 | " ePCR Phase 1 | \n",
199 | " VXT-3292 | \n",
200 | " Epic | \n",
201 | " Awaiting Prioritisation | \n",
202 | " Reporting for ePCR - Additional Reports | \n",
203 | "
\n",
204 | " \n",
205 | " 19 | \n",
206 | " ePCR MVP | \n",
207 | " VXT-3264 | \n",
208 | " Epic | \n",
209 | " Awaiting Prioritisation | \n",
210 | " Setup connectivity for integration points for ... | \n",
211 | "
\n",
212 | " \n",
213 | " 20 | \n",
214 | " ePCR MVP | \n",
215 | " VXT-3261 | \n",
216 | " Epic | \n",
217 | " Awaiting Prioritisation | \n",
218 | " Setting up new ePCR tenants | \n",
219 | "
\n",
220 | " \n",
221 | " 21 | \n",
222 | " ePCR MVP | \n",
223 | " VXT-3260 | \n",
224 | " Epic | \n",
225 | " Awaiting Prioritisation | \n",
226 | " New pod in Australia region | \n",
227 | "
\n",
228 | " \n",
229 | "
\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 | " epic | \n",
376 | " key | \n",
377 | " type | \n",
378 | " status | \n",
379 | " SP | \n",
380 | " summary | \n",
381 | " team | \n",
382 | " emptySP | \n",
383 | " notEmptySP | \n",
384 | "
\n",
385 | " \n",
386 | " \n",
387 | " \n",
388 | " 41 | \n",
389 | " VXT-3337 | \n",
390 | " VXT-3252 | \n",
391 | " Story | \n",
392 | " Tech Refinement | \n",
393 | " 3.0 | \n",
394 | " Task to get email from FHIR location and enabl... | \n",
395 | " Custom Controls | \n",
396 | " False | \n",
397 | " True | \n",
398 | "
\n",
399 | " \n",
400 | " 25 | \n",
401 | " VXT-3351 | \n",
402 | " VXT-3494 | \n",
403 | " Story | \n",
404 | " Awaiting Prioritisation | \n",
405 | " 3.0 | \n",
406 | " Provision dual connectivity to UK pod | \n",
407 | " Ops Team | \n",
408 | " False | \n",
409 | " True | \n",
410 | "
\n",
411 | " \n",
412 | " 35 | \n",
413 | " VXT-3351 | \n",
414 | " VXT-3356 | \n",
415 | " Story | \n",
416 | " Awaiting Prioritisation | \n",
417 | " 8.0 | \n",
418 | " Security testing dual connectivity to UK pod | \n",
419 | " Ops Team | \n",
420 | " False | \n",
421 | " True | \n",
422 | "
\n",
423 | " \n",
424 | " 36 | \n",
425 | " VXT-3351 | \n",
426 | " VXT-3354 | \n",
427 | " Story | \n",
428 | " Awaiting Prioritisation | \n",
429 | " NaN | \n",
430 | " Correct issues found due to dual connectivity ... | \n",
431 | " Platform Team | \n",
432 | " True | \n",
433 | " False | \n",
434 | "
\n",
435 | " \n",
436 | " 37 | \n",
437 | " VXT-3351 | \n",
438 | " VXT-3352 | \n",
439 | " Story | \n",
440 | " Awaiting Prioritisation | \n",
441 | " NaN | \n",
442 | " Test dual connectivity changes to UK pod | \n",
443 | " Platform Team | \n",
444 | " True | \n",
445 | " False | \n",
446 | "
\n",
447 | " \n",
448 | " 33 | \n",
449 | " VXT-3376 | \n",
450 | " VXT-3378 | \n",
451 | " Story | \n",
452 | " Tech Refinement | \n",
453 | " NaN | \n",
454 | " Design ePCR template | \n",
455 | " None | \n",
456 | " True | \n",
457 | " False | \n",
458 | "
\n",
459 | " \n",
460 | " 34 | \n",
461 | " VXT-3376 | \n",
462 | " VXT-3377 | \n",
463 | " Story | \n",
464 | " Tech Refinement | \n",
465 | " NaN | \n",
466 | " Create generic ePCR form wireframes | \n",
467 | " None | \n",
468 | " True | \n",
469 | " False | \n",
470 | "
\n",
471 | " \n",
472 | " 16 | \n",
473 | " VXT-3484 | \n",
474 | " VXT-3589 | \n",
475 | " Story | \n",
476 | " Idea | \n",
477 | " NaN | \n",
478 | " Move form redirect functionality into form ren... | \n",
479 | " Web Team | \n",
480 | " True | \n",
481 | " False | \n",
482 | "
\n",
483 | " \n",
484 | " 44 | \n",
485 | " VXT-3506 | \n",
486 | " VXT-3233 | \n",
487 | " Story | \n",
488 | " Awaiting Prioritisation | \n",
489 | " 30.0 | \n",
490 | " Placeholder for additional custom controls | \n",
491 | " None | \n",
492 | " False | \n",
493 | " True | \n",
494 | "
\n",
495 | " \n",
496 | " 46 | \n",
497 | " VXT-3506 | \n",
498 | " VXT-3205 | \n",
499 | " Story | \n",
500 | " Awaiting Prioritisation | \n",
501 | " 1.0 | \n",
502 | " Online awareness for postcode lookup / address... | \n",
503 | " None | \n",
504 | " False | \n",
505 | " True | \n",
506 | "
\n",
507 | " \n",
508 | " 47 | \n",
509 | " VXT-3506 | \n",
510 | " VXT-3164 | \n",
511 | " Story | \n",
512 | " PO Refinement | \n",
513 | " 5.0 | \n",
514 | " Address/Postcode lookup control | \n",
515 | " Custom Controls | \n",
516 | " False | \n",
517 | " True | \n",
518 | "
\n",
519 | " \n",
520 | " 15 | \n",
521 | " VXT-3524 | \n",
522 | " VXT-3608 | \n",
523 | " Story | \n",
524 | " Idea | \n",
525 | " NaN | \n",
526 | " Bad State Panel Colour | \n",
527 | " None | \n",
528 | " True | \n",
529 | " False | \n",
530 | "
\n",
531 | " \n",
532 | " 18 | \n",
533 | " VXT-3524 | \n",
534 | " VXT-3574 | \n",
535 | " Story | \n",
536 | " Idea | \n",
537 | " NaN | \n",
538 | " Standalone Input Mask | \n",
539 | " Web Team | \n",
540 | " True | \n",
541 | " False | \n",
542 | "
\n",
543 | " \n",
544 | " 38 | \n",
545 | " VXT-3527 | \n",
546 | " VXT-3325 | \n",
547 | " Story | \n",
548 | " Idea | \n",
549 | " NaN | \n",
550 | " Disable a users account after a period of inac... | \n",
551 | " Platform Team | \n",
552 | " True | \n",
553 | " False | \n",
554 | "
\n",
555 | " \n",
556 | " 39 | \n",
557 | " VXT-3546 | \n",
558 | " VXT-3307 | \n",
559 | " Story | \n",
560 | " Idea | \n",
561 | " 5.0 | \n",
562 | " Implement Scenario - Check Patient Medical Dat... | \n",
563 | " Integration Team | \n",
564 | " False | \n",
565 | " True | \n",
566 | "
\n",
567 | " \n",
568 | " 48 | \n",
569 | " VXT-3546 | \n",
570 | " VXT-3146 | \n",
571 | " Story | \n",
572 | " Idea | \n",
573 | " 4.0 | \n",
574 | " Updates of staging DB with automatic migrations | \n",
575 | " Integration Team | \n",
576 | " False | \n",
577 | " True | \n",
578 | "
\n",
579 | " \n",
580 | " 14 | \n",
581 | " VXT-3546 | \n",
582 | " VXT-3638 | \n",
583 | " Task | \n",
584 | " In Development | \n",
585 | " 2.0 | \n",
586 | " Change endpoints to point to internal domains ... | \n",
587 | " Integration Team | \n",
588 | " False | \n",
589 | " True | \n",
590 | "
\n",
591 | " \n",
592 | " 5 | \n",
593 | " VXT-3564 | \n",
594 | " VXT-3735 | \n",
595 | " Bug | \n",
596 | " Idea | \n",
597 | " NaN | \n",
598 | " Spine user details missing from gateway audit ... | \n",
599 | " Platform Team | \n",
600 | " False | \n",
601 | " True | \n",
602 | "
\n",
603 | " \n",
604 | " 10 | \n",
605 | " VXT-3564 | \n",
606 | " VXT-3705 | \n",
607 | " Bug | \n",
608 | " Idea | \n",
609 | " NaN | \n",
610 | " Spine user details missing from preferences au... | \n",
611 | " Platform Team | \n",
612 | " False | \n",
613 | " True | \n",
614 | "
\n",
615 | " \n",
616 | " 12 | \n",
617 | " VXT-3564 | \n",
618 | " VXT-3696 | \n",
619 | " Bug | \n",
620 | " Idea | \n",
621 | " NaN | \n",
622 | " Spine user details missing from login audit entry | \n",
623 | " Platform Team | \n",
624 | " False | \n",
625 | " True | \n",
626 | "
\n",
627 | " \n",
628 | " 3 | \n",
629 | " VXT-3564 | \n",
630 | " VXT-3819 | \n",
631 | " Story | \n",
632 | " Idea | \n",
633 | " NaN | \n",
634 | " Patient Access audit report: Allow user to def... | \n",
635 | " None | \n",
636 | " True | \n",
637 | " False | \n",
638 | "
\n",
639 | " \n",
640 | " 6 | \n",
641 | " VXT-3564 | \n",
642 | " VXT-3731 | \n",
643 | " Story | \n",
644 | " Idea | \n",
645 | " 1.0 | \n",
646 | " Audit Report UI - Display patient name and ide... | \n",
647 | " Web Team | \n",
648 | " False | \n",
649 | " True | \n",
650 | "
\n",
651 | " \n",
652 | " 11 | \n",
653 | " VXT-3564 | \n",
654 | " VXT-3700 | \n",
655 | " Story | \n",
656 | " Idea | \n",
657 | " NaN | \n",
658 | " Audit access to audit reports | \n",
659 | " Platform Team | \n",
660 | " True | \n",
661 | " False | \n",
662 | "
\n",
663 | " \n",
664 | " 7 | \n",
665 | " VXT-3690 | \n",
666 | " VXT-3726 | \n",
667 | " Story | \n",
668 | " Idea | \n",
669 | " NaN | \n",
670 | " Improvements to spine-sensitive patients handling | \n",
671 | " Integration Team | \n",
672 | " True | \n",
673 | " False | \n",
674 | "
\n",
675 | " \n",
676 | " 28 | \n",
677 | " VXT-3690 | \n",
678 | " VXT-3448 | \n",
679 | " Task | \n",
680 | " Backlog | \n",
681 | " NaN | \n",
682 | " Configurable set of search params | \n",
683 | " Integration Team | \n",
684 | " False | \n",
685 | " True | \n",
686 | "
\n",
687 | " \n",
688 | " 13 | \n",
689 | " VXT-3690 | \n",
690 | " VXT-3691 | \n",
691 | " Task | \n",
692 | " Idea | \n",
693 | " NaN | \n",
694 | " Asynchronous purge | \n",
695 | " Gdansk Team 1 | \n",
696 | " False | \n",
697 | " True | \n",
698 | "
\n",
699 | " \n",
700 | " 19 | \n",
701 | " VXT-3690 | \n",
702 | " VXT-3572 | \n",
703 | " Task | \n",
704 | " Idea | \n",
705 | " NaN | \n",
706 | " Make data extract validity configurable | \n",
707 | " Integration Team | \n",
708 | " False | \n",
709 | " True | \n",
710 | "
\n",
711 | " \n",
712 | " 4 | \n",
713 | " VXT-3773 | \n",
714 | " VXT-3774 | \n",
715 | " Story | \n",
716 | " Idea | \n",
717 | " NaN | \n",
718 | " Changes to JUYI branding | \n",
719 | " None | \n",
720 | " True | \n",
721 | " False | \n",
722 | "
\n",
723 | " \n",
724 | " 26 | \n",
725 | " VXT-3773 | \n",
726 | " VXT-3470 | \n",
727 | " Story | \n",
728 | " Idea | \n",
729 | " NaN | \n",
730 | " Enable sorting on six columns for which FHIR s... | \n",
731 | " None | \n",
732 | " True | \n",
733 | " False | \n",
734 | "
\n",
735 | " \n",
736 | " 40 | \n",
737 | " VXT-3952 | \n",
738 | " VXT-3281 | \n",
739 | " Bug | \n",
740 | " Backlog | \n",
741 | " NaN | \n",
742 | " Form properties trigger function wrongly inter... | \n",
743 | " Gdansk Team 1 | \n",
744 | " False | \n",
745 | " True | \n",
746 | "
\n",
747 | " \n",
748 | " 0 | \n",
749 | " VXT-3952 | \n",
750 | " VXT-4109 | \n",
751 | " Story | \n",
752 | " Backlog | \n",
753 | " 3.0 | \n",
754 | " Encounter Screen - Rework of search filter opt... | \n",
755 | " Gdansk Team 1 | \n",
756 | " False | \n",
757 | " True | \n",
758 | "
\n",
759 | " \n",
760 | " 1 | \n",
761 | " VXT-3952 | \n",
762 | " VXT-4100 | \n",
763 | " Story | \n",
764 | " Backlog | \n",
765 | " 3.0 | \n",
766 | " Encounter Screen - Rework of default form task... | \n",
767 | " Gdansk Team 1 | \n",
768 | " False | \n",
769 | " True | \n",
770 | "
\n",
771 | " \n",
772 | " 17 | \n",
773 | " VXT-3952 | \n",
774 | " VXT-3587 | \n",
775 | " Story | \n",
776 | " Backlog | \n",
777 | " NaN | \n",
778 | " Task listing endpoints expose sensitive clinic... | \n",
779 | " Gdansk Team 1 | \n",
780 | " True | \n",
781 | " False | \n",
782 | "
\n",
783 | " \n",
784 | " 42 | \n",
785 | " VXT-3952 | \n",
786 | " VXT-3245 | \n",
787 | " Story | \n",
788 | " Tech Refinement | \n",
789 | " 6.5 | \n",
790 | " New patient manager endpoint | \n",
791 | " Gdansk Team 1 | \n",
792 | " False | \n",
793 | " True | \n",
794 | "
\n",
795 | " \n",
796 | "
\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 | " epic | \n",
976 | " notEmptySP | \n",
977 | " emptySP | \n",
978 | " SP | \n",
979 | " estimatedPerc | \n",
980 | "
\n",
981 | " \n",
982 | " \n",
983 | " \n",
984 | " 0 | \n",
985 | " VXT-3337 | \n",
986 | " 1.0 | \n",
987 | " 0.0 | \n",
988 | " 3.0 | \n",
989 | " 100.0 | \n",
990 | "
\n",
991 | " \n",
992 | " 1 | \n",
993 | " VXT-3351 | \n",
994 | " 2.0 | \n",
995 | " 2.0 | \n",
996 | " 11.0 | \n",
997 | " 50.0 | \n",
998 | "
\n",
999 | " \n",
1000 | " 2 | \n",
1001 | " VXT-3376 | \n",
1002 | " 0.0 | \n",
1003 | " 2.0 | \n",
1004 | " 0.0 | \n",
1005 | " 0.0 | \n",
1006 | "
\n",
1007 | " \n",
1008 | " 3 | \n",
1009 | " VXT-3484 | \n",
1010 | " 0.0 | \n",
1011 | " 1.0 | \n",
1012 | " 0.0 | \n",
1013 | " 0.0 | \n",
1014 | "
\n",
1015 | " \n",
1016 | " 4 | \n",
1017 | " VXT-3506 | \n",
1018 | " 3.0 | \n",
1019 | " 0.0 | \n",
1020 | " 36.0 | \n",
1021 | " 100.0 | \n",
1022 | "
\n",
1023 | " \n",
1024 | " 5 | \n",
1025 | " VXT-3524 | \n",
1026 | " 0.0 | \n",
1027 | " 2.0 | \n",
1028 | " 0.0 | \n",
1029 | " 0.0 | \n",
1030 | "
\n",
1031 | " \n",
1032 | " 6 | \n",
1033 | " VXT-3527 | \n",
1034 | " 0.0 | \n",
1035 | " 1.0 | \n",
1036 | " 0.0 | \n",
1037 | " 0.0 | \n",
1038 | "
\n",
1039 | " \n",
1040 | " 7 | \n",
1041 | " VXT-3546 | \n",
1042 | " 3.0 | \n",
1043 | " 0.0 | \n",
1044 | " 11.0 | \n",
1045 | " 100.0 | \n",
1046 | "
\n",
1047 | " \n",
1048 | " 8 | \n",
1049 | " VXT-3564 | \n",
1050 | " 4.0 | \n",
1051 | " 2.0 | \n",
1052 | " 1.0 | \n",
1053 | " 67.0 | \n",
1054 | "
\n",
1055 | " \n",
1056 | " 9 | \n",
1057 | " VXT-3690 | \n",
1058 | " 3.0 | \n",
1059 | " 1.0 | \n",
1060 | " 0.0 | \n",
1061 | " 75.0 | \n",
1062 | "
\n",
1063 | " \n",
1064 | " 10 | \n",
1065 | " VXT-3773 | \n",
1066 | " 0.0 | \n",
1067 | " 2.0 | \n",
1068 | " 0.0 | \n",
1069 | " 0.0 | \n",
1070 | "
\n",
1071 | " \n",
1072 | " 11 | \n",
1073 | " VXT-3952 | \n",
1074 | " 4.0 | \n",
1075 | " 1.0 | \n",
1076 | " 12.5 | \n",
1077 | " 80.0 | \n",
1078 | "
\n",
1079 | " \n",
1080 | "
\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 | " epic | \n",
1206 | " summary | \n",
1207 | " SP | \n",
1208 | " estimatedPerc | \n",
1209 | " minCost | \n",
1210 | " avgCost | \n",
1211 | " maxCost | \n",
1212 | "
\n",
1213 | " \n",
1214 | " \n",
1215 | " \n",
1216 | " 0 | \n",
1217 | " VXT-3337 | \n",
1218 | " Custom task for sending emails to the given lo... | \n",
1219 | " 3.0 | \n",
1220 | " 100.0 | \n",
1221 | " 1200 | \n",
1222 | " 1600 | \n",
1223 | " 2400 | \n",
1224 | "
\n",
1225 | " \n",
1226 | " 1 | \n",
1227 | " VXT-3351 | \n",
1228 | " Dual connectivity to UK pod - Internet/N3 | \n",
1229 | " 11.0 | \n",
1230 | " 50.0 | \n",
1231 | " Data not sufficient to estimate | \n",
1232 | " Data not sufficient to estimate | \n",
1233 | " Data not sufficient to estimate | \n",
1234 | "
\n",
1235 | " \n",
1236 | " 2 | \n",
1237 | " VXT-3376 | \n",
1238 | " ePCR template productisation | \n",
1239 | " 0.0 | \n",
1240 | " 0.0 | \n",
1241 | " Data not sufficient to estimate | \n",
1242 | " Data not sufficient to estimate | \n",
1243 | " Data not sufficient to estimate | \n",
1244 | "
\n",
1245 | " \n",
1246 | " 3 | \n",
1247 | " VXT-3484 | \n",
1248 | " iOS epcr MVP bugs | \n",
1249 | " 0.0 | \n",
1250 | " 0.0 | \n",
1251 | " Data not sufficient to estimate | \n",
1252 | " Data not sufficient to estimate | \n",
1253 | " Data not sufficient to estimate | \n",
1254 | "
\n",
1255 | " \n",
1256 | " 4 | \n",
1257 | " VXT-3506 | \n",
1258 | " Custom Controls for ePCR MVP (ePCR) | \n",
1259 | " 36.0 | \n",
1260 | " 100.0 | \n",
1261 | " 14400 | \n",
1262 | " 19200 | \n",
1263 | " 28800 | \n",
1264 | "
\n",
1265 | " \n",
1266 | " 5 | \n",
1267 | " VXT-3524 | \n",
1268 | " UX improvements - Post MVP, Phase 1 | \n",
1269 | " 0.0 | \n",
1270 | " 0.0 | \n",
1271 | " Data not sufficient to estimate | \n",
1272 | " Data not sufficient to estimate | \n",
1273 | " Data not sufficient to estimate | \n",
1274 | "
\n",
1275 | " \n",
1276 | " 6 | \n",
1277 | " VXT-3527 | \n",
1278 | " User account enhancements - GCCG Phase 1 | \n",
1279 | " 0.0 | \n",
1280 | " 0.0 | \n",
1281 | " Data not sufficient to estimate | \n",
1282 | " Data not sufficient to estimate | \n",
1283 | " Data not sufficient to estimate | \n",
1284 | "
\n",
1285 | " \n",
1286 | " 7 | \n",
1287 | " VXT-3546 | \n",
1288 | " Integration engine improvements for GCCG Phase 1 | \n",
1289 | " 11.0 | \n",
1290 | " 100.0 | \n",
1291 | " 4400 | \n",
1292 | " 5867 | \n",
1293 | " 8800 | \n",
1294 | "
\n",
1295 | " \n",
1296 | " 8 | \n",
1297 | " VXT-3564 | \n",
1298 | " Audit Reports - GCCG Phase 1 [NEW REPORTING] | \n",
1299 | " 1.0 | \n",
1300 | " 67.0 | \n",
1301 | " Data not sufficient to estimate | \n",
1302 | " Data not sufficient to estimate | \n",
1303 | " Data not sufficient to estimate | \n",
1304 | "
\n",
1305 | " \n",
1306 | " 9 | \n",
1307 | " VXT-3690 | \n",
1308 | " Distributed Patient Record - Phase 1 Improvements | \n",
1309 | " 0.0 | \n",
1310 | " 75.0 | \n",
1311 | " Data not sufficient to estimate | \n",
1312 | " Data not sufficient to estimate | \n",
1313 | " Data not sufficient to estimate | \n",
1314 | "
\n",
1315 | " \n",
1316 | " 10 | \n",
1317 | " VXT-3773 | \n",
1318 | " Additional MVP requirements post release 1.10 | \n",
1319 | " 0.0 | \n",
1320 | " 0.0 | \n",
1321 | " Data not sufficient to estimate | \n",
1322 | " Data not sufficient to estimate | \n",
1323 | " Data not sufficient to estimate | \n",
1324 | "
\n",
1325 | " \n",
1326 | " 11 | \n",
1327 | " VXT-3952 | \n",
1328 | " Patient Manager Performance Improvements | \n",
1329 | " 12.5 | \n",
1330 | " 80.0 | \n",
1331 | " Data not sufficient to estimate | \n",
1332 | " Data not sufficient to estimate | \n",
1333 | " Data not sufficient to estimate | \n",
1334 | "
\n",
1335 | " \n",
1336 | "
\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 | 
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": "iVBORw0KGgoAAAANSUhEUgAAAX0AAAEICAYAAACzliQjAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzt3Xd8FXX2//HXSUhAegtC6B2iImgoi4BIDSBFEYyuq7j2svpzd11RF0Qsa1tFv1/9iq7oyuoi4KqoSWhiAaQEpQZQOkFKgFBCIPX8/pgJe4kJuYEkc5N7no/HfWTulDvvO7n33CmfmRFVxRhjTHAI8TqAMcaYsmNF3xhjgogVfWOMCSJW9I0xJohY0TfGmCBiRd8YY4KIFf1iEJENItK3kGF9RSS5jCMVSES+FpHb3e5xIrLY60y+ROS3IjLP6xwFEREVkTZe5zDnzv6HZ1chi76I7BCRkyKSJiL7ROQ9Eal+vq+rqhep6tclENFT4tgmIkmlOI8W7pcvzeexBkBVP1DVQT7jBuSX1P3cPO11jtIkIg+535FjIjJNRCoXMl64iMx2v1uaf+VHRCqLyJsisl9EDovI5yLSuBTyevJZEZH7RSRRRDJE5L0ixr1YROaKyEER+dWJUO53I05EUt1l/78iUqnUwudTIYu+a7iqVgc6A12ARz3OUyJK6MPRB2gAtBKRriXwemdTW1Wru49LS+IFy/ILUpGJyGBgPNAfaA60Ap48yySLgZuAfQUMexD4DdAJiARSgf8pybwe+wV4Gpjmx7hZwEzgtkKGvwEcABrh1KcrgXtLIKNfKnLRB0BV9wFzcRYucHqt5CUR2eWumbwpIhe4w+qLyBcicsRdY/lORELcYTtEZIDbfYG7JpjqrjGfUTzzr5HkX2sUkatFZLU7n6Ui0qmw9+C+1n0i8jPws9uvp4isFJGj7t+exVgstwCfAXFud958rheRxHzzfkhE5rjd9dw1uGPuPJ8+l11HvrucRORbt/cad2vg+kLGXyIir4jIIWCS2//3IrLR/R/MFZHmbn9xxz3gZl0nIhe7w07v+sqfJd887wR+C/zFzfW52/8REdkjIsdFZLOI9PfzPb8nIm+ISLz7ektEpKGITHHzbxKRLj7jn/XzU0JuAd5R1Q2qmgo8BYwraERVzVTVKaq6GMgpYJSWwFxV3a+qp4CPgIvOJZSItBGRb9zP9kER+cjtX+BnRUQeFpG9IvKLiPz+XOZZFFX9j6p+ChzyY9zNqvoOsKGQUVoCM1X1lFufEjjHZXUuKnzRF5EmwBBgi0/v54B2OD8EbYDGwER32J+AZCACuBB4DCjoWhVPAK3dx2B8iqcfmbrgrDHcBdQDpgJzpJBNa9cooDsQJSJ1gS+B19zpXwa+FJF6fsy7KnAd8IH7iBWRcHfw50B7EWnrM8mNwIdu9+vACaAhzvv1+z0XRlX7uJ2XulsDHxUyandgG87/5BkRGYnzv7kW53/1HfBvd9xBOFsz7YBawFj8+LLmy/UWzvJ5wc01XETaA/cDXVW1Bs7/fQeAiPQSkSNFvOxY4K9AfSAD+B74wX0+G+f/WGx58z7Lo1chk14ErPF5vga40J/PUQHeAa4QkUj3M/ZbIP4cXgecH595QB2gCe4WQ0GfFRGJAf4MDATaAgPO9sLuD29hy2ntOeYtrik437uq4uwCG4JT+MtERS76n4rIcWA3zqbUE+CsBQJ3Ag+p6mFVPQ48C8S602XhbHY1V9UsVf1OC75A0VjgGfc1duMUYH/dCUxV1eWqmqOq/8QpAj3OMs3f3HmdBIYBP6vqdFXNVtV/A5uA4X7M+1p3XvNwfjjC3NdDVdNxtgBuAHCLfwecH6RQYDTwhKqmq2oS8E8/5nfQ50v1Zz/GL8wvqvo/7vs9CdyNs0w2qmo2zv+ws7u2nwXUcLOLO87e85h3nhygMs4Pb5iq7lDVrQCqulhVaxcx/SequspdE/4EOKWq76tqDs6acZezT16wvHmf5VHY1lh14KjP87zuGucQ42ec79oe4BjQEZh8Dq8Dzv+vORDprg2fbWtyLPCuqq5X1RO4W4GFUdV7z7KcCt3aLmHf4vzgHsNZwUwEPi2jeVfooj/KXRvri/Plr+/2jwCqAqvyihHOr2yEO/xFnK2CeeIc7BxfyOtH4nzI8+wsRrbmwJ981zKApu5rFsZ3XpEFzG8nzhZLUW7B2bTMdovPx5y5xv4hbtHHWcv/1P0xiAAq5cvh212Y+j5fqpf8GL8w+efVHHjVZ/kdBgRorKpfAf+Ls2VyQETeEpGa5zFvAFR1C/D/cArLARGZISJn+5/lt9+n+2QBz8+7sUExpQG+yyWv+/g5vNbrOD+I9YBqwH849zX9v+D8L1eI02LubLtszud7WObE2VWcgLN8quHUpTrA82WVoSIXfQBU9RvgPSCv4BzE+YJd5FOMarkHfVHV46r6J1VtBYwA/ljIftu9OIU6T7N8w9NxflzyNPTp3o2zleC7llHVXWMv9K34dP+CU/R8NcNZyyqUu6urH3CTOK0G9uHs6hkqInk/ivOBCBHpjFP883btpADZOJvbeXzff2nLv7W1G7gr3zK8QFWXAqjqa6p6ORCFs5vnYXe6ExT+fylqnqjqh6raC2f5K6X3ZT3b5+cMItJbzmwllf/Ru5BJNwC+B9cvBfararF2hbk6A++5W6MZOLtkuvl8rvymqvtU9Q5VjcTZBfqGFN5ip6jv4RnEOX5X2HIqbB98SarrZvxfVc1wl/W7wNAymDcQBEXfNQUYKCKXqmou8Dbwiog0ABCRxuK0ZMg7wNrG3Q10FGeTPreA15wJPCoiddxi+od8w1cDN4pIqLvf8UqfYW8Dd4tId3FUE5FhIuLvZnUc0E5EbhSRSu4BrSjgiyKm+x3wE9Ae50vaGacgJuOu3atqFjALZ4unLs6PAO4uiP8Ak9x9kR2Am/3MW5T9OC1HiuNNnOV/EYCI1BKRMW53V3fZhuEU+VP893+4GrjWfQ9tKLyFxa9yiUh7EennHns5hbPyUNBnoySc7fNzBncXZPWzPL4rZNL3gdtEJEpEauMcb3ivsPmI0wCiivs0XESquN8TgJXAze7/IQynNcovqnrQnfY9KaKpo898xrjfKXBaASn/Xc75PyszgXHue6iKuxu3MKp691mWU6EHU93vWRUgFAh133uBrcjc73QVINx9XsX9zOAuj+3APe5r1sbZ0i6r4wnBUfRVNQXnA553sPYRnF04y0TkGLAApxCCczBoAc6m7/fAG6q6qICXfRJnU3I7zv7x6fmGP4izj/0IzkGt0/vsVDURuANnF0Sqm2VcMd7PIeBqnIPOh3A2h6/O+4KdxS3u+9nn+8ApoPl38QwAZrn7y/Pcj3NgdJ/7fv+Nc3zgfE0C/unuqhnrzwSq+gnOWvYM93+4HueAGDi7Kd7GWbY7cZbRi+6wV4BMnOLxT5yDtYV5B2f//RER+RRn98VzOFuL+3CavT4K/13b9uvd+qfQz09JUdUE4AVgEbALZ1mdLprurpXf+kyyGeeHrjFOi7iT/HeL8884P4Q/42wVDgWu8Zm2KbDEz2hdgeXu8pwDPKiq29xhk/D5rKhqPM5K3Vc436Ov/JxHcf0V5/2Ox2m2etLth4g0c7cU8rYymrvD87YcTuIsuzzXAjE4y2kLzjGMh0op969IwccojSmaiDwPNFTV827FYyoucVqHrQE6uVuSxkNBsaZvSoaIdBCRTu7mazecXSOfeJ3LBDZ12vh3tIIfGOzMRlMcNXB26UTi7B75O04TT2NMOWG7d4wxJojY7h1jjAkiAbd7p379+tqiRQuvYxhjTLmyatWqg6oaUdR4AVf0W7RoQWJiYtEjGmOMOU1E/Dob2XbvGGNMELGib4wxQcSKvjHGBBEr+sYYE0Ss6BtjTBCxom+MMUHEir4xxgQRK/rGGBNErOgbY0wQCbgzco0pjrs+v8vrCKVq6vCpXkcwFYxfa/oiEiMim0VkixR+o3BEZLSIqIhE+/R71J1uc94tCY0xxnijyDV9EQnFudP9QJx7qa4UkTmqmpRvvBo4t3hb7tMvCogFLsK5BvsCEWnn3m/VGGNMGfNnTb8bsEVVt6lqJjADGFnAeE/h3LP0lE+/kcAM967v23HuB9ntPDMbY4w5R/4U/cbAbp/nyW6/00TkMqCpqn5Z3Gnd6e8UkUQRSUxJSfEruDHGmOI779Y7IhICvAz86VxfQ1XfUtVoVY2OiCjyctDGGGPOkT+td/YATX2eN3H75akBXAx8LSIADYE5IjLCj2mNMcaUIX/W9FcCbUWkpYiE4xyYnZM3UFWPqmp9VW2hqi2AZcAIVU10x4sVkcoi0hJoC6wo8XdhjDHGL0Wu6atqtojcD8wFQoFpqrpBRCYDiao65yzTbhCRmUASkA3cZy13jDHGO36dnKWqcUBcvn4TCxm3b77nzwDPnGM+Y4wxJcguw2CMMUHEir4xxgQRK/rGGBNErOgbY0wQsaJvjDFBxIq+McYEESv6xhgTRKzoG2NMELGib4wxQcSKvjHGBBEr+sYYE0Ss6BtjTBCxom+MMUHEir4xxgQRK/rGGBNErOgbY0wQ8avoi0iMiGwWkS0iMr6A4XeLyDoRWS0ii0Ukyu3fQkROuv1Xi8ibJf0GjDHG+K/IO2eJSCjwOjAQSAZWisgcVU3yGe1DVX3THX8E8DIQ4w7bqqqdSza2McaYc+HPmn43YIuqblPVTGAGMNJ3BFU95vO0GqAlF9EYY0xJ8afoNwZ2+zxPdvudQUTuE5GtwAvAAz6DWorIjyLyjYj0LmgGInKniCSKSGJKSkox4htjjCmOEjuQq6qvq2pr4BHgr27vvUAzVe0C/BH4UERqFjDtW6oararRERERJRXJGGNMPv4U/T1AU5/nTdx+hZkBjAJQ1QxVPeR2rwK2Au3OLaoxxpjz5U/RXwm0FZGWIhIOxAJzfEcQkbY+T4cBP7v9I9wDwYhIK6AtsK0kghtjjCm+IlvvqGq2iNwPzAVCgWmqukFEJgOJqjoHuF9EBgBZQCpwizt5H2CyiGQBucDdqnq4NN6IMcaYohVZ9AFUNQ6Iy9dvok/3g4VM9zHw8fkENMYr2RnZpKemk56azqljp8jJyiEnM4fc7FwqVa5EaHgoYVXCqFqnKlXrVqVyjcqIiNexjTkrv4q+MRWZqnJ0z1H2bdzHwa0HOZJ8hNTdqZxMPVms1wkND6V249rUaVaHOs3q0KBdAxq0b0BYlbBSSm5M8VnRN0HpxKET7F61m12Ju9i7YS8ZxzN+NU5IpRBnLb5OVarUrEJoeCih4aGEhIaQk5lDdkY2WSezSD+SzolDJ8hKz+LQ9kMc2n7o9GtIiFC/dX0ad25M867NiWgbQUioXf3EeMeKvgkaJw6dYOvirWz9ZispW848H6Rq3ao07NiQiHYR1Glah9pNalOjQQ0kxP/dNZknMkndnUrqrlQO7TjE/k37ObTtECk/p5DycwqrZ62mSq0qtPxNS9r2bcuFHS+03UGmzFnRNxVabk4uu1buIik+iT1r9qC5zsnilSpXIvLSSJpFN6NJ5ybUuLDGeRfg8GrhXNjhQi7scOHpfpnpmezftJ9dibvYtWIXxw8cZ2PCRjYmbKTGhTVo3789HQZ3oGqdquc1b2P8ZUXfVEgZaRlsTNhIUnwSaSlpgLO7pnm35rS5sg3NoptRqXLpf/zDq4bT9LKmNL2sKT3v6MnhHYfZ8s0WtnyzheP7j5P4YSI/zPyBVle04pIRlxDR1k5ONKXLir6pUE4ePcm6OevY8OUGstKzAKjZqCZRQ6Jo178dVWpU8SybiFCvZT3qtaxH19915Ze1v7AhbgM7V+w8/UPQpEsTLrv+MhpGNfQsp6nYrOibCiHzRCZrPlnDus/WkZ2RDUDkJZF0urYTTbs0Lda++bIQEhpCky5NaNKlCcf3H2fDlxtISkgi+cdkkn9MJvKSSLqN6+Z1TFMBiWpgXRAzOjpaExMTvY5hyonb/3M7SXFJ/DDzh9MtcJpFN6PL2C5n7FsvD04dO8X6z9ez/ov1ZJ7IBGDs2LE8++yztG7d2uN0JtCJyCpVjS5yPCv6pryaP38+Y38/liPJRwBoGNWQ7uO6l7tin19GWgarZ69m/efrycnKITw8nIcffpjHHnuMqlXtgK8pmBV9U2Ht2bOHBx98kI8/dk72rhVZix6/70Gzrs0qVBPItJQ0Qr8OZfr06QA0b96cV199lZEjRxYxpQlG/hZ9O0vElBuqyttvv01UVBQff/wxVatWpdvN3bjuf66jebfmFargA1SPqM7777/PkiVL6Ny5Mzt37mTUqFGMHTuW/fv3ex3PlFNW9E25sGPHDvr378+dd97JsWPHGD58OJs2baLzdZ0JDQv1Ol6p6tmzJytXruS1116jWrVqzJo1i6ioKD744AMCbUvdBD4r+iagqSrTp0+nU6dOLFq0iIiICGbMmMFnn31G06ZNi36BCqJSpUr84Q9/YP369QwcOJDDhw9z0003ERsbS2pqqtfxTDliRd8ErNTUVGJjY7n55ps5fvw41157LUlJSVx//fUVbleOv1q0aMHcuXN5++23qV69OjNnzqRTp058/fXXXkcz5YQVfROQVqxYQZcuXZg5cybVq1dn2rRpzJ49m/r163sdzXMiwu23387q1avp3r07ycnJ9OvXj0mTJpGTk+N1PBPgrOibgKKqTJkyhV69erFz5066du3KmjVruPXWW4N27b4wrVu35rvvvmPChAkAPPnkk8TExHDgwAGPk5lAZkXfBIy0tDSuv/56HnroIbKysnjggQdYvHgxrVq18jpawAoLC2Py5MnMmzePiIgIFixYQOfOnVm6dKnX0UyA8qvoi0iMiGwWkS0iMr6A4XeLyDoRWS0ii0UkymfYo+50m0VkcEmGNxXHtm3b6NmzJ7NmzaJGjRrMnj2bV199lfDwcK+jlQsDBgxg9erV9O7dm71799K3b1/+8Y9/eB3LBKAii757Y/PXgSFAFHCDb1F3faiql6hqZ+AF4GV32iicG6lfBMQAb+TdKN2YPAsWLCA6Opp169bRvn17VqxYwejRo72OVe5ERkaycOFCHnjgAbKysrjjjju47777yMrK8jqaCSD+rOl3A7ao6jZVzQRmAGecEqiqx3yeVgPyGg+PBGaoaoaqbge2uK9nDABvvvkmMTExpKamMnz4cJYvX06HDh28jlVuhYWF8eqrrzJt2jTCw8N54403Ti9fY8C/ot8Y2O3zPNntdwYRuU9EtuKs6T9QzGnvFJFEEUlMSUnJP9hUQDk5OTz00EPcc8895OTk8Oijj/Lpp59Sq1Ytr6NVCLfeeivffvstF154IV999RU9e/Zk69atXscyAaDEDuSq6uuq2hp4BPhrMad9S1WjVTU6IsJuIlHRpaenc+211zJlyhTCwsJ49913efbZZwkJsXYFJal79+6sWLGCSy65hE2bNtGjRw++//57r2MZj/nzLdsD+J762MTtV5gZwKhznNZUcAcPHqR///7MmTOHOnXqMH/+fMaNG+d1rAqrWbNmLF68mCFDhnDw4EH69evHnDlzvI5lPORP0V8JtBWRliISjnNg9oxPjYi09Xk6DPjZ7Z4DxIpIZRFpCbQFVpx/bFMe5bXQWbZsGc2aNWPJkiVceeWVXseq8GrWrMmcOXO44447OHXqFNdccw1vvfWW17GMR4q8c5aqZovI/cBcIBSYpqobRGQykKiqc4D7RWQAkAWkAre4024QkZlAEpAN3KeqdspgEFq7di2DBw9m3759XHrppcTFxREZGel1rKBRqVIlpk6dSuPGjZk0aRJ33XUX+/btY8KECXbSW5Dx63aJqhoHxOXrN9Gn+8GzTPsM8My5BjTl39KlSxk2bBhHjhzhqquu4tNPP6VmzZpexwo6IsITTzxBZGQkd999N0888QSHDx/m5ZdftuMpQcT+06ZUzZs3j4EDB3LkyBFGjRpFXFycFXyP3XHHHcycOfN0887bbruN7Oxsr2OZMmJF35Sazz77jOHDh5Oens64ceOYNWsWVapU8TqWAUaPHs0XX3xB1apVee+994iNjSUzM9PrWKYMWNE3pWLWrFlcd911ZGZm8sADD/DOO+9QqZJfexNNGRk0aBDz58+nVq1afPzxx4wePZpTp055HcuUMiv6psRNnz6d2NhYsrOzGT9+PFOmTLF9xgGqZ8+eLFy4kLp16/LFF18wcuRI0tPTvY5lSpF9E02Jev/997nlllvIzc3lySef5Nlnn7XWIQHu8ssvP31Xsnnz5nH11Vdb4a/ArOibEvP+++8zbtw4VJVnnnmGiRMnWsEvJzp16sQ333xDw4YNWbRo0eljMabisaJvSkT+gv/YY495HckUU8eOHVm0aNHp6/WMGDHCCn8FZEXfnLcPP/zQCn4F0aFDh9OFf+HChYwaNcoO7lYw1pzCddfnd3kdodRMHT611F579uzZ3HzzzagqTz/9tBX8EubV57LPhD588fgXzJ8/n/a92zPw0YGEhtmtMPxVmt+582Vr+uaczZkzhxtuuIGcnBwmTJjA448/7nUkU0LqNKvDsKeGUblGZXYl7mLhSwvJzcn1OpYpAVb0zTmZP38+Y8aMITs7m4cffpgnn3zS60imhNVtUZdhk4cRXi2cHd/vYNEri6zwVwBW9E2xLVmyhFGjRpGZmckf/vAHnn/+eWulU0HVb12foZOGEnZBGFu/3criNxejqkVPaAKWFX1TLD/88ANDhw4lPT2dW2+9lSlTpljBr+AatG/A4AmDCQ0PZdPcTSx/b7kV/nLMir7x26ZNmxg8eDDHjh1jzJgxvP3223ambZCIvDiSgeMHIqHC2k/W8uOsH72OZM6RfWONX3bt2sXAgQM5ePAgQ4YM4V//+hehodaaI5g0i25Gvz/1Q0KExH8lkhSX5HUkcw6s6JsipaSkMGjQIJKTk7niiiuYPXs24eHhXscyHmjdqzW97ukFwOKpi9n6nd1svbzxq+iLSIyIbBaRLSIyvoDhfxSRJBFZKyILRaS5z7AcEVntPuzmnOXM8ePHGTJkCJs3b6ZTp06nL8drglfHwR3pdnM3UFj0yiJ2/7Db60imGIos+iISCrwODAGigBtEJCrfaD8C0araCZgNvOAz7KSqdnYfI0ootykDGRkZXHPNNaxatYpWrVqRkJBA7dq1vY5lAsCloy+l06hO5GbnMv9v8znw0wGvIxk/+bOm3w3YoqrbVDUTmAGM9B1BVRepat5FOpYBTUo2pilrubm53HLLLSxcuJAGDRowb948GjVq5HUsEyBEhO63dqddv3ZkZ2STMDmBI8lHvI5l/OBP0W8M+G6/Jbv9CnMbEO/zvIqIJIrIMhEZVdAEInKnO05iSkqKH5FMaVJVHnroIT766CNq1KhBQkICrVu39jqWCTAiQp/7+9A0uimnjp0iblIcJw6d8DqWKUKJHsgVkZuAaOBFn97NVTUauBGYIiK/qh6q+paqRqtqdERERElGMufghRde4LXXXiM8PJxPP/2ULl26eB3JBKiQSiEM+MsAGrRrQNqBNOInxZN5wm67GMj8Kfp7gKY+z5u4/c4gIgOAx4ERqpqR119V97h/twFfA1ZBAtj06dMZP348IsL06dPp16+f15FMgAurEkbMxBhqN6nN4Z2HmffsPHKycryOZQrhT9FfCbQVkZYiEg7EAme0whGRLsBUnIJ/wKd/HRGp7HbXB64ArHFvgJo3bx6///3vAXjllVcYO3asx4lMeVGlZhWGTBpC1bpV+WXdL3w95Ws0187aDURFFn1VzQbuB+YCG4GZqrpBRCaLSF5rnBeB6sCsfE0zOwKJIrIGWAQ8p6pW9APQjz/+yOjRo8nOzubPf/4zDz74oNeRTDlTo0ENYibGONfp+W4ry95d5nUkUwC/rqevqnFAXL5+E326BxQy3VLgkvMJaErfzp07GTp0KGlpadxwww08//zzXkcy5VT9VvUZ+OhA4p+MZ91n66geUZ1LRlgJCCR2Rm6QS01NZciQIezbt4++ffvy7rvv2vV0zHlp0rkJfR/oC8D373zPtqXbvA1kzmDf7iCWkZHBqFGj2LhxIxdddBGffPIJlStX9jqWqQDaXtWWrjd1dc7afXkR+zbu8zqScVnRD1K5ubmMGzeOb7/9lsjISOLj4+1sW1OiOo/pTIfBHcjJzGHu03M5+stRryMZrOgHrccff5wZM2ZQvXp1vvzyS5o2bVr0RMYUg4jQ6+5eNItuRsbxDOInxXPy6EmvYwU9K/pB6K233uK5554jNDSU2bNn07lzZ68jmQoqJDSE/g/3p37r+hzbd4y5T88lOyPb61hBzYp+kElISODee+8FYOrUqQwePNjjRKaiC7sgjJgJMVSPqM6BzQdY9Moia8PvISv6QWTNmjWMGTOGnJwcHn/8cW677TavI5kgUbVuVWKeiCGsahjbl25n+T+Xex0paFnRDxLJyckMGzaMtLQ0brzxRp566imvI5kgU7dZXQY9Ouj0LReT4u08TS9Y0Q8Cx48f5+qrr2bPnj307t2badOm2c3MjScaX9qYPvf3AWDJ1CXsWrXL40TBx4p+BZebk0tsbCxr1qyhbdu21hbfeK59//Z0GdsFzVUWvrCQQ9sPeR0pqFjRr8BUlaVvLyUuLo569eqd/muM16J/G02bK9uQdTKLhMkJdh3+MmRFvwJbN2cdSXFJVK5cmc8++4w2bdp4HckYwGnDf+UDV9IwqiEnDp0gYXICWSezvI4VFKzoV1A7lu1g2TTnKofvvfceV1xxhceJjDlTaFgogx4bRK3IWhzafoiFLy4kNyfX61gVnhX9Cijl5xQWvrQQFKJviiY2NtbrSMYUqErNKsRMjKFyjcrsStzF9//43utIFZ4V/QomLSWNhKcTyMnMoV3/dnQZYzcqM4GtVmQtBj02iJBKIWz4cgPrP1/vdaQKzYp+BZKZnkn85HhOpp4k8pJIet/b25pmmnKh0UWNuPKBKwHncsw7V+z0OFHF5VfRF5EYEdksIltEZHwBw/8oIkkislZEFopIc59ht4jIz+7jlpIMb/4rNyeXBc8vIHVnKrWb1GbgowMJDQv1OpYxfmvQYoMvAAAX60lEQVTbty2X33i505TzpYUc3HrQ60gVUpFFX0RCgdeBIUAUcIOIROUb7UcgWlU7AbOBF9xp6wJPAN2BbsATIlKn5OIbcJpmLpm6hOQfk6lSy91HWt3a4pvy57LrL6PtVW3JPpVNwlMJpB1M8zpShePPmn43YIuqblPVTGAGMNJ3BFVdpKrp7tNlQBO3ezAwX1UPq2oqMB+IKZnoJs+6z9axMWEjoWGhDH58MDUb1vQ6kjHnREToc38fGl3UiPTD6cx9ai6Z6Zlex6pQ/Cn6jYHdPs+T3X6FuQ2IL860InKniCSKSGJKSoofkUyeHct2nL4Bdd+H+nJhhws9TmTM+QkNC2XgowP/25TzJWvKWZJK9ECuiNwERAMvFmc6VX1LVaNVNToiIqIkI1Vovk0zu/6uK617tfY6kjElwrcp5+7E3Sx9eymqdjnmkuBP0d8D+N5WqYnb7wwiMgB4HBihqhnFmdYU3/EDx0l4ymma2X5AezpfZzdCMRWLb1POpLgka8pZQvwp+iuBtiLSUkTCgVhgju8IItIFmIpT8A/4DJoLDBKROu4B3EFuP3MeMk9kkjA5gZNHTtL40sbWNNNUWI0uakTfB/sCTlPOHct2eJqnIiiy6KtqNnA/TrHeCMxU1Q0iMllERrijvQhUB2aJyGoRmeNOexh4CueHYyUw2e1nzlFudi7zn59P6q5UajetzYBHBhBSyU63MBVXmyvbEP3baFD46u9fkbLFjvudj0r+jKSqcUBcvn4TfboHnGXaacC0cw1o/ktVWfzmYvas3uM0zZxgTTNNcOgytgvH9h3jp4U/kfBUAte8dA3VI6p7HatcslXEcmT17NVsmreJ0PBQYv4aY00zTdAQEXrf25vITpGcTD1J/OR4Mk9YU85zYUW/nNj63VZWTl8JAv3+2I8G7Rt4HcmYMhUaFsrA8QOp3bQ2qTtTmf/cfHKzrSlncVnRLwf2Je1j0SuLAOhxaw9a9mzpcSJjvFG5emWGTBzCBbUuYM+aPXz3xnfWlLOYrOgHuCN7jjD3mbnkZucSNTSKS0Ze4nUkYzxV48IaDJ4wmNDwUDYv2MyPs370OlK5YkU/gJ08epL4J+PJOJ5Bs67N6HlHT2uaaQzQoF0D+v+5Pwgk/iuRn7/+2etI5YYV/QCVnZHN3Kfncnzfceq3rk//P/cnJNT+XcbkadGjBT1v7wnAN699wy/rfvE4UflgVSQA5ebk8tXfv+LA5gNUj6hOzIQYwi4I8zqWMQHn4uEXc/GIi8nNzmXes/NI3ZXqdaSAZ0U/AC2btowdy3YQXi2cmCdiqFq3qteRjAlYPW7tQYvftCDzRCbxT8aTfji96ImCmBX9ALP2s7Ws/3w9IZVCGPTYIOo2q+t1JGMCWkhoyOlmzGkpaU4bfrscc6Gs6AeQbYu3sWyae5nkB/sSeUmkx4mMKR8qVa7E4L8OpmajmhzadoiFLyy0NvyFsKIfIPau38tXL38FCt1u7kabK9t4HcmYcuWCWhcw5IkhVKlZhd0/7Obb17+1NvwFsKIfAA7vOnxGW/xLR1/qdSRjyqVakbWImRhDpcqV+GnhT6z6cJXXkQKOFX2PnTh0gvhJznVEWvRoYW3xjTlPDdo1oP9f+iMhwg8f/cDGhI1eRwooVvQ9lJGWQdykOE4cPMGFHS6k35/6WVt8Y0pA867N6XVPLwAWv7nYrsPvwyqMR7Izs5n3zDxSdzrXxR88YTCVKvt1pWtjjB86Du7IZbGXobnKwpcWsi9pn9eRAoIVfQ/k5uSy6OVF7N2wl6p1qzJ00lCq1KjidSxjKpzLb7icDoM7kJOZw9yn53J4l93DyYp+GVNVlkxdwval2wmvFs7QSUPtZhDGlBIRodfdvWjevTkZaRnET4onLSXN61ie8qvoi0iMiGwWkS0iMr6A4X1E5AcRyRaR6/INy3FvoXj6NorBbNW/V7ExYSOhYaEMfnwwdVvYyVfGlKaQ0BD6/7k/DaMacuLgCeKeiOPUsVNex/JMkUVfREKB14EhQBRwg4hE5RttFzAO+LCAlzipqp3dx4gChgeNDXEb+GHGD0iI0P/h/jS6uJHXkYwJCnknb9VpXocjyUdImJxA1sksr2N5wp81/W7AFlXdpqqZwAxgpO8IqrpDVdcCdgpcIbZ+t5UlU5cA0Pve3rTo0cLbQMYEmcrVKzu7UxtU58BPB5j/3HxysnK8jlXm/Cn6jYHdPs+T3X7+qiIiiSKyTERGFTSCiNzpjpOYklLx7nS/e9Xu02fbdr2pKx0GdfA6kjFBqVq9agx9cihValUh+cdkFk1ZRG5OcK2rlsWB3OaqGg3cCEwRkdb5R1DVt1Q1WlWjIyIiyiBS2dm3cR/z/jYPzVE6jepE5zGdvY5kTFCr3bg2QycNJeyCMLZ9t40lU5cE1eUa/Cn6e4CmPs+buP38oqp73L/bgK+BLsXIV64d2n6IhMkJ5GTm0H5Ae7rf2t3OtjUmANRvXf/0LRc3Jmxk5fsrvY5UZvwp+iuBtiLSUkTCgVjAr1Y4IlJHRCq73fWBK4Ckcw1bnhzZc4S4iXGnL6/Q+77eVvCNCSCRF0cy4JEBSIiw+uPVrP54tdeRykSRRV9Vs4H7gbnARmCmqm4QkckiMgJARLqKSDIwBpgqIhvcyTsCiSKyBlgEPKeqFb7oHz9wnC8nfMnJoydp3Lkx/R+2Wx0aE4iad23OVQ9dBQIr/rmCDV9uKHqics6v8/5VNQ6Iy9dvok/3SpzdPvmnWwpccp4Zy5X0w+l8OeHL09fTGfTYIELDQr2OZYwpRJsr25B1Movv3viOJVOXEFYljHb923kdq9TY6mcJOnn0JF9O+JJje49Rr1U9YibGEFbF7m1rTKDrGNORHrf2AOCb//mGbYu3eZyo9NgVvkpIRloGcRPjSN2dSp2mdRj65FAqV6/sdSwA7vr8Lq8jGBPwOl3TiaxTWaz69yoW/n0hIZVCKuT5NLamXwIy0zOJmxTHoe2HqBVZi2FPDeOCWhd4HcsYU0yXxV7GpddeiuYoC15YwO5Vu4ueqJyxon+eMtMziZ8UT8pPKdRoUINhTw2jat2qXscyxpwDEaHbLd24ePjF5GbnMu9v89izxu8W6uWCFf3zkHUyi4TJCezftJ9q9asx7OlhdsVMY8o5EeE3t/+GjjEdycnMIeGphApV+K3on6OsU07B35e0j2r1qzH8meHUbFjT61jGmBKQd0nm9gPbny78v6z7xetYJcKK/jnIOpVFwlMJp2+CcvXTV1OzkRV8YyoSCRH63NeHdv3bOYV/cgK/rC//hd+KfjFlncwiflI8e9e5Bf+Zq6kVWcvrWMaYUiAhQp/7+9CuXzuyM7JJeDKBX9aW78JvRb8YMtMziX8y3tmlU68aw58dTu3Gtb2OZYwpRSGhIfT5g7PGn52RTfzkeJJXJ3sd65xZ0fdT5gmnlU7ePnxbwzcmeISEhnDlH66kw6D/3m+3vDbntKLvh1PHTvHFhC/Yv2k/1SOqM/zZ4VbwjQkyEiL0vrf36VY9c5+Zy45lO7yOVWxW9IuQnprO549/zsEtB6nZsCYjnhthrXSMCVISIvS6p9fpdvzzn5vPlm+3eB2rWKzon0VaShqfP/Y5qTtTqd2kNsP/Ntza4RsT5PLa8Xe+rjOaq3z196/YNH+T17H8ZkW/EEeSj/DZI59xdM9R6rWsx/Bnh1OtXjWvYxljAoCI0O3mbkTfFA0K3/7Pt6z9dK3XsfxiRb8AB7cdZM6jc05fHvnqZ67mgtp2LR1jzJkuG3sZPe/oCcCyactY+a+VAX/rRbvKZj6/rP+FuU/PJSs9iyZdmjDw0YF2eWRjTKEuHn4x4dXC+ea1b/hx5o9kHM8gZ1gOoaGBeR8Nv9b0RSRGRDaLyBYRGV/A8D4i8oOIZIvIdfmG3SIiP7uPW0oqeGnYvnQ78U/Ek5WeRcueLRn818FW8I0xRWrXrx0Dxw8kNCyUpPgkYmNjOXXqlNexClRk0ReRUOB1YAgQBdwgIlH5RtsFjAM+zDdtXeAJoDvQDXhCROqcf+yStzFhIwteWEBOVg5RQ6Po/3B/u+OVMcZvLXq0YMiTQwirGsbs2bMZMmQIR48e9TrWr/izpt8N2KKq21Q1E5gBjPQdQVV3qOpaIDfftIOB+ap6WFVTgflATAnkLjGqyoQJE/juje/QXOXyGy/niruusHvaGmOKLfLiSEb8bQSNGjXi66+/pk+fPuzZE1hX6PSnsjUGfE89S3b7+cOvaUXkThFJFJHElJQUP1/6/GVmZjJu3Diefvrp0ydeXB57OSJSZhmMMRVLvZb1WLp0Ke3bt2ft2rX06NGDdevWeR3rtIBYnVXVt1Q1WlWjIyIiymSeR48e5eqrr+b999+natWqDH58MB1jOpbJvI0xFVuLFi1YsmQJV1xxBcnJyfTq1YuFCxd6HQvwr+jvAZr6PG/i9vPH+UxbarZv307Pnj2ZP38+DRo04JtvvqFZ12ZexzLGVCD16tVjwYIFjBkzhmPHjhETE8M//vEPr2P5VfRXAm1FpKWIhAOxwBw/X38uMEhE6rgHcAe5/Tzz/fff0717d5KSkoiKimLZsmVER0d7GckYU0FVqVKFGTNm8PDDD5Odnc0dd9zBX/7yF3Jz8x/+LDtFFn1VzQbuxynWG4GZqrpBRCaLyAgAEekqIsnAGGCqiGxwpz0MPIXzw7ESmOz288S//vUvrrrqKlJSUhg4cCBLly6lZcuWXsUxxgSBkJAQXnjhBd5++20qVarEiy++yLXXXsvx48e9yePPSKoap6rtVLW1qj7j9puoqnPc7pWq2kRVq6lqPVW9yGfaaaraxn28Wzpv4+xycnJ45JFH+N3vfkdGRgZ33303X375JbVq2ZUyjTFl4/bbbychIYHatWvz2Wef0bNnT7Zv317mOQLiQG5pOnr0KKNGjeKFF14gNDSUN954g//7v/8jLMxOujLGlK3+/fuzfPly2rdvz/r16+natSuLFi0q0wwVuugnJSXRrVs3vvjiC+rWrcv8+fO55557vI5ljAli7dq1Y/ny5QwZMoRDhw4xcOBApkyZUmbX7KmwRf8///kP3bt356effqJTp06sXLmSq666yutYxhhDrVq1+Pzzz3nkkUfIycnhoYce4qabbuLEiROlPu8KV/SzsrJ4+OGHGT16NGlpadxwww0sXbqUVq1aeR3NGGNOCw0N5bnnnmPWrFlUq1aNDz/8kN/85jfs2LGjVOdboYr+nj176NevHy+99BKVKlXi5Zdf5oMPPqBaNbsOvjEmMF133XWsWLGCdu3acezYMWrUqFGq86swl1b+6quviI2NJSUlhcaNG/PRRx9xxRVXeB3LGGOKFBUVxYoVK9i7dy/16tUr1XlVmKKflpZ2uv39Bx98QFldzsEYY0pCrVq1yqQZeYUp+iNGjGDevHn069cvYG9eYIwxXqswRR9g4MCBXkcwxpiAVqEO5BpjjDk7K/rGGBNErOgbY0wQsaJvjDFBxIq+McYEESv6xhgTRKzoG2NMEPGr6ItIjIhsFpEtIjK+gOGVReQjd/hyEWnh9m8hIidFZLX7eLNk4xtjjCmOIk/OEpFQ4HVgIJAMrBSROaqa5DPabUCqqrYRkVjgeeB6d9hWVe1cwrmNMcacA3/W9LsBW1R1m6pmAjOAkfnGGQn80+2eDfQXESm5mMYYY0qCP0W/MbDb53my26/AcdwbqR8F8i4V11JEfhSRb0Skd0EzEJE7RSRRRBJTUlKK9QaMMcb4r7QP5O4FmqlqF+CPwIciUjP/SKr6lqpGq2q0XR3TGGNKjz9Ffw/Q1Od5E7dfgeOISCWgFnBIVTNU9RCAqq4CtgLtzje0McaYc+NP0V8JtBWRliISDsQCc/KNMwe4xe2+DvhKVVVEItwDwYhIK6AtsK1kohtjjCmuIlvvqGq2iNwPzAVCgWmqukFEJgOJqjoHeAeYLiJbgMM4PwwAfYDJIpIF5AJ3q+rh0ngjxhhjiubX9fRVNQ6Iy9dvok/3KWBMAdN9DHx8nhmNMcaUEDsj1xhjgogVfWOMCSJW9I0xJohY0TfGmCBiRd8YY4KIFX1jjAkiVvSNMSaIWNE3xpggYkXfGGOCiBV9Y4wJIlb0jTEmiFjRN8aYIGJF3xhjgogVfWOMCSJW9I0xJohY0TfGmCBiRd8YY4KIX0VfRGJEZLOIbBGR8QUMrywiH7nDl4tIC59hj7r9N4vI4JKLbowxpriKLPrujc1fB4YAUcANIhKVb7TbgFRVbQO8AjzvThuFc7/ci4AY4I28G6UbY4wpe/6s6XcDtqjqNlXNBGYAI/ONMxL4p9s9G+gvIuL2n6GqGaq6Hdjivp4xxhgP+HNj9MbAbp/nyUD3wsZR1WwROQrUc/svyzdt4/wzEJE7gTvdp2kisjnfKPWBg35kLUuBmAksV3EEYiawXMUVcLne4i0vMjX3ZyR/in6pU9W3gLcKGy4iiaoaXYaRihSImcByFUcgZgLLVVyBmCsQM+XxZ/fOHqCpz/Mmbr8CxxGRSkAt4JCf0xpjjCkj/hT9lUBbEWkpIuE4B2bn5BtnDnCL230d8JWqqts/1m3d0xJoC6womejGGGOKq8jdO+4++vuBuUAoME1VN4jIZCBRVecA7wDTRWQLcBjnhwF3vJlAEpAN3KeqOeeQs9BdPx4KxExguYojEDOB5SquQMwViJkAEGeF3BhjTDCwM3KNMSaIWNE3xpggEpBFX0ReFJFNIrJWRD4RkdqFjHfWy0OUcKYxIrJBRHJFpNCmWCKyQ0TWichqEUkszUzFzFVmy8qdX10RmS8iP7t/6xQyXo67rFaLSP4GAiWV5ZwvI1Ka/Mg1TkRSfJbP7WWQaZqIHBCR9YUMFxF5zc28VkQuK+1MfubqKyJHfZbVxDLI1FREFolIkvsdfLCAcTxZXmelqgH3AAYBldzu54HnCxgnFNgKtALCgTVAVClm6gi0B74Gos8y3g6gfhkuqyJzlfWycuf5AjDe7R5f0P/QHZZWyjmKfO/AvcCbbncs8FEZ/N/8yTUO+N+y+iy58+wDXAasL2T4UCAeEKAHsDxAcvUFvijjZdUIuMztrgH8VMD/0JPldbZHQK7pq+o8Vc12ny7Dad+fnz+XhyjJTBtVNf+Zwp7zM1eZLiuX76U5/gmMKuX5FeZ8LiPida4yp6rf4rTAK8xI4H11LANqi0ijAMhV5lR1r6r+4HYfBzby6ysOeLK8ziYgi34+v8f5pcyvoMtD/OoSDx5QYJ6IrHIvLxEIvFhWF6rqXrd7H3BhIeNVEZFEEVkmIqXxw+DPez/jMiJA3mVESpO//5PR7m6B2SLStIDhZS1Qv3cAvxGRNSISLyIXleWM3V2CXYDl+QYF3PLy7DIMIrIAaFjAoMdV9TN3nMdx2vd/ECiZ/NBLVfeISANgvohsctdSvM5V4s6Wy/eJqqqIFNY2uLm7vFoBX4nIOlXdWtJZy6nPgX+raoaI3IWzNdLP40yB6gecz1KaiAwFPsU5GbTUiUh14GPg/6nqsbKY5/nwrOir6oCzDReRccDVQH91d47lU+KXeCgqk5+vscf9e0BEPsHZjD+vol8CuUrlchhnyyUi+0WkkarudTdnDxTyGnnLa5uIfI2ztlSSRb84lxFJljMvI1Kaisylqr4Z/oFznMRrAXlpFd9iq6pxIvKGiNRX1VK96JmIhOEU/A9U9T8FjBJwyysgd++ISAzwF2CEqqYXMpo/l4coUyJSTURq5HXjHJAusLVBGfNiWflemuMW4FdbJCJSR0Qqu931gStwzt4uSedzGZHSVGSufPt+R+DsM/baHOBmt1VKD+Coz248z4hIw7zjMCLSDae2leoPtzu/d4CNqvpyIaMF3vLy+khyQQ+c6+7vBla7j7yWFZFAnM94Q3GOmG/F2dVRmpmuwdkflwHsB+bmz4TTEmON+9hQ2pn8zVXWy8qdXz1gIfAzsACo6/aPBv7hdvcE1rnLax1wWyll+dV7BybjrFQAVAFmuZ+7FUCr0l4+fub6m/s5WgMsAjqUQaZ/A3uBLPdzdRtwN3C3O1xwbqq01f2fFdqSrYxz3e+zrJYBPcsgUy+cY3hrfWrV0EBYXmd72GUYjDEmiATk7h1jjDGlw4q+McYEESv6xhgTRKzoG2NMELGib4wxQcSKvjHGBBEr+sYYE0T+PzcbOmrQzB5eAAAAAElFTkSuQmCC\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
--------------------------------------------------------------------------------