Spaces:
Running
Running
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 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 | |
# Маршрут сохранения в базу | |
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'}) | |
# Проверка входа на страницы | |
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 | |
# Тестовый запрос с установки | |
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) | |
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" | |
# Тестовый запрос с установки | |
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) | |
# Маршрут для вывода всех данных из таблицы | |
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 | |
# Удаление базы | |
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 | |
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)}") | |
def index(): | |
return flask.render_template('index.html') | |
def online(): | |
return render_template('online.html') | |
def table(): | |
return render_template('table.html') | |
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 # Добавляем время | |
) | |
def settings(): | |
return render_template('settings.html') | |
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 | |
) | |
def set_pH_value(): | |
ph_value = request.args.get('value') | |
globs.ph_set = ph_value | |
globs.eep_set = 1 | |
return "pH value set successfully" | |
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" | |
def set_EC_value(): | |
ec_value = request.args.get('value') | |
globs.ec_set = ec_value | |
globs.eep_set = 3 | |
return "EC value set successfully" | |
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" | |
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" | |
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" | |
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" | |
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" | |
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" | |
def but_start(): | |
globs.eep_set = 10 | |
return jsonify(value_set="start") | |
def but_stop(): | |
globs.eep_set = 11 | |
return jsonify(value_set="stop") | |
def but_res(): | |
globs.eep_set = 12 | |
return jsonify(value_set="res") | |
def but_sliv(): | |
globs.eep_set = 13 | |
return jsonify(value_set="sliv") | |
def handle_api(): | |
response = api() | |
return response | |
def handle_save_db(): | |
response = save_db() | |
return response | |
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 | |
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 | |
def uploaded_file(filename): | |
return send_from_directory(UPLOAD_FOLDER, filename) | |
# 🧠 2. Маршрут: сохраняет файл только в память (BytesIO) | |
# Маршрут для загрузки файла в память | |
# Загрузка изображения в память (в виде байтов) | |
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 | |
# Получение последнего изображения | |
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"] | |
) | |
def view_image(): | |
return render_template('show_image.html') | |
def nutri_call(): | |
return render_template('nutri_call.html') | |
from tabulate import tabulate | |
# Константы | |
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} | |
} | |
from tabulate import tabulate | |
# Константы | |
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 | |
# Веса компенсации для элементов (кроме азотов) | |
self.element_compensation_weights = { | |
"POTASSIUM_SULFATE": {"weight": 0.23, "fert": "Калий сернокислый", "main_element": "K"}, | |
"MAGNESIUM_SULFATE": {"weight": -0.1, "fert": "Сульфат магния", "main_element": "Mg"}, | |
"MONOPOTASSIUM_PHOSPHATE": {"weight": 0.14, "fert": "Монофосфат калия", "main_element": "P"} | |
} | |
# Расчёт азота | |
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) | |
def _label(self, element): | |
"""Форматирование названий элементов для вывода""" | |
labels = { | |
'N (NO3-)': 'NO3', | |
'N (NH4+)': 'NH4' | |
} | |
return labels.get(element, element) | |
def calculate(self): | |
try: | |
# Вносим магний через компенсацию | |
self._compensate_element("Mg") | |
# Вносим кальций напрямую | |
self._apply("Кальциевая селитра", "Ca", self.target_profile['Ca']) | |
# Вносим фосфор через компенсацию | |
self._compensate_element("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._compensate_element("S") | |
# Компенсируем калий через компенсацию | |
self._compensate_element("K") | |
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 _compensate_element(self, element): | |
needed = self.target_profile[element] - self.actual_profile.get(element, 0) | |
if needed <= 0: | |
return | |
candidates = [] | |
for weight_key, weight_data in self.element_compensation_weights.items(): | |
fert_name = weight_data["fert"] | |
if element in self.fertilizers.get(fert_name, {}): | |
candidates.append({ | |
'name': fert_name, | |
'weight': weight_data["weight"], | |
'content': self.fertilizers[fert_name][element] | |
}) | |
if not candidates: | |
raise ValueError(f"Нет удобрений для элемента {element}") | |
total_weight = sum(c['weight'] for c in candidates) | |
for candidate in candidates: | |
share = candidate['weight'] / total_weight | |
ppm_to_apply = needed * share | |
self._apply(candidate['name'], element, ppm_to_apply) | |
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" + "="*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 handle_calculation(): | |
try: | |
data = request.get_json() | |
# Проверка обязательных полей | |
if not data or 'profileSettings' not in data: | |
return jsonify({'error': 'Неверный формат данных'}), 400 | |
# Инициализация калькулятора | |
calculator = NutrientCalculator(volume_liters=float(data['profileSettings'].get('liters', 100))) | |
# Настройка целевого профиля | |
calculator.target_profile = { | |
'P': float(data['profileSettings'].get('P', 31.0)), | |
'K': float(data['profileSettings'].get('K', 210.0)), | |
'Mg': float(data['profileSettings'].get('Mg', 24.0)), | |
'Ca': float(data['profileSettings'].get('Ca', 84.0)), | |
'S': float(data['profileSettings'].get('S', 56.439)), | |
'N (NO3-)': 0.0, | |
'N (NH4+)': 0.0 | |
} | |
# Извлечение значений для азотов | |
total_n = float(data['profileSettings'].get('TOTAL_NITROG', 125.0)) | |
no3_ratio = float(data['profileSettings'].get('NO3_RAT', 8.25)) # Берем значение из запроса | |
nh4_ratio = 1.0 # Фиксированное значение для NH4 | |
# Расчет соотношения азотов | |
total_parts = no3_ratio + nh4_ratio | |
calculator.target_profile['N (NO3-)'] = total_n * (no3_ratio / total_parts) | |
calculator.target_profile['N (NH4+)'] = total_n * (nh4_ratio / total_parts) | |
# Выполнение расчета | |
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))) | |