Пример #1
0
class VoucherUpdate(jsonobject.JsonObject):
    voucher_id = jsonobject.StringProperty(required=True)
    payment_status = jsonobject.StringProperty(required=True,
                                               choices=['success', 'failure'])
    payment_amount = jsonobject.DecimalProperty(required=False)
    failure_description = jsonobject.StringProperty(required=False)

    case_type = CASE_TYPE_VOUCHER

    @property
    def case_id(self):
        return self.voucher_id

    @property
    def properties(self):
        if self.payment_status == 'success':
            return {
                'state': 'paid',
                'amount_fulfilled': self.payment_amount,
                'date_fulfilled':
                datetime.datetime.utcnow().date().isoformat(),
            }
        else:
            return {
                'state': 'rejected',
                'reason_rejected': self.failure_description,
                'date_rejected': datetime.datetime.utcnow().date().isoformat(),
            }
Пример #2
0
class AggregationSpec(jsonobject.JsonObject):
    domain = jsonobject.StringProperty(required=True)
    table_id = jsonobject.StringProperty(required=True)
    display_name = jsonobject.StringProperty()
    primary_table = jsonobject.ObjectProperty(PrimaryTableSpec)
    time_aggregation = jsonobject.ObjectProperty(TimeAggregationConfigSpec)
    secondary_tables = jsonobject.ListProperty(SecondaryTableSpec)
Пример #3
0
class IncentiveUpdate(PaymentUpdate):
    eventType = jsonobject.StringProperty(required=True, choices=['Incentive'])
    eventID = jsonobject.StringProperty(
        required=False, choices=BETS_EVENT_IDS)
    case_type = CASE_TYPE_EPISODE

    @property
    def properties(self):
        status_key = 'tb_incentive_{}_status'.format(self.eventID)
        comments_key = 'tb_incentive_{}_comments'.format(self.eventID)
        if self.status == SUCCESS:
            amount_key = 'tb_incentive_{}_amount'.format(self.eventID)
            date_key = 'tb_incentive_{}_payment_date'.format(self.eventID)
            payment_mode_key = 'tb_incentive_{}_payment_mode'.format(self.eventID)
            check_number_key = 'tb_incentive_{}_check_number'.format(self.eventID)
            bank_name_key = 'tb_incentive_{}_bank_name'.format(self.eventID)
            return {
                status_key: 'paid',
                amount_key: self.amount,
                date_key: self.paymentDate.isoformat(),
                comments_key: self.comments or "",
                payment_mode_key: self.paymentMode or "",
                check_number_key: self.checkNumber or "",
                bank_name_key: self.bankName or "",
            }
        else:
            date_key = 'tb_incentive_{}_rejection_date'.format(self.eventID)
            reason_key = 'tb_incentive_{}_rejection_reason'.format(self.eventID)
            return {
                status_key: 'rejected',
                date_key: self.paymentDate.isoformat(),
                reason_key: self.failureDescription or "",
                comments_key: self.comments or "",
            }
Пример #4
0
class ChangeMeta(jsonobject.JsonObject):
    """
    Metadata about a change. If available, this will be set on Change.metadata.

    This is only used in kafka-based pillows.
    """
    _allow_dynamic_properties = False

    document_id = DefaultProperty(required=True)

    # Only relevant for Couch documents
    document_rev = jsonobject.StringProperty()

    # 'couch' or 'sql'
    data_source_type = jsonobject.StringProperty(required=True)

    # couch database name or one of data sources listed in corehq.apps.change_feed.data_sources
    data_source_name = jsonobject.StringProperty(required=True)

    # doc_type property of doc or else the topic name
    document_type = DefaultProperty()

    document_subtype = jsonobject.StringProperty()
    domain = jsonobject.StringProperty()
    is_deletion = jsonobject.BooleanProperty()
    publish_timestamp = jsonobject.DateTimeProperty(default=datetime.utcnow)

    # track of retry attempts
    attempts = jsonobject.IntegerProperty(default=0)
