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 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
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'lucky_koi'
3
+ LuckyKoi.new
@@ -0,0 +1,3 @@
1
+ module LuckyKoi
2
+ VERSION = "0.1.0"
3
+ end
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: []