1
1

add statistics for irregular expenses, interpret negative transactions as outgoing and positive as incoming

This commit is contained in:
2020-02-27 10:35:19 +01:00
parent eee65c63fc
commit 1722ef79a2
3 changed files with 51 additions and 14 deletions

View File

@@ -6,6 +6,10 @@ from django.db.models import Q
from financeplanner.utils import current_date from financeplanner.utils import current_date
days_per_year = Decimal("365.25")
days_per_month = days_per_year / Decimal("12")
days_per_week = Decimal("7")
def _floor_to_first_two_places(dec): def _floor_to_first_two_places(dec):
if dec < 100: if dec < 100:
@@ -21,6 +25,9 @@ class ActualTransaction:
self.subject = subject self.subject = subject
self.amount = amount self.amount = amount
def __str__(self):
return f"ActualTransaction({self.date}, {self.subject}, {self.amount})"
class DailyStat: class DailyStat:
def __init__(self, date, balance_amount, actual_transactions, resulting_amount): def __init__(self, date, balance_amount, actual_transactions, resulting_amount):
@@ -32,6 +39,10 @@ class DailyStat:
self.color_class = "" self.color_class = ""
self.opacity_class = "" self.opacity_class = ""
def __str__(self):
return f"DailyStat({self.date}, {self.balance_amount}, " \
f"{len(self.actual_transactions)} a. transactions, {self.resulting_amount})"
def generate_graph_bar_attributes(self, amount_scale, today): def generate_graph_bar_attributes(self, amount_scale, today):
if self.resulting_amount is not None: if self.resulting_amount is not None:
self.percentage = int((self.resulting_amount / amount_scale) * 100) self.percentage = int((self.resulting_amount / amount_scale) * 100)
@@ -68,6 +79,9 @@ class Statistics:
self.avg_monthly_income = 0 self.avg_monthly_income = 0
self.avg_monthly_expenses = 0 self.avg_monthly_expenses = 0
self.avg_monthly_result = 0 self.avg_monthly_result = 0
self.avg_daily_irregular_expenses = 0
self.avg_weekly_irregular_expenses = 0
self.avg_monthly_irregular_expenses = 0
self._fetch_relevant_balances() self._fetch_relevant_balances()
self._fetch_relevant_transactions() self._fetch_relevant_transactions()
@@ -150,7 +164,7 @@ class Statistics:
if transaction.date == iter_date: if transaction.date == iter_date:
actual_transactions.append(transaction) actual_transactions.append(transaction)
if iter_amount is not None: if iter_amount is not None:
iter_amount -= transaction.amount iter_amount += transaction.amount
if iter_amount is not None: if iter_amount is not None:
self.daily_stats.append(DailyStat( self.daily_stats.append(DailyStat(
@@ -174,20 +188,38 @@ class Statistics:
def _calculate_analysis(self): def _calculate_analysis(self):
booked = Q(booking_date__lte=self.today) booked = Q(booking_date__lte=self.today)
incoming = Q(amount__lt=0) incoming = Q(amount__gt=0)
outgoing = Q(amount__gt=0) outgoing = Q(amount__lt=0)
recurring = Q(recurring_months__isnull=False) & ( recurring = Q(recurring_months__isnull=False) & (
Q(not_recurring_after__isnull=True) | Q(not_recurring_after__gte=self.today)) Q(not_recurring_after__isnull=True) | Q(not_recurring_after__gte=self.today))
in_trans = self.user.transactions.filter(booked & incoming & recurring) in_trans = self.user.transactions.filter(booked & incoming & recurring)
in_trans_per_month = [t.amount / t.recurring_months for t in in_trans] in_trans_per_month = [t.amount / t.recurring_months for t in in_trans]
self.avg_monthly_income = -sum(in_trans_per_month) self.avg_monthly_income = sum(in_trans_per_month)
out_trans = self.user.transactions.filter(booked & outgoing & recurring) out_trans = self.user.transactions.filter(booked & outgoing & recurring)
out_trans_per_month = [t.amount / t.recurring_months for t in out_trans] out_trans_per_month = [t.amount / t.recurring_months for t in out_trans]
self.avg_monthly_expenses = sum(out_trans_per_month) self.avg_monthly_expenses = sum(out_trans_per_month)
self.avg_monthly_result = self.avg_monthly_income - self.avg_monthly_expenses self.avg_monthly_result = self.avg_monthly_income + self.avg_monthly_expenses
daily_irregular_expenses = []
if len(self.balances) > 1:
for i in range(1, len(self.balances)):
previous_balance = self.balances[i - 1]
current_balance = self.balances[i]
day_before = current_balance.date - relativedelta(days=1)
relevant_stats = [s for s in self.daily_stats if s.date == day_before]
if relevant_stats and day_before > previous_balance.date:
assert len(relevant_stats) == 1, \
f"daily stats should be unique per date, but here are {relevant_stats}"
amount_diff = current_balance.amount - relevant_stats[0].resulting_amount
time_diff = current_balance.date - previous_balance.date
daily_irregular_expenses.append(amount_diff / time_diff.days)
if daily_irregular_expenses:
self.avg_daily_irregular_expenses = sum(daily_irregular_expenses) / len(daily_irregular_expenses)
self.avg_weekly_irregular_expenses = self.avg_daily_irregular_expenses * days_per_week
self.avg_monthly_irregular_expenses = self.avg_daily_irregular_expenses * days_per_month
def get_daily_stats_in_range(self): def get_daily_stats_in_range(self):
return [s for s in self.daily_stats if self.start <= s.date <= self.end] return [s for s in self.daily_stats if self.start <= s.date <= self.end]

View File

@@ -44,36 +44,38 @@
<div class="main-container"> <div class="main-container">
<h2>Analysis</h2> <h2>Analysis</h2>
<p>current average amounts per month:</p>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td>current average monthly income</td> <td>income</td>
<td>{{ avg_monthly_income|euro }}</td> <td>{{ avg_monthly_income|euro }}</td>
</tr> </tr>
<tr> <tr>
<td>current average monthly expenses</td> <td>expenses</td>
<td>{{ avg_monthly_expenses|euro }}</td> <td>{{ avg_monthly_expenses|euro }}</td>
</tr> </tr>
<tr> <tr>
<td>current average monthly result</td> <td>result</td>
<td>{{ avg_monthly_result|euro }}</td> <td>{{ avg_monthly_result|euro }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p>average irregular expenses based on the last half-a-year:</p>
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td>irregular expenses / month</td> <td>monthly</td>
<td>0</td> <td>{{ avg_monthly_irregular_expenses|euro }}</td>
</tr> </tr>
<tr> <tr>
<td>irregular expenses / week</td> <td>weekly</td>
<td>0</td> <td>{{ avg_weekly_irregular_expenses|euro }}</td>
</tr> </tr>
<tr> <tr>
<td>irregular expenses / day</td> <td>daily</td>
<td>0</td> <td>{{ avg_daily_irregular_expenses|euro }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -18,5 +18,8 @@ def index(request):
"avg_monthly_income": statistics.avg_monthly_income, "avg_monthly_income": statistics.avg_monthly_income,
"avg_monthly_expenses": statistics.avg_monthly_expenses, "avg_monthly_expenses": statistics.avg_monthly_expenses,
"avg_monthly_result": statistics.avg_monthly_result, "avg_monthly_result": statistics.avg_monthly_result,
"avg_daily_irregular_expenses": statistics.avg_daily_irregular_expenses,
"avg_weekly_irregular_expenses": statistics.avg_weekly_irregular_expenses,
"avg_monthly_irregular_expenses": statistics.avg_monthly_irregular_expenses,
} }
return render(request, "financeplanner/index.html", context) return render(request, "financeplanner/index.html", context)