Add backtest pipeline, betting_brain filters, score coherence + social v3

betting_brain.py:
- HARD_MIN_SAMPLES=50 floor for calibrator bypass
- ev_edge < 0 + >= 0.20 hard vetoes
- BTTS muted (grid search found no profitable config)
- Per-market optimal envelopes (MS, OU25)
- Score coherence filter: main_pick must agree with score prediction
- HTFT reversal cross-check for MS picks

feature_builder.py / data_loader.py:
- Real home/away_position from data (was hardcoded 10)
- Cup detection wired into UpsetEngine
- _estimate_league_position with 300-day season filter

New scripts:
- diagnostic_backtest.py: per-bet diagnostic backtest with loss patterns
- optimize_filters.py: grid search per-market optimal thresholds
- analyze_backtest_csv.py: root-cause hypothesis testing on CSV
- compare_backtests.py: side-by-side validation with verdict
- test_score_coherence.py: smoke test for coherence filter (20/20 pass)

Reports:
- diagnostic_backtest_20260525_024437 (50-match smoke)
- diagnostic_backtest_20260525_035649 (1000-match in-sample)
- filter_optimization_patch.json (grid search winners per market)

Social poster v3:
- satori + resvg HTML/CSS rendering pipeline
- Twemoji football/basketball + flag SVGs
- caption SEO: 12 curated hashtags per post
- image SEO: descriptive filenames + .json metadata sidecar
- /health, /preview-png, /run-now endpoints

