Skip to main content

Overview

The Reports feature transforms your transaction data into actionable insights through interactive charts, spending breakdowns, and exportable reports. Analyze your financial habits across different time periods and wallets.

Report Types

Pie Chart

Category breakdown showing spending distribution

Bar Chart

Income vs Expenses vs Transfers comparison

Line Chart

Spending trends over time (daily or monthly)

Time Range Filters

Reports support flexible time range selection:
# From app/routes/report.py:26-43
def get_date_range(time_range):
    today = date.today()
    if time_range == 'this_month':
        start_date = date(today.year, today.month, 1)
        last_day = calendar.monthrange(today.year, today.month)[1]
        end_date = date(today.year, today.month, last_day)
    elif time_range == 'last_month':
        first_of_this_month = date(today.year, today.month, 1)
        end_date = first_of_this_month - timedelta(days=1)
        start_date = date(end_date.year, end_date.month, 1)
    elif time_range == 'year':
        start_date = date(today.year, 1, 1)
        end_date = date(today.year, 12, 31)
Time ranges are calculated dynamically based on the current date, ensuring accurate month and year boundaries.

Generating Report Data

1

Request Report Data

Call the report API with filters:
GET /api/reports/data?time_range=this_month&wallet_id=all&type=expense
Parameters:
  • time_range: this_month, last_month, or year
  • wallet_id: Specific wallet ID or all
  • type: expense or income
2

System Builds Base Query

The backend constructs a filtered query (app/routes/report.py:64-70):
query = db.session.query(Transaction).filter(
    Transaction.user_id == user_id, 
    Transaction.date >= start_date, 
    Transaction.date <= end_date
)
if wallet_id != 'all' and wallet_id: 
    query = query.filter(Transaction.wallet_id == wallet_id)
3

Generate Chart Data

The system creates three chart datasets and a summary in a single response
4

Receive Comprehensive Report

Get all visualizations and statistics in one API call

Chart Implementations

Pie Chart: Category Breakdown

Shows spending distribution across categories:
# From app/routes/report.py:72-76
cat_query = query.filter(Transaction.type == db_type)
category_stats = cat_query.with_entities(
    Category.name, func.sum(Transaction.amount)
).outerjoin(Category, Transaction.category_id == Category.id)
 .group_by(Category.name).all()

pie_labels = [item[0] if item[0] else "Chưa phân loại" for item in category_stats]
pie_data = [float(item[1]) for item in category_stats]
Example response:
{
  "pie_chart": {
    "labels": ["Ăn uống", "Xăng xe", "Giải trí"],
    "data": [3500000, 1200000, 800000]
  }
}

Bar Chart: Transaction Type Summary

Compares total income, expenses, and transfers:
# From app/routes/report.py:78-88
type_stats = query.with_entities(
    Transaction.type, func.sum(Transaction.amount)
).group_by(Transaction.type).all()

totals = {'thu': 0, 'chi': 0, 'chuyen': 0}
for t_type, amount in type_stats:
    if t_type in totals:
        totals[t_type] = float(amount)
        
bar_chart_data = {
    "labels": ["Thu nhập", "Chi tiêu", "Chuyển khoản"], 
    "data": [totals['thu'], totals['chi'], totals['chuyen']]
}
Example response:
{
  "bar_chart": {
    "labels": ["Thu nhập", "Chi tiêu", "Chuyển khoản"],
    "data": [15000000, 5500000, 2000000]
  }
}
The line chart adapts based on time range:

Monthly View (for ‘this_month’ or ‘last_month’)

# From app/routes/report.py:102-108
# Group by day
daily_data = {}
for t in transactions_for_line:
    day_str = t.date.strftime('%d/%m')
    daily_data[day_str] = daily_data.get(day_str, 0) + float(t.amount)
line_chart_data["labels"] = list(daily_data.keys())
line_chart_data["data"] = list(daily_data.values())
Example response:
{
  "line_chart": {
    "labels": ["01/03", "02/03", "03/03", "04/03", "05/03"],
    "data": [250000, 180000, 320000, 150000, 420000]
  }
}

Yearly View (for ‘year’)

