diff --git a/app.py b/app.py index afdbe13..3a4ac11 100644 --- a/app.py +++ b/app.py @@ -2,7 +2,8 @@ import sys from pathlib import Path from datetime import datetime import re -from flask import Flask, render_template +import time +from flask import Flask, render_template, g # Add scripts directory to path to allow importing from it scripts_dir = Path(__file__).parent / "scripts" @@ -13,6 +14,35 @@ from match_payments import reconcile, fetch_sheet_data, fetch_exceptions, normal app = Flask(__name__) +@app.before_request +def start_timer(): + g.start_time = time.perf_counter() + g.steps = [] + +def record_step(name): + g.steps.append((name, time.perf_counter())) + +@app.context_processor +def inject_render_time(): + def get_render_time(): + total = time.perf_counter() - g.start_time + breakdown = [] + last_time = g.start_time + for name, timestamp in g.steps: + duration = timestamp - last_time + breakdown.append(f"{name}:{duration:.3f}s") + last_time = timestamp + + # Add remaining time as 'render' + render_duration = time.perf_counter() - last_time + breakdown.append(f"render:{render_duration:.3f}s") + + return { + "total": f"{total:.3f}", + "breakdown": " | ".join(breakdown) + } + return dict(get_render_time=get_render_time) + @app.route("/") def index(): # Redirect root to /fees for convenience while there are no other apps @@ -24,6 +54,7 @@ def fees(): payments_url = f"https://docs.google.com/spreadsheets/d/{PAYMENTS_SHEET_ID}/edit" members, sorted_months = get_members_with_fees() + record_step("fetch_members") if not members: return "No data." @@ -40,6 +71,7 @@ def fees(): # Get exceptions for formatting credentials_path = ".secret/fuj-management-bot-credentials.json" exceptions = fetch_exceptions(PAYMENTS_SHEET_ID, credentials_path) + record_step("fetch_exceptions") formatted_results = [] for name, month_fees in results: @@ -63,6 +95,8 @@ def fees(): row["months"].append({"cell": cell, "overridden": is_overridden}) formatted_results.append(row) + record_step("process_data") + return render_template( "fees.html", months=[month_labels[m] for m in sorted_months], @@ -81,12 +115,16 @@ def reconcile_view(): credentials_path = ".secret/fuj-management-bot-credentials.json" members, sorted_months = get_members_with_fees() + record_step("fetch_members") if not members: return "No data." transactions = fetch_sheet_data(PAYMENTS_SHEET_ID, credentials_path) + record_step("fetch_payments") exceptions = fetch_exceptions(PAYMENTS_SHEET_ID, credentials_path) + record_step("fetch_exceptions") result = reconcile(members, sorted_months, transactions, exceptions) + record_step("reconcile") # Format month labels month_labels = { @@ -128,6 +166,8 @@ def reconcile_view(): unmatched = result["unmatched"] import json + record_step("process_data") + return render_template( "reconcile.html", months=[month_labels[m] for m in sorted_months], @@ -148,6 +188,7 @@ def payments(): credentials_path = ".secret/fuj-management-bot-credentials.json" transactions = fetch_sheet_data(PAYMENTS_SHEET_ID, credentials_path) + record_step("fetch_payments") # Group transactions by person grouped = {} @@ -171,6 +212,7 @@ def payments(): # Sort by date descending grouped[p].sort(key=lambda x: str(x.get("date", "")), reverse=True) + record_step("process_data") return render_template( "payments.html", grouped_payments=grouped, diff --git a/templates/fees.html b/templates/fees.html index df601f1..8e9bd8d 100644 --- a/templates/fees.html +++ b/templates/fees.html @@ -148,6 +148,23 @@ .description a:hover { text-decoration: underline; } + + .footer { + margin-top: 50px; + margin-bottom: 20px; + color: #333; + font-size: 9px; + text-align: center; + width: 100%; + cursor: pointer; + user-select: none; + } + + .perf-breakdown { + display: none; + margin-top: 5px; + color: #222; + } @@ -199,6 +216,14 @@ + {% set rt = get_render_time() %} + \ No newline at end of file diff --git a/templates/payments.html b/templates/payments.html index 0ee6d77..a3c55db 100644 --- a/templates/payments.html +++ b/templates/payments.html @@ -137,6 +137,23 @@ tr:hover { background-color: #1a1a1a; } + + .footer { + margin-top: 50px; + margin-bottom: 20px; + color: #333; + font-size: 9px; + text-align: center; + width: 100%; + cursor: pointer; + user-select: none; + } + + .perf-breakdown { + display: none; + margin-top: 5px; + color: #222; + } @@ -183,6 +200,14 @@ {% endfor %} + {% set rt = get_render_time() %} + \ No newline at end of file diff --git a/templates/reconcile.html b/templates/reconcile.html index d55e168..6b97aa5 100644 --- a/templates/reconcile.html +++ b/templates/reconcile.html @@ -343,6 +343,23 @@ color: #888; font-style: italic; } + + .footer { + margin-top: 50px; + margin-bottom: 20px; + color: #333; + font-size: 9px; + text-align: center; + width: 100%; + cursor: pointer; + user-select: none; + } + + .perf-breakdown { + display: none; + margin-top: 5px; + color: #222; + } @@ -485,6 +502,15 @@ + {% set rt = get_render_time() %} + +