Пример #5
0
class ElasticsearchIndexInfo(jsonobject.JsonObject):
    index = jsonobject.StringProperty(required=True)
    alias = jsonobject.StringProperty()
    type = jsonobject.StringProperty()
    mapping = jsonobject.DictProperty()
    hq_index_name = jsonobject.StringProperty()

    def __str__(self):
        return '{} ({})'.format(self.alias, self.index)

    @property
    def meta(self):
        meta_settings = deepcopy(ES_INDEX_SETTINGS['default'])
        meta_settings.update(ES_INDEX_SETTINGS.get(self.hq_index_name, {}))
        meta_settings.update(
            ES_INDEX_SETTINGS.get(settings.SERVER_ENVIRONMENT,
                                  {}).get(self.hq_index_name, {}))

        if settings.ES_SETTINGS is not None:
            for hq_index_name in ['default', self.hq_index_name]:
                for key, value in settings.ES_SETTINGS.get(hq_index_name,
                                                           {}).items():
                    if value is REMOVE_SETTING:
                        del meta_settings['settings'][key]
                    else:
                        meta_settings['settings'][key] = value

        return meta_settings

    def to_json(self):
        json = super(ElasticsearchIndexInfo, self).to_json()
        json['meta'] = self.meta
        return json
Пример #6
0
class XFormPhoneMetadata(jsonobject.JsonObject):
    """
    Metadata of an xform, from a meta block structured like:

        <Meta>
            <timeStart />
            <timeEnd />
            <instanceID />
            <userID />
            <deviceID />
            <username />

            <!-- CommCare extension -->
            <appVersion />
            <location />
        </Meta>

    See spec: https://bitbucket.org/javarosa/javarosa/wiki/OpenRosaMetaDataSchema

    username is not part of the spec but included for convenience
    """

    timeStart = jsonobject.DateTimeProperty()
    timeEnd = jsonobject.DateTimeProperty()
    instanceID = jsonobject.StringProperty()
    userID = jsonobject.StringProperty()
    deviceID = jsonobject.StringProperty()
    username = jsonobject.StringProperty()
    appVersion = jsonobject.StringProperty()
    location = GeoPointProperty()
Пример #7
0
class FieldSpec(jsonobject.StrictJsonObject):
    field = jsonobject.StringProperty()
    description = jsonobject.StringProperty()
    show_in_menu = jsonobject.BooleanProperty(default=False)
    discoverable = jsonobject.BooleanProperty(default=True)
    values_hints = jsonobject.ListProperty()
    deprecated = jsonobject.BooleanProperty(default=False)
Пример #8
0
class TaskStatusResultError(jsonobject.StrictJsonObject):
    title = jsonobject.StringProperty()
    description = jsonobject.StringProperty()
    column = jsonobject.StringProperty()
    # usually an int, but field has been hijacked to include other debug info
    # search 'row_number=' in tasks.py
    # longer-term solution would be to have another field for debug info
    rows = jsonobject.ListProperty()
Пример #9
0
class ElasticsearchIndexInfo(jsonobject.JsonObject):
    index = jsonobject.StringProperty(required=True)
    alias = jsonobject.StringProperty()
    type = jsonobject.StringProperty()
    meta = jsonobject.DictProperty()
    mapping = jsonobject.DictProperty()

    def __unicode__(self):
        return u'{} ({})'.format(self.alias, self.index)
Пример #10
0
class ChangeMeta(jsonobject.JsonObject):
    """
    Metadata about a change. If available, this will be set on Change.metadata.

    This is only used in kafka-based pillows.
    """
    document_id = DefaultProperty(required=True)
    data_source_type = jsonobject.StringProperty(required=True)
    data_source_name = jsonobject.StringProperty(required=True)
    document_type = DefaultProperty()
    document_subtype = jsonobject.StringProperty()
    domain = jsonobject.StringProperty()
    is_deletion = jsonobject.BooleanProperty()
    _allow_dynamic_properties = False
Пример #11
0
class FormDetails(jsonobject.JsonObject):
    xmlns = jsonobject.StringProperty()
    app = jsonobject.ObjectProperty(AppInfo)
    module = jsonobject.ObjectProperty(AppPart)
    form = jsonobject.ObjectProperty(AppPart)
    is_deleted = jsonobject.BooleanProperty()
    is_user_registration = jsonobject.BooleanProperty(default=False)