# From app/routes/report.py:94-100
# Group by month
monthly_data = {m: 0 for m in range(1, 13)}
for t in transactions_for_line:
    monthly_data[t.date.month] += float(t.amount)
line_chart_data["labels"] = [f"Tháng {m}" for m in range(1, 13)]
line_chart_data["data"] = [monthly_data[m] for m in range(1, 13)]
Example response:
{
  "line_chart": {
    "labels": ["Tháng 1", "Tháng 2", "Tháng 3"],
    "data": [4500000, 5200000, 5500000]
  }
}
The system automatically chooses daily or monthly grouping based on the selected time range, optimizing readability.

Top Spending Categories

The report includes a ranked list of categories:
# From app/routes/report.py:110-121
top_cat_query = query.filter(Transaction.type == 'chi')
    .with_entities(Category.name, func.sum(Transaction.amount))
    .outerjoin(Category, Transaction.category_id == Category.id)
    .group_by(Category.name)
    .order_by(func.sum(Transaction.amount).desc()).all()

total_expense_period = sum([item[1] for item in top_cat_query])
top_spending_list = [
    {
        "category": name if name else "Chưa phân loại", 
        "amount": float(amount), 
        "amount_formatted": "{:,.0f} đ".format(amount).replace(",", "."), 
        "percent": round((amount / total_expense_period * 100), 1)
    } 
    for name, amount in top_cat_query
]
Example response:
{
  "top_spending": [
    {
      "category": "Ăn uống",
      "amount": 3500000,
      "amount_formatted": "3.500.000 đ",
      "percent": 63.6
    },
    {
      "category": "Xăng xe",
      "amount": 1200000,
      "amount_formatted": "1.200.000 đ",
      "percent": 21.8
    }
  ]
}

Financial Summary

Each report includes a summary section:
{
  "summary": {
    "total_income": 15000000,
    "total_expense": 5500000,
    "balance": 9500000
  }
}
The balance represents net cash flow for the selected period.

Export to Excel

Export your transaction data to Excel format:
GET /api/reports/export/excel?time_range=this_month
The system uses pandas to generate Excel files:
# From app/routes/report.py:138-163
transactions = query.all()

data_list = []
for t in transactions:
    loai = "Chi tiêu" if t.type == 'chi' else ("Thu nhập" if t.type == 'thu' else "Chuyển khoản")
    data_list.append({
        "Ngày": t.date.strftime('%d/%m/%Y'), 
        "Danh mục": t.category.name if t.category else "Chưa phân loại", 
        "Nội dung": t.description, 
        "Số tiền": t.amount, 
        "Loại": loai, 
        "Ví": t.wallet.name if t.wallet else "Không xác định"
    })

df = pd.DataFrame(data_list)
output = BytesIO()
with pd.ExcelWriter(output, engine='openpyxl') as writer:
    df.to_excel(writer, index=False, sheet_name='Báo cáo')
output.seek(0)
return send_file(output, download_name=f"Bao_cao_{time_range}_{date.today()}.xlsx", as_attachment=True)
The Excel file includes all transactions in the selected time range with proper Vietnamese formatting.

Export to PDF

Generate a printable PDF report:
GET /api/reports/export/pdf?time_range=this_month
The PDF export renders an HTML template with transaction data:
# From app/routes/report.py:165-192
transactions = db.session.query(Transaction).filter(
    Transaction.user_id == user_id, 
    Transaction.date >= start_date, 
    Transaction.date <= end_date
).outerjoin(Category, Transaction.category_id == Category.id)
 .outerjoin(Wallet, Transaction.wallet_id == Wallet.id)
 .order_by(Transaction.date.desc()).all()
     
total_income = sum(t.amount for t in transactions if t.type == 'thu')
total_expense = sum(t.amount for t in transactions if t.type == 'chi')

return render_template('user/pdf_report.html', 
    transactions=transactions, 
    start_date=start_date.strftime('%d/%m/%Y'), 
    end_date=end_date.strftime('%d/%m/%Y'), 
    total_income=total_income, 
    total_expense=total_expense, 
    user_name=session.get('user_name', 'Người dùng'), 
    today=date.today().strftime('%d/%m/%Y')
)
The template (user/pdf_report.html) formats the data for printing.

