def report2(self, ending=None, step=1, length=12, billed=True): total = [] ending = parse_date(ending) for date in iter_dates(ending, step, length): total = [(date[0], self.period_report(date, fields, billed)),] + total return total
def bc(self, date=None, step=1, length=12, invalidate=None): """ Given a starting date, walk back 'length' in time, and return BC-based results for a given model aggregating month values at each 'step'. eg. model.months.bc('2011.07', 1, 12) Returns BC values for the 12 months before July 2011. model.months.bc('2011.08', 3, 12) Returns BC values for 4 quarters ending in August 2011. """ # setup date date = parse_date(date, self.latest) year, month = date.year, date.month # setup cache key and try to fetch result datestamp = date.strftime('%Y.%m') cache_key = 'months.bc.%s.len%s.step%s' % (datestamp, length, step) if invalidate is None: invalidate = self.invalidate_flag if invalidate: final = self.instance.del_cache(cache_key) else: final = self.instance.get_cache(cache_key) # build result if not final: final = {self.instance.pk: { 'json': {}, 'name': self.instance.name, 'step': step, 'length': length, }} # set the 'length' to no less than 'step' to prevent empty results length = step if step > length else length for date_to, date_from in iter_dates(date, step, length): result = self.bc_single(date_to, date_from, invalidate) datestamp = date_to.strftime("%Y.%m") final[self.instance.pk]['json'].update({datestamp: result}) return final
def invalidate(self, date=None): if not date: date = Entry.objects.latest().date for x, y in iter_dates(date=date, length=15, as_instance=True): datestamp = '%s.%02d' % (x.year, x.month) cc = ( 'months.bc.single.', 'months.bc.', ) for c in cc: self.instance.del_cache(c + datestamp) # set this flag to pass to each function with params once. self.invalidate_flag = True
def _value(self, date=None, step=1, length=12, agg=False, qs_filter=None): result = {} dates = [] date = parse_date(date, Entry.objects.latest().date) for before, after in iter_dates(date, step, length): try: qs = self.qs if qs_filter is not None: qs = self.qs.filter(qs_filter) qs = qs.filter(date__lte=before, date__gte=after) except ObjectDoesNotExist: qs = Entry.objects.none() key = '%s.%02d' % (before.year, before.month) dates += [key] # yield a value for each month if not agg: value = self.queryset(before.year, before.month) else: value = qs.aggregate(Sum('value'))['value__sum'] result.update({key: value}) if not value: result.update({key: {}}) nozero = lambda x: Decimal(0) if not x else x if not agg: base = SortedDict({}) for d, lines in result.items(): for line, value in lines.items(): current = base.get(line, SortedDict({})) current.update({d: nozero(value)}) base.update({line: current}) # base = {k: {kk:vv for kk, nozero(vv) in v} for k, v in result.items()} else: base = {k: v for k, v in result.items()} for k, v in base.items(): yield {k: v}
def report(self, ending=None, step=1, length=12, fields=None, billed=True): if step > length: length = step total = {} extracted = {} ending = parse_date(ending) print (ending, step, length) for date in iter_dates(ending, step, length): # [{'code': 123, 'value': 10}, {'code': 234, 'value': 2}] period_report = self.period_report(date, fields=fields, billed=billed) # {'123': {'value': 10}, '234': {'value': 2}} period_formatted = {} for d in period_report: # d means dict for key in ('billed_corp', 'billed_cons', 'total'): # let's convert these to floats, adding 0s where needed if not d.get(key, None): d[key] = 0.0 else: d[key] = float(d[key]) d['billed'] = d.get('billed_corp', 0.0) + d.get('billed_cons', 0.0) if d['billed_cons'] == 0.0 or d['billed_corp'] == 0.0: cons_index = d['cons_index'] else: try: cons_index = d['billed_cons'] / d['billed'] except ZeroDivisionError: cons_index = 0.5 corp_index = 1 - cons_index d['corp'] = d['total'] * corp_index d['cons'] = d['total'] * cons_index d['cons_index'] = cons_index d['corp_index'] = corp_index # period_formatted[acc]['json'].update() # fields is a dict that contains a mapping of the extra # fields to be include and a choice of field name # represented by an F object or a permanent value # # these are all valid: # {'parent_id': F('group__parent_id')} # {'parent_id': 9} # {'level': 'foo'} # code = d.pop('code') total[code] = total.get(code, {'json': {}}) if fields is not None: for key, value in fields.items(): if isinstance(value, F): total[code][key] = d.get(value.name) else: total[code][key] = value nice_date = date[1].strftime("%Y-%m") # period_formatted[code][nice_date] = d total[code]['json'][nice_date] = d return total
def bc(self, date=None, step=1, length=12, invalidate=None): "Get BC-indexed results for every child." # setup date date = parse_date(date, self.latest) year, month = date.year, date.month # setup cache key and try to fetch result datestamp = date.strftime('%Y.%m') # cache_key = 'months.bc.' + datestamp cache_key = 'months.bc.%s.len%s.step%s' % (datestamp, length, step) if invalidate is None: invalidate = self.invalidate_flag if invalidate: final = self.instance.del_cache(cache_key) else: final = self.instance.get_cache(cache_key) if not final: final = {'step': step, 'length': length, 'json': {}} # set the 'length' to no less than 'step' to prevent empty results length = step if step > length else length # fill in placeholders for the result keys = ['cons_sum', 'cons_index', 'cons_result', 'corp_sum', 'corp_index', 'corp_result', 'total'] result = {} empty_dict = {} for yielded, nothing in iter_dates(date, step, length): datestr = yielded.strftime('%Y.%m') empty_dict[datestr] = {} for k in keys: empty_dict[datestr].update({k: 0.0}) # descendants = self.instance.get_descendants(include_self=True)\ # .values('pk', 'name') # descendants_pk = [pk for pk, name in [obj.values() for obj in descendants]] # desc = [(pk, name) for pk, name in [obj.values() for obj in descendants]] descendants = self.instance.get_descendants(include_self=True) descendants_pk = [] from copy import deepcopy for group in descendants: descendants_pk += [group.pk] result.update({group.pk: { 'json': deepcopy(empty_dict), 'name':group.name, 'lft':group.lft, 'group_id': group.pk, 'parent_id': group.parent_id, 'level': group.level }}) # fill in results from accounts for acc in self.accounts: acc_result = acc.months.bc(date, step, length, invalidate) # FIXME: is this the best way to pop 'length' and 'date' # out of the account dict. acc_result[acc.pk].pop('step') acc_result[acc.pk].pop('length') acc_result[acc.pk].update({ 'level': acc.group.level +1, 'lft': 9000, 'parent_id': acc.group_id, 'group_id': acc.group_id, }) result.update(**acc_result) # ----------------------------------------------------------- # for each group to which this account is a descendant of, # add the account's value to that group's result # some of the account's groups might be above self.instance, # so filter on descendants too _groups = acc.groups().filter(pk__in=descendants_pk).values_list('pk', flat=True) for pk in _groups: group_result = result[pk]['json'] for d, values in group_result.items(): for value_name in values: try: v = float(acc_result[acc.pk]['json'][d][value_name]) except: v = 0.0 vv = result[pk]['json'][d][value_name] result[pk]['json'][d][value_name] = vv + v # get account groups only # regex = re.compile('\d+') # for pk in [k for k in result.keys() if regex.match(k)]: # iterator = [obj, obj['json'] for obj in result] for pk, json in [(obj[0], obj[1]['json']) for obj in result.items()]: for d, values in json.items(): # cast these string back to float first cons_sum = float(values['cons_sum']) corp_sum = float(values['corp_sum']) cons_result = float(values['cons_result']) corp_result = float(values['corp_result']) bc_total = cons_sum + corp_sum total = float(values['total']) try: cons_index = cons_sum / bc_total except ZeroDivisionError: cons_index = 0 try: corp_index = corp_sum / bc_total except ZeroDivisionError: corp_index = 0 if corp_index < 0 or cons_index > 1: corp_index = 0 cons_index = 1 elif cons_index < 0 or corp_index > 1: cons_index = 0 corp_index = 1 result[pk]['json'][d].update({ "cons_sum": '%02.3f' % cons_sum, "cons_index": '%02.2f' % cons_index, "cons_result": '%02.3f' % cons_result, "corp_sum": '%02.3f' % corp_sum, "corp_index": '%02.2f' % corp_index, "corp_result": '%02.3f' % corp_result, "total": '%02.3f' % total, }) self.result = result return self.result