The Kakeibo Budget Plan: Your Life Will not be the Same from Now On
家計簿
Building a Kakeibo Budget System Application Using Python and Qt6
1. Introduction to the Kakeibo Money Management System
Kakeibo (pronounced "kah-keh-bo") is a traditional Japanese money management technique that dates back over 100 years. Developed by Hani Motoko, Japan’s first female journalist, Kakeibo is designed to help individuals save money by increasing mindfulness around spending. The word "Kakeibo"
translates to "household financial ledger" in English, and it’s a method used to track income, expenses, and savings, with an emphasis on reflecting on your spending habits to make better financial decisions in the future.
The Kakeibo method divides expenses into four categories:
Essentials (e.g., groceries, rent, utilities)
Optional (e.g., dining out, entertainment)
Culture (e.g., books, museum visits)
Unexpected (e.g., emergency repairs, medical expenses)
By categorizing expenses and reflecting on them, the Kakeibo system encourages thoughtful spending and helps you achieve savings goals.
2. Understanding Qt6 and Its Role in the Application
Qt6 is a modern, cross-platform application framework that allows developers to create applications with a native look and feel. It is particularly well-suited for building GUI (Graphical User Interface) applications in Python, thanks to the PyQt6 bindings.
PyQt6 provides Python bindings for the Qt application framework, allowing Python developers to leverage the power of Qt for their applications. With PyQt6, you can build fully-featured desktop applications with rich user interfaces. In this tutorial, we use PyQt6 to create the Kakeibo budget system application.
3. Application Overview
The Kakeibo budget system application allows users to:
Record income and expenses.
Categorize expenses according to the Kakeibo system.
Set and track savings goals.
Calculate actual savings.
Save and load data from CSV files.
Print a summary report of income, expenses, and savings.
The application consists of several key components, each with its own function.
4. Detailed Explanation of Functions
Below is a breakdown of each function in the application, explaining how it contributes to the overall functionality.
4.1 init(self): Application Initialization
The init method initializes the application. It sets up the main window, creates all the UI elements, and connects the buttons to their respective functions. The main components initialized here include:
Title Label: Displays the title of the application.
Income Table: A table to record income data.
Expense Table: A table to record expense data.
Savings Tracker: Inputs for setting and displaying savings goals and actual savings.
Control Buttons: Buttons for adding rows, calculating summaries, clearing the screen, saving/loading data, printing reports, and exiting the application.
4.2 add_income_row(self): Adding a New Income Row
This function adds a new row to the Income Table. Each row represents a new income entry, with columns for the date, income source, and income amount. The date is auto-filled with the current date, though the user can change it.
4.3 add_expense_row(self): Adding a New Expense Row
This function adds a new row to the Expense Table. Each row represents a new expense entry, with columns for the date, category (using a dropdown menu), description, and amount. Like the income rows, the date is auto-filled.
4.4 calculate_summary(self): Calculating Income, Expenses, and Savings
This critical function calculates the total income, total expenses, and actual savings. It:
Sums all values in the income and expense tables.
Subtracts total expenses from total income to calculate actual savings.
Updates the Savings Actual field with the calculated savings.
Displays a summary message with the totals.
Error handling ensures that any issues during the calculation (e.g., non-numeric inputs) are caught and reported to the user.
4.5 clear_screen(self): Clearing the Tables and Fields
This function resets the application’s UI to a blank state. It:
Clears the contents of the Income and Expense tables, leaving only the header rows.
Resets the Savings Goal and Savings Actual fields.
Re-populates the date fields with the current date.
4.6 print_report(self): Printing the Budget Summary
This function generates and prints a summary report of the income, expenses, and savings. It:
Collects all data from the Income and Expense tables.
Generates a textual summary of the data.
Sends the summary to the printer using the QPrinter and QPrintDialog classes.
4.7 generate_report_text(self): Generating the Report Text
This helper function assembles the text for the report. It reads data from the tables and formats it into a structured report, which is then passed to the print_report function for printing.
4.8 save_to_csv(self): Saving Data to a CSV File
This function allows users to save their income, expenses, and savings data to a CSV file. It:
Opens a file dialog to specify the save location.
Writes the data from the tables, along with the savings totals, to the CSV file.
4.9 load_from_csv(self): Loading Data from a CSV File
This function loads data from a previously saved CSV file. It:
Opens a file dialog to select the CSV file.
Reads the data from the file and populates the Income and Expense tables.
Updates the Savings Goal and Savings Actual fields with the loaded data.
4.10 context_menu_income_table(self, position): Managing Context Menu for Income Table
This function provides a context menu (right-click menu) for the Income Table, allowing users to delete rows.
4.11 context_menu_expense_table(self, position): Managing Context Menu for Expense Table
This function provides a context menu (right-click menu) for the Expense Table, allowing users to delete rows.
4.12 exit_app(self): Exiting the Application
This function closes the application when the "Exit" button is clicked.
5. Wrapping Up: Integrating Qt6 with Python for a Functional Kakeibo System
This application demonstrates how Python, combined with Qt6, can be used to create a fully functional, user-friendly desktop application. By leveraging the power of PyQt6, we built an application that not only automates the Kakeibo budgeting process but also provides valuable tools for financial reflection and decision-making.
The modular nature of the functions makes the application easy to maintain and extend. You could, for example, add additional features like graphical reports or integration with other financial tools.
With this tutorial, you now have a complete understanding of both the Kakeibo system and the technical aspects of the application. This knowledge will empower you to further customize and enhance the application as needed.
Kakeibo is a Japanese budgeting method designed to help individuals save money and manage finances more effectively.
The term "Kakeibo" translates to "household financial ledger" and has been used in Japan for over a century.
Kakeibo is more than just a budgeting tool; it’s a holistic approach emphasizing awareness, control, and mindful spending.
The concept was introduced in 1904 by Hani Motoko, a Japanese journalist and women’s rights activist.
Kakeibo remains a cultural staple in Japan, with many households still using it today.
Manual recording: One of the core principles is manually writing down income, expenses, and savings goals in a dedicated notebook.
This manual recording increases mindfulness, encouraging intentional spending decisions.
Categorization: Expenses are divided into four main categories: necessities, optional, culture, and unexpected.
Categorizing expenses helps identify areas of overspending and prioritize essential costs.
Reflection: At the end of each month, Kakeibo encourages reviewing and reflecting on financial habits.
Regular reflection helps develop a deeper understanding of one’s relationship with money and promotes positive financial changes.
Goal setting: Setting specific savings goals each month is a key aspect, helping to stay motivated and focused.
Monitoring progress toward savings goals ensures consistent effort toward long-term financial objectives.
Mindfulness and intentionality: Kakeibo aligns financial practices with personal values and priorities.
Conscious spending ensures that money is used for what truly matters, such as saving for significant purchases or supporting important causes.
Implementation: Start by selecting a dedicated notebook, creating categories, and setting up a template for tracking income, expenses, and goals.
Set savings targets at the beginning of each month, record income, and diligently track expenses.
Review spending at the end of the month, reflect on financial behavior, and make necessary adjustments for the next month.
Benefits: Increased financial awareness, improved spending habits, better savings, and a greater sense of control and purpose.
Kakeibo provides a proven framework for achieving financial stability, making it an effective method for cultivating economic well-being.
Python Code |
import sys
import csv
import datetime
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel, QTableWidget,
QTableWidgetItem, QPushButton, QHBoxLayout, QComboBox, QLineEdit, QMessageBox, QHeaderView,
QFileDialog, QMenu, QTextEdit
)
from PyQt6.QtCore import Qt
from PyQt6.QtPrintSupport import QPrinter, QPrintDialog
class BudgetSystem(QMainWindow):
def init(self):
super().__init__()
self.setWindowTitle("Kakeibo Budget Plan")
self.setGeometry(100, 100, 800, 600)
self.setStyleSheet("background-color: white; color: black;")
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QVBoxLayout(self.central_widget)
# Title Label
self.title_label = QLabel("Kakeibo Budget Plan", self)
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: black;")
self.layout.addWidget(self.title_label)
# Income Table
self.income_table = QTableWidget(1, 3)
self.income_table.setHorizontalHeaderLabels(["Date", "Income Source", "Income Amount"])
self.income_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.layout.addWidget(self.income_table)
# Expense Table
self.expense_table = QTableWidget(1, 4)
self.expense_table.setHorizontalHeaderLabels(["Date", "Category", "Description", "Amount"])
self.expense_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.layout.addWidget(self.expense_table)
# Savings Tracker
self.savings_layout = QHBoxLayout()
self.savings_goal_label = QLabel("Savings Goal:")
self.savings_goal_input = QLineEdit()
self.savings_actual_label = QLabel("Savings Actual:")
self.savings_actual_input = QLineEdit()
self.savings_actual_input.setReadOnly(True)
self.savings_goal_label.setStyleSheet("color: black;")
self.savings_actual_label.setStyleSheet("color: black;")
self.savings_layout.addWidget(self.savings_goal_label)
self.savings_layout.addWidget(self.savings_goal_input)
self.savings_layout.addWidget(self.savings_actual_label)
self.savings_layout.addWidget(self.savings_actual_input)
self.layout.addLayout(self.savings_layout)
# Category Dropdown Setup
self.expense_table.setCellWidget(0, 1, QComboBox())
self.expense_table.cellWidget(0, 1).addItems(["Essentials", "Optional", "Culture", "Unexpected"])
# Autofill date with current date
current_date = datetime.datetime.now().strftime('%Y-%m-%d')
self.income_table.setItem(0, 0, QTableWidgetItem(current_date))
self.expense_table.setItem(0, 0, QTableWidgetItem(current_date))
# Add Row Buttons
self.button_layout = QHBoxLayout()
self.add_income_button = QPushButton("Add Income Row")
self.add_expense_button = QPushButton("Add Expense Row")
self.calculate_button = QPushButton("Calculate Summary")
self.clear_button = QPushButton("Clear Screen")
self.print_button = QPushButton("Print Report")
self.save_button = QPushButton("Save to CSV")
self.load_button = QPushButton("Load from CSV")
self.exit_button = QPushButton("Exit")
buttons = [
self.add_income_button, self.add_expense_button, self.calculate_button,
self.clear_button, self.print_button, self.save_button, self.load_button, self.exit_button
]
for button in buttons:
button.setStyleSheet("background-color: black; color: white;")
self.button_layout.addWidget(self.add_income_button)
self.button_layout.addWidget(self.add_expense_button)
self.button_layout.addWidget(self.calculate_button)
self.button_layout.addWidget(self.clear_button)
self.button_layout.addWidget(self.print_button)
self.button_layout.addWidget(self.save_button)
self.button_layout.addWidget(self.load_button)
self.button_layout.addWidget(self.exit_button)
self.layout.addLayout(self.button_layout)
# Connect Buttons to Functions
self.add_income_button.clicked.connect(self.add_income_row)
self.add_expense_button.clicked.connect(self.add_expense_row)
self.calculate_button.clicked.connect(self.calculate_summary)
self.clear_button.clicked.connect(self.clear_screen)
self.print_button.clicked.connect(self.print_report)
self.save_button.clicked.connect(self.save_to_csv)
self.load_button.clicked.connect(self.load_from_csv)
self.exit_button.clicked.connect(self.exit_app)
# Context Menu for deleting rows
self.income_table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.income_table.customContextMenuRequested.connect(self.context_menu_income_table)
self.expense_table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.expense_table.customContextMenuRequested.connect(self.context_menu_expense_table)
def add_income_row(self):
row_position = self.income_table.rowCount()
self.income_table.insertRow(row_position)
self.income_table.setItem(row_position, 0, QTableWidgetItem(datetime.datetime.now().strftime('%Y-%m-%d')))
def add_expense_row(self):
row_position = self.expense_table.rowCount()
self.expense_table.insertRow(row_position)
category_combo = QComboBox()
category_combo.addItems(["Essentials", "Optional", "Culture", "Unexpected"])
self.expense_table.setCellWidget(row_position, 1, category_combo)
self.expense_table.setItem(row_position, 0, QTableWidgetItem(datetime.datetime.now().strftime('%Y-%m-%d')))
def calculate_summary(self):
try:
total_income = 0
total_expenses = 0
# Calculate Total Income (skip header)
for row in range(1, self.income_table.rowCount()):
amount_item = self.income_table.item(row, 2)
if amount_item and amount_item.text().strip():
total_income += float(amount_item.text().strip())
# Calculate Total Expenses (skip header)
for row in range(1, self.expense_table.rowCount()):
amount_item = self.expense_table.item(row, 3)
if amount_item and amount_item.text().strip():
total_expenses += float(amount_item.text().strip())
# Calculate Savings
savings_goal = float(self.savings_goal_input.text().strip()) if self.savings_goal_input.text().strip() else 0
actual_savings = total_income - total_expenses
self.savings_actual_input.setText(f"{actual_savings:,.2f}")
summary_message = (
f"Total Income: ${total_income:,.2f}\n"
f"Total Expenses: ${total_expenses:,.2f}\n"
f"Savings Goal: ${savings_goal:,.2f}\n"
f"Actual Savings: ${actual_savings:,.2f}\n"
f"Savings Difference: ${savings_goal - actual_savings:,.2f}"
)
QMessageBox.information(self, "Budget Summary", summary_message)
except Exception as e:
QMessageBox.warning(self, "Calculation Error", f"An error occurred during calculation: {e}")
def clear_screen(self):
self.income_table.setRowCount(1)
self.expense_table.setRowCount(1)
for i in range(self.income_table.columnCount()):
self.income_table.setItem(0, i, QTableWidgetItem(""))
for i in range(self.expense_table.columnCount()):
self.expense_table.setItem(0, i, QTableWidgetItem(""))
self.savings_goal_input.clear()
self.savings_actual_input.clear()
current_date = datetime.datetime.now().strftime('%Y-%m-%d')
self.income_table.setItem(0, 0, QTableWidgetItem(current_date))
self.expense_table.setItem(0, 0, QTableWidgetItem(current_date))
def print_report(self):
try:
printer = QPrinter()
dialog = QPrintDialog(printer, self)
if dialog.exec():
report_text = self.generate_report_text()
editor = QTextEdit()
editor.setText(report_text)
editor.document().print(printer)
except Exception as e:
QMessageBox.warning(self, "Print Error", f"An error occurred while printing: {e}")
def generate_report_text(self):
report_lines = ["Kakeibo Budget Plan Report\n", "="*30, "\n"]
report_lines.append("Income:\n")
for row in range(1, self.income_table.rowCount()):
row_data = [self.income_table.item(row, i).text() if self.income_table.item(row, i) else "" for i in range(3)]
report_lines.append("\t".join(row_data) + "\n")
report_lines.append("\n")
report_lines.append("Expenses:\n")
for row in range(1, self.expense_table.rowCount()):
category = self.expense_table.cellWidget(row, 1).currentText() if self.expense_table.cellWidget(row, 1) else ""
description = self.expense_table.item(row, 2).text() if self.expense_table.item(row, 2) else ""
amount = self.expense_table.item(row, 3).text() if self.expense_table.item(row, 3) else ""
report_lines.append(f"{category}\t{description}\t{amount}\n")
report_lines.append("\n")
report_lines.append(f"Savings Goal: ${self.savings_goal_input.text()}\n")
report_lines.append(f"Actual Savings: ${self.savings_actual_input.text()}\n")
return "".join(report_lines)
def save_to_csv(self):
path, _ = QFileDialog.getSaveFileName(self, "Save File", "", "CSV Files (*.csv)")
if path:
try:
with open(path, mode='w', newline='') as file:
writer = csv.writer(file)
writer.writerow(["Income Table"])
writer.writerow([self.income_table.horizontalHeaderItem(i).text() for i in range(self.income_table.columnCount())])
for row in range(1, self.income_table.rowCount()): # Skip header
row_data = []
for column in range(self.income_table.columnCount()):
item = self.income_table.item(row, column)
row_data.append(item.text() if item else '')
writer.writerow(row_data)
writer.writerow([]) # Empty row as separator
writer.writerow(["Expense Table"])
writer.writerow([self.expense_table.horizontalHeaderItem(i).text() for i in range(self.expense_table.columnCount())])
for row in range(1, self.expense_table.rowCount()): # Skip header
row_data = []
row_data.append(self.expense_table.item(row, 0).text() if self.expense_table.item(row, 0) else '')
row_data.append(self.expense_table.cellWidget(row, 1).currentText() if self.expense_table.cellWidget(row, 1) else '')
row_data.append(self.expense_table.item(row, 2).text() if self.expense_table.item(row, 2) else '')
row_data.append(self.expense_table.item(row, 3).text() if self.expense_table.item(row, 3) else '')
writer.writerow(row_data)
writer.writerow([]) # Empty row as separator
writer.writerow(["Summary"])
writer.writerow(["Savings Goal", self.savings_goal_input.text()])
writer.writerow(["Actual Savings", self.savings_actual_input.text()])
QMessageBox.information(self, "File Saved", f"File saved successfully to {path}")
except Exception as e:
QMessageBox.warning(self, "Save Error", f"An error occurred while saving the file: {e}")
def load_from_csv(self):
path, _ = QFileDialog.getOpenFileName(self, "Load File", "", "CSV Files (*.csv)")
if path:
try:
with open(path, mode='r', newline='') as file:
reader = csv.reader(file)
self.income_table.setRowCount(0)
self.expense_table.setRowCount(0)
income_section = True
for row in reader:
if not row: # Skip empty rows
income_section = False
continue
if income_section:
if row[0] == "Income Table":
continue
self.income_table.insertRow(self.income_table.rowCount())
for i, item in enumerate(row):
self.income_table.setItem(self.income_table.rowCount() - 1, i, QTableWidgetItem(item))
else:
if row[0] == "Expense Table":
continue
self.expense_table.insertRow(self.expense_table.rowCount())
for i, item in enumerate(row):
if i == 1:
category_combo = QComboBox()
category_combo.addItems(["Essentials", "Optional", "Culture", "Unexpected"])
category_combo.setCurrentText(item)
self.expense_table.setCellWidget(self.expense_table.rowCount() - 1, i, category_combo)
else:
self.expense_table.setItem(self.expense_table.rowCount() - 1, i, QTableWidgetItem(item))
if row[0] == "Summary":
self.savings_goal_input.setText(row[1])
self.savings_actual_input.setText(row[1])
QMessageBox.information(self, "File Loaded", "CSV file loaded successfully.")
except Exception as e:
QMessageBox.warning(self, "Load Error", f"An error occurred while loading the file: {e}")
def context_menu_income_table(self, position):
menu = QMenu()
delete_action = menu.addAction("Delete Row")
action = menu.exec(self.income_table.mapToGlobal(position))
if action == delete_action:
self.income_table.removeRow(self.income_table.currentRow())
def context_menu_expense_table(self, position):
menu = QMenu()
delete_action = menu.addAction("Delete Row")
action = menu.exec(self.expense_table.mapToGlobal(position))
if action == delete_action:
self.expense_table.removeRow(self.expense_table.currentRow())
def exit_app(self):
self.close()
if name == "__main__":
app = QApplication(sys.argv)
window = BudgetSystem()
sys.exit(app.exec())
John Nunez is a retired technology instructor, blogger and writer from Pennsylvania, USA, holding multiple certifications from Microsoft, Linux, and other leading organizations. As an AI evangelist, he remains passionate about the transformative power of technology, particularly AI, to advance science and foster greater understanding among people.