corally 1.0.0__py3-none-any.whl

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.
corally/cli/main.py ADDED
@@ -0,0 +1,208 @@
1
+ """
2
+ Main CLI entry point for Corally calculator suite.
3
+ """
4
+
5
+ import sys
6
+ from typing import Optional
7
+
8
+ from ..core import CalculatorCore, CurrencyConverter, InterestCalculator
9
+ from ..gui.launcher import launch_gui
10
+ from ..api.server import start_server
11
+ from ..api.free_server import start_free_server
12
+
13
+
14
+ def main_cli() -> None:
15
+ """Main CLI interface for Corally calculator suite."""
16
+ print("๐Ÿงฎ Corally Calculator Suite")
17
+ print("=" * 40)
18
+
19
+ while True:
20
+ print("\nSelect an option:")
21
+ print("1: Basic Calculator")
22
+ print("2: Currency Converter")
23
+ print("3: Interest Calculator")
24
+ print("4: Launch GUI")
25
+ print("5: Start API Server (Free)")
26
+ print("6: Start API Server (Paid)")
27
+ print("7: Exit")
28
+
29
+ try:
30
+ choice = int(input("\nEnter your choice (1-7): "))
31
+ except ValueError:
32
+ print("โŒ Invalid input. Please enter a number between 1-7.")
33
+ continue
34
+
35
+ if choice == 7:
36
+ print("๐Ÿ‘‹ Goodbye!")
37
+ break
38
+ elif choice == 1:
39
+ calculator_cli()
40
+ elif choice == 2:
41
+ currency_cli()
42
+ elif choice == 3:
43
+ interest_cli()
44
+ elif choice == 4:
45
+ print("๐Ÿš€ Launching GUI...")
46
+ launch_gui()
47
+ elif choice == 5:
48
+ print("๐ŸŒ Starting Free API Server...")
49
+ start_free_server()
50
+ elif choice == 6:
51
+ print("๐ŸŒ Starting Paid API Server...")
52
+ start_server()
53
+ else:
54
+ print("โŒ Invalid choice. Please select 1-7.")
55
+
56
+
57
+ def calculator_cli() -> None:
58
+ """Basic calculator CLI interface."""
59
+ calc = CalculatorCore()
60
+ print("\n๐Ÿงฎ Basic Calculator")
61
+ print("-" * 20)
62
+
63
+ while True:
64
+ print("\nOperations:")
65
+ print("1: Addition")
66
+ print("2: Subtraction")
67
+ print("3: Multiplication")
68
+ print("4: Division")
69
+ print("5: Back to main menu")
70
+
71
+ try:
72
+ choice = int(input("\nSelect operation (1-5): "))
73
+ except ValueError:
74
+ print("โŒ Invalid input. Please enter a number.")
75
+ continue
76
+
77
+ if choice == 5:
78
+ break
79
+ elif choice not in [1, 2, 3, 4]:
80
+ print("โŒ Invalid choice. Please select 1-5.")
81
+ continue
82
+
83
+ try:
84
+ a = float(input("Enter first number: "))
85
+ b = float(input("Enter second number: "))
86
+ except ValueError:
87
+ print("โŒ Invalid number input.")
88
+ continue
89
+
90
+ result = None
91
+ if choice == 1:
92
+ result = calc.add(a, b)
93
+ print(f"โœ… {a} + {b} = {result}")
94
+ elif choice == 2:
95
+ result = calc.subtract(a, b)
96
+ print(f"โœ… {a} - {b} = {result}")
97
+ elif choice == 3:
98
+ result = calc.multiply(a, b)
99
+ print(f"โœ… {a} ร— {b} = {result}")
100
+ elif choice == 4:
101
+ result = calc.divide(a, b)
102
+ if result is not None:
103
+ print(f"โœ… {a} รท {b} = {result}")
104
+
105
+
106
+ def currency_cli() -> None:
107
+ """Currency converter CLI interface."""
108
+ converter = CurrencyConverter()
109
+ print("\n๐Ÿ’ฑ Currency Converter")
110
+ print("-" * 22)
111
+
112
+ while True:
113
+ print("\nAvailable conversions:")
114
+ print("1: EUR to USD")
115
+ print("2: USD to EUR")
116
+ print("3: EUR to GBP")
117
+ print("4: GBP to EUR")
118
+ print("5: EUR to JPY")
119
+ print("6: JPY to EUR")
120
+ print("7: Back to main menu")
121
+
122
+ try:
123
+ choice = int(input("\nSelect conversion (1-7): "))
124
+ except ValueError:
125
+ print("โŒ Invalid input. Please enter a number.")
126
+ continue
127
+
128
+ if choice == 7:
129
+ break
130
+ elif choice not in range(1, 7):
131
+ print("โŒ Invalid choice. Please select 1-7.")
132
+ continue
133
+
134
+ try:
135
+ amount = float(input("Enter amount: "))
136
+ except ValueError:
137
+ print("โŒ Invalid amount.")
138
+ continue
139
+
140
+ result = None
141
+ if choice == 1:
142
+ result = converter.eur_to_usd(amount)
143
+ print(f"โœ… {amount} EUR = {result} USD")
144
+ elif choice == 2:
145
+ result = converter.usd_to_eur(amount)
146
+ print(f"โœ… {amount} USD = {result} EUR")
147
+ elif choice == 3:
148
+ result = converter.eur_to_gbp(amount)
149
+ print(f"โœ… {amount} EUR = {result} GBP")
150
+ elif choice == 4:
151
+ result = converter.gbp_to_eur(amount)
152
+ print(f"โœ… {amount} GBP = {result} EUR")
153
+ elif choice == 5:
154
+ result = converter.eur_to_jpy(amount)
155
+ print(f"โœ… {amount} EUR = {result} JPY")
156
+ elif choice == 6:
157
+ result = converter.jpy_to_eur(amount)
158
+ print(f"โœ… {amount} JPY = {result} EUR")
159
+
160
+
161
+ def interest_cli() -> None:
162
+ """Interest calculator CLI interface."""
163
+ print("\n๐Ÿ“ˆ Interest Calculator")
164
+ print("-" * 22)
165
+
166
+ while True:
167
+ print("\nCalculation methods:")
168
+ print("1: 30/360 method")
169
+ print("2: act/360 method")
170
+ print("3: act/365 method")
171
+ print("4: act/act method")
172
+ print("5: Back to main menu")
173
+
174
+ try:
175
+ choice = int(input("\nSelect method (1-5): "))
176
+ except ValueError:
177
+ print("โŒ Invalid input. Please enter a number.")
178
+ continue
179
+
180
+ if choice == 5:
181
+ break
182
+ elif choice not in range(1, 5):
183
+ print("โŒ Invalid choice. Please select 1-5.")
184
+ continue
185
+
186
+ try:
187
+ principal = float(input("Enter principal amount: "))
188
+ rate = float(input("Enter interest rate (%): "))
189
+ start_date = input("Enter start date (DD.MM.YYYY): ")
190
+ end_date = input("Enter end date (DD.MM.YYYY): ")
191
+ except ValueError:
192
+ print("โŒ Invalid input.")
193
+ continue
194
+
195
+ methods = ["30/360", "act/360", "act/365", "act/act"]
196
+ method = methods[choice - 1]
197
+
198
+ try:
199
+ result = InterestCalculator.calculate_interest(
200
+ principal, rate, start_date, end_date, method
201
+ )
202
+ print(f"โœ… Interest ({method}): {result}")
203
+ except Exception as e:
204
+ print(f"โŒ Error calculating interest: {e}")
205
+
206
+
207
+ if __name__ == "__main__":
208
+ main_cli()
@@ -0,0 +1,7 @@
1
+ """
2
+ Core calculation modules for Corally calculator suite.
3
+ """
4
+
5
+ from .calculator import CalculatorCore, CurrencyConverter, InterestCalculator
6
+
7
+ __all__ = ["CalculatorCore", "CurrencyConverter", "InterestCalculator"]
@@ -0,0 +1,392 @@
1
+ """
2
+ Calculator Suite Core Library
3
+
4
+ A unified library combining all calculator functionality:
5
+ - Basic arithmetic operations with logging
6
+ - Currency conversion (static rates)
7
+ - Interest calculations with multiple methods
8
+
9
+ This module replaces the separate Maxim.py and zinsen.py files.
10
+ """
11
+
12
+ import csv
13
+ import os
14
+ from datetime import datetime
15
+ from typing import Optional, Union
16
+ from pathlib import Path
17
+
18
+
19
+ class CalculatorCore:
20
+ """
21
+ Main calculator class providing basic arithmetic operations with automatic logging.
22
+
23
+ Features:
24
+ - Four basic operations: add, subtract, multiply, divide
25
+ - Automatic CSV logging of all operations
26
+ - Robust error handling and input validation
27
+ """
28
+
29
+ def __init__(self, csv_file: Optional[str] = None):
30
+ """
31
+ Initialize calculator with CSV logging.
32
+
33
+ Args:
34
+ csv_file (str): Path to CSV file for logging operations.
35
+ If None, uses 'data/rechner_log.csv' in current directory.
36
+ """
37
+ if csv_file is None:
38
+ # Create data directory if it doesn't exist
39
+ data_dir = Path("data")
40
+ data_dir.mkdir(exist_ok=True)
41
+ self.csv_file = str(data_dir / "rechner_log.csv")
42
+ else:
43
+ self.csv_file = csv_file
44
+ self._initialize_csv()
45
+
46
+ def _initialize_csv(self) -> None:
47
+ """Initialize CSV file with headers if it doesn't exist or is empty."""
48
+ try:
49
+ with open(self.csv_file, mode="a", newline="", encoding="utf-8") as file:
50
+ writer = csv.writer(file)
51
+ if file.tell() == 0:
52
+ writer.writerow(["Zahl 1", "Operator", "Zahl 2", "Ergebnis", "Zeitstempel"])
53
+ except Exception as e:
54
+ print(f"Warning: Could not initialize CSV file: {e}")
55
+
56
+ def _log_operation(self, a: float, operator: str, b: float, result: float) -> None:
57
+ """
58
+ Log calculation to CSV file.
59
+
60
+ Args:
61
+ a (float): First number
62
+ operator (str): Operation symbol
63
+ b (float): Second number
64
+ result (float): Calculation result
65
+ """
66
+ try:
67
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
68
+ with open(self.csv_file, mode="a", newline="", encoding="utf-8") as file:
69
+ writer = csv.writer(file)
70
+ writer.writerow([a, operator, b, result, timestamp])
71
+ except Exception as e:
72
+ print(f"Warning: Could not log operation: {e}")
73
+
74
+ def _validate_numbers(self, a: Union[str, int, float], b: Union[str, int, float]) -> tuple[float, float]:
75
+ """
76
+ Validate and convert input numbers to float.
77
+
78
+ Args:
79
+ a: First number (any numeric type or string)
80
+ b: Second number (any numeric type or string)
81
+
82
+ Returns:
83
+ tuple[float, float]: Validated numbers as floats
84
+
85
+ Raises:
86
+ ValueError: If inputs cannot be converted to float
87
+ """
88
+ try:
89
+ return float(a), float(b)
90
+ except (TypeError, ValueError) as e:
91
+ raise ValueError(f"Invalid number input: {e}")
92
+
93
+ def add(self, a: Union[str, int, float], b: Union[str, int, float]) -> Optional[float]:
94
+ """
95
+ Add two numbers.
96
+
97
+ Args:
98
+ a: First number
99
+ b: Second number
100
+
101
+ Returns:
102
+ float: Sum of a and b, or None if error
103
+ """
104
+ try:
105
+ num_a, num_b = self._validate_numbers(a, b)
106
+ result = num_a + num_b
107
+ self._log_operation(num_a, "+", num_b, result)
108
+ return result
109
+ except ValueError as e:
110
+ print(f"Addition error: {e}")
111
+ return None
112
+
113
+ def subtract(self, a: Union[str, int, float], b: Union[str, int, float]) -> Optional[float]:
114
+ """
115
+ Subtract two numbers.
116
+
117
+ Args:
118
+ a: Minuend (first number)
119
+ b: Subtrahend (second number)
120
+
121
+ Returns:
122
+ float: Difference of a and b, or None if error
123
+ """
124
+ try:
125
+ num_a, num_b = self._validate_numbers(a, b)
126
+ result = num_a - num_b
127
+ self._log_operation(num_a, "-", num_b, result)
128
+ return result
129
+ except ValueError as e:
130
+ print(f"Subtraction error: {e}")
131
+ return None
132
+
133
+ def multiply(self, a: Union[str, int, float], b: Union[str, int, float]) -> Optional[float]:
134
+ """
135
+ Multiply two numbers.
136
+
137
+ Args:
138
+ a: First number
139
+ b: Second number
140
+
141
+ Returns:
142
+ float: Product of a and b, or None if error
143
+ """
144
+ try:
145
+ num_a, num_b = self._validate_numbers(a, b)
146
+ result = num_a * num_b
147
+ self._log_operation(num_a, "*", num_b, result)
148
+ return result
149
+ except ValueError as e:
150
+ print(f"Multiplication error: {e}")
151
+ return None
152
+
153
+ def divide(self, a: Union[str, int, float], b: Union[str, int, float]) -> Optional[float]:
154
+ """
155
+ Divide two numbers.
156
+
157
+ Args:
158
+ a: Dividend (first number)
159
+ b: Divisor (second number)
160
+
161
+ Returns:
162
+ float: Quotient of a and b, or None if error
163
+ """
164
+ try:
165
+ num_a, num_b = self._validate_numbers(a, b)
166
+ if num_b == 0:
167
+ print("Division error: Division by zero is not allowed!")
168
+ return None
169
+ result = num_a / num_b
170
+ self._log_operation(num_a, "/", num_b, result)
171
+ return result
172
+ except ValueError as e:
173
+ print(f"Division error: {e}")
174
+ return None
175
+
176
+
177
+ class CurrencyConverter:
178
+ """
179
+ Currency converter with predefined exchange rates.
180
+
181
+ Supported conversions:
182
+ - EUR โ†” USD
183
+ - EUR โ†” GBP
184
+ - EUR โ†” JPY
185
+ """
186
+
187
+ # Exchange rates (static)
188
+ RATES = {
189
+ 'EUR_TO_USD': 1.17,
190
+ 'EUR_TO_GBP': 0.87,
191
+ 'EUR_TO_JPY': 173.84
192
+ }
193
+
194
+ @classmethod
195
+ def _validate_amount(cls, amount: Union[str, int, float]) -> float:
196
+ """
197
+ Validate and convert amount to float.
198
+
199
+ Args:
200
+ amount: Amount to convert
201
+
202
+ Returns:
203
+ float: Validated amount
204
+
205
+ Raises:
206
+ ValueError: If amount cannot be converted to float
207
+ """
208
+ try:
209
+ return float(amount)
210
+ except (TypeError, ValueError) as e:
211
+ raise ValueError(f"Invalid amount: {e}")
212
+
213
+ @classmethod
214
+ def eur_to_usd(cls, eur: Union[str, int, float]) -> Optional[float]:
215
+ """Convert EUR to USD."""
216
+ try:
217
+ amount = cls._validate_amount(eur)
218
+ result = amount * cls.RATES['EUR_TO_USD']
219
+ return round(result, 2)
220
+ except ValueError as e:
221
+ print(f"EUR to USD conversion error: {e}")
222
+ return None
223
+
224
+ @classmethod
225
+ def usd_to_eur(cls, usd: Union[str, int, float]) -> Optional[float]:
226
+ """Convert USD to EUR."""
227
+ try:
228
+ amount = cls._validate_amount(usd)
229
+ result = amount / cls.RATES['EUR_TO_USD']
230
+ return round(result, 2)
231
+ except ValueError as e:
232
+ print(f"USD to EUR conversion error: {e}")
233
+ return None
234
+
235
+ @classmethod
236
+ def eur_to_gbp(cls, eur: Union[str, int, float]) -> Optional[float]:
237
+ """Convert EUR to GBP."""
238
+ try:
239
+ amount = cls._validate_amount(eur)
240
+ result = amount * cls.RATES['EUR_TO_GBP']
241
+ return round(result, 2)
242
+ except ValueError as e:
243
+ print(f"EUR to GBP conversion error: {e}")
244
+ return None
245
+
246
+ @classmethod
247
+ def gbp_to_eur(cls, gbp: Union[str, int, float]) -> Optional[float]:
248
+ """Convert GBP to EUR."""
249
+ try:
250
+ amount = cls._validate_amount(gbp)
251
+ result = amount / cls.RATES['EUR_TO_GBP']
252
+ return round(result, 2)
253
+ except ValueError as e:
254
+ print(f"GBP to EUR conversion error: {e}")
255
+ return None
256
+
257
+ @classmethod
258
+ def eur_to_jpy(cls, eur: Union[str, int, float]) -> Optional[float]:
259
+ """Convert EUR to JPY."""
260
+ try:
261
+ amount = cls._validate_amount(eur)
262
+ result = amount * cls.RATES['EUR_TO_JPY']
263
+ return round(result, 2)
264
+ except ValueError as e:
265
+ print(f"EUR to JPY conversion error: {e}")
266
+ return None
267
+
268
+ @classmethod
269
+ def jpy_to_eur(cls, jpy: Union[str, int, float]) -> Optional[float]:
270
+ """Convert JPY to EUR."""
271
+ try:
272
+ amount = cls._validate_amount(jpy)
273
+ result = amount / cls.RATES['EUR_TO_JPY']
274
+ return round(result, 2)
275
+ except ValueError as e:
276
+ print(f"JPY to EUR conversion error: {e}")
277
+ return None
278
+
279
+
280
+ class InterestCalculator:
281
+ """
282
+ Interest calculator supporting multiple calculation methods.
283
+
284
+ Supported methods:
285
+ - 30/360: 30-day months, 360-day years
286
+ - act/360: Actual days, 360-day years
287
+ - act/365: Actual days, 365-day years
288
+ - act/act: Actual days, actual years
289
+ """
290
+
291
+ VALID_METHODS = ["30/360", "act/360", "act/365", "act/act"]
292
+
293
+ @classmethod
294
+ def calculate_interest(
295
+ cls,
296
+ capital: Union[str, int, float],
297
+ interest_rate: Union[str, int, float],
298
+ start_date: str,
299
+ end_date: str,
300
+ method: str = "act/365"
301
+ ) -> Optional[float]:
302
+ """
303
+ Calculate interest between two dates using specified method.
304
+
305
+ Args:
306
+ capital: Principal amount
307
+ interest_rate: Annual interest rate (percentage)
308
+ start_date: Start date in DD.MM.YYYY format
309
+ end_date: End date in DD.MM.YYYY format
310
+ method: Calculation method (30/360, act/360, act/365, act/act)
311
+
312
+ Returns:
313
+ float: Calculated interest amount, or None if error
314
+ """
315
+ try:
316
+ # Validate inputs
317
+ cap = float(capital)
318
+ rate = float(interest_rate) / 100 # Convert percentage to decimal
319
+
320
+ if method not in cls.VALID_METHODS:
321
+ raise ValueError(f"Invalid method. Allowed: {', '.join(cls.VALID_METHODS)}")
322
+
323
+ # Parse dates
324
+ d1 = datetime.strptime(start_date, "%d.%m.%Y")
325
+ d2 = datetime.strptime(end_date, "%d.%m.%Y")
326
+
327
+ # Calculate days and year basis
328
+ if method == "30/360":
329
+ days = (d2.year - d1.year) * 360 + (d2.month - d1.month) * 30 + (d2.day - d1.day)
330
+ year_basis = 360
331
+ else:
332
+ days = (d2 - d1).days
333
+ if method == "act/360":
334
+ year_basis = 360
335
+ elif method in ("act/365", "act/act"):
336
+ year_basis = 365
337
+
338
+ # Calculate interest
339
+ interest = cap * rate * (days / year_basis)
340
+ return round(interest, 2)
341
+
342
+ except (ValueError, TypeError) as e:
343
+ print(f"Interest calculation error: {e}")
344
+ return None
345
+ except Exception as e:
346
+ print(f"Unexpected error in interest calculation: {e}")
347
+ return None
348
+
349
+
350
+ # Backward compatibility aliases
351
+ Rechner = CalculatorCore
352
+ Waerungsrechner = CurrencyConverter
353
+ tageszins = InterestCalculator.calculate_interest
354
+
355
+ # Legacy method aliases for Waerungsrechner
356
+ # CurrencyConverter.eur_in_usd = CurrencyConverter.eur_to_usd
357
+ # CurrencyConverter.usd_in_eur = CurrencyConverter.usd_to_eur
358
+ # CurrencyConverter.eur_in_bp = CurrencyConverter.eur_to_gbp
359
+ # CurrencyConverter.bp_in_eur = CurrencyConverter.gbp_to_eur
360
+ # CurrencyConverter.eur_in_yen = CurrencyConverter.eur_to_jpy
361
+ # CurrencyConverter.yen_in_euro = CurrencyConverter.jpy_to_eur
362
+
363
+
364
+ def main():
365
+ """Interactive console interface for interest calculation."""
366
+ print("๐Ÿ“Š Interest Calculator\n")
367
+
368
+ try:
369
+ capital = float(input("Capital (โ‚ฌ): "))
370
+ rate = float(input("Interest rate (% per year): "))
371
+ start_date = input("Start date (DD.MM.YYYY): ")
372
+ end_date = input("End date (DD.MM.YYYY): ")
373
+ method = input(f"Method ({', '.join(InterestCalculator.VALID_METHODS)}): ")
374
+
375
+ interest = InterestCalculator.calculate_interest(capital, rate, start_date, end_date, method)
376
+
377
+ if interest is not None:
378
+ print("\n๐Ÿ’ฐ Result:")
379
+ print(f"Capital: {capital:,.2f} โ‚ฌ")
380
+ print(f"Period: {start_date} to {end_date}")
381
+ print(f"Method: {method}")
382
+ print(f"Interest: {interest:,.2f} โ‚ฌ")
383
+ print(f"Total: {capital + interest:,.2f} โ‚ฌ")
384
+ else:
385
+ print("โŒ Calculation failed. Please check your inputs.")
386
+
387
+ except (ValueError, KeyboardInterrupt):
388
+ print("\nโŒ Invalid input or operation cancelled.")
389
+
390
+
391
+ if __name__ == "__main__":
392
+ main()
@@ -0,0 +1,8 @@
1
+ """
2
+ GUI modules for Corally calculator suite.
3
+ """
4
+
5
+ from .main import ModernCalculatorGUI as CalculatorGUI
6
+ from .launcher import launch_gui
7
+
8
+ __all__ = ["CalculatorGUI", "launch_gui"]