# Suggest-Bet-BE — Veritabanı Şemas ve Proje Tam Referansı > **Tarih:** 2026-03-13 > Google Gemini 3.1 Pro Deep Think ile çözüm üretimi için hazırlanmış kapsamlı referans dokümanıdır. --- ## 1. Proje Özeti **Suggest-Bet-BE**, yapay zeka destekli bir spor bahis tahmin ve analiz platformu backend servisidir. | Katman | Teknoloji | Port | |--------|-----------|------| | **Backend API** | NestJS 11 (TypeScript) | `3005` | | **AI Engine** | Python FastAPI, XGBoost, LightGBM Ensemble | `8000` | | **Veritabanı** | PostgreSQL 16 + Prisma ORM | `5432` | | **Kuyruk/Cache** | BullMQ + Redis (opsiyonel) | `6379` | | **Auth** | JWT + Passport (Access 15dk + Refresh 7gün) | — | | **Scraping** | Axios + Cheerio (Mackolik.com) | — | | **AI** | Google Gemini API (yorum üretimi) | — | **Temel Akış:** 1. Mackolik API'den canlı maç verisi çekilir (15dk cron) 2. Kullanıcı maç seçer → AI Engine'e gönderilir 3. V20+ model çoklu market tahmin paketi üretir (MS, OU, BTTS, DC, HT/FT) 4. İsteğe bağlı akıllı kupon önerilir (5 strateji: SAFE, BALANCED, AGGRESSIVE, VALUE, MIRACLE) --- ## 2. Veritabanı Şeması (PostgreSQL — 27 Tablo, 6 Enum) ### 2.1 Enum Tanımları ```sql -- Spor türleri enum Sport { football, basketball } -- Kullanıcı rolleri enum UserRole { user, superadmin } -- Abonelik durumu enum SubscriptionStatus { free, active, expired } -- Oyuncu pozisyonları (futbol) enum PlayerPosition { goalkeeper, defender, midfielder, striker } -- Maç olayı türleri enum EventType { goal, card, substitute } -- Maç pozisyonu (ev sahibi/deplasman) enum MatchPosition { home, away } ``` ### 2.2 Tablo Detayları --- #### `countries` — Ülkeler (160 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `String` PK | Mackolik ülke ID | | `name` | `String` UNIQUE | Ülke adı | | `flag_url` | `String?` | Bayrak görseli URL | | `created_at` | `DateTime` | — | **İlişkiler:** → `leagues[]` --- #### `leagues` — Ligler (1,505 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `String` PK | Mackolik lig ID | | `name` | `String` | Lig adı | | `country_id` | `String?` FK → countries | Ülke | | `sport` | `Sport` | football / basketball | | `competition_slug` | `String?` | URL slug | | `code` | `String?` | Kısa kod | | `logo_url` | `String?` | Logo URL | | `created_at` | `DateTime` | — | **Unique:** `(name, country_id, sport)` **İndeksler:** `sport`, `country_id` **İlişkiler:** → `matches[]`, `live_matches[]` --- #### `teams` — Takımlar (19,595 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `String` PK | Mackolik takım ID | | `name` | `String` | Takım adı | | `slug` | `String?` | URL slug | | `sport` | `Sport` | football / basketball | | `logo_url` | `String?` | Logo URL. CDN: `https://file.mackolikfeeds.com/teams/{id}` | | `created_at` | `DateTime` | — | **İndeksler:** `sport`, `name` **İlişkiler:** → home/away matches, participations, events, stats --- #### `players` — Oyuncular (217,040 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `String` PK | Mackolik oyuncu ID | | `name` | `String` | Oyuncu adı | | `slug` | `String?` UNIQUE | URL slug | | `created_at` | `DateTime` | — | **İndeksler:** `name` **İlişkiler:** → participations, events (scorer, assist, out), stats --- #### `matches` — Kalıcı Maç Kayıtları (236,859 kayıt, 100 MB) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `String` PK | Mackolik maç ID | | `league_id` | `String?` FK → leagues | Lig | | `home_team_id` | `String?` FK → teams | Ev sahibi | | `away_team_id` | `String?` FK → teams | Deplasman | | `sport` | `Sport` | football / basketball | | `match_name` | `String?` | "Galatasaray vs Fenerbahçe" | | `match_slug` | `String?` | URL slug | | `mst_utc` | `BigInt` | Maç zamanı (Unix ms) | | `status` | `String?` | Durum | | `state` | `String?` | postGame, preGame, live, etc. | | `score_home` | `Int?` | Ev sahibi skor | | `score_away` | `Int?` | Deplasman skor | | `ht_score_home` | `Int?` | İlk yarı ev sahibi skor | | `ht_score_away` | `Int?` | İlk yarı deplasman skor | | `winner` | `String?` | Kazanan | | `iddaa_code` | `String?` | İddaa maç kodu | | `created_at` | `DateTime` | — | | `updated_at` | `DateTime` | — | **İndeksler:** `mst_utc DESC`, `sport`, `state`, `league_id`, `home_team_id`, `away_team_id`, `iddaa_code` **İlişkiler:** → oddCategories, teamStats, playerParticipations, playerEvents, playerStats, officials, prediction, aiFeatures, couponItems --- #### `live_matches` — Canlı/Yaklaşan Maçlar (82 kayıt, döngüsel) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `String` PK | Mackolik maç ID | | `league_id` | `String?` FK → leagues | Lig | | `home_team_id` | `String?` FK → teams | Ev sahibi | | `away_team_id` | `String?` FK → teams | Deplasman | | `sport` | `String?` | football / basketball | | `match_name` | `String?` | Maç adı | | `match_slug` | `String?` | URL slug | | `mst_utc` | `BigInt?` | Maç zamanı (Unix ms) | | `status` | `String?` | Durum | | `state` | `String?` | pre, live, post | | `substate` | `String?` | Alt durum | | `score_home` | `Int?` | Ev sahibi skor | | `score_away` | `Int?` | Deplasman skor | | `updated_at` | `DateTime` | Son güncelleme | | **`odds`** | **`Json?`** | **Tüm bahis oranları (JSON blob)** | | `odds_updated_at` | `DateTime?` | Oran güncelleme zamanı | | `referee_name` | `String?` | Hakem adı | | **`lineups`** | **`Json?`** | **Kadro (JSON blob, home/away dizileri)** | | **`sidelined`** | **`Json?`** | **Sakatlar/Cezalılar (JSON blob)** | **İndeksler:** `mst_utc`, `state` **Not:** Maç bitince `live_matches` → `matches` tablosuna migrate edilir (30dk cron). --- #### `odd_categories` — Bahis Kategorileri (3,161,172 kayıt, 689 MB) | Kolon | Tip | Açıklama | |-------|-----|----------| | `db_id` | `Int` PK autoincrement | — | | `match_id` | `String` FK → matches | Maç | | `category_json_id` | `Int?` | Mackolik kategori ID | | `name` | `String?` | "Maç Sonucu", "2,5 Alt/Üst", "Karşılıklı Gol" vb. | | `created_at` | `DateTime` | — | **Unique:** `(match_id, name)` **İndeksler:** `match_id` --- #### `odd_selections` — Bahis Seçimleri ve Oranları (8,511,132 kayıt, 1 GB) | Kolon | Tip | Açıklama | |-------|-----|----------| | `db_id` | `Int` PK autoincrement | — | | `odd_category_db_id` | `Int` FK → odd_categories | Kategori | | `name` | `String?` | "1", "X", "2", "Alt", "Üst", "1-X", vb. | | `odd_value` | `String?` | Oran değeri ("1.85", "3.04") | | `position` | `String?` | Sıralama pozisyonu | | `sov` | `Float?` | — | | `state` | `String?` | — | | `created_at` | `DateTime` | — | | `updated_at` | `DateTime` | — | **Unique:** `(odd_category_db_id, name)` **İndeksler:** `odd_category_db_id` --- #### `odds_history` — Oran Değişim Geçmişi (0 kayıt, henüz aktif değil) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `BigInt` PK autoincrement | — | | `selection_id` | `Int` FK → odd_selections | Seçim | | `match_id` | `String` | Maç ID | | `previous_value` | `Float` | Önceki oran | | `new_value` | `Float` | Yeni oran | | `bookmaker` | `String?` default "MACKOLIK" | — | | `change_time` | `DateTime` | Değişim zamanı | **İndeksler:** `(match_id, change_time)`, `selection_id` --- #### `match_team_stats` — Takım İstatistikleri (310,991 kayıt, 91 MB) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `Int` PK autoincrement | — | | `match_id` | `String` FK → matches | Maç | | `team_id` | `String` FK → teams | Takım | | **Futbol Alanları:** | | | | `possession_percentage` | `Float?` | Topla oynama % | | `shots_on_target` | `Int?` | İsabetli şut | | `shots_off_target` | `Int?` | İsabetsiz şut | | `total_shots` | `Int?` | Toplam şut | | `total_passes` | `Int?` | Toplam pas | | `corners` | `Int?` | Korner | | `fouls` | `Int?` | Faul | | `offsides` | `Int?` | Ofsayt | | **Basketbol Alanları:** | | | | `points` | `Int?` | Toplam sayı | | `rebounds` | `Int?` | Ribaund | | `assists` | `Int?` | Asist | | `fg_made` / `fg_attempted` | `Int?` | Field goal | | `three_pt_made` / `three_pt_attempted` | `Int?` | 3 sayı | | `ft_made` / `ft_attempted` | `Int?` | Serbest atış | | `steals` | `Int?` | Top çalma | | `blocks` | `Int?` | Blok | | `turnovers` | `Int?` | Top kaybı | | `q1_score` ... `q4_score`, `ot_score` | `Int?` | Periyot skorları | **Unique:** `(match_id, team_id)` --- #### `match_player_participation` — Oyuncu Kadro Katılımları (3,342,839 kayıt, 1 GB) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `Int` PK autoincrement | — | | `match_id` | `String` FK → matches | Maç | | `player_id` | `String` FK → players | Oyuncu | | `team_id` | `String` FK → teams | Takım | | `position` | `PlayerPosition?` | goalkeeper, defender, midfielder, striker | | `shirt_number` | `Int?` | Forma numarası | | `is_starting` | `Boolean` default true | İlk 11'de mi? | | `created_at` | `DateTime` | — | **Unique:** `(match_id, player_id, team_id)` --- #### `match_player_events` — Maç Olayları (1,453,227 kayıt, 356 MB) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `Int` PK autoincrement | — | | `match_id` | `String` FK → matches | Maç | | `player_id` | `String` FK → players | Olay yapan oyuncu | | `team_id` | `String` FK → teams | Takım | | `event_type` | `EventType` | goal, card, substitute | | `event_subtype` | `String?` | "yellow", "red", "penalty", vb. | | `time_minute` | `String` | Dakika ("45+2") | | `time_seconds` | `Int?` | Saniye | | `period_id` | `Int?` | Periyot | | `assist_player_id` | `String?` FK → players | Asist yapan oyuncu | | `score_after` | `String?` | Olay sonrası skor ("1-0") | | `player_out_id` | `String?` FK → players | Çıkan oyuncu (değişiklik için) | | `position` | `MatchPosition?` | home / away | **İndeksler:** `match_id`, `player_id`, `team_id`, `event_type`, `assist_player_id` **Dağılım:** substitute: 787K, card: 409K, goal: 257K --- #### `match_player_stats` — Oyuncu İstatistikleri (344,688 kayıt, basketbol odaklı) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `Int` PK autoincrement | — | | `match_id` | `String` FK → matches | Maç | | `player_id` | `String` FK → players | Oyuncu | | `team_id` | `String` FK → teams | Takım | | `minutes` | `String?` | Oynanan süre | | `points`, `rebounds`, `assists` | `Int?` | Temel istatistikler | | `steals`, `blocks`, `turnovers`, `fouls` | `Int?` | Detay istatistikler | | `fg_made/attempted`, `three_pt_made/attempted`, `ft_made/attempted` | `Int?` | Şut istatistikleri | **Unique:** `(match_id, player_id, team_id)` --- #### `match_officials` — Hakem Bilgileri (340,824 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `Int` PK autoincrement | — | | `match_id` | `String` FK → matches | Maç | | `name` | `String` | Hakem adı | | `role_id` | `Int` FK → official_roles | Hakem rolü | **Unique:** `(match_id, name, role_id)` --- #### `official_roles` — Hakem Rolleri (5 kayıt) | id | Rol | |----|-----| | 1 | Orta Hakem | | 2 | Yardımcı Hakem | | 3 | 4. Hakem | | 4 | VAR | | 5 | AVAR | --- #### `match_ai_features` — AI Feature Cache (279 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `match_id` | `String` PK FK → matches | Maç | | `home_elo` | `Float` default 1500 | Ev sahibi ELO | | `away_elo` | `Float` default 1500 | Deplasman ELO | | `home_form_score` | `Float` default 50 | Ev sahibi form skoru | | `away_form_score` | `Float` default 50 | Deplasman form skoru | | `missing_players_impact` | `Float` default 0 | Eksik oyuncu etkisi | | `calculator_ver` | `String` default "v1.0" | Hesaplama versiyonu | | `updated_at` | `DateTime` | — | --- #### `predictions` — AI Tahmin Cache (3 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `match_id` | `String` PK FK → matches | Maç | | `prediction_json` | `Json` | Tam tahmin paketi (SingleMatchPredictionPackage) | | `created_at` | `DateTime` | — | | `updated_at` | `DateTime` | TTL: 6 saat | --- #### `ai_predictions_log` — AI Tahmin Loglama (0 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `Int` PK autoincrement | — | | `match_id` | `String` | Maç ID | | `model_version` | `String` | "v20plus.X" | | `recommended_bets` | `Json?` | Önerilen bahisler | | `confidence_score` | `Float?` | Güven skoru | | `is_resolved` | `Boolean` default false | Sonuçlandı mı? | | `actual_result` | `String?` | Gerçek sonuç | | `is_correct` | `Boolean?` | Doğru mu? | | `accuracy_score` | `Float?` | Doğruluk puanı | --- #### `users` — Kullanıcılar (1 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `UUID` PK | — | | `email` | `String` UNIQUE | — | | `password_hash` | `String` | bcrypt (12 rounds) | | `first_name` | `String?` | — | | `last_name` | `String?` | — | | `role` | `UserRole` default user | user / superadmin | | `subscription_status` | `SubscriptionStatus` default free | free / active / expired | | `subscription_expires_at` | `DateTime?` | — | | `encrypted_api_key` | `String?` | — | | `is_active` | `Boolean` default true | — | | `deleted_at` | `DateTime?` | Soft delete | **Limitler:** Free: 10 analiz + 3 kupon/gün, Active: 50 analiz + 10 kupon/gün --- #### `user_coupons` — Kullanıcı Kuponları (0 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `UUID` PK | — | | `user_id` | `String` FK → users | Kullanıcı | | `strategy` | `String` | SAFE, BALANCED, AGGRESSIVE, VALUE, MIRACLE | | `total_odds` | `Float` | Toplam oran | | `status` | `String` default "PENDING" | PENDING, WON, LOST | | `is_public` | `Boolean` default false | Herkes görebilir mi? | --- #### `user_coupon_items` — Kupon Bahisleri (0 kayıt) | Kolon | Tip | Açıklama | |-------|-----|----------| | `id` | `Int` PK autoincrement | — | | `coupon_id` | `String` FK → user_coupons | Kupon | | `match_id` | `String` FK → matches | Maç | | `selection` | `String` | "MS 1", "2.5 Üst", "KG Var" vb. | | `odd_at_time` | `Float` | Kayıt anındaki oran | | `is_correct` | `Boolean?` | Sonuç | --- #### `usage_limits`, `analyses`, `refresh_tokens`, `app_settings`, `translations` Bu tablolar standart destek tablolarıdır (kullanım limiti, analiz geçmişi, JWT refresh token, key-value ayarlar, çeviri verileri). --- ## 3. İlişki Diyagramı (ER) ``` Country 1──N League 1──N Match N──1 Team (home/away) │ ├──N OddCategory 1──N OddSelection 1──N OddsHistory ├──N MatchTeamStats N──1 Team ├──N MatchPlayerParticipation N──1 Player, N──1 Team ├──N MatchPlayerEvents N──1 Player (scorer, assist, out), N──1 Team ├──N MatchPlayerStats N──1 Player, N──1 Team ├──N MatchOfficial N──1 OfficialRole ├──1 MatchAiFeature └──1 Prediction User 1──N Analysis User 1──N UserCoupon 1──N UserCouponItem N──1 Match User 1──1 UsageLimit User 1──N RefreshToken League 1──N LiveMatch N──1 Team (home/away) ``` --- ## 4. Canlı Veritabanı İstatistikleri (2026-03-12) | Tablo | Kayıt | Boyut | |-------|-------|-------| | `odd_selections` | 8,511,132 | 1,070 MB | | `match_player_participation` | 3,342,839 | 1,077 MB | | `odd_categories` | 3,161,172 | 689 MB | | `match_player_events` | 1,453,227 | 356 MB | | `match_player_stats` | 344,688 | 120 MB | | `match_officials` | 340,824 | 75 MB | | `match_team_stats` | 310,991 | 91 MB | | `matches` | 236,859 | 100 MB | | `players` | 217,040 | 64 MB | | `teams` | 19,595 | 5.2 MB | | `leagues` | 1,505 | 760 KB | | **Toplam DB** | — | **3,658 MB** | ### Spor Dağılımı | Spor | Maç | Lig | Ort. Ev Skor | Ort. Dep. Skor | |------|-----|-----|-------------|---------------| | Futbol | 189,291 | 1,094 | 1.55 | 1.27 | | Basketbol | 47,568 | 304 | 84.36 | 81.57 | --- ## 5. AI Engine V20+ Tahmin Çıktısı (SingleMatchPredictionPackage) ```json { "model_version": "v20plus.X", "match_info": { "match_id", "match_name", "home_team", "away_team", "league", "match_date_ms" }, "data_quality": { "label": "HIGH|MEDIUM|LOW", "score": 0-1, "flags": [], "home_lineup_count", "away_lineup_count" }, "risk": { "level": "LOW|MEDIUM|HIGH|EXTREME", "score", "is_surprise_risk", "surprise_type", "warnings" }, "engine_breakdown": { "team", "player", "odds", "referee" }, "main_pick": { "market", "pick", "probability", "confidence", "odds", "raw_confidence", "calibrated_confidence", "min_required_confidence", "edge", "play_score", "playable", "bet_grade": "A|B|C|PASS", "stake_units", "decision_reasons" }, "value_pick": { same as main_pick, odds >= 1.60 }, "bet_advice": { "playable", "suggested_stake_units", "reason" }, "bet_summary": [{ "market", "pick", "raw_confidence", "calibrated_confidence", "bet_grade", "playable", "stake_units", "play_score", "reasons" }], "supporting_picks": [pick objects], "aggressive_pick": { "market", "pick", "probability", "confidence", "odds" }, "scenario_top5": [{ "score", "prob" }], "score_prediction": { "ft", "ht", "xg_home", "xg_away", "xg_total" }, "market_board": { "MS": {pick, confidence, probs}, "DC", "OU15", "OU25", "OU35", "BTTS", "HT", "HTFT": {probs: {"1/1": n, ...}} }, "reasoning_factors": ["..."] } ``` --- ## 6. API Endpointleri (50 Toplam) ### Auth (4) — Public - `POST /api/auth/register` — Kayıt ol - `POST /api/auth/login` — Giriş yap - `POST /api/auth/refresh` — Token yenile - `POST /api/auth/logout` — Çıkış yap ### Matches (4) — Public - `GET /api/matches` — Maç listesi (paginated, matches tablosundan) - `POST /api/matches/query` — Gelişmiş maç sorgusu (sport, league, status, date, team filtresi, live_matches tablosundan) - `GET /api/matches/leagues/active` — Aktif ligler (cached 1dk) - `GET /api/matches/:id` — Maç detayı (kadro, stat, oran, olaylar) ### Leagues (8) — Public - `GET /api/leagues` — Tüm ligler - `GET /api/leagues/:id` — Lig detay - `GET /api/leagues/countries` — Ülke listesi - `GET /api/leagues/countries/:id` — Ülke detay + ligleri - `GET /api/leagues/teams/search` — Takım arama - `GET /api/leagues/teams/:id` — Takım detay - `GET /api/leagues/teams/:id/matches` — Takım son maçları - `GET /api/leagues/teams/h2h` — Head-to-head ### Coupon (6) — Mixed - `POST /api/coupon/analyze-match` — Tekil maç analizi (Public) - `POST /api/coupon/daily-banko` — Günün bankosu (Public) - `POST /api/coupon/suggest` — Akıllı kupon öner (Public) - `POST /api/coupon/create` — Kupon kaydet (Auth) - `GET /api/coupon/my-stats` — Kullanıcı istatistikleri (Auth) - `GET /api/coupon/history` — Kupon geçmişi (Auth) ### Predictions (7) — Requires Redis - `GET /api/predictions/health` — AI Engine health - `GET /api/predictions/upcoming` — Yaklaşan tahminler - `GET /api/predictions/value-bets` — EV+ fırsatları - `GET /api/predictions/history` — Tahmin geçmişi - `GET /api/predictions/:matchId` — Tekil tahmin (cached 6 saat) - `POST /api/predictions/generate` — Tahmin üret - `POST /api/predictions/smart-coupon` — Smart Coupon ### Admin (11) — Superadmin - Kullanıcı CRUD, rol/abonelik güncelleme, ayarlar, analytics ### Analysis (2) — Auth - `POST /api/analysis/analyze-matches` — Çoklu maç analizi - `GET /api/analysis/history` — Analiz geçmişi ### Users (5) — Auth - CRUD + restore ### Health (3) — Public - `GET /api/health` — Readiness - `GET /api/health/live` — Liveness - `GET /api/health/detail` — Detaylı sağlık --- ## 7. Cron/Zamanlanmış Görevler | Görev | Cron | Açıklama | |-------|------|----------| | `fetchLiveMatches()` | `*/15 * * * *` | Mackolik API'den futbol maçlarını çek → live_matches | | `fetchOddsForPreMatches()` | `*/15 * * * *` | Başlamamış maçların oranlarını çek | | `fetchBasketballMatches()` | Manuel | Basketbol maçlarını çek | | `updateLiveScores()` | `*/15 * * * *` | Canlı maç skorlarını güncelle | | `finalizeFinishedMatches()` | `*/30 * * * *` | Bitmiş maçları live_matches → matches'e migrate et | | `resetUsageLimits()` | `0 3 * * *` | Günlük kullanım limitlerini sıfırla | | `cleanupOldData()` | `0 4 * * *` | 30 günlük AI logları sil | | `checkSubscriptions()` | `0 0 * * *` | Süresi dolmuş abonelikleri expired yap | --- ## 8. Standart API Response Formatı ```json { "success": true, "status": 200, "message": "Success", "data": { ... }, "errors": [] } ``` **Not:** Global Exception Filter tüm hataları HTTP 200 olarak döner, gerçek status body içindedir.