Skip to content

Commit 35c1589

Browse files
authored
Refactor services and unify backend–bot integration
- Switched to async processing in `SolverService` with method selection logic - Reorganized `ApplicationProcessingService` for clearer async flow - Introduced `NotFoundException` for missing resources - Replaced `SolverException` where appropriate - Added centralized exception handling in `GlobalExceptionHandler` - Updated routing in `SolverController`, including parameter types (e.g. `Integer → Long`) - Improved logging and error responses - Changed column types and added constraints in `schema.sql` - Updated `DBService` queries for history filtering and limiting - Adjusted `Dockerfile`, versioning, and jar paths - Made log directory path relative in `logback-spring.xml` - Synced API usage with updated backend routes and parameters - Minor formatting/styling improvements
2 parents 479e70d + 3588f0a commit 35c1589

23 files changed

+692
-627
lines changed

database/schema.sql

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
create Table users (
2-
id INT PRIMARY KEY NOT NULL,
3-
method VARCHAR(25) NOT NULL,
4-
rounding VARCHAR(2) NOT NULL,
5-
language VARCHAR(2) NOT NULL,
1+
CREATE TABLE users (
2+
id BIGINT PRIMARY KEY,
3+
method TEXT NOT NULL,
4+
rounding SMALLINT NOT NULL,
5+
language TEXT NOT NULL,
66
hints BOOLEAN NOT NULL
77
);
88

99
CREATE TABLE applications (
1010
id SERIAL PRIMARY KEY,
11-
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
11+
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
1212
parameters JSONB NOT NULL,
13-
status TEXT NOT NULL CHECK (status IN ('new', 'in_progress', 'completed', 'error')),
13+
status TEXT NOT NULL,
1414
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
1515
last_updated_at TIMESTAMP NOT NULL DEFAULT NOW()
1616
);
1717

1818
CREATE TABLE results (
1919
id SERIAL PRIMARY KEY,
20-
application_id INTEGER NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
20+
application_id INT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
2121
data JSONB NOT NULL,
2222
created_at TIMESTAMP NOT NULL DEFAULT NOW()
2323
);

docker-compose.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@ services:
88
env_file:
99
- .env
1010
environment:
11-
- CLIENT_API_KEY=${CLIENT_API_KEY}
1211
- CLIENT_API_URL=http://server:8080/api/solver
1312
ports:
1413
- "8001:8000"
1514
volumes:
16-
- ./solver-bot/src:/solver-client/src
1715
- .env:/solver-client/.env
1816
depends_on:
1917
- database
@@ -30,16 +28,10 @@ services:
3028
env_file:
3129
- .env
3230
environment:
33-
- DB_CONNECTION=${DB_CONNECTION}
3431
- DB_HOST=database
35-
- DB_PORT=${DB_PORT}
36-
- DB_DATABASE=${DB_DATABASE}
37-
- DB_USERNAME=${DB_USERNAME}
38-
- DB_PASSWORD=${DB_PASSWORD}
3932
ports:
4033
- "8081:8080"
4134
volumes:
42-
- ./solver-common/src:/solver-server/src
4335
- .env:/solver-server/.env
4436
depends_on:
4537
- database

solver-bot/src/main/python/equation/equation_parser.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from equation.function_replacer import replace_math_functions
55

6+
67
def get_equation_order(eq):
78
derivatives = list(eq.lhs.atoms(sp.Derivative))
89
if not derivatives:
@@ -16,20 +17,20 @@ def convert_to_first_order(eq, y, x):
1617
if order == 0:
1718
return eq, order
1819

19-
y_vars = [sp.Symbol(f'y[{i}]') for i in range(order)]
20+
y_vars = [sp.Symbol(f"y[{i}]") for i in range(order)]
2021
subs_dict = {y.diff(x, i): y_vars[i] for i in range(order)}
2122
subs_dict[y] = y_vars[0]
2223

2324
last_derivative = y.diff(x, order)
2425
last_eq = sp.solve(eq, last_derivative)[0].subs(subs_dict)
25-
last_equation = sp.Eq(sp.Symbol(f'y[{order - 1}]').diff(x), last_eq)
26+
last_equation = sp.Eq(sp.Symbol(f"y[{order - 1}]").diff(x), last_eq)
2627

2728
return last_equation, order
2829

2930

3031
def parse_equation(eq):
31-
x = sp.Symbol('x')
32-
y = sp.Function('y')(x)
32+
x = sp.Symbol("x")
33+
y = sp.Function("y")(x)
3334

3435
eq = eq.replace(" ", "").replace("`", "'").replace("’", "'")
3536
eq = re.sub(r"y\^\((\d+)\)", lambda m: "y" + "'" * int(m.group(1)), eq)

solver-bot/src/main/python/equation/equation_validator.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,27 @@
1111

1212

1313
def validate_symbols(equation):
14-
allowed_vars = {'x', 'y', 'X', 'Y'}
15-
symbols = re.findall(r'[a-zA-Z]+|\d+|\S', equation)
14+
allowed_vars = {"x", "y", "X", "Y"}
15+
symbols = re.findall(r"[a-zA-Z]+|\d+|\S", equation)
1616
for symbol in symbols:
17-
if symbol.isalpha() and symbol not in allowed_vars and symbol not in MATH_FUNCTIONS:
17+
if (
18+
symbol.isalpha()
19+
and symbol not in allowed_vars
20+
and symbol not in MATH_FUNCTIONS
21+
):
1822
return False, symbol
1923
return True, None
2024

2125

2226
def validate_parentheses(equation):
2327
stack = []
2428
for i, char in enumerate(equation):
25-
if char == '(':
29+
if char == "(":
2630
stack.append(i)
27-
elif char == ')':
31+
elif char == ")":
2832
if not stack:
2933
return False
3034
start_index = stack.pop()
31-
# Check for empty parentheses
3235
if start_index + 1 == i:
3336
return False
3437

solver-bot/src/main/python/equation/function_replacer.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,11 @@ def replace_math_functions(equation):
1717
if there are, it converts them.
1818
"""
1919
for func, replacement in MATH_FUNCTIONS.items():
20-
if func in ['coth', 'cth']:
21-
equation = re.sub(rf'\b{func}\((.*?)\)',
22-
r'(cosh(\1) / sinh(\1))',
23-
equation)
24-
elif func in ['acot', 'actg', 'arccot', 'arcctg']:
25-
equation = re.sub(rf'\b{func}\((.*?)\)',
26-
r'(atan(1 / \1))',
27-
equation)
20+
if func in ["coth", "cth"]:
21+
equation = re.sub(rf"\b{func}\((.*?)\)", r"(cosh(\1) / sinh(\1))", equation)
22+
elif func in ["acot", "actg", "arccot", "arcctg"]:
23+
equation = re.sub(rf"\b{func}\((.*?)\)", r"(atan(1 / \1))", equation)
2824
else:
29-
equation = re.sub(rf'\b{func}\b',
30-
f'{replacement}',
31-
equation)
25+
equation = re.sub(rf"\b{func}\b", f"{replacement}", equation)
3226

3327
return equation

solver-bot/src/main/python/languages.json

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@
2727

2828
"hints_text": "Hint:",
2929

30-
"hints_enter_equation": "For example: y' = x + y or y'' + sin(x). You can use variables x and y, arithmetic operators (+, -, *, /), and functions such as sin(x), exp(x), ln(x).",
31-
"hints_enter_x": "The point at which you start solving. For example, if you want to solve the equation with the initial value y(0) = 1, enter 0.",
32-
"hints_enter_y": "The initial value of the function y at the given x. For example, if you want to solve the equation with the initial value y(0) = 1, enter 1.",
33-
"hints_enter_y_multiple": "Initial values of the function y at the given x values. For example, if you want to solve the equation with initial values y(0) = 1 and y(1) = 2, enter 1, 2 or 1 2.",
34-
"hints_enter_reach_point": "Specifies up to which point you want to solve the equation. For example, if you want to solve the equation up to x = 10, enter 10.",
35-
"hints_enter_step_size": "Affects the calculation accuracy. For example: if you want the step size to be 0.1, enter 0.1.",
30+
"hints": {
31+
"enter_equation": "For example: y' = x + y or y'' + sin(x). You can use variables x and y, arithmetic operators (+, -, *, /), and functions such as sin(x), exp(x), ln(x).",
32+
"enter_x": "The point at which you start solving. For example, if you want to solve the equation with the initial value y(0) = 1, enter 0.",
33+
"enter_y": "The initial value of the function y at the given x. For example, if you want to solve the equation with the initial value y(0) = 1, enter 1.",
34+
"enter_y_multiple": "Initial values of the function y at the given x values. For example, if you want to solve the equation with initial values y(0) = 1 and y(1) = 2, enter 1, 2 or 1 2.",
35+
"enter_reach_point": "Specifies up to which point you want to solve the equation. For example, if you want to solve the equation up to x = 10, enter 10.",
36+
"enter_step_size": "Affects the calculation accuracy. For example: if you want the step size to be 0.1, enter 0.1."
37+
},
3638

3739
"equation_error": "The equation is incorrect.",
3840
"symbols_error": "The equation contains invalid symbols: ",
@@ -42,10 +44,11 @@
4244
"invalid_initial_y_count1": "Invalid number of initial y values: ",
4345
"invalid_initial_y_count2": "Expected: ",
4446
"invalid_reach_point": "Invalid approximation point.",
47+
"reach_point_equals_initial": "The approximation point must be different from the initial x value.",
4548
"invalid_step_size": "Invalid step size value.",
49+
"too_many_points": "Too many calculation points: ",
50+
"max_points_allowed": "Maximum allowed: ",
4651

47-
"processing": "Processing your request...",
48-
"processing_longer": "It may take longer than usual to process your request. Please wait...",
4952
"processing_error": "Error processing your request.",
5053
"data_error": "The entered data is incorrect.",
5154
"server_error": "Server error.",
@@ -98,12 +101,14 @@
98101

99102
"hints_text": "Подсказка:",
100103

101-
"hints_enter_equation": "Например: y' = x + y или y'' + sin(x). Можно использовать переменные x и y, арифметические знаки (+, -, *, /) и функции, такие как sin(x), exp(x), ln(x).",
102-
"hints_enter_x": "Точка, в которой вы начинаете решение. Например, если вы хотите решить уравнение с начальным значением y(0) = 1, введите 0.",
103-
"hints_enter_y": "Начальное значение функции y при заданном x. Например, если вы хотите решить уравнение с начальным значением y(0) = 1, введите 1.",
104-
"hints_enter_y_multiple": "Начальные значения функции y при заданных x. Например, если вы хотите решить уравнение с начальными значениями y(0) = 1 и y(1) = 2, введите 1, 2 или 1 2.",
105-
"hints_enter_reach_point": "Определяет, до какой точки вы хотите решить уравнение. Например, если вы хотите решить уравнение до x = 10, введите 10.",
106-
"hints_enter_step_size": "Влияет на точность вычислений. Например: если вы хотите, чтобы шаг был 0.1, введите 0.1.",
104+
"hints": {
105+
"enter_equation": "Например: y' = x + y или y'' + sin(x). Можно использовать переменные x и y, арифметические знаки (+, -, *, /) и функции, такие как sin(x), exp(x), ln(x).",
106+
"enter_x": "Точка, в которой вы начинаете решение. Например, если вы хотите решить уравнение с начальным значением y(0) = 1, введите 0.",
107+
"enter_y": "Начальное значение функции y при заданном x. Например, если вы хотите решить уравнение с начальным значением y(0) = 1, введите 1.",
108+
"enter_y_multiple": "Начальные значения функции y при заданных x. Например, если вы хотите решить уравнение с начальными значениями y(0) = 1 и y(1) = 2, введите 1, 2 или 1 2.",
109+
"enter_reach_point": "Определяет, до какой точки вы хотите решить уравнение. Например, если вы хотите решить уравнение до x = 10, введите 10.",
110+
"enter_step_size": "Влияет на точность вычислений. Например: если вы хотите, чтобы шаг был 0.1, введите 0.1."
111+
},
107112

108113
"equation_error": "Уравнение некорректно.",
109114
"symbols_error": "Уравнение содержит недопустимые символы: ",
@@ -113,10 +118,11 @@
113118
"invalid_initial_y_count1": "Некорректное количество начальных значений y: ",
114119
"invalid_initial_y_count2": "Ожидалось: ",
115120
"invalid_reach_point": "Некорректное значение точки аппроксимации.",
121+
"reach_point_equals_initial": "Точка аппроксимации должна отличаться от начального значения x.",
116122
"invalid_step_size": "Некорректное значение размера шага.",
123+
"too_many_points": "Слишком много точек для вычисления: ",
124+
"max_points_allowed": "Максимально допустимо: ",
117125

118-
"processing": "Обработка вашего запроса...",
119-
"processing_longer": "Обработка вашего запроса может занять больше времени, чем обычно. Пожалуйста, подождите...",
120126
"processing_error": "Ошибка при обработке вашего запроса.",
121127
"data_error": "Введенные данные некорректны.",
122128
"server_error": "Ошибка сервера.",
@@ -169,12 +175,14 @@
169175

170176
"hints_text": "提示:",
171177

172-
"hints_enter_equation": "例如:y' = x + y 或 y'' + sin(x)。您可以使用变量 x 和 y,算术运算符(+、-、*、/),以及 sin(x)、exp(x)、ln(x) 等函数。",
173-
"hints_enter_x": "开始求解的点。例如,如果您想求解初值 y(0) = 1 的方程,输入 0。",
174-
"hints_enter_y": "在给定 x 处函数 y 的初值。例如,如果您想求解初值 y(0) = 1 的方程,输入 1。",
175-
"hints_enter_y_multiple": "在给定 x 值处函数 y 的初值。例如,如果您想求解初值 y(0) = 1 和 y(1) = 2 的方程,输入 1, 2 或 1 2。",
176-
"hints_enter_reach_point": "指定要解方程到的点。例如,如果您想解方程到 x = 10,输入 10。",
177-
"hints_enter_step_size": "影响计算精度。例如:如果您希望步长为 0.1,输入 0.1。",
178+
"hints": {
179+
"enter_equation": "例如:y' = x + y 或 y'' + sin(x)。您可以使用变量 x 和 y,算术运算符(+、-、*、/),以及 sin(x)、exp(x)、ln(x) 等函数。",
180+
"enter_x": "开始求解的点。例如,如果您想求解初值 y(0) = 1 的方程,输入 0。",
181+
"enter_y": "在给定 x 处函数 y 的初值。例如,如果您想求解初值 y(0) = 1 的方程,输入 1。",
182+
"enter_y_multiple": "在给定 x 值处函数 y 的初值。例如,如果您想求解初值 y(0) = 1 和 y(1) = 2 的方程,输入 1, 2 或 1 2。",
183+
"enter_reach_point": "指定要解方程到的点。例如,如果您想解方程到 x = 10,输入 10。",
184+
"enter_step_size": "影响计算精度。例如:如果您希望步长为 0.1,输入 0.1。"
185+
},
178186

179187
"equation_error": "方程不正确。",
180188
"symbols_error": "方程包含无效符号:",
@@ -184,10 +192,11 @@
184192
"invalid_initial_y_count1": "初始y值数量无效:",
185193
"invalid_initial_y_count2": "应为:",
186194
"invalid_reach_point": "无效的近似点。",
195+
"reach_point_equals_initial": "近似点必须与初始x值不同。",
187196
"invalid_step_size": "无效的步长值。",
197+
"too_many_points": "计算点数过多:",
198+
"max_points_allowed": "最大允许:",
188199

189-
"processing": "正在处理您的请求...",
190-
"processing_longer": "处理时间可能较长,请稍候...",
191200
"processing_error": "处理请求时出错。",
192201
"data_error": "输入数据不正确。",
193202
"server_error": "服务器错误。",

solver-bot/src/main/python/logger.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@
66
LOGS_DIR.mkdir(parents=True, exist_ok=True)
77

88
formatter = logging.Formatter(
9-
"%(asctime)s - %(levelname)s - %(name)s - %(message)s",
10-
datefmt="%Y-%m-%d %H:%M:%S"
9+
"%(asctime)s - %(levelname)s - %(name)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
1110
)
1211

1312
console_handler = logging.StreamHandler()
1413
console_handler.setFormatter(formatter)
1514

16-
file_handler = logging.FileHandler(LOGS_DIR / "solver-bot.log", encoding='utf-8')
15+
file_handler = logging.FileHandler(LOGS_DIR / "solver-bot.log", encoding="utf-8")
1716
file_handler.setFormatter(formatter)
1817

1918
logging.basicConfig(level=logging.INFO, handlers=[console_handler, file_handler])

solver-bot/src/main/python/plotting/plotter.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,26 @@
33

44
from printing.printer import get_variable_name
55

6-
def plot_solution(x_values, y_values, order):
7-
plt.clf()
86

7+
def plot_solution(x_values, y_values, order):
98
plt.figure(figsize=(10, 6), dpi=200)
10-
119
plt.grid(True)
1210

1311
variable_names = ["y"] + [get_variable_name(i) for i in range(1, order)]
1412

15-
plt.plot(x_values, y_values, label=variable_names)
13+
is_multivariable = isinstance(y_values[0], (list, tuple))
14+
15+
if is_multivariable:
16+
for i, var_name in enumerate(variable_names):
17+
plt.plot(x_values, [y[i] for y in y_values], label=var_name)
18+
else:
19+
plt.plot(x_values, y_values, label=variable_names[0])
1620

1721
plt.legend()
1822

1923
buffer = io.BytesIO()
20-
plt.savefig(buffer, format='png', bbox_inches="tight")
21-
24+
plt.savefig(buffer, format="png", bbox_inches="tight")
2225
plt.close()
23-
2426
buffer.seek(0)
2527

2628
return buffer

solver-bot/src/main/python/printing/printer.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@ def get_variable_name(i):
66
if i < 4:
77
return f"y{chr(39) * i}"
88
else:
9-
return "y⁽" + ''.join(superscripts[int(d)] for d in str(i)) + "⁾"
9+
return "y⁽" + "".join(superscripts[int(d)] for d in str(i)) + "⁾"
1010

1111

1212
def print_solution(result, order, rounding):
1313
def format_value(value):
1414
formatted_value = f"{value:.{rounding}f}"
15-
if '.' in formatted_value:
16-
formatted_value = formatted_value.rstrip('0')
17-
if formatted_value.endswith('.'):
18-
formatted_value += '0'
15+
if "." in formatted_value:
16+
formatted_value = formatted_value.rstrip("0")
17+
if formatted_value.endswith("."):
18+
formatted_value += "0"
1919
return formatted_value
2020

2121
variable_names = [get_variable_name(i) for i in range(order)]
22-
values = [format_value(result[i]) if i < len(result) else "NaN" for i in range(1, order + 1)]
22+
values = [
23+
format_value(result[i]) if i < len(result) else "NaN"
24+
for i in range(1, order + 1)
25+
]
2326

2427
formatted_x = format_value(result[0])
25-
variables_str = ", ".join(f"{name}: {val}" for name, val in zip(variable_names, values))
28+
variables_str = ", ".join(
29+
f"{name}: {val}" for name, val in zip(variable_names, values)
30+
)
2631

2732
return f"x: {formatted_x}, {variables_str}"

0 commit comments

Comments
 (0)