def find_cycle_time_ave(report_group_slug, stop): try: dr = DailyRecord.objects.get(date=make_end_date(date=stop), group=report_group_slug) except DailyRecord.DoesNotExist: print "No Daily Record: %s %s" % (report_group_slug, make_end_date(date=stop)) raise return dr.moving_cycle_time
def state_transition_counts(state, months, count_type="exit", raw=False): end = make_end_date(date=datetime.datetime.now()) start = end - relativedelta(months=months) start = make_start_date(date=start) counts = [] data = [] current_date = start while current_date <= end: if current_date.weekday() == 5: # Saturday current_date += relativedelta(days=2) elif current_date.weekday() == 6: # Sunday current_date += relativedelta(days=1) range_start = make_start_date(date=current_date) range_end = make_end_date(date=current_date) if count_type == "exit": kwargs = dict( exited__gte=range_start, exited__lte=range_end, ) elif count_type == "enter": kwargs = dict( entered__gte=range_start, entered__lte=range_end, ) count = StateLog.objects.filter( state=state, **kwargs ).count() data.append((current_date, count)) counts.append(count) current_date = current_date + relativedelta(days=1) counts.sort() print "%s\t%s" % (start, end) print "Median\t%s" % median(counts) print "Average\t%s" % average(counts) print "Min\t%s" % counts[0] print "Max\t%s" % counts[-1] hist = histogram(counts) keys = hist.keys() keys.sort() for k in keys: print "%s\t%s" % (k, hist[k]) if raw is True: for date, count in data: print "%s\t%s" % (date, count)
def _hit_due_date(card): if card.due_date is None: return '' due_date = make_end_date(date=card.due_date) if card.done_date is None: done_date = make_end_date(date=now()) else: done_date = make_end_date(date=card.done_date) return done_date <= due_date
def find_cycle_time_ave(report_group_slug, stop): try: dr = DailyRecord.objects.get( date=make_end_date(date=stop), group=report_group_slug, ) except DailyRecord.DoesNotExist: print "No Daily Record: %s %s" % (report_group_slug, make_end_date(date=stop)) raise return dr.moving_cycle_time
def state_transition_counts(state, months, count_type="exit", raw=False): end = make_end_date(date=datetime.datetime.now()) start = end - relativedelta(months=months) start = make_start_date(date=start) counts = [] data = [] current_date = start while current_date <= end: if current_date.weekday() == 5: # Saturday current_date += relativedelta(days=2) elif current_date.weekday() == 6: # Sunday current_date += relativedelta(days=1) range_start = make_start_date(date=current_date) range_end = make_end_date(date=current_date) if count_type == "exit": kwargs = dict( exited__gte=range_start, exited__lte=range_end, ) elif count_type == "enter": kwargs = dict( entered__gte=range_start, entered__lte=range_end, ) count = StateLog.objects.filter(state=state, **kwargs).count() data.append((current_date, count)) counts.append(count) current_date = current_date + relativedelta(days=1) counts.sort() print "%s\t%s" % (start, end) print "Median\t%s" % median(counts) print "Average\t%s" % average(counts) print "Min\t%s" % counts[0] print "Max\t%s" % counts[-1] hist = histogram(counts) keys = hist.keys() keys.sort() for k in keys: print "%s\t%s" % (k, hist[k]) if raw is True: for date, count in data: print "%s\t%s" % (date, count)
def find_cycle_time_ave(stop): dr = DailyRecord.objects.get( date=make_end_date(date=stop), group='dev', ) return dr.moving_cycle_time
def calculate(cls, start_date, end_date, group="all"): from kardboard.models import Kard from kardboard.models import ReportGroup start_date = make_start_date(date=start_date) end_date = make_end_date(date=end_date) try: record = cls.objects.get( group=group, start_date=start_date, end_date=end_date, ) except cls.DoesNotExist: record = cls() record.start_date = start_date record.end_date = end_date record.group = group record.data = {} kards = ReportGroup(group, Kard.objects.filter( done_date__gte=start_date, done_date__lte=end_date, ) ) record.data = report_on_cards(list(kards.queryset)) record.save() return record
def calculate(cls, start_date, end_date, group="all"): from kardboard.models import Kard from kardboard.models import ReportGroup start_date = make_start_date(date=start_date) end_date = make_end_date(date=end_date) try: record = cls.objects.get( group=group, start_date=start_date, end_date=end_date, ) except cls.DoesNotExist: record = cls() record.start_date = start_date record.end_date = end_date record.group = group record.data = {} kards = ReportGroup(group, Kard.objects.filter( start_date__gte=start_date, start_date__lte=end_date, ) ) record.data = report_on_cards(kards) record.save() return record
def weekly_moving_data(report_group_slug, start): start = parse_date(start) stop = start + relativedelta(days=6) start = make_start_date(date=start) stop = make_end_date(date=stop) return moving_data(report_group_slug, start, stop)
def moving_median_abs_dev(self, year=None, month=None, day=None, weeks=4): """ The moving median absolute deviation of cycle time for every day in the last N weeks. See http://en.wikipedia.org/wiki/Median_absolute_deviation """ end_date = make_end_date(year, month, day) start_date = end_date - relativedelta(weeks=weeks) start_date = make_start_date(date=start_date) qs = self.done().filter( done_date__lte=end_date, done_date__gte=start_date, ).scalar('_cycle_time') cycle_times = [t for t in qs if t is not None] median_cycle_time = median(cycle_times) if median_cycle_time is not None: absolute_deviations = [ math.fabs(median_cycle_time - c) for c in cycle_times ] mad = median(absolute_deviations) else: mad = None if mad is None: mad = 0 return int(round(mad))
def test_regress_service_class_miscalc(self): from kardboard.util import make_start_date, make_end_date c = self.make_card( key="JANUS-234", title="MMF: Database visualization (Iteration 1)", backlog_date=datetime(2013, 2, 14), start_date=datetime(2013, 5, 30), done_date=datetime(2013, 8, 1), _service_class="Normal", state="Done", team="Team Venture", ) c.save() Record = self._get_target_class() start_date = make_start_date(date=datetime(2013, 8, 1)) end_date = make_end_date(date=datetime(2013, 8, 31)) Record.calculate(start_date, end_date, group="team-venture") r = Record.objects.get( start_date=start_date, end_date=end_date, group="team-venture", ) actual = r.data assert actual['Normal']['wip'] == 1
def state(): date = datetime.datetime.now() date = make_end_date(date=date) states = States() board = DisplayBoard(backlog_limit=0) # defaults to all teams, 7 days of done board.cards # force the card calculation board.rows title = app.config.get("SITE_NAME") metrics = [] teams = teams_service.setup_teams(app.config) context = { "title": title, "board": board, "states": states, "metrics": metrics, "wip_limits": {}, "date": date, "teams": teams, "updated_at": datetime.datetime.now(), "version": VERSION, } return render_template("state.html", **context)
def team(team_slug=None): date = datetime.datetime.now() date = make_end_date(date=date) teams = app.config.get("CARD_TEAMS", []) team_mapping = {} for team in teams: team_mapping[slugify(team)] = team target_team = None if team_slug: target_team = team_mapping.get(team_slug, None) if not team: abort(404) board = DisplayBoard(teams=[target_team]) metrics = [ { "Ave. Cycle Time": Kard.objects.filter(team=target_team).moving_cycle_time( year=date.year, month=date.month, day=date.day ) }, { "Done this week": Kard.objects.filter(team=target_team) .done_in_week(year=date.year, month=date.month, day=date.day) .count() }, { "Done this month": Kard.objects.filter(team=target_team) .done_in_month(year=date.year, month=date.month, day=date.day) .count() }, {"On the board": len(board.cards)}, ] done_cards = Kard.objects.done().filter(team=target_team).order_by("-done_date") title = "%s cards" % target_team report_config = ( {"slug": "cycle", "name": "Cycle time"}, {"slug": "classes", "name": "Throughput"}, {"slug": "leaderboard", "name": "Leaderboard"}, ) context = { "title": title, "team_slug": team_slug, "target_team": target_team, "done_cards": done_cards, "metrics": metrics, "report_config": report_config, "board": board, "date": date, "updated_at": datetime.datetime.now(), "version": VERSION, } return render_template("team.html", **context)
def dashboard(year=None, month=None, day=None): date = kardboard.util.now() now = kardboard.util.now() scope = "current" if year: date = date.replace(year=year) scope = "year" if month: date = date.replace(month=month) scope = "month" start, end = month_range(date) date = end if day: date = date.replace(day=day) scope = "day" date = make_end_date(date=date) wip_cards = list(Kard.in_progress(date)) wip_cards = sorted(wip_cards, key=lambda c: c.current_cycle_time(date)) wip_cards.reverse() backlog_cards = Kard.backlogged(date).order_by("key") metrics = [ {"Ave. Cycle Time": Kard.objects.moving_cycle_time(year=date.year, month=date.month, day=date.day)}, {"Done this week": Kard.objects.done_in_week(year=date.year, month=date.month, day=date.day).count()}, {"Done this month": Kard.objects.done_in_month(year=date.year, month=date.month, day=date.day).count()}, {"On the board": len(wip_cards) + backlog_cards.count()}, ] title = "Dashboard" if scope == "year": title += " for %s" if scope == "month": title += " for %s/%s" % (date.month, date.year) if scope == "day" or scope == "current": title += " for %s/%s/%s" % (date.month, date.day, date.year) forward_date = date + relativedelta.relativedelta(days=1) back_date = date - relativedelta.relativedelta(days=1) if forward_date > now: forward_date = None context = { "forward_date": forward_date, "back_date": back_date, "scope": scope, "date": date, "title": title, "metrics": metrics, "wip_cards": wip_cards, "backlog_cards": backlog_cards, "updated_at": now, "version": VERSION, } return render_template("dashboard.html", **context)
def state(): date = datetime.datetime.now() date = make_end_date(date=date) states = States() board = DisplayBoard() # defaults to all teams, 7 days of done board.cards # force the card calculation board.rows title = app.config.get("SITE_NAME") wip_cards = [k for k in board.cards if k.state in states.in_progress] done_this_week = [k for k in board.cards if k.state == states.done] blocked = [k for k in wip_cards if k.blocked] metrics = [{"WIP": len(wip_cards)}, {"Blocked": len(blocked)}, {"Done this week": len(done_this_week)}] context = { "title": title, "board": board, "states": states, "metrics": metrics, "date": date, "updated_at": datetime.datetime.now(), "version": VERSION, } return render_template("team.html", **context)
def state(): date = datetime.datetime.now() date = make_end_date(date=date) states = States() board = DisplayBoard(backlog_limit=0) # defaults to all teams, 7 days of done board.cards # force the card calculation board.rows title = app.config.get('SITE_NAME') metrics = [] teams = teams_service.setup_teams( app.config ) context = { 'title': title, 'board': board, 'states': states, 'metrics': metrics, 'wip_limits': {}, 'date': date, 'teams': teams, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('state.html', **context)
def report_detailed_flow(group="all", months=3, cards_only=False): end = kardboard.util.now() months_ranges = month_ranges(end, months) start_day = make_start_date(date=months_ranges[0][0]) end_day = make_end_date(date=end) if cards_only: only_arg = ('state_card_counts', 'date', 'group') else: only_arg = ('state_counts', 'date', 'group') reports = FlowReport.objects.filter( date__gte=start_day, date__lte=end_day, group=group).only(*only_arg) if not reports: abort(404) chart = {} chart['categories'] = [] series = [] for state in States(): seri = {'name': state, 'data': []} series.append(seri) done_starting_point = 0 for report in reports: chart['categories'].append(report.date.strftime("%m/%d")) for seri in series: if cards_only: daily_seri_data = report.state_card_counts.get(seri['name'], 0) else: daily_seri_data = report.state_counts.get(seri['name'], 0) if seri['name'] == "Done": if len(seri['data']) == 0: done_starting_point = daily_seri_data daily_seri_data = 0 else: daily_seri_data = daily_seri_data - done_starting_point seri['data'].append(daily_seri_data) chart['series'] = series start_date = reports.order_by('date').first().date reports.order_by('-date') context = { 'title': "Detailed Cumulative Flow", 'reports': reports, 'months': months, 'cards_only': cards_only, 'chart': chart, 'start_date': start_date, 'updated_at': reports[0].updated_at, 'states': States(), 'version': VERSION, } return render_template('report-detailed-flow.html', **context)
def state(): date = datetime.datetime.now() date = make_end_date(date=date) states = States() board = DisplayBoard(backlog_limit=0) # defaults to all teams, 7 days of done board.cards # force the card calculation board.rows title = app.config.get('SITE_NAME') metrics = [] teams = teams_service.setup_teams( app.config ) context = { 'title': title, 'board': board, 'states': states, 'metrics': metrics, 'date': date, 'teams': teams, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('team.html', **context)
def state(): date = datetime.datetime.now() date = make_end_date(date=date) board = DisplayBoard() # defaults to all teams, 7 days of done title = app.config.get("SITE_NAME") wip_cards = Kard.in_progress(date) backlog_cards = Kard.backlogged(date) metrics = [ {"Ave. Cycle Time": Kard.objects.moving_cycle_time(year=date.year, month=date.month, day=date.day)}, {"Done this week": Kard.objects.done_in_week(year=date.year, month=date.month, day=date.day).count()}, {"Done this month": Kard.objects.done_in_month(year=date.year, month=date.month, day=date.day).count()}, {"On the board": wip_cards.count() + backlog_cards.count()}, ] context = { "title": title, "board": board, "states": states, "metrics": metrics, "date": date, "updated_at": datetime.datetime.now(), "version": VERSION, } return render_template("team.html", **context)
def chart_flow(group="all", months=3): end = kardboard.util.now() months_ranges = month_ranges(end, months) start_day = make_start_date(date=months_ranges[0][0]) end_day = make_end_date(date=end) records = DailyRecord.objects.filter( date__gte=start_day, date__lte=end_day, group=group) chart = CumulativeFlowChart(900, 300) chart.add_data([r.backlog_cum for r in records]) chart.add_data([r.in_progress_cum for r in records]) chart.add_data([r.done for r in records]) chart.setup_grid(records) records.order_by('-date') context = { 'title': "Cumulative Flow", 'updated_at': datetime.datetime.now(), 'chart': chart, 'flowdata': records, 'version': VERSION, } return render_template('chart-flow.html', **context)
def state(): date = datetime.datetime.now() date = make_end_date(date=date) board = DisplayBoard() # defaults to all teams, 7 days of done title = app.config.get('SITE_NAME') wip_cards = Kard.in_progress(date) backlog_cards = Kard.backlogged(date) metrics = [ {'Ave. Cycle Time': Kard.objects.moving_cycle_time( year=date.year, month=date.month, day=date.day)}, {'Done this week': Kard.objects.done_in_week( year=date.year, month=date.month, day=date.day).count()}, {'Done this month': Kard.objects.done_in_month( year=date.year, month=date.month, day=date.day).count()}, {'On the board': wip_cards.count() + backlog_cards.count()}, ] context = { 'title': title, 'board': board, 'states': states, 'metrics': metrics, 'date': date, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('state.html', **context)
def report_flow(group="all", months=3): end = kardboard.util.now() months_ranges = month_ranges(end, months) start_day = make_start_date(date=months_ranges[0][0]) end_day = make_end_date(date=end) records = DailyRecord.objects.filter(date__gte=start_day, date__lte=end_day, group=group) chart = {} chart["categories"] = [report.date.strftime("%m/%d") for report in records] series = [{"name": "Planning", "data": []}, {"name": "Todo", "data": []}, {"name": "Done", "data": []}] for row in records: series[0]["data"].append(row.backlog) series[1]["data"].append(row.in_progress) series[2]["data"].append(row.done) chart["series"] = series start_date = records.order_by("date").first().date records.order_by("-date") context = { "title": "Cumulative Flow", "updated_at": datetime.datetime.now(), "chart": chart, "start_date": start_date, "flowdata": records, "version": VERSION, } return render_template("chart-flow.html", **context)
def moving_median_abs_dev(self, year=None, month=None, day=None, weeks=4): """ The moving median absolute deviation of cycle time for every day in the last N weeks. See http://en.wikipedia.org/wiki/Median_absolute_deviation """ end_date = make_end_date(year, month, day) start_date = end_date - relativedelta(weeks=weeks) start_date = make_start_date(date=start_date) qs = self.done().filter( done_date__lte=end_date, done_date__gte=start_date, ).scalar('_cycle_time') cycle_times = [t for t in qs if t is not None] median_cycle_time = median(cycle_times) if median_cycle_time is not None: absolute_deviations = [math.fabs(median_cycle_time - c) for c in cycle_times] mad = median(absolute_deviations) else: mad = None if mad is None: mad = 0 return int(round(mad))
def std_dev(start, stop): start = make_start_date(date=start) stop = make_end_date(date=stop) rg = ReportGroup('dev', Kard.objects.filter(done_date__gte=start, done_date__lte=stop)) cards = [c for c in rg.queryset if c.is_card] cycle_times = [c.cycle_time for c in cards] over_21 = [c for c in cycle_times if c > 21] over_21_pct = float(len(over_21)) / len(cycle_times) print "%s -- %s" % (start, stop) print "\t Sample: %s" % len(cards) print "\t Over 21: %s / %s" % (len(over_21), over_21_pct * 100) print "\t Ave: %s" % average(cycle_times) print "\t Stdev: %s" % standard_deviation(cycle_times) ct, pct = percentile(.8, cycle_times) print "\t 80pct: %s / %s" % (ct, pct * 100) cards_by_class = {} for c in cards: cards_by_class.setdefault(c.service_class['name'], []) cards_by_class[c.service_class['name']].append(c) for sclass, cards in cards_by_class.items(): cycle_times = [c.cycle_time for c in cards] print "\t ## %s" % (sclass) print "\t\t Sample: %s" % len(cards) print "\t\t Ave: %s" % average(cycle_times) print "\t\t Stdev: %s" % standard_deviation(cycle_times)
def calculate(klass, date, group='all'): """ Creates or updates a DailyRecord for the date provided. """ date = make_end_date(date=date) try: k = klass.objects.get(date=date, group=group) except klass.DoesNotExist: k = klass() k.date = date k.group = group k.backlog = ReportGroup(group, Kard.backlogged(date)).queryset.count() k.in_progress = ReportGroup(group, Kard.in_progress(date)).queryset.count() k.done = ReportGroup( group, Kard.objects.filter(done_date__lte=date)).queryset.count() k.completed = ReportGroup( group, Kard.objects.filter(done_date=date)).queryset.count() k.moving_cycle_time = ReportGroup( group, Kard.objects).queryset.moving_cycle_time(year=date.year, month=date.month, day=date.day) k.moving_lead_time = ReportGroup( group, Kard.objects).queryset.moving_lead_time(year=date.year, month=date.month, day=date.day) k.save()
def calculate(klass, date, group='all'): """ Creates or updates a DailyRecord for the date provided. """ date = make_end_date(date=date) try: k = klass.objects.get(date=date, group=group) except klass.DoesNotExist: k = klass() k.date = date k.group = group k.backlog = ReportGroup(group, Kard.backlogged(date)).queryset.count() k.in_progress = ReportGroup(group, Kard.in_progress(date)).queryset.count() k.done = ReportGroup(group, Kard.objects.filter(done_date__lte=date)).queryset.count() k.completed = ReportGroup(group, Kard.objects.filter(done_date=date)).queryset.count() k.moving_cycle_time = ReportGroup(group, Kard.objects).queryset.moving_cycle_time( year=date.year, month=date.month, day=date.day) k.moving_lead_time = ReportGroup(group, Kard.objects).queryset.moving_lead_time( year=date.year, month=date.month, day=date.day) k.moving_std_dev = ReportGroup(group, Kard.objects).queryset.moving_std_dev( year=date.year, month=date.month, day=date.day) k.moving_median_abs_dev = ReportGroup(group, Kard.objects).queryset.moving_median_abs_dev( year=date.year, month=date.month, day=date.day) k.save()
def _get_time_range(weeks): end = make_end_date( date=datetime.now() ) start = make_start_date( date=end - relativedelta(weeks=weeks) ) return start, end
def report_efficiency(group="all", months=3): state_mappings = app.config.get("EFFICIENCY_MAPPINGS", None) if state_mappings is None: abort(404) stats = teams_service.EfficiencyStats(mapping=state_mappings) end = kardboard.util.now() months_ranges = month_ranges(end, months) start_day = make_start_date(date=months_ranges[0][0]) end_day = make_end_date(date=end) records = FlowReport.objects.filter(date__gte=start_day, date__lte=end_day, group=group) incremented_state_counts = [] for r in records: a_record = {"date": r.date} a_record.update(r.state_counts) incremented_state_counts.append(a_record) for group_name in app.config.get("EFFICIENCY_INCREMENTS", ()): stats.make_incremental(incremented_state_counts, group_name) data = [] for r in incremented_state_counts: efficiency_stats = stats.calculate(r) data.append({"date": r["date"], "stats": efficiency_stats}) chart = {} chart["categories"] = [report.date.strftime("%m/%d") for report in records] group_names = app.config.get("EFFICIENCY_MAPPINGS_ORDER", state_mappings.keys()) series = [] for group_name in group_names: seri_data = [] for d in data: seri_data.append(d["stats"][group_name]) seri = dict(name=group_name, data=seri_data) series.append(seri) chart["series"] = series table_data = [] for row in data: table_row = {"Date": row["date"]} for group_name in group_names: table_row[group_name] = row["stats"][group_name] table_data.append(table_row) start_date = records.order_by("date").first().date context = { "title": "Efficiency", "start_date": start_date, "chart": chart, "table_data": table_data, "data_keys": ["Date"] + list(group_names), "updated_at": records.order_by("date")[len(records) - 1].date, "version": VERSION, } return render_template("chart-efficiency.html", **context)
def report_detailed_flow(group="all", months=3, cards_only=False): end = kardboard.util.now() months_ranges = month_ranges(end, months) start_day = make_start_date(date=months_ranges[0][0]) end_day = make_end_date(date=end) if cards_only: only_arg = ("state_card_counts", "date", "group") else: only_arg = ("state_counts", "date", "group") reports = FlowReport.objects.filter(date__gte=start_day, date__lte=end_day, group=group).only(*only_arg) if not reports: abort(404) chart = {} chart["categories"] = [] series = [] for state in States(): seri = {"name": state, "data": []} series.append(seri) done_starting_point = 0 for report in reports: chart["categories"].append(report.date.strftime("%m/%d")) for seri in series: if cards_only: daily_seri_data = report.state_card_counts.get(seri["name"], 0) else: daily_seri_data = report.state_counts.get(seri["name"], 0) if seri["name"] == "Done": if len(seri["data"]) == 0: done_starting_point = daily_seri_data daily_seri_data = 0 else: daily_seri_data = daily_seri_data - done_starting_point seri["data"].append(daily_seri_data) chart["series"] = series start_date = reports.order_by("date").first().date reports.order_by("-date") context = { "title": "Detailed Cumulative Flow", "reports": reports, "months": months, "cards_only": cards_only, "chart": chart, "start_date": start_date, "updated_at": reports[0].updated_at, "states": States(), "version": VERSION, } return render_template("report-detailed-flow.html", **context)
def find_value_cards(year): start = make_start_date(year, 1, 1) end = make_end_date(year, 12, 31) query = Kard.objects.filter(done_date__gte=start, done_date__lte=end) rg = ReportGroup('dev', query) cards = [c for c in rg.queryset if c.is_card] return cards
def done_in_week(self, year=None, month=None, day=None, date=None): """ Kards that were completed in the week of the specified day. """ if not date: date = munge_date(year, month, day) date = make_end_date(date=date) start_date, end_date = week_range(date) results = self.done().filter(done_date__lte=date, done_date__gte=start_date) return results
def done_in_range(self, start_date, end_date): end_date = make_end_date(date=end_date) start_date = make_start_date(date=start_date) done = Kard.objects.filter( team=self.team_name, done_date__gte=start_date, done_date__lte=end_date, _service_class__nin=self.exclude_classes, ) return done
def done_in_month(self, year=None, month=None, day=None, date=None): """ Kards that have been completed in the specified month. """ if not date: date = munge_date(year=year, month=month, day=day) date = make_end_date(date=date) start_date, faux_end_date = month_range(date) results = self.done().filter(done_date__lte=date, done_date__gte=start_date) return results
def team(team_slug=None): date = datetime.datetime.now() date = make_end_date(date=date) teams = app.config.get('CARD_TEAMS', []) team_mapping = {} for team in teams: team_mapping[slugify(team)] = team target_team = None if team_slug: target_team = team_mapping.get(team_slug, None) if not team: abort(404) board = DisplayBoard(teams=[target_team, ]) metrics = [ {'Ave. Cycle Time': Kard.objects.filter(team=target_team).moving_cycle_time( year=date.year, month=date.month, day=date.day)}, {'Done this week': Kard.objects.filter(team=target_team).done_in_week( year=date.year, month=date.month, day=date.day).count()}, {'Done this month': Kard.objects.filter(team=target_team).done_in_month( year=date.year, month=date.month, day=date.day).count()}, {'On the board': len(board.cards)}, ] done_cards = Kard.objects.done().filter(team=target_team).order_by('-done_date') title = "%s cards" % target_team report_config = ( {'slug': 'cycle', 'name': 'Cycle time'}, {'slug': 'classes', 'name': 'Throughput'}, {'slug': 'leaderboard', 'name': 'Leaderboard'}, ) context = { 'title': title, 'team_slug': team_slug, 'target_team': target_team, 'done_cards': done_cards, 'metrics': metrics, 'report_config': report_config, 'board': board, 'date': date, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('team.html', **context)
def find_wip(report_group_slug, stop): sl = FlowReport.objects.get( date=make_end_date(date=stop), group=report_group_slug, ) exclude = [u'Backlog', u'Done', ] wip = 0 for state, count in sl.state_counts.items(): if state not in exclude: wip += count return wip
def done_in_range(self, start_date, end_date): end_date = make_end_date(date=end_date) start_date = make_start_date(date=start_date) done = Kard.objects.filter( team=self.team_name, done_date__gte=start_date, done_date__lte=end_date, _service_class__nin=self.exclude_classes, ) self._card_info(done) return done
def report_cycle(group="all", months=3, year=None, month=None, day=None): today = datetime.datetime.today() if day: end_day = datetime.datetime(year=year, month=month, day=day) if end_day > today: end_day = today else: end_day = today start_day = end_day - relativedelta.relativedelta(months=months) start_day = make_start_date(date=start_day) end_day = make_end_date(date=end_day) records = DailyRecord.objects.filter( date__gte=start_day, date__lte=end_day, group=group) daily_moving_averages = [(r.date, r.moving_cycle_time) for r in records] daily_moving_lead = [(r.date, r.moving_lead_time) for r in records] daily_mad = [(r.date, r.moving_median_abs_dev) for r in records] start_date = daily_moving_averages[0][0] chart = {} chart['series'] = [ { 'name': 'Cycle time', 'data': [r[1] for r in daily_moving_averages], }, { 'name': 'Unpredictability', 'data': [r[1] for r in daily_mad], } ] chart['goal'] = app.config.get('CYCLE_TIME_GOAL', ()) daily_moving_averages.reverse() # reverse order for display daily_moving_lead.reverse() daily_mad.reverse() context = { 'title': "How quick can we do it?", 'updated_at': datetime.datetime.now(), 'chart': chart, 'months': months, 'start_date': start_date, 'daily_averages': daily_moving_averages, 'daily_mad': daily_mad, 'version': VERSION, } return render_template('report-cycle.html', **context)
def chart_cycle_distribution(group="all", months=3): ranges = ( (0, 4, "< 5 days"), (5, 10, "5-10 days"), (11, 15, "11-15 days"), (16, 20, "16-20 days"), (21, 25, "21-25 days"), (26, 30, "26-30 days",), (31, 9999, "> 30 days"), ) today = datetime.datetime.today() start_day = today - relativedelta.relativedelta(months=months) start_day = make_start_date(date=start_day) end_day = make_end_date(date=today) context = { 'title': "How quick can we do it?", 'updated_at': datetime.datetime.now(), 'version': VERSION, } query = Q(done_date__gte=start_day) & Q(done_date__lte=end_day) rg = ReportGroup(group, Kard.objects.filter(query)) total = rg.queryset.count() if total == 0: context = { 'error': "Zero cards were completed in the past %s months" % months } return render_template('chart-cycle-distro.html', **context) distro = [] for row in ranges: lower, upper, label = row query = Q(done_date__gte=start_day) & Q(done_date__lte=end_day) & \ Q(_cycle_time__gte=lower) & Q(_cycle_time__lte=upper) pct = ReportGroup(group, Kard.objects.filter(query)).queryset.count() / float(total) distro.append((label, pct)) chart = CycleDistributionChart(700, 400) chart.add_data([r[1] for r in distro]) chart.set_pie_labels([r[0] for r in distro]) context = { 'data': distro, 'chart': chart, 'title': "How quick can we do it?", 'updated_at': datetime.datetime.now(), 'distro': distro, 'version': VERSION, } return render_template('chart-cycle-distro.html', **context)
def _date(self, dtype, date=None, days=0): from kardboard.util import make_end_date, make_start_date from kardboard.util import now if not date: date = now() if dtype == 'start': date = make_start_date(date=date) elif dtype == 'end': date = make_end_date(date=date) date = date + relativedelta(days=days) return date
def queue_daily_record_updates(days=365): from kardboard.app import app from kardboard.util import make_end_date report_groups = app.config.get('REPORT_GROUPS', {}) group_slugs = report_groups.keys() group_slugs.append('all') now = datetime.datetime.now() for i in xrange(0, days): target_date = now - relativedelta.relativedelta(days=i) target_date = make_end_date(date=target_date) for slug in group_slugs: update_daily_record.delay(target_date, slug)
def _set_up_records(self): from kardboard.util import make_start_date from kardboard.util import make_end_date start_date = datetime.datetime(2011, 1, 1) end_date = datetime.datetime(2011, 6, 30) start_date = make_start_date(date=start_date) end_date = make_end_date(date=end_date) current_date = start_date while current_date <= end_date: r = self.make_record(current_date) r.save() current_date = current_date + relativedelta(days=1)
def report_service_class(group="all", months=None): from kardboard.app import app service_class_order = app.config.get('SERVICE_CLASSES', {}).keys() service_class_order.sort() service_classes = [ app.config['SERVICE_CLASSES'][k] for k in service_class_order ] if months is None: # We want the current report try: scr = ServiceClassSnapshot.objects.get(group=group, ) except ServiceClassSnapshot.DoesNotExist: scr = ServiceClassSnapshot.calculate(group=group, ) time_range = 'current' start_date = make_start_date(date=datetime.datetime.now()) end_date = make_end_date(date=datetime.datetime.now()) else: start = now() months_ranges = month_ranges(start, months) start_date = months_ranges[0][0] end_date = months_ranges[-1][1] try: scr = ServiceClassRecord.objects.get( group=group, start_date=start_date, end_date=end_date, ) except ServiceClassRecord.DoesNotExist: scr = ServiceClassRecord.calculate( group=group, start_date=start_date, end_date=end_date, ) time_range = 'past %s months' % months context = { 'title': "Service classes: %s" % time_range, 'service_classes': service_classes, 'data': scr.data, 'start_date': start_date, 'end_date': end_date, 'updated_at': scr.updated_at, 'version': VERSION, } return render_template('report-service-class.html', **context)
def card_block(key): try: card = Kard.objects.get(key=key) action = 'block' if card.blocked: action = 'unblock' except Kard.DoesNotExist: abort(404) now = datetime.datetime.now() if action == 'block': f = CardBlockForm(request.form, blocked_at=now) if action == 'unblock': f = CardUnblockForm(request.form, unblocked_at=now) if 'cancel' in request.form.keys(): return True # redirect elif request.method == "POST" and f.validate(): if action == 'block': blocked_at = datetime.datetime.combine(f.blocked_at.data, datetime.time()) blocked_at = make_start_date(date=blocked_at) result = card.block(f.reason.data, blocked_at) if result: card.save() flash("%s blocked" % card.key) return True # redirect if action == 'unblock': unblocked_at = datetime.datetime.combine(f.unblocked_at.data, datetime.time()) unblocked_at = make_end_date(date=unblocked_at) result = card.unblock(unblocked_at) if result: card.save() flash("%s unblocked" % card.key) return True # redurect context = { 'title': "%s a card" % (action.capitalize(), ), 'action': action, 'card': card, 'form': f, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('card-block.html', **context)
def main(): oldest_card = Kard.objects.all().order_by('+backlog_date')[0] start_date = make_start_date(date=oldest_card.backlog_date) end_date = make_end_date(date=datetime.datetime.now()) print "Daily records: %s" % DailyRecord.objects.count() print "Creating daily records" print "%s --> %s" % (start_date, end_date) days = end_date - start_date print "Going back %s days" % days.days r = queue_daily_record_updates.apply(args=(days.days, )) r.get() print "DONE!" print "Daily records: %s" % DailyRecord.objects.count()
def report_flow(group="all", months=3): end = kardboard.util.now() months_ranges = month_ranges(end, months) start_day = make_start_date(date=months_ranges[0][0]) end_day = make_end_date(date=end) records = DailyRecord.objects.filter(date__gte=start_day, date__lte=end_day, group=group) chart = {} chart['categories'] = [report.date.strftime("%m/%d") for report in records] series = [ { 'name': "Planning", 'data': [] }, { 'name': "Todo", 'data': [] }, { 'name': "Done", 'data': [] }, ] for row in records: series[0]['data'].append(row.backlog) series[1]['data'].append(row.in_progress) series[2]['data'].append(row.done) chart['series'] = series start_date = records.order_by('date').first().date records.order_by('-date') context = { 'title': "Cumulative Flow", 'updated_at': datetime.datetime.now(), 'chart': chart, 'start_date': start_date, 'flowdata': records, 'version': VERSION, } return render_template('chart-flow.html', **context)
def moving_lead_time(self, year=None, month=None, day=None, weeks=4): """ The moving average of lead time for every day in the last N weeks. """ end_date = make_end_date(year, month, day) start_date = end_date - relativedelta(weeks=weeks) start_date = make_start_date(date=start_date) qs = self.done().filter( done_date__lte=end_date, done_date__gte=start_date, ) average = qs.average('_lead_time') if math.isnan(average): average = 0 return int(round(average))
def capture(klass, group='all'): date = datetime.datetime.now() date = make_end_date(date=date) try: r = klass.objects.get(date=date, group=group) except klass.DoesNotExist: r = klass() r.date = date r.group = group states = States() for state in states: group_cards = ReportGroup( group, Kard.objects.filter(state=state)).queryset r.state_counts[state] = group_cards.count() non_defects = [c for c in group_cards.only('_type') if c.is_card] r.state_card_counts[state] = len(non_defects) r.save() return r
def moving_std_dev(self, year=None, month=None, day=None, weeks=4): """ The moving cycle time standard deviation for every day in the last N weeks. """ end_date = make_end_date(year, month, day) start_date = end_date - relativedelta(weeks=weeks) start_date = make_start_date(date=start_date) qs = self.done().filter( done_date__lte=end_date, done_date__gte=start_date, ).scalar('_cycle_time') cycle_times = [t for t in qs if t is not None] stdev = standard_deviation(cycle_times) if stdev is not None: stdev = int(round(stdev)) else: stdev = 0 return stdev
def state(): date = datetime.datetime.now() date = make_end_date(date=date) states = States() board = DisplayBoard() # defaults to all teams, 7 days of done board.cards # force the card calculation board.rows title = app.config.get('SITE_NAME') wip_cards = [k for k in board.cards if k.state in states.in_progress] done_this_week = [k for k in board.cards if k.state == states.done] blocked = [k for k in wip_cards if k.blocked] metrics = [ { 'WIP': len(wip_cards) }, { 'Blocked': len(blocked) }, { 'Done this week': len(done_this_week) }, ] context = { 'title': title, 'board': board, 'states': states, 'metrics': metrics, 'date': date, 'updated_at': datetime.datetime.now(), 'version': VERSION, } return render_template('team.html', **context)
def moving_cycle_time(self, year=None, month=None, day=None, weeks=4): """ The moving average of cycle time for every day in the last N weeks. """ end_date = make_end_date(year, month, day) start_date = end_date - relativedelta(weeks=weeks) start_date = make_start_date(date=start_date) qs = self.done().filter( done_date__lte=end_date, done_date__gte=start_date, ) try: average = qs.average('_cycle_time') except TypeError: average = float('nan') if math.isnan(average): return 0 return int(round(average))
def report_cycle_distribution(group="all", months=3, limit=None): from kardboard.services.reports import CycleTimeDistribution defects_only, cards_only = False, False if limit == 'cards': cards_only = True if limit == 'defects': defects_only = True today = datetime.datetime.today() start_day = today - relativedelta.relativedelta(months=months) start_day = make_start_date(date=start_day) end_day = make_end_date(date=today) context = { 'title': "How quick can we do it?", 'updated_at': datetime.datetime.now(), 'version': VERSION, } query = Q(done_date__gte=start_day) & Q(done_date__lte=end_day) if defects_only: query = query & Q(_type__in=app.config.get('DEFECT_TYPES', [])) elif cards_only: query = query & Q(_type__nin=app.config.get('DEFECT_TYPES', [])) rg = ReportGroup(group, Kard.objects.filter(query)) cards = list(rg.queryset) total = len(cards) if total == 0: context = { 'error': "Zero cards were completed in the past %s months" % months, } return render_template('report-cycle-distro.html', **context) cdr = CycleTimeDistribution(cards=cards) chart = {} chart['categories'] = cdr.days() chart['series'] = [] service_class_series = cdr.service_class_series() sclasses = service_class_series.keys() sclasses.sort() for sclass in sclasses: seri = service_class_series[sclass] chart['series'].append(dict(name=sclass, data=seri)) context = { 'histogram_data': cdr.histogram(), 'chart': chart, 'title': "How quick can we do it?", 'months': months, 'total': total, 'updated_at': datetime.datetime.now(), 'version': VERSION, } if defects_only: context['title'] = "Defects: %s" % (context['title']) context['card_type'] = 'defects' elif cards_only: context['title'] = "Cards: %s" % (context['title']) context['card_type'] = 'cards' else: context['title'] = "All: %s" % (context['title']) context['card_type'] = 'cards and defects' return render_template('report-cycle-distro.html', **context)