Last active
September 6, 2024 05:51
Revisions
-
HarryR revised this gist
Sep 6, 2024 . 1 changed file with 23 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,9 @@ #!/usr/bin/env python3 import os import re import sys import requests import calendar from datetime import datetime, timedelta, date from typing import TypedDict from collections import defaultdict @@ -51,19 +54,19 @@ def api(*args, **params): resp = SESSION.get(url, params=params) return resp.json() def reporting_range(year:int, month:int): first_day = date(year, month, 1) _, last_day_num = calendar.monthrange(year, month) last_date = date(year, month, last_day_num) return first_day.strftime('%Y-%m-%d'), last_date.strftime('%Y-%m-%d') def main(year:int, month:int): board: KBF_Board = api('board') color2name = {_['value']: _['name'] for _ in board['colors']} time_by_color: dict[str,int] = defaultdict(int) total_time = 0 last_month_start, last_month_end = reporting_range(year, month) tasks_by_day: dict[str,list[KBF_Task]] = defaultdict(list[KBF_Task]) daily_times: dict[str, int] = defaultdict(int) @@ -94,13 +97,22 @@ def main(): print(f'### {day} ({d.strftime("%A")}, {daily_time_pct}%)') for t in tasks: time_pct = t['totalSecondsSpent'] / total_time daily_task_pct = round((t['totalSecondsSpent'] / daily_times[day]) * 100,1) print(f' * {color2name[t["color"]]}:', t['name'], f'({round(time_pct * 100, 1)}%m/{daily_task_pct}%d)') for st in t['subtasks']: print(' *', '[x]' if st['finished'] else '[ ]', st['name']) print() print() if __name__ == "__main__": month = datetime.now().month year = datetime.now().year if len(sys.argv) > 1: m = re.match(r'^((?P<month>[0-9]{1,2})|(?P<year>[0-9]{4})-(?P<month2>[0-9]{1,2}))$', sys.argv[1]) x = m.groupdict() month = int(x['month'] or x['month2']) year = int(x.get('year',None) or year) if month < 1 or month > 12: print("Error: invalid month") sys.exit(1) main(year, month) -
HarryR revised this gist
Sep 6, 2024 . No changes.There are no files selected for viewing
-
HarryR created this gist
Sep 6, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,106 @@ #!/usr/bin/env python3 import os import requests from datetime import datetime, timedelta, date from typing import TypedDict from collections import defaultdict API_SECRET = os.getenv('KANBANFLOW_SECRET') SESSION = requests.Session() SESSION.auth = ('apiToken', API_SECRET) class KBF_Column(TypedDict): uniqueId: str name: str class KBF_Color(TypedDict): name: str value: str class KBF_Board(TypedDict): _id: str name: str columns: list[KBF_Column] colors: list[KBF_Color] class KBF_Subtask(TypedDict): name: str finished: str class KBF_Task(TypedDict): _id: str name: str description: str color: str columnId: str totalSecondsSpent: int totalSecondsEstimate: int responsibleUserId: str groupingDate: str subtasks: list[KBF_Subtask] class KBF_Tasks(TypedDict): columnId: str columnName: str tasksLimited: bool tasks: list[KBF_Task] def api(*args, **params): url = f'https://kanbanflow.com/api/v1/{"/".join(args)}' resp = SESSION.get(url, params=params) return resp.json() def reporting_range(): today = datetime.now() last_day_of_previous_month = today.replace(day=1) - timedelta(days=1) first_day = last_day_of_previous_month.replace(day=1) return first_day.strftime('%Y-%m-%d'), last_day_of_previous_month.strftime('%Y-%m-%d') def main(): board: KBF_Board = api('board') color2name = {_['value']: _['name'] for _ in board['colors']} time_by_color: dict[str,int] = defaultdict(int) total_time = 0 last_month_start, last_month_end = reporting_range() tasks_by_day: dict[str,list[KBF_Task]] = defaultdict(list[KBF_Task]) daily_times: dict[str, int] = defaultdict(int) all_tasks: list[KBF_Tasks] = api('tasks', limit=100, startGroupingDate=last_month_start, order='asc', columnName='Done') for tasks in all_tasks: for t in tasks['tasks']: if not t['groupingDate'].startswith(last_month_start[:8]): continue time_by_color[t['color']] += t['totalSecondsSpent'] total_time += t['totalSecondsSpent'] daily_times[t['groupingDate']] += t['totalSecondsSpent'] tasks_by_day[t['groupingDate']].append(t) t['subtasks'] = api('tasks', t['_id'], 'subtasks') print('# Task report for', last_month_start, 'to', last_month_end) print() print('## Time Breakdown') for c, totalTimeForColor in time_by_color.items(): time_pct = totalTimeForColor / total_time print(' *', color2name[c], f'({round(time_pct * 100, 1)}%)') print() print('## Daily Breakdown') print() for day, tasks in tasks_by_day.items(): d = date.fromisoformat(day) daily_time_pct = round((daily_times[day] / total_time) * 100,1) print(f'### {day} ({d.strftime("%A")}, {daily_time_pct}%)') for t in tasks: time_pct = t['totalSecondsSpent'] / total_time print(f' * {color2name[t["color"]]}:', t['name'], f'({round(time_pct * 100, 1)}%)') for st in t['subtasks']: print(' *', '[x]' if st['finished'] else '[ ]', st['name']) print() print() if __name__ == "__main__": main()