import streamlit as st import requests import json import pandas as pd import io import zipfile from typing import Dict, Any 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: files = {"zip_file": (filename, file_data, "application/zip")} 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 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 = 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', 'Неизвестная ошибка')}") # Футер 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()