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, tab5, tab6, tab7 = 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', 'Неизвестная ошибка')}") # Вкладка 5: Статусы ремонта СА with tab5: 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', 'Неизвестная ошибка')}") # Вкладка 6: Мониторинг ТЭР with tab6: 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( "Выберите режим данных:", ["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: Операционные справки технологических позиций with tab7: 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', 'Неизвестная ошибка')}") # Футер 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()