ch
This commit is contained in:
31
streamlit_app/.dockerignore
Normal file
31
streamlit_app/.dockerignore
Normal file
@@ -0,0 +1,31 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.tox
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.log
|
||||
.git
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
.hypothesis
|
||||
.DS_Store
|
||||
.env
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
23
streamlit_app/Dockerfile
Normal file
23
streamlit_app/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Устанавливаем системные зависимости
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Копируем файлы зависимостей
|
||||
COPY requirements.txt .
|
||||
|
||||
# Устанавливаем Python зависимости
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Копируем код приложения
|
||||
COPY . .
|
||||
|
||||
# Открываем порт
|
||||
EXPOSE 8501
|
||||
|
||||
# Команда запуска
|
||||
CMD ["streamlit", "run", "app.py", "--server.port", "8501", "--server.address", "0.0.0.0"]
|
||||
44
streamlit_app/README.md
Normal file
44
streamlit_app/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 📊 Streamlit App - NIN Excel Parsers API
|
||||
|
||||
Изолированное Streamlit приложение для демонстрации работы NIN Excel Parsers API.
|
||||
|
||||
## 🚀 Запуск
|
||||
|
||||
### Локально:
|
||||
```bash
|
||||
cd streamlit_app
|
||||
pip install -r requirements.txt
|
||||
streamlit run app.py
|
||||
```
|
||||
|
||||
### В Docker:
|
||||
```bash
|
||||
docker build -t streamlit-app .
|
||||
docker run -p 8501:8501 streamlit-app
|
||||
```
|
||||
|
||||
## 🔧 Конфигурация
|
||||
|
||||
### Переменные окружения:
|
||||
- `API_BASE_URL` - адрес FastAPI сервера (по умолчанию: `http://fastapi:8000`)
|
||||
|
||||
### Параметры Streamlit:
|
||||
- Порт: 8501
|
||||
- Адрес: 0.0.0.0 (для Docker)
|
||||
- Режим: headless (для Docker)
|
||||
|
||||
## 📁 Структура
|
||||
|
||||
```
|
||||
streamlit_app/
|
||||
├── app.py # Основное приложение
|
||||
├── requirements.txt # Зависимости Python
|
||||
├── Dockerfile # Docker образ
|
||||
├── .streamlit/ # Конфигурация Streamlit
|
||||
│ └── config.toml # Настройки
|
||||
└── README.md # Документация
|
||||
```
|
||||
|
||||
## 🌐 Доступ
|
||||
|
||||
После запуска приложение доступно по адресу: **http://localhost:8501**
|
||||
456
streamlit_app/app.py
Normal file
456
streamlit_app/app.py
Normal file
@@ -0,0 +1,456 @@
|
||||
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 - автоматически определяем правильный адрес
|
||||
def get_api_base_url():
|
||||
"""Автоматически определяет правильный адрес API"""
|
||||
# Если запущено в Docker, используем внутренний адрес
|
||||
if os.getenv("DOCKER_ENV") == "true":
|
||||
return "http://fastapi:8000"
|
||||
|
||||
# Если запущено локально, используем localhost
|
||||
return "http://localhost:8000"
|
||||
|
||||
API_BASE_URL = os.getenv("API_BASE_URL", get_api_base_url())
|
||||
|
||||
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_parser_getters(parser_name: str):
|
||||
"""Получение информации о геттерах парсера"""
|
||||
try:
|
||||
response = requests.get(f"{API_BASE_URL}/parsers/{parser_name}/getters")
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
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_BASE_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("📊 Сводки ПМ - Полный функционал")
|
||||
|
||||
# Получаем информацию о геттерах
|
||||
getters_info = get_parser_getters("svodka_pm")
|
||||
|
||||
# Секция загрузки файлов
|
||||
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("🔍 Получение данных")
|
||||
|
||||
# Показываем доступные геттеры
|
||||
if getters_info and "getters" in getters_info:
|
||||
st.info("📋 Доступные геттеры:")
|
||||
for getter_name, getter_info in getters_info["getters"].items():
|
||||
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
|
||||
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
|
||||
if getter_info.get('optional_params'):
|
||||
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
|
||||
|
||||
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 = {
|
||||
"getter": "single_og",
|
||||
"id": og_id,
|
||||
"codes": codes,
|
||||
"columns": columns
|
||||
}
|
||||
|
||||
result, status = make_api_request("/svodka_pm/get_data", 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 = {
|
||||
"getter": "total_ogs",
|
||||
"codes": codes_total,
|
||||
"columns": columns_total
|
||||
}
|
||||
|
||||
result, status = make_api_request("/svodka_pm/get_data", data)
|
||||
|
||||
if status == 200:
|
||||
st.success("✅ Данные получены")
|
||||
st.json(result)
|
||||
else:
|
||||
st.error(f"❌ Ошибка: {result.get('message', 'Неизвестная ошибка')}")
|
||||
else:
|
||||
st.warning("⚠️ Выберите коды и столбцы")
|
||||
|
||||
# Вкладка 2: Сводки СА - полный функционал
|
||||
with tab2:
|
||||
st.header("🏭 Сводки СА - Полный функционал")
|
||||
|
||||
# Получаем информацию о геттерах
|
||||
getters_info = get_parser_getters("svodka_ca")
|
||||
|
||||
# Секция загрузки файлов
|
||||
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("<EFBFBD><EFBFBD> Получение данных")
|
||||
|
||||
# Показываем доступные геттеры
|
||||
if getters_info and "getters" in getters_info:
|
||||
st.info("📋 Доступные геттеры:")
|
||||
for getter_name, getter_info in getters_info["getters"].items():
|
||||
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
|
||||
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
|
||||
if getter_info.get('optional_params'):
|
||||
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
|
||||
|
||||
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 = {
|
||||
"getter": "get_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', f'Неизвестная ошибка: {status}')}")
|
||||
else:
|
||||
st.warning("⚠️ Выберите режимы и таблицы")
|
||||
|
||||
# Вкладка 3: Мониторинг топлива - полный функционал
|
||||
with tab3:
|
||||
st.header("⛽ Мониторинг топлива - Полный функционал")
|
||||
|
||||
# Получаем информацию о геттерах
|
||||
getters_info = get_parser_getters("monitoring_fuel")
|
||||
|
||||
# Секция загрузки файлов
|
||||
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("🔍 Получение данных")
|
||||
|
||||
# Показываем доступные геттеры
|
||||
if getters_info and "getters" in getters_info:
|
||||
st.info("📋 Доступные геттеры:")
|
||||
for getter_name, getter_info in getters_info["getters"].items():
|
||||
st.write(f"• **{getter_name}**: {getter_info.get('description', 'Нет описания')}")
|
||||
st.write(f" - Обязательные параметры: {', '.join(getter_info.get('required_params', []))}")
|
||||
if getter_info.get('optional_params'):
|
||||
st.write(f" - Необязательные параметры: {', '.join(getter_info['optional_params'])}")
|
||||
|
||||
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 = {
|
||||
"getter": "total_by_columns",
|
||||
"columns": columns_fuel
|
||||
}
|
||||
|
||||
result, status = make_api_request("/monitoring_fuel/get_data", 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 = {
|
||||
"getter": "month_by_code",
|
||||
"month": month
|
||||
}
|
||||
|
||||
result, status = make_api_request("/monitoring_fuel/get_data", 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_BASE_URL}/docs")
|
||||
|
||||
# Информация о проекте
|
||||
with st.expander("ℹ️ О проекте"):
|
||||
st.markdown("""
|
||||
**NIN Excel Parsers API** - это веб-сервис для парсинга и обработки Excel-файлов нефтеперерабатывающих заводов.
|
||||
|
||||
**Возможности:**
|
||||
- 📊 Парсинг сводок ПМ (план и факт)
|
||||
- 🏭 Парсинг сводок СА
|
||||
- ⛽ Мониторинг топлива
|
||||
|
||||
**Технологии:**
|
||||
- FastAPI
|
||||
- Pandas
|
||||
- MinIO (S3-совместимое хранилище)
|
||||
- Streamlit (веб-интерфейс)
|
||||
""")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
streamlit_app/requirements.txt
Normal file
4
streamlit_app/requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
streamlit>=1.28.0
|
||||
requests>=2.31.0
|
||||
pandas>=1.5.0
|
||||
numpy>=1.24.0
|
||||
Reference in New Issue
Block a user