DRRK/app/ui/common.py
Raykov-MS c1c73e96f5 Add funding rates, positions, business calendar and xlsx export
- funding_rate table and "Фондирование" page: rate from date, history, rate on date (D27)
- positions by issue: BUY - SELL per portfolio, "Остатки" page (D29)
- quotes are loaded only for securities with an open position (D10)
- business calendar from xmlcalendar.ru: yearly file upload, report date
  and update job use previous working day (D30)
- xlsx download for positions, quotes and funding rate history
- manual operation type as radio buttons
- QUIK instruments with purchase marks ("МТС 2Р-18_1") match the issue,
  file name kept in operation.instrument (D17)
- migrations d3a6f8b1c254, e7b2c9d4a813, f1c4a7e9b352
- project.md: decisions and questions after customer meetings
2026-10-29 11:20:15 +03:00

89 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import logging
from collections.abc import Coroutine
from dataclasses import asdict, is_dataclass
from datetime import date
from decimal import Decimal
from typing import Any, TypeVar
import streamlit as st
from app.db.base import run_db
from app.exports.xlsx import MIME, table_xlsx
from app.services import business_calendar
from app.services.dates import previous_business_day
logger = logging.getLogger(__name__)
T = TypeVar("T")
def current_user() -> str:
"""email из профиля Keycloak (AuthService гарантирует, что он есть)."""
profile = st.session_state.get("user_profile", {})
if isinstance(profile, dict) and profile.get("email"):
return str(profile["email"])
raise RuntimeError("Пользователь не авторизован.")
def default_report_date() -> date:
"""Предыдущий рабочий день по производственному календарю (D30); если БД
недоступна — без праздников, чтобы экран всё равно открылся."""
try:
return run_db(business_calendar.report_date())
except Exception: # noqa: BLE001
logger.exception("Не удалось загрузить производственный календарь")
return previous_business_day()
def call_db(coro: Coroutine[Any, Any, T], error_message: str) -> T | None:
"""run_db() для экранов: непредвиденная ошибка -> st.error, а не трейсбек.
Ожидаемые ошибки сервисов (с понятным текстом) ловятся на месте вызова.
"""
try:
return run_db(coro)
except Exception as exc: # noqa: BLE001
logger.exception(error_message)
st.error(f"{error_message}: {exc}")
return None
def as_table(rows: list[Any], columns: dict[str, str]) -> list[dict[str, Any]]:
"""Список dataclass -> строки для st.dataframe с русскими заголовками.
Decimal выводим строкой: так видно ровно то, что лежит в БД, без float.
"""
table = []
for row in rows:
data = asdict(row) if is_dataclass(row) else dict(row)
table.append(
{title: _display(data.get(name)) for name, title in columns.items()}
)
return table
def download_xlsx(
rows: list[Any], columns: dict[str, str], sheet: str, file_name: str
) -> None:
"""Кнопка «Скачать .xlsx» с той же таблицей, что на экране (D13)."""
st.download_button(
"Скачать .xlsx",
data=table_xlsx(rows, columns, sheet),
file_name=file_name,
mime=MIME,
key=f"xlsx_{file_name}",
)
def decimal_text(value: Decimal | None) -> str:
"""Decimal -> текст для поля ввода ('' для None), без экспоненты и хвостовых нулей."""
return "" if value is None else format(value.normalize(), "f")
def _display(value: Any) -> Any:
if isinstance(value, Decimal):
return format(value.normalize(), "f")
return value