Пример #12
0
class ChangeMeta(jsonobject.JsonObject):
    """
    Metadata about a change. If available, this will be set on Change.metadata.

    This is only used in kafka-based pillows.
    """
    document_id = DefaultProperty(required=True)
    document_rev = jsonobject.StringProperty()  # Only relevant for Couch documents
    data_source_type = jsonobject.StringProperty(required=True)
    data_source_name = jsonobject.StringProperty(required=True)
    document_type = DefaultProperty()
    document_subtype = jsonobject.StringProperty()
    domain = jsonobject.StringProperty()
    is_deletion = jsonobject.BooleanProperty()
    publish_timestamp = jsonobject.DateTimeProperty(default=datetime.utcnow)
    _allow_dynamic_properties = False
Пример #13
0
class PaymentUpdate(jsonobject.JsonObject):
    id = jsonobject.StringProperty(required=True)
    status = jsonobject.StringProperty(required=True,
                                       choices=[SUCCESS, FAILURE])
    amount = jsonobject.FloatProperty(required=False)
    paymentDate = FlexibleDateTimeProperty(required=True)
    comments = jsonobject.StringProperty(required=False)
    failureDescription = jsonobject.StringProperty(required=False)
    paymentMode = jsonobject.StringProperty(required=False)
    checkNumber = jsonobject.StringProperty(required=False)
    bankName = jsonobject.StringProperty(required=False)

    @classmethod
    def wrap(cls, data):
        amount = data.get('amount', None)
        if amount:
            try:
                float_amount = float(amount)
                data['amount'] = float_amount
            except (ValueError, TypeError):
                raise BadValueError(
                    "amount '{}' is not a number".format(amount))
        return super(PaymentUpdate, cls).wrap(data)

    @property
    def case_id(self):
        return self.id
Пример #14
0
class IncentiveUpdate(jsonobject.JsonObject):
    beneficiary_id = jsonobject.StringProperty(required=True)
    episode_id = jsonobject.StringProperty(required=True)
    payment_status = jsonobject.StringProperty(required=True,
                                               choices=['success', 'failure'])
    payment_amount = jsonobject.DecimalProperty(required=False)
    failure_description = jsonobject.StringProperty(required=False)
    bets_parent_event_id = jsonobject.StringProperty(required=False,
                                                     choices=BETS_EVENT_IDS)

    case_type = CASE_TYPE_EPISODE

    @property
    def case_id(self):
        return self.episode_id

    @property
    def properties(self):
        status_key = 'tb_incentive_{}_status'.format(self.bets_parent_event_id)
        if self.payment_status == 'success':
            amount_key = 'tb_incentive_{}_amount'.format(
                self.bets_parent_event_id)
            date_key = 'tb_incentive_{}_payment_date'.format(
                self.bets_parent_event_id)
            return {
                status_key: 'paid',
                amount_key: self.payment_amount,
                date_key: datetime.datetime.utcnow().date().isoformat(),
            }
        else:
            date_key = 'tb_incentive_{}_rejection_date'.format(
                self.bets_parent_event_id)
            reason_key = 'tb_incentive_{}_rejection_reason'.format(
                self.bets_parent_event_id)
            return {
                status_key: 'rejected',
                date_key: datetime.datetime.utcnow().date().isoformat(),
                reason_key: self.failure_description,
            }
