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:
@@ -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.
|
||||
Reference in New Issue
Block a user