feat: Enhance financial transparency

Introduced detailed financial reporting on a per-category basis, including notes for specific expenses and administrative costs in the data model. Enhanced the financial transparency feature by integrating a monthly financial report generator in the backend, enabling dynamic generation and display of detailed financial reports on the website. This commit also includes a new transparency report page, enriching user engagement with detailed insights into financial allocations and expenditures.

- Refactored finance-related data structures to include notes about specific financial activities.
- Extended the financial reporting capabilities to generate and display detailed notes and categorizations of expenses and incomes, improving transparency.
- Updated the UI to display these detailed reports, ensuring that users have access to comprehensive financial information.

This change aligns with our commitment to transparency and accountability by providing clear, detailed financial information to our members and donors.
This commit is contained in:
Kumi 2024-06-03 16:12:21 +02:00
parent b3ae436679
commit 1a10e8968b
Signed by: kumi
GPG key ID: ECBCC9082395383F
5 changed files with 103 additions and 32 deletions

View file

@ -4,15 +4,14 @@
"Membership Fees": {
"EUR": 365
},
"Donations": {},
"Server Costs": {
"EUR": -216.57
},
"Domain Names": {},
"Operating Expenses": {
"EUR": -36.10
},
"Conversions": {}
"Administrative Expenses": {
"EUR": -36.10,
"Notes": "Administrative fee for the formation of the association"
}
},
"5": {
"Membership Fees": {
@ -23,11 +22,9 @@
"XMR": 0.447661805527
},
"Server Costs": {
"EUR": -430.04
},
"Domain Names": {},
"Operating Expenses": {},
"Conversions": {}
"EUR": -430.04,
"Notes": "Includes setup costs and two monthly payments for new server"
}
},
"6": {
"Server Costs": {

View file

@ -9,6 +9,7 @@ def get_latest_month(data):
return int(latest_month), int(latest_year)
def get_transparency_data(data, year=None, month=None):
if year is None:
year = max(data.keys())
@ -23,6 +24,7 @@ def get_transparency_data(data, year=None, month=None):
balances = {}
incomes = {}
expenses = {}
notes = {}
start_balance = {}
end_balance = {}
@ -37,41 +39,51 @@ def get_transparency_data(data, year=None, month=None):
# If the month is the one we are interested in, capture the start balance
if int(y) == int(year) and int(m) == int(month):
start_balance = {k: Decimal(v) for k, v in balances.items()}
start_balance = {
k: Decimal(v) for k, v in balances.items() if k != "Notes"
}
for category in data[y][m]:
for currency, amount in data[y][m][category].items():
if currency not in balances:
balances[currency] = Decimal(0)
balances[currency] += Decimal(str(amount))
if currency == "Notes":
if int(y) == int(year) and int(m) == int(month):
notes[category] = amount
else:
if currency not in balances:
balances[currency] = Decimal(0)
balances[currency] += Decimal(str(amount))
# Track incomes and expenses
if int(y) == int(year) and int(m) == int(month):
if Decimal(str(amount)) > 0:
if category not in incomes:
incomes[category] = {}
if currency not in incomes[category]:
incomes[category][currency] = Decimal(0)
incomes[category][currency] += Decimal(str(amount))
else:
if category not in expenses:
expenses[category] = {}
if currency not in expenses[category]:
expenses[category][currency] = Decimal(0)
expenses[category][currency] += Decimal(str(amount))
# Track incomes and expenses
if int(y) == int(year) and int(m) == int(month):
if Decimal(str(amount)) > 0:
if category not in incomes:
incomes[category] = {}
if currency not in incomes[category]:
incomes[category][currency] = Decimal(0)
incomes[category][currency] += Decimal(str(amount))
else:
if category not in expenses:
expenses[category] = {}
if currency not in expenses[category]:
expenses[category][currency] = Decimal(0)
expenses[category][currency] += Decimal(str(amount))
# If the month is the one we are interested in, capture the end balance
if int(y) == int(year) and int(m) == int(month):
end_balance = {k: Decimal(v) for k, v in balances.items()}
end_balance = {
k: Decimal(v) for k, v in balances.items() if k != "Notes"
}
# Calculate accumulated sums of incomes and expenses
accumulated_incomes = {
currency: sum(incomes[cat].get(currency, Decimal(0)) for cat in incomes)
for currency in balances
if currency != "Notes"
}
accumulated_expenses = {
currency: sum(expenses[cat].get(currency, Decimal(0)) for cat in expenses)
for currency in balances
if currency != "Notes"
}
return {
@ -81,6 +93,7 @@ def get_transparency_data(data, year=None, month=None):
"expenses": expenses,
"accumulated_incomes": accumulated_incomes,
"accumulated_expenses": accumulated_expenses,
"notes": notes,
}
@ -94,7 +107,7 @@ def generate_transparency_table(result, currencies=None):
+ list(data["accumulated_incomes"].keys())
+ list(data["accumulated_expenses"].keys())
)
- {"EUR"}
- {"EUR", "Notes"}
)
)
@ -152,7 +165,8 @@ def generate_transparency_table(result, currencies=None):
# Add income rows
for category, transactions in result["incomes"].items():
html += f"<tr><td>{category}</td>"
has_notes = result["notes"].get(category)
html += f"<tr><td>{category}{'*' if has_notes else ''}</td>"
for currency in currencies:
value = transactions.get(currency, "")
if value != "":
@ -163,7 +177,8 @@ def generate_transparency_table(result, currencies=None):
# Add expense rows
for category, transactions in result["expenses"].items():
html += f"<tr><td>{category}</td>"
has_notes = result["notes"].get(category)
html += f"<tr><td>{category}{'*' if has_notes else ''}</td>"
for currency in currencies:
value = transactions.get(currency, "")
if value != "":
@ -198,4 +213,11 @@ def generate_transparency_table(result, currencies=None):
</table>
"""
if result["notes"]:
html += "<p><b>Notes:</b></p>"
html += "<ul>"
for category, footnote in result["notes"].items():
html += f"<li>{category}: {footnote}</li>"
html += "</ul>"
return html

22
main.py
View file

@ -69,6 +69,28 @@ def catch_all(path):
}
)
if path == "transparency":
finances = json.loads(
(pathlib.Path(__file__).parent / "data" / "finances.json").read_text()
)
finance_data = {}
for year in sorted(finances.keys(), reverse=True):
for month in sorted(finances[year].keys(), reverse=True):
if year not in finance_data:
finance_data[year] = {}
print(get_transparency_data(finances, year, month))
finance_data[year][month] = generate_transparency_table(
get_transparency_data(finances, year, month)
)
kwargs.update(
{
"finances": finance_data,
}
)
return render_template(f"{path}.html", **kwargs)
except TemplateNotFound:

View file

@ -85,6 +85,11 @@
donations are being used.
</p>
<div class="table-responsive">{{ finances|safe }}</div>
<p class="card-text">
Want to know how we got here? Check out all of our
<a href="/transparency.html">transparency reports</a> for more
information.
</p>
</div>
</div>

View file

@ -0,0 +1,25 @@
{% extends "base.html" %} {% block title %}Membership / Donations{% endblock %}
{% block content %}
<div class="container my-5">
<div class="text-center mb-5">
<h1 class="special-header fancy-text-primary">Transparency</h1>
<p class="lead">
Private.coffee is funded by its members and donations. We believe in
transparency and accountability. Below you can find financial reports for
each month since our inception.
</p>
</div>
{% for year, year_data in finances.items() %}
{% for month, month_data in year_data.items() %}
<div class="card shadow-sm mt-4">
<div class="card-body">
<h5 class="card-title">Transparency Report for {{ month }}/{{ year }}</h5>
<div class="table-responsive">{{ month_data|safe }}</div>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
{% endblock %}