Пример #15
0
class StockReportHelper(jsonobject.JsonObject):
    """
    Intermediate class for dealing with stock XML
    """

    domain = jsonobject.StringProperty()
    form_id = jsonobject.StringProperty()
    timestamp = jsonobject.DateTimeProperty()
    tag = jsonobject.StringProperty()
    transactions = jsonobject.ListProperty(lambda: StockTransactionHelper)
    server_date = jsonobject.DateTimeProperty()
    deprecated = jsonobject.BooleanProperty()

    @property
    def report_type(self):
        # this is for callers to be able to use a less confusing name
        return self.tag

    @classmethod
    def make_from_form(cls, form, timestamp, tag, transactions):
        deprecated = form.is_deprecated
        return cls(
            domain=form.domain,
            form_id=form.form_id if not deprecated else form.orig_id,
            timestamp=timestamp,
            tag=tag,
            transactions=transactions,
            server_date=form.received_on,
            deprecated=deprecated,
        )

    def validate(self):
        """
        Validates this object as best we can and raises Exceptions if we find anything invalid .
        """
        if any(transaction_helper.product_id in ('', None)
               for transaction_helper in self.transactions):
            raise MissingProductId(
                _('Product IDs must be set for all ledger updates!'))
Пример #16
0
class ElasticsearchIndexInfo(jsonobject.JsonObject):
    index = jsonobject.StringProperty(required=True)
    alias = jsonobject.StringProperty()
    type = jsonobject.StringProperty()
    mapping = jsonobject.DictProperty()

    def __unicode__(self):
        return u'{} ({})'.format(self.alias, self.index)

    @property
    def meta(self):
        meta_settings = deepcopy(settings.ES_META['default'])
        meta_settings.update(settings.ES_META.get(self.alias, {}))
        meta_settings.update(
            settings.ES_META.get(settings.SERVER_ENVIRONMENT,
                                 {}).get(self.alias, {}))
        return meta_settings

    def to_json(self):
        json = super(ElasticsearchIndexInfo, self).to_json()
        json['meta'] = self.meta
        return json
Пример #17
0
class CaseUploadJSON(jsonobject.StrictJsonObject):
    domain = jsonobject.StringProperty(required=True)
    # In user display format, e.g. Dec 08, 2016 19:19 EST
    created_display = jsonobject.StringProperty(required=True)
    created = jsonobject.StringProperty(required=True)
    upload_id = jsonobject.StringProperty(required=True)
    task_status = jsonobject.ObjectProperty(lambda: TaskStatus)
    user_name = jsonobject.StringProperty(required=True)
    case_type = jsonobject.StringProperty(required=True)
    comment = jsonobject.StringProperty()

    upload_file_name = jsonobject.StringProperty()
    upload_file_length = jsonobject.IntegerProperty()
    upload_file_download_allowed = jsonobject.BooleanProperty(required=True)
    upload_comment_edit_allowed = jsonobject.BooleanProperty(required=True)
Пример #18
0
class TestFormMetadata(jsonobject.JsonObject):
    domain = jsonobject.StringProperty(required=False)
    xmlns = jsonobject.StringProperty(default='http://openrosa.org/formdesigner/form-processor')
    app_id = jsonobject.StringProperty(default='123')
    form_name = jsonobject.StringProperty(default='New Form')
    device_id = jsonobject.StringProperty(default='DEV IL')
    user_id = jsonobject.StringProperty(default='cruella_deville')
    username = jsonobject.StringProperty(default='eve')
    time_end = jsonobject.DateTimeProperty(default=datetime(2013, 4, 19, 16, 52, 2))
    time_start = jsonobject.DateTimeProperty(default=datetime(2013, 4, 19, 16, 53, 2))
    # Set this property to fake the submission time
    received_on = jsonobject.DateTimeProperty(default=datetime.utcnow)
Пример #19
0
class XFormPhoneMetadata(jsonobject.JsonObject):
    """
    Metadata of an xform, from a meta block structured like:

        <Meta>
            <timeStart />
            <timeEnd />
            <instanceID />
            <userID />
            <deviceID />
            <username />

            <!-- CommCare extension -->
            <appVersion />
            <location />
        </Meta>

    See spec: https://bitbucket.org/javarosa/javarosa/wiki/OpenRosaMetaDataSchema

    username is not part of the spec but included for convenience
    """

    timeStart = jsonobject.DateTimeProperty()
    timeEnd = jsonobject.DateTimeProperty()
    instanceID = jsonobject.StringProperty()
    userID = jsonobject.StringProperty()
    deviceID = jsonobject.StringProperty()
    username = jsonobject.StringProperty()
    appVersion = jsonobject.StringProperty()
    location = GeoPointProperty()

    @property
    def commcare_version(self):
        from corehq.apps.receiverwrapper.util import get_commcare_version_from_appversion_text
        from distutils.version import LooseVersion
        version_text = get_commcare_version_from_appversion_text(self.appVersion)
        if version_text:
            return LooseVersion(version_text)
