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 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') # Константы TOTAL_NITROGEN = 125.000 NO3_RATIO = 8.25 NH4_RATIO = 1.00 VOLUME_LITERS = 100 # Коэффициенты электропроводности 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 } # Целевые значения BASE_PROFILE = { 'P': 31.000, 'K': 210.000, 'Mg': 24.000, 'Ca': 84.000, 'S': 56.439, '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} } 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+)'] } def _label(self, element): """Форматирование названий элементов для вывода""" labels = { 'N (NO3-)': 'NO3', 'N (NH4+)': 'NH4' } return labels.get(element, element) def calculate(self): try: self._apply("Сульфат магния", "Mg", self.target_profile['Mg']) self._apply("Кальциевая селитра", "Ca", self.target_profile['Ca']) self._apply("Монофосфат калия", "P", self.target_profile['P']) self._apply("Аммоний азотнокислый", "N (NH4+)", self.target_profile['N (NH4+)']) current_no3 = self.actual_profile['N (NO3-)'] no3_needed = self.target_profile['N (NO3-)'] - current_no3 if no3_needed > 0.1: self._apply("Калий азотнокислый", "N (NO3-)", no3_needed) self._apply_k_sulfate() k_deficit = self.target_profile['K'] - self.actual_profile['K'] if k_deficit > 0.1: self._apply("Калий азотнокислый", "K", k_deficit) return self.results except Exception as e: print(f"Ошибка при расчёте: {str(e)}") raise def _apply(self, fert_name, main_element, required_ppm): if required_ppm <= 0: return try: content = self.fertilizers[fert_name][main_element] grams = (required_ppm * self.volume) / (content * 1000) if fert_name not in self.results: result = { 'граммы': 0.0, 'миллиграммы': 0, 'вклад в EC': 0.0 } for element in self.fertilizers[fert_name]: result[f'внесет {self._label(element)}'] = 0.0 self.results[fert_name] = result self.results[fert_name]['граммы'] += grams self.results[fert_name]['миллиграммы'] += int(grams * 1000) fert_ec = 0.0 for element, percent in self.fertilizers[fert_name].items(): added_ppm = (grams * percent * 1000) / self.volume self.results[fert_name][f'внесет {self._label(element)}'] += added_ppm self.actual_profile[element] += added_ppm fert_ec += added_ppm * EC_COEFFICIENTS.get(element, 0.0015) self.results[fert_name]['вклад в EC'] += fert_ec self.total_ec += fert_ec except KeyError as e: print(f"Ошибка: отсутствует элемент {str(e)} в удобрении {fert_name}") raise def _apply_k_sulfate(self): fert = "Калий сернокислый" k_def = self.target_profile['K'] - self.actual_profile['K'] s_def = self.target_profile['S'] - self.actual_profile['S'] if k_def <= 0 and s_def <= 0: return try: if s_def > 0.1: s_content = self.fertilizers[fert]["S"] grams_s = (s_def * self.volume) / (s_content * 1000) k_content = self.fertilizers[fert]["K"] k_from_s = (grams_s * k_content * 1000) / self.volume if k_from_s > k_def and k_def > 0.1: grams = (k_def * self.volume) / (k_content * 1000) else: grams = grams_s self._apply(fert, "S", s_def) except Exception as e: print(f"Ошибка при расчёте сульфата калия: {str(e)}") raise def calculate_ec(self): return round(self.total_ec, 2) 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 # Данные на страницу def get_web_results(self): """Возвращает данные в формате для web-интерфейса""" return { 'fertilizers': self._format_fertilizers(), 'profile': self._format_profile(), 'ec': self.calculate_ec(), 'deficits': self.calculate_deficits() } def _format_fertilizers(self): formatted = [] for name, data in self.results.items(): fert = { 'name': name, 'grams': round(data['граммы'], 3), 'adds': {} } for k, v in data.items(): if k.startswith('внесет'): fert['adds'][k.replace('внесет ', '')] = round(v, 1) formatted.append(fert) return formatted def _format_profile(self): return [{'element': k, 'ppm': round(v, 1)} for k, v in self.actual_profile.items()] def calculate_deficits(self): """Вычисляет остаточные дефициты элементов""" return { k: max(self.target_profile[k] - self.actual_profile[k], 0) for k in self.target_profile } def get_web_results(self): """Возвращает данные в формате для web-интерфейса""" return { 'fertilizers': self._format_fertilizers(), 'profile': self._format_profile(), 'ec': self.calculate_ec(), 'deficits': self.calculate_deficits(), 'volume': self.volume } def _format_fertilizers(self): formatted = [] for name, data in self.results.items(): fert = { 'name': name, 'grams': round(data['граммы'], 3), 'adds': {} } for k, v in data.items(): if k.startswith('внесет'): fert['adds'][k.replace('внесет ', '')] = round(v, 1) formatted.append(fert) return formatted def _format_profile(self): return [{'element': k.replace(' (NO3-)', '-NO3').replace(' (NH4+)', '-NH4'), 'ppm': round(v, 1)} for k, v in self.actual_profile.items()] # Функции для преобразования данных от клиента FERTILIZER_NAME_MAP = { "CaN2O6": "Кальциевая селитра", "KNO3": "Калий азотнокислый", "K2SO4": "Калий сернокислый", "NH4NO3": "Аммоний азотнокислый", "MgSO4": "Сульфат магния", "KH2PO4": "Монофосфат калия" } NUTRIENT_MAP = { "NO3": "N (NO3-)", "NH4": "N (NH4+)" } def convert_client_data(client_data): """Преобразует данные от клиента в серверный формат""" server_format = {} for chem_form, nutrients in client_data.items(): fert_name = FERTILIZER_NAME_MAP.get(chem_form, chem_form) server_format[fert_name] = {} for element, value in nutrients.items(): server_element = NUTRIENT_MAP.get(element, element) server_format[fert_name][server_element] = value return server_format 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: # Получаем и логируем сырые данные raw_data = request.json print("\n=== ПРИНЯТЫЕ ДАННЫЕ ===") print("Тип данных:", type(raw_data)) print("Содержимое:", raw_data) # Проверка структуры данных if not isinstance(raw_data, dict): raise ValueError("Данные должны быть в формате JSON-объекта") if 'fertilizerConstants' not in raw_data or 'profileSettings' not in raw_data: raise ValueError("Отсутствуют обязательные поля fertilizerConstants или profileSettings") # Логируем удобрения print("\n=== УДОБРЕНИЯ ===") for fert_name, fert_data in raw_data['fertilizerConstants'].items(): print(f"{fert_name}: {fert_data}") # Логируем настройки профиля print("\n=== НАСТРОЙКИ ПРОФИЛЯ ===") for param, value in raw_data['profileSettings'].items(): print(f"{param}: {value} (тип: {type(value)})") # Преобразуем данные удобрений fertilizers = {} for fert_name, fert_data in raw_data['fertilizerConstants'].items(): fertilizers[fert_name] = { "N (NO3-)": float(fert_data.get("N (NO3-)", 0)), "N (NH4+)": float(fert_data.get("N (NH4+)", 0)), "P": float(fert_data.get("P", 0)), "K": float(fert_data.get("K", 0)), "Ca": float(fert_data.get("Ca", 0)), "Mg": float(fert_data.get("Mg", 0)), "S": float(fert_data.get("S", 0)) } # Преобразуем настройки профиля profile_settings = raw_data['profileSettings'] profile = { 'P': float(profile_settings.get('P', 0)), 'K': float(profile_settings.get('K', 0)), 'Mg': float(profile_settings.get('Mg', 0)), 'Ca': float(profile_settings.get('Ca', 0)), 'S': float(profile_settings.get('S', 0)), 'N (NO3-)': float(profile_settings.get('N (NO3-)', 0)), 'N (NH4+)': float(profile_settings.get('N (NH4+)', 0)) } liters = int(profile_settings.get('liters', 100)) # Логируем преобразованные данные print("\n=== ПРЕОБРАЗОВАННЫЕ ДАННЫЕ ===") print("Удобрения:", fertilizers) print("Профиль:", profile) print("Литры:", liters) # Создаем калькулятор calculator = NutrientCalculator(volume_liters=liters) calculator.fertilizers = fertilizers calculator.target_profile.update(profile) # Выполняем расчет calculator.calculate() # Формируем ответ response = { "fertilizers": [], "profile": [], "ec": calculator.calculate_ec(), "deficits": calculator.calculate_deficits() } # Заполняем удобрения for fert_name, result in calculator.results.items(): response["fertilizers"].append({ "name": fert_name, "grams": round(result['граммы'], 3), "adds": {k.replace('внесет ', ''): v for k, v in result.items() if k.startswith('внесет')} }) # Заполняем профиль for element, value in calculator.actual_profile.items(): response["profile"].append({ "element": element.replace(' (NO3-)', '-NO3').replace(' (NH4+)', '-NH4'), "ppm": round(value, 1) }) # Логируем результат print("\n=== РЕЗУЛЬТАТ РАСЧЕТА ===") print(response) print("\n") return jsonify(response) except ValueError as e: error_msg = f"Ошибка данных: {str(e)}" print(f"\n!!! ОШИБКА: {error_msg}\n") return jsonify({"error": error_msg}), 400 except Exception as e: error_msg = f"Ошибка сервера: {str(e)}" print(f"\n!!! КРИТИЧЕСКАЯ ОШИБКА: {error_msg}\n") return jsonify({"error": error_msg}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))