from flask import Flask, request, jsonify, redirect, url_for, render_template, send_from_directory, send_file, render_template_string import os import sqlite3 from datetime import datetime import pytz import io import base64 from dotenv import load_dotenv import requests import json import logging import uuid from io import BytesIO import numpy as np from tabulate import tabulate from typing import Dict, Any, Union, List import itertools import traceback import logging from werkzeug.utils import secure_filename import globs from api_logic import api from urllib.parse import urlencode # Добавлен правильный импорт load_dotenv() # Инициализация базы данных def init_db(db_name): conn = sqlite3.connect(db_name) cursor = conn.cursor() # Таблица с системными данными (твоя старая таблица) cursor.execute(''' CREATE TABLE IF NOT EXISTS system_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, date_time TEXT, dey TEXT, wek TEXT, v_hid TEXT, v_min TEXT, ph TEXT, ec TEXT, tS TEXT, tA TEXT, hDm TEXT, sVen TEXT, onA TEXT, onB TEXT, onC TEXT, nPh TEXT, nEC TEXT, nTa TEXT, nLon TEXT, nLoff TEXT ) ''') # **Новая таблица для пользователей бота** cursor.execute(''' CREATE TABLE IF NOT EXISTS bot_users ( id INTEGER PRIMARY KEY AUTOINCREMENT, -- Уникальный ID chat_id INTEGER UNIQUE, -- Telegram ID пользователя created_at TEXT -- Время добавления (ISO формат) ) ''') conn.commit() conn.close() # Глобальные переменные api_key_sys = os.getenv('api_key') # Берём значение API-ключа btg_key = os.getenv('btg_key') # Берём значение btg_id = os.getenv('btg_id') # Берём значение btg_on = os.getenv('btg_on') # Берём значение globs.dey = 0 globs.wek = 0 globs.v_hid = 0 globs.v_min = 0 globs.ph = 0 globs.ec = 0 globs.tS = 0 globs.tA = 0 globs.hDm = 0 globs.sVen = 0 globs.onA = 0 globs.onB = 0 globs.onC = 0 globs.ph_eep = 0 globs.ph_on_eep = 0 globs.ec_eep = 0 globs.ec_A_eep = 0 globs.ec_B_eep = 0 globs.ec_C_eep = 0 globs.l_ON_h_eep = 0 globs.l_ON_m_eep = 0 globs.l_OFF_h_eep = 0 globs.l_OFF_m_eep = 0 globs.t_Voz_eep = 0 # Создаем экземпляр Flask-приложения app = Flask(__name__, template_folder="./") app.config['DEBUG'] = True UPLOAD_FOLDER = 'uploads' ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'} # Создаем папку для загрузок если ее нет os.makedirs(UPLOAD_FOLDER, exist_ok=True) # Глобальная переменная для хранения последнего изображения в памяти latest_image = {"data": None, "filename": None} # Настроим логирование logging.basicConfig(level=logging.DEBUG) # Функция сохранения в базу данных системы автоматизации гидропоники def save_data_to_db(db_name, data): try: conn = sqlite3.connect(db_name) cursor = conn.cursor() # ✅ Устанавливаем московское время (UTC+3) moscow_tz = pytz.timezone("Europe/Moscow") current_time = datetime.now(moscow_tz).strftime('%Y-%m-%d %H:%M:%S') # Вставляем данные в таблицу cursor.execute(''' INSERT INTO system_data ( date_time, dey, wek, v_hid, v_min, ph, ec, tS, tA, hDm, sVen, onA, onB, onC, nPh, nEC, nTa, nLon, nLoff ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', ( current_time, # ✅ Дата и время по Москве data['dey'], data['wek'], data['v_hid'], data['v_min'], data['ph'], data['ec'], data['tS'], data['tA'], data['hDm'], data['sVen'], data['onA'], data['onB'], data['onC'], data['nPh'], data['nEC'], data['nTa'], data['nLon'], data['nLoff'] )) conn.commit() conn.close() except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 # Маршрут сохранения в базу @app.route('/sav_db_api', methods=['GET']) def sav_db_api(): # Инициализируем базу данных init_db('system_data.db') # Получаем данные из запроса data = { 'dey': request.args.get('dey'), 'wek': request.args.get('wek'), 'v_hid': request.args.get('v_hid'), 'v_min': request.args.get('v_min'), 'ph': request.args.get('ph'), 'ec': request.args.get('ec'), 'tS': request.args.get('tS'), 'tA': request.args.get('tA'), 'hDm': request.args.get('hDm'), 'sVen': request.args.get('sVen'), 'onA': request.args.get('onA'), 'onB': request.args.get('onB'), 'onC': request.args.get('onC'), 'nPh': request.args.get('nPh'), 'nEC': request.args.get('nEC'), 'nTa': request.args.get('nTa'), 'nLon': request.args.get('nLon'), 'nLoff': request.args.get('nLoff') } # Проверяем, что все необходимые параметры переданы required_params = ['dey', 'wek', 'v_hid', 'v_min', 'ph', 'ec', 'tS', 'tA', 'hDm', 'sVen', 'onA', 'onB', 'onC', 'nPh', 'nEC', 'nTa', 'nLon', 'nLoff'] for param in required_params: if data[param] is None: return jsonify({'status': 'error', 'message': f'Отсутствует параметр: {param}'}), 400 # Сохраняем данные в базу save_data_to_db('system_data.db', data) # Возвращаем ответ return jsonify({'status': 'success', 'message': 'Save OK'}) # Проверка входа на страницы @app.route('/page_key', methods=['GET']) def check_api_key(): api_sys_param = request.args.get('api_sys') # Получаем параметр из запроса if api_sys_param == api_key_sys: return jsonify({"status": "ok"}), 200 # ✅ Совпадает — отправляем "ok" else: return jsonify({"status": "error", "message": "Invalid API key"}), 403 # ❌ Ошибка 403 # Тестовый запрос с установки @app.route('/test_server', methods=['GET']) def test_server(): api_key_param = request.args.get('api_sys') # Получаем параметр из запроса err_ser = 1 if api_key_param == api_key_sys else 0 # Проверяем совпадение ключей return jsonify(err_ser=err_ser) @app.route('/test_server_str', methods=['GET']) def test_server_str(): api_key_param = request.args.get('api_sys') err_ser = "1" if api_key_param == api_key_sys else "0" return err_ser # Возвращаем строку "1" или "0" # Тестовый запрос с установки @app.route('/btg_teleg', methods=['GET']) def btg_teleg(): api_key_param = request.args.get('api_sys') # Получаем параметр из запроса return jsonify(btg_key_ser=btg_key,btg_id_ser=btg_id,btg_on_ser=btg_on) # Маршрут для вывода всех данных из таблицы @app.route('/get_all_data', methods=['GET']) def get_all_data(): try: conn = sqlite3.connect('system_data.db') cursor = conn.cursor() # Выполняем запрос для получения всех данных из таблицы cursor.execute('SELECT * FROM system_data') rows = cursor.fetchall() # Получаем названия столбцов column_names = [description[0] for description in cursor.description] # Преобразуем данные в формат JSON data = [] for row in rows: data.append(dict(zip(column_names, row))) conn.close() # Возвращаем данные в формате JSON return jsonify(data) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 # Удаление базы @app.route('/delite_db', methods=['GET']) def delete_db(): try: conn = sqlite3.connect("system_data.db") # Используем вашу БД cursor = conn.cursor() # ✅ Удаляем все записи из таблицы cursor.execute("DELETE FROM system_data") # ✅ Сбрасываем автоинкрементный счётчик ID (для SQLite) cursor.execute("DELETE FROM sqlite_sequence WHERE name='system_data'") conn.commit() conn.close() return jsonify({'status': 'ok', 'message': 'База данных успешно очищена'}) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 @app.route('/plot_week', methods=['GET']) def plot_week(): try: # Получаем номер недели из параметров запроса week_number = request.args.get('week', default=1, type=int) week_number = max(1, min(30, week_number)) # Ограничиваем диапазон 1-30 # Подключаемся к базе данных conn = sqlite3.connect('system_data.db') cursor = conn.cursor() # Проверяем существование таблицы cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='system_data'") table_exists = cursor.fetchone() if not table_exists: conn.close() return render_template('plot_week.html', data=None, week_number=week_number, table_exists=False) # Запрашиваем данные за выбранную неделю cursor.execute(''' SELECT date_time, dey, ph, ec, tS, tA, hDm, sVen, onA, onB, onC, v_hid, v_min FROM system_data WHERE wek = ? ORDER BY date_time ''', (str(week_number),)) # Приводим week_number к строке, так как wek имеет тип TEXT rows = cursor.fetchall() conn.close() # Если данных нет if not rows: return render_template('plot_week.html', data=None, week_number=week_number, table_exists=True) # Формируем данные для JSON data = { 'week': week_number, 'dates': [row[0] for row in rows], 'days_of_week': [int(row[1]) if row[1] else 0 for row in rows], # Преобразуем dey в int 'ph': [float(row[2]) if row[2] else 0.0 for row in rows], # pH 'ec': [float(row[3]) if row[3] else 0.0 for row in rows], # EC 'tS': [float(row[4]) if row[4] else 0.0 for row in rows], # Температура раствора 'tA': [float(row[5]) if row[5] else 0.0 for row in rows], # Температура воздуха 'hDm': [float(row[6]) if row[6] else 0.0 for row in rows], # Влажность воздуха 'sVen': [float(row[7]) if row[7] else 0.0 for row in rows], # Обороты вентилятора 'onA': [float(row[8]) if row[8] else 0.0 for row in rows], # Насос A 'onB': [float(row[9]) if row[9] else 0.0 for row in rows], # Насос B 'onC': [float(row[10]) if row[10] else 0.0 for row in rows], # Насос C 'sus': [f"{row[11]}:{row[12]}" if row[11] and row[12] else "0:0" for row in rows] # Объединяем v_hid и v_min } # Отправляем данные в HTML-шаблон return render_template('plot_week.html', data=data, week_number=week_number, table_exists=True) except Exception as e: # Логируем ошибку в консоль для отладки print(f"Ошибка: {str(e)}") return render_template('plot_week.html', data=None, week_number=week_number, table_exists=True, message=f"Ошибка: {str(e)}") @app.route("/") def index(): return flask.render_template('index.html') @app.route('/online', methods=['GET']) def online(): return render_template('online.html') @app.route('/table', methods=['GET']) def table(): return render_template('table.html') @app.route('/online_api', methods=['GET']) def online_api(): # Устанавливаем московское время (UTC+3) moscow_tz = pytz.timezone("Europe/Moscow") current_time = datetime.now(moscow_tz) # Форматируем дату и время отдельно date = current_time.strftime('%Y-%m-%d') # Например, "2025-03-23" time = current_time.strftime('%H:%M:%S') # Например, "14:35:42" return jsonify( dey=globs.dey, wek=globs.wek, v_hid=globs.v_hid, v_min=globs.v_min, ph=globs.ph, ec=globs.ec, tS=globs.tS, tA=globs.tA, hDm=globs.hDm, sVen=globs.sVen, rFul=globs.rFul, rLi=globs.rLi, rWat=globs.rWat, rRas=globs.rRas, rPH=globs.rPH, rEC=globs.rEC, rSl=globs.rSl, rLe=globs.rLe, alW=globs.alW, ec_A_eep=globs.ec_A_eep, ec_B_eep=globs.ec_B_eep, ec_C_eep=globs.ec_C_eep, date=date, # Добавляем дату time=time # Добавляем время ) @app.route('/settings', methods=['GET']) def settings(): return render_template('settings.html') @app.route('/settings_api', methods=['GET']) def settings_api(): return jsonify(ph_eep=globs.ph_eep, ph_on_eep=globs.ph_on_eep, ec_eep=globs.ec_eep, ec_A_eep=globs.ec_A_eep, ec_B_eep=globs.ec_B_eep, ec_C_eep=globs.ec_C_eep, l_ON_h_eep=globs.l_ON_h_eep, l_ON_m_eep=globs.l_ON_m_eep, l_OFF_h_eep=globs.l_OFF_h_eep, l_OFF_m_eep=globs.l_OFF_m_eep, t_Voz_eep=globs.t_Voz_eep, set_st=globs.set_status ) @app.route('/pH_set', methods=['GET']) def set_pH_value(): ph_value = request.args.get('value') globs.ph_set = ph_value globs.eep_set = 1 return "pH value set successfully" @app.route('/ph_on_set', methods=['GET']) def ph_on_value(): ph_on_value = request.args.get('value') globs.ph_on_set = ph_on_value globs.eep_set = 2 return "EC value set successfully" @app.route('/EC_set', methods=['GET']) def set_EC_value(): ec_value = request.args.get('value') globs.ec_set = ec_value globs.eep_set = 3 return "EC value set successfully" @app.route('/ec_A_set', methods=['GET']) def ec_A_setValue(): ec_A_setValue = request.args.get('value') globs.ec_A_set = ec_A_setValue globs.eep_set = 4 return "EC value set successfully" @app.route('/ec_B_set', methods=['GET']) def ec_B_setValue(): ec_B_setValue = request.args.get('value') globs.ec_B_set = ec_B_setValue globs.eep_set = 5 return "EC value set successfully" @app.route('/ec_C_set', methods=['GET']) def ec_C_setValue(): ec_C_setValue = request.args.get('value') globs.ec_C_set = ec_C_setValue globs.eep_set = 6 return "EC value set successfully" @app.route('/l_ON_set', methods=['GET']) def l_ON_set(): globs.l_ON_h_set = request.args.get('l_ON_h_set') globs.l_ON_m_set = request.args.get('l_ON_m_set') globs.eep_set = 7 return "EC value set successfully" @app.route('/l_OFF_set', methods=['GET']) def l_OFF_set(): globs.l_OFF_h_set = request.args.get('l_OFF_h_set') globs.l_OFF_m_set = request.args.get('l_OFF_m_set') globs.eep_set = 8 return "EC value set successfully" @app.route('/t_Voz_eep_set', methods=['GET']) def t_Voz_eep_set(): t_Voz_eep_set = request.args.get('value') globs.t_Voz_set = t_Voz_eep_set globs.eep_set = 9 return "EC value set successfully" @app.route('/but_start', methods=['GET']) def but_start(): globs.eep_set = 10 return jsonify(value_set="start") @app.route('/but_stop', methods=['GET']) def but_stop(): globs.eep_set = 11 return jsonify(value_set="stop") @app.route('/but_res', methods=['GET']) def but_res(): globs.eep_set = 12 return jsonify(value_set="res") @app.route('/but_sliv', methods=['GET']) def but_sliv(): globs.eep_set = 13 return jsonify(value_set="sliv") @app.route("/api", methods=['GET']) def handle_api(): response = api() return response @app.route("/save_db", methods=['GET']) def handle_save_db(): response = save_db() return response @app.route('/set_res') def set_res(): globs.eep_set = 0 return jsonify(value_set="reset") def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/upload', methods=['POST']) def upload_file(): if 'file' not in request.files: return jsonify({"error": "No file part"}), 400 file = request.files['file'] if file.filename == '': return jsonify({"error": "No selected file"}), 400 if not allowed_file(file.filename): return jsonify({"error": "Invalid file type"}), 400 # Генерируем имя для файла с использованием времени timestamp = datetime.now().strftime('%Y.%m.%d_%H:%M:%S_') filename = timestamp + file.filename save_path = os.path.join(UPLOAD_FOLDER, filename) # Открываем файл для записи и собираем его части try: with open(save_path, 'wb') as f: while chunk := file.read(1024): # Чтение и запись данных частями f.write(chunk) return jsonify({ "message": "File uploaded successfully", "filename": filename, "path": save_path }), 200 except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/uploads/', methods=['GET']) def uploaded_file(filename): return send_from_directory(UPLOAD_FOLDER, filename) # 🧠 2. Маршрут: сохраняет файл только в память (BytesIO) # Маршрут для загрузки файла в память # Загрузка изображения в память (в виде байтов) @app.route('/upload_memory', methods=['POST']) def upload_file_to_memory(): if 'file' not in request.files: return jsonify({"error": "No file part"}), 400 file = request.files['file'] if file.filename == '': return jsonify({"error": "No selected file"}), 400 if not file.filename.lower().endswith(('.jpg', '.jpeg', '.png')): return jsonify({"error": "Invalid file type"}), 400 timestamp = datetime.now().strftime('%Y.%m.%d_%H:%M:%S_') filename = timestamp + file.filename try: # Читаем весь файл в байты file_bytes = file.read() # Сохраняем в переменную latest_image["data"] = file_bytes latest_image["filename"] = filename return jsonify({ "message": "Image uploaded successfully", "filename": filename }), 200 except Exception as e: return jsonify({"error": str(e)}), 500 # Получение последнего изображения @app.route('/last_image', methods=['GET']) def get_last_image(): if not latest_image["data"]: return jsonify({"error": "No image available"}), 404 # Возвращаем через новый BytesIO каждый раз return send_file( BytesIO(latest_image["data"]), mimetype='image/jpeg', download_name=latest_image["filename"] ) @app.route('/view_image', methods=['GET']) def view_image(): return render_template('show_image.html') @app.route('/nutri_call', methods=['GET']) def nutri_call(): return render_template('nutri_call.html') from tabulate import tabulate # Глобальные параметры TOTAL_NITROGEN = 120.0 # Общее количество азота NO3_RATIO = 8.0 # Соотношение NO3:NH4 NH4_RATIO = 1.00 # Соотношение NH4:NO3 VOLUME_LITERS = 100 # Объем раствора BASE_PROFILE = { "P": 50, # Фосфор "K": 210, # Калий "Mg": 120, # Магний (высокий уровень) "Ca": 150, # Кальций "S": 50, # Сера "N (NO3-)": 0, # Рассчитывается автоматически "N (NH4+)": 0 # Рассчитывается автоматически } NUTRIENT_CONTENT_IN_FERTILIZERS = { "Кальциевая селитра": {"N (NO3-)": 0.11863, "Ca": 0.16972}, "Калий азотнокислый": {"N (NO3-)": 0.136, "K": 0.382}, "Калий сернокислый": {"K": 0.44874, "S": 0.18401}, "Аммоний азотнокислый": {"N (NO3-)": 0.17499, "N (NH4+)": 0.17499}, "Сульфат магния": {"Mg": 0.09861, "S": 0.13010}, "Монофосфат калия": {"P": 0.218, "K": 0.275}, "Сульфат кальция": {"Ca": 0.23, "S": 0.186}, "Кольцевая селитра": {"N (NO3-)": 0.15, "Ca": 0.20} # Новое удобрение } EC_COEFFICIENTS = { 'P': 0.0012, 'K': 0.0018, 'Mg': 0.0015, 'Ca': 0.0016, 'S': 0.0014, 'N (NO3-)': 0.0017, 'N (NH4+)': 0.0019 } class NutrientCalculator: def __init__(self, volume_liters=1.0): self.volume = volume_liters self.results = {} self.target_profile = BASE_PROFILE.copy() self.actual_profile = {k: 0.0 for k in BASE_PROFILE} self.fertilizers = NUTRIENT_CONTENT_IN_FERTILIZERS self.total_ec = 0.0 # Расчёт азота total_parts = NO3_RATIO + NH4_RATIO self.target_profile['N (NO3-)'] = TOTAL_NITROGEN * (NO3_RATIO / total_parts) self.target_profile['N (NH4+)'] = TOTAL_NITROGEN * (NH4_RATIO / total_parts) # Сохраняем исходный профиль азота self.initial_n_profile = { "NO3-": self.target_profile['N (NO3-)'], "NH4+": self.target_profile['N (NH4+)'] } # Список всех удобрений self.fertilizer_list = list(self.fertilizers.keys()) def _label(self, element): """Форматирование названий элементов для вывода""" labels = { 'N (NO3-)': 'NO3', 'N (NH4+)': 'NH4' } return labels.get(element, element) def calculate(self): try: max_attempts = 100 # Максимальное количество попыток attempt = 0 while attempt < max_attempts: attempt += 1 print(f"Попытка {attempt} из {max_attempts}") # Генерация случайного порядка удобрений fertilizer_order = self._shuffle_fertilizers() # Сброс фактического профиля self._reset_actual_profile() # Пробуем рассчитать с текущим порядком удобрений if self._try_combination(fertilizer_order): print("Успешная комбинация найдена!") return self.results raise ValueError("Не удалось найти подходящую комбинацию удобрений после максимального количества попыток.") except Exception as e: print(f"Ошибка при расчёте: {str(e)}") raise def _shuffle_fertilizers(self): """Генерация случайного порядка удобрений""" return list(itertools.permutations(self.fertilizer_list))[hash(str(self.target_profile)) % len(self.fertilizer_list)] def _reset_actual_profile(self): """Сброс фактического профиля перед новой попыткой""" self.actual_profile = {k: 0.0 for k in BASE_PROFILE} self.total_ec = 0.0 self.results = {} def _try_combination(self, fertilizer_order): """Попытка расчета с заданным порядком удобрений""" for fert_name in fertilizer_order: for element, content in self.fertilizers[fert_name].items(): required_ppm = self.target_profile[element] - self.actual_profile[element] if required_ppm > 0.1: grams = (required_ppm * self.volume) / (content * 1000) if fert_name not in self.results: self.results[fert_name] = { 'граммы': 0.0, 'миллиграммы': 0, 'вклад в EC': 0.0 } for elem in self.fertilizers[fert_name]: self.results[fert_name][f'внесет {self._label(elem)}'] = 0.0 self.results[fert_name]['граммы'] += grams self.results[fert_name]['миллиграммы'] += int(grams * 1000) fert_ec = 0.0 for elem, percent in self.fertilizers[fert_name].items(): added_ppm = (grams * percent * 1000) / self.volume self.results[fert_name][f'внесет {self._label(elem)}'] += added_ppm self.actual_profile[elem] += added_ppm fert_ec += added_ppm * EC_COEFFICIENTS.get(elem, 0.0015) self.results[fert_name]['вклад в EC'] += fert_ec self.total_ec += fert_ec # Проверка результатов return self.validate_results() def validate_results(self): """Проверка соответствия целевым значениям""" for element, target_value in self.target_profile.items(): actual_value = self.actual_profile[element] if abs(actual_value - target_value) > 0.1: # Допустимая погрешность print(f"Несоответствие: {element} (цель: {target_value:.2f}, фактически: {actual_value:.2f})") return False return True def calculate_ec(self): return round(self.total_ec, 2) def print_initial_nitrogen_report(self): try: print("Исходный расчёт азота:") print(f" NO3-: {self.initial_n_profile['NO3-']} ppm") print(f" NH4+: {self.initial_n_profile['NH4+']} ppm") except Exception as e: print(f"Ошибка при выводе отчёта: {str(e)}") raise def print_report(self): try: print("\n" + "="*60) print("ПРОФИЛЬ ПИТАТЕЛЬНОГО РАСТВОРА (ИТОГО):") print("="*60) table = [[el, round(self.actual_profile[el], 1)] for el in self.actual_profile] print(tabulate(table, headers=["Элемент", "ppm"])) print("\nИсходный расчёт азота:") for form, val in self.initial_n_profile.items(): print(f" {form}: {round(val, 1)} ppm") print("\n" + "="*60) print(f"РАСЧЕТ ДЛЯ {self.volume} ЛИТРОВ РАСТВОРА") print("="*60) print(f"Общая концентрация: {round(sum(self.actual_profile.values()), 1)} ppm") print(f"EC: {self.calculate_ec()} mS/cm") print("\nРЕКОМЕНДУЕМЫЕ УДОБРЕНИЯ:") fert_table = [] for fert, data in self.results.items(): adds = [f"+{k}: {v:.1f} ppm" for k, v in data.items() if k.startswith('внесет')] fert_table.append([ fert, round(data['граммы'], 3), data['миллиграммы'], round(data['вклад в EC'], 3), "\n".join(adds) ]) print(tabulate(fert_table, headers=["Удобрение", "Граммы", "Миллиграммы", "EC (мСм/см)", "Добавит"])) print("\nОСТАТОЧНЫЙ ДЕФИЦИТ:") deficit = { k: round(self.target_profile[k] - self.actual_profile[k], 1) for k in self.target_profile if abs(self.target_profile[k] - self.actual_profile[k]) > 0.1 } if deficit: for el, val in deficit.items(): print(f" {el}: {val} ppm") else: print(" Все элементы покрыты полностью") except Exception as e: print(f"Ошибка при выводе отчёта: {str(e)}") raise if __name__ == "__main__": try: calculator = NutrientCalculator(volume_liters=VOLUME_LITERS) calculator.calculate() calculator.print_report() # Правильный вызов метода класса except Exception as e: print(f"Критическая ошибка: {str(e)}") @app.route('/calculation', methods=['POST']) def handle_calculation(): try: data = request.get_json() # Проверка обязательных полей if not data or 'profileSettings' not in data or 'fertilizerConstants' not in data: return jsonify({'error': 'Неверный формат данных'}), 400 # Инициализация калькулятора calculator = NutrientCalculator(volume_liters=float(data['profileSettings'].get('liters', 100))) # Базовый профиль (начальные значения) BASE_PROFILE = { "P": 50, # Фосфор "K": 210, # Калий "Mg": 120, # Магний "Ca": 150, # Кальций "S": 100, # Сера "N (NO3-)": 0, # Рассчитывается автоматически "N (NH4+)": 0 # Рассчитывается автоматически } # Обновление базового профиля данными из запроса for element in BASE_PROFILE: if element in data['profileSettings']: BASE_PROFILE[element] = float(data['profileSettings'][element]) # Расчет соотношения азотов total_n = float(data['profileSettings'].get('TOTAL_NITROG', 125.0)) no3_ratio = float(data['profileSettings'].get('NO3_RAT', 8.35)) # Берем значение из запроса nh4_ratio = 1.0 # Фиксированное значение для NH4 total_parts = no3_ratio + nh4_ratio BASE_PROFILE['N (NO3-)'] = total_n * (no3_ratio / total_parts) BASE_PROFILE['N (NH4+)'] = total_n * (nh4_ratio / total_parts) # Настройка целевого профиля calculator.target_profile = BASE_PROFILE # Установка констант удобрений из запроса calculator.fertilizers = data['fertilizerConstants'] # Выполнение расчета results = calculator.calculate() # Подготовка ответа response = { "actual_profile": calculator.actual_profile, "fertilizers": results, "nitrogen_ratios": { "NH4_RATIO": nh4_ratio, "NO3_RATIO": no3_ratio, "TOTAL_NITROGEN": total_n }, "total_ec": calculator.calculate_ec(), "total_ppm": round(sum(calculator.actual_profile.values()), 3) } return jsonify(response) except Exception as e: return jsonify({'error': str(e)}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))