System Audit Report — SoccerPredictAI¶
Date: 2026-04-24
Version: v2
Auditor: GitHub Copilot (Claude Sonnet 4.6)
Scope: верхнеуровневый аудит системы — архитектура, потоки данных, компоненты, контракты, риски
Метод: прямой анализ кода (dvc.yaml, params.yaml, src/, airflow/, docker/, k8s/, tests/)
1. Архитектура системы¶
High-level схема¶
[WhoScored.com]
│ (Selenoid browser session)
▼
[Celery worker-api] (очередь "api")
│ ── scraping results ──► [PostgreSQL]
│ │
[Airflow DAGs] [export_data_raw task]
(5 DAGs, @hourly / manual) │
trigger via PATCH/GET + X-Token [MinIO "data-raw"]
│ │
└──► [FastAPI] [DVC Pipeline] ← params.yaml / Hydra conf/
│ (15 stages, see §3.3)
[RabbitMQ] │
/ \ [MLflow Tracking + Registry]
[worker-api] [worker-ml] │
│ [PredictionService]
[MLflow Registry] (загружается при старте worker-ml)
│ │
[FeatureLookupService] [Redis (result backend)]
match_features.parquet │
(MinIO predictions bucket) │
[FastAPI /predict /livescores]
│
[Nginx (K8s Ingress)]
│
[Streamlit UI]
(внешний VPS, HTTPS)
│
[End User]
Компоненты — подтверждены кодом¶
| Компонент | Технология | Статус |
|---|---|---|
| Airflow (5 DAGs) | Apache Airflow, HttpOperator, HttpSensor | ✅ airflow/dags/etl_livescores_01–04.py, etl_export_01.py |
| FastAPI Service | FastAPI + Gunicorn + Pydantic + Prometheus | ✅ src/app/main.py |
| Celery worker-api | Celery + RabbitMQ | ✅ src/app/worker_api.py |
| Celery worker-ml | Celery + RabbitMQ | ✅ src/app/worker_ml.py |
| PredictionService | MLflow pyfunc, загрузка при worker_process_init |
✅ src/app/tasks/predict.py |
| FeatureLookupService | boto3 + pandas, MinIO head_object cache | ✅ src/app/services/predict.py |
| DVC Pipeline | DVC + Hydra + Great Expectations | ✅ dvc.yaml (15 stages) |
| MLflow Tracking + Registry | MLflow | ✅ src/utils/mlflow_meta.py, src/pipelines/register_model.py |
| PostgreSQL | PostgreSQL + SQLModel | ✅ src/app/database.py |
| MinIO | boto3, S3-compatible | ✅ src/data/storage.py, src/app/config/storage.py |
| Redis | Celery result backend | ✅ src/app/connections/broker.py |
| Prometheus | prometheus_client, middleware + worker :9091 | ✅ src/app/main.py, src/app/tasks/predict.py |
| Grafana | dashboards | 📋 Planned (не задеплоены) |
| Streamlit UI | Streamlit + api_client | ✅ src/ui/app/ |
| K8s + Helm | single-node (healserver) | ✅ k8s/helm/ns_soccer-api/ |
| GitLab CI/CD | Docker + Helm deploy | ✅ Docker: 10 Dockerfiles |
2. Слои системы¶
Product / Problem Layer¶
- Задача: классификация исхода матча (1×2), target =
outcome_1x2(params.yaml) - Модели: baseline, logreg, HGBT, XGBoost (с Optuna tuning + isotonic calibration)
- Метрики: logloss, ECE, segment metrics
- Статус: ✅ Реализовано
Data Layer¶
- Компоненты:
src/data/source.py,src/data/preprocess.py,src/data/splitting.py,src/data/storage.py - Ответственность: загрузка из MinIO, preprocessing (outlier removal по
score_outlier_pct=0.9999), split finished/future, temporal CV splits - Статус: ✅ Реализовано
Feature Layer¶
- Компоненты:
src/features/stats_matches.py(rolling stats: win/draw/loss/goals_for/goals_against × 5 windows),src/features/elo.py(ELO per tournament, k=32, initial=1500, home_adv=50),src/features/select.py - Артефакты:
features.parquet,features_meta.parquet(source of truth) - side:
diff(разность home–away метрик) - Статус: ✅ Реализовано
Model Layer¶
- Компоненты:
src/models/classification.py,src/models/final_train.py,src/models/tuning.py,src/models/metrics.py,src/models/pipelines.py - Tuning: Optuna (
n_trials=2,frac=0.1) — параметры smoke-режима - Calibration: isotonic,
calib_frac=0.15,min_calib_samples=100 - MLflow artifact format:
mlflow.sklearn - Статус: ✅ Реализовано (⚠️ tuning n_trials=2 — smoke-конфиг)
Experimentation Layer (MLflow)¶
- Эксперимент:
matches_clf_smoke - Registry алиас:
champion(register_model.model_stage) - Model name:
soccer_clf - Статус: ✅ Операционально
Pipeline Layer (DVC / Hydra)¶
- Stages (15):
load_data_from_sources,validate_raw,export_metadata,preprocessing,validate_finished,validate_future,feature_engineering,validate_features,split_data,batch_inference,classification_models,ablation_study,tune_xgb,final_train,register_model - batch_inference: независимая ветка, не зависит от training stages, безопасен для параллельного запуска
- Валидация: Great Expectations на 3 уровнях (raw, finished, features)
- Статус: ✅ Реализовано
Serving Layer¶
- Компоненты:
src/app/routers/predict.py(6 endpoints),src/app/tasks/predict.py,PredictionService,FeatureLookupService - Sync inference:
POST /predict/→ Celery ml (timeout=30s) - Async inference:
POST /predict/async/→ task_id →GET /monitoring/task_status/{task_id} - Batch lookup:
GET /predict/{match_id}→ FeatureLookupService →match_features.parquet - Stats router:
src/app/routers/stats.pyсуществует, но не включён вmain.py(⚠️ dead code) - Статус: ✅ Операционально
UI Layer (Streamlit)¶
- Компоненты:
src/ui/app/,src/ui/nginx/ - Статус: ✅ Операционально
Orchestration Layer (Airflow)¶
- DAGs:
etl_livescores_01–04(@hourly),etl_export_01 - Retry: 3 попытки, 5-мин интервал
- Auth: X-Token через Airflow Variable
SOCCER_FASTAPI_HEADER_TOKEN - Статус: ✅ Операционально
Ops / Infra Layer¶
- Docker: 10 образов (
Dockerfile.api,Dockerfile.celery_ml,Dockerfile.env_api/ml/dev/prod,Dockerfile.airflow_custom*) - K8s:
k8s/helm/ns_soccer-api/(single-node, healserver) - Auth/Security:
X-Tokenheader + query (FASTAPI_HEADER_TOKEN/FASTAPI_QUERY_TOKEN) для ETL endpoints,.envчерез pydantic-settings - Статус: ✅ Операционально (Grafana dashboards — 📋 Planned)
Testing / Validation Layer¶
- Файлы: 32 файла, ~5100 строк
- Типы: unit, property (Hypothesis), service, contract, load (Locust), data quality, metrics
- Integration: с моками (без live MLflow/Celery в CI)
- Статус: ✅ Операционально (live integration — 🚧 Частично)
Documentation Layer¶
- Компоненты:
docs/(MkDocs), ADRs, architecture, runbooks, status, validation - Статус: ✅ Детальная
3. Ключевые потоки¶
3.1 Data flow¶
WhoScored.com
→ Selenoid (browser automation)
→ Celery worker-api (очередь "api")
→ PostgreSQL (match_raw, match tables)
→ Celery export_data_raw
→ MinIO s3://data-raw/match.parquet + match_raw.parquet [+ .minio.json metadata]
→ DVC: load_data_from_sources (always_changed: true)
→ data/raw/match_raw.parquet
→ validate_raw (GE gate) → data/evaluation/ge_raw.json
→ export_metadata → data/metadata/*.json (stageId, tournamentId, regionId, seasonId, teamIds)
→ preprocessing (score_outlier_pct=0.9999)
→ data/interim/finished.parquet + future.parquet
→ validate_finished / validate_future (GE gates)
→ feature_engineering
→ data/features/features.parquet + features_meta.parquet
→ validate_features (GE gate, sample_rows=500)
→ split_data (test_start=2024-01-01, folds 2022–2024)
→ data/processed/dataset.parquet + splits/train_ids/test_ids/folds.parquet
Форматы: .parquet на всех этапах. Метаданные: .json.
Трансформации: src/data/preprocess.py → src/features/stats_matches.py → src/features/elo.py
3.2 Feature flow¶
data/interim/finished.parquet
→ src/features/stats_matches.py
(rolling windows: [1,2,3,5,10], cols: win/draw/loss/goals_for/goals_against, side=diff)
→ src/features/elo.py
(ELO per tournamentId, k=32, initial=1500, home_adv=50)
→ data/features/features.parquet [все исторические матчи]
→ data/features/features_meta.parquet [контракт: имена, типы, окна]
Для serving (batch_inference, независимая ветка):
data/interim/future.parquet + finished.parquet + features_meta.parquet
→ src/pipelines/inference.py
→ data/predictions/match_features.parquet
→ MinIO predictions bucket
→ FeatureLookupService (serving)
Контракт: features_meta.parquet — единый source of truth.
Training и serving используют один и тот же код src/features/.
3.3 Model flow¶
data/processed/dataset.parquet + splits + features_meta
→ classification_models (screening с fracs_for_train=[0.001, 0.002])
→ data/models/run_id.json (лучший MLflow run)
data/models/run_id.json + xgb_best_params.json
→ final_train (retrain на полном train set + isotonic calibration)
→ data/models/final_run_id.json
→ MLflow Registry (алиас "champion", name "soccer_clf")
MLflow Registry
→ PredictionService.load() (при worker_process_init сигнале)
→ predict_match Celery task (ml queue)
→ INFERENCE_LATENCY, PREDICTION_CONFIDENCE, MODEL_INFO (Prometheus)
Модель: sklearn Pipeline + XGBoost (или HGBT/LogReg) + optional isotonic calibration.
Format: mlflow.sklearn (pickle).
3.4 Execution flow¶
Ручной / CI:
dvc repro → 15 stages → MLflow → register_model → (ручной деплой worker-ml)
Production ETL (scheduled):
Airflow @hourly → PATCH /sources/livescores/ (X-Token) → Celery api → PostgreSQL
Production export (manual trigger):
Airflow manual → GET /sources/export/{table} → Celery api → MinIO
Inference:
Streamlit UI → POST /predict/ → Celery ml (sync, timeout=30s) → PredictionService → MLflow model
Streamlit UI → GET /predict/{match_id} → FeatureLookupService → match_features.parquet
Streamlit UI → POST /predict/async/ → task_id → polling /monitoring/task_status/{task_id} → Redis
4. Границы и контракты¶
Data contracts¶
| Граница | Формат | Примечание |
|---|---|---|
| PostgreSQL → MinIO | Parquet | export через Celery task, retry 3×60s |
| MinIO → DVC | Parquet | .minio.json metadata files для versioning |
| raw → interim | Parquet | GE gate на каждом уровне |
| interim → features | Parquet | features_meta.parquet как явный контракт |
| features → dataset | Parquet | join features + target |
| dataset → splits | Parquet | temporal split (test_start=2024-01-01) |
| batch_inference → serving | Parquet | через MinIO predictions bucket |
| API request | JSON (Pydantic) | PredictRequest (schemas/predict.py) |
| API response | JSON (Pydantic) | PredictResponse с probabilities + model_version |
Feature contracts¶
features_meta.parquet— единственный source of truth по именам и типам фич- Используется в:
classification_models,final_train,batch_inference,FeatureLookupService - ⚠️ Нет отдельного schema-документа — только файловый артефакт
Model contracts¶
- Формат:
mlflow.sklearnpickle-based Pipeline - Загрузка:
mlflow.pyfunc.load_model(f"models:/soccer_clf@champion") - Input: DataFrame с колонками из
features_meta.parquet - Output:
predict_proba()→{0: p_home_win, 1: p_draw, 2: p_away_win} - Без hot-reload: модель живёт в памяти worker до его перезапуска
API contracts¶
- Pydantic schemas:
src/app/schemas/predict.py - OpenAPI автогенерируется FastAPI
- Auth:
X-Tokenheader только для/sources/(черезFASTAPI_HEADER_TOKENenv); inference endpoints — без auth - Prometheus метрики:
REQUEST_COUNT,REQUEST_LATENCY,PREDICTION_COUNT,PREDICTION_TIMEOUTS,INFERENCE_LATENCY,PREDICTION_CONFIDENCE,MODEL_INFO - Celery:
task_time_limit=3600,task_soft_time_limit=3000,task_acks_late=True,task_reject_on_worker_lost=True,worker_max_tasks_per_child=1000
5. Потенциальные риски¶
R1 — params.yaml в smoke-режиме (высокий)¶
tuning.n_trials=2, tuning.frac=0.1, classification.fracs_for_train=[0.001, 0.002].
Это непроизводственные значения. Если pipeline запускается с этими параметрами в production — модель тренируется на 0.1–0.2% данных. Риск: production deploy с smoke-моделью.
R2 — DVC pipeline запускается вручную (высокий)¶
Нет автоматической цепочки: Airflow export → dvc repro → register_model → worker-ml restart.
После обновления данных в MinIO pipeline должен быть запущен вручную или через CI.
R3 — Модель не hot-reload в serving (высокий)¶
PredictionService.load() вызывается один раз при worker_process_init. После register_model в MLflow Registry новая версия не появляется в serving без перезапуска Celery worker-ml (= нового деплоя).
R4 — stats router не зарегистрирован (средний)¶
src/app/routers/stats.py (/stats/teams/search/) существует и содержит рабочий код, но не включён в main.py. Endpoint недоступен. Либо forgot to register, либо намеренно отключён без документации.
R5 — Batch inference artifact staleness (высокий)¶
FeatureLookupService проверяет MinIO LastModified с интервалом FEATURE_CACHE_CHECK_INTERVAL (default 60s). Если batch_inference DVC stage не запускался после обновления данных — serving работает с устаревшими features без явного сигнала об этом.
R6 — Нет Staging→Production gate (высокий)¶
Регистрация модели (register_model stage) автоматически присваивает алиас champion. Нет автоматической проверки метрик перед продвижением. Деградировавшая модель может стать champion.
R7 — Нет drift detection (средний)¶
Evidently не интегрирован. Нет online мониторинга distribution shift между training и serving features. Деградация модели может быть необнаружена.
R8 — Single-node K8s, нет HA (средний)¶
Весь стек на healserver. Сбой ноды = полная недоступность системы.
R9 — CORS allow_origins=["*"] (средний)¶
main.py: CORSMiddleware(allow_origins=["*"]). Все origins разрешены. В production окружении это должно быть ограничено доверенными origins.
R10 — Inference endpoints без auth (средний)¶
/predict/*, /livescores/ — без аутентификации. Только ETL endpoints защищены X-Token. Нет rate limiting на API уровне.
R11 — Grafana dashboards не задеплоены (низкий)¶
Prometheus собирает метрики. Grafana: dashboards planned, не реализованы. Мониторинг пассивный.
R12 — Integration tests без live services (низкий для CI)¶
Все тесты используют моки. Нет live integration test с MLflow/Celery/PostgreSQL в pipeline.
6. Интеграции¶
MinIO ↔ DVC Pipeline¶
Что соединяет: S3 object storage ↔ 15 DVC stages
Как: DVC remote (content-addressed) + boto3 (src/data/storage.py)
load_data_from_sources: always_changed=true (всегда тянет из MinIO)
batch_inference: пишет в data/predictions/match_features.parquet
Надёжность: высокая (idempotent DVC stages)
Риски: нет retry на DVC уровне при network failure; credentials через env vars
Pipeline ↔ MLflow¶
Что соединяет: classification_models / final_train / register_model → MLflow
Как: mlflow.sklearn.log_model, MlflowClient.create_model_version, алиас "champion"
Надёжность: высокая (идемпотентная регистрация)
Риски: нет quality gate перед register; smoke params могут дать некачественную модель
MLflow ↔ Celery worker-ml¶
Что соединяет: MLflow Registry → PredictionService (in-process)
Как: mlflow.pyfunc.load_model("models:/soccer_clf@champion") при worker_process_init
Надёжность: высокая при запуске; нет hot-reload
Риски: cold start при первом деплое (download from MinIO); отставание версий при деплое
FeatureLookupService ↔ MinIO¶
Что соединяет: serving ↔ batch_inference artifact
Как: boto3 head_object (LastModified check ≤60s) → read_parquet from MinIO
Надёжность: средняя (кэш in-process, проверка по времени)
Риски: stale features при задержке batch_inference; падение при отсутствии файла
Airflow ↔ FastAPI¶
Что соединяет: ETL scheduler → scraping HTTP triggers
Как: HttpOperator (PATCH/GET) + HttpSensor (polling task_id из xcom)
Auth: X-Token через Airflow Variable SOCCER_FASTAPI_HEADER_TOKEN
Retry: 3×5min
Надёжность: средняя
Риски: Airflow в отдельном namespace; зависимость от K8s Ingress; нет circuit breaker
FastAPI ↔ Celery (RabbitMQ)¶
Что соединяет: HTTP layer ↔ async task execution
Как: celery_app.send_task() → RabbitMQ → worker-api / worker-ml
Queues: "api" (scraping/export), "ml" (inference)
Celery config: task_acks_late=True, task_reject_on_worker_lost=True (at-least-once)
Надёжность: высокая (acks_late, retry)
Риски: нет dead-letter queue; дублирование при worker crash + retry
7. Карта системы¶
7.1 Архитектурная схема¶
[WhoScored.com] ←(Selenoid)─ [Celery worker-api] ─► [PostgreSQL]
│
[Airflow DAGs] ──PATCH/GET + X-Token──► [FastAPI] [Celery export_data_raw]
│ │
[RabbitMQ] [MinIO data-raw]
/ \ │
[worker-api] [worker-ml] [DVC Pipeline]
│ (15 stages)
[MLflow Registry] │
(soccer_clf@champion) │
│ [MLflow Tracking]
[PredictionService]
(model in memory)
│
[FeatureLookupService]──[MinIO predictions]
│
[Streamlit UI] ──HTTPS──► [Nginx+Ingress] ──► [FastAPI]
│ │
/predict /livescores
/monitoring /healthcheck
│
[Redis]
(result backend + cache)
7.2 Таблица компонентов¶
| Component | Layer | Responsibility | Inputs | Outputs |
|---|---|---|---|---|
| Airflow (5 DAGs) | Orchestration | ETL scheduling, retry 3×5min | cron @hourly / manual | HTTP triggers to FastAPI |
| Selenoid | Data Ingestion | Browser-based scraping | HTTP from worker-api | HTML → scraped matches |
| Celery worker-api | Data / ETL | Scraping, DB writes, export | RabbitMQ "api" queue | PostgreSQL records, MinIO parquet |
| PostgreSQL | Data | Canonical data store | worker-api | SQL queries, parquet export |
| MinIO S3 | Data / Artifacts | Object storage | DVC, workers | Parquet files, model artifacts |
| DVC Pipeline (15 stages) | Pipeline | ML lifecycle | MinIO raw data | features, model, metrics, batch preds |
| Great Expectations (3 gates) | Validation | Data contract enforcement | parquet checkpoints | pass/fail + ge_*.json |
| src/features/ | Feature | Deterministic feature engineering | finished.parquet | features.parquet, features_meta.parquet |
| src/models/ | Model | Training, calibration, MLflow | dataset + splits | MLflow runs, artifacts |
| MLflow Tracking + Registry | Experimentation | Experiment tracking, registry | DVC pipeline stages | model_uri, version aliases |
| FastAPI Service | Serving | HTTP API orchestration | HTTP requests | JSON responses, task IDs |
| Celery worker-ml | Serving | ML inference (heavy) | RabbitMQ "ml" queue | predictions → Redis |
| PredictionService | Serving | Model lifecycle per worker process | MLflow model_uri | probabilities dict |
| FeatureLookupService | Serving | Batch feature lookup with cache | match_features.parquet / MinIO | feature dicts |
| Redis | Serving / Cache | Celery result backend | Celery results | task status, predictions |
| stats router | Serving | Team search (⚠️ not registered) | PostgreSQL | — (unused) |
| Streamlit UI | UI | User-facing interface | FastAPI HTTP | rendered match + prediction views |
| Prometheus | Monitoring | Metrics collection | /metrics, worker :9091 | time-series |
| Grafana | Monitoring | Dashboards | Prometheus | — (📋 Planned) |
| pytest (32 files, ~5100 loc) | Testing | Quality assurance | source code | pass/fail |
| K8s + Helm | Infra | Container orchestration (single-node) | Docker images | deployed services |
| GitLab CI/CD | Infra | Build + test + deploy | git push | Docker build, Helm deploy |
7.3 Ключевые зависимости¶
DVC pipeline ← зависит от → MinIO data-raw
DVC pipeline ← зависит от → MLflow (tracking + registry)
Celery worker-ml ← зависит от → MLflow Registry (при запуске)
Celery worker-ml ← зависит от → MinIO predictions (FeatureLookupService)
FastAPI /predict ← зависит от → Celery worker-ml (sync 30s timeout)
FastAPI /predict/{id} ← зависит от → FeatureLookupService → MinIO predictions
Airflow DAGs ← зависит от → FastAPI (через K8s Ingress)
Streamlit UI ← зависит от → FastAPI HTTPS
Serving freshness ← зависит от → своевременного запуска batch_inference + register_model
Узкие места (подтверждены кодом):
1. Celery worker-ml — единственная точка inference, нет горизонтального масштабирования в текущем Helm chart
2. DVC pipeline — полностью ручной запуск в production
3. match_features.parquet — единственный канал между offline batch_inference и online serving
4. healserver — single-node K8s, нет failover
5. params.yaml — smoke-режим (n_trials=2) активен как конфиг по умолчанию
8. Итоговая оценка¶
System maturity: medium-high
Сильные стороны:
- Чёткое разделение offline (DVC pipeline, 15 stages) и online (FastAPI/Celery) путей
- feature contract через features_meta.parquet (один код для training и serving)
- Great Expectations на 3 уровнях pipeline (raw, interim, features)
- Полная воспроизводимость: DVC + params.yaml + MLflow run tracking
- Детальная документация с явным implemented/planned/planned разграничением
- 32 test файла (~5100 строк): unit, property, contract, load
- Prometheus метрики на всех serving компонентах (FastAPI + worker :9091)
- Airflow ETL с retry (3×5min) и X-Token auth
- task_acks_late=True, task_reject_on_worker_lost=True — защита от потери задач
- Изолированные очереди Celery: "api" vs "ml" (ресурсная изоляция)
Основные риски:
- params.yaml в smoke-режиме (n_trials=2, fracs_for_train=[0.001, 0.002]) — production deploy с игрушечной моделью
- Ручной запуск DVC pipeline — нет автоматической цепочки data update → retrain → deploy
- Нет hot-reload модели (только перезапуск worker-ml)
- stats router (src/app/routers/stats.py) существует, но не подключён в main.py
- CORS allow_origins=["*"] в production FastAPI
- Нет Staging→Production quality gate перед register_model
- Serving зависит от freshness batch_inference artifact без SLA
- Нет drift detection (Evidently — planned)
- Grafana dashboards не задеплоены — мониторинг пассивный
- Single-node K8s без HA
Что требует углублённого аудита:
- 01: схема данных PostgreSQL vs parquet contract (нет явной валидации на границе)
- 03: реальные метрики при smoke params vs production params
- 04: корректность всех 15 DVC stage зависимостей
- 05: политика продвижения моделей в MLflow Registry
- 06: полная проверка train/serve feature contract через features_meta.parquet
- 07: поведение FeatureLookupService при отсутствии match_features.parquet
Отчёт является базой для:
- 01_data_audit — схема PostgreSQL ↔ parquet contract
- 02_feature_audit — rolling stats / ELO correctness, leakage check
- 03_training_evaluation_audit — метрики при production params, calibration
- 04_pipeline_dvc_hydra_audit — stage deps, params coverage, always_changed
- 05_mlflow_registry_audit — алиасы, promotion policy, model versioning
- 06_train_serve_consistency_audit — features_meta contract, serving paths
- 07_serving_audit — FeatureLookupService edge cases, endpoint behavior