def get_data(self): startkey = [self.domain, self.active_location._id if self.active_location else None] product_cases = SPPCase.view('commtrack/product_cases', startkey=startkey, endkey=startkey + [{}], include_docs=True) if self.program_id: product_cases = filter(lambda c: Product.get(c.product).program_id == self.program_id, product_cases) def latest_case(cases): # getting last report date should probably be moved to a util function in a case wrapper class return max(cases, key=lambda c: getattr(c, 'last_reported', datetime(2000, 1, 1)).date()) cases_by_site = map_reduce(lambda c: [(tuple(c.location_),)], lambda v: reporting_status(latest_case(v), self.start_date, self.end_date), data=product_cases, include_docs=True) # TODO if aggregating, won't want to fetch all these locs (will only want to fetch aggregation sites) locs = dict((loc._id, loc) for loc in Location.view( '_all_docs', keys=[path[-1] for path in cases_by_site.keys()], include_docs=True)) for path, status in cases_by_site.iteritems(): loc = locs[path[-1]] yield { 'loc_id': loc._id, 'loc_path': loc.path, 'name': loc.name, 'type': loc.location_type, 'reporting_status': status, 'geo': loc._geopoint, }
def make_supply_point_product(supply_point_case, product_uuid, owner_id=None): domain = supply_point_case.domain id = uuid.uuid4().hex user_id = const.get_commtrack_user_id(domain) owner_id = owner_id or get_owner_id(supply_point_case) or user_id username = const.COMMTRACK_USERNAME product_name = Product.get(product_uuid).name caseblock = CaseBlock( case_id=id, create=True, version=V2, case_name=product_name, user_id=user_id, owner_id=owner_id, case_type=const.SUPPLY_POINT_PRODUCT_CASE_TYPE, update={ "product": product_uuid }, index={ const.PARENT_CASE_REF: (const.SUPPLY_POINT_CASE_TYPE, supply_point_case._id), } ) casexml = ElementTree.tostring(caseblock.as_xml()) submit_case_blocks(casexml, domain, username, user_id, xmlns=const.COMMTRACK_SUPPLY_POINT_PRODUCT_XMLNS) sppc = SupplyPointProductCase.get(id) sppc.bind_to_location(supply_point_case.location) sppc.save() return sppc
def make_supply_point_product(supply_point_case, product_uuid, owner_id=None): domain = supply_point_case.domain id = uuid.uuid4().hex user_id = get_commtrack_user_id(domain) owner_id = owner_id or get_owner_id(supply_point_case) or user_id username = const.COMMTRACK_USERNAME product_name = Product.get(product_uuid).name caseblock = CaseBlock(case_id=id, create=True, version=V2, case_name=product_name, user_id=user_id, owner_id=owner_id, case_type=const.SUPPLY_POINT_PRODUCT_CASE_TYPE, update={"product": product_uuid}, index={ const.PARENT_CASE_REF: (const.SUPPLY_POINT_CASE_TYPE, supply_point_case._id), }) casexml = ElementTree.tostring(caseblock.as_xml()) submit_case_blocks(casexml, domain, username, user_id, xmlns=const.COMMTRACK_SUPPLY_POINT_PRODUCT_XMLNS) return SupplyPointProductCase.get(id)
def get_data(self, slugs=None): startkey = [self.domain, self.active_location._id if self.active_location else None] if self.active_product: startkey.append(self.active_product['_id']) product_cases = SPPCase.view('commtrack/product_cases', startkey=startkey, endkey=startkey + [{}], include_docs=True) if self.config.get('aggregate'): return self.aggregate_cases(product_cases, slugs) else: return self.raw_cases(product_cases, slugs)
def aggregate_cases(self, product_cases, slugs): cases_by_product = map_reduce(lambda c: [(c.product,)], data=product_cases, include_docs=True) products = Product.view('_all_docs', keys=cases_by_product.keys(), include_docs=True) def _sum(vals): return sum(vals) if vals else None def aggregate_product(cases): data = [(c.current_stock_level, c.monthly_consumption) for c in cases if is_timely(c, 1000)] total_stock = _sum([d[0] for d in data if d[0] is not None]) total_consumption = _sum([d[1] for d in data if d[1] is not None]) # exclude stock values w/o corresponding consumption figure from total months left calculation consumable_stock = _sum([d[0] for d in data if d[0] is not None and d[1] is not None]) return { 'total_stock': total_stock, 'total_consumption': total_consumption, 'consumable_stock': consumable_stock, } status_by_product = dict((p, aggregate_product(cases)) for p, cases in cases_by_product.iteritems()) for p in sorted(products, key=lambda p: p.name): stats = status_by_product[p._id] months_left = SPPCase.months_of_stock_remaining(stats['consumable_stock'], stats['total_consumption']) category = SPPCase.stock_category(stats['total_stock'], stats['total_consumption'], stats['consumable_stock']) full_output = { self.SLUG_PRODUCT_NAME: p.name, self.SLUG_PRODUCT_ID: p._id, self.SLUG_LOCATION_ID: self.active_location._id if self.active_location else None, self.SLUG_LOCATION_LINEAGE: self.active_location.lineage if self.active_location else None, self.SLUG_CURRENT_STOCK: stats['total_stock'], self.SLUG_CONSUMPTION: stats['total_consumption'], self.SLUG_MONTHS_REMAINING: months_left, self.SLUG_CATEGORY: category, } yield dict((slug, full_output['slug']) for slug in slugs) if slugs else full_output
def get_prod_data(self): startkey = [self.domain, self.active_location._id if self.active_location else None] product_cases = SPPCase.view('commtrack/product_cases', startkey=startkey, endkey=startkey + [{}], include_docs=True) cases_by_product = map_reduce(lambda c: [(c.product,)], data=product_cases, include_docs=True) products = Product.view('_all_docs', keys=cases_by_product.keys(), include_docs=True) def _sum(vals): return sum(vals) if vals else None def aggregate_product(cases): data = [(c.current_stock_level, c.monthly_consumption) for c in cases if is_timely(c, 1000)] total_stock = _sum([d[0] for d in data if d[0] is not None]) total_consumption = _sum([d[1] for d in data if d[1] is not None]) # exclude stock values w/o corresponding consumption figure from total months left calculation consumable_stock = _sum([d[0] for d in data if d[0] is not None and d[1] is not None]) return { 'total_stock': total_stock, 'total_consumption': total_consumption, 'consumable_stock': consumable_stock, } status_by_product = dict((p, aggregate_product(cases)) for p, cases in cases_by_product.iteritems()) for p in sorted(products, key=lambda p: p.name): stats = status_by_product[p._id] months_left = SPPCase.months_of_stock_remaining(stats['consumable_stock'], stats['total_consumption']) category = SPPCase.stock_category(stats['total_stock'], stats['total_consumption'], stats['consumable_stock']) yield [ p.name, stats['total_stock'], stats['total_consumption'], months_left, category, ]
def attach_locations(xform, cases): """ Given a received form and cases, update the location of that form to the location of its cases (if they have one). """ # todo: this won't change locations if you are trying to do that via XML. # this is mainly just a performance thing so you don't have to do extra lookups # every time you touch a case if cases: found_loc = None for case in cases: loc = None if not case.location_: if case.type == const.SUPPLY_POINT_CASE_TYPE: loc_id = getattr(case, 'location_id', None) if loc_id: loc = Location.get(loc_id) case.bind_to_location(loc) elif case.type == const.SUPPLY_POINT_PRODUCT_CASE_TYPE: wrapped_case = SupplyPointProductCase.wrap(case._doc) sp = wrapped_case.get_supply_point_case() if sp and sp.location_: loc = sp.location_ case.location_ = loc elif case.type == const.REQUISITION_CASE_TYPE: req = RequisitionCase.wrap(case._doc) prod = req.get_product_case() if prod and prod.location_ and prod.location_ != case.location_: case.location_ = prod.location_ case.save() if loc and found_loc and loc != found_loc: raise Exception( 'Submitted a commtrack case with multiple locations in a single form. ' 'This is currently not allowed.' ) found_loc = loc case = cases[0] if case.location_ is not None: # should probably store this in computed_ xform.location_ = list(case.location_)
def _data(self): startkey = [self.domain, self.active_location._id if self.active_location else None] product_cases = SPPCase.view('commtrack/product_cases', startkey=startkey, endkey=startkey + [{}], include_docs=True) def latest_case(cases): # getting last report date should probably be moved to a util function in a case wrapper class return max(cases, key=lambda c: getattr(c, 'last_reported', datetime(2000, 1, 1)).date()) cases_by_site = map_reduce(lambda c: [(tuple(c.location_),)], lambda v: reporting_status(latest_case(v)), data=product_cases, include_docs=True) def child_loc(path): root = self.active_location ix = path.index(root._id) if root else -1 try: return path[ix + 1] except IndexError: return None def case_iter(): for k, v in cases_by_site.iteritems(): if child_loc(k) is not None: yield (k, v) status_by_agg_site = map_reduce(lambda (path, status): [(child_loc(path), status)], data=case_iter()) sites_by_agg_site = map_reduce(lambda (path, status): [(child_loc(path), path[-1])], data=case_iter()) def status_tally(statuses): total = len(statuses) return map_reduce(lambda s: [(s,)], lambda v: {'count': len(v), 'pct': len(v) / float(total)}, data=statuses) status_counts = dict((loc_id, status_tally(statuses)) for loc_id, statuses in status_by_agg_site.iteritems()) master_tally = status_tally(cases_by_site.values()) locs = sorted(Location.view('_all_docs', keys=status_counts.keys(), include_docs=True), key=lambda loc: loc.name) def fmt(pct): return '%.1f%%' % (100. * pct) def fmt_col(loc, col_type): return fmt(status_counts[loc._id].get(col_type, {'pct': 0.})['pct']) def _rows(): for loc in locs: num_sites = len(sites_by_agg_site[loc._id]) yield [loc.name, len(sites_by_agg_site[loc._id])] + [fmt_col(loc, k) for k in ('ontime', 'late', 'nonreporting')] return master_tally, _rows()
def get_prod_data(self): startkey = [self.domain, self.active_location._id if self.active_location else None] product_cases = SPPCase.view('commtrack/product_cases', startkey=startkey, endkey=startkey + [{}], include_docs=True) cases_by_product = map_reduce(lambda c: [(c.product,)], data=product_cases, include_docs=True) products = Product.view('_all_docs', keys=cases_by_product.keys(), include_docs=True) def status(case): return case.current_stock_category if is_timely(case, 1000) else 'nonreporting' status_by_product = dict((p, map_reduce(lambda c: [(status(c),)], len, data=cases)) for p, cases in cases_by_product.iteritems()) cols = ['stockout', 'understock', 'adequate', 'overstock', 'nodata'] #'nonreporting', 'nodata'] for p in sorted(products, key=lambda p: p.name): cases = cases_by_product.get(p._id, []) results = status_by_product.get(p._id, {}) def val(key): return results.get(key, 0) / float(len(cases)) yield [p.name, len(cases)] + [100. * val(key) for key in cols]
def requisition_case_id(self): # for somewhat obscure reasons, the case_id is the id of the # supply_point_product case, so we add a new field for this. # though for newly created requisitions it's just empty if self.base_action_type == RequisitionActions.REQUEST: return None if self.base_action_type == RequisitionActions.RECEIPTS: # for receipts the id should point to the most recent open requisition # (or none) try: product_stock_case = SupplyPointProductCase.get(self.case_id) return RequisitionCase.open_for_product_case( self.domain, product_stock_case.location_[-1], self.case_id )[0] except IndexError: # there was no open requisition. this is ok return None assert False, "%s is an unexpected action type!" % self.base_action_type
def requisition_case_id(self): # for somewhat obscure reasons, the case_id is the id of the # supply_point_product case, so we add a new field for this. # though for newly created requisitions it's just empty if self.base_action_type == RequisitionActions.REQUEST: return None if self.base_action_type == RequisitionActions.RECEIPTS: # for receipts the id should point to the most recent open requisition # (or none) try: product_stock_case = SupplyPointProductCase.get(self.case_id) return RequisitionCase.open_for_product_case( self.domain, product_stock_case.location_[-1], self.case_id)[0] except IndexError: # there was no open requisition. this is ok return None assert False, "%s is an unexpected action type!" % self.base_action_type