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" — стоит уточнить семантику у источника данных |