Skip to content

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-Token header + 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.pysrc/features/stats_matches.pysrc/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.sklearn pickle-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-Token header только для /sources/ (через FASTAPI_HEADER_TOKEN env); 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