def fmt_time(dt): h_ago = int(round(delta_secs(datetime.now() - dt) / 3600.)) days = h_ago / 24 hours = h_ago % 24 str_ago = ', '.join(filter(lambda s: s, [ '%d day%s' % (days, 's' if days != 1 else '') if days > 0 else '', '%d hour%s' % (hours, 's' if hours != 1 else '') if (hours > 0 and days < 4) or days == 0 else '' ])) return {'date': dt, 'ago': '%s ago' % str_ago}
def daily_consumption(supply_point, product, datespan=None, consumption_settings=None): """ Calculate daily consumption through the following algorithm: Consider each non-stockout SOH report to be the start of a period. We iterate through the stock transactions following it until we reach another SOH. If it's a stockout, we drop the period from the calculation. We keep track of the total receipts in each period and add them to the start quantity. The total quantity consumed is: (Start SOH Quantity + SUM(receipts during period) - End SOH Quantity) We add the quantity consumed and the length of the period to the running count, then at the end divide one by the other. This algorithm effectively deals with cases where a SOH report immediately follows a receipt. """ from logistics.models import StockTransaction consumption_settings = consumption_settings or ConsumptionSettings.default( ) total_time = timedelta(0) total_consumption = 0 txs = StockTransaction.objects.filter\ (supply_point=supply_point,product=product).order_by('-date') if datespan: txs = txs.filter(date__gte=datespan.startdate, date__lte=datespan.enddate) if txs.count() < consumption_settings.min_transactions: return None period_receipts = 0 end_transaction = None for t in txs: # Go through each StockTransaction in turn (backwards!). if t.ending_balance == 0 and not consumption_settings.include_end_stockouts: # Previous period ended in stockout -- pass on this period end_transaction = None period_receipts = 0 continue if t.product_report.report_type.code == Reports.SOH: if end_transaction: # End of a period. if t.ending_balance + period_receipts >= end_transaction.ending_balance: # if this check fails it's an anomalous data point # (finished with higher stock than possible) # Add the period stats to the running count. # But first scale them if they fall within the cutoff window period_time = (end_transaction.date - t.date) period_consumption = t.ending_balance + period_receipts - end_transaction.ending_balance scaling_factor = 1 if consumption_settings.cutoff_date < t.date \ else max(0, delta_secs(end_transaction.date - consumption_settings.cutoff_date) \ / delta_secs(period_time)) total_time += timedelta(seconds=scaling_factor * delta_secs(period_time)) total_consumption += scaling_factor * period_consumption if t.date < consumption_settings.cutoff_date: break else: # Start a new period. end_transaction = t period_receipts = 0 elif t.product_report.report_type.code == Reports.REC: # Receipt. if end_transaction: # Mid-period receipt, so we care about it. period_receipts += t.quantity days = total_time.days if days < consumption_settings.min_days: return None return round( abs((float(total_consumption) / delta_secs(total_time)) * 60 * 60 * 24), 2)
def daily_consumption(supply_point, product, datespan=None, consumption_settings=None): """ Calculate daily consumption through the following algorithm: Consider each non-stockout SOH report to be the start of a period. We iterate through the stock transactions following it until we reach another SOH. If it's a stockout, we drop the period from the calculation. We keep track of the total receipts in each period and add them to the start quantity. The total quantity consumed is: (Start SOH Quantity + SUM(receipts during period) - End SOH Quantity) We add the quantity consumed and the length of the period to the running count, then at the end divide one by the other. This algorithm effectively deals with cases where a SOH report immediately follows a receipt. """ from logistics.models import StockTransaction consumption_settings = consumption_settings or ConsumptionSettings.default() total_time = timedelta(0) total_consumption = 0 txs = StockTransaction.objects.filter\ (supply_point=supply_point,product=product).order_by('-date') if datespan: txs = txs.filter(date__gte=datespan.startdate, date__lte=datespan.enddate) if txs.count() < consumption_settings.min_transactions: return None period_receipts = 0 end_transaction = None for t in txs: # Go through each StockTransaction in turn (backwards!). if t.ending_balance == 0 and not consumption_settings.include_end_stockouts: # Previous period ended in stockout -- pass on this period end_transaction = None period_receipts = 0 continue if t.product_report.report_type.code == Reports.SOH: if end_transaction: # End of a period. if t.ending_balance + period_receipts >= end_transaction.ending_balance: # if this check fails it's an anomalous data point # (finished with higher stock than possible) # Add the period stats to the running count. # But first scale them if they fall within the cutoff window period_time = (end_transaction.date - t.date) period_consumption = t.ending_balance + period_receipts - end_transaction.ending_balance scaling_factor = 1 if consumption_settings.cutoff_date < t.date \ else max(0, delta_secs(end_transaction.date - consumption_settings.cutoff_date) \ / delta_secs(period_time)) total_time += timedelta(seconds=scaling_factor * delta_secs(period_time)) total_consumption += scaling_factor * period_consumption if t.date < consumption_settings.cutoff_date: break else: # Start a new period. end_transaction = t period_receipts = 0 elif t.product_report.report_type.code == Reports.REC: # Receipt. if end_transaction: # Mid-period receipt, so we care about it. period_receipts += t.quantity days = total_time.days if days < consumption_settings.min_days: return None return round(abs((float(total_consumption) / delta_secs(total_time)) * 60*60*24),2)