Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- pages/2_⚙️_Hardware.py +712 -0
- pages/3_📝_SingleLine.py +199 -0
pages/2_⚙️_Hardware.py
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import math
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
from io import BytesIO
|
| 7 |
+
|
| 8 |
+
st.set_page_config(page_title="Расчет потребления", layout="wide")
|
| 9 |
+
|
| 10 |
+
DB_FILE = "equipment_db.json"
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# ==================== ФУНКЦИИ БАЗЫ ДАННЫХ ====================
|
| 14 |
+
def load_db():
|
| 15 |
+
if os.path.exists(DB_FILE):
|
| 16 |
+
try:
|
| 17 |
+
with open(DB_FILE, "r", encoding="utf-8") as f:
|
| 18 |
+
return json.load(f)
|
| 19 |
+
except:
|
| 20 |
+
return []
|
| 21 |
+
return []
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def save_db(data):
|
| 25 |
+
with open(DB_FILE, "w", encoding="utf-8") as f:
|
| 26 |
+
json.dump(data, f, ensure_ascii=False, indent=4)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# ==================== БОКОВАЯ ПАНЕЛЬ ====================
|
| 30 |
+
with st.sidebar:
|
| 31 |
+
st.header("📂 База данных")
|
| 32 |
+
uploaded_file = st.file_uploader("Импорт Excel", type=["xlsx", "xls"])
|
| 33 |
+
if uploaded_file:
|
| 34 |
+
try:
|
| 35 |
+
df_import = pd.read_excel(uploaded_file, header=None)
|
| 36 |
+
df_import.columns = [f"Колонка {i}" for i in range(len(df_import.columns))]
|
| 37 |
+
col_target = st.selectbox("В какой колонке данные?", options=df_import.columns)
|
| 38 |
+
if st.button("📥 Разбить текст и загрузить", type="primary"):
|
| 39 |
+
current_db = load_db()
|
| 40 |
+
existing_map = {item["article"]: item["power"] for item in current_db}
|
| 41 |
+
new_items = []
|
| 42 |
+
count_ok = 0
|
| 43 |
+
for index, row in df_import.iterrows():
|
| 44 |
+
raw_text = str(row[col_target]).strip()
|
| 45 |
+
if not raw_text or raw_text.lower() == "nan": continue
|
| 46 |
+
parts = raw_text.rsplit(' ', 1)
|
| 47 |
+
if len(parts) == 2:
|
| 48 |
+
name = parts[0].strip()
|
| 49 |
+
article = parts[1].strip()
|
| 50 |
+
else:
|
| 51 |
+
name = raw_text
|
| 52 |
+
article = "-"
|
| 53 |
+
power = existing_map.get(article, 0.0)
|
| 54 |
+
new_items.append({"name": name, "article": article, "power": power})
|
| 55 |
+
count_ok += 1
|
| 56 |
+
final_db_map = {item["article"]: item for item in current_db}
|
| 57 |
+
for item in new_items: final_db_map[item["article"]] = item
|
| 58 |
+
save_db(list(final_db_map.values()))
|
| 59 |
+
st.success(f"Готово! Обработано строк: {count_ok}.")
|
| 60 |
+
except Exception as e:
|
| 61 |
+
st.error(f"Ошибка: {e}")
|
| 62 |
+
|
| 63 |
+
# ==================== ОСНОВНОЙ КОД ====================
|
| 64 |
+
raw_cabinet = st.session_state.get("cabinet_final", "")
|
| 65 |
+
cabinet_label = raw_cabinet if raw_cabinet else "Общий_шкаф"
|
| 66 |
+
st.title(f"⚡ Расчет потребления: {cabinet_label}")
|
| 67 |
+
|
| 68 |
+
# --- ИНИЦИАЛИЗАЦИЯ И РАСЧЕТ МОДУЛЕЙ ---
|
| 69 |
+
if "manual_signals_counts" in st.session_state:
|
| 70 |
+
manual = st.session_state['manual_signals_counts']
|
| 71 |
+
init_rs, init_eth = manual.get("RS-485", 0), manual.get("ETH", 0)
|
| 72 |
+
init_ti, init_ts = manual.get("AI (TI)", 0), manual.get("DI (TS)", 0)
|
| 73 |
+
init_tu, init_tr = manual.get("DO (TU)", 0), manual.get("TR (AO)", 0)
|
| 74 |
+
init_reserve = st.session_state.get("reserve_val", 20)
|
| 75 |
+
st.toast("✅ Загружены данные с учетом ручной коррекции!", icon="✏️")
|
| 76 |
+
elif "res_list" in st.session_state and st.session_state.res_list:
|
| 77 |
+
init_rs = sum(r.counts.RS485 for r in st.session_state.res_list)
|
| 78 |
+
init_eth = sum(r.counts.ETH for r in st.session_state.res_list)
|
| 79 |
+
init_ti = sum(r.counts.TI for r in st.session_state.res_list)
|
| 80 |
+
init_ts = sum(r.counts.TS for r in st.session_state.res_list)
|
| 81 |
+
init_tu = sum(r.counts.TU for r in st.session_state.res_list)
|
| 82 |
+
init_tr = sum(r.counts.AO for r in st.session_state.res_list)
|
| 83 |
+
init_reserve = st.session_state.get("reserve_val", 20)
|
| 84 |
+
else:
|
| 85 |
+
init_rs, init_eth, init_ti, init_ts, init_tu, init_tr = 12, 2, 42, 102, 51, 0
|
| 86 |
+
init_reserve = 20
|
| 87 |
+
|
| 88 |
+
with st.expander("🛠 Настройки модулей (Канальность)", expanded=False):
|
| 89 |
+
c1, c2 = st.columns(2)
|
| 90 |
+
ti_ch = c1.number_input("Каналов ТИ", value=32)
|
| 91 |
+
ts_ch = c2.number_input("Каналов ТС", value=32)
|
| 92 |
+
tu_ch = c1.number_input("Каналов ТУ", value=32)
|
| 93 |
+
tr_ch = c2.number_input("Каналов TR", value=8)
|
| 94 |
+
rs_ch = c1.number_input("Каналов RS", value=8)
|
| 95 |
+
|
| 96 |
+
st.subheader("1. Сигналы")
|
| 97 |
+
col_l, col_r = st.columns([3, 1])
|
| 98 |
+
with col_r:
|
| 99 |
+
st.write("###")
|
| 100 |
+
reserve_pct = st.number_input("Резерв %", value=init_reserve, step=5)
|
| 101 |
+
with col_l:
|
| 102 |
+
signals_data = [
|
| 103 |
+
{"Тип": "RS-485", "Факт": init_rs}, {"Тип": "ETH", "Факт": init_eth},
|
| 104 |
+
{"Тип": "TI (AI)", "Факт": init_ti}, {"Тип": "TS (DI)", "Факт": init_ts},
|
| 105 |
+
{"Тип": "TU (DO)", "Факт": init_tu}, {"Тип": "TR (AO)", "Факт": init_tr},
|
| 106 |
+
]
|
| 107 |
+
for item in signals_data:
|
| 108 |
+
item["Итого (с резервом)"] = math.ceil(item["Факт"] * (1 + reserve_pct / 100))
|
| 109 |
+
edited_signals = st.data_editor(pd.DataFrame(signals_data), hide_index=True, key="sig_edit")
|
| 110 |
+
|
| 111 |
+
final_signals = {row["Тип"]: row["Итого (с резервом)"] for _, row in edited_signals.iterrows()}
|
| 112 |
+
q_ti = math.ceil(final_signals.get("TI (AI)", 0) / ti_ch) if ti_ch else 0
|
| 113 |
+
q_ts = math.ceil(final_signals.get("TS (DI)", 0) / ts_ch) if ts_ch else 0
|
| 114 |
+
q_tu = math.ceil(final_signals.get("TU (DO)", 0) / tu_ch) if tu_ch else 0
|
| 115 |
+
q_tr = math.ceil(final_signals.get("TR (AO)", 0) / tr_ch) if tr_ch else 0
|
| 116 |
+
q_rs = math.ceil(final_signals.get("RS-485", 0) / rs_ch) if rs_ch else 0
|
| 117 |
+
total_io = q_ti + q_ts + q_tu + q_tr + q_rs
|
| 118 |
+
designation_io = f"A12...A{11 + total_io}" if total_io > 0 else ""
|
| 119 |
+
|
| 120 |
+
# ==================== ТАБЛИЦА ОБОРУДОВАНИЯ ====================
|
| 121 |
+
st.subheader("2. Спецификация оборудования")
|
| 122 |
+
|
| 123 |
+
current_project_id = st.session_state.get("last_filename", "")
|
| 124 |
+
saved_project_id = st.session_state.get("equip_rows_source_id", "")
|
| 125 |
+
|
| 126 |
+
if current_project_id != saved_project_id:
|
| 127 |
+
if "equip_rows" in st.session_state:
|
| 128 |
+
del st.session_state["equip_rows"]
|
| 129 |
+
st.session_state["equip_rows_source_id"] = current_project_id
|
| 130 |
+
st.toast("🔄 Таблица оборудования сброшена для нового проекта!", icon="🆕")
|
| 131 |
+
|
| 132 |
+
# --- ИНИЦИАЛИЗАЦИЯ ---
|
| 133 |
+
if "equip_rows" not in st.session_state or len(st.session_state.equip_rows) == 0:
|
| 134 |
+
st.session_state.equip_rows = [
|
| 135 |
+
{"Потребитель": "ПЛК (Master)", "Артикул": "ПР (Р02)", "P_шт": 3.0, "Q_внутр": 1, "Q_внешн": 0, "Q_спец": 0,
|
| 136 |
+
"Q_220": 0, "Коэф.": 1.0, "Это БП?": False, "ups": False, "input2": False},
|
| 137 |
+
{"Потребитель": "Блок питания осн.", "Артикул": "БП (Р01)", "P_шт": 2.0, "Q_внутр": 1, "Q_внешн": 0,
|
| 138 |
+
"Q_спец": 0, "Q_220": 0, "Коэф.": 1.0, "Это БП?": True, "ups": False, "input2": False},
|
| 139 |
+
{"Потребитель": "Модули ввода-вывода", "Артикул": designation_io, "P_шт": 3.0, "Q_внутр": total_io,
|
| 140 |
+
"Q_внешн": 0, "Q_спец": 0, "Q_220": 0, "Коэф.": 1.0, "Это БП?": False, "ups": False, "input2": False},
|
| 141 |
+
{"Потребитель": "Вентилятор", "Артикул": "NLV-2600", "P_шт": 50.0, "Q_внутр": 0, "Q_внешн": 0, "Q_спец": 0,
|
| 142 |
+
"Q_220": 1, "Коэф.": 1.0, "Это БП?": False, "ups": False, "input2": False},
|
| 143 |
+
{"Потребитель": "Светильник", "Артикул": "ДБО 3001", "P_шт": 4.0, "Q_внутр": 0, "Q_внешн": 0, "Q_спец": 0,
|
| 144 |
+
"Q_220": 1, "Коэф.": 1.0, "Это БП?": False, "ups": False, "input2": False},
|
| 145 |
+
{"Потребитель": "Розетка пользователя", "Артикул": "18012DEK", "P_шт": 0.0, "Q_внутр": 0, "Q_внешн": 0,
|
| 146 |
+
"Q_спец": 0, "Q_220": 1, "Коэф.": 1.0, "Это БП?": False, "ups": False, "input2": False},
|
| 147 |
+
]
|
| 148 |
+
|
| 149 |
+
# === НАСТРОЙКА ТАБЛИЦЫ ===
|
| 150 |
+
has_input2_flag = st.session_state.get("has_input2", False)
|
| 151 |
+
|
| 152 |
+
for r in st.session_state.equip_rows:
|
| 153 |
+
if "input2" not in r: r["input2"] = False
|
| 154 |
+
if "Q_спец" not in r: r["Q_спец"] = 0
|
| 155 |
+
|
| 156 |
+
df_to_edit = pd.DataFrame(st.session_state.equip_rows)
|
| 157 |
+
|
| 158 |
+
try:
|
| 159 |
+
df_to_edit = df_to_edit.astype({
|
| 160 |
+
"P_шт": "float", "Q_внутр": "int", "Q_внешн": "int", "Q_спец": "int", "Q_220": "int",
|
| 161 |
+
"Коэф.": "float", "Это БП?": "bool", "ups": "bool", "input2": "bool"
|
| 162 |
+
})
|
| 163 |
+
except Exception:
|
| 164 |
+
pass
|
| 165 |
+
|
| 166 |
+
my_column_config = {
|
| 167 |
+
"Потребитель": st.column_config.TextColumn(required=True, width="medium"),
|
| 168 |
+
"P_шт": st.column_config.NumberColumn("P_1шт (Вт)", format="%.2f", width="small"),
|
| 169 |
+
"Q_внутр": st.column_config.NumberColumn("Q (Внутр)", min_value=0, step=1, help="Нагрузка 24В (Логика)"),
|
| 170 |
+
"Q_внешн": st.column_config.NumberColumn("Q (Внешн)", min_value=0, step=1, help="Нагрузка 24В (Датчики)"),
|
| 171 |
+
"Q_спец": st.column_config.NumberColumn("Q (Доп.)", min_value=0, step=1, help="Нагрузка 12В"),
|
| 172 |
+
"Q_220": st.column_config.NumberColumn("Q (220В)", min_value=0, step=1),
|
| 173 |
+
"Коэф.": st.column_config.NumberColumn("К спр.", step=0.05, format="%.2f", width="small"),
|
| 174 |
+
"Это БП?": st.column_config.CheckboxColumn("Это БП?", help="Источник Питания"),
|
| 175 |
+
"ups": st.column_config.CheckboxColumn("На ИБП?", help="Питается от ИБП"),
|
| 176 |
+
}
|
| 177 |
+
my_column_order = ["Потребитель", "Артикул", "P_шт", "Q_внутр", "Q_внешн", "Q_спец", "Q_220", "Коэф.", "Это БП?", "ups"]
|
| 178 |
+
|
| 179 |
+
if has_input2_flag:
|
| 180 |
+
my_column_config["input2"] = st.column_config.CheckboxColumn("На ��вод 2?", help="Питается от резерва")
|
| 181 |
+
my_column_order.append("input2")
|
| 182 |
+
|
| 183 |
+
st.info(
|
| 184 |
+
"ℹ️ Потребление Блоков Питания (DC) добавляется к ОБОИМ вводам (резервирование). Потребители 220В распределяются по галочке 'На Ввод 2'.")
|
| 185 |
+
edited_equip = st.data_editor(
|
| 186 |
+
df_to_edit,
|
| 187 |
+
num_rows="dynamic",
|
| 188 |
+
use_container_width=True,
|
| 189 |
+
column_config=my_column_config,
|
| 190 |
+
column_order=my_column_order,
|
| 191 |
+
key="equip_editor"
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
st.session_state.equip_rows = edited_equip.to_dict('records')
|
| 195 |
+
|
| 196 |
+
col_save, _ = st.columns([1, 4])
|
| 197 |
+
with col_save:
|
| 198 |
+
if st.button("💾 Сохранить устройство в db"):
|
| 199 |
+
current_db = load_db()
|
| 200 |
+
db_map = {(str(i.get("article", "")), str(i.get("name", ""))): i for i in current_db}
|
| 201 |
+
upd = 0
|
| 202 |
+
for _, row in edited_equip.iterrows():
|
| 203 |
+
name = str(row.get("Потребитель", "")).strip()
|
| 204 |
+
art = str(row.get("Артикул", "")).strip()
|
| 205 |
+
if not name: continue
|
| 206 |
+
try:
|
| 207 |
+
p_val = float(row.get("P_шт", 0))
|
| 208 |
+
except:
|
| 209 |
+
p_val = 0.0
|
| 210 |
+
|
| 211 |
+
key = (art, name)
|
| 212 |
+
new_item = {"name": name, "article": art, "power": p_val}
|
| 213 |
+
|
| 214 |
+
if key not in db_map or db_map[key]["power"] != p_val:
|
| 215 |
+
db_map[key] = new_item
|
| 216 |
+
upd += 1
|
| 217 |
+
|
| 218 |
+
if upd > 0:
|
| 219 |
+
save_db(list(db_map.values()))
|
| 220 |
+
st.toast(f"✅ Успешно! Обновлено: {upd} записей.")
|
| 221 |
+
else:
|
| 222 |
+
st.toast("Нет изменений для сохранения.")
|
| 223 |
+
|
| 224 |
+
# Меню добавления
|
| 225 |
+
with st.container():
|
| 226 |
+
db_data = load_db()
|
| 227 |
+
db_options = {f"{i['name']} ({i['article']}) — {i['power']}Вт": i for i in db_data}
|
| 228 |
+
c_a1, c_a2, c_a3 = st.columns([3, 1, 1])
|
| 229 |
+
sel_key = c_a1.selectbox("Поиск по базе:", [""] + list(db_options.keys()))
|
| 230 |
+
add_qty = c_a2.number_input("Кол-во", 1, value=1)
|
| 231 |
+
if c_a3.button("➕ Добавить") and sel_key:
|
| 232 |
+
item = db_options[sel_key]
|
| 233 |
+
nm_low = item["name"].lower()
|
| 234 |
+
is_psu = any(x in nm_low for x in ["ип ", "бп ", "питания"])
|
| 235 |
+
q_in, q_ext, q_sp, q_220 = 0, 0, 0, 0
|
| 236 |
+
if "вентилятор" in nm_low or "свет" in nm_low or "220" in nm_low or "розетка" in nm_low:
|
| 237 |
+
q_220 = add_qty
|
| 238 |
+
elif "датчик" in nm_low:
|
| 239 |
+
q_ext = add_qty
|
| 240 |
+
elif "12в" in nm_low or "tk16" in item.get("article", "").lower():
|
| 241 |
+
q_sp = add_qty
|
| 242 |
+
else:
|
| 243 |
+
q_in = add_qty
|
| 244 |
+
|
| 245 |
+
new_row = {
|
| 246 |
+
"Потребитель": item["name"], "Артикул": item["article"], "P_шт": float(item["power"]),
|
| 247 |
+
"Q_внутр": int(q_in), "Q_внешн": int(q_ext), "Q_спец": int(q_sp), "Q_220": int(q_220),
|
| 248 |
+
"Коэф.": 1.0, "Это БП?": is_psu, "ups": False, "input2": False
|
| 249 |
+
}
|
| 250 |
+
st.session_state.equip_rows.append(new_row)
|
| 251 |
+
st.rerun()
|
| 252 |
+
|
| 253 |
+
st.divider()
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
# ==================== ЛОГИКА РАСЧЕТА ====================
|
| 257 |
+
def safe_float(val):
|
| 258 |
+
try:
|
| 259 |
+
return float(val) if val is not None else 0.0
|
| 260 |
+
except:
|
| 261 |
+
return 0.0
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
col_eff, _ = st.columns([1, 3])
|
| 265 |
+
with col_eff:
|
| 266 |
+
efficiency = st.number_input("КПД Источников Питания (AC/DC)", min_value=0.5, max_value=1.0, value=0.92, step=0.01)
|
| 267 |
+
|
| 268 |
+
# Списки для суммирования
|
| 269 |
+
loads = {"Internal": 0.0, "External": 0.0, "Special": 0.0, "Mains": 0.0}
|
| 270 |
+
ups_groups_map = {"Internal": False, "External": False, "Special": False}
|
| 271 |
+
direct_ups_load_220 = 0.0
|
| 272 |
+
non_critical_220 = 0.0
|
| 273 |
+
|
| 274 |
+
mains_220_in1 = 0.0
|
| 275 |
+
mains_220_in2 = 0.0
|
| 276 |
+
|
| 277 |
+
psu_export_list = []
|
| 278 |
+
custom_220_list = []
|
| 279 |
+
|
| 280 |
+
# --- 1. ПЕРВЫЙ ПРОХОД: Считаем нагрузки (DC) и распределяем 220В ---
|
| 281 |
+
dc_loads_total = {"Internal": 0.0, "External": 0.0, "Special": 0.0}
|
| 282 |
+
|
| 283 |
+
for _, row in edited_equip.iterrows():
|
| 284 |
+
is_psu = row.get("Это БП?", False)
|
| 285 |
+
if is_psu: continue
|
| 286 |
+
|
| 287 |
+
p_unit = safe_float(row.get("P_шт"))
|
| 288 |
+
k_coef = safe_float(row.get("Коэф.")) or 1.0
|
| 289 |
+
|
| 290 |
+
# --- DC Нагрузка ---
|
| 291 |
+
val_in = p_unit * safe_float(row.get("Q_внутр")) * k_coef
|
| 292 |
+
val_ex = p_unit * safe_float(row.get("Q_внешн")) * k_coef
|
| 293 |
+
val_sp = p_unit * safe_float(row.get("Q_спец")) * k_coef
|
| 294 |
+
|
| 295 |
+
dc_loads_total["Internal"] += val_in
|
| 296 |
+
dc_loads_total["External"] += val_ex
|
| 297 |
+
dc_loads_total["Special"] += val_sp
|
| 298 |
+
|
| 299 |
+
loads["Internal"] += val_in
|
| 300 |
+
loads["External"] += val_ex
|
| 301 |
+
loads["Special"] += val_sp
|
| 302 |
+
|
| 303 |
+
# --- AC Нагрузка ---
|
| 304 |
+
q_220 = safe_float(row.get("Q_220"))
|
| 305 |
+
if q_220 > 0:
|
| 306 |
+
p_ac = p_unit * q_220 * k_coef
|
| 307 |
+
loads["Mains"] += p_ac
|
| 308 |
+
|
| 309 |
+
is_in2 = row.get("input2", False)
|
| 310 |
+
if is_in2:
|
| 311 |
+
mains_220_in2 += p_ac
|
| 312 |
+
else:
|
| 313 |
+
mains_220_in1 += p_ac
|
| 314 |
+
|
| 315 |
+
if row.get("ups", False):
|
| 316 |
+
direct_ups_load_220 += p_ac
|
| 317 |
+
else:
|
| 318 |
+
non_critical_220 += p_ac
|
| 319 |
+
|
| 320 |
+
custom_220_list.append({
|
| 321 |
+
"name": str(row.get("Потребитель", "")),
|
| 322 |
+
"power": p_ac,
|
| 323 |
+
"on_ups": row.get("ups", False),
|
| 324 |
+
"input_id": 2 if is_in2 else 1
|
| 325 |
+
})
|
| 326 |
+
|
| 327 |
+
# --- 2. РАСЧЕТ ИТОГОВОГО ПОТРЕБЛЕНИЯ ---
|
| 328 |
+
total_dc_watts = dc_loads_total["Internal"] + dc_loads_total["External"] + dc_loads_total["Special"]
|
| 329 |
+
psu_ac_consumption_total = total_dc_watts / efficiency if efficiency > 0 else 0
|
| 330 |
+
|
| 331 |
+
watts_input_1 = mains_220_in1 + psu_ac_consumption_total
|
| 332 |
+
watts_input_2 = mains_220_in2 + psu_ac_consumption_total if has_input2_flag else 0
|
| 333 |
+
|
| 334 |
+
# --- 3. ВТОРОЙ ПРОХОД: Формируем список БП ---
|
| 335 |
+
for _, row in edited_equip.iterrows():
|
| 336 |
+
if not row.get("Это БП?", False): continue
|
| 337 |
+
|
| 338 |
+
original_name = str(row.get("Потребитель", ""))
|
| 339 |
+
article_name = str(row.get("Артикул", ""))
|
| 340 |
+
is_in2 = row.get("input2", False)
|
| 341 |
+
on_ups = row.get("ups", False)
|
| 342 |
+
|
| 343 |
+
has_in = safe_float(row.get("Q_внутр")) > 0
|
| 344 |
+
has_ex = safe_float(row.get("Q_внешн")) > 0
|
| 345 |
+
has_sp = safe_float(row.get("Q_спец")) > 0
|
| 346 |
+
|
| 347 |
+
# ФОРМИРУЕМ ИМЯ
|
| 348 |
+
if has_sp:
|
| 349 |
+
export_name = "Питание терминального контроллера"
|
| 350 |
+
elif has_in and has_ex:
|
| 351 |
+
export_name = "Питание внутренних и внешних цепей шкафа"
|
| 352 |
+
elif has_ex:
|
| 353 |
+
export_name = "Питание внешних потребителей"
|
| 354 |
+
elif has_in:
|
| 355 |
+
export_name = "Питание внутренних цепей шкафа"
|
| 356 |
+
else:
|
| 357 |
+
# Если БП без нагрузки - добавляем артикул к имени для ясности
|
| 358 |
+
if article_name and article_name != "-":
|
| 359 |
+
export_name = f"{original_name} ({article_name})"
|
| 360 |
+
else:
|
| 361 |
+
export_name = original_name
|
| 362 |
+
|
| 363 |
+
current_psu_dc_load = 0.0
|
| 364 |
+
if has_in: current_psu_dc_load += dc_loads_total["Internal"]
|
| 365 |
+
if has_ex: current_psu_dc_load += dc_loads_total["External"]
|
| 366 |
+
if has_sp: current_psu_dc_load += dc_loads_total["Special"]
|
| 367 |
+
|
| 368 |
+
if on_ups:
|
| 369 |
+
if has_in: ups_groups_map["Internal"] = True
|
| 370 |
+
if has_ex: ups_groups_map["External"] = True
|
| 371 |
+
if has_sp: ups_groups_map["Special"] = True
|
| 372 |
+
|
| 373 |
+
volts = 12 if has_sp else 24
|
| 374 |
+
|
| 375 |
+
psu_export_list.append({
|
| 376 |
+
"name": export_name,
|
| 377 |
+
"dc_power": current_psu_dc_load,
|
| 378 |
+
"voltage_dc": volts,
|
| 379 |
+
"ac_power": 0,
|
| 380 |
+
"on_ups": on_ups,
|
| 381 |
+
"input_id": 2 if is_in2 else 1
|
| 382 |
+
})
|
| 383 |
+
|
| 384 |
+
# --- ВЫВОД РЕЗУЛЬТАТОВ ---
|
| 385 |
+
st.subheader("3. Расчет нагрузки по группам (DC)")
|
| 386 |
+
c1, c2, c3, c4 = st.columns(4)
|
| 387 |
+
c1.metric("Внутр (24В)", f"{loads['Internal']:.1f} Вт", delta="На ИБП" if ups_groups_map["Internal"] else "Мимо ИБП")
|
| 388 |
+
c2.metric("Внешн (24В)", f"{loads['External']:.1f} Вт", delta="На ИБП" if ups_groups_map["External"] else "Мимо ИБП")
|
| 389 |
+
c3.metric("Спец (12В)", f"{loads['Special']:.1f} Вт", delta="На ИБП" if ups_groups_map["Special"] else "Мимо ИБП")
|
| 390 |
+
c4.metric("Сеть (220В)", f"{loads['Mains']:.1f} Вт", f"UPS: {direct_ups_load_220:.1f} | Grid: {non_critical_220:.1f}")
|
| 391 |
+
|
| 392 |
+
# === БЛОК: Скачивание спецификации + ИТОГИ ===
|
| 393 |
+
|
| 394 |
+
st.write("###") # Отступ
|
| 395 |
+
if st.session_state.equip_rows:
|
| 396 |
+
# 1. Готовим данные таблицы
|
| 397 |
+
df_spec = pd.DataFrame(st.session_state.equip_rows).copy()
|
| 398 |
+
|
| 399 |
+
# Считаем общее кол-во и мощность
|
| 400 |
+
df_spec["Всего_шт"] = (df_spec.get("Q_внутр", 0) + df_spec.get("Q_внешн", 0) +
|
| 401 |
+
df_spec.get("Q_спец", 0) + df_spec.get("Q_220", 0))
|
| 402 |
+
df_spec["P_общ (Вт)"] = df_spec["P_шт"] * df_spec["Всего_шт"] * df_spec["Коэф."]
|
| 403 |
+
|
| 404 |
+
# --- ЗАМЕНА TRUE/FALSE НА ГАЛОЧКИ ---
|
| 405 |
+
bool_cols = ["Это БП?", "ups", "input2"]
|
| 406 |
+
for col in bool_cols:
|
| 407 |
+
if col in df_spec.columns:
|
| 408 |
+
df_spec[col] = df_spec[col].map({True: "✔", False: ""})
|
| 409 |
+
|
| 410 |
+
# Настройка названий
|
| 411 |
+
cols_export = {
|
| 412 |
+
"Потребитель": "Наименование",
|
| 413 |
+
"Артикул": "Артикул",
|
| 414 |
+
"P_шт": "P_ед (Вт)",
|
| 415 |
+
"Всего_шт": "Кол-во",
|
| 416 |
+
"P_общ (Вт)": "P_итог (Вт)",
|
| 417 |
+
"Q_внутр": "Внутр (24В)",
|
| 418 |
+
"Q_внешн": "Внешн (24В)",
|
| 419 |
+
"Q_спец": "Спец",
|
| 420 |
+
"Q_220": "220В",
|
| 421 |
+
"Это БП?": "Блок Питания",
|
| 422 |
+
"ups": "На ИБП",
|
| 423 |
+
"input2": "На Ввод 2"
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
available_cols = [c for c in cols_export.keys() if c in df_spec.columns]
|
| 427 |
+
df_final = df_spec[available_cols].rename(columns=cols_export)
|
| 428 |
+
|
| 429 |
+
# 2. Подготовка данных для ИТОГОВ
|
| 430 |
+
# Используем переменные loads и efficiency, которые уже посчитаны выше в коде
|
| 431 |
+
val_in = loads['Internal']
|
| 432 |
+
val_ex = loads['External']
|
| 433 |
+
val_psu_in = val_in / efficiency if efficiency > 0 else 0
|
| 434 |
+
val_psu_ex = val_ex / efficiency if efficiency > 0 else 0
|
| 435 |
+
|
| 436 |
+
# Общая мощность (220В + вся DC через КПД)
|
| 437 |
+
val_total_cab = loads['Mains'] + (loads['Internal'] + loads['External'] + loads['Special']) / efficiency
|
| 438 |
+
|
| 439 |
+
summary_data = [
|
| 440 |
+
["--- ИТОГИ ---", ""],
|
| 441 |
+
["Нагрузка Внутр (24В)", round(val_in, 2)],
|
| 442 |
+
["Нагрузка Внешн (24В)", round(val_ex, 2)],
|
| 443 |
+
[f"Потребление БП Внутр (КПД {efficiency})", round(val_psu_in, 2)],
|
| 444 |
+
[f"Потребление БП Внешн (КПД {efficiency})", round(val_psu_ex, 2)],
|
| 445 |
+
["ОБЩИЙ ВВОД 220В", round(val_total_cab, 2)]
|
| 446 |
+
]
|
| 447 |
+
|
| 448 |
+
# 3. Создаем Excel
|
| 449 |
+
bio_spec = BytesIO()
|
| 450 |
+
with pd.ExcelWriter(bio_spec, engine='openpyxl') as writer:
|
| 451 |
+
# Записываем основную таблицу
|
| 452 |
+
df_final.to_excel(writer, index=False, sheet_name="Спецификация")
|
| 453 |
+
|
| 454 |
+
ws = writer.sheets["Спецификация"]
|
| 455 |
+
from openpyxl.styles import Alignment, Font
|
| 456 |
+
|
| 457 |
+
# Настройка ширины
|
| 458 |
+
ws.column_dimensions['A'].width = 40
|
| 459 |
+
ws.column_dimensions['B'].width = 20
|
| 460 |
+
|
| 461 |
+
# Центрируем галочки
|
| 462 |
+
for row in ws.iter_rows(min_row=2, max_row=len(df_final) + 1):
|
| 463 |
+
for cell in row:
|
| 464 |
+
if cell.column > 2:
|
| 465 |
+
cell.alignment = Alignment(horizontal='center')
|
| 466 |
+
|
| 467 |
+
# --- ДОПИСЫВАЕМ ИТОГИ ВНИЗУ ---
|
| 468 |
+
start_row = len(df_final) + 3 # Отступаем 2 строки вниз
|
| 469 |
+
|
| 470 |
+
for i, (label, value) in enumerate(summary_data):
|
| 471 |
+
current_row = start_row + i
|
| 472 |
+
|
| 473 |
+
# Пишем название в колонку A
|
| 474 |
+
cell_name = ws.cell(row=current_row, column=1, value=label)
|
| 475 |
+
cell_name.font = Font(bold=True)
|
| 476 |
+
|
| 477 |
+
# Пишем значение в колонку E (P_итог) - чтобы было наглядно
|
| 478 |
+
# Колонка E это 5-я по счету
|
| 479 |
+
ws.cell(row=current_row, column=5, value=value)
|
| 480 |
+
|
| 481 |
+
# 4. Кнопка
|
| 482 |
+
st.download_button(
|
| 483 |
+
label="📥 Скачать спецификацию оборудования (.xlsx)",
|
| 484 |
+
data=bio_spec.getvalue(),
|
| 485 |
+
file_name=f"Spec_{cabinet_label}.xlsx",
|
| 486 |
+
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
| 487 |
+
)
|
| 488 |
+
# ===========================================
|
| 489 |
+
st.markdown("---")
|
| 490 |
+
|
| 491 |
+
# ==================== БЛОК РАСЧЕТА ИБП ====================
|
| 492 |
+
# 1. Инициализация переменных (чтобы не было ошибки NameError)
|
| 493 |
+
ups_dc_demand = 0.0
|
| 494 |
+
ups_charge_power = 0.0
|
| 495 |
+
ups_input_normal = 0.0
|
| 496 |
+
ups_input_max = 0.0
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
# 2. Сбор нагрузки по группам
|
| 501 |
+
if ups_groups_map["Internal"]: ups_dc_demand += loads["Internal"]
|
| 502 |
+
if ups_groups_map["External"]: ups_dc_demand += loads["External"]
|
| 503 |
+
if ups_groups_map["Special"]: ups_dc_demand += loads["Special"]
|
| 504 |
+
|
| 505 |
+
ups_load_from_psus = ups_dc_demand / efficiency if efficiency > 0 else 0
|
| 506 |
+
ups_inverter_load = direct_ups_load_220 + ups_load_from_psus
|
| 507 |
+
|
| 508 |
+
# 3. ЕСЛИ ЕСТЬ НАГРУЗКА -> РАСЧЕТ
|
| 509 |
+
ups_vars = {
|
| 510 |
+
"t_req": 0, "u_sys": 0, "eff": 0, "k_temp": 0, "k_depth": 0,
|
| 511 |
+
"c_req": 0, "c_act": 0, "bat_qty": 0, "bat_nom": 0,
|
| 512 |
+
"t_fact_str": "", "p_norm": 0, "p_charge": 0, "p_max": 0
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
if ups_inverter_load > 0:
|
| 516 |
+
st.subheader("4. Подробный расчет ИБП и АКБ")
|
| 517 |
+
st.markdown("#### А. Определение нагрузки на инвертор")
|
| 518 |
+
st.latex(r"P_{Inv} = P_{220(UPS)} + \frac{P_{DC(UPS)}}{\eta_{PSU}}")
|
| 519 |
+
st.info(f"⚡ **Расчетная нагрузка на инвертор:** {ups_inverter_load:.2f} Вт")
|
| 520 |
+
|
| 521 |
+
# Ввод параметров
|
| 522 |
+
c_u1, c_u2, c_u3 = st.columns(3)
|
| 523 |
+
t_autonomy_req = c_u1.number_input("Время автономии (мин)", value=60, step=10)
|
| 524 |
+
u_bat_sys = c_u2.selectbox("Напряжение АКБ (U_sys)", [12, 24, 36, 48, 72, 96], index=2)
|
| 525 |
+
k_inv_eff = c_u3.number_input("КПД ИБП (Inv)", 0.8, 1.0, 0.90, step=0.01)
|
| 526 |
+
|
| 527 |
+
with st.expander("Коэф. АКБ", expanded=False):
|
| 528 |
+
c_k1, c_k2 = st.columns(2)
|
| 529 |
+
k_temp = c_k1.number_input("K_temp", 1.0, 1.5, 1.2)
|
| 530 |
+
k_depth = c_k2.number_input("K_depth", 0.5, 1.0, 0.7)
|
| 531 |
+
|
| 532 |
+
# 1. Расчет требуемой емкости
|
| 533 |
+
hours_req = t_autonomy_req / 60
|
| 534 |
+
st.markdown("**1. Рас��ет требуемой емкости:**")
|
| 535 |
+
st.latex(r"C_{req} = \frac{P_{Inv} \times T_{hours} \times K_{temp}}{U_{sys} \times \eta_{Inv} \times K_{depth}}")
|
| 536 |
+
capacity_needed = (ups_inverter_load * hours_req * k_temp) / (u_bat_sys * k_inv_eff * k_depth)
|
| 537 |
+
st.write(f"📉 Требуемая емкость: **{capacity_needed:.2f} Ач**")
|
| 538 |
+
|
| 539 |
+
col_b1, col_b2 = st.columns(2)
|
| 540 |
+
with col_b1:
|
| 541 |
+
st.write("**2. Выбор батарей:**")
|
| 542 |
+
bat_nominal = st.selectbox("Номинал АКБ (Ач)", [4.5, 5, 7, 9, 12, 17, 26, 40, 55, 100], index=3)
|
| 543 |
+
series_qty = int(u_bat_sys / 12)
|
| 544 |
+
parallel_qty = math.ceil(capacity_needed / bat_nominal)
|
| 545 |
+
total_bat_qty = series_qty * parallel_qty
|
| 546 |
+
actual_capacity_system = parallel_qty * bat_nominal
|
| 547 |
+
|
| 548 |
+
st.success(
|
| 549 |
+
f"🔋 Конфигурация: {total_bat_qty} шт. ({series_qty} послед. x {parallel_qty} паралл.) = {actual_capacity_system} Ач")
|
| 550 |
+
|
| 551 |
+
if st.button("➕ Добавить ИБП и АКБ в таблицу"):
|
| 552 |
+
st.session_state.equip_rows.append(
|
| 553 |
+
{"Потребитель": f"ИБП (Вх. макс)", "Артикул": "UPS", "P_шт": 0.0, "Q_внутр": 0, "Q_внешн": 0,
|
| 554 |
+
"Q_спец": 0, "Q_220": 1, "Коэф.": 1.0, "Это БП?": False, "ups": False, "input2": False})
|
| 555 |
+
st.session_state.equip_rows.append(
|
| 556 |
+
{"Потребитель": f"АКБ 12В {bat_nominal}Ач", "Артикул": "BAT", "P_шт": 0.0, "Q_внутр": 0, "Q_внешн": 0,
|
| 557 |
+
"Q_спец": 0, "Q_220": total_bat_qty, "Коэф.": 1.0, "Это БП?": False, "ups": False, "input2": False})
|
| 558 |
+
st.rerun()
|
| 559 |
+
|
| 560 |
+
with col_b2:
|
| 561 |
+
st.write("**3. Энергобаланс:**")
|
| 562 |
+
ups_input_normal = ups_inverter_load / k_inv_eff
|
| 563 |
+
charge_current = actual_capacity_system * 0.15
|
| 564 |
+
ups_charge_power = (u_bat_sys * charge_current) / 0.85
|
| 565 |
+
ups_input_max = ups_input_normal + ups_charge_power
|
| 566 |
+
|
| 567 |
+
st.latex(r"P_{Charge} = \frac{U_{sys} \times C_{act} \times 0.15}{0.85}")
|
| 568 |
+
st.write(f"Мощность заряда: **{ups_charge_power:.2f} Вт**")
|
| 569 |
+
|
| 570 |
+
# Расчет фактического времени (для Excel)
|
| 571 |
+
t_fact_hours = (actual_capacity_system * u_bat_sys * k_inv_eff * k_depth) / (ups_inverter_load * k_temp)
|
| 572 |
+
h_disp = int(t_fact_hours)
|
| 573 |
+
m_disp = int((t_fact_hours - h_disp) * 60)
|
| 574 |
+
t_fact_str = f"{h_disp}ч {m_disp}мин"
|
| 575 |
+
st.caption(f"Фактическое время автономии: {t_fact_str}")
|
| 576 |
+
|
| 577 |
+
# Сохраняем данные для Excel
|
| 578 |
+
ups_vars = {
|
| 579 |
+
"t_req": t_autonomy_req, "u_sys": u_bat_sys, "eff": k_inv_eff,
|
| 580 |
+
"k_temp": k_temp, "k_depth": k_depth,
|
| 581 |
+
"c_req": capacity_needed, "c_act": actual_capacity_system,
|
| 582 |
+
"bat_qty": total_bat_qty, "bat_nom": bat_nominal,
|
| 583 |
+
"t_fact_str": t_fact_str,
|
| 584 |
+
"p_norm": ups_input_normal, "p_charge": ups_charge_power, "p_max": ups_input_max
|
| 585 |
+
}
|
| 586 |
+
else:
|
| 587 |
+
st.info("Нет нагрузки на ИБП.")
|
| 588 |
+
|
| 589 |
+
st.divider()
|
| 590 |
+
st.subheader("5. Итоговое потребление (Вводные автоматы)")
|
| 591 |
+
|
| 592 |
+
final_cabinet_max = watts_input_1 + ups_charge_power
|
| 593 |
+
|
| 594 |
+
c_fin1, c_fin2 = st.columns(2)
|
| 595 |
+
with c_fin1:
|
| 596 |
+
st.write(f"### Ввод 1 (Основной)")
|
| 597 |
+
st.latex(r"P_{In1} = \sum P_{220(In1)} + P_{PSU(Total)}")
|
| 598 |
+
st.write(f"= {mains_220_in1:.2f} + {psu_ac_consumption_total:.2f}")
|
| 599 |
+
st.success(f"Рабочий ток: **{watts_input_1:.2f} Вт**")
|
| 600 |
+
if ups_charge_power > 0:
|
| 601 |
+
st.warning(f"Макс. (с зарядом АКБ): **{final_cabinet_max:.2f} Вт**")
|
| 602 |
+
|
| 603 |
+
with c_fin2:
|
| 604 |
+
if has_input2_flag:
|
| 605 |
+
st.write(f"### Ввод 2 (Резерв)")
|
| 606 |
+
st.latex(r"P_{In2} = \sum P_{220(In2)} + P_{PSU(Total)}")
|
| 607 |
+
st.write(f"= {mains_220_in2:.2f} + {psu_ac_consumption_total:.2f}")
|
| 608 |
+
st.success(f"Рабочий ток: **{watts_input_2:.2f} Вт**")
|
| 609 |
+
else:
|
| 610 |
+
st.caption("Ввод 2 не используется")
|
| 611 |
+
|
| 612 |
+
# Сохранение данных для файла 3
|
| 613 |
+
st.session_state['summary_data'] = {
|
| 614 |
+
"total_input_1": watts_input_1,
|
| 615 |
+
"max_input_1": final_cabinet_max,
|
| 616 |
+
"total_input_2": watts_input_2,
|
| 617 |
+
"has_input2": has_input2_flag,
|
| 618 |
+
"psu_list": psu_export_list,
|
| 619 |
+
"custom_220_list": custom_220_list,
|
| 620 |
+
"cabinet_name": cabinet_label,
|
| 621 |
+
"ups_charge_power": ups_charge_power,
|
| 622 |
+
"ups_input_normal": ups_input_normal, # Передаем потребление ИБП в норм. режиме
|
| 623 |
+
"ups_input_max": ups_input_max # Передаем потребление ИБП в макс. режиме
|
| 624 |
+
}
|
| 625 |
+
st.success("✅ Данные сохранены для экспорта.")
|
| 626 |
+
|
| 627 |
+
# ==================== ЭКСПОРТ В EXCEL (ПОДРОБНЫЙ) ====================
|
| 628 |
+
st.markdown("---")
|
| 629 |
+
st.subheader("7. Выгрузка отчета")
|
| 630 |
+
|
| 631 |
+
if st.button("📥 Скачать полный расчет (.xlsx)"):
|
| 632 |
+
report_data = []
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
# Функция-помощник для добавления строк
|
| 636 |
+
def add_row(param, val, unit, comment=""):
|
| 637 |
+
report_data.append({
|
| 638 |
+
"Параметр": param,
|
| 639 |
+
"Значение": val,
|
| 640 |
+
"Ед.изм": unit,
|
| 641 |
+
"Формула / Комментарий": comment
|
| 642 |
+
})
|
| 643 |
+
|
| 644 |
+
|
| 645 |
+
# === БЛОК 1: ИСХОДНЫЕ ДАННЫЕ ===
|
| 646 |
+
add_row("ИСХОДНЫЕ ДАННЫЕ", "", "", "")
|
| 647 |
+
add_row("Нагрузка 24В (Внутр)", round(loads["Internal"], 2), "Вт", "Логика, модули")
|
| 648 |
+
add_row("Нагрузка 24В (Внешн)", round(loads["External"], 2), "Вт", "Датчики")
|
| 649 |
+
add_row("Нагрузка 220В (Прямая)", round(loads["Mains"], 2), "Вт", "Вент, розетки")
|
| 650 |
+
add_row("КПД Блоков питания", efficiency, "о.е.", "")
|
| 651 |
+
add_row("", "", "", "") # Пустая строка
|
| 652 |
+
|
| 653 |
+
# === БЛОК 2: РАСЧЕТ ИБП ===
|
| 654 |
+
if ups_inverter_load > 0:
|
| 655 |
+
add_row("РАСЧЕТ ИБП И АКБ", "", "", "")
|
| 656 |
+
add_row("Нагрузка на инвертор (P_inv)", round(ups_inverter_load, 2), "Вт", "P_220(ups) + P_24(ups)/Efficiency")
|
| 657 |
+
add_row("Время автономии (T_req)", ups_vars["t_req"], "мин", "Задание")
|
| 658 |
+
add_row("Напряжение АКБ (U_sys)", ups_vars["u_sys"], "В", "")
|
| 659 |
+
add_row("КПД Инвертора (Eff)", ups_vars["eff"], "о.е.", "")
|
| 660 |
+
add_row("Коэф. глубины разряда (K_depth)", ups_vars["k_depth"], "о.е.", "")
|
| 661 |
+
add_row("Коэф. запаса (K_temp)", ups_vars["k_temp"], "о.е.", "")
|
| 662 |
+
|
| 663 |
+
add_row("Требуемая емкость (C_req)", round(ups_vars["c_req"], 2), "Ач",
|
| 664 |
+
"(P_inv * T_h * K_temp) / (U_sys * Eff * K_depth)")
|
| 665 |
+
add_row("Выбранная емкость (C_act)", ups_vars["c_act"], "Ач",
|
| 666 |
+
f"{ups_vars['bat_qty']} шт по {ups_vars['bat_nom']} Ач")
|
| 667 |
+
add_row("Фактическое время (T_fact)", ups_vars["t_fact_str"], "",
|
| 668 |
+
"(C_act * U_sys * Eff * K_depth) / (P_inv * K_temp)")
|
| 669 |
+
|
| 670 |
+
add_row("", "", "", "") # Пустая строка
|
| 671 |
+
|
| 672 |
+
add_row("ЭНЕРГОБАЛАНС", "", "", "")
|
| 673 |
+
add_row("Потребление ИБП (Норма)", round(ups_vars["p_norm"], 2), "Вт", "P_inv / Eff_inv")
|
| 674 |
+
add_row("Мощность заряда (P_charge)", round(ups_vars["p_charge"], 2), "Вт", "(U_sys * C_act * 0.15) / 0.85")
|
| 675 |
+
add_row("Потребление ИБП (Макс)", round(ups_vars["p_max"], 2), "Вт", "P_norm + P_charge")
|
| 676 |
+
add_row("", "", "", "") # Пустая строка
|
| 677 |
+
|
| 678 |
+
# === БЛОК 3: ИТОГИ ===
|
| 679 |
+
add_row("ИТОГИ ПО ШКАФУ", "", "", "")
|
| 680 |
+
|
| 681 |
+
# Ввод 1
|
| 682 |
+
add_row("Ввод 1 (Рабочий ток)", round(watts_input_1, 2), "Вт", "Вся нагрузка + потери КПД")
|
| 683 |
+
if ups_charge_power > 0:
|
| 684 |
+
add_row("Ввод 1 (Для автомата)", round(final_cabinet_max, 2), "Вт", "С учетом заряда АКБ")
|
| 685 |
+
|
| 686 |
+
# Ввод 2 (если есть)
|
| 687 |
+
if has_input2_flag:
|
| 688 |
+
add_row("Ввод 2 (Резерв)", round(watts_input_2, 2), "Вт", "Резервирование нагрузки")
|
| 689 |
+
|
| 690 |
+
# Генерация Excel
|
| 691 |
+
df_rep = pd.DataFrame(report_data)
|
| 692 |
+
bio_rep = BytesIO()
|
| 693 |
+
with pd.ExcelWriter(bio_rep, engine='openpyxl') as writer:
|
| 694 |
+
df_rep.to_excel(writer, index=False, sheet_name="Calculation")
|
| 695 |
+
|
| 696 |
+
# Наводим красоту
|
| 697 |
+
ws = writer.sheets["Calculation"]
|
| 698 |
+
from openpyxl.styles import Font, Border, Side
|
| 699 |
+
|
| 700 |
+
# Ширина колонок
|
| 701 |
+
ws.column_dimensions['A'].width = 35
|
| 702 |
+
ws.column_dimensions['B'].width = 15
|
| 703 |
+
ws.column_dimensions['C'].width = 10
|
| 704 |
+
ws.column_dimensions['D'].width = 60
|
| 705 |
+
|
| 706 |
+
# Жирный шрифт для заголовков разделов-
|
| 707 |
+
for row in ws.iter_rows():
|
| 708 |
+
cell_param = row[0]
|
| 709 |
+
if cell_param.value in ["ИСХОДНЫЕ ДАННЫЕ", "РАСЧЕТ ИБП И АКБ", "ЭНЕРГОБАЛАНС", "ИТОГИ ПО ШКАФУ"]:
|
| 710 |
+
cell_param.font = Font(bold=True)
|
| 711 |
+
|
| 712 |
+
st.download_button("Скачать файл (.xlsx)", data=bio_rep.getvalue(), file_name=f"Calculation_{cabinet_label}.xlsx")
|
pages/3_📝_SingleLine.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import os
|
| 4 |
+
from io import BytesIO
|
| 5 |
+
|
| 6 |
+
st.set_page_config(page_title="Генерация таблицы Eplan", layout="wide")
|
| 7 |
+
st.title("📑 Генерация данных для Eplan")
|
| 8 |
+
|
| 9 |
+
# ==================== 1. ЗАГРУЗКА ДАННЫХ ====================
|
| 10 |
+
if "summary_data" not in st.session_state:
|
| 11 |
+
st.warning("⚠️ Нет данных для расчета! Сначала выполните расчет на странице 'Hardware' и нажмите 'Сохранить'.")
|
| 12 |
+
st.stop()
|
| 13 |
+
|
| 14 |
+
data = st.session_state['summary_data']
|
| 15 |
+
cabinet_name = data.get("cabinet_name", "Шкаф")
|
| 16 |
+
st.info(f"Активный проект: **{cabinet_name}**")
|
| 17 |
+
|
| 18 |
+
# ==================== 2. НАСТРОЙКИ МАКРОСОВ ====================
|
| 19 |
+
with st.expander("⚙️ Настройки макросов", expanded=False):
|
| 20 |
+
base_macro_path = st.text_input("Базовая папка:", value=r"C:\Eplan_DB\Data\Макросы\Однолинейные схемы")
|
| 21 |
+
col1, col2 = st.columns(2)
|
| 22 |
+
with col1:
|
| 23 |
+
f_in = st.text_input("Ввод (XT)", "XT.ema")
|
| 24 |
+
f_qs = st.text_input("Автомат (QS)", "QF.ema")
|
| 25 |
+
with col2:
|
| 26 |
+
f_psu = st.text_input("Блок питания (PSU)", "Fus_24.ems")
|
| 27 |
+
f_light = st.text_input("Свет/Вент", "Lamp_fan.ems")
|
| 28 |
+
f_generic = st.text_input("Устройства", "Gen_Device.ems")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def get_path(filename):
|
| 32 |
+
return os.path.join(base_macro_path, filename) if filename else ""
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
u_ac = 230
|
| 36 |
+
|
| 37 |
+
# ==================== 3. ПОДГОТОВКА ДАННЫХ ====================
|
| 38 |
+
p_charge = data.get('ups_charge_power', 0)
|
| 39 |
+
val_in1_norm = data.get('total_input_1', 0)
|
| 40 |
+
val_in1_max = data.get('max_input_1', 0)
|
| 41 |
+
val_in2 = data.get('total_input_2', 0)
|
| 42 |
+
|
| 43 |
+
# ДАННЫЕ ПО ИБП ИЗ ФАЙЛА 2
|
| 44 |
+
ups_val_norm = data.get('ups_input_normal', 0)
|
| 45 |
+
ups_val_max = data.get('ups_input_max', 0)
|
| 46 |
+
|
| 47 |
+
has_in2 = data.get('has_input2', False)
|
| 48 |
+
|
| 49 |
+
psu_items = data.get('psu_list', [])
|
| 50 |
+
custom_items = data.get('custom_220_list', [])
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# Функции форматирования
|
| 54 |
+
def fmt_p(val_normal, val_max=None, placeholder=False):
|
| 55 |
+
if not val_normal or val_normal <= 0:
|
| 56 |
+
return "ХХ Вт" if placeholder else ""
|
| 57 |
+
|
| 58 |
+
base = f"{int(val_normal)} Вт"
|
| 59 |
+
if val_max and (val_max - val_normal) > 5:
|
| 60 |
+
return f"{base}\n{int(val_max)} Вт (при заряде АКБ)"
|
| 61 |
+
return base
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def fmt_iu(watts, volts, suffix, watts_max=None, placeholder=False):
|
| 65 |
+
if not watts or watts <= 0:
|
| 66 |
+
return "ХХ-ХХ" if placeholder else ""
|
| 67 |
+
|
| 68 |
+
amp = watts / volts
|
| 69 |
+
base = f"{amp:.2f}A / {volts}В {suffix}"
|
| 70 |
+
if watts_max and (watts_max - watts) > 5:
|
| 71 |
+
amp_max = watts_max / volts
|
| 72 |
+
return f"{base}\n{amp_max:.2f} А (макс)"
|
| 73 |
+
return base
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# ==================== 4. СБОРКА ТАБЛИЦЫ ====================
|
| 77 |
+
structure = []
|
| 78 |
+
|
| 79 |
+
# 1. ВВОД 1
|
| 80 |
+
structure.append({
|
| 81 |
+
"Установка": "Вводные клеммы", "Устройство": "Ввод питания 230В (Ввод 1)",
|
| 82 |
+
"I-расч./U-ном.": fmt_iu(val_in1_norm, u_ac, "AC", val_in1_max),
|
| 83 |
+
"P установочное": fmt_p(val_in1_norm, val_in1_max),
|
| 84 |
+
"Макросы": get_path(f_in)
|
| 85 |
+
})
|
| 86 |
+
|
| 87 |
+
# 1.1 ВВОД 2 (Если есть)
|
| 88 |
+
if has_in2:
|
| 89 |
+
structure.append({
|
| 90 |
+
"Установка": "Вводные клеммы", "Устройство": "Ввод питания 230В (Ввод 2)",
|
| 91 |
+
"I-расч./U-ном.": fmt_iu(val_in2, u_ac, "AC"),
|
| 92 |
+
"P установочное": fmt_p(val_in2),
|
| 93 |
+
"Макросы": get_path(f_in)
|
| 94 |
+
})
|
| 95 |
+
|
| 96 |
+
structure.append({"Установка": "Вводные автоматы", "Устройство": "", "I-расч./U-ном.": "", "P установочное": "",
|
| 97 |
+
"Макросы": get_path(f_qs)})
|
| 98 |
+
structure.append({"Установка": "Распред.Автоматы шкафа", "Устройство": "", "I-расч./U-ном.": "", "P установочное": "",
|
| 99 |
+
"Макросы": get_path(f_qs)})
|
| 100 |
+
|
| 101 |
+
# 2. Блоки Питания
|
| 102 |
+
for psu in psu_items:
|
| 103 |
+
name = psu['name']
|
| 104 |
+
dc_p = psu.get('dc_power', 0)
|
| 105 |
+
volts = psu['voltage_dc']
|
| 106 |
+
i_dc = dc_p / volts if volts else 0
|
| 107 |
+
|
| 108 |
+
structure.append({
|
| 109 |
+
"Установка": "Оборудование шкафа",
|
| 110 |
+
"Устройство": name,
|
| 111 |
+
"I-расч./U-ном.": f"{i_dc:.2f}A / {volts}В DC",
|
| 112 |
+
"P установочное": fmt_p(dc_p),
|
| 113 |
+
"Макросы": get_path(f_psu)
|
| 114 |
+
})
|
| 115 |
+
|
| 116 |
+
# 3. Устройства 220В
|
| 117 |
+
for item in custom_items:
|
| 118 |
+
name = item['name']
|
| 119 |
+
p_val = item['power']
|
| 120 |
+
|
| 121 |
+
# ПРОВЕРКА НА ИБП
|
| 122 |
+
is_ups_device = any(k in name.lower() for k in ["ибп", "ups", "sr1101"])
|
| 123 |
+
|
| 124 |
+
# Если это ИБП, подставляем расчетные данные из Файла 2
|
| 125 |
+
if is_ups_device:
|
| 126 |
+
p_show = ups_val_norm
|
| 127 |
+
p_max_show = ups_val_max
|
| 128 |
+
use_place = False
|
| 129 |
+
else:
|
| 130 |
+
p_show = p_val
|
| 131 |
+
p_max_show = None
|
| 132 |
+
# Если мощность 0 (как у розетки), включаем плейсхолдер
|
| 133 |
+
use_place = True if p_val == 0 else False
|
| 134 |
+
|
| 135 |
+
if any(x in name.lower() for x in ["свет", "вент", "розет"]):
|
| 136 |
+
macro = get_path(f_light)
|
| 137 |
+
else:
|
| 138 |
+
macro = get_path(f_generic)
|
| 139 |
+
|
| 140 |
+
structure.append({
|
| 141 |
+
"Установка": "Оборудование шкафа",
|
| 142 |
+
"Устройство": name,
|
| 143 |
+
"I-расч./U-ном.": fmt_iu(p_show, u_ac, "AC", p_max_show, placeholder=use_place),
|
| 144 |
+
"P установочное": fmt_p(p_show, p_max_show, placeholder=use_place),
|
| 145 |
+
"Макросы": macro
|
| 146 |
+
})
|
| 147 |
+
|
| 148 |
+
# Пустые строки
|
| 149 |
+
structure.append(
|
| 150 |
+
{"Установка": "Р установочное", "Устройство": "", "I-расч./U-ном.": "", "P установочное": "", "Макросы": ""})
|
| 151 |
+
structure.append(
|
| 152 |
+
{"Установка": "I-расч./U-ном.", "Устройство": "", "I-расч./U-ном.": "", "P установочное": "", "Макросы": ""})
|
| 153 |
+
structure.append(
|
| 154 |
+
{"Установка": "Устройство", "Устройство": "", "I-расч./U-ном.": "", "P установочное": "", "Макросы": ""})
|
| 155 |
+
|
| 156 |
+
# Создание DataFrame
|
| 157 |
+
df_initial = pd.DataFrame(structure)
|
| 158 |
+
if df_initial.empty:
|
| 159 |
+
df_initial = pd.DataFrame(columns=["Установка", "Устройство", "I-расч./U-ном.", "P установочное", "Макросы"])
|
| 160 |
+
else:
|
| 161 |
+
df_initial = df_initial[["Установка", "Устройство", "I-расч./U-ном.", "P установочное", "Макросы"]]
|
| 162 |
+
|
| 163 |
+
# ==================== 5. ВЫВОД- ====================
|
| 164 |
+
st.subheader("Предварительный просмотр (Можно добавлять строки 👇)")
|
| 165 |
+
|
| 166 |
+
edited_df = st.data_editor(
|
| 167 |
+
df_initial,
|
| 168 |
+
use_container_width=True,
|
| 169 |
+
hide_index=True,
|
| 170 |
+
num_rows="dynamic",
|
| 171 |
+
column_config={
|
| 172 |
+
"Установка": st.column_config.TextColumn("Установка", width="medium"),
|
| 173 |
+
"Устройство": st.column_config.TextColumn("Устройство", width="large"),
|
| 174 |
+
"Макросы": st.column_config.TextColumn("Макросы", width="large"),
|
| 175 |
+
}
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
st.divider()
|
| 179 |
+
if st.button("📥 Скачать Excel (.xlsx)", type="primary"):
|
| 180 |
+
bio = BytesIO()
|
| 181 |
+
with pd.ExcelWriter(bio, engine='openpyxl') as writer:
|
| 182 |
+
edited_df.to_excel(writer, index=False, sheet_name="Generate_Data")
|
| 183 |
+
|
| 184 |
+
ws = writer.sheets["Generate_Data"]
|
| 185 |
+
from openpyxl.styles import Alignment, Font
|
| 186 |
+
|
| 187 |
+
ws.column_dimensions['A'].width = 25
|
| 188 |
+
ws.column_dimensions['B'].width = 40
|
| 189 |
+
ws.column_dimensions['C'].width = 30
|
| 190 |
+
ws.column_dimensions['D'].width = 30
|
| 191 |
+
ws.column_dimensions['E'].width = 60
|
| 192 |
+
|
| 193 |
+
for row in ws.iter_rows(min_row=1):
|
| 194 |
+
for cell in row:
|
| 195 |
+
cell.alignment = Alignment(wrap_text=True, vertical='center', horizontal='left')
|
| 196 |
+
if cell.row == 1:
|
| 197 |
+
cell.font = Font(bold=True)
|
| 198 |
+
|
| 199 |
+
st.download_button("Скачать файл", data=bio.getvalue(), file_name=f"Eplan_{cabinet_name}.xlsx")
|