Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 156 additions & 2 deletions back/boxtribute_server/business_logic/statistics/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from peewee import JOIN, SQL, fn

from ...db import execute_sql
from ...enums import BoxState, TaggableObjectType, TargetType
from ...enums import BeneficiaryReachType, BoxState, TaggableObjectType, TargetType
from ...errors import InvalidDate
from ...models.definitions.base import Base
from ...models.definitions.beneficiary import Beneficiary
Expand All @@ -18,13 +18,14 @@
from ...models.definitions.organisation import Organisation
from ...models.definitions.product import Product
from ...models.definitions.product_category import ProductCategory
from ...models.definitions.services_relation import ServicesRelation
from ...models.definitions.shareable_link import ShareableLink
from ...models.definitions.size import Size
from ...models.definitions.size_range import SizeRange
from ...models.definitions.tag import Tag
from ...models.definitions.tags_relation import TagsRelation
from ...models.definitions.transaction import Transaction
from ...models.utils import compute_age, convert_ids, utcnow
from ...models.utils import HISTORY_DELETION_MESSAGE, compute_age, convert_ids, utcnow
from ...utils import in_ci_environment, in_production_environment
from ..metrics.crud import exclude_test_organisation
from .sql import MOVED_BOXES_QUERY, STOCK_OVERVIEW_QUERY
Expand Down Expand Up @@ -92,6 +93,31 @@ def _generate_dimensions(*names, facts):
.dicts()
)

if "beneficiary" in names:
beneficiary_ids = {f["beneficiary_id"] for f in facts}
age = fn.IF(
Beneficiary.date_of_birth > 0, compute_age(Beneficiary.date_of_birth), None
)
dimensions["beneficiary"] = (
Beneficiary.select(
Beneficiary.id,
age.alias("age"),
Beneficiary.gender,
fn.GROUP_CONCAT(TagsRelation.tag.distinct()).alias("tag_ids"),
)
.left_outer_join(
TagsRelation,
on=(
(TagsRelation.object_id == Beneficiary.id)
& (TagsRelation.object_type == TaggableObjectType.Beneficiary)
& (TagsRelation.deleted_on.is_null())
),
)
.where(Beneficiary.id << beneficiary_ids)
.group_by(Beneficiary.id)
.dicts()
)

if "target" in names:
dimensions["target"] = []
for target_type in TargetType:
Expand Down Expand Up @@ -180,6 +206,134 @@ def compute_beneficiary_demographics(base_id):
)


def compute_beneficiary_reach(base_id):
"""Obtain data about beneficiary interactions (categorized by BeneficiaryReachType).
Related to the reachedBeneficiariesNumbers metric which is an aggregated form of
this data.
"""
_validate_existing_base(base_id)
FamilyMember = Beneficiary.alias()
Interactions = (
# created/edited (persistently logged in history table)
DbChangeHistory.select(
fn.DATE(DbChangeHistory.change_date).alias("reached_on"),
DbChangeHistory.record_id.alias("beneficiary_id"),
SQL(f"{BeneficiaryReachType.CreatedOrEdited.value}").alias("reach_type"),
)
.join(
Beneficiary,
on=(
(Beneficiary.id == DbChangeHistory.record_id)
& (Beneficiary.base == base_id)
),
)
.where(
DbChangeHistory.table_name == Beneficiary._meta.table_name,
# Exclude "Record deleted [by dailyroutine|without undelete]"
~DbChangeHistory.changes.startswith(HISTORY_DELETION_MESSAGE),
)
| (
# created acc. to people table (contains info for some beneficiaries
# directly imported to the DB but misses permanently deleted beneficiaries)
Beneficiary.select(
fn.DATE(Beneficiary.created_on).alias("reached_on"),
Beneficiary.id.alias("beneficiary_id"),
SQL(f"{BeneficiaryReachType.CreatedOrEdited.value}").alias(
"reach_type"
),
).where(Beneficiary.base == base_id)
)
| (
# involved in transactions (family heads)
Transaction.select(
fn.DATE(Transaction.created_on).alias("reached_on"),
Transaction.beneficiary.alias("beneficiary_id"),
SQL(f"{BeneficiaryReachType.Checkout.value}").alias("reach_type"),
).join(
Beneficiary,
on=(
(Beneficiary.id == Transaction.beneficiary)
& (Beneficiary.base == base_id)
& (Transaction.count > 0)
),
),
)
| (
# Family members of a beneficiary who had a transaction are also
# counted as reached via "Checkout" on the same date
Transaction.select(
fn.DATE(Transaction.created_on).alias("reached_on"),
FamilyMember.id.alias("beneficiary_id"),
SQL(f"{BeneficiaryReachType.Checkout.value}").alias("reach_type"),
)
.join(
Beneficiary,
on=(
(Beneficiary.id == Transaction.beneficiary)
& (Beneficiary.base == base_id)
& (Transaction.count > 0)
),
)
.join(
FamilyMember,
on=(FamilyMember.family_head == Transaction.beneficiary),
),
)
| (
ServicesRelation.select(
fn.DATE(ServicesRelation.created_on).alias("reached_on"),
ServicesRelation.beneficiary.alias("beneficiary_id"),
SQL(f"{BeneficiaryReachType.ServiceUsed.value}").alias("reach_type"),
).join(
Beneficiary,
on=(
(Beneficiary.id == ServicesRelation.beneficiary)
& (Beneficiary.base == base_id)
),
),
)
| (
TagsRelation.select(
fn.DATE(TagsRelation.created_on).alias("reached_on"),
TagsRelation.object_id.alias("beneficiary_id"),
SQL(f"{BeneficiaryReachType.TagApplied.value}").alias("reach_type"),
)
.join(
Beneficiary,
on=(
(TagsRelation.object_type == TaggableObjectType.Beneficiary)
& (Beneficiary.id == TagsRelation.object_id)
& (Beneficiary.base == base_id)
),
)
.where(TagsRelation.created_on.is_null(False))
)
)
facts = (
Beneficiary.select(
Interactions.c.reached_on,
Interactions.c.beneficiary_id,
Interactions.c.reach_type,
fn.COUNT(Interactions.c.beneficiary_id).alias("count"),
)
.from_(Interactions)
.group_by(
SQL("reached_on"),
SQL("beneficiary_id"),
SQL("reach_type"),
)
.dicts()
.execute()
)
for fact in facts:
fact["reach_type"] = BeneficiaryReachType(fact["reach_type"])
dimensions = _generate_dimensions("beneficiary", facts=facts)
for b in dimensions["beneficiary"]:
b["tag_ids"] = sorted(convert_ids(b["tag_ids"]))
dimensions.update(_generate_dimensions("tag", facts=dimensions["beneficiary"]))
return DataCube(facts=facts, dimensions=dimensions, type="BeneficiaryReachData")
Comment on lines +330 to +334


