Skip to content

UI Audit Report — SoccerPredictAI

Date: 2026-04-24 Auditor: GitHub Copilot (Claude Sonnet 4.6) Scope: Streamlit UI — структура, API интеграция, error handling, UX Method: анализ src/ui/app/main.py, src/ui/app/api_client.py, src/ui/app/pages/


1. Структура приложения

1.1 Файловая структура

src/ui/app/
├── main.py          — главная страница (Livescores)
├── api_client.py    — HTTP клиент к FastAPI
└── pages/           — ❌ ПУСТАЯ директория
Страница Статус
Livescores ✅ Реализовано
Predictions Не реализовано — pages/ пусто
Model info ❌ Не реализовано
Batch inference results ❌ Не реализовано

⚠️ P0: UI реализует только livescores. Нет страницы предсказаний, нет страницы информации о модели. Предсказания доступны только через прямой вызов API.

1.2 APIClient

# Конфигурация:
API_URL = os.getenv("API_URL", "http://localhost:8000")
API_TOKEN = os.getenv("API_TOKEN")
API_HEADER_TOKEN = os.getenv("API_HEADER_TOKEN")
Аспект Значение
Retry Retry(total=2, backoff_factor=0.3, status_forcelist=[500, 502, 503, 504])
Default timeout 10s
POST timeout 30s
Auth: token Передаётся как query param ?token=...
Auth: header X-Token header

⚠️ P2 Security: Токен передаётся как query parameter (?token=...) — попадает в URL, логи, browser history. Рекомендуется передавать только через header.

1.3 Реализованные API методы

Метод APIClient Endpoint Используется в UI
get_livescores(year, month) GET /livescores/ ✅ main.py
get_health() GET /healthcheck/ ❌ не используется в UI

Нет методов для: - POST /predict/ (inline prediction) - GET /predict/{match_id} (match prediction) - POST /predict/async/ (async prediction) - GET /predict/model/info (model info) - GET /predict/matches/ (list matches)


2. Livescores Page Audit

2.1 Реализованный функционал

Функция Реализована
Year/Month фильтр
Refresh button
Status labels с emoji
@st.cache_data(ttl=30) ✅ — 30s stale data в норме
Error handling (APIError) ✅ — st.error() + st.stop()
Empty state handling ✅ — st.info("No matches...")
Spinner during load
UTC clock

2.2 Status labels

_STATUS_LABEL: dict[int, str] = {
    0: "❓ Unknown",
    1: "⏳ Upcoming",
    2: "📌 Postponed",
    3: "🟢 In progress",
    4: "❓ Unknown",
    5: "❓ Unknown",
    6: "✅ Finished",
    7: "❌ Cancelled",
}

⚠️ P3: Статусы 4 и 5 помечены как "Unknown" без пояснения — возможно, специфичные для источника данных статусы.

2.3 Кеш Streamlit

@st.cache_resource
def _client() -> APIClient:
    return APIClient()

@st.cache_data(ttl=30, show_spinner=False)
def _fetch(year: int, month: int | None) -> list[dict]:
    return _client().get_livescores(year=year, month=month)

cache_resource для singleton клиента, cache_data(ttl=30) для данных — правильное разделение.


3. Findings

ID Severity Описание
UI-01 P0 pages/ директория пуста — нет страницы предсказаний, нет страницы информации о модели
UI-02 P1 APIClient не имеет методов для prediction endpoints — невозможно добавить страницу предсказаний без доработки клиента
UI-03 P2 Auth token передаётся как query param (?token=...) — попадает в логи и URL
UI-04 P2 get_health() метод существует в клиенте, но не используется в UI — нет healthcheck индикатора
UI-05 P3 Статусы 4 и 5 помечены "Unknown" — стоит уточнить семантику у источника данных