mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-29 09:52:36 +02:00
Improve performance of balance and statistics computation (see #889)
This commit is contained in:
parent
d2c96c5bc6
commit
f68263328c
1 changed files with 41 additions and 30 deletions
|
@ -99,27 +99,37 @@ class Project(db.Model):
|
||||||
return [m for m in self.members if m.activated]
|
return [m for m in self.members if m.activated]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def balance(self):
|
def full_balance(self):
|
||||||
|
"""Returns a triple of dicts:
|
||||||
|
|
||||||
|
- dict mapping each member to its balance
|
||||||
|
|
||||||
|
- dict mapping each member to how much he/she should pay others
|
||||||
|
(i.e. how much he/she benefited from bills)
|
||||||
|
|
||||||
|
- dict mapping each member to how much he/she should be paid by
|
||||||
|
others (i.e. how much he/she has paid for bills)
|
||||||
|
|
||||||
|
"""
|
||||||
balances, should_pay, should_receive = (defaultdict(int) for time in (1, 2, 3))
|
balances, should_pay, should_receive = (defaultdict(int) for time in (1, 2, 3))
|
||||||
|
|
||||||
# for each person
|
for bill in self.get_bills_unordered().all():
|
||||||
for person in self.members:
|
should_receive[bill.payer.id] += bill.converted_amount
|
||||||
# get the list of bills he has to pay
|
total_weight = sum(ower.weight for ower in bill.owers)
|
||||||
bills = Bill.query.options(orm.subqueryload(Bill.owers)).filter(
|
for ower in bill.owers:
|
||||||
Bill.owers.contains(person)
|
should_pay[ower.id] += (
|
||||||
|
ower.weight * bill.converted_amount / total_weight
|
||||||
)
|
)
|
||||||
for bill in bills.all():
|
|
||||||
if person != bill.payer:
|
|
||||||
share = bill.pay_each() * person.weight
|
|
||||||
should_pay[person] += share
|
|
||||||
should_receive[bill.payer] += share
|
|
||||||
|
|
||||||
for person in self.members:
|
for person in self.members:
|
||||||
balance = should_receive[person] - should_pay[person]
|
balance = should_receive[person.id] - should_pay[person.id]
|
||||||
balances[person.id] = balance
|
balances[person.id] = balance
|
||||||
|
|
||||||
return balances
|
return balances, should_pay, should_receive
|
||||||
|
|
||||||
|
@property
|
||||||
|
def balance(self):
|
||||||
|
return self.full_balance[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def members_stats(self):
|
def members_stats(self):
|
||||||
|
@ -128,23 +138,13 @@ class Project(db.Model):
|
||||||
:return: one stat dict per member
|
:return: one stat dict per member
|
||||||
:rtype list:
|
:rtype list:
|
||||||
"""
|
"""
|
||||||
|
balance, spent, paid = self.full_balance
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"member": member,
|
"member": member,
|
||||||
"paid": sum(
|
"paid": paid[member.id],
|
||||||
[
|
"spent": spent[member.id],
|
||||||
bill.converted_amount
|
"balance": balance[member.id],
|
||||||
for bill in self.get_member_bills(member.id).all()
|
|
||||||
]
|
|
||||||
),
|
|
||||||
"spent": sum(
|
|
||||||
[
|
|
||||||
bill.pay_each() * member.weight
|
|
||||||
for bill in self.get_bills_unordered().all()
|
|
||||||
if member in bill.owers
|
|
||||||
]
|
|
||||||
),
|
|
||||||
"balance": self.balance[member.id],
|
|
||||||
}
|
}
|
||||||
for member in self.active_members
|
for member in self.active_members
|
||||||
]
|
]
|
||||||
|
@ -232,8 +232,13 @@ class Project(db.Model):
|
||||||
|
|
||||||
def get_bills_unordered(self):
|
def get_bills_unordered(self):
|
||||||
"""Base query for bill list"""
|
"""Base query for bill list"""
|
||||||
|
# The subqueryload option allows to pre-load data from the
|
||||||
|
# billowers table, which makes access to this data much faster.
|
||||||
|
# Without this option, any access to bill.owers would trigger a
|
||||||
|
# new SQL query, ruining overall performance.
|
||||||
return (
|
return (
|
||||||
Bill.query.join(Person, Project)
|
Bill.query.options(orm.subqueryload(Bill.owers))
|
||||||
|
.join(Person, Project)
|
||||||
.filter(Bill.payer_id == Person.id)
|
.filter(Bill.payer_id == Person.id)
|
||||||
.filter(Person.project_id == Project.id)
|
.filter(Person.project_id == Project.id)
|
||||||
.filter(Project.id == self.id)
|
.filter(Project.id == self.id)
|
||||||
|
@ -572,7 +577,10 @@ class Bill(db.Model):
|
||||||
}
|
}
|
||||||
|
|
||||||
def pay_each_default(self, amount):
|
def pay_each_default(self, amount):
|
||||||
"""Compute what each share has to pay"""
|
"""Compute what each share has to pay. Warning: this is slow, if you need
|
||||||
|
to compute this for many bills, do it differently (see
|
||||||
|
balance_full function)
|
||||||
|
"""
|
||||||
if self.owers:
|
if self.owers:
|
||||||
weights = (
|
weights = (
|
||||||
db.session.query(func.sum(Person.weight))
|
db.session.query(func.sum(Person.weight))
|
||||||
|
@ -587,6 +595,9 @@ class Bill(db.Model):
|
||||||
return self.what
|
return self.what
|
||||||
|
|
||||||
def pay_each(self):
|
def pay_each(self):
|
||||||
|
"""Warning: this is slow, if you need to compute this for many bills, do
|
||||||
|
it differently (see balance_full function)
|
||||||
|
"""
|
||||||
return self.pay_each_default(self.converted_amount)
|
return self.pay_each_default(self.converted_amount)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
Loading…
Reference in a new issue