def compute_created_boxes(base_id):
"""For each combination of product ID, category ID, gender, and day-truncated
creation date count the number of created boxes, and the contained items, in the
Expand Down
10 changes: 10 additions & 0 deletions back/boxtribute_server/business_logic/statistics/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from . import query
from .crud import (
compute_beneficiary_demographics,
compute_beneficiary_reach,
compute_created_boxes,
compute_moved_boxes,
compute_stock_overview,
Expand All @@ -29,6 +30,15 @@ def resolve_beneficiary_demographics(*_, base_id):
return compute_beneficiary_demographics(base_id)


@query.field("beneficiaryReach")
@use_db_replica
def resolve_beneficiary_reach(*_, base_id):
# No cross-organisational access for beneficiary-related data
authorize(permission="beneficiary:read", base_id=base_id)
authorize(permission="tag_relation:read")
return compute_beneficiary_reach(base_id)


@query.field("createdBoxes")
@use_db_replica
def resolve_created_boxes(*_, base_id):
Expand Down
7 changes: 7 additions & 0 deletions back/boxtribute_server/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ class TargetType(enum.IntEnum):
BoxState = enum.auto()


class BeneficiaryReachType(enum.IntEnum):
CreatedOrEdited = 1
TagApplied = enum.auto()
ServiceUsed = enum.auto()
Checkout = enum.auto()


class ProductTypeFilter(enum.Enum):
Custom = "Custom"
StandardInstantiation = "StandardInstantiation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ enum ShareableView {
StockOverview
}

enum BeneficiaryReachType {
CreatedOrEdited
TagApplied
ServiceUsed
Checkout
}

"""
TODO: Add description here once specs are final/confirmed
"""
Expand Down
28 changes: 26 additions & 2 deletions back/boxtribute_server/graph_ql/definitions/basic/types.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ interface DataCube {
dimensions: Dimensions
}

union Result = BeneficiaryDemographicsResult | CreatedBoxesResult | TopProductsCheckedOutResult | TopProductsDonatedResult | MovedBoxesResult | StockOverviewResult
union Dimensions = BeneficiaryDemographicsDimensions | CreatedBoxDataDimensions | TopProductsDimensions | MovedBoxDataDimensions | StockOverviewDataDimensions
union Result = BeneficiaryDemographicsResult | CreatedBoxesResult | TopProductsCheckedOutResult | TopProductsDonatedResult | MovedBoxesResult | StockOverviewResult | BeneficiaryReachResult
union Dimensions = BeneficiaryDemographicsDimensions | CreatedBoxDataDimensions | TopProductsDimensions | MovedBoxDataDimensions | StockOverviewDataDimensions | BeneficiaryReachDimensions

type BeneficiaryDemographicsData implements DataCube {
facts: [BeneficiaryDemographicsResult]
Expand All @@ -24,6 +24,30 @@ type BeneficiaryDemographicsResult {
count: Int
}

type BeneficiaryReachData implements DataCube {
facts: [BeneficiaryReachResult!]!
dimensions: BeneficiaryReachDimensions!
}

type BeneficiaryReachResult {
reachedOn: Date!
beneficiaryId: Int!
reachType: BeneficiaryReachType
count: Int!
}

type BeneficiaryReachDimensions {
beneficiary: [BeneficiaryDimensionInfo!]
tag: [TagDimensionInfo!]
}

type BeneficiaryDimensionInfo {
id: Int!
age: Int
tagIds: [Int!]
gender: HumanGender!
}

type CreatedBoxesData implements DataCube {
facts: [CreatedBoxesResult]
dimensions: CreatedBoxDataDimensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type Query {
metrics(organisationId: ID): Metrics

beneficiaryDemographics(baseId: Int!): BeneficiaryDemographicsData
beneficiaryReach(baseId: Int!): BeneficiaryReachData
createdBoxes(baseId: Int!): CreatedBoxesData
topProductsCheckedOut(baseId: Int!): TopProductsCheckedOutData
topProductsDonated(baseId: Int!): TopProductsDonatedData
Expand Down
2 changes: 2 additions & 0 deletions back/boxtribute_server/graph_ql/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ariadne import EnumType

from ..enums import (
BeneficiaryReachType,
BoxState,
DistributionEventState,
DistributionEventsTrackingGroupState,
Expand Down Expand Up @@ -36,6 +37,7 @@
TagType,
EnumType("TaggableResourceType", TaggableObjectType),
TargetType,
BeneficiaryReachType,
TransferAgreementState,
TransferAgreementType,
ProductType,
Expand Down
Loading
Loading