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

View file

@ -9,6 +9,7 @@ def get_latest_month(data):
return int(latest_month), int(latest_year) return int(latest_month), int(latest_year)
def get_transparency_data(data, year=None, month=None): def get_transparency_data(data, year=None, month=None):
if year is None: if year is None:
year = max(data.keys()) year = max(data.keys())
@ -23,6 +24,7 @@ def get_transparency_data(data, year=None, month=None):
balances = {} balances = {}
incomes = {} incomes = {}
expenses = {} expenses = {}
notes = {}
start_balance = {} start_balance = {}
end_balance = {} end_balance = {}
@ -37,10 +39,16 @@ def get_transparency_data(data, year=None, month=None):
# If the month is the one we are interested in, capture the start balance # If the month is the one we are interested in, capture the start balance
if int(y) == int(year) and int(m) == int(month): 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 category in data[y][m]:
for currency, amount in data[y][m][category].items(): for currency, amount in data[y][m][category].items():
if currency == "Notes":
if int(y) == int(year) and int(m) == int(month):
notes[category] = amount
else:
if currency not in balances: if currency not in balances:
balances[currency] = Decimal(0) balances[currency] = Decimal(0)
balances[currency] += Decimal(str(amount)) balances[currency] += Decimal(str(amount))
@ -62,16 +70,20 @@ def get_transparency_data(data, year=None, month=None):
# If the month is the one we are interested in, capture the end balance # If the month is the one we are interested in, capture the end balance
if int(y) == int(year) and int(m) == int(month): 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 # Calculate accumulated sums of incomes and expenses
accumulated_incomes = { accumulated_incomes = {
currency: sum(incomes[cat].get(currency, Decimal(0)) for cat in incomes) currency: sum(incomes[cat].get(currency, Decimal(0)) for cat in incomes)
for currency in balances for currency in balances
if currency != "Notes"
} }
accumulated_expenses = { accumulated_expenses = {
currency: sum(expenses[cat].get(currency, Decimal(0)) for cat in expenses) currency: sum(expenses[cat].get(currency, Decimal(0)) for cat in expenses)
for currency in balances for currency in balances
if currency != "Notes"
} }
return { return {
@ -81,6 +93,7 @@ def get_transparency_data(data, year=None, month=None):
"expenses": expenses, "expenses": expenses,
"accumulated_incomes": accumulated_incomes, "accumulated_incomes": accumulated_incomes,
"accumulated_expenses": accumulated_expenses, "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_incomes"].keys())
+ list(data["accumulated_expenses"].keys()) + list(data["accumulated_expenses"].keys())
) )
- {"EUR"} - {"EUR", "Notes"}
) )
) )
@ -152,7 +165,8 @@ def generate_transparency_table(result, currencies=None):
# Add income rows # Add income rows
for category, transactions in result["incomes"].items(): 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: for currency in currencies:
value = transactions.get(currency, "") value = transactions.get(currency, "")
if value != "": if value != "":
@ -163,7 +177,8 @@ def generate_transparency_table(result, currencies=None):
# Add expense rows # Add expense rows
for category, transactions in result["expenses"].items(): 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: for currency in currencies:
value = transactions.get(currency, "") value = transactions.get(currency, "")
if value != "": if value != "":
@ -198,4 +213,11 @@ def generate_transparency_table(result, currencies=None):
</table> </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 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) return render_template(f"{path}.html", **kwargs)
except TemplateNotFound: except TemplateNotFound:

View file

@ -85,6 +85,11 @@
donations are being used. donations are being used.
</p> </p>
<div class="table-responsive">{{ finances|safe }}</div> <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>
</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 %}