DmitrMakeev's picture
Update app.py
53cd319 verified
raw
history blame
31 kB
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/<filename>', 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')
# Общий азот и соотношение NO3:NH4
TOTAL_NITROGEN = 220 # ppm
N_RATIO = (10, 5) # Пример: 10:1 (NO3:NH4)
# Профиль питательного раствора для томатов (ppm)
TOMATO_PROFILE = {
'P': 50,
'K': 350,
'Mg': 50,
'Ca': 200,
'S': 100
}
# База данных удобрений
fertilizers_db = {
"Кальциевая селитра": {
"N (NO3-)": 0.118,
"Ca": 0.169,
},
"Калий азотнокислый": {
"N (NO3-)": 0.138,
"K": 0.387,
},
"Калий сернокислый": {
"K": 0.448,
"S": 0.184,
},
"Аммоний азотнокислый": {
"N (NO3-)": 0.175,
"N (NH4+)": 0.175,
},
"Сульфат магния": {
"Mg": 0.098,
"S": 0.13,
},
"Монофосфат калия": {
"P": 0.227,
"K": 0.287,
}
}
class NutrientCalculator:
def __init__(self, volume_liters=1.0):
self.volume = volume_liters
self.results = {}
self.final_profile = {}
self.total_ppm = 0
def calculate(self, base_profile, total_n, n_ratio):
# Расчёт NO3 и NH4 на основе общего азота и соотношения
total_parts = n_ratio[0] + n_ratio[1]
no3 = total_n * (n_ratio[0] / total_parts)
nh4 = total_n * (n_ratio[1] / total_parts)
# Формирование полного профиля
self.final_profile = base_profile.copy()
self.final_profile['N (NO3-)'] = no3
self.final_profile['N (NH4+)'] = nh4
self.total_ppm = total_n + sum(base_profile.values())
# Последовательное внесение удобрений
self._apply_magnesium_sulfate()
self._apply_calcium_nitrate()
self._apply_mkp()
self._apply_potassium_fertilizers()
self._apply_ammonium_nitrate()
return self.results
def _apply_fertilizer(self, fert_name, grams, additions):
self.results[fert_name] = {
'граммы': round(grams, 3),
'миллиграммы': int(grams * 1000),
}
self.results[fert_name].update(additions)
def _apply_magnesium_sulfate(self):
mg_need = self.final_profile['Mg']
mg_content = fertilizers_db["Сульфат магния"]["Mg"]
grams = (mg_need * self.volume) / (mg_content * 1000)
added_s = grams * fertilizers_db["Сульфат магния"]["S"] * 1000 / self.volume
self.final_profile['S'] -= added_s
self._apply_fertilizer("Сульфат магния", grams, {'внесет S': round(added_s, 1)})
self.final_profile['Mg'] = 0
def _apply_calcium_nitrate(self):
ca_need = self.final_profile['Ca']
ca_content = fertilizers_db["Кальциевая селитра"]["Ca"]
grams = (ca_need * self.volume) / (ca_content * 1000)
added_n = grams * fertilizers_db["Кальциевая селитра"]["N (NO3-)"] * 1000 / self.volume
self.final_profile['N (NO3-)'] -= added_n
self._apply_fertilizer("Кальциевая селитра", grams, {'внесет NO3': round(added_n, 1)})
self.final_profile['Ca'] = 0
def _apply_mkp(self):
p_need = self.final_profile['P']
p_content = fertilizers_db["Монофосфат калия"]["P"]
grams = (p_need * self.volume) / (p_content * 1000)
added_k = grams * fertilizers_db["Монофосфат калия"]["K"] * 1000 / self.volume
self.final_profile['K'] -= added_k
self._apply_fertilizer("Монофосфат калия", grams, {'внесет K': round(added_k, 1)})
self.final_profile['P'] = 0
def _apply_potassium_fertilizers(self):
k_need = self.final_profile['K']
if k_need <= 0:
return
s_deficit = max(0, self.final_profile['S'])
if s_deficit > 0:
s_content = fertilizers_db["Калий сернокислый"]["S"]
k2so4_grams = (s_deficit * self.volume) / (s_content * 1000)
added_k = k2so4_grams * fertilizers_db["Калий сернокислый"]["K"] * 1000 / self.volume
if added_k > k_need:
k2so4_grams = (k_need * self.volume) / (fertilizers_db["Калий сернокислый"]["K"] * 1000)
added_k = k_need
added_s = k2so4_grams * fertilizers_db["Калий сернокислый"]["S"] * 1000 / self.volume
else:
added_s = s_deficit
self._apply_fertilizer("Калий сернокислый", k2so4_grams, {
'внесет K': round(added_k, 1),
'внесет S': round(added_s, 1)
})
self.final_profile['K'] -= added_k
self.final_profile['S'] -= added_s
k_need = self.final_profile['K']
if k_need > 0:
kno3_grams = (k_need * self.volume) / (fertilizers_db["Калий азотнокислый"]["K"] * 1000)
added_n = kno3_grams * fertilizers_db["Калий азотнокислый"]["N (NO3-)"] * 1000 / self.volume
self._apply_fertilizer("Калий азотнокислый", kno3_grams, {'внесет NO3': round(added_n, 1)})
self.final_profile['K'] = 0
self.final_profile['N (NO3-)'] -= added_n
def _apply_ammonium_nitrate(self):
nh4_need = self.final_profile['N (NH4+)']
if nh4_need <= 0:
return
nh4_content = fertilizers_db["Аммоний азотнокислый"]["N (NH4+)"]
grams = (nh4_need * self.volume) / (nh4_content * 1000)
added_n = grams * fertilizers_db["Аммоний азотнокислый"]["N (NO3-)"] * 1000 / self.volume
self.final_profile['N (NO3-)'] -= added_n
self._apply_fertilizer("Аммоний азотнокислый", grams, {'внесет NO3': round(added_n, 1)})
self.final_profile['N (NH4+)'] = 0
def calculate_ec(self):
return round(self.total_ppm / 700, 2)
def print_report(self):
print("\n" + "="*50)
print("ЗАДАННЫЙ ПРОФИЛЬ ПИТАТЕЛЬНОГО РАСТВОРА (ppm):")
print("="*50)
profile_table = []
for element, value in self.final_profile.items():
profile_table.append([element, round(value, 1)])
print(tabulate(profile_table, headers=["Элемент", "Концентрация (ppm)"]))
ec = self.calculate_ec()
print("\n" + "="*50)
print(f"РАСЧЕТ ДЛЯ {self.volume} ЛИТРОВ РАСТВОРА")
print("="*50)
print(f"\nОБЩАЯ КОНЦЕНТРАЦИЯ: {self.total_ppm} ppm")
print(f"ЭЛЕКТРОПРОВОДИМОСТЬ (EC): {ec} mS/cm (при 25°C)")
print("\nРЕКОМЕНДУЕМЫЕ УДОБРЕНИЯ:")
table = []
for fert, data in self.results.items():
additions = [f"+{k}: {v} ppm" for k, v in data.items() if k.startswith('внесет')]
table.append([
fert,
f"{data['граммы']:.3f} г",
f"{data['миллиграммы']} мг",
"\n".join(additions)
])
print(tabulate(table, headers=["Удобрение", "Граммы", "Миллиграммы", "Добавит"]))
print("\nОСТАТОЧНЫЙ ДЕФИЦИТ:")
deficit = {k: v for k, v in self.final_profile.items() if v > 0.1}
if deficit:
for el, val in deficit.items():
print(f" {el}: {round(val, 1)} ppm")
else:
print(" Все элементы полностью покрыты")
# Пример использования
if __name__ == "__main__":
calc = NutrientCalculator(volume_liters=10)
results = calc.calculate(TOMATO_PROFILE.copy(), total_n=TOTAL_NITROGEN, n_ratio=N_RATIO)
calc.print_report()
@app.route("/calll")
def index():
target_profile = {
"P": 0,
"K": 0,
"Mg": 0,
"Ca": 0,
"S": 0,
"N (NO3-)": -32.7,
"N (NH4+)": 0,
}
fertilizers = [
{"name": "Сульфат магния", "g": 5.102, "mg": 5102, "adds": ["S: 66.3 ppm"]},
{"name": "Кальциевая селитра", "g": 11.834, "mg": 11834, "adds": ["NO3: 139.6 ppm"]},
{"name": "Монофосфат калия", "g": 2.203, "mg": 2202, "adds": ["K: 63.2 ppm"]},
{"name": "Калий сернокислый", "g": 1.830, "mg": 1830, "adds": ["K: 82.0 ppm", "S: 33.7 ppm"]},
{"name": "Калий азотнокислый", "g": 5.292, "mg": 5291, "adds": ["NO3: 73.0 ppm"]},
{"name": "Аммоний азотнокислый", "g": 1.143, "mg": 1142, "adds": ["NO3: 20.0 ppm"]},
]
html = """
<pre>
==================================================
ЗАДАННЫЙ ПРОФИЛЬ ПИТАТЕЛЬНОГО РАСТВОРА (ppm):
==================================================
Элемент Концентрация (ppm)
--------- --------------------
{% for element, value in target_profile.items() %}
{{ "{:<12}".format(element) }}{{ "{:>10}".format(value) }}
{% endfor %}
==================================================
РАСЧЕТ ДЛЯ 10 ЛИТРОВ РАСТВОРА
==================================================
ОБЩАЯ КОНЦЕНТРАЦИЯ: 970 ppm
ЭЛЕКТРОПРОВОДИМОСТЬ (EC): 1.39 mS/cm (при 25°C)
РЕКОМЕНДУЕМЫЕ УДОБРЕНИЯ:
Удобрение Граммы Миллиграммы Добавит
-------------------- -------- ------------- ----------------------
{% for fert in fertilizers %}
{{ "{:<20}".format(fert.name) }} {{ "{:>6.3f}".format(fert.g) }} г {{ "{:>6}".format(int(fert.mg)) }} мг +внесет {{ ", ".join(fert.adds) }}
{% endfor %}
ОСТАТОЧНЫЙ ДЕФИЦИТ:
Все элементы полностью покрыты
</pre>
"""
return render_template_string(html, target_profile=target_profile, fertilizers=fertilizers)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.environ.get('PORT', 7860)))