520 lines
22 KiB
Python
520 lines
22 KiB
Python
import streamlit as st
|
||
import requests
|
||
import json
|
||
import pandas as pd
|
||
import io
|
||
import zipfile
|
||
from typing import Dict, Any, List
|
||
import os
|
||
|
||
# Конфигурация страницы
|
||
st.set_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():
|
||
st.title("🚀 NIN Excel Parsers API - Демонстрация")
|
||
st.markdown("---")
|
||
|
||
# Проверка доступности API
|
||
if not check_api_health():
|
||
st.error(f"❌ API недоступен по адресу {API_BASE_URL}")
|
||
st.info("Убедитесь, что FastAPI сервер запущен")
|
||
return
|
||
|
||
st.success(f"✅ API доступен по адресу {API_PUBLIC_URL}")
|
||
|
||
# Боковая панель с информацией
|
||
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}")
|
||
|
||
# Основные вкладки - по одной на каждый парсер
|
||
tab1, tab2, tab3, tab4 = st.tabs([
|
||
"📊 Сводки ПМ",
|
||
"🏭 Сводки СА",
|
||
"⛽ Мониторинг топлива",
|
||
"🔧 Ремонт СА"
|
||
])
|
||
|
||
# Вкладка 1: Сводки ПМ - полный функционал
|
||
with tab1:
|
||
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(
|
||
"Выберите ОГ",
|
||
["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: Сводки СА - полный функционал
|
||
with tab2:
|
||
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(
|
||
"Выберите режимы",
|
||
["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: Мониторинг топлива - полный функционал
|
||
with tab3:
|
||
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(
|
||
"Выберите столбцы",
|
||
["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: Ремонт СА
|
||
with tab4:
|
||
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(
|
||
"Выберите типы ремонта (оставьте пустым для всех)",
|
||
["КР", "КП", "ТР"],
|
||
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', 'Неизвестная ошибка')}")
|
||
|
||
# Футер
|
||
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 (веб-интерфейс)
|
||
""")
|
||
|
||
if __name__ == "__main__":
|
||
main() |