Пример #20
0
class StockLedgerValueWrapper(jsonobject.JsonObject):
    """
    Wrapper class to abstract StockState and the equivalent model coming out of Elasticsearch
    """
    domain = jsonobject.StringProperty()
    case_id = jsonobject.StringProperty()
    section_id = jsonobject.StringProperty()
    entry_id = jsonobject.StringProperty()
    balance = jsonobject.DecimalProperty()  # todo: should this be an int?
    last_modified = jsonobject.DateTimeProperty()
    last_modified_form_id = jsonobject.StringProperty()
    daily_consumption = jsonobject.FloatProperty()
    location_id = jsonobject.StringProperty()

    _sql_product = None
    _sql_location = None

    def __init__(self, _obj=None, sql_product=None, sql_location=None, *args, **kwargs):
        self._sql_product = sql_product
        self._sql_location = sql_location
        super(StockLedgerValueWrapper, self).__init__(_obj, *args, **kwargs)

    @property
    def sql_product(self):
        if self.entry_id and not self._sql_product:
            try:
                self._sql_product = SQLProduct.objects.get(domain=self.domain, product_id=self.entry_id)
            except ObjectDoesNotExist:
                # todo: cache this result so multiple failing calls don't keep hitting the DB
                return None

        return self._sql_product

    @property
    def sql_location(self):
        if self.location_id and not self._sql_location:
            try:
                self._sql_location = SQLLocation.objects.get(domain=self.domain, location_id=self.location_id)
            except ObjectDoesNotExist:
                # todo: cache this result so multiple failing calls don't keep hitting the DB
                return None

        return self._sql_location

    @classmethod
    def from_stock_state(cls, stock_state):
        return cls(
            case_id=stock_state.case_id,
            section_id=stock_state.section_id,
            entry_id=stock_state.product_id,
            balance=stock_state.stock_on_hand,
            last_modified=stock_state.last_modified_date,
            last_modified_form_id=stock_state.last_modified_form_id,
            daily_consumption=stock_state.daily_consumption,
            location_id=stock_state.location_id,
            sql_location=stock_state.sql_location,
            sql_product=stock_state.sql_product,
        )
Пример #21
0
class PaymentUpdate(jsonobject.JsonObject):
    id = jsonobject.StringProperty(required=True)
    status = jsonobject.StringProperty(required=True,
                                       choices=[SUCCESS, FAILURE])
    amount = jsonobject.IntegerProperty(required=False)
    paymentDate = FlexibleDateTimeProperty(required=True)
    comments = jsonobject.StringProperty(required=False)
    failureDescription = jsonobject.StringProperty(required=False)
    paymentMode = jsonobject.StringProperty(required=False)
    checkNumber = jsonobject.StringProperty(required=False)
    bankName = jsonobject.StringProperty(required=False)

    @property
    def case_id(self):
        return self.id
Пример #22
0
class StockLedgerValueWrapper(jsonobject.JsonObject):
    """
    Wrapper class to abstract StockState and the equivalent model coming out of Elasticsearch
    """
    domain = jsonobject.StringProperty()
    case_id = jsonobject.StringProperty()
    section_id = jsonobject.StringProperty()
    entry_id = jsonobject.StringProperty()
    balance = jsonobject.DecimalProperty()  # todo: should this be an int?
    last_modified = jsonobject.DateTimeProperty()
    last_modified_form_id = jsonobject.StringProperty()
    daily_consumption = jsonobject.FloatProperty()
    location_id = jsonobject.StringProperty()

    @property
    @quickcache(['self.domain', 'self.entry_id'])
    def sql_product(self):
        try:
            return SQLProduct.objects.get(domain=self.domain, product_id=self.entry_id)
        except ObjectDoesNotExist:
            return None

    @property
    @quickcache(['self.domain', 'self.location_id'])
    def sql_location(self):
        try:
            return (
                SQLLocation.objects
                .prefetch_related('location_type')
                .get(domain=self.domain, location_id=self.location_id)
            )
        except ObjectDoesNotExist:
            return None

    @classmethod
    def from_stock_state(cls, stock_state):
        return cls(
            case_id=stock_state.case_id,
            section_id=stock_state.section_id,
            entry_id=stock_state.product_id,
            balance=stock_state.stock_on_hand,
            last_modified=stock_state.last_modified_date,
            last_modified_form_id=stock_state.last_modified_form_id,
            daily_consumption=stock_state.daily_consumption,
            location_id=stock_state.location_id,
            sql_location=stock_state.sql_location,
            sql_product=stock_state.sql_product,
        )