Docs:
- mds/SESSION_HANDOFF.md: full session state for cross-machine continuity
- mds/SOCIAL_POSTER_SETUP.md: API keys + test commands

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 20:43:28 +03:00
parent b619c2454a
commit 988ee2f50d
36 changed files with 5268 additions and 46 deletions
+261
View File
@@ -0,0 +1,261 @@
# SESSION HANDOFF — iddaai sistem durumu
**Son güncelleme**: 2026-05-25 ~20:30
**Hedef**: Başka makinede / yeni Claude session'ında bu doc tek başına okunup işin nerede kaldığı anlaşılabilmeli.
---
## 🎯 Üst-seviye hedef
Sistem **maç başı-1 saat** kullanıcı tetiklemesiyle çalışacak. Bahis uzmanı seviyesinde:
- **main_pick + value_pick** (sistemin önerdiği)
- **Tüm market olasılıkları** (MS, HT, OU05-45, BTTS, OE, DC, HTFT, HCAP, Cards, Corners)
- **Net HT + FT skoru** + **Top-5 olası skor dağılımı**
- **Evidence panel**: lineup impact, son 5 maç, h2h, hakem profili, benzer-oran-band geçmişi
Ürün modeli: hem user kendi bahisini oynar, hem sistem para kazanırsa abonelik satılır.
Hedef ROI: **≥%10**. Günde **3-5 kaliteli bahis**.
Detaylı requirements doc: bu dosyanın altında, "Requirements Spec" bölümü.
---
## 🟢 Şu an arka planda KOŞAN işler
### 1. Validation backtest (LOCAL — bu laptop)
- **Script**: `ai-engine/scripts/diagnostic_backtest.py`
- **Komut**: `python scripts/diagnostic_backtest.py --start 2026-05-01 --end 2026-05-14 --max-matches 1500`
- **Log**: `ai-engine/validation_full.log` (OneDrive senkronize)
- **Çıkış**: bittiğinde `ai-engine/reports/diagnostic_backtest_<timestamp>.{csv,json,txt}`
- **Tahmini bitiş**: 2026-05-25 ~22:00 (yaklaşık)
- **Amaç**: Yeni kodla (calibrator + ev_edge veto + envelope + coherence + BTTS mute) **out-of-sample** doğrulama
- **Risk**: Laptop uyursa ölür. Bitmesini beklemen lazım VEYA partial sonuçla devam.
```powershell
# Status check (kendin)
$log='C:\Users\fahri\OneDrive\المستندات\GitHub\iddaai\iddaai-be\ai-engine\validation_full.log'
Select-String $log 'rate=|Outputs:' | Select-Object -Last 3 | ForEach-Object {$_.Line}
```
### 2. Feeder historical scan (REMOTE — Pi server)
- **Konum**: SSH @ haruncan@95.70.252.214:2222 → docker container `iddaai-be` → pm2
- **PM2 process**: `feeder-historical` (id=1)
- **Log rotation**: pm2-logrotate kurulu (max 30MB/dosya, 3 dosya, gzip)
- **Davranış**: 2026-05-03'ten geriye 2023-06-01'e kadar mackolik'ten odds/lineup patch
- **Otomatik restart**: 502 olunca 30 sn delay sonra restart (max 1000 kez)
- **Beklenen süre**: 24-72 saat
```bash
# Status (kendin SSH'la)
sudo docker exec iddaai-be pm2 list
sudo docker exec iddaai-be pm2 logs feeder-historical --lines 30 --nostream
```
---
## 📝 Bu seansta yapılan KOD değişiklikleri
Hepsi local repo'da, OneDrive senkronize edecek, başka makinede pull etmesen de açtığında orada olacak.
### A. Settlement / data layer
| Dosya | Değişiklik |
|---|---|
| `iddaai-be/prisma.config.ts` | `.env` fallback ekledim (`.env.local` üstüne) — `prisma generate` çalışsın diye |
| `iddaai-be/src/tasks/prediction-settlement.market-resolver.ts` | DC parser ayraçsız "1X/X2/12" kabul ediyor + HT_OU05/HT_OU15/HT_OU25 resolver eklendi |
| `iddaai-be/src/tasks/feature-enrichment.task.ts` **(YENİ)** | Cron 08:15 — eksik football_ai_features row insert + odds_movement SQL backfill |
| `iddaai-be/src/tasks/python-enrichment.task.ts` **(YENİ)** | Cron 08:25 — Python `enrich_ai_features.py` subprocess |
| `iddaai-be/src/tasks/tasks.module.ts` | İki yeni task register |
| `iddaai-be/src/scripts/run-feature-enrichment.ts` **(YENİ)** | Manuel one-shot trigger |
### B. AI engine — betting brain
`iddaai-be/ai-engine/services/betting_brain.py` — büyük revizyon:
- **HARD_MIN_SAMPLES = 50** floor (calibrator bypass <50 sample)
- **`ev_edge < 0.0` HARD VETO** (`negative_ev_edge`)
- **`ev_edge >= 0.20` HARD VETO** (`ev_edge_too_high_trap`)
- **`MUTED_MARKETS = {"BTTS"}`** — backtest no profitable config bulduğu için
- **`MARKET_OPTIMAL_FILTERS`** — MS ve OU25 için grid-search'ten gelen optimal envelope
- **`_score_consistent_markets()`** — skor tahminine uymayan picks elimine
- **`judge()` score coherence filter** — main_pick coherent set'ten seçilir
- **HTFT reversal cross-check** — Man City 1/2 senaryosu
### C. AI engine — model & calibration
| Dosya | Değişiklik |
|---|---|
| `ai-engine/models/calibration.py` | HARD_MIN_SAMPLES floor + sample-weighted blend formülü değişti |
| `ai-engine/models/calibration/*.pkl` | **10 calibrator retrain** (ms_home/draw/away, ou15/25/35, btts, ht_home/draw/away) — 4989-5000 sample her biri |
### D. AI engine — orchestrator feature builder
`ai-engine/services/orchestrator/feature_builder.py`:
- Hardcoded `home_position=10, away_position=10` → real `data.home_position` kullanılıyor
- Cup detection upper'a taşındı, `is_cup_match` UpsetEngine'e geçiyor
- Total teams parametresi UpsetEngine'e geçiyor
`ai-engine/services/orchestrator/data_loader.py`:
- `_estimate_league_position` artık **sezon filtresi** (son 300 gün) kullanıyor
### E. AI engine — scripts (yeni)
| Dosya | Ne yapıyor |
|---|---|
| `ai-engine/scripts/diagnostic_backtest.py` | Per-bet diagnostic backtest (CSV+JSON+TXT output) |
| `ai-engine/scripts/analyze_backtest_csv.py` | Backtest CSV üzerinde root-cause hipotez testleri |
| `ai-engine/scripts/optimize_filters.py` | Grid search per-market optimal threshold |
| `ai-engine/scripts/compare_backtests.py` | İki CSV karşılaştırması verdict ile |
| `ai-engine/scripts/test_score_coherence.py` | Coherence filter smoke test (LAFC senaryosu) |
### F. Social poster modülü (NestJS)
| Dosya | Değişiklik |
|---|---|
| `src/modules/social-poster/social-poster.service.ts` | Cron 15→10 dk, window 10-60, MAX_POSTS_PER_RUN, getHealthStatus() |
| `src/modules/social-poster/image-renderer.service.ts` | SEO filename + metadata sidecar (.json) |
| `src/modules/social-poster/caption-generator.service.ts` | SEO hashtag stratejisi (12 küratör tag) |
| `src/modules/social-poster/social-poster.controller.ts` | `/health` public + `/preview-png/:matchId` + `/run-now` endpoints |
| `mds/SOCIAL_POSTER_SETUP.md` **(YENİ)** | Env vars + API key alma adımları + test komutları |
### G. Modern image rendering (deneme)
| Dosya | Açıklama |
|---|---|
| `src/scripts/render-social-card-v3.ts` | satori + resvg-js ile modern HTML→PNG rendering (Twemoji top + bayrak) |
| `src/modules/social-poster/assets/*.svg` | Twemoji futbol/basket/bayrak SVG'leri |
### H. Yapılan DB değişiklikleri (idempotent — tekrar koşturulursa sorun yok)
| İşlem | Etki |
|---|---|
| `football_ai_features` 4008+ satır backfill | Son 60 günün FT maçları için feature row var artık (calculator_ver=feature_enrichment_task_v1) |
| Python enrichment koştu | h2h, referee, possession, league_avg, implied_* hepsi gerçek değerlerle dolu (181,614+ satır enriched) |
| Calibrator dosyaları yazıldı | `ai-engine/models/calibration/*.pkl` overwritten |
---
## 📂 Önemli dosya konumları (OneDrive synced)
```
iddaai-be/
├── mds/
│ ├── SESSION_HANDOFF.md ← BU DOSYA
│ └── SOCIAL_POSTER_SETUP.md ← social poster env+keys
├── ai-engine/
│ ├── reports/ ← BACKTEST CIKTILARI
│ │ ├── diagnostic_backtest_*.csv,json,txt
│ │ └── filter_optimization_patch.json
│ ├── validation_full.log ← validation backtest canlı log
│ ├── diagnostic_backtest_run.log ← önceki backtest log
│ ├── enrichment_run3.log ← enrichment koşma log
│ └── calibration_run.log ← calibrator retrain log
├── public/predictions/ ← render edilmiş social card PNG/JSON
└── src/scripts/ ← tüm yeni script'ler
```
---
## 🔑 Erişim bilgileri
### Pi sunucu (feeder + prod stack)
- **SSH**: `haruncan@95.70.252.214:2222`
- **Şifre**: `M594xH%$iM&4MM`
- **Plink kullan**: `~/plink.exe -ssh -P 2222 -pw '<password>' -hostkey 'SHA256:iq0YVI/4J897sf9dkksI7QzetpLCD0l57ZMX4UissI8' haruncan@95.70.252.214`
- **Docker**: `iddaai-be`, `iddaai-ai-engine`, `iddaai-fe`, `iddaai-postgres`, `iddaai-redis`, `gitea`
### DB (uzak Postgres @ Pi)
- **SSH tunnel function**: `iddaai-db` PowerShell fonksiyonu (yerel makinedeki profile'da kayıtlı)
- **Tunnel: localhost:5432 → Pi:5432**
- **Connection string**: `postgresql://iddaai_user:IddaA1_S4crET!@localhost:5432/iddaai_db?schema=public`
- **MCP**: Claude'un postgres MCP'si bu tunnel üzerinden çalışıyor (restricted mode, read-only)
---
## 📊 BACKTEST sonuçları geçmişi
### Backtest #1 — In-sample grid search (2026-05-11 → 05-24, 1000 maç)
- **CSV**: `ai-engine/reports/diagnostic_backtest_20260525_035649.csv`
- **TXT**: `ai-engine/reports/diagnostic_backtest_20260525_035649.txt`
- **Toplam playable**: 524 bet
- **Hit rate**: %54.77
- **ROI**: **%16.73** (baseline kötü)
- **Grid-search'ten çıkan optimal filtreler (in-sample)**:
- MS: edge [-5%, +15%], V27 AGREE zorunlu → +%8.23 (21 bet)
- OU25: odds ≥ 1.80, edge ≤ +15% → +%28.91 (20 bet)
- BTTS: tüm config'lerde kayıp → MUTE
- **Aggregate optimize**: 95 bet, ROI +%2.16 (in-sample)
### Backtest #2 — Validation (2026-05-01 → 05-14, KOŞUYOR)
- **Bitince konum**: `ai-engine/reports/diagnostic_backtest_<yeni_timestamp>.{csv,json,txt}`
- **Karşılaştırma çalıştır**: `python scripts/compare_backtests.py` (otomatik en yeni 2'yi alır)
- **Beklenen sonuç**: ROI ≥ 0 → out-of-sample doğrulama BAŞARILI; in-sample overfit değil
---
## ❓ Backtest BİTTİĞİNDE yapılacak (yeni session'da bu kısımdan başla)
### 1. Sonucu oku
```powershell
cd C:\Users\fahri\OneDrive\المستندات\GitHub\iddaai\iddaai-be\ai-engine
Get-ChildItem reports\diagnostic_backtest_*.txt | Sort-Object LastWriteTime -Descending | Select-Object -First 1 | Get-Content
```
### 2. Karşılaştır
```powershell
python scripts\compare_backtests.py
```
Bu otomatik en yeni 2 backtest'i karşılaştırır, **VERDICT** verir:
- ✅ "FILTERS WORK" → ROI pozitif AND improved
- 🟡 "PARTIAL" → improved ama hâlâ negatif
- ❌ "OVERFITTING" → validation ROI collapse
### 3. Karara göre 2 yol
**Eğer ROI ≥ +%2 ve overfit yok:**
- `/sc:design` ile UI/API contract → Sprint 1
- Sprint 1: top-5 skor + evidence panel + "why" cümlesi
- Test edip prod'a aç
**Eğer ROI negatif veya overfit:**
- `analyze_backtest_csv.py` ile loss diagnostic
- Hangi market hâlâ kötü → tighten filter veya mute
- Calibrator recalibrate (özellikle BTTS dışındakiler için yeni sample)
- Tekrar backtest
---
## ⚠️ Bilinen açık problemler / sorular
1. **Coherence filter validate edilmedi production-side** — smoke test 20/20 ama gerçek production data ile karşılaştırma yok
2. **Lineup-overlap last-5 hesabı** — yazılmadı, requirements doc'ta F8 var
3. **Skor top-5 distribution** — Poisson zaten hesaplıyor, surface edilmedi (UI tarafı)
4. **"Why" cümlesi main_pick'te** — boş, doldurulması gerek
5. **Cards/Corners/RED CARD model** — yok, "henüz desteklenmiyor" placeholder ile bırak (kullanıcı onayladı: mevcut market'ler sağlamlaşsın)
6. **Orphan match_id 51 satır**`prediction_runs` içinde, `matches`'ta yok. Sample noise, geçiştirilebilir.
7. **opening_value feeder bug**`odds_movement_*` SQL yazıyor ama tüm değerler 0 (opening == closing). Feeder upstream sorun. Düşük öncelik.
---
## 🚦 Yeni Claude session'ında ilk komut
```
Bu projeye yeni bağlandım. Lütfen aşağıdaki dosyayı oku ve bana proje durumunu özet ver:
C:\Users\fahri\OneDrive\المستندات\GitHub\iddaai\iddaai-be\mds\SESSION_HANDOFF.md
Sonra validation backtest'in sonucuna bak:
- C:\Users\fahri\OneDrive\المستندات\GitHub\iddaai\iddaai-be\ai-engine\reports\
içindeki en yeni diagnostic_backtest_*.txt dosyasını oku
- compare_backtests.py script'ini koş, verdict göster
- Verdict'e göre sonraki adımı öner
```
Buradan devam eder. Tüm context bu doc'ta + dosyalarda + DB'de.
---
## 🛠️ Requirements spec (sıkıştırılmış)
**Ürün**: UI-tetikli per-match analiz, bahis uzmanı seviyesi
**Trigger**: User tıklar, on-demand
**Output**: main_pick + value_pick + tüm market olasılıkları + tek HT/FT skoru + top-5 skor dağılımı + evidence panel
**Kapsam**: Mevcut market'ler sağlamlaştırılır, yeni market eklenmez (kullanıcı onayı)
**Quality bar**: Calibration sapması ±2-5pp per market, NaN yok, response <3sn
**Validation**: Out-of-sample backtest (1500 maç, May 1-14) — KOŞUYOR
---
**SON NOT**: Backtest'in TAMAMLANMASINI bekle (~22:00). Laptop'u kapatma. Bittiğinde OneDrive senkronize eder, başka makinede otomatik orada olur. Yeni session'da bu dosyayı oku, sonuçlara bak, devam et.
+212
View File
@@ -0,0 +1,212 @@
# Social Poster — Setup & Operations
Otomatik tahmin kartı üretip Twitter / Facebook / Instagram'a postlayan modül.
Cron her **10 dakikada bir** çalışır, **yaklaşan 10-60 dk içindeki maçları**
yakalar, AI Engine'den tahmin alır, 1080×1080 görsel üretir, caption üretir,
3 platforma post eder.
## 1) ENV değişkenleri
`.env`'ye ekle:
```bash
# Master switch — false ise cron çalışmaz, manual endpoint'ler de boş döner.
SOCIAL_POSTER_ENABLED=true
# Hangi sporlar (virgüllü liste). Varsayılan: football,basketball
SOCIAL_POSTER_SPORTS=football,basketball
# Yaklaşan maç penceresi (dakika). Varsayılan 10-60.
SOCIAL_POSTER_WINDOW_MIN=10
SOCIAL_POSTER_WINDOW_MAX=60
# Tek cron koşusunda kaç maç post edilir (rate-limit koruması). Varsayılan 5.
SOCIAL_POSTER_MAX_PER_RUN=5
# Public base URL — Instagram media upload için fotoğrafın HTTPS'ten erişilebilir
# olması ŞART. Localhost ile IG çalışmaz; production domain veya ngrok kullan.
APP_BASE_URL=https://api.iddaai.com
# AI Engine URL (orchestrator)
AI_ENGINE_URL=http://localhost:8000
# ─── Twitter / X ───
TWITTER_API_KEY=...
TWITTER_API_SECRET=...
TWITTER_ACCESS_TOKEN=...
TWITTER_ACCESS_SECRET=...
# ─── Meta (Facebook + Instagram) ───
META_PAGE_ACCESS_TOKEN=... # FB Page'in long-lived access token'ı
META_PAGE_ID=... # FB Page numeric ID
META_IG_USER_ID=... # IG Business account numeric ID
META_GRAPH_API_VERSION=v25.0 # opsiyonel
# ─── Caption AI (opsiyonel — yoksa template caption kullanılır) ───
ENABLE_GEMINI=true
GEMINI_API_KEY=...
GEMINI_MODEL=gemini-1.5-flash
# Veya local Ollama:
OLLAMA_BASE_URL=http://localhost:11434
SOCIAL_POSTER_OLLAMA_MODEL=llama3.1
```
## 2) API anahtarlarını alma
### Twitter / X
1. https://developer.x.com → Project + App oluştur
2. App'ın "Keys and tokens" → "API Key", "API Secret" al
3. User authentication settings → "Read and write and Direct Message"
4. "Access Token and Secret" generate et (bu hesap adına post eder)
5. **Free tier**: 1500 tweet/ay, 50 post/24 saat — 10 dk'lık cron'la günde
~144 koşu × 5 post = 720 potansiyel post → free tier yetmez, **Basic plan
($200/ay)** lazım. Cron interval'i 30 dk'ya alıp 50/gün kalmak istersen
`@Cron("*/30 * * * *")` olarak değiştir.
### Meta (Facebook + Instagram)
1. https://developers.facebook.com → App oluştur (type: Business)
2. Facebook Page bağla (mevcut sayfan yoksa oluştur)
3. Instagram Business hesabını Facebook Page'e bağla
4. Graph API Explorer'dan **page access token** al (User token değil!)
5. Long-lived token'a çevir (60 gün geçerli, refresh edilebilir)
6. **Page ID**: `https://graph.facebook.com/me/accounts?access_token=...`
7. **IG User ID**: `graph.facebook.com/{pageId}?fields=instagram_business_account&access_token=...`
8. Required permissions: `pages_show_list`, `pages_manage_posts`,
`pages_read_engagement`, `instagram_basic`, `instagram_content_publish`
### Gemini (caption AI — opsiyonel)
- https://aistudio.google.com → API key (free tier yeterli, günde ~1500 istek)
- `ENABLE_GEMINI=true` + `GEMINI_API_KEY=...`
- Gemini yoksa template caption kullanılır (yine SEO'lu, sadece daha statik)
## 3) Test komutları
```bash
# Servisi başlat
npm run start:dev
# Health endpoint — auth gerekmez
curl http://localhost:3005/social-poster/health | jq
# Manuel preview (görsel + JSON) — superadmin token gerekir
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3005/social-poster/preview/<matchId>
# Görseli tarayıcıda direkt göster
open http://localhost:3005/social-poster/preview-png/<matchId>?token=$TOKEN
# Manuel post (tek maç, tüm platformlara) — superadmin token
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:3005/social-poster/post/<matchId>
# Cron'u beklemeden full sweep koş — superadmin token
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:3005/social-poster/run-now
```
## 4) SEO özellikleri
### Image dosya adı (SEO)
Eskiden: `prediction_basketball_xyz12345_1716595200000.jpg` (opaque)
Yeni: `sampiyonlar-ligi-unicaja-malaga-vs-aek-20260525.jpg` (Google indexable)
### Yan dosya: metadata sidecar
Her görsel için aynı dizinde `.json`:
- `title`, `description`, `og:*`, `schema.org SportsEvent`, `picks[]`
- Sayfada `<head>` Open Graph + Twitter Cards bu dosyadan beslenir
- Schema.org markup zengin sonuç (Google rich snippet) sağlar
### Caption (SEO + hashtags)
Her post 12'ye kadar küratör hashtag içerir:
- Marka: `#MaçTahmini #İddaa #BugünMaç`
- Spor: `#Futbol #Basketbol #FutbolTahmin`
- Lig: `#SüperLig #PremierLeague #ŞampiyonlarLigi #EuroLeague #NBA`
- Bölge: `#Türkiye #İngiltere #İspanya`
- Takım: `#Galatasaray #Fenerbahçe`
- Gün: `#PazarTahmini #CumartesiTahmini`
- Market: `#AltÜst #KGVar #ÇifteŞans #MaçSonucu #Handikap`
LLM (Gemini) caption üretiyorsa hashtag'leri çıkarır; sistem kendi
hashtag set'ini ekler. Tutarlı index için tek kaynak.
## 5) İzleme
```bash
# Health endpoint — periyodik monitor
curl http://localhost:3005/social-poster/health | jq
# Sample output:
{
"enabled": true,
"sports": ["football", "basketball"],
"window_min_minutes": 10,
"window_max_minutes": 60,
"max_posts_per_run": 5,
"top_leagues_loaded": 42,
"posted_match_count": 137,
"last_run_at": "2026-05-25T03:10:00.123Z",
"last_run_result": { "posted": 4, "skipped": 1, "errors": 0 },
"twitter_available": true,
"meta_facebook_available": true,
"meta_instagram_available": true,
"ai_engine_url": "http://localhost:8000",
"app_base_url": "https://api.iddaai.com"
}
```
`posted_match_count` `storage/social-poster-posted.json`'dan okunur, son 500
match ID hafızada — aynı maçı 2 kere post etmez.
## 6) Rate limit ipuçları
| Platform | Free limit | Tedbir |
|---|---|---|
| Twitter | 50 post/24 saat | `SOCIAL_POSTER_MAX_PER_RUN=2` + cron `*/30` → günde ~96 |
| Facebook | ~200 post/saat (Page) | Default config rahat |
| Instagram | 25 post/24 saat | `MAX_PER_RUN=1` + cron `*/60` → günde 24, sınırın hemen altında |
IG en sıkı sınır — production için **IG ayrı cron'da daha seyrek post**
yapılması önerilir (kod henüz tek cron, ileride ayrılabilir).
## 7) Hangi maçlar seçilir?
`top_leagues.json` dosyasındaki league_id'ler içinden:
- Şu anda 10-60 dakika sonra başlayacak
- Daha önce post edilmemiş
- `sport: football, basketball` filtresi geçen
`top_leagues.json` yoksa **tüm liglerden** maç seçer (hacmi yüksek tutar).
Sadece premium ligler postlamak istersen dosyayı doldur.
## 8) Görsel formatı
- **Boyut**: 1080×1080 (Instagram square — Twitter da kabul ediyor)
- **Format**: JPEG, quality 94
- **Tema**: Sport'a göre değişir — football yeşil, basketball turuncu
- **İçerik**: Lig logosu + ülke bayrağı, takım logoları + adları, HT skor,
FT skor, top 3 tahmin (confidence ile), risk badge
Card layout `image-renderer.service.ts` içinde — value-pick yıldız ile
işaretli, scenario top 3 listelenir, footer alt'ta tarih + brand.
## 9) Sık sorular
**Q: Görseller nereye yazılıyor?**
`public/predictions/` (gitignored). ServeStatic ile `/predictions/<file>.jpg`
URL'inden erişilir.
**Q: Eski görseller temizleniyor mu?**
Hayır — manuel temizlik gerekir. Cron eklemek istersen `LimitResetterTask`
örneği var.
**Q: AI Engine çalışmıyorsa ne olur?**
Cron tahmin alamaz, log'a hata düşer, devam eder. Sonraki koşuda dener.
**Q: Bir maç 2 kere post ediliyor mu?**
Hayır — `postedMatchIds` set'i Match ID bazında dedup yapar, dosyaya yazılır
(restart-safe).
**Q: Caption Gemini olmadan ne kadar iyi?**
Template caption tüm bilgileri + 12 hashtag içerir. SEO açısından yeterli,
sadece anlatım daha statik. Gemini ile her post için özgün metin.