988ee2f50d
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>
213 lines
7.7 KiB
Markdown
213 lines
7.7 KiB
Markdown
# 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.
|