Пример #23
0
class VoucherUpdate(PaymentUpdate):
    eventType = jsonobject.StringProperty(required=True, choices=['Voucher'])
    case_type = CASE_TYPE_VOUCHER

    @property
    def properties(self):
        if self.status == SUCCESS:
            return {
                'state': 'paid',
                'amount_fulfilled': self.amount,
                'date_fulfilled': self.paymentDate.isoformat(),
                'comments': self.comments or "",
                'payment_mode': self.paymentMode or "",
                'check_number': self.checkNumber or "",
                'bank_name': self.bankName or "",
            }
        else:
            return {
                'state': 'rejected',
                'comments': self.comments or "",
                'reason_rejected': self.failureDescription or "",
                'date_rejected': self.paymentDate.isoformat(),
            }
Пример #24
0
class StockTransactionHelper(jsonobject.JsonObject):
    """
    Helper class for transactions
    """

    product_id = jsonobject.StringProperty()
    action = jsonobject.StringProperty()
    subaction = jsonobject.StringProperty()
    domain = jsonobject.StringProperty()
    quantity = jsonobject.DecimalProperty()
    # todo: this field is never populated during normal form submissions, only on SMS submissions
    location_id = jsonobject.StringProperty()
    timestamp = jsonobject.DateTimeProperty()
    case_id = jsonobject.StringProperty()
    section_id = jsonobject.StringProperty()

    @property
    def ledger_reference(self):
        return UniqueLedgerReference(
            case_id=self.case_id, section_id=self.section_id, entry_id=self.product_id
        )

    @property
    def relative_quantity(self):
        """
        Gets the quantity of this transaction as a positive or negative number
        depending on the action/context
        """
        if self.action == const.StockActions.CONSUMPTION:
            return -self.quantity
        else:
            return self.quantity

    def action_config(self, commtrack_config):
        action = CommtrackActionConfig(action=self.action,
                                       subaction=self.subaction)
        for a in commtrack_config.all_actions:
            if a.name == action.name:
                return a
        return None

    @property
    def date(self):
        if self.timestamp:
            return dateparse.json_format_datetime(self.timestamp)

    def to_xml(self, E=None, **kwargs):
        if not E:
            E = XML()

        return E.entry(
            id=self.product_id,
            quantity=str(self.quantity if self.action != StockActions.STOCKOUT
                         else 0),
        )

    @property
    def category(self):
        return 'stock'

    def fragment(self):
        """
        A short string representation of this to be used in sms correspondence
        """
        if self.quantity is not None:
            quant = self.quantity
        else:
            quant = ''
        # FIXME product fetch here is inefficient
        return '%s%s' % (Product.get(self.product_id).code.lower(), quant)

    def __repr__(self):
        return '{action} ({subaction}): {quantity} (loc: {location_id}, product: {product_id})'.format(
            action=self.action,
            subaction=self.subaction,
            quantity=self.quantity,
            location_id=self.location_id,
            product_id=self.product_id,
        )
Пример #25
0
class SingleFieldColumnProperties(jsonobject.JsonObject):
    referenced_column = jsonobject.StringProperty(required=True)
Пример #26
0
class SqlColumnProperties(jsonobject.JsonObject):
    datatype = DataTypeProperty(required=True)
    statement = jsonobject.StringProperty(required=True)
    statement_params = jsonobject.DictProperty()
