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 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 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 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 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 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 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 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 value_txt_report(year_number, month_number, group="all"): start_date = munge_date(year=year_number, month=month_number, day=1) start_date = make_start_date(date=start_date) start_date, end_date = month_range(start_date) rg = ReportGroup(group, Kard.objects.done()) done = rg.queryset cards = done.filter(done_date__gte=start_date, done_date__lte=end_date).order_by('-done_date') cards = [c for c in cards if c.is_card] context = { 'title': "Completed Value Cards", 'cards': cards, 'start_date': start_date, 'end_date': end_date, 'updated_at': datetime.datetime.now(), 'version': VERSION, } response = make_response(render_template('done-report.txt', **context)) response.headers['Content-Type'] = "text/plain" return response
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 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 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 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 _get_time_range(weeks): end = make_end_date( date=datetime.now() ) start = make_start_date( date=end - relativedelta(weeks=weeks) ) return start, end
def daily_throughput_average(report_group_slug, stop, weeks=4): start = (stop - relativedelta(days=weeks * 7)) + relativedelta(days=1) start = make_start_date(date=start) query = Kard.objects.filter(done_date__gte=start, done_date__lte=stop) kards = list(ReportGroup(report_group_slug, query).queryset) return len(kards) / float(weeks * 7)
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 started_after_report(team_or_rg_name, start_date): start_date = make_start_date(date=start_date) cards = _get_cards(team_or_rg_name, start_date) columns = ( 'key', 'team', 'service_class', 'type', 'start_date', 'done_date', 'due_date', 'cycle_time', 'is_wip', 'hit_due_date', 'hit_sla', 'time_in_otis', 'time_wait_qa', 'time_in_qa', 'time_building', 'time_elabo', ) Row = namedtuple('Row', ' '.join(columns)) rows = [] for c in cards: row = Row( key=c.key, team=c.team, service_class=c.service_class['name'], type=_card_type(c), start_date=_format_date_or_empty(c.start_date), done_date=_format_date_or_empty(c.done_date), due_date=_format_date_or_empty(c.due_date), cycle_time=c.cycle_time or c.current_cycle_time(), is_wip=(c.done_date is None), hit_due_date=_hit_due_date(c), hit_sla=_hit_sla(c), time_in_otis=_time_in_otis(c), time_wait_qa=_time_wait_qa(c), time_in_qa=_time_in_qa(c), time_building=_time_building(c), time_elabo=_time_elabo(c), ) rows.append(row) rows.insert(0, columns) report_filename = "%s_%s.csv" % ( start_date.strftime('%Y-%m-%d'), slugify(team_or_rg_name) ) writer = csv.writer( open(report_filename, 'w'), ) writer.writerows(rows)
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 daily_throughput_average(report_group_slug, stop, weeks=4): start = (stop - relativedelta(days=weeks * 7)) + relativedelta(days=1) start = make_start_date(date=start) query = Kard.objects.filter( done_date__gte=start, done_date__lte=stop,) kards = list(ReportGroup(report_group_slug, query).queryset) return len(kards) / float(weeks * 7)
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_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 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 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 year_std_dev(year): year_start = make_start_date(date=datetime(year, 1, 1)) year_end = make_end_date(date=datetime(year, 12, 31)) rg = ReportGroup('dev', Kard.objects.filter(start_date__gte=year_start, done_date__lte=year_end)) cards = [c for c in rg.queryset if c.is_card] cycle_times = [c.cycle_time for c in cards] data = {} data['n'] = len(cards) data['ave'] = average(cycle_times) data['std'] = standard_deviation(cycle_times) return data
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 report_cycle_distribution(group="all", months=3): ranges = ( (0, 4, "Less than 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("report-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) pct = round(pct, 2) distro.append((label, pct)) chart = {} chart["data"] = distro context = { "data": distro, "chart": chart, "title": "How quick can we do it?", "updated_at": datetime.datetime.now(), "version": VERSION, } return render_template("report-cycle-distro.html", **context)
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 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 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 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 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 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 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 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 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 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 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 _get_time_range(weeks, start=None, end=None): if end is None: end = datetime.now() if start is None: start = end - relativedelta(weeks=weeks) return make_start_date(date=start), make_end_date(date=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_cycle_distribution(group="all", months=3, limit=None): defects_only, cards_only = False, False if limit == 'cards': cards_only = True if limit == 'defects': defects_only = True ranges = ( (0, 4, "Less than 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) 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)) total = rg.queryset.count() if total == 0: context = { 'error': "Zero cards were completed in the past %s months" % months } return render_template('report-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) if defects_only: query = query & Q(_type__in=app.config.get('DEFECT_TYPES', [])) else: query = query & Q(_type__nin=app.config.get('DEFECT_TYPES', [])) pct = ReportGroup( group, Kard.objects.filter(query)).queryset.count() / float(total) pct = round(pct, 2) distro.append((label, pct)) chart = {} chart['data'] = distro context = { 'data': distro, '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)
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)