Complete API Response

A full report API response combines all components:
{
  "pie_chart": {
    "labels": ["Ăn uống", "Xăng xe", "Giải trí"],
    "data": [3500000, 1200000, 800000]
  },
  "bar_chart": {
    "labels": ["Thu nhập", "Chi tiêu", "Chuyển khoản"],
    "data": [15000000, 5500000, 2000000]
  },
  "line_chart": {
    "labels": ["01/03", "02/03", "03/03", "04/03", "05/03"],
    "data": [250000, 180000, 320000, 150000, 420000]
  },
  "top_spending": [
    {
      "category": "Ăn uống",
      "amount": 3500000,
      "amount_formatted": "3.500.000 đ",
      "percent": 63.6
    }
  ],
  "summary": {
    "total_income": 15000000,
    "total_expense": 5500000,
    "balance": 9500000
  }
}

AI-Powered Dashboard Insights

The dashboard includes AI-generated financial insights:
GET /api/dashboard-insights
This feature uses the chatbot engine to generate personalized advice:
# From app/routes/ai.py:168-225
transactions = Transaction.query.filter(
    Transaction.user_id == user_id,
    Transaction.date >= first_day
).all()

total_income = sum(t.amount for t in transactions if t.type == 'thu')
total_expense = sum(t.amount for t in transactions if t.type == 'chi')

context_text = f"Thống kê tháng này - Thu nhập: {int(total_income):,} VND. Chi tiêu: {int(total_expense):,} VND."

question = (
    "Dựa vào số liệu trên, hãy đưa ra 3 lời khuyên tài chính. "
    "YÊU CẦU NGHIÊM NGẶT ĐỂ ĐỊNH DẠNG: "
    "1. TUYỆT ĐỐI KHÔNG chào hỏi, KHÔNG xưng hô. "
    "2. TUYỆT ĐỐI KHÔNG có câu dẫn dắt. "
    "3. Trả về ĐÚNG 3 dòng, mỗi dòng là một lời khuyên trực tiếp. "
    "4. Độ dài tối đa: Dưới 15 chữ cho MỖI dòng. "
    "5. Không dùng markdown, không dùng icon."
)
Response:
{
  "status": "success",
  "data": [
    "Chi tiêu đang vượt 36% so với thu nhập",
    "Nên cắt giảm ăn uống xuống 2.5 triệu",
    "Cố gắng tiết kiệm ít nhất 20% thu nhập"
  ]
}
The AI prompt is heavily constrained to return exactly 3 short, actionable tips in a consistent format for dashboard display.

Disabling AI Insights

Users can disable AI suggestions in settings:
# From app/routes/ai.py:171-177
user_setting = UserSetting.query.get(user_id)
if user_setting and user_setting.ai_suggestions == 0:
    return jsonify({
        'status': 'disabled',
        'message': 'Tính năng AI đang bị tắt.'
    })

Performance Optimizations

The reports system uses several optimizations:

1. Single Query for Type Statistics

# From app/routes/report.py:78-83
# Groups all transaction types in ONE query instead of 3 separate queries
type_stats = query.with_entities(
    Transaction.type, func.sum(Transaction.amount)
).group_by(Transaction.type).all()

2. Explicit JOIN Operations

# From app/routes/report.py:173-178
# Explicitly joins related tables to prevent N+1 query problems
.outerjoin(Category, Transaction.category_id == Category.id)
.outerjoin(Wallet, Transaction.wallet_id == Wallet.id)

3. Aggregation at Database Level

All summation and grouping happens in SQL rather than Python, leveraging database performance.

User Interface Flow

1

Access Reports Page

Navigate to /reports in the web interface
2

Select Filters

Choose time range (this month, last month, year) and wallet
3

View Interactive Charts

Charts render dynamically using the report data API
4

Analyze Top Spending

Review the ranked category list with percentages
5

Export if Needed

Download Excel or PDF for external analysis or record-keeping

Build docs developers (and LLMs) love