Пример #27
0
class MonthlyPerformanceSummary(jsonobject.JsonObject):
    month = jsonobject.DateProperty()
    domain = jsonobject.StringProperty()
    performance_threshold = jsonobject.IntegerProperty()
    active = jsonobject.IntegerProperty()
    performing = jsonobject.IntegerProperty()

    def __init__(self,
                 domain,
                 month,
                 selected_users,
                 active_not_deleted_users,
                 performance_threshold,
                 previous_summary=None,
                 delta_high_performers=0,
                 delta_low_performers=0):
        self._previous_summary = previous_summary
        self._next_summary = None
        self._is_final = None

        base_queryset = MALTRow.objects.filter(
            domain_name=domain,
            month=month,
            user_type__in=['CommCareUser', 'CommCareUser-Deleted'],
            user_id__in=active_not_deleted_users,
        )
        if selected_users:
            base_queryset = base_queryset.filter(user_id__in=selected_users, )

        self._user_stat_from_malt = (base_queryset.values(
            'user_id',
            'username').annotate(total_num_forms=Sum('num_of_forms')))

        num_performing_users = (self._user_stat_from_malt.filter(
            total_num_forms__gte=performance_threshold).count())

        num_active_users = self._user_stat_from_malt.count()
        num_low_performing_user = num_active_users - num_performing_users

        if self._previous_summary:
            delta_high_performers = num_performing_users - self._previous_summary.number_of_performing_users
            delta_low_performers = num_low_performing_user - self._previous_summary.number_of_low_performing_users

        super(MonthlyPerformanceSummary, self).__init__(
            month=month,
            domain=domain,
            performance_threshold=performance_threshold,
            active=num_active_users,
            total_users_by_month=0,
            percent_active=0,
            performing=num_performing_users,
            delta_high_performers=delta_high_performers,
            delta_low_performers=delta_low_performers,
        )

    def set_next_month_summary(self, next_month_summary):
        self._next_summary = next_month_summary

    def set_percent_active(self):
        self.total_users_by_month = self.inactive + self.number_of_active_users
        if self.total_users_by_month:
            self.percent_active = float(self.number_of_active_users) / float(
                self.total_users_by_month)
        else:
            self.percent_active = 0

    @property
    def number_of_performing_users(self):
        return self.performing

    @property
    def number_of_low_performing_users(self):
        return self.active - self.performing

    @property
    def number_of_active_users(self):
        return self.active

    @property
    @memoized
    def inactive(self):
        dropouts = self.get_dropouts()
        return len(dropouts) if dropouts else 0

    @property
    def previous_month(self):
        prev_year, prev_month = add_months(self.month.year, self.month.month,
                                           -1)
        return datetime.datetime(prev_year, prev_month, 1)

    @property
    def delta_high_performing(self):
        if self._previous_summary:
            return self.number_of_performing_users - self._previous_summary.number_of_performing_users
        else:
            return self.number_of_performing_users

    @property
    def delta_high_performing_pct(self):
        if (self.delta_high_performing and self._previous_summary
                and self._previous_summary.number_of_performing_users):
            return float(self.delta_high_performing / float(
                self._previous_summary.number_of_performing_users)) * 100.

    @property
    def delta_low_performing(self):
        if self._previous_summary:
            return self.number_of_low_performing_users - self._previous_summary.number_of_low_performing_users
        else:
            return self.number_of_low_performing_users

    @property
    def delta_low_performing_pct(self):
        if self.delta_low_performing and self._previous_summary \
                and self._previous_summary.number_of_low_performing_users:
            return float(self.delta_low_performing / float(
                self._previous_summary.number_of_low_performing_users)) * 100.

    @property
    def delta_active(self):
        return self.active - self._previous_summary.active if self._previous_summary else self.active

    @property
    def delta_active_pct(self):
        if self.delta_active and self._previous_summary and self._previous_summary.active:
            return float(self.delta_active /
                         float(self._previous_summary.active)) * 100.

    @property
    def delta_inactive(self):
        return self.inactive - self._previous_summary.inactive if self._previous_summary else self.inactive

    @property
    def delta_inactive_pct(self):
        if self.delta_inactive and self._previous_summary:
            if self._previous_summary.inactive == 0:
                return self.delta_inactive * 100.
            return float(self.delta_inactive /
                         float(self._previous_summary.inactive)) * 100.

    def _get_all_user_stubs(self):
        return {
            row['user_id']: UserActivityStub(
                user_id=row['user_id'],
                username=raw_username(row['username']),
                num_forms_submitted=row['total_num_forms'],
                is_performing=row['total_num_forms'] >=
                self.performance_threshold,
                previous_stub=None,
                next_stub=None,
            )
            for row in self._user_stat_from_malt
        }

    def finalize(self):
        """
        Before a summary is "finalized" certain fields can't be accessed.
        """
        self._is_final = True

    @memoized
    def _get_all_user_stubs_with_extra_data(self):
        if not self._is_final:
            # intentionally fail-hard with developer-facing error
            raise Exception(
                "User stubs accessed before finalized. "
                "Please call finalize() before calling this method.")
        if self._previous_summary:
            previous_stubs = self._previous_summary._get_all_user_stubs()
            next_stubs = self._next_summary._get_all_user_stubs(
            ) if self._next_summary else {}
            user_stubs = self._get_all_user_stubs()
            ret = []
            for user_stub in user_stubs.values():
                ret.append(
                    UserActivityStub(
                        user_id=user_stub.user_id,
                        username=user_stub.username,
                        num_forms_submitted=user_stub.num_forms_submitted,
                        is_performing=user_stub.is_performing,
                        previous_stub=previous_stubs.get(user_stub.user_id),
                        next_stub=next_stubs.get(user_stub.user_id),
                    ))
            for missing_user_id in set(previous_stubs.keys()) - set(
                    user_stubs.keys()):
                previous_stub = previous_stubs[missing_user_id]
                ret.append(
                    UserActivityStub(
                        user_id=previous_stub.user_id,
                        username=previous_stub.username,
                        num_forms_submitted=0,
                        is_performing=False,
                        previous_stub=previous_stub,
                        next_stub=next_stubs.get(missing_user_id),
                    ))
            return ret

    def get_unhealthy_users(self):
        """
        Get a list of unhealthy users - defined as those who were "performing" last month
        but are not this month (though are still active).
        """
        if self._previous_summary:
            unhealthy_users = filter(
                lambda stub: stub.is_active and not stub.is_performing,
                self._get_all_user_stubs_with_extra_data())
            return sorted(unhealthy_users, key=lambda stub: stub.delta_forms)

    def get_dropouts(self):
        """
        Get a list of dropout users - defined as those who were active last month
        but are not active this month
        """
        if self._previous_summary:
            dropouts = filter(lambda stub: not stub.is_active,
                              self._get_all_user_stubs_with_extra_data())
            return sorted(dropouts, key=lambda stub: stub.delta_forms)

    def get_newly_performing(self):
        """
        Get a list of "newly performing" users - defined as those who are "performing" this month
        after not performing last month.
        """
        if self._previous_summary:
            dropouts = filter(lambda stub: stub.is_newly_performing,
                              self._get_all_user_stubs_with_extra_data())
            return sorted(dropouts, key=lambda stub: -stub.delta_forms)
Пример #28
0
class FuzzyProperties(jsonobject.JsonObject):
    case_type = jsonobject.StringProperty()
    properties = jsonobject.ListProperty(unicode)
Пример #29
0
class SecondaryTableSpec(jsonobject.JsonObject):
    data_source_id = jsonobject.StringProperty(required=True)
    join_column_primary = jsonobject.StringProperty(required=True)
    join_column_secondary = jsonobject.StringProperty(required=True)
    time_window_column = jsonobject.StringProperty()
    columns = jsonobject.ListProperty(SecondaryColumnSpec)
Пример #30
0
class AppInfo(jsonobject.JsonObject):
    id = jsonobject.StringProperty()
    names = jsonobject.StringProperty()
    langs = jsonobject.ListProperty(unicode)