luckykoi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/lucky_koi +3 -0
- data/lib/lucky_koi/version.rb +3 -0
- data/lib/lucky_koi.rb +234 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1e914aeabaa0eb6201d4a4a6cebf457c526e38751b9cb8d4cfd28efd56c04c50
|
|
4
|
+
data.tar.gz: ba3a7f7933a13ccf2e60420635c22ecd0a6646b2907537fba628da0410e141e4
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 7137d40a6d2fe35cd509cabb728342ba5fb0829ecd2a5370f418f77b1ed3058cc1aff8d1a5c929fda33f520747202326e873abcc605a53afbe9f0bb08dc9ef61
|
|
7
|
+
data.tar.gz: 77cacc1354c81c434cc1bf95882ae3475f32afddacc7e9e2f4d26edbe4d48fa7b10c7eb9de5d17887d4fd3d9b0223dbf27d538ab9f23cab31743760ae30b1695
|
data/bin/lucky_koi
ADDED
data/lib/lucky_koi.rb
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
require 'glimmer-dsl-libui'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
class LuckyKoi
|
|
6
|
+
include Glimmer
|
|
7
|
+
FILE_NAME = 'lucky_koi_vault.json'
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@currency = "$"
|
|
11
|
+
@balance, @income_baseline, @transactions = 0.0, 0.0, []
|
|
12
|
+
@selected_index = nil
|
|
13
|
+
load_data
|
|
14
|
+
@income_baseline <= 0 ? launch_setup_window : launch_main_app
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# --- 1. DATA: 10 GLOBAL MISSIONS ---
|
|
18
|
+
def all_missions
|
|
19
|
+
[
|
|
20
|
+
{ tier: "Foundation", name: "The 48-Hour Rule", goal: "No impulse > 5% of pay.", advice: "Wait 48 hours before buying non-essentials." },
|
|
21
|
+
{ tier: "Foundation", name: "Subscription Audit", goal: "Cancel 2 unused services.", advice: "Small recurring leaks drain wealth." },
|
|
22
|
+
{ tier: "Foundation", name: "Budget Benchmark", goal: "Track 30 days of history.", advice: "Management requires measurement." },
|
|
23
|
+
{ tier: "Security", name: "1-Month Anchor", goal: "Balance >= Baseline.", advice: "Your first month of savings is your safety net." },
|
|
24
|
+
{ tier: "Security", name: "The 50/30/20 Rule", goal: "Needs < 50% of Income.", advice: "Fixed costs shouldn't exceed half of pay." },
|
|
25
|
+
{ tier: "Security", name: "Insurance Shield", goal: "Health/Life setup.", advice: "Protect your downside from emergencies." },
|
|
26
|
+
{ tier: "Growth", name: "3-Month Shield", goal: "Ratio >= 3.0x.", advice: "Survive a job loss without stress." },
|
|
27
|
+
{ tier: "Growth", name: "Investment Seed", goal: "Automate 10% to Assets.", advice: "Let compound interest begin its work." },
|
|
28
|
+
{ tier: "Mastery", name: "6-Month Fortress", goal: "Ratio >= 6.0x.", advice: "You are now financially bulletproof." },
|
|
29
|
+
{ tier: "Mastery", name: "Freedom Year", goal: "Ratio >= 12.0x.", advice: "One year of life saved. Take major risks." }
|
|
30
|
+
]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def current_suggested_mission
|
|
34
|
+
r = current_ratio
|
|
35
|
+
return all_missions[0] if @transactions.size < 5
|
|
36
|
+
if r < 0.5 then all_missions[1]
|
|
37
|
+
elsif r < 1.0 then all_missions[3]
|
|
38
|
+
elsif r < 2.0 then all_missions[4]
|
|
39
|
+
elsif r < 3.0 then all_missions[6]
|
|
40
|
+
elsif r < 5.0 then all_missions[7]
|
|
41
|
+
elsif r < 10.0 then all_missions[8]
|
|
42
|
+
else all_missions[9]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# --- 2. SETUP MODAL ---
|
|
47
|
+
def launch_setup_window
|
|
48
|
+
@setup_win = window('Setup', 350, 150) {
|
|
49
|
+
margined true
|
|
50
|
+
vertical_box {
|
|
51
|
+
label("Monthly Income Baseline:")
|
|
52
|
+
@setup_input = entry
|
|
53
|
+
button("Confirm & Open") {
|
|
54
|
+
on_clicked {
|
|
55
|
+
val = @setup_input.text.to_f
|
|
56
|
+
if val > 0
|
|
57
|
+
@income_baseline = val; save_data; @setup_win.destroy; launch_main_app
|
|
58
|
+
else
|
|
59
|
+
msg_box('Error', 'Baseline must be > 0')
|
|
60
|
+
end
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
@setup_win.show
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# --- 3. MAIN APP ---
|
|
69
|
+
def launch_main_app
|
|
70
|
+
@main_window = window('LuckyKoi: Sovereign Architect 🎏', 1050, 900) {
|
|
71
|
+
margined true
|
|
72
|
+
vertical_box {
|
|
73
|
+
tab {
|
|
74
|
+
stretchy true
|
|
75
|
+
|
|
76
|
+
tab_item('📊 Analytics') {
|
|
77
|
+
vertical_box {
|
|
78
|
+
padded true
|
|
79
|
+
|
|
80
|
+
# --- REAL-TIME VISUAL BAR ---
|
|
81
|
+
group('💹 Real-Time Savings Efficiency (Income vs Expenses)') {
|
|
82
|
+
stretchy false
|
|
83
|
+
vertical_box {
|
|
84
|
+
@efficiency_bar = progress_bar
|
|
85
|
+
@efficiency_lbl = label("Efficiency: 0%")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
horizontal_box {
|
|
90
|
+
stretchy false
|
|
91
|
+
group('🎎 Identity') {
|
|
92
|
+
vertical_box {
|
|
93
|
+
@rank_lbl = label("RANK: #{calculate_rank}")
|
|
94
|
+
@ratio_lbl = label("Ratio: #{current_ratio}x")
|
|
95
|
+
@impulse_lbl = label("Impulse-Free: #{days_since_big_spend} Days")
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
group('🎯 Active Mission') {
|
|
99
|
+
vertical_box {
|
|
100
|
+
@active_m_lbl = label("MISSION: #{current_suggested_mission[:name]}")
|
|
101
|
+
@active_g_lbl = label("GOAL: #{current_suggested_mission[:goal]}", size: 10)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# --- FIXED UI TEXTBOXES ---
|
|
107
|
+
group('🧧 Management') {
|
|
108
|
+
stretchy false
|
|
109
|
+
vertical_box {
|
|
110
|
+
padded true
|
|
111
|
+
horizontal_box {
|
|
112
|
+
# Fixed width container for Amount
|
|
113
|
+
vertical_box {
|
|
114
|
+
label 'Amount:'
|
|
115
|
+
horizontal_box { @amt_in = entry; stretchy false }
|
|
116
|
+
stretchy false
|
|
117
|
+
}
|
|
118
|
+
# Stretching container for Details
|
|
119
|
+
vertical_box {
|
|
120
|
+
label 'Details:'
|
|
121
|
+
@desc_in = entry
|
|
122
|
+
stretchy true
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
horizontal_box {
|
|
126
|
+
button('✨ Income') { on_clicked { handle_entry(:inc) } }
|
|
127
|
+
button('💢 Expense') { on_clicked { handle_entry(:exp) } }
|
|
128
|
+
@save_btn = button('💾 Save Edit') { enabled false; on_clicked { save_edit } }
|
|
129
|
+
@del_btn = button('🗑️ Delete') { enabled false; on_clicked { run_delete } }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@hist_table = table {
|
|
135
|
+
text_column('Type'); text_column('Details'); text_column('Amount'); text_column('Date')
|
|
136
|
+
cell_rows @transactions.map { |t| t.map(&:to_s) }
|
|
137
|
+
on_selection_changed { |_, sel| @selected_index = sel; load_selection if sel }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
tab_item('🏆 Tracker') {
|
|
143
|
+
vertical_box {
|
|
144
|
+
padded true
|
|
145
|
+
group('📍 Your Strategy') {
|
|
146
|
+
vertical_box {
|
|
147
|
+
@m_title = label("MISSION: #{current_suggested_mission[:name]}", size: 14)
|
|
148
|
+
@m_advice = label("ADVICE: #{current_suggested_mission[:advice]}")
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
table {
|
|
153
|
+
text_column('Tier'); text_column('Mission'); text_column('Goal')
|
|
154
|
+
cell_rows all_missions.map { |m| [m[:tier], m[:name], m[:goal]] }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
tab_item('⚙️ Settings') {
|
|
160
|
+
vertical_box {
|
|
161
|
+
padded true
|
|
162
|
+
label "Baseline: #{@income_baseline}"
|
|
163
|
+
horizontal_box {
|
|
164
|
+
@new_base = entry
|
|
165
|
+
button("Update") { on_clicked { @income_baseline = @new_base.text.to_f; update_ui } }
|
|
166
|
+
stretchy false
|
|
167
|
+
}
|
|
168
|
+
button("Wipe All Data") { on_clicked { reset_all } }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
on_closing { destroy; exit }
|
|
174
|
+
}
|
|
175
|
+
update_ui
|
|
176
|
+
@main_window.show
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# --- 4. CORE LOGIC ---
|
|
180
|
+
def handle_entry(type)
|
|
181
|
+
val = @amt_in.text.to_f
|
|
182
|
+
return if val <= 0
|
|
183
|
+
@balance += (type == :inc ? val : -val)
|
|
184
|
+
@transactions << [(type == :inc ? "🟢 INC" : "🔴 EXP"), @desc_in.text, (type == :inc ? "+#{val}" : "-#{val}"), Date.today.to_s]
|
|
185
|
+
update_ui; clear_inputs
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def update_ui
|
|
189
|
+
total_inc = @transactions.select { |t| t[2].to_f > 0 }.map { |t| t[2].to_f }.sum
|
|
190
|
+
total_exp = @transactions.select { |t| t[2].to_f < 0 }.map { |t| t[2].to_f.abs }.sum
|
|
191
|
+
|
|
192
|
+
# Update Efficiency Bar (0 to 100)
|
|
193
|
+
efficiency = total_inc > 0 ? (([total_inc - total_exp, 0].max / total_inc) * 100).to_i : 0
|
|
194
|
+
@efficiency_bar.value = efficiency
|
|
195
|
+
@efficiency_lbl.text = "Efficiency: #{efficiency}% of income retained as savings"
|
|
196
|
+
|
|
197
|
+
@rank_lbl.text = "RANK: #{calculate_rank}"
|
|
198
|
+
@ratio_lbl.text = "Ratio: #{current_ratio}x"
|
|
199
|
+
@impulse_lbl.text = "Impulse-Free: #{days_since_big_spend} Days"
|
|
200
|
+
|
|
201
|
+
m = current_suggested_mission
|
|
202
|
+
@active_m_lbl.text = "MISSION: #{m[:name]}"
|
|
203
|
+
@active_g_lbl.text = "GOAL: #{m[:goal]}"
|
|
204
|
+
@m_title.text = "MISSION: #{m[:name]}" rescue nil
|
|
205
|
+
@m_advice.text = "ADVICE: #{m[:advice]}" rescue nil
|
|
206
|
+
|
|
207
|
+
@hist_table.cell_rows = @transactions.map { |t| t.map(&:to_s) }
|
|
208
|
+
save_data
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def current_ratio; @income_baseline > 0 ? (@balance / @income_baseline).round(2) : 0; end
|
|
212
|
+
def calculate_rank; r = current_ratio; (r >= 12 ? "Celestial Dragon 🏮" : r >= 6 ? "Golden Dragon 🐉" : r >= 1 ? "Great Nishiki 🐠" : "Little Fry 🐟"); end
|
|
213
|
+
|
|
214
|
+
def days_since_big_spend
|
|
215
|
+
limit = @income_baseline * 0.05
|
|
216
|
+
big_spends = @transactions.select { |t| t[2].to_f < 0 && t[2].to_f.abs > limit }
|
|
217
|
+
big_spends.empty? ? "N/A" : (Date.today - Date.parse(big_spends.last[3])).to_i
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def load_selection
|
|
221
|
+
row = @transactions[@selected_index]
|
|
222
|
+
@amt_in.text = row[2].gsub(/[+-]/, ''); @desc_in.text = row[1]
|
|
223
|
+
@save_btn.enabled = @del_btn.enabled = true
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def save_edit; run_delete(false); handle_entry(@amt_in.text.to_f > 0 ? :inc : :exp); end
|
|
227
|
+
def run_delete(refresh = true); return unless @selected_index; @balance -= @transactions[@selected_index][2].to_f; @transactions.delete_at(@selected_index); update_ui if refresh; clear_inputs if refresh; end
|
|
228
|
+
def clear_inputs; @selected_index = nil; @amt_in.text = ''; @desc_in.text = ''; @save_btn.enabled = @del_btn.enabled = false; end
|
|
229
|
+
def save_data; File.write(FILE_NAME, JSON.generate({bal: @balance, inc: @income_baseline, tx: @transactions})); end
|
|
230
|
+
def load_data; return unless File.exist?(FILE_NAME); d = JSON.parse(File.read(FILE_NAME)); @balance, @income_baseline, @transactions = d['bal'], d['inc'], d['tx']; end
|
|
231
|
+
def reset_all; @balance = 0.0; @income_baseline = 0.0; @transactions = []; save_data; @main_window.destroy; launch_setup_window; end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
LuckyKoi.new
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: luckykoi
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Mark Angelo P. Santonil
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: glimmer-dsl-libui
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: json
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: A visual financial management tool with mission-based tracking.
|
|
41
|
+
email: cillia2203@gmail.com
|
|
42
|
+
executables:
|
|
43
|
+
- lucky_koi
|
|
44
|
+
extensions: []
|
|
45
|
+
extra_rdoc_files: []
|
|
46
|
+
files:
|
|
47
|
+
- bin/lucky_koi
|
|
48
|
+
- lib/lucky_koi.rb
|
|
49
|
+
- lib/lucky_koi/version.rb
|
|
50
|
+
licenses:
|
|
51
|
+
- MIT
|
|
52
|
+
metadata: {}
|
|
53
|
+
rdoc_options: []
|
|
54
|
+
require_paths:
|
|
55
|
+
- lib
|
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '0'
|
|
66
|
+
requirements: []
|
|
67
|
+
rubygems_version: 4.0.3
|
|
68
|
+
specification_version: 4
|
|
69
|
+
summary: Sovereign Architect Financial Tracker
|
|
70
|
+
test_files: []
|