mattoofahaddcube commited on
Commit
95bad29
1 Parent(s): d4d782c

adding a streamlit application

Browse files
Files changed (5) hide show
  1. .github/workflows/hf-space.yml +19 -0
  2. app.py +85 -0
  3. constants.py +37 -0
  4. functions.py +248 -53
  5. salary_calculator.py +14 -5
.github/workflows/hf-space.yml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ # to run this workflow manually from the Actions tab
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ sync-to-hub:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v3
13
+ with:
14
+ fetch-depth: 0
15
+ lfs: true
16
+ - name: Push to hub
17
+ env:
18
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
19
+ run: git push https://mattoofahad:$HF_TOKEN@huggingface.co/spaces/mattoofahad/Net-Salary-Calculator main
app.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+
3
+ from functions import Functions, StreamlitFunctions
4
+ import pandas as pd
5
+
6
+ StreamlitFunctions.initialize_session_values()
7
+ StreamlitFunctions.print_tax_brackets()
8
+ StreamlitFunctions.reset_tax_brackets()
9
+
10
+ StreamlitFunctions.print_salary_parameters()
11
+ StreamlitFunctions.reset_salary_parameters()
12
+
13
+ StreamlitFunctions.initial_salary_parameter()
14
+ StreamlitFunctions.reset_initial_salary_parameter()
15
+ StreamlitFunctions.check_initial_salary_parameter()
16
+
17
+ if st.button("Calculate", use_container_width=True) and st.session_state.valid_input:
18
+ if st.session_state.user_initial_desired_net > 0:
19
+ initial_desired_net = st.session_state.user_initial_desired_net
20
+ else:
21
+ initial_desired_net = Functions.calculated_initial_desired_net(
22
+ st.session_state.current_salary,
23
+ st.session_state.desired_increment_percentage,
24
+ st.session_state.daily_cost_of_travel,
25
+ st.session_state.physical_days_per_week,
26
+ )
27
+
28
+ result = Functions.calculate_additional_amount(
29
+ initial_desired_net, st.session_state.tax_brackets
30
+ )
31
+ st.header("Salary Calculation Results")
32
+ col1, col2 = st.columns(2)
33
+ with col1:
34
+ # custom_metric("Initial Desired Net Salary", result['initial_desired_net'])
35
+ StreamlitFunctions.custom_metric("Final Net Salary", result["final_net_salary"])
36
+ StreamlitFunctions.custom_metric("Tax", result["tax"])
37
+
38
+ with col2:
39
+ # custom_metric("Additional Amount Needed", result['additional_amount'])
40
+ StreamlitFunctions.custom_metric(
41
+ "Gross Salary Needed", result["gross_salary_needed"]
42
+ )
43
+
44
+ # Display how initial_desired_net was determined
45
+ st.markdown("---")
46
+ if st.session_state.user_initial_desired_net > 0:
47
+ st.success("✅ Initial Desired Net Salary was provided by the user.")
48
+ else:
49
+ st.info(
50
+ "ℹ️ Initial Desired Net Salary was calculated based on the provided parameters."
51
+ )
52
+
53
+ # Display a summary of the calculation
54
+ st.subheader("Calculation Summary")
55
+ summary_df = pd.DataFrame(
56
+ {
57
+ "Parameter": [
58
+ "Current Salary",
59
+ "Desired Increment",
60
+ "Daily Travel Cost",
61
+ "On-Site Days/Week",
62
+ "Gross Salary",
63
+ "Tax",
64
+ "Final Net Salary",
65
+ ],
66
+ "Value": [
67
+ f"PKR {st.session_state.current_salary:,.2f}",
68
+ f"{st.session_state.desired_increment_percentage:.2%}",
69
+ f"PKR {st.session_state.daily_cost_of_travel:,.2f}",
70
+ f"{st.session_state.physical_days_per_week}",
71
+ f"PKR {result['gross_salary_needed']:,.2f}",
72
+ f"PKR {result['tax']:,.2f}",
73
+ f"PKR {result['final_net_salary']:,.2f}",
74
+ ],
75
+ }
76
+ )
77
+ st.table(summary_df)
78
+
79
+ st.subheader("Salary Breakdown")
80
+ breakdown_data = {
81
+ "Component": ["Net Salary", "Tax"],
82
+ "Amount": [result["final_net_salary"], result["tax"]],
83
+ }
84
+ breakdown_df = pd.DataFrame(breakdown_data)
85
+ st.bar_chart(breakdown_df.set_index("Component"))
constants.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Constants:
2
+ DEFAULT_CURRENT_SALARY = 100000.0
3
+ DEFAULT_INCREMENT_PERCENTAGE = 0.3
4
+ DEFAULT_DAILY_TRAVEL_COST = 1500
5
+ DEFAULT_PHYSICAL_DAYS = 5
6
+ DEFAULT_INITIAL_NET = 0.0
7
+
8
+ DEFAULT_TAX_BRACKETS = [
9
+ (0, 600000, 0),
10
+ (600000, 1200000, 0.05),
11
+ (1200000, 2200000, 0.15),
12
+ (2200000, 3200000, 0.25),
13
+ (3200000, 4100000, 0.30),
14
+ (4100000, float("inf"), 0.35),
15
+ ]
16
+
17
+ class Styles:
18
+ METRIC_STYLE = """
19
+ <style>
20
+ .metric-container {
21
+ background-color: #f0f2f6;
22
+ border-radius: 7px;
23
+ padding: 10px;
24
+ margin-bottom: 10px;
25
+ }
26
+ .metric-label {
27
+ font-size: 14px;
28
+ color: #555;
29
+ margin-bottom: 5px;
30
+ }
31
+ .metric-value {
32
+ font-size: 24px;
33
+ font-weight: bold;
34
+ color: #0066cc;
35
+ }
36
+ </style>
37
+ """
functions.py CHANGED
@@ -1,53 +1,248 @@
1
- def calculate_monthly_tax(monthly_income):
2
- annual_income = monthly_income * 12
3
- tax_brackets = [
4
- (0, 600000, 0),
5
- (600000, 1200000, 0.05),
6
- (1200000, 2200000, 0.15),
7
- (2200000, 3200000, 0.25),
8
- (3200000, 4100000, 0.30),
9
- (4100000, float("inf"), 0.35),
10
- ]
11
- total_tax = 0
12
- remaining_income = annual_income
13
- for lower, upper, rate in tax_brackets:
14
- if remaining_income <= 0:
15
- break
16
-
17
- taxable_amount = min(remaining_income, upper - lower)
18
- tax = taxable_amount * rate
19
- total_tax += tax
20
- remaining_income -= taxable_amount
21
- monthly_tax = total_tax / 12
22
- return round(monthly_tax, 2)
23
-
24
-
25
- def calculate_net_salary(gross_salary):
26
- return gross_salary - calculate_monthly_tax(gross_salary)
27
-
28
-
29
- def calculated_initial_desired_net(
30
- current_salary, desired_increment, daily_cost_of_travel, physical_days_per_week
31
- ):
32
- return (current_salary + current_salary * desired_increment) + (
33
- daily_cost_of_travel * physical_days_per_week * 4.5
34
- )
35
-
36
-
37
- def calculate_additional_amount(initial_desired_net):
38
- gross_salary = initial_desired_net
39
- max_iterations = 100
40
- for _ in range(max_iterations):
41
- net_salary = calculate_net_salary(gross_salary)
42
- if abs(net_salary - initial_desired_net) < 0.01:
43
- break
44
- gross_salary += initial_desired_net - net_salary
45
- additional_amount = gross_salary - initial_desired_net
46
-
47
- return {
48
- "initial_desired_net": round(initial_desired_net, 2),
49
- "gross_salary_needed": round(gross_salary, 2),
50
- "additional_amount": round(additional_amount, 2),
51
- "tax": round(calculate_monthly_tax(gross_salary), 2),
52
- "final_net_salary": round(calculate_net_salary(gross_salary), 2),
53
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from constants import Styles, Constants
2
+ import streamlit as st
3
+
4
+
5
+ class Functions:
6
+ @staticmethod
7
+ def calculate_monthly_tax(monthly_income, tax_brackets):
8
+ annual_income = monthly_income * 12
9
+ total_tax = 0
10
+ remaining_income = annual_income
11
+ for lower, upper, rate in tax_brackets:
12
+ if remaining_income <= 0:
13
+ break
14
+ taxable_amount = min(remaining_income, upper - lower)
15
+ tax = taxable_amount * rate
16
+ total_tax += tax
17
+ remaining_income -= taxable_amount
18
+ monthly_tax = total_tax / 12
19
+ return round(monthly_tax, 2)
20
+
21
+ @staticmethod
22
+ def calculate_net_salary(gross_salary, tax_brackets):
23
+ return gross_salary - Functions.calculate_monthly_tax(
24
+ gross_salary, tax_brackets
25
+ )
26
+
27
+ @staticmethod
28
+ def calculated_initial_desired_net(
29
+ current_salary, desired_increment, daily_cost_of_travel, physical_days_per_week
30
+ ):
31
+ return (current_salary + current_salary * desired_increment) + (
32
+ daily_cost_of_travel * physical_days_per_week * 4.5
33
+ )
34
+
35
+ @staticmethod
36
+ def calculate_additional_amount(initial_desired_net, tax_brackets):
37
+ gross_salary = initial_desired_net
38
+ max_iterations = 100
39
+ for _ in range(max_iterations):
40
+ net_salary = Functions.calculate_net_salary(gross_salary, tax_brackets)
41
+ if abs(net_salary - initial_desired_net) < 0.01:
42
+ break
43
+ gross_salary += initial_desired_net - net_salary
44
+ additional_amount = gross_salary - initial_desired_net
45
+
46
+ return {
47
+ "initial_desired_net": round(initial_desired_net, 2),
48
+ "gross_salary_needed": round(gross_salary, 2),
49
+ "additional_amount": round(additional_amount, 2),
50
+ "tax": round(
51
+ Functions.calculate_monthly_tax(gross_salary, tax_brackets), 2
52
+ ),
53
+ "final_net_salary": round(
54
+ Functions.calculate_net_salary(gross_salary, tax_brackets), 2
55
+ ),
56
+ }
57
+
58
+
59
+ import pandas as pd
60
+
61
+
62
+ class StreamlitFunctions:
63
+ @staticmethod
64
+ def check_initial_salary_parameter():
65
+ if (
66
+ st.session_state.user_initial_desired_net > 0
67
+ and st.session_state.user_initial_desired_net
68
+ <= st.session_state.current_salary
69
+ ):
70
+ st.warning(
71
+ "⚠️ The Initial Desired Net Salary should be greater than your Current Salary. Please adjust your input or leave it at 0 to calculate automatically."
72
+ )
73
+ st.session_state.valid_input = False
74
+ else:
75
+ st.session_state.valid_input = True
76
+
77
+ @staticmethod
78
+ def update_initial_salary_parameter():
79
+ if (
80
+ st.session_state.user_initial_desired_net_state
81
+ < st.session_state.current_salary
82
+ ):
83
+ st.session_state.user_initial_desired_net = (
84
+ st.session_state.current_salary
85
+ + st.session_state.user_initial_desired_net_state
86
+ )
87
+ else:
88
+ st.session_state.user_initial_desired_net = (
89
+ st.session_state.user_initial_desired_net_state
90
+ )
91
+
92
+ @staticmethod
93
+ def reset_initial_salary_parameter():
94
+ def reset_values():
95
+ st.session_state.user_initial_desired_net = Constants.DEFAULT_INITIAL_NET
96
+ st.session_state.valid_input = True
97
+
98
+ st.button(
99
+ "Reset Final Desired Net Salary Value",
100
+ on_click=reset_values,
101
+ use_container_width=True,
102
+ )
103
+
104
+ @staticmethod
105
+ def initial_salary_parameter():
106
+ st.header("Calculation based on Final Desired Net Salary")
107
+ if st.session_state.user_initial_desired_net >= st.session_state.current_salary:
108
+ minimum_value = st.session_state.current_salary
109
+ else:
110
+ minimum_value = 0.0
111
+ st.number_input(
112
+ "Final Desired Net Salary (PKR, optional)",
113
+ min_value=minimum_value,
114
+ value=st.session_state.user_initial_desired_net,
115
+ step=1000.0,
116
+ key="user_initial_desired_net_state",
117
+ on_change=StreamlitFunctions.update_initial_salary_parameter,
118
+ )
119
+
120
+ @staticmethod
121
+ def reset_tax_brackets():
122
+ def reset_values():
123
+ st.session_state.tax_brackets = Constants.DEFAULT_TAX_BRACKETS
124
+
125
+ st.button(
126
+ "Reset Tax Brackets",
127
+ use_container_width=True,
128
+ on_click=reset_values,
129
+ )
130
+
131
+ @staticmethod
132
+ def reset_salary_parameters():
133
+ def reset_values():
134
+ st.session_state.current_salary = Constants.DEFAULT_CURRENT_SALARY
135
+ st.session_state.desired_increment_percentage = (
136
+ Constants.DEFAULT_INCREMENT_PERCENTAGE
137
+ )
138
+ st.session_state.daily_cost_of_travel = Constants.DEFAULT_DAILY_TRAVEL_COST
139
+ st.session_state.physical_days_per_week = Constants.DEFAULT_PHYSICAL_DAYS
140
+
141
+ st.button(
142
+ "Reset Salary Parameters Values",
143
+ use_container_width=True,
144
+ on_click=reset_values,
145
+ )
146
+
147
+ @staticmethod
148
+ def print_salary_parameters():
149
+ st.header("Calculation Based on Salary Parameters")
150
+
151
+ def update_current_salary_parameter():
152
+ st.session_state.current_salary = st.session_state.current_salary_state
153
+
154
+ st.session_state.current_salary = st.number_input(
155
+ "Current monthly salary (PKR)",
156
+ min_value=0.0,
157
+ step=1000.0,
158
+ value=st.session_state.current_salary,
159
+ key="current_salary_state",
160
+ on_change=update_current_salary_parameter,
161
+ )
162
+
163
+ def update_desired_increment_percentage_parameter():
164
+ st.session_state.desired_increment_percentage = (
165
+ st.session_state.desired_increment_percentage_state
166
+ )
167
+
168
+ st.session_state.desired_increment_percentage = st.number_input(
169
+ "Desired salary increment (as a decimal)",
170
+ min_value=0.0,
171
+ step=0.05,
172
+ value=st.session_state.desired_increment_percentage,
173
+ format="%.2f",
174
+ key="desired_increment_percentage_state",
175
+ on_change=update_desired_increment_percentage_parameter,
176
+ )
177
+
178
+ def update_daily_cost_of_travel_parameter():
179
+ st.session_state.daily_cost_of_travel = (
180
+ st.session_state.daily_cost_of_travel_state
181
+ )
182
+
183
+ st.session_state.daily_cost_of_travel = st.number_input(
184
+ "Daily cost of travel (PKR)",
185
+ min_value=0,
186
+ step=100,
187
+ value=st.session_state.daily_cost_of_travel,
188
+ key="daily_cost_of_travel_state",
189
+ on_change=update_daily_cost_of_travel_parameter,
190
+ )
191
+
192
+ def update_physical_days_per_week_parameter():
193
+ st.session_state.physical_days_per_week = (
194
+ st.session_state.physical_days_per_week_state
195
+ )
196
+
197
+ st.session_state.physical_days_per_week = st.number_input(
198
+ "Number of On-Site days per week",
199
+ min_value=0,
200
+ max_value=7,
201
+ step=1,
202
+ value=st.session_state.physical_days_per_week,
203
+ key="physical_days_per_week_state",
204
+ on_change=update_physical_days_per_week_parameter,
205
+ )
206
+
207
+ @staticmethod
208
+ def print_tax_brackets():
209
+ st.header("Tax Brackets")
210
+ tax_brackets_df = pd.DataFrame(
211
+ st.session_state.tax_brackets,
212
+ columns=["Lower Limit", "Upper Limit", "Tax Rate"],
213
+ )
214
+ edited_tax_brackets = st.data_editor(tax_brackets_df, num_rows="dynamic")
215
+ st.session_state.tax_brackets = list(
216
+ edited_tax_brackets.itertuples(index=False, name=None)
217
+ )
218
+
219
+ @staticmethod
220
+ def initialize_session_values():
221
+ st.title("Net Salary Calculator")
222
+ if "tax_brackets" not in st.session_state:
223
+ st.session_state.tax_brackets = Constants.DEFAULT_TAX_BRACKETS
224
+ if "current_salary" not in st.session_state:
225
+ st.session_state.current_salary = Constants.DEFAULT_CURRENT_SALARY
226
+ if "desired_increment_percentage" not in st.session_state:
227
+ st.session_state.desired_increment_percentage = (
228
+ Constants.DEFAULT_INCREMENT_PERCENTAGE
229
+ )
230
+ if "daily_cost_of_travel" not in st.session_state:
231
+ st.session_state.daily_cost_of_travel = Constants.DEFAULT_DAILY_TRAVEL_COST
232
+ if "physical_days_per_week" not in st.session_state:
233
+ st.session_state.physical_days_per_week = Constants.DEFAULT_PHYSICAL_DAYS
234
+ if "user_initial_desired_net" not in st.session_state:
235
+ st.session_state.user_initial_desired_net = Constants.DEFAULT_INITIAL_NET
236
+
237
+ @staticmethod
238
+ def custom_metric(label, value):
239
+ st.markdown(Styles.METRIC_STYLE, unsafe_allow_html=True)
240
+ st.markdown(
241
+ f"""
242
+ <div class="metric-container">
243
+ <div class="metric-label">{label}</div>
244
+ <div class="metric-value">PKR {value:,.2f}</div>
245
+ </div>
246
+ """,
247
+ unsafe_allow_html=True,
248
+ )
salary_calculator.py CHANGED
@@ -1,10 +1,16 @@
1
- import typer
2
  from typing import Optional
3
 
4
- from functions import calculated_initial_desired_net, calculate_additional_amount
 
 
 
5
 
6
  app = typer.Typer()
7
 
 
 
 
 
8
 
9
  @app.command()
10
  def calculate_salary(
@@ -22,10 +28,13 @@ def calculate_salary(
22
  """
23
  Calculate the additional amount needed for desired salary after tax adjustment.
24
  """
25
- initial_desired_net = calculated_initial_desired_net(
26
- current_salary, desired_increment_percentage, daily_cost_of_travel, physical_days_per_week
 
 
 
27
  )
28
- result = calculate_additional_amount(initial_desired_net)
29
 
30
  typer.echo("Salary Calculation Results")
31
  typer.echo("--------------------------")
 
 
1
  from typing import Optional
2
 
3
+ import typer
4
+ import pandas as pd
5
+ from constants import Constants
6
+ from functions import Functions
7
 
8
  app = typer.Typer()
9
 
10
+ tax_brackets_df = pd.DataFrame(
11
+ Constants.DEFAULT_TAX_BRACKETS, columns=["Lower Limit", "Upper Limit", "Tax Rate"]
12
+ )
13
+
14
 
15
  @app.command()
16
  def calculate_salary(
 
28
  """
29
  Calculate the additional amount needed for desired salary after tax adjustment.
30
  """
31
+ initial_desired_net = Functions.calculated_initial_desired_net(
32
+ current_salary,
33
+ desired_increment_percentage,
34
+ daily_cost_of_travel,
35
+ physical_days_per_week,
36
  )
37
+ result = Functions.calculate_additional_amount(initial_desired_net, tax_brackets_df)
38
 
39
  typer.echo("Salary Calculation Results")
40
  typer.echo("--------------------------")