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
+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.