streamlit рефактор
This commit is contained in:
80
streamlit_app/api_client.py
Normal file
80
streamlit_app/api_client.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"""
|
||||||
|
Модуль для работы с API
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any, List, Tuple
|
||||||
|
|
||||||
|
# Конфигурация API
|
||||||
|
API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000") # Внутренний адрес для Docker
|
||||||
|
API_PUBLIC_URL = os.getenv("API_PUBLIC_URL", "http://localhost:8000") # Внешний адрес для пользователя
|
||||||
|
|
||||||
|
|
||||||
|
def check_api_health() -> bool:
|
||||||
|
"""Проверка доступности API"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/", timeout=5)
|
||||||
|
return response.status_code == 200
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_parsers() -> List[str]:
|
||||||
|
"""Получение списка доступных парсеров"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/parsers")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()["parsers"]
|
||||||
|
return []
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_server_info() -> Dict[str, Any]:
|
||||||
|
"""Получение информации о сервере"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/server-info")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
return {}
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def upload_file_to_api(endpoint: str, file_data: bytes, filename: str) -> Tuple[Dict[str, Any], int]:
|
||||||
|
"""Загрузка файла на API"""
|
||||||
|
try:
|
||||||
|
# Определяем правильное имя поля в зависимости от эндпоинта
|
||||||
|
if "zip" in endpoint:
|
||||||
|
files = {"zip_file": (filename, file_data, "application/zip")}
|
||||||
|
else:
|
||||||
|
files = {"file": (filename, file_data, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
|
||||||
|
|
||||||
|
response = requests.post(f"{API_BASE_URL}{endpoint}", files=files)
|
||||||
|
return response.json(), response.status_code
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}, 500
|
||||||
|
|
||||||
|
|
||||||
|
def make_api_request(endpoint: str, data: Dict[str, Any]) -> Tuple[Dict[str, Any], int]:
|
||||||
|
"""Выполнение API запроса"""
|
||||||
|
try:
|
||||||
|
response = requests.post(f"{API_BASE_URL}{endpoint}", json=data)
|
||||||
|
return response.json(), response.status_code
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": str(e)}, 500
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_ogs(parser_name: str) -> List[str]:
|
||||||
|
"""Получение доступных ОГ для парсера"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{API_BASE_URL}/parsers/{parser_name}/available_ogs")
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return data.get("available_ogs", [])
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Ошибка получения ОГ: {response.status_code}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Ошибка при запросе ОГ: {e}")
|
||||||
|
return []
|
||||||
53
streamlit_app/config.py
Normal file
53
streamlit_app/config.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"""
|
||||||
|
Конфигурация приложения
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
|
# Конфигурация страницы
|
||||||
|
def setup_page_config():
|
||||||
|
"""Настройка конфигурации страницы Streamlit"""
|
||||||
|
st.set_page_config(
|
||||||
|
page_title="NIN Excel Parsers API Demo",
|
||||||
|
page_icon="📊",
|
||||||
|
layout="wide",
|
||||||
|
initial_sidebar_state="expanded"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Константы для парсеров
|
||||||
|
PARSER_TABS = [
|
||||||
|
"📊 Сводки ПМ",
|
||||||
|
"🏭 Сводки СА",
|
||||||
|
"⛽ Мониторинг топлива",
|
||||||
|
"🔧 Ремонт СА",
|
||||||
|
"📋 Статусы ремонта СА",
|
||||||
|
"⚡ Мониторинг ТЭР",
|
||||||
|
"🏭 Операционные справки"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Константы для ОГ
|
||||||
|
DEFAULT_OGS = [
|
||||||
|
"SNPZ", "KNPZ", "ANHK", "AchNPZ", "UNPZ", "UNH", "NOV",
|
||||||
|
"NovKuybNPZ", "KuybNPZ", "CyzNPZ", "TuapsNPZ", "RNPK",
|
||||||
|
"NVNPO", "KLNPZ", "PurNP", "YANOS"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Константы для кодов строк ПМ
|
||||||
|
PM_CODES = [78, 79, 394, 395, 396, 397, 81, 82, 83, 84]
|
||||||
|
|
||||||
|
# Константы для столбцов ПМ
|
||||||
|
PM_COLUMNS = ["БП", "ПП", "СЭБ", "Факт", "План"]
|
||||||
|
|
||||||
|
# Константы для режимов СА
|
||||||
|
CA_MODES = ["plan", "fact", "normativ"]
|
||||||
|
|
||||||
|
# Константы для таблиц СА
|
||||||
|
CA_TABLES = ["ТиП", "Топливо", "Потери"]
|
||||||
|
|
||||||
|
# Константы для столбцов мониторинга топлива
|
||||||
|
FUEL_COLUMNS = ["normativ", "total", "total_1"]
|
||||||
|
|
||||||
|
# Константы для типов ремонта
|
||||||
|
REPAIR_TYPES = ["КР", "КП", "ТР"]
|
||||||
|
|
||||||
|
# Константы для режимов мониторинга ТЭР
|
||||||
|
TAR_MODES = ["all", "total", "last_day"]
|
||||||
3
streamlit_app/parsers_ui/__init__.py
Normal file
3
streamlit_app/parsers_ui/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
UI модули для парсеров
|
||||||
|
"""
|
||||||
91
streamlit_app/parsers_ui/monitoring_fuel_ui.py
Normal file
91
streamlit_app/parsers_ui/monitoring_fuel_ui.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
"""
|
||||||
|
UI модуль для мониторинга топлива
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
from api_client import upload_file_to_api, make_api_request
|
||||||
|
from config import FUEL_COLUMNS
|
||||||
|
|
||||||
|
|
||||||
|
def render_monitoring_fuel_tab():
|
||||||
|
"""Рендер вкладки мониторинга топлива"""
|
||||||
|
st.header("⛽ Мониторинг топлива - Полный функционал")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_fuel = st.file_uploader(
|
||||||
|
"Выберите ZIP архив с мониторингом топлива",
|
||||||
|
type=['zip'],
|
||||||
|
key="fuel_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_fuel is not None:
|
||||||
|
if st.button("📤 Загрузить мониторинг топлива", key="upload_fuel_btn"):
|
||||||
|
with st.spinner("Загружаю файл..."):
|
||||||
|
result, status = upload_file_to_api(
|
||||||
|
"/monitoring_fuel/upload-zip",
|
||||||
|
uploaded_fuel.read(),
|
||||||
|
uploaded_fuel.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
||||||
|
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("🔍 Получение данных")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Агрегация по колонкам")
|
||||||
|
|
||||||
|
columns_fuel = st.multiselect(
|
||||||
|
"Выберите столбцы",
|
||||||
|
FUEL_COLUMNS,
|
||||||
|
default=["normativ", "total"],
|
||||||
|
key="fuel_columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить агрегированные данные", key="fuel_total_btn"):
|
||||||
|
if columns_fuel:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"columns": columns_fuel
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/monitoring_fuel/get_total_by_columns", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите столбцы")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("Данные за месяц")
|
||||||
|
|
||||||
|
month = st.selectbox(
|
||||||
|
"Выберите месяц",
|
||||||
|
[f"{i:02d}" for i in range(1, 13)],
|
||||||
|
key="fuel_month"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить данные за месяц", key="fuel_month_btn"):
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"month": month
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/monitoring_fuel/get_month_by_code", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
108
streamlit_app/parsers_ui/monitoring_tar_ui.py
Normal file
108
streamlit_app/parsers_ui/monitoring_tar_ui.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
"""
|
||||||
|
UI модуль для мониторинга ТЭР
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
import json
|
||||||
|
from api_client import upload_file_to_api, make_api_request
|
||||||
|
from config import TAR_MODES
|
||||||
|
|
||||||
|
|
||||||
|
def render_monitoring_tar_tab():
|
||||||
|
"""Рендер вкладки мониторинга ТЭР"""
|
||||||
|
st.header("⚡ Мониторинг ТЭР (Топливно-энергетических ресурсов)")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_file = st.file_uploader(
|
||||||
|
"Выберите ZIP архив с файлами мониторинга ТЭР",
|
||||||
|
type=['zip'],
|
||||||
|
key="monitoring_tar_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_file is not None:
|
||||||
|
if st.button("📤 Загрузить файл", key="monitoring_tar_upload_btn"):
|
||||||
|
with st.spinner("Загружаем файл..."):
|
||||||
|
file_data = uploaded_file.read()
|
||||||
|
result, status_code = upload_file_to_api("/monitoring_tar/upload", file_data, uploaded_file.name)
|
||||||
|
|
||||||
|
if status_code == 200:
|
||||||
|
st.success("✅ Файл успешно загружен!")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка загрузки: {result}")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("📊 Получение данных")
|
||||||
|
|
||||||
|
# Выбор формата отображения
|
||||||
|
display_format = st.radio(
|
||||||
|
"Формат отображения:",
|
||||||
|
["JSON", "Таблица"],
|
||||||
|
key="monitoring_tar_display_format",
|
||||||
|
horizontal=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Выбор режима данных
|
||||||
|
mode = st.selectbox(
|
||||||
|
"Выберите режим данных:",
|
||||||
|
TAR_MODES,
|
||||||
|
help="total - строки 'Всего' (агрегированные данные), last_day - последние строки данных, all - все данные",
|
||||||
|
key="monitoring_tar_mode"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("📊 Получить данные", key="monitoring_tar_get_data_btn"):
|
||||||
|
with st.spinner("Получаем данные..."):
|
||||||
|
# Выбираем эндпоинт в зависимости от режима
|
||||||
|
if mode == "all":
|
||||||
|
# Используем полный эндпоинт
|
||||||
|
result, status_code = make_api_request("/monitoring_tar/get_full_data", {})
|
||||||
|
else:
|
||||||
|
# Используем фильтрованный эндпоинт
|
||||||
|
request_data = {"mode": mode}
|
||||||
|
result, status_code = make_api_request("/monitoring_tar/get_data", request_data)
|
||||||
|
|
||||||
|
if status_code == 200 and result.get("success"):
|
||||||
|
st.success("✅ Данные успешно получены!")
|
||||||
|
|
||||||
|
# Показываем данные
|
||||||
|
data = result.get("data", {}).get("value", {})
|
||||||
|
if data:
|
||||||
|
st.subheader("📋 Результат:")
|
||||||
|
|
||||||
|
# Парсим данные, если они пришли как строка
|
||||||
|
if isinstance(data, str):
|
||||||
|
try:
|
||||||
|
data = json.loads(data)
|
||||||
|
st.write("✅ JSON успешно распарсен")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
st.error(f"❌ Ошибка при парсинге JSON данных: {e}")
|
||||||
|
st.write("Сырые данные:", data)
|
||||||
|
return
|
||||||
|
|
||||||
|
if display_format == "JSON":
|
||||||
|
# Отображаем как JSON
|
||||||
|
st.json(data)
|
||||||
|
else:
|
||||||
|
# Отображаем как таблицы
|
||||||
|
if isinstance(data, dict):
|
||||||
|
# Показываем данные по установкам
|
||||||
|
for installation_id, installation_data in data.items():
|
||||||
|
with st.expander(f"🏭 {installation_id}"):
|
||||||
|
if isinstance(installation_data, dict):
|
||||||
|
# Показываем структуру данных
|
||||||
|
for data_type, type_data in installation_data.items():
|
||||||
|
st.write(f"**{data_type}:**")
|
||||||
|
if isinstance(type_data, list) and type_data:
|
||||||
|
df = pd.DataFrame(type_data)
|
||||||
|
st.dataframe(df)
|
||||||
|
else:
|
||||||
|
st.write("Нет данных")
|
||||||
|
else:
|
||||||
|
st.write("Нет данных")
|
||||||
|
else:
|
||||||
|
st.json(data)
|
||||||
|
else:
|
||||||
|
st.info("📋 Нет данных для отображения")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
84
streamlit_app/parsers_ui/oper_spravka_tech_pos_ui.py
Normal file
84
streamlit_app/parsers_ui/oper_spravka_tech_pos_ui.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""
|
||||||
|
UI модуль для операционных справок технологических позиций
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
from api_client import upload_file_to_api, make_api_request, get_available_ogs
|
||||||
|
|
||||||
|
|
||||||
|
def render_oper_spravka_tech_pos_tab():
|
||||||
|
"""Рендер вкладки операционных справок технологических позиций"""
|
||||||
|
st.header("🏭 Операционные справки технологических позиций")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
|
||||||
|
uploaded_file = st.file_uploader(
|
||||||
|
"Выберите ZIP архив с файлами операционных справок",
|
||||||
|
type=['zip'],
|
||||||
|
key="oper_spravka_tech_pos_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_file is not None:
|
||||||
|
if st.button("📤 Загрузить файл", key="oper_spravka_tech_pos_upload_btn"):
|
||||||
|
with st.spinner("Загружаем файл..."):
|
||||||
|
file_data = uploaded_file.read()
|
||||||
|
result, status_code = upload_file_to_api("/oper_spravka_tech_pos/upload", file_data, uploaded_file.name)
|
||||||
|
|
||||||
|
if status_code == 200:
|
||||||
|
st.success("✅ Файл успешно загружен!")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка загрузки: {result}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("📊 Получение данных")
|
||||||
|
|
||||||
|
# Выбор формата отображения
|
||||||
|
display_format = st.radio(
|
||||||
|
"Формат отображения:",
|
||||||
|
["JSON", "Таблица"],
|
||||||
|
key="oper_spravka_tech_pos_display_format",
|
||||||
|
horizontal=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Получаем доступные ОГ динамически
|
||||||
|
available_ogs = get_available_ogs("oper_spravka_tech_pos")
|
||||||
|
|
||||||
|
# Выбор ОГ
|
||||||
|
og_id = st.selectbox(
|
||||||
|
"Выберите ОГ:",
|
||||||
|
available_ogs if available_ogs else ["SNPZ", "KNPZ", "ANHK", "BASH", "UNH", "NOV"],
|
||||||
|
key="oper_spravka_tech_pos_og_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("📊 Получить данные", key="oper_spravka_tech_pos_get_data_btn"):
|
||||||
|
with st.spinner("Получаем данные..."):
|
||||||
|
request_data = {"id": og_id}
|
||||||
|
result, status_code = make_api_request("/oper_spravka_tech_pos/get_data", request_data)
|
||||||
|
|
||||||
|
if status_code == 200 and result.get("success"):
|
||||||
|
st.success("✅ Данные успешно получены!")
|
||||||
|
|
||||||
|
# Показываем данные
|
||||||
|
data = result.get("data", [])
|
||||||
|
|
||||||
|
if data and len(data) > 0:
|
||||||
|
st.subheader("📋 Результат:")
|
||||||
|
|
||||||
|
if display_format == "JSON":
|
||||||
|
# Отображаем как JSON
|
||||||
|
st.json(data)
|
||||||
|
else:
|
||||||
|
# Отображаем как таблицу
|
||||||
|
if isinstance(data, list) and data:
|
||||||
|
df = pd.DataFrame(data)
|
||||||
|
st.dataframe(df, use_container_width=True)
|
||||||
|
else:
|
||||||
|
st.write("Нет данных")
|
||||||
|
else:
|
||||||
|
st.info("📋 Нет данных для отображения")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
146
streamlit_app/parsers_ui/statuses_repair_ca_ui.py
Normal file
146
streamlit_app/parsers_ui/statuses_repair_ca_ui.py
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
"""
|
||||||
|
UI модуль для статусов ремонта СА
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
from api_client import upload_file_to_api, make_api_request, get_available_ogs
|
||||||
|
|
||||||
|
|
||||||
|
def render_statuses_repair_ca_tab():
|
||||||
|
"""Рендер вкладки статусов ремонта СА"""
|
||||||
|
st.header("📋 Статусы ремонта СА")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_file = st.file_uploader(
|
||||||
|
"Выберите файл статусов ремонта СА",
|
||||||
|
type=['xlsx', 'xlsm', 'xls', 'zip'],
|
||||||
|
key="statuses_repair_ca_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_file is not None:
|
||||||
|
if st.button("📤 Загрузить файл", key="statuses_repair_ca_upload_btn"):
|
||||||
|
with st.spinner("Загружаем файл..."):
|
||||||
|
file_data = uploaded_file.read()
|
||||||
|
result, status_code = upload_file_to_api("/statuses_repair_ca/upload", file_data, uploaded_file.name)
|
||||||
|
|
||||||
|
if status_code == 200:
|
||||||
|
st.success("✅ Файл успешно загружен!")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка загрузки: {result}")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("📊 Получение данных")
|
||||||
|
|
||||||
|
# Получаем доступные ОГ динамически
|
||||||
|
available_ogs = get_available_ogs("statuses_repair_ca")
|
||||||
|
|
||||||
|
# Фильтр по ОГ
|
||||||
|
og_ids = st.multiselect(
|
||||||
|
"Выберите ОГ (оставьте пустым для всех)",
|
||||||
|
available_ogs if available_ogs else ["KNPZ", "ANHK", "SNPZ", "BASH", "UNH", "NOV"], # fallback
|
||||||
|
key="statuses_repair_ca_og_ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Предустановленные ключи для извлечения
|
||||||
|
st.subheader("🔑 Ключи для извлечения данных")
|
||||||
|
|
||||||
|
# Основные ключи
|
||||||
|
include_basic_keys = st.checkbox("Основные данные", value=True, key="statuses_basic_keys")
|
||||||
|
include_readiness_keys = st.checkbox("Готовность к КР", value=True, key="statuses_readiness_keys")
|
||||||
|
include_contract_keys = st.checkbox("Заключение договоров", value=True, key="statuses_contract_keys")
|
||||||
|
include_supply_keys = st.checkbox("Поставка МТР", value=True, key="statuses_supply_keys")
|
||||||
|
|
||||||
|
# Формируем ключи на основе выбора
|
||||||
|
keys = []
|
||||||
|
if include_basic_keys:
|
||||||
|
keys.append(["Дата начала ремонта"])
|
||||||
|
keys.append(["Отставание / опережение подготовки к КР", "Отставание / опережение"])
|
||||||
|
keys.append(["Отставание / опережение подготовки к КР", "Динамика за прошедшую неделю"])
|
||||||
|
|
||||||
|
if include_readiness_keys:
|
||||||
|
keys.append(["Готовность к КР", "Факт"])
|
||||||
|
|
||||||
|
if include_contract_keys:
|
||||||
|
keys.append(["Заключение договоров на СМР", "Договор", "%"])
|
||||||
|
|
||||||
|
if include_supply_keys:
|
||||||
|
keys.append(["Поставка МТР", "На складе, позиций", "%"])
|
||||||
|
|
||||||
|
# Кнопка получения данных
|
||||||
|
if st.button("📊 Получить данные", key="statuses_repair_ca_get_data_btn"):
|
||||||
|
if not keys:
|
||||||
|
st.warning("⚠️ Выберите хотя бы одну группу ключей для извлечения")
|
||||||
|
else:
|
||||||
|
with st.spinner("Получаем данные..."):
|
||||||
|
request_data = {
|
||||||
|
"ids": og_ids if og_ids else None,
|
||||||
|
"keys": keys
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status_code = make_api_request("/statuses_repair_ca/get_data", request_data)
|
||||||
|
|
||||||
|
if status_code == 200 and result.get("success"):
|
||||||
|
st.success("✅ Данные успешно получены!")
|
||||||
|
|
||||||
|
data = result.get("data", {}).get("value", [])
|
||||||
|
if data:
|
||||||
|
# Отображаем данные в виде таблицы
|
||||||
|
if isinstance(data, list) and len(data) > 0:
|
||||||
|
# Преобразуем в DataFrame для лучшего отображения
|
||||||
|
df_data = []
|
||||||
|
for item in data:
|
||||||
|
row = {
|
||||||
|
"ID": item.get("id", ""),
|
||||||
|
"Название": item.get("name", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавляем основные поля
|
||||||
|
if "Дата начала ремонта" in item:
|
||||||
|
row["Дата начала ремонта"] = item["Дата начала ремонта"]
|
||||||
|
|
||||||
|
# Добавляем готовность к КР
|
||||||
|
if "Готовность к КР" in item:
|
||||||
|
readiness = item["Готовность к КР"]
|
||||||
|
if isinstance(readiness, dict) and "Факт" in readiness:
|
||||||
|
row["Готовность к КР (Факт)"] = readiness["Факт"]
|
||||||
|
|
||||||
|
# Добавляем отставание/опережение
|
||||||
|
if "Отставание / опережение подготовки к КР" in item:
|
||||||
|
delay = item["Отставание / опережение подготовки к КР"]
|
||||||
|
if isinstance(delay, dict):
|
||||||
|
if "Отставание / опережение" in delay:
|
||||||
|
row["Отставание/опережение"] = delay["Отставание / опережение"]
|
||||||
|
if "Динамика за прошедшую неделю" in delay:
|
||||||
|
row["Динамика за неделю"] = delay["Динамика за прошедшую неделю"]
|
||||||
|
|
||||||
|
# Добавляем договоры
|
||||||
|
if "Заключение договоров на СМР" in item:
|
||||||
|
contracts = item["Заключение договоров на СМР"]
|
||||||
|
if isinstance(contracts, dict) and "Договор" in contracts:
|
||||||
|
contract = contracts["Договор"]
|
||||||
|
if isinstance(contract, dict) and "%" in contract:
|
||||||
|
row["Договоры (%)"] = contract["%"]
|
||||||
|
|
||||||
|
# Добавляем поставки МТР
|
||||||
|
if "Поставка МТР" in item:
|
||||||
|
supply = item["Поставка МТР"]
|
||||||
|
if isinstance(supply, dict) and "На складе, позиций" in supply:
|
||||||
|
warehouse = supply["На складе, позиций"]
|
||||||
|
if isinstance(warehouse, dict) and "%" in warehouse:
|
||||||
|
row["МТР на складе (%)"] = warehouse["%"]
|
||||||
|
|
||||||
|
df_data.append(row)
|
||||||
|
|
||||||
|
if df_data:
|
||||||
|
df = pd.DataFrame(df_data)
|
||||||
|
st.dataframe(df, use_container_width=True)
|
||||||
|
else:
|
||||||
|
st.info("📋 Нет данных для отображения")
|
||||||
|
else:
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.info("📋 Нет данных для отображения")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
80
streamlit_app/parsers_ui/svodka_ca_ui.py
Normal file
80
streamlit_app/parsers_ui/svodka_ca_ui.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"""
|
||||||
|
UI модуль для парсера сводок СА
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
import requests
|
||||||
|
from api_client import make_api_request, API_BASE_URL
|
||||||
|
from config import CA_MODES, CA_TABLES
|
||||||
|
|
||||||
|
|
||||||
|
def render_svodka_ca_tab():
|
||||||
|
"""Рендер вкладки сводок СА"""
|
||||||
|
st.header("🏭 Сводки СА - Полный функционал")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_ca = st.file_uploader(
|
||||||
|
"Выберите Excel файл сводки СА",
|
||||||
|
type=['xlsx', 'xlsm', 'xls'],
|
||||||
|
key="ca_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_ca is not None:
|
||||||
|
if st.button("📤 Загрузить сводку СА", key="upload_ca_btn"):
|
||||||
|
with st.spinner("Загружаю файл..."):
|
||||||
|
try:
|
||||||
|
files = {"file": (uploaded_ca.name, uploaded_ca.read(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
|
||||||
|
response = requests.post(f"{API_BASE_URL}/svodka_ca/upload", files=files)
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
||||||
|
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"❌ Ошибка: {str(e)}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("🔍 Получение данных")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Параметры запроса")
|
||||||
|
|
||||||
|
modes = st.multiselect(
|
||||||
|
"Выберите режимы",
|
||||||
|
CA_MODES,
|
||||||
|
default=["plan", "fact"],
|
||||||
|
key="ca_modes"
|
||||||
|
)
|
||||||
|
|
||||||
|
tables = st.multiselect(
|
||||||
|
"Выберите таблицы",
|
||||||
|
CA_TABLES,
|
||||||
|
default=["ТиП", "Топливо"],
|
||||||
|
key="ca_tables"
|
||||||
|
)
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("Результат")
|
||||||
|
if st.button("🔍 Получить данные СА", key="ca_btn"):
|
||||||
|
if modes and tables:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"modes": modes,
|
||||||
|
"tables": tables
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/svodka_ca/get_data", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите режимы и таблицы")
|
||||||
118
streamlit_app/parsers_ui/svodka_pm_ui.py
Normal file
118
streamlit_app/parsers_ui/svodka_pm_ui.py
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
"""
|
||||||
|
UI модуль для парсера сводок ПМ
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
from api_client import upload_file_to_api, make_api_request
|
||||||
|
from config import PM_CODES, PM_COLUMNS, DEFAULT_OGS
|
||||||
|
|
||||||
|
|
||||||
|
def render_svodka_pm_tab():
|
||||||
|
"""Рендер вкладки сводок ПМ"""
|
||||||
|
st.header("📊 Сводки ПМ - Полный функционал")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
uploaded_pm = st.file_uploader(
|
||||||
|
"Выберите ZIP архив со сводками ПМ",
|
||||||
|
type=['zip'],
|
||||||
|
key="pm_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_pm is not None:
|
||||||
|
if st.button("📤 Загрузить сводки ПМ", key="upload_pm_btn"):
|
||||||
|
with st.spinner("Загружаю файл..."):
|
||||||
|
result, status = upload_file_to_api(
|
||||||
|
"/svodka_pm/upload-zip",
|
||||||
|
uploaded_pm.read(),
|
||||||
|
uploaded_pm.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
||||||
|
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("🔍 Получение данных")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Данные по одному ОГ")
|
||||||
|
|
||||||
|
og_id = st.selectbox(
|
||||||
|
"Выберите ОГ",
|
||||||
|
DEFAULT_OGS,
|
||||||
|
key="pm_single_og"
|
||||||
|
)
|
||||||
|
|
||||||
|
codes = st.multiselect(
|
||||||
|
"Выберите коды строк",
|
||||||
|
PM_CODES,
|
||||||
|
default=[78, 79],
|
||||||
|
key="pm_single_codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
columns = st.multiselect(
|
||||||
|
"Выберите столбцы",
|
||||||
|
PM_COLUMNS,
|
||||||
|
default=["БП", "ПП"],
|
||||||
|
key="pm_single_columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить данные по ОГ", key="pm_single_btn"):
|
||||||
|
if codes and columns:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"id": og_id,
|
||||||
|
"codes": codes,
|
||||||
|
"columns": columns
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/svodka_pm/get_single_og", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите коды и столбцы")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("Данные по всем ОГ")
|
||||||
|
|
||||||
|
codes_total = st.multiselect(
|
||||||
|
"Выберите коды строк",
|
||||||
|
PM_CODES,
|
||||||
|
default=[78, 79, 394, 395],
|
||||||
|
key="pm_total_codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
columns_total = st.multiselect(
|
||||||
|
"Выберите столбцы",
|
||||||
|
PM_COLUMNS,
|
||||||
|
default=["БП", "ПП", "СЭБ"],
|
||||||
|
key="pm_total_columns"
|
||||||
|
)
|
||||||
|
|
||||||
|
if st.button("🔍 Получить данные по всем ОГ", key="pm_total_btn"):
|
||||||
|
if codes_total and columns_total:
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"codes": codes_total,
|
||||||
|
"columns": columns_total
|
||||||
|
}
|
||||||
|
|
||||||
|
result, status = make_api_request("/svodka_pm/get_total_ogs", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
else:
|
||||||
|
st.warning("⚠️ Выберите коды и столбцы")
|
||||||
110
streamlit_app/parsers_ui/svodka_repair_ca_ui.py
Normal file
110
streamlit_app/parsers_ui/svodka_repair_ca_ui.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
UI модуль для ремонта СА
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
import pandas as pd
|
||||||
|
from api_client import upload_file_to_api, make_api_request, get_available_ogs
|
||||||
|
from config import REPAIR_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
def render_svodka_repair_ca_tab():
|
||||||
|
"""Рендер вкладки ремонта СА"""
|
||||||
|
st.header("🔧 Ремонт СА - Управление ремонтными работами")
|
||||||
|
|
||||||
|
# Секция загрузки файлов
|
||||||
|
st.subheader("📤 Загрузка файлов")
|
||||||
|
|
||||||
|
uploaded_file = st.file_uploader(
|
||||||
|
"Выберите Excel файл или ZIP архив с данными о ремонте СА",
|
||||||
|
type=['xlsx', 'xlsm', 'xls', 'zip'],
|
||||||
|
key="repair_ca_upload"
|
||||||
|
)
|
||||||
|
|
||||||
|
if uploaded_file is not None:
|
||||||
|
if st.button("📤 Загрузить файл", key="repair_ca_upload_btn"):
|
||||||
|
with st.spinner("Загружаю файл..."):
|
||||||
|
file_data = uploaded_file.read()
|
||||||
|
result, status = upload_file_to_api("/svodka_repair_ca/upload", file_data, uploaded_file.name)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Файл успешно загружен")
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка загрузки: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
|
# Секция получения данных
|
||||||
|
st.subheader("🔍 Получение данных")
|
||||||
|
|
||||||
|
col1, col2 = st.columns(2)
|
||||||
|
|
||||||
|
with col1:
|
||||||
|
st.subheader("Фильтры")
|
||||||
|
|
||||||
|
# Получаем доступные ОГ динамически
|
||||||
|
available_ogs = get_available_ogs("svodka_repair_ca")
|
||||||
|
|
||||||
|
# Фильтр по ОГ
|
||||||
|
og_ids = st.multiselect(
|
||||||
|
"Выберите ОГ (оставьте пустым для всех)",
|
||||||
|
available_ogs if available_ogs else ["KNPZ", "ANHK", "SNPZ", "BASH", "UNH", "NOV"], # fallback
|
||||||
|
key="repair_ca_og_ids"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Фильтр по типам ремонта
|
||||||
|
repair_types = st.multiselect(
|
||||||
|
"Выберите типы ремонта (оставьте пустым для всех)",
|
||||||
|
REPAIR_TYPES,
|
||||||
|
key="repair_ca_types"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Включение плановых/фактических данных
|
||||||
|
include_planned = st.checkbox("Включать плановые данные", value=True, key="repair_ca_planned")
|
||||||
|
include_factual = st.checkbox("Включать фактические данные", value=True, key="repair_ca_factual")
|
||||||
|
|
||||||
|
with col2:
|
||||||
|
st.subheader("Действия")
|
||||||
|
|
||||||
|
if st.button("🔍 Получить данные о ремонте", key="repair_ca_get_btn"):
|
||||||
|
with st.spinner("Получаю данные..."):
|
||||||
|
data = {
|
||||||
|
"include_planned": include_planned,
|
||||||
|
"include_factual": include_factual
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавляем фильтры только если они выбраны
|
||||||
|
if og_ids:
|
||||||
|
data["og_ids"] = og_ids
|
||||||
|
if repair_types:
|
||||||
|
data["repair_types"] = repair_types
|
||||||
|
|
||||||
|
result, status = make_api_request("/svodka_repair_ca/get_data", data)
|
||||||
|
|
||||||
|
if status == 200:
|
||||||
|
st.success("✅ Данные получены")
|
||||||
|
|
||||||
|
# Отображаем данные в виде таблицы, если возможно
|
||||||
|
if result.get("data") and isinstance(result["data"], list):
|
||||||
|
df_data = []
|
||||||
|
for item in result["data"]:
|
||||||
|
df_data.append({
|
||||||
|
"ID ОГ": item.get("id", ""),
|
||||||
|
"Наименование": item.get("name", ""),
|
||||||
|
"Тип ремонта": item.get("type", ""),
|
||||||
|
"Дата начала": item.get("start_date", ""),
|
||||||
|
"Дата окончания": item.get("end_date", ""),
|
||||||
|
"План": item.get("plan", ""),
|
||||||
|
"Факт": item.get("fact", ""),
|
||||||
|
"Простой": item.get("downtime", "")
|
||||||
|
})
|
||||||
|
|
||||||
|
if df_data:
|
||||||
|
df = pd.DataFrame(df_data)
|
||||||
|
st.dataframe(df, use_container_width=True)
|
||||||
|
else:
|
||||||
|
st.info("📋 Нет данных для отображения")
|
||||||
|
else:
|
||||||
|
st.json(result)
|
||||||
|
else:
|
||||||
|
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||||
53
streamlit_app/sidebar.py
Normal file
53
streamlit_app/sidebar.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"""
|
||||||
|
Модуль для сайдбара
|
||||||
|
"""
|
||||||
|
import streamlit as st
|
||||||
|
from api_client import get_server_info, get_available_parsers, API_PUBLIC_URL
|
||||||
|
|
||||||
|
|
||||||
|
def render_sidebar():
|
||||||
|
"""Рендер боковой панели"""
|
||||||
|
with st.sidebar:
|
||||||
|
st.header("ℹ️ Информация")
|
||||||
|
|
||||||
|
# Информация о сервере
|
||||||
|
server_info = get_server_info()
|
||||||
|
if server_info:
|
||||||
|
st.subheader("Сервер")
|
||||||
|
st.write(f"PID: {server_info.get('process_id', 'N/A')}")
|
||||||
|
st.write(f"CPU ядер: {server_info.get('cpu_cores', 'N/A')}")
|
||||||
|
st.write(f"Память: {server_info.get('memory_mb', 'N/A'):.1f} MB")
|
||||||
|
|
||||||
|
# Доступные парсеры
|
||||||
|
parsers = get_available_parsers()
|
||||||
|
if parsers:
|
||||||
|
st.subheader("Доступные парсеры")
|
||||||
|
for parser in parsers:
|
||||||
|
st.write(f"• {parser}")
|
||||||
|
|
||||||
|
|
||||||
|
def render_footer():
|
||||||
|
"""Рендер футера"""
|
||||||
|
st.markdown("---")
|
||||||
|
st.markdown("### 📚 Документация API")
|
||||||
|
st.markdown(f"Полная документация доступна по адресу: {API_PUBLIC_URL}/docs")
|
||||||
|
|
||||||
|
# Информация о проекте
|
||||||
|
with st.expander("ℹ️ О проекте"):
|
||||||
|
st.markdown("""
|
||||||
|
**NIN Excel Parsers API** - это веб-сервис для парсинга и обработки Excel-файлов нефтеперерабатывающих заводов.
|
||||||
|
|
||||||
|
**Возможности:**
|
||||||
|
- 📊 Парсинг сводок ПМ (план и факт)
|
||||||
|
- 🏭 Парсинг сводок СА
|
||||||
|
- ⛽ Мониторинг топлива
|
||||||
|
- ⚡ Мониторинг ТЭР (Топливно-энергетические ресурсы)
|
||||||
|
- 🔧 Управление ремонтными работами СА
|
||||||
|
- 📋 Мониторинг статусов ремонта СА
|
||||||
|
|
||||||
|
**Технологии:**
|
||||||
|
- FastAPI
|
||||||
|
- Pandas
|
||||||
|
- MinIO (S3-совместимое хранилище)
|
||||||
|
- Streamlit (веб-интерфейс)
|
||||||
|
""")
|
||||||
@@ -1,87 +1,17 @@
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
import requests
|
from config import setup_page_config, PARSER_TABS, API_PUBLIC_URL
|
||||||
import json
|
from api_client import check_api_health
|
||||||
import pandas as pd
|
from sidebar import render_sidebar, render_footer
|
||||||
import io
|
from parsers_ui.svodka_pm_ui import render_svodka_pm_tab
|
||||||
import zipfile
|
from parsers_ui.svodka_ca_ui import render_svodka_ca_tab
|
||||||
from typing import Dict, Any, List
|
from parsers_ui.monitoring_fuel_ui import render_monitoring_fuel_tab
|
||||||
import os
|
from parsers_ui.svodka_repair_ca_ui import render_svodka_repair_ca_tab
|
||||||
|
from parsers_ui.statuses_repair_ca_ui import render_statuses_repair_ca_tab
|
||||||
|
from parsers_ui.monitoring_tar_ui import render_monitoring_tar_tab
|
||||||
|
from parsers_ui.oper_spravka_tech_pos_ui import render_oper_spravka_tech_pos_tab
|
||||||
|
|
||||||
# Конфигурация страницы
|
# Конфигурация страницы
|
||||||
st.set_page_config(
|
setup_page_config()
|
||||||
page_title="NIN Excel Parsers API Demo",
|
|
||||||
page_icon="📊",
|
|
||||||
layout="wide",
|
|
||||||
initial_sidebar_state="expanded"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Конфигурация API
|
|
||||||
API_BASE_URL = os.getenv("API_BASE_URL", "http://fastapi:8000") # Внутренний адрес для Docker
|
|
||||||
API_PUBLIC_URL = os.getenv("API_PUBLIC_URL", "http://localhost:8000") # Внешний адрес для пользователя
|
|
||||||
|
|
||||||
def check_api_health():
|
|
||||||
"""Проверка доступности API"""
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{API_BASE_URL}/", timeout=5)
|
|
||||||
return response.status_code == 200
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def get_available_parsers():
|
|
||||||
"""Получение списка доступных парсеров"""
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{API_BASE_URL}/parsers")
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()["parsers"]
|
|
||||||
return []
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def get_server_info():
|
|
||||||
"""Получение информации о сервере"""
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{API_BASE_URL}/server-info")
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()
|
|
||||||
return {}
|
|
||||||
except:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def upload_file_to_api(endpoint: str, file_data: bytes, filename: str):
|
|
||||||
"""Загрузка файла на API"""
|
|
||||||
try:
|
|
||||||
# Определяем правильное имя поля в зависимости от эндпоинта
|
|
||||||
if "zip" in endpoint:
|
|
||||||
files = {"zip_file": (filename, file_data, "application/zip")}
|
|
||||||
else:
|
|
||||||
files = {"file": (filename, file_data, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
|
|
||||||
|
|
||||||
response = requests.post(f"{API_BASE_URL}{endpoint}", files=files)
|
|
||||||
return response.json(), response.status_code
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}, 500
|
|
||||||
|
|
||||||
def make_api_request(endpoint: str, data: Dict[str, Any]):
|
|
||||||
"""Выполнение API запроса"""
|
|
||||||
try:
|
|
||||||
response = requests.post(f"{API_BASE_URL}{endpoint}", json=data)
|
|
||||||
return response.json(), response.status_code
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}, 500
|
|
||||||
|
|
||||||
def get_available_ogs(parser_name: str) -> List[str]:
|
|
||||||
"""Получение доступных ОГ для парсера"""
|
|
||||||
try:
|
|
||||||
response = requests.get(f"{API_BASE_URL}/parsers/{parser_name}/available_ogs")
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
|
||||||
return data.get("available_ogs", [])
|
|
||||||
else:
|
|
||||||
print(f"⚠️ Ошибка получения ОГ: {response.status_code}")
|
|
||||||
return []
|
|
||||||
except Exception as e:
|
|
||||||
print(f"⚠️ Ошибка при запросе ОГ: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
st.title("🚀 NIN Excel Parsers API - Демонстрация")
|
st.title("🚀 NIN Excel Parsers API - Демонстрация")
|
||||||
@@ -89,759 +19,48 @@ def main():
|
|||||||
|
|
||||||
# Проверка доступности API
|
# Проверка доступности API
|
||||||
if not check_api_health():
|
if not check_api_health():
|
||||||
st.error(f"❌ API недоступен по адресу {API_BASE_URL}")
|
st.error(f"❌ API недоступен по адресу {API_PUBLIC_URL}")
|
||||||
st.info("Убедитесь, что FastAPI сервер запущен")
|
st.info("Убедитесь, что FastAPI сервер запущен")
|
||||||
return
|
return
|
||||||
|
|
||||||
st.success(f"✅ API доступен по адресу {API_PUBLIC_URL}")
|
st.success(f"✅ API доступен по адресу {API_PUBLIC_URL}")
|
||||||
|
|
||||||
# Боковая панель с информацией
|
# Боковая панель с информацией
|
||||||
with st.sidebar:
|
render_sidebar()
|
||||||
st.header("ℹ️ Информация")
|
|
||||||
|
|
||||||
# Информация о сервере
|
|
||||||
server_info = get_server_info()
|
|
||||||
if server_info:
|
|
||||||
st.subheader("Сервер")
|
|
||||||
st.write(f"PID: {server_info.get('process_id', 'N/A')}")
|
|
||||||
st.write(f"CPU ядер: {server_info.get('cpu_cores', 'N/A')}")
|
|
||||||
st.write(f"Память: {server_info.get('memory_mb', 'N/A'):.1f} MB")
|
|
||||||
|
|
||||||
# Доступные парсеры
|
|
||||||
parsers = get_available_parsers()
|
|
||||||
if parsers:
|
|
||||||
st.subheader("Доступные парсеры")
|
|
||||||
for parser in parsers:
|
|
||||||
st.write(f"• {parser}")
|
|
||||||
|
|
||||||
# Основные вкладки - по одной на каждый парсер
|
# Основные вкладки - по одной на каждый парсер
|
||||||
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([
|
tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs(PARSER_TABS)
|
||||||
"📊 Сводки ПМ",
|
|
||||||
"🏭 Сводки СА",
|
|
||||||
"⛽ Мониторинг топлива",
|
|
||||||
"🔧 Ремонт СА",
|
|
||||||
"📋 Статусы ремонта СА",
|
|
||||||
"⚡ Мониторинг ТЭР",
|
|
||||||
"🏭 Операционные справки"
|
|
||||||
])
|
|
||||||
|
|
||||||
# Вкладка 1: Сводки ПМ - полный функционал
|
# Вкладка 1: Сводки ПМ - полный функционал
|
||||||
with tab1:
|
with tab1:
|
||||||
st.header("📊 Сводки ПМ - Полный функционал")
|
render_svodka_pm_tab()
|
||||||
|
|
||||||
# Секция загрузки файлов
|
|
||||||
st.subheader("📤 Загрузка файлов")
|
|
||||||
uploaded_pm = st.file_uploader(
|
|
||||||
"Выберите ZIP архив со сводками ПМ",
|
|
||||||
type=['zip'],
|
|
||||||
key="pm_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
if uploaded_pm is not None:
|
|
||||||
if st.button("📤 Загрузить сводки ПМ", key="upload_pm_btn"):
|
|
||||||
with st.spinner("Загружаю файл..."):
|
|
||||||
result, status = upload_file_to_api(
|
|
||||||
"/svodka_pm/upload-zip",
|
|
||||||
uploaded_pm.read(),
|
|
||||||
uploaded_pm.name
|
|
||||||
)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
|
||||||
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
st.markdown("---")
|
|
||||||
|
|
||||||
# Секция получения данных
|
|
||||||
st.subheader("🔍 Получение данных")
|
|
||||||
|
|
||||||
col1, col2 = st.columns(2)
|
|
||||||
|
|
||||||
with col1:
|
|
||||||
st.subheader("Данные по одному ОГ")
|
|
||||||
|
|
||||||
og_id = st.selectbox(
|
|
||||||
"Выберите ОГ",
|
|
||||||
["SNPZ", "KNPZ", "ANHK", "AchNPZ", "UNPZ", "UNH", "NOV",
|
|
||||||
"NovKuybNPZ", "KuybNPZ", "CyzNPZ", "TuapsNPZ", "RNPK",
|
|
||||||
"NVNPO", "KLNPZ", "PurNP", "YANOS"],
|
|
||||||
key="pm_single_og"
|
|
||||||
)
|
|
||||||
|
|
||||||
codes = st.multiselect(
|
|
||||||
"Выберите коды строк",
|
|
||||||
[78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
|
|
||||||
default=[78, 79],
|
|
||||||
key="pm_single_codes"
|
|
||||||
)
|
|
||||||
|
|
||||||
columns = st.multiselect(
|
|
||||||
"Выберите столбцы",
|
|
||||||
["БП", "ПП", "СЭБ", "Факт", "План"],
|
|
||||||
default=["БП", "ПП"],
|
|
||||||
key="pm_single_columns"
|
|
||||||
)
|
|
||||||
|
|
||||||
if st.button("🔍 Получить данные по ОГ", key="pm_single_btn"):
|
|
||||||
if codes and columns:
|
|
||||||
with st.spinner("Получаю данные..."):
|
|
||||||
data = {
|
|
||||||
"id": og_id,
|
|
||||||
"codes": codes,
|
|
||||||
"columns": columns
|
|
||||||
}
|
|
||||||
|
|
||||||
result, status = make_api_request("/svodka_pm/get_single_og", data)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success("✅ Данные получены")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
else:
|
|
||||||
st.warning("⚠️ Выберите коды и столбцы")
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
st.subheader("Данные по всем ОГ")
|
|
||||||
|
|
||||||
codes_total = st.multiselect(
|
|
||||||
"Выберите коды строк",
|
|
||||||
[78, 79, 394, 395, 396, 397, 81, 82, 83, 84],
|
|
||||||
default=[78, 79, 394, 395],
|
|
||||||
key="pm_total_codes"
|
|
||||||
)
|
|
||||||
|
|
||||||
columns_total = st.multiselect(
|
|
||||||
"Выберите столбцы",
|
|
||||||
["БП", "ПП", "СЭБ", "Факт", "План"],
|
|
||||||
default=["БП", "ПП", "СЭБ"],
|
|
||||||
key="pm_total_columns"
|
|
||||||
)
|
|
||||||
|
|
||||||
if st.button("🔍 Получить данные по всем ОГ", key="pm_total_btn"):
|
|
||||||
if codes_total and columns_total:
|
|
||||||
with st.spinner("Получаю данные..."):
|
|
||||||
data = {
|
|
||||||
"codes": codes_total,
|
|
||||||
"columns": columns_total
|
|
||||||
}
|
|
||||||
|
|
||||||
result, status = make_api_request("/svodka_pm/get_total_ogs", data)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success("✅ Данные получены")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
else:
|
|
||||||
st.warning("⚠️ Выберите коды и столбцы")
|
|
||||||
|
|
||||||
# Вкладка 2: Сводки СА - полный функционал
|
# Вкладка 2: Сводки СА - полный функционал
|
||||||
with tab2:
|
with tab2:
|
||||||
st.header("🏭 Сводки СА - Полный функционал")
|
render_svodka_ca_tab()
|
||||||
|
|
||||||
# Секция загрузки файлов
|
|
||||||
st.subheader("📤 Загрузка файлов")
|
|
||||||
uploaded_ca = st.file_uploader(
|
|
||||||
"Выберите Excel файл сводки СА",
|
|
||||||
type=['xlsx', 'xlsm', 'xls'],
|
|
||||||
key="ca_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
if uploaded_ca is not None:
|
|
||||||
if st.button("📤 Загрузить сводку СА", key="upload_ca_btn"):
|
|
||||||
with st.spinner("Загружаю файл..."):
|
|
||||||
try:
|
|
||||||
files = {"file": (uploaded_ca.name, uploaded_ca.read(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")}
|
|
||||||
response = requests.post(f"{API_BASE_URL}/svodka_ca/upload", files=files)
|
|
||||||
result = response.json()
|
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
|
||||||
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
except Exception as e:
|
|
||||||
st.error(f"❌ Ошибка: {str(e)}")
|
|
||||||
|
|
||||||
st.markdown("---")
|
|
||||||
|
|
||||||
# Секция получения данных
|
|
||||||
st.subheader("🔍 Получение данных")
|
|
||||||
|
|
||||||
col1, col2 = st.columns(2)
|
|
||||||
|
|
||||||
with col1:
|
|
||||||
st.subheader("Параметры запроса")
|
|
||||||
|
|
||||||
modes = st.multiselect(
|
|
||||||
"Выберите режимы",
|
|
||||||
["plan", "fact", "normativ"],
|
|
||||||
default=["plan", "fact"],
|
|
||||||
key="ca_modes"
|
|
||||||
)
|
|
||||||
|
|
||||||
tables = st.multiselect(
|
|
||||||
"Выберите таблицы",
|
|
||||||
["ТиП", "Топливо", "Потери"],
|
|
||||||
default=["ТиП", "Топливо"],
|
|
||||||
key="ca_tables"
|
|
||||||
)
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
st.subheader("Результат")
|
|
||||||
if st.button("🔍 Получить данные СА", key="ca_btn"):
|
|
||||||
if modes and tables:
|
|
||||||
with st.spinner("Получаю данные..."):
|
|
||||||
data = {
|
|
||||||
"modes": modes,
|
|
||||||
"tables": tables
|
|
||||||
}
|
|
||||||
|
|
||||||
result, status = make_api_request("/svodka_ca/get_data", data)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success("✅ Данные получены")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
else:
|
|
||||||
st.warning("⚠️ Выберите режимы и таблицы")
|
|
||||||
|
|
||||||
# Вкладка 3: Мониторинг топлива - полный функционал
|
# Вкладка 3: Мониторинг топлива - полный функционал
|
||||||
with tab3:
|
with tab3:
|
||||||
st.header("⛽ Мониторинг топлива - Полный функционал")
|
render_monitoring_fuel_tab()
|
||||||
|
|
||||||
# Секция загрузки файлов
|
|
||||||
st.subheader("📤 Загрузка файлов")
|
|
||||||
uploaded_fuel = st.file_uploader(
|
|
||||||
"Выберите ZIP архив с мониторингом топлива",
|
|
||||||
type=['zip'],
|
|
||||||
key="fuel_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
if uploaded_fuel is not None:
|
|
||||||
if st.button("📤 Загрузить мониторинг топлива", key="upload_fuel_btn"):
|
|
||||||
with st.spinner("Загружаю файл..."):
|
|
||||||
result, status = upload_file_to_api(
|
|
||||||
"/monitoring_fuel/upload-zip",
|
|
||||||
uploaded_fuel.read(),
|
|
||||||
uploaded_fuel.name
|
|
||||||
)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success(f"✅ {result.get('message', 'Файл загружен')}")
|
|
||||||
st.info(f"ID объекта: {result.get('object_id', 'N/A')}")
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
st.markdown("---")
|
|
||||||
|
|
||||||
# Секция получения данных
|
|
||||||
st.subheader("🔍 Получение данных")
|
|
||||||
|
|
||||||
col1, col2 = st.columns(2)
|
|
||||||
|
|
||||||
with col1:
|
|
||||||
st.subheader("Агрегация по колонкам")
|
|
||||||
|
|
||||||
columns_fuel = st.multiselect(
|
|
||||||
"Выберите столбцы",
|
|
||||||
["normativ", "total", "total_1"],
|
|
||||||
default=["normativ", "total"],
|
|
||||||
key="fuel_columns"
|
|
||||||
)
|
|
||||||
|
|
||||||
if st.button("🔍 Получить агрегированные данные", key="fuel_total_btn"):
|
|
||||||
if columns_fuel:
|
|
||||||
with st.spinner("Получаю данные..."):
|
|
||||||
data = {
|
|
||||||
"columns": columns_fuel
|
|
||||||
}
|
|
||||||
|
|
||||||
result, status = make_api_request("/monitoring_fuel/get_total_by_columns", data)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success("✅ Данные получены")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
else:
|
|
||||||
st.warning("⚠️ Выберите столбцы")
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
st.subheader("Данные за месяц")
|
|
||||||
|
|
||||||
month = st.selectbox(
|
|
||||||
"Выберите месяц",
|
|
||||||
[f"{i:02d}" for i in range(1, 13)],
|
|
||||||
key="fuel_month"
|
|
||||||
)
|
|
||||||
|
|
||||||
if st.button("🔍 Получить данные за месяц", key="fuel_month_btn"):
|
|
||||||
with st.spinner("Получаю данные..."):
|
|
||||||
data = {
|
|
||||||
"month": month
|
|
||||||
}
|
|
||||||
|
|
||||||
result, status = make_api_request("/monitoring_fuel/get_month_by_code", data)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success("✅ Данные получены")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
# Вкладка 4: Ремонт СА
|
# Вкладка 4: Ремонт СА
|
||||||
with tab4:
|
with tab4:
|
||||||
st.header("🔧 Ремонт СА - Управление ремонтными работами")
|
render_svodka_repair_ca_tab()
|
||||||
|
|
||||||
# Секция загрузки файлов
|
|
||||||
st.subheader("📤 Загрузка файлов")
|
|
||||||
|
|
||||||
uploaded_file = st.file_uploader(
|
|
||||||
"Выберите Excel файл или ZIP архив с данными о ремонте СА",
|
|
||||||
type=['xlsx', 'xlsm', 'xls', 'zip'],
|
|
||||||
key="repair_ca_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
if uploaded_file is not None:
|
|
||||||
if st.button("📤 Загрузить файл", key="repair_ca_upload_btn"):
|
|
||||||
with st.spinner("Загружаю файл..."):
|
|
||||||
file_data = uploaded_file.read()
|
|
||||||
result, status = upload_file_to_api("/svodka_repair_ca/upload", file_data, uploaded_file.name)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success("✅ Файл успешно загружен")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка загрузки: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
st.markdown("---")
|
|
||||||
|
|
||||||
# Секция получения данных
|
|
||||||
st.subheader("🔍 Получение данных")
|
|
||||||
|
|
||||||
col1, col2 = st.columns(2)
|
|
||||||
|
|
||||||
with col1:
|
|
||||||
st.subheader("Фильтры")
|
|
||||||
|
|
||||||
# Получаем доступные ОГ динамически
|
|
||||||
available_ogs = get_available_ogs("svodka_repair_ca")
|
|
||||||
|
|
||||||
# Фильтр по ОГ
|
|
||||||
og_ids = st.multiselect(
|
|
||||||
"Выберите ОГ (оставьте пустым для всех)",
|
|
||||||
available_ogs if available_ogs else ["KNPZ", "ANHK", "SNPZ", "BASH", "UNH", "NOV"], # fallback
|
|
||||||
key="repair_ca_og_ids"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Фильтр по типам ремонта
|
|
||||||
repair_types = st.multiselect(
|
|
||||||
"Выберите типы ремонта (оставьте пустым для всех)",
|
|
||||||
["КР", "КП", "ТР"],
|
|
||||||
key="repair_ca_types"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Включение плановых/фактических данных
|
|
||||||
include_planned = st.checkbox("Включать плановые данные", value=True, key="repair_ca_planned")
|
|
||||||
include_factual = st.checkbox("Включать фактические данные", value=True, key="repair_ca_factual")
|
|
||||||
|
|
||||||
with col2:
|
|
||||||
st.subheader("Действия")
|
|
||||||
|
|
||||||
if st.button("🔍 Получить данные о ремонте", key="repair_ca_get_btn"):
|
|
||||||
with st.spinner("Получаю данные..."):
|
|
||||||
data = {
|
|
||||||
"include_planned": include_planned,
|
|
||||||
"include_factual": include_factual
|
|
||||||
}
|
|
||||||
|
|
||||||
# Добавляем фильтры только если они выбраны
|
|
||||||
if og_ids:
|
|
||||||
data["og_ids"] = og_ids
|
|
||||||
if repair_types:
|
|
||||||
data["repair_types"] = repair_types
|
|
||||||
|
|
||||||
result, status = make_api_request("/svodka_repair_ca/get_data", data)
|
|
||||||
|
|
||||||
if status == 200:
|
|
||||||
st.success("✅ Данные получены")
|
|
||||||
|
|
||||||
# Отображаем данные в виде таблицы, если возможно
|
|
||||||
if result.get("data") and isinstance(result["data"], list):
|
|
||||||
df_data = []
|
|
||||||
for item in result["data"]:
|
|
||||||
df_data.append({
|
|
||||||
"ID ОГ": item.get("id", ""),
|
|
||||||
"Наименование": item.get("name", ""),
|
|
||||||
"Тип ремонта": item.get("type", ""),
|
|
||||||
"Дата начала": item.get("start_date", ""),
|
|
||||||
"Дата окончания": item.get("end_date", ""),
|
|
||||||
"План": item.get("plan", ""),
|
|
||||||
"Факт": item.get("fact", ""),
|
|
||||||
"Простой": item.get("downtime", "")
|
|
||||||
})
|
|
||||||
|
|
||||||
if df_data:
|
|
||||||
df = pd.DataFrame(df_data)
|
|
||||||
st.dataframe(df, use_container_width=True)
|
|
||||||
else:
|
|
||||||
st.info("📋 Нет данных для отображения")
|
|
||||||
else:
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
# Вкладка 5: Статусы ремонта СА
|
# Вкладка 5: Статусы ремонта СА
|
||||||
with tab5:
|
with tab5:
|
||||||
st.header("📋 Статусы ремонта СА")
|
render_statuses_repair_ca_tab()
|
||||||
|
|
||||||
# Секция загрузки файлов
|
|
||||||
st.subheader("📤 Загрузка файлов")
|
|
||||||
uploaded_file = st.file_uploader(
|
|
||||||
"Выберите файл статусов ремонта СА",
|
|
||||||
type=['xlsx', 'xlsm', 'xls', 'zip'],
|
|
||||||
key="statuses_repair_ca_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
if uploaded_file is not None:
|
|
||||||
if st.button("📤 Загрузить файл", key="statuses_repair_ca_upload_btn"):
|
|
||||||
with st.spinner("Загружаем файл..."):
|
|
||||||
file_data = uploaded_file.read()
|
|
||||||
result, status_code = upload_file_to_api("/statuses_repair_ca/upload", file_data, uploaded_file.name)
|
|
||||||
|
|
||||||
if status_code == 200:
|
|
||||||
st.success("✅ Файл успешно загружен!")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка загрузки: {result}")
|
|
||||||
|
|
||||||
# Секция получения данных
|
|
||||||
st.subheader("📊 Получение данных")
|
|
||||||
|
|
||||||
# Получаем доступные ОГ динамически
|
|
||||||
available_ogs = get_available_ogs("statuses_repair_ca")
|
|
||||||
|
|
||||||
# Фильтр по ОГ
|
|
||||||
og_ids = st.multiselect(
|
|
||||||
"Выберите ОГ (оставьте пустым для всех)",
|
|
||||||
available_ogs if available_ogs else ["KNPZ", "ANHK", "SNPZ", "BASH", "UNH", "NOV"], # fallback
|
|
||||||
key="statuses_repair_ca_og_ids"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Предустановленные ключи для извлечения
|
|
||||||
st.subheader("🔑 Ключи для извлечения данных")
|
|
||||||
|
|
||||||
# Основные ключи
|
|
||||||
include_basic_keys = st.checkbox("Основные данные", value=True, key="statuses_basic_keys")
|
|
||||||
include_readiness_keys = st.checkbox("Готовность к КР", value=True, key="statuses_readiness_keys")
|
|
||||||
include_contract_keys = st.checkbox("Заключение договоров", value=True, key="statuses_contract_keys")
|
|
||||||
include_supply_keys = st.checkbox("Поставка МТР", value=True, key="statuses_supply_keys")
|
|
||||||
|
|
||||||
# Формируем ключи на основе выбора
|
|
||||||
keys = []
|
|
||||||
if include_basic_keys:
|
|
||||||
keys.append(["Дата начала ремонта"])
|
|
||||||
keys.append(["Отставание / опережение подготовки к КР", "Отставание / опережение"])
|
|
||||||
keys.append(["Отставание / опережение подготовки к КР", "Динамика за прошедшую неделю"])
|
|
||||||
|
|
||||||
if include_readiness_keys:
|
|
||||||
keys.append(["Готовность к КР", "Факт"])
|
|
||||||
|
|
||||||
if include_contract_keys:
|
|
||||||
keys.append(["Заключение договоров на СМР", "Договор", "%"])
|
|
||||||
|
|
||||||
if include_supply_keys:
|
|
||||||
keys.append(["Поставка МТР", "На складе, позиций", "%"])
|
|
||||||
|
|
||||||
# Кнопка получения данных
|
|
||||||
if st.button("📊 Получить данные", key="statuses_repair_ca_get_data_btn"):
|
|
||||||
if not keys:
|
|
||||||
st.warning("⚠️ Выберите хотя бы одну группу ключей для извлечения")
|
|
||||||
else:
|
|
||||||
with st.spinner("Получаем данные..."):
|
|
||||||
request_data = {
|
|
||||||
"ids": og_ids if og_ids else None,
|
|
||||||
"keys": keys
|
|
||||||
}
|
|
||||||
|
|
||||||
result, status_code = make_api_request("/statuses_repair_ca/get_data", request_data)
|
|
||||||
|
|
||||||
if status_code == 200 and result.get("success"):
|
|
||||||
st.success("✅ Данные успешно получены!")
|
|
||||||
|
|
||||||
data = result.get("data", {}).get("value", [])
|
|
||||||
if data:
|
|
||||||
# Отображаем данные в виде таблицы
|
|
||||||
if isinstance(data, list) and len(data) > 0:
|
|
||||||
# Преобразуем в DataFrame для лучшего отображения
|
|
||||||
df_data = []
|
|
||||||
for item in data:
|
|
||||||
row = {
|
|
||||||
"ID": item.get("id", ""),
|
|
||||||
"Название": item.get("name", ""),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Добавляем основные поля
|
|
||||||
if "Дата начала ремонта" in item:
|
|
||||||
row["Дата начала ремонта"] = item["Дата начала ремонта"]
|
|
||||||
|
|
||||||
# Добавляем готовность к КР
|
|
||||||
if "Готовность к КР" in item:
|
|
||||||
readiness = item["Готовность к КР"]
|
|
||||||
if isinstance(readiness, dict) and "Факт" in readiness:
|
|
||||||
row["Готовность к КР (Факт)"] = readiness["Факт"]
|
|
||||||
|
|
||||||
# Добавляем отставание/опережение
|
|
||||||
if "Отставание / опережение подготовки к КР" in item:
|
|
||||||
delay = item["Отставание / опережение подготовки к КР"]
|
|
||||||
if isinstance(delay, dict):
|
|
||||||
if "Отставание / опережение" in delay:
|
|
||||||
row["Отставание/опережение"] = delay["Отставание / опережение"]
|
|
||||||
if "Динамика за прошедшую неделю" in delay:
|
|
||||||
row["Динамика за неделю"] = delay["Динамика за прошедшую неделю"]
|
|
||||||
|
|
||||||
# Добавляем договоры
|
|
||||||
if "Заключение договоров на СМР" in item:
|
|
||||||
contracts = item["Заключение договоров на СМР"]
|
|
||||||
if isinstance(contracts, dict) and "Договор" in contracts:
|
|
||||||
contract = contracts["Договор"]
|
|
||||||
if isinstance(contract, dict) and "%" in contract:
|
|
||||||
row["Договоры (%)"] = contract["%"]
|
|
||||||
|
|
||||||
# Добавляем поставки МТР
|
|
||||||
if "Поставка МТР" in item:
|
|
||||||
supply = item["Поставка МТР"]
|
|
||||||
if isinstance(supply, dict) and "На складе, позиций" in supply:
|
|
||||||
warehouse = supply["На складе, позиций"]
|
|
||||||
if isinstance(warehouse, dict) and "%" in warehouse:
|
|
||||||
row["МТР на складе (%)"] = warehouse["%"]
|
|
||||||
|
|
||||||
df_data.append(row)
|
|
||||||
|
|
||||||
if df_data:
|
|
||||||
df = pd.DataFrame(df_data)
|
|
||||||
st.dataframe(df, use_container_width=True)
|
|
||||||
else:
|
|
||||||
st.info("📋 Нет данных для отображения")
|
|
||||||
else:
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.info("📋 Нет данных для отображения")
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
# Вкладка 6: Мониторинг ТЭР
|
# Вкладка 6: Мониторинг ТЭР
|
||||||
with tab6:
|
with tab6:
|
||||||
st.header("⚡ Мониторинг ТЭР (Топливно-энергетических ресурсов)")
|
render_monitoring_tar_tab()
|
||||||
|
|
||||||
# Секция загрузки файлов
|
|
||||||
st.subheader("📤 Загрузка файлов")
|
|
||||||
uploaded_file = st.file_uploader(
|
|
||||||
"Выберите ZIP архив с файлами мониторинга ТЭР",
|
|
||||||
type=['zip'],
|
|
||||||
key="monitoring_tar_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
if uploaded_file is not None:
|
|
||||||
if st.button("📤 Загрузить файл", key="monitoring_tar_upload_btn"):
|
|
||||||
with st.spinner("Загружаем файл..."):
|
|
||||||
file_data = uploaded_file.read()
|
|
||||||
result, status_code = upload_file_to_api("/monitoring_tar/upload", file_data, uploaded_file.name)
|
|
||||||
|
|
||||||
if status_code == 200:
|
|
||||||
st.success("✅ Файл успешно загружен!")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка загрузки: {result}")
|
|
||||||
|
|
||||||
# Секция получения данных
|
|
||||||
st.subheader("📊 Получение данных")
|
|
||||||
|
|
||||||
# Выбор формата отображения
|
|
||||||
display_format = st.radio(
|
|
||||||
"Формат отображения:",
|
|
||||||
["JSON", "Таблица"],
|
|
||||||
key="monitoring_tar_display_format",
|
|
||||||
horizontal=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Выбор режима данных
|
|
||||||
mode = st.selectbox(
|
|
||||||
"Выберите режим данных:",
|
|
||||||
["all", "total", "last_day"],
|
|
||||||
help="total - строки 'Всего' (агрегированные данные), last_day - последние строки данных, all - все данные",
|
|
||||||
key="monitoring_tar_mode"
|
|
||||||
)
|
|
||||||
|
|
||||||
if st.button("📊 Получить данные", key="monitoring_tar_get_data_btn"):
|
|
||||||
with st.spinner("Получаем данные..."):
|
|
||||||
# Выбираем эндпоинт в зависимости от режима
|
|
||||||
if mode == "all":
|
|
||||||
# Используем полный эндпоинт
|
|
||||||
result, status_code = make_api_request("/monitoring_tar/get_full_data", {})
|
|
||||||
else:
|
|
||||||
# Используем фильтрованный эндпоинт
|
|
||||||
request_data = {"mode": mode}
|
|
||||||
result, status_code = make_api_request("/monitoring_tar/get_data", request_data)
|
|
||||||
|
|
||||||
if status_code == 200 and result.get("success"):
|
|
||||||
st.success("✅ Данные успешно получены!")
|
|
||||||
|
|
||||||
# Показываем данные
|
|
||||||
data = result.get("data", {}).get("value", {})
|
|
||||||
if data:
|
|
||||||
st.subheader("📋 Результат:")
|
|
||||||
|
|
||||||
# # Отладочная информация
|
|
||||||
# st.write(f"🔍 Тип данных: {type(data)}")
|
|
||||||
# if isinstance(data, str):
|
|
||||||
# st.write(f"🔍 Длина строки: {len(data)}")
|
|
||||||
# st.write(f"🔍 Первые 200 символов: {data[:200]}...")
|
|
||||||
|
|
||||||
# Парсим данные, если они пришли как строка
|
|
||||||
if isinstance(data, str):
|
|
||||||
try:
|
|
||||||
import json
|
|
||||||
data = json.loads(data)
|
|
||||||
st.write("✅ JSON успешно распарсен")
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
st.error(f"❌ Ошибка при парсинге JSON данных: {e}")
|
|
||||||
st.write("Сырые данные:", data)
|
|
||||||
return
|
|
||||||
|
|
||||||
if display_format == "JSON":
|
|
||||||
# Отображаем как JSON
|
|
||||||
st.json(data)
|
|
||||||
else:
|
|
||||||
# Отображаем как таблицы
|
|
||||||
if isinstance(data, dict):
|
|
||||||
# Показываем данные по установкам
|
|
||||||
for installation_id, installation_data in data.items():
|
|
||||||
with st.expander(f"🏭 {installation_id}"):
|
|
||||||
if isinstance(installation_data, dict):
|
|
||||||
# Показываем структуру данных
|
|
||||||
for data_type, type_data in installation_data.items():
|
|
||||||
st.write(f"**{data_type}:**")
|
|
||||||
if isinstance(type_data, list) and type_data:
|
|
||||||
df = pd.DataFrame(type_data)
|
|
||||||
st.dataframe(df)
|
|
||||||
else:
|
|
||||||
st.write("Нет данных")
|
|
||||||
else:
|
|
||||||
st.write("Нет данных")
|
|
||||||
else:
|
|
||||||
st.json(data)
|
|
||||||
else:
|
|
||||||
st.info("📋 Нет данных для отображения")
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
# Вкладка 7: Операционные справки технологических позиций
|
# Вкладка 7: Операционные справки технологических позиций
|
||||||
with tab7:
|
with tab7:
|
||||||
st.header("🏭 Операционные справки технологических позиций")
|
render_oper_spravka_tech_pos_tab()
|
||||||
|
|
||||||
# Секция загрузки файлов
|
|
||||||
st.subheader("📤 Загрузка файлов")
|
|
||||||
|
|
||||||
uploaded_file = st.file_uploader(
|
|
||||||
"Выберите ZIP архив с файлами операционных справок",
|
|
||||||
type=['zip'],
|
|
||||||
key="oper_spravka_tech_pos_upload"
|
|
||||||
)
|
|
||||||
|
|
||||||
if uploaded_file is not None:
|
|
||||||
if st.button("📤 Загрузить файл", key="oper_spravka_tech_pos_upload_btn"):
|
|
||||||
with st.spinner("Загружаем файл..."):
|
|
||||||
file_data = uploaded_file.read()
|
|
||||||
result, status_code = upload_file_to_api("/oper_spravka_tech_pos/upload", file_data, uploaded_file.name)
|
|
||||||
|
|
||||||
if status_code == 200:
|
|
||||||
st.success("✅ Файл успешно загружен!")
|
|
||||||
st.json(result)
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка загрузки: {result}")
|
|
||||||
|
|
||||||
st.markdown("---")
|
|
||||||
|
|
||||||
# Секция получения данных
|
|
||||||
st.subheader("📊 Получение данных")
|
|
||||||
|
|
||||||
# Выбор формата отображения
|
|
||||||
display_format = st.radio(
|
|
||||||
"Формат отображения:",
|
|
||||||
["JSON", "Таблица"],
|
|
||||||
key="oper_spravka_tech_pos_display_format",
|
|
||||||
horizontal=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Получаем доступные ОГ динамически
|
|
||||||
available_ogs = get_available_ogs("oper_spravka_tech_pos")
|
|
||||||
|
|
||||||
# Выбор ОГ
|
|
||||||
og_id = st.selectbox(
|
|
||||||
"Выберите ОГ:",
|
|
||||||
available_ogs if available_ogs else ["SNPZ", "KNPZ", "ANHK", "BASH", "UNH", "NOV"],
|
|
||||||
key="oper_spravka_tech_pos_og_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
if st.button("📊 Получить данные", key="oper_spravka_tech_pos_get_data_btn"):
|
|
||||||
with st.spinner("Получаем данные..."):
|
|
||||||
request_data = {"id": og_id}
|
|
||||||
result, status_code = make_api_request("/oper_spravka_tech_pos/get_data", request_data)
|
|
||||||
|
|
||||||
if status_code == 200 and result.get("success"):
|
|
||||||
st.success("✅ Данные успешно получены!")
|
|
||||||
|
|
||||||
# Показываем данные
|
|
||||||
data = result.get("data", [])
|
|
||||||
|
|
||||||
if data and len(data) > 0:
|
|
||||||
st.subheader("📋 Результат:")
|
|
||||||
|
|
||||||
if display_format == "JSON":
|
|
||||||
# Отображаем как JSON
|
|
||||||
st.json(data)
|
|
||||||
else:
|
|
||||||
# Отображаем как таблицу
|
|
||||||
if isinstance(data, list) and data:
|
|
||||||
df = pd.DataFrame(data)
|
|
||||||
st.dataframe(df, use_container_width=True)
|
|
||||||
else:
|
|
||||||
st.write("Нет данных")
|
|
||||||
else:
|
|
||||||
st.info("📋 Нет данных для отображения")
|
|
||||||
else:
|
|
||||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
|
||||||
|
|
||||||
# Футер
|
# Футер
|
||||||
st.markdown("---")
|
render_footer()
|
||||||
st.markdown("### 📚 Документация API")
|
|
||||||
st.markdown(f"Полная документация доступна по адресу: {API_PUBLIC_URL}/docs")
|
|
||||||
|
|
||||||
# Информация о проекте
|
|
||||||
with st.expander("ℹ️ О проекте"):
|
|
||||||
st.markdown("""
|
|
||||||
**NIN Excel Parsers API** - это веб-сервис для парсинга и обработки Excel-файлов нефтеперерабатывающих заводов.
|
|
||||||
|
|
||||||
**Возможности:**
|
|
||||||
- 📊 Парсинг сводок ПМ (план и факт)
|
|
||||||
- 🏭 Парсинг сводок СА
|
|
||||||
- ⛽ Мониторинг топлива
|
|
||||||
- ⚡ Мониторинг ТЭР (Топливно-энергетические ресурсы)
|
|
||||||
- 🔧 Управление ремонтными работами СА
|
|
||||||
- 📋 Мониторинг статусов ремонта СА
|
|
||||||
|
|
||||||
**Технологии:**
|
|
||||||
- FastAPI
|
|
||||||
- Pandas
|
|
||||||
- MinIO (S3-совместимое хранилище)
|
|
||||||
- Streamlit (веб-интерфейс)
|
|
